From c40ad22300204250f84f8fa07462b48a51dc100b Mon Sep 17 00:00:00 2001 From: "Constantine.mirin" Date: Fri, 13 Nov 2026 13:55:44 +0100 Subject: [PATCH 1/3] fix(schemas): spell three experimental 3.3 values as their counterparts do Three tokens are spelled two ways for one concept, and in each the side that changes is experimental, added in 3.3 and only in the 3.3 betas, so it is renamed in place before the RC with no stable release touched. - execution-readiness-status: input_required becomes input-required, the task-status spelling, which follows A2A's task states. - creative-change-kind: click_through becomes clickthrough, the url_type value the change kind names. - delivery-projection-end-state: underdelivery becomes under_delivery, the offer-reason spelling. DR-0029 is amended to match. Updates the readiness and projection schema descriptions, index.json, the media-buy specification, list_products, get_media_buy_delivery, sync_creatives and get_adcp_capabilities docs, the experimental status page, the training agent's change classifier, and the tests. The additive-minor check compares against 3.2.x, which has none of the three enums, so no baseline entry is needed. The 3.3 release pages are left to #8883. Part of #8901 --- .changeset/experimental-token-spellings.md | 13 +++++++++++++ docs/creative/task-reference/sync_creatives.mdx | 4 ++-- docs/media-buy/specification.mdx | 6 +++--- .../task-reference/get_media_buy_delivery.mdx | 6 +++--- docs/media-buy/task-reference/list_products.mdx | 2 +- docs/protocol/get_adcp_capabilities.mdx | 2 +- docs/reference/experimental-status.mdx | 6 +++--- .../DR-0029-delivery-projection-advisory.md | 2 +- server/src/training-agent/live-creative-updates.ts | 8 ++++---- server/tests/unit/live-creative-updates.test.ts | 2 +- static/schemas/source/core/delivery-projection.json | 2 +- .../core/execution-requirement-readiness.json | 2 +- .../source/core/product-execution-readiness.json | 2 +- .../schemas/source/enums/creative-change-kind.json | 4 ++-- .../source/enums/delivery-projection-end-state.json | 4 ++-- .../source/enums/execution-readiness-status.json | 4 ++-- static/schemas/source/index.json | 6 +++--- tests/creative-live-updates.test.cjs | 2 +- tests/delivery-projection.test.cjs | 4 ++-- tests/execution-readiness.test.cjs | 12 ++++++------ 20 files changed, 53 insertions(+), 40 deletions(-) create mode 100644 .changeset/experimental-token-spellings.md diff --git a/.changeset/experimental-token-spellings.md b/.changeset/experimental-token-spellings.md new file mode 100644 index 00000000000..0f48bfced95 --- /dev/null +++ b/.changeset/experimental-token-spellings.md @@ -0,0 +1,13 @@ +--- +"adcontextprotocol": minor +--- + +Give three experimental 3.3 values the spelling their stable or sibling counterpart already uses (adcp#8901). Each was spelled two ways for one concept, and the side that changes is experimental, added in 3.3 and only in the 3.3 betas. + +- **`enums/execution-readiness-status.json` (`media_buy.execution_readiness`, #7934, beta.1 to beta.5):** `input_required` becomes `input-required`, the spelling of `enums/task-status.json`, which follows A2A's task states. +- **`enums/creative-change-kind.json` (`creative.live_updates`, #8488, beta.3 to beta.5):** `click_through` becomes `clickthrough`, the `url_type` value the change kind names in `enums/url-asset-type.json`. +- **`enums/delivery-projection-end-state.json` (`media_buy.delivery_projection`, #8043, beta.0 to beta.5):** `underdelivery` becomes `under_delivery`, the spelling of `enums/offer-reason.json`. DR-0029 is amended to the new spelling. + +**This changes 3.3.0-beta.0 through beta.5; no stable release has these enums.** The values are renamed in place, with no alias: they are experimental, and the additive-minor check compares against 3.2.x, which has none of the three files. The betas are immutable, so a seller or buyer built from a beta bundle sends and accepts the new spelling: `readiness_status` and the product rollup, `creative.live_updates.in_place[].change`, `staged_update.change_kinds[]` and the `CREATIVE_LIVE_UPDATE_UNSUPPORTED` details, and `projection.projected_end_state`. The training agent classifies creative changes with `clickthrough`. + +The consumed changesets for these features stay in `.changeset/pre/` as history. diff --git a/docs/creative/task-reference/sync_creatives.mdx b/docs/creative/task-reference/sync_creatives.mdx index 69d5738ba48..60407ff7497 100644 --- a/docs/creative/task-reference/sync_creatives.mdx +++ b/docs/creative/task-reference/sync_creatives.mdx @@ -443,7 +443,7 @@ A creative is **live** when its `status` is `approved` and it has at least one a | Kind | Changed fields | |---|---| -| `click_through` | A `url` asset with `url_type: "clickthrough"` | +| `clickthrough` | A `url` asset with `url_type: "clickthrough"` | | `trackers` | A `url` asset with `url_type` `tracker_pixel` or `tracker_script`, or a `pixel_tracker`, `vast_tracker`, or `daast_tracker` asset | | `format` | `format_id`, `format_kind`, or `format_option_ref` | | `asset_content` | Any other field of [canonical revision content](#creative-revisions), including future ones: any other asset (markup or VAST that embeds clicks or trackers included), `component_assets`, `localization`, `representation_selection`, `inputs`, `provenance`, `rights`, `industry_identifiers` | @@ -479,7 +479,7 @@ Modes rank from least to most disruptive: `uninterrupted`, `staged` with `on_rej **Staged update notifications.** A staged outcome is not a status transition, so it is not reported on `creative.status_changed`: an `approved → approved` fire would fail the closed validator of a buyer on a release that predates live updates. The seller reports it on the separate experimental `creative.staged_update_changed` event ([`creative-staged-update-changed-webhook.json`](https://adcontextprotocol.org/schemas/latest/creative/creative-staged-update-changed-webhook.json)), and only to `notification_configs[]` entries whose `event_types[]` include it. A buyer that does not register it never receives it and reads the outcome from `staged_update` on [`list_creatives`](/docs/creative/task-reference/list_creatives#staged-update-readback). See [lifecycle webhooks](/docs/creative/specification#lifecycle-webhooks). -**In every mode**, one `creative_id` is one creative. An accepted or promoted change applies to every assignment of the creative on every media buy, and the seller MUST NOT leave a prior version serving alongside the new one beyond the declared `max_propagation_seconds` (cache, SSAI, and exchange propagation). An assignment's own [`landing_page_url`](#assignment-landing-pages) or [`trackers`](#assignment-trackers) still apply on that assignment: a library `click_through` change does not reach an assignment that overrides it. +**In every mode**, one `creative_id` is one creative. An accepted or promoted change applies to every assignment of the creative on every media buy, and the seller MUST NOT leave a prior version serving alongside the new one beyond the declared `max_propagation_seconds` (cache, SSAI, and exchange propagation). An assignment's own [`landing_page_url`](#assignment-landing-pages) or [`trackers`](#assignment-trackers) still apply on that assignment: a library `clickthrough` change does not reach an assignment that overrides it. **Refusal.** A declaring seller fails an item it does not apply in place to a live creative with [`CREATIVE_LIVE_UPDATE_UNSUPPORTED`](/docs/building/verification/compliance-catalog#error-code-creative-live-update-unsupported), and leaves the creative, its review state, and its serving unchanged. Other items in the request still process. `error.details` SHOULD conform to [`error-details/creative-live-update-unsupported.json`](https://adcontextprotocol.org/schemas/latest/error-details/creative-live-update-unsupported.json): `refused_changes` and `live_package_ids`. To make the change anyway, sync it as a new `creative_id` and swap it in with an `assignment_operations` `replace` on each live package. A seller that does not declare `creative.live_updates` is not covered by this section: the original rule applies unchanged (creatives on active, non-paused packages cannot be updated, and the refusal is [`INVALID_STATE`](/docs/building/verification/compliance-catalog#error-code-invalid-state)), so buyers handle both codes. diff --git a/docs/media-buy/specification.mdx b/docs/media-buy/specification.mdx index 97e78358703..2d60e7cb4bb 100644 --- a/docs/media-buy/specification.mdx +++ b/docs/media-buy/specification.mdx @@ -452,7 +452,7 @@ Statuses, per requirement and rolled up per product: | `ready` | The account has exactly one suitable resource (for a connection, the one the seller would use is connected) | None beyond binding (below) | | `setup_required` | The account has none, or the connection is `missing`, `pending`, `expired`, or `revoked` | Create or restore it, for example with `sync_event_sources`, `sync_catalogs`, or `connection_state.authorization_url` | | `selection_required` | The account has several suitable resources | Choose one from `candidates[]` and bind it | -| `input_required` | The seller cannot discover a suitable resource from the account | Supply one on the request, such as an inline catalog | +| `input-required` | The seller cannot discover a suitable resource from the account | Supply one on the request, such as an inline catalog | | `unknown` | The seller could not read the account's resources | Depends on `unknown_reason`: `permission_denied` needs a human to fix the seller's grant on the account, `platform_unavailable` is worth retrying, `not_evaluated` means refetch fewer products. Meanwhile the purchase task decides | Rules: @@ -464,7 +464,7 @@ Rules: `execution_requirements` get an entry. A missing entry means not evaluated, not ready. - **Rollup.** A product's `readiness_status` is the first of `setup_required`, - `input_required`, `selection_required`, `unknown` that any requirement + `input-required`, `selection_required`, `unknown` that any requirement reports, else `ready`: the most blocking state the seller could establish. Buyers needing more MUST read `requirements[]`. - **Could not check is not lacking.** `unknown` carries `unknown_reason` @@ -494,7 +494,7 @@ Rules: `missing`, `expired`, or `revoked` connection SHOULD include `authorization_url` or `authorization_instructions`; `pending` MAY omit both. This slice does not list several identities or let the buyer choose among them. - `selection_required` and `input_required` never apply to connections. + `selection_required` and `input-required` never apply to connections. - **Account-scoped and advisory.** The map appears only when the request set `include_execution_readiness: true` and carried an `account` the caller is authorized for; otherwise the seller omits it (an `account` that does not diff --git a/docs/media-buy/task-reference/get_media_buy_delivery.mdx b/docs/media-buy/task-reference/get_media_buy_delivery.mdx index 005c9d6f340..fdb4f76f03d 100644 --- a/docs/media-buy/task-reference/get_media_buy_delivery.mdx +++ b/docs/media-buy/task-reference/get_media_buy_delivery.mdx @@ -756,7 +756,7 @@ The `by_package` array provides per-package delivery details with these key fiel "$schema": "https://adcontextprotocol.org/schemas/latest/core/delivery-projection.json", "projected_impressions": 1180000, "projected_spend": 9440.00, - "projected_end_state": "underdelivery", + "projected_end_state": "under_delivery", "basis": "trailing_daily_avg", "as_of": "2026-10-01T00:00:00Z" } @@ -764,7 +764,7 @@ The `by_package` array provides per-package delivery details with these key fiel | Field | Meaning | |---|---| -| `projected_end_state` | Required. One package-level judgment against the package's binding constraint, whichever of goal or budget it would reach first. `on_pace`: reaches it at flight end, within the seller's tolerance. `complete` (a projected early finish, unrelated to `delivery_status: completed`): reaches it before flight end, with the undelivered share of the flight below the seller's materiality threshold, so it simply finishes early. `early_exhaustion`: reaches it before flight end, with the undelivered share at or above the materiality threshold. `underdelivery`: falls short at flight end by more than tolerance. There is no over-delivery state: a package projected to exceed its constraint is projected to reach it early. | +| `projected_end_state` | Required. One package-level judgment against the package's binding constraint, whichever of goal or budget it would reach first. `on_pace`: reaches it at flight end, within the seller's tolerance. `complete` (a projected early finish, unrelated to `delivery_status: completed`): reaches it before flight end, with the undelivered share of the flight below the seller's materiality threshold, so it simply finishes early. `early_exhaustion`: reaches it before flight end, with the undelivered share at or above the materiality threshold. `under_delivery`: falls short at flight end by more than tolerance. There is no over-delivery state: a package projected to exceed its constraint is projected to reach it early. | | `projected_impressions`, `projected_spend` | Optional supporting numbers. Projected **lifetime** totals when delivery ends, counted from the package's start rather than `reporting_period.start`, so they include delivery already reported and are not comparable to the row's period `impressions` and `spend` unless the period covers the flight to date. Spend is in the package row's `currency`. A seller omits a volume it does not project; a projection with only an end state is valid. | | `basis` | Required. The method class: `trailing_daily_avg`, `trailing_hourly_avg`, `platform_model`, or `seller_defined`. It names the class, not the parameters. Window length, model name, and seasonality inputs belong in the projection's `ext`. `basis` is how the projection was sampled or modeled. It is not the commitment tier of [`forecast_method`](https://adcontextprotocol.org/schemas/v3/enums/forecast-method.json) (`estimate`, `modeled`, `guaranteed`), and a projection from actuals carries no delivery commitment at any `basis`. A client on an older schema version treats an unrecognized value like `seller_defined`. | | `as_of` | Required. The latest delivery data the projection was computed from. It is independent of `reporting_period` and can precede the response time because reporting lags. | @@ -780,7 +780,7 @@ Tolerance and materiality are the seller's own thresholds, which it SHOULD also | `projection.projected_end_state` | Expected `pacing_risk` on the same package | |---|---| | `on_pace`, `complete` | Not present | -| `underdelivery`, `early_exhaustion` | Present when the seller's tolerance treats the gap as material | +| `under_delivery`, `early_exhaustion` | Present when the seller's tolerance treats the gap as material | A seller MAY emit `projection` without advertising `pacing_risk`, and the reverse. The two share the seller's tolerance, so neither is a substitute for the other and a buyer MUST NOT infer one from the absence of the other. `pacing_risk` also covers over-delivery, which `projection` has no state for beyond early completion. Use `pacing_risk` to decide when to look, and `projection` for the number behind it. diff --git a/docs/media-buy/task-reference/list_products.mdx b/docs/media-buy/task-reference/list_products.mdx index bd0de2d8a45..b567d99db29 100644 --- a/docs/media-buy/task-reference/list_products.mdx +++ b/docs/media-buy/task-reference/list_products.mdx @@ -141,7 +141,7 @@ With `include_execution_readiness: true` and an `account`, a seller that lists ` |-------|------|-------------| | `version` | string | Opaque token for the account's readiness, independent of `feed_version` and `pricing_version`. Compare for equality only. | | `evaluated_at` | string (date-time) | When the seller read the account's resources. | -| `products` | object | Readiness keyed by `product_id`, for products on the page that declare `execution_requirements`. Each value has a rollup `readiness_status` (`ready`, `setup_required`, `selection_required`, `input_required`, `unknown`) and one `requirements[]` entry per declared requirement. A missing entry means not evaluated, not ready. | +| `products` | object | Readiness keyed by `product_id`, for products on the page that declare `execution_requirements`. Each value has a rollup `readiness_status` (`ready`, `setup_required`, `selection_required`, `input-required`, `unknown`) and one `requirements[]` entry per declared requirement. A missing entry means not evaluated, not ready. | Each `requirements[]` entry echoes the declared `requirement` and carries `readiness_status`, plus `binding_field`, `resolved` (when `ready`), `candidates[]` (when `selection_required`), `connection_state` (for `downstream_connection`), `unknown_reason` (when `unknown`), and `issues[]`. `binding_field` names the [`buy_products`](/docs/media-buy/task-reference/buy_products) path: `purchases[].optimization_goals[].event_sources[].event_source_id` for an event source and `purchases[].catalog_ids[]` for a catalog. A `candidates[].resource_id` or `resolved.resource_id` is directly usable there. `ready` does not waive binding, and `unknown` means the seller could not check, not that the account lacks the resource. See [per-account execution readiness](/docs/media-buy/specification#per-account-execution-readiness-experimental) for the full rules. diff --git a/docs/protocol/get_adcp_capabilities.mdx b/docs/protocol/get_adcp_capabilities.mdx index 28e60c80d02..496ae82b53d 100644 --- a/docs/protocol/get_adcp_capabilities.mdx +++ b/docs/protocol/get_adcp_capabilities.mdx @@ -1566,7 +1566,7 @@ Creative protocol capabilities. Only present if `creative` is in `supported_prot | `supports_assignment_trackers` | boolean | Added in 3.3; defaults to `false`. When `true`, accepts assignment-scoped `trackers` in `sync_creatives` assignment requests, fires them alongside the creative's own trackers or rejects the assignment, enforces shared format tracker-slot limits where the format declares tracker slots, and returns the assignment tracker list in readback. Requires `has_creative_library: true`. When `false` or absent, buyers MUST NOT send the field and the agent MUST reject it with [`UNSUPPORTED_FEATURE`](/docs/building/verification/compliance-catalog#error-code-unsupported-feature). Independent of landing-page override support. See [assignment trackers](/docs/creative/task-reference/sync_creatives#assignment-trackers). | | `supports_revisions` | boolean | When `true`, the library accepts buyer-authored immutable `revision_id` values and preserves them through sync echo, current readback, review attribution, and revision-homogeneous delivery rows. Requires `has_creative_library: true`. Does not imply revision history, rollback, or staged activation. | | `supports_assignment_landing_page` | boolean | Added in 3.3; defaults to `false`. When `true`, accepts assignment-scoped `landing_page_url` in `sync_creatives` assignment requests, serves the override or rejects the assignment, and reports the effective destination in assignment readback. Requires `has_creative_library: true`. When `false` or absent, buyers MUST NOT send the field and the agent MUST reject it with [`UNSUPPORTED_FEATURE`](/docs/building/verification/compliance-catalog#error-code-unsupported-feature). See [assignment landing pages](/docs/creative/task-reference/sync_creatives#assignment-landing-pages). | -| `live_updates` | object | Experimental; added in 3.3. Which kinds of change (`click_through`, `trackers`, `asset_content`, `format`) the agent applies in place to a live creative under the same `creative_id`, and how the creative serves while each is decided: `uninterrupted`, `staged` (the prior version serves until the change is approved; `on_rejection` is `keeps_prior` or `stops`), or `paused_for_review`. A floor: listed kinds are accepted on every live creative in the listed mode or a less disruptive one. Optional `max_propagation_seconds` bounds how long a prior version may still serve after a change applies. A declaring agent refuses other changes with [`CREATIVE_LIVE_UPDATE_UNSUPPORTED`](/docs/building/verification/compliance-catalog#error-code-creative-live-update-unsupported) and answers the exact outcome of one change on a [`sync_creatives` `dry_run`](/docs/creative/task-reference/sync_creatives#live-creative-updates). Requires `has_creative_library: true`. See [Live creative updates](/docs/creative/task-reference/sync_creatives#live-creative-updates). | +| `live_updates` | object | Experimental; added in 3.3. Which kinds of change (`clickthrough`, `trackers`, `asset_content`, `format`) the agent applies in place to a live creative under the same `creative_id`, and how the creative serves while each is decided: `uninterrupted`, `staged` (the prior version serves until the change is approved; `on_rejection` is `keeps_prior` or `stops`), or `paused_for_review`. A floor: listed kinds are accepted on every live creative in the listed mode or a less disruptive one. Optional `max_propagation_seconds` bounds how long a prior version may still serve after a change applies. A declaring agent refuses other changes with [`CREATIVE_LIVE_UPDATE_UNSUPPORTED`](/docs/building/verification/compliance-catalog#error-code-creative-live-update-unsupported) and answers the exact outcome of one change on a [`sync_creatives` `dry_run`](/docs/creative/task-reference/sync_creatives#live-creative-updates). Requires `has_creative_library: true`. See [Live creative updates](/docs/creative/task-reference/sync_creatives#live-creative-updates). | | `supports_assignment_merge` | boolean | When `true`, the agent accepts the `merge` operation in [`sync_creatives`](/docs/creative/task-reference/sync_creatives#merging-a-seller-created-copy) `assignment_operations`, which moves a seller-assigned creative's assignment on a package, with its serving object, approval, and delivery continuity, to the buyer's matching `creative_id`. Requires `has_creative_library: true` and `stable_creative_ids: true`, so the seller-created creative being merged has a stable ID. When `false` or absent, the agent rejects `merge` with `UNSUPPORTED_FEATURE` and buyers MUST NOT send it. | | `representation_resolution` | object | Destination-sales-agent opt-in to accept a complete `creative_representation_set` on [`build_creative`](/docs/creative/task-reference/build_creative), verify its revision digest, and select one compatible `representation_id` against that seller's current product and execution contract using an advertised deterministic strategy. Standalone creative agents cannot advertise seller-bound resolution. Absence means the caller selects before sending a seller-bound manifest; the agent cannot guess. See [creative representation sets](/docs/creative/representation-sets). | | `supported_formats` | object[] | Canonical creative operation catalog. New 3.2 producers MUST emit a stable `capability_id`, a full canonical `format` declaration, and explicit `operations` (`build`, `validate`, `preview`). Exact publisher support carries `{publisher_domain, format_option_id}` inside `format`; generic capabilities declare a satisfiable canonical parameter envelope. For 3.x compatibility, consumers accept entries without `capability_id` and interpret absent `operations` as `build`; such entries can be matched by contract but not selected through a capability-ID route. Replaces [`list_creative_formats`](/docs/creative/task-reference/list_creative_formats) in 3.2. | diff --git a/docs/reference/experimental-status.mdx b/docs/reference/experimental-status.mdx index c575d16f4a1..0846159e35c 100644 --- a/docs/reference/experimental-status.mdx +++ b/docs/reference/experimental-status.mdx @@ -123,20 +123,20 @@ Schemas marked with `x-status: experimental` are the authoritative source. The c | `creative.signal_fanout` | The `signal_conditions[]` and `selection_strategy` inputs on [`build_creative`](/docs/creative/task-reference/build_creative), the `creatives[].signal_condition` / `selection_strategy_applied` / `estimate.conditions_total` fields on its response, the `creative.multiplicity.supports_signal_fanout` / `max_signal_conditions_limit` / `selection_strategies` capability fields on `get_adcp_capabilities`, the `enums/creative-selection-strategy.json` enum, and the `SIGNAL_TARGETING_INCOMPATIBLE` error code (#5240 / #5304, folds #5262) | New signal-driven creative fan-out surface introducing a cross-agent reject-at-trafficking MUST (`SIGNAL_TARGETING_INCOMPATIBLE`) spanning creative agent + sales agent — not yet field-tested across parties. The numeric (`value_type: numeric`) condition-compatibility comparison (range-overlap vs exact-match) is still WG-open, and the `proximity` selection strategy's geo-input binding is not yet settled — both stay revisable under experimental. Condition identity itself is resolvable today (`signal_agent_segment_id` / `signal_ref` via `get_signals`), so there is no frozen-identifier problem. | | `media_buy.audience_activation` | The [`audience_activation`](/docs/media-buy/product-discovery/media-products#audience-activation) declaration on products, the `audience_targeting.supported_activation_methods` capability field on [`get_adcp_capabilities`](/docs/protocol/get_adcp_capabilities), the `audience_activation_methods` product filter on [`get_products`](/docs/media-buy/task-reference/get_products), and the `core/audience-activation-method.json` schema (#4324). The cluster also covers [external audience source references](/docs/media-buy/task-reference/sync_audiences#external-audience-sources-experimental) on `sync_audiences` — the `source` request field (`core/audience-source.json`), the response `source` echo with `access_status`/`columns_read`, and the `SOURCE_ACCESS_FAILED` error code (#6540). | New activation-path discovery surface (how audience data moves: dataset sharing, clean rooms, platform distribution, file transfer) — not yet field-tested across parties. Pattern-specific field shapes (`consumer_identities[]` qualifiers, `bind_expiry_days` semantics), the grantee-identified admissibility boundary, and filter matching semantics all need integration experience with real Snowflake/Databricks/LiveRamp pipelines before freezing. The runtime leg (#6540) may reshape the declaration fields it consumes. | | `media_buy.execution_requirements` | The experimental `Product.execution_requirements` declaration on [`get_products`](/docs/media-buy/task-reference/get_products) and [`list_products`](/docs/media-buy/task-reference/list_products) results (schema: `core/product-execution-requirement.json`), and the `error-details/execution-requirement-unmet.json` details shape for the resulting `VALIDATION_ERROR` on `create_media_buy` and `update_media_buy` (#7763) | First, account-independent half of the execution-readiness RFC. The requirement kinds and their binding points reuse existing request fields, but the vocabulary is expected to grow (lead forms, messaging destinations) and per-account readiness — which resources an account has and which one to use — is the separate `media_buy.execution_readiness` surface below and may reshape how requirements and readiness compose. | -| `media_buy.execution_readiness` | The `include_execution_readiness` request field and response-level `execution_readiness` map on [`list_products`](/docs/media-buy/task-reference/list_products#per-account-execution-readiness-experimental) (schemas: `core/product-execution-readiness.json`, `core/execution-requirement-readiness.json`, `enums/execution-readiness-status.json`) (#7763). The 3.3 betas carried it on `get_products`; it moved to `list_products` before GA (#8501). | Second, account-scoped half of the execution-readiness RFC. It reports `ready`, `setup_required`, `selection_required`, `input_required`, or `unknown` per declared requirement, with account-scoped candidates. Identity selection, lead-form and messaging-destination requirements, and proposal bindings are not specified yet and may change how readiness composes with requirements. | +| `media_buy.execution_readiness` | The `include_execution_readiness` request field and response-level `execution_readiness` map on [`list_products`](/docs/media-buy/task-reference/list_products#per-account-execution-readiness-experimental) (schemas: `core/product-execution-readiness.json`, `core/execution-requirement-readiness.json`, `enums/execution-readiness-status.json`) (#7763). The 3.3 betas carried it on `get_products`; it moved to `list_products` before GA (#8501). The betas spelled the `input-required` status `input_required`; it took the task-status spelling before GA (#8901). | Second, account-scoped half of the execution-readiness RFC. It reports `ready`, `setup_required`, `selection_required`, `input-required`, or `unknown` per declared requirement, with account-scoped candidates. Identity selection, lead-form and messaging-destination requirements, and proposal bindings are not specified yet and may change how readiness composes with requirements. | | `signals.delivery_methods` | The `signals.delivery_methods[]` capability on [`get_adcp_capabilities`](/docs/protocol/get_adcp_capabilities) (schema: `core/signal-delivery-method.json`), the `delivery_method` pin on agent destinations, and the `delivery_status` (`enums/signal-delivery-status.json`), `delivery`, `delivery_error`, `match_status`, `matched_count`, `effective_match_rate`, `match_rate_basis`, `audience_size` fields on agent [`activate_signal`](/docs/signals/tasks/activate_signal#delivering-membership-to-agent-destinations-experimental) deployments (#6552) | Provider-initiated membership delivery to agent destinations consumes the experimental `media_buy.audience_activation` vocabulary and has a single implementer pull so far (a publisher ad-server seller agent). Rail-selection authority, the arrival-state vocabulary, and match-outcome placement need integration experience across real signal-agent and seller pairs before freezing. Delivery cost is absorbed in the signal price for now; rail-dependent fee declarations may follow. | | `media_buy.gender_targeting` | Core `demographics.gender` predicates, product gender capabilities and overlay support/requirements, exact gender and mixed age/gender targeting resolution, the gender capability rollup, authoritative signal gender metadata, and the `sex_gender` governance classification — see [Core gender targeting](/docs/media-buy/advanced-topics/targeting#core-gender-targeting-33). Stable age targeting is excluded. | New portable gender contract without multi-party adoption evidence. Canonical demographic delivery reporting remains age-only in 3.3; this experiment does not add gendered delivery readback. | | `media_buy.product_identity` | The experimental `Product.identity` declaration on [`get_products`](/docs/media-buy/product-discovery/media-products) and [`list_products`](/docs/media-buy/task-reference/list_products) results, plus reach-unit coherence in delivery reporting | Product-level identity availability, the boundary between modeled and identifier-backed reach, and the methodology disclosure remain new cross-channel interoperability semantics. The field is deliberately product-scoped so mixed addressable and non-addressable inventory remains truthful while implementers validate the disclosure and reporting behavior. | | `media_buy.match_keys` | The `core/match-key.json` shape and `enums/match-key-use.json`, `media_buy.supported_match_keys` on [`get_adcp_capabilities`](/docs/protocol/get_adcp_capabilities), `Product.identity.match_keys`, and `match_keys` on TMP provider registration (#8375). See [Match keys and cross-publisher frequency capping](/docs/media-buy/specification#match-keys-and-cross-publisher-frequency-capping-experimental). | Identity resolution now varies by surface and can change between browser and OS releases without a spec change. The use split (`frequency_cap` vs `identity_match`), the compatibility rule for counting a cap across sellers, and the surface qualifier need integration experience with real identity graphs and TMP deployments before freezing. | | `media_buy.installment_lifecycle` | The `installment_status` and `publication` fields on `by_installment[]` rows of [`get_media_buy_delivery`](/docs/media-buy/task-reference/get_media_buy_delivery), the `core/installment-publication.json` schema, the `available_installment_statuses` declaration in the canonical product's `reporting_capabilities` (`core/canonical-reporting-capabilities.json`), the `publication` value in `enums/available-metric.json`, and the `installment_delivery` param of `simulate_delivery` in `comply_test_controller` (#8088; see [Installment lifecycle and proof of publication](/docs/media-buy/media-buys/optimization-reporting#installment-lifecycle-and-proof-of-publication)) | Installment lifecycle and proof of post for creator posts, newsletter editions, and podcast episodes, parallel to `media_buy.spot_lifecycle`. Validated so far by one adopter, Availnet, a creator marketplace. The publication record is seller-reported evidence; a notification when an installment publishes is planned for 3.4 and may reshape how buyers learn of publication. | | `media_buy.spot_lifecycle` | The `spot_status`, `scheduled_at`, `replaces_spot_id`, and `break_position` fields on `by_spot[]` rows of [`get_media_buy_delivery`](/docs/media-buy/task-reference/get_media_buy_delivery), the `enums/spot-status.json` enum, and the `available_statuses` and `supports_break_position` declarations on `supports_spot_breakdown` in product `reporting_capabilities` (#5683, #8009; see [Spot lifecycle, makegoods, and break position](/docs/media-buy/media-buys/optimization-reporting#spot-lifecycle-makegoods-and-break-position)) | Report-time half of linear-TV preemption and makegoods, validated so far by one Dutch linear TV pilot and one US broadcast operator. Contract-time preemption criteria and an interactive replacement-offer flow are planned for 3.4 and may reshape the status vocabulary, the makegood linkage, and how plan rows reconcile with broadcast as-run logs. | -| `media_buy.delivery_projection` | The optional `projection` object on top-level `by_package[]` rows of [`get_media_buy_delivery`](/docs/media-buy/task-reference/get_media_buy_delivery#delivery-projection) (schema: `core/delivery-projection.json`) and the `delivery-projection-basis` and `delivery-projection-end-state` enums (#5850) | Advisory forward-looking projection of a package's end-of-flight delivery, with one production adopter so far. The `complete` / `early_exhaustion` materiality split was authored during implementation rather than ratified in the brief, and the `basis` method-class vocabulary is expected to grow. Sellers declare the feature id in `experimental_features`; there is no capability field to gate it, because the object appears only on delivery responses. | +| `media_buy.delivery_projection` | The optional `projection` object on top-level `by_package[]` rows of [`get_media_buy_delivery`](/docs/media-buy/task-reference/get_media_buy_delivery#delivery-projection) (schema: `core/delivery-projection.json`) and the `delivery-projection-basis` and `delivery-projection-end-state` enums (#5850). The 3.3 betas spelled the `under_delivery` end state `underdelivery`; it took the `offer_reason` spelling before GA (#8901). | Advisory forward-looking projection of a package's end-of-flight delivery, with one production adopter so far. The `complete` / `early_exhaustion` materiality split was authored during implementation rather than ratified in the brief, and the `basis` method-class vocabulary is expected to grow. Sellers declare the feature id in `experimental_features`; there is no capability field to gate it, because the object appears only on delivery responses. | | `media_buy.reporting_delivery` | **Reliable Reporting 1.0**: [`get_reporting_status`](/docs/media-buy/task-reference/get_reporting_status), opt-in [`sync_reporting_status`](/docs/media-buy/task-reference/sync_reporting_status), [`sync_reporting_receipts`](/docs/media-buy/task-reference/sync_reporting_receipts), `sync_accounts.reporting_delivery_configs[]`, reporting notifications, and the `media_buy.reporting_delivery` capability block | Proper-name AdCP 3.2 reporting reliability surface (#6911), explicitly discovered through `reliable_reporting_version: "1.0"`. Core, Managed Delivery, and Reconciled Billing have separate capability-gated conformance storyboards. The [consumer-status migration](/docs/reference/migration/reliable-reporting-status-loop) is an active breaking-change notice: current source adds an opt-in task, which becomes required Core only in the next eligible minor after October 24, 2026. Its `content_mismatch` status, stale-`received` grace window, buyer posting deadline, issue lifecycle fields, escalation contact, the reserved `authoritative_party` field ([#7440](https://github.com/adcontextprotocol/adcp/issues/7440)), and the optional report-definition `source.custody` field, new in 3.3 ([#8185](https://github.com/adcontextprotocol/adcp/issues/8185)) are all additive within that experimental surface. [Superseded snapshot compaction](/docs/reference/migration/3-2-to-3-3#superseded-reporting-snapshots-may-be-compacted) ([#8081](https://github.com/adcontextprotocol/adcp/issues/8081)) is a second active breaking-change notice: sellers may compact older superseded snapshot content only in the next eligible minor after November 20, 2026. Multi-party production operation and provider-specific compatibility still need implementation experience before freezing. | | `media_buy.reporting_subscriptions` | Principal-level reporting subscriptions: the `reporting_subscriptions` section of [`sync_principal`](/docs/protocol/sync_principal#reporting-subscriptions) and its [`get_principal`](/docs/protocol/get_principal) readback, including `accounts_unauthorized`, (schemas: `core/reporting-subscription.json`, `core/reporting-subscription-state.json`), `subscription_coverage` on [`get_reporting_status`](/docs/media-buy/task-reference/get_reporting_status#subscription-coverage) (`core/reporting-subscription-coverage.json`), the extensible `enums/reporting-dimension.json` vocabulary, the `media_buy.reporting_delivery.subscriptions` and `configuration_tasks` capability fields, and the optional `authorization.authorized_at` on accounts and `completed_at` on media buys ([#8543](https://github.com/adcontextprotocol/adcp/issues/8543), Part B) | New in 3.3 with no adopter yet. One registration covers every authorized account instead of one configuration per account. The dimension vocabulary (`package`, `creative`, `country`, `region`, `metro`), `exact` as the default match when `dimensions` is given (`finest` otherwise), backfill on re-grant as a seller capability rather than a requirement, and whether billing feeds may use all-accounts scope are open working-group questions. Builds on `media_buy.reporting_delivery` and `protocol.principal`. A strict validator built from the 3.2 `core/reporting-delivery-capabilities.json`, which sets `additionalProperties: false`, rejects the new `configuration_tasks` and `subscriptions` fields; that is acceptable for an experimental surface. `configuration_task` is also no longer always required: it is present when and only when `configuration_tasks` lists `sync_accounts` or is absent. That is a relaxation gated by version pin: callers pinned below 3.3 see only `configuration_task`, so a seller MUST serve `sync_accounts` registration to them, and a seller listing only `sync_principal` serves reporting registration to 3.3 callers only. | | `media_buy.seller_offers` | Seller-initiated offers against an accepted media buy: the `proposal_initiator` field on [`core/canonical-proposal.json`](/docs/media-buy/product-discovery/proposal-negotiation#seller-offers) and the `offer_reason` field on `list_proposals` rows, the `enums/proposal-initiator.json` and `enums/offer-reason.json` enums, and the `proposal.offered` notification type with its `core/proposal-offered-webhook.json` payload (#8087; absorbs the offer-and-accept loop of #5683). Offers are read through `list_proposals` and announced by a sibling of `proposal.created`, both defined by `media_buy.open_opportunities` (#8103); a seller declaring this id MUST also advertise `list_proposals` | First protocol path for a seller to propose a change to a signed buy: a moved or cancelled installment, a linear preemption, under-delivery. One adopter group has asked for it so far. The `offer_reason` values, the `expired` disposition and `PROPOSAL_EXPIRED` rule for a lapsed offer, and how the same shape carries control-rejection alternatives in 3.4 (#7631) may be revised once sellers have run real offers through it. Seller proposals into a buyer-opened open opportunity are `media_buy.open_opportunities` (#8096), and unsolicited pitches stay with #5989. | | `media_buy.hold_extension` | `action: "extend_hold"` on [`refine_proposals`](/docs/media-buy/task-reference/refine_proposals#extend-a-hold-experimental) and the `media_buy.proposal_refinement.hold_extension` capability on [`get_adcp_capabilities`](/docs/protocol/get_adcp_capabilities#proposal_refinement) ([#8086](https://github.com/adcontextprotocol/adcp/issues/8086)) | New committed-hold extension for scheduled media, with one named adopter so far: Availnet, a creator marketplace. When the committed proposal carries the experimental `hold_policy` (`media_buy.hold_policy`, [#7661](https://github.com/adcontextprotocol/adcp/issues/7661)), its `extendable` (default `false`) and `max_total_duration` bound extension, measured from the original `hold_placed_at`; absent it, limits are seller policy expressed through `hold_unavailable`. Applies only to committed proposals that hold inventory: an entry whose effective policy is terms-only fails `extend_hold` with `INVALID_STATE`. A successor lists the same per-installment `holds` (`media_buy.installment_holds`) as its source. | | `creative.broadcast_loudness` | The optional `loudness` object (`target_lufs`, `tolerance_db`, `max_true_peak_dbfs`, `standard`) on the `video_hosted` canonical format params (schema: `formats/canonical/video_hosted.json`), documented in [Broadcast creative](/docs/creative/channels/broadcast#loudness-experimental) (#8011) | New broadcast-television delivery requirement with very few adopters so far. The `standard` enum (`ebu_r128`, `atsc_a85`, `custom`) is expected to grow, and the object shape may be reconciled with the flat `loudness_*` fields on `audio_hosted` in a later 3.x release. The `mxf` container and the `mpeg2` / `avc_intra` / `xavc` codec values added alongside it are stable additive enum values, not part of this experimental surface. | -| `creative.live_updates` | The `creative.live_updates` capability block on [`get_adcp_capabilities`](/docs/protocol/get_adcp_capabilities#creative), `staged_update` on [`list_creatives`](/docs/creative/task-reference/list_creatives#staged-update-readback) and on [`sync_creatives`](/docs/creative/task-reference/sync_creatives#live-creative-updates) results, the `creative.staged_update_changed` notification type, the `CREATIVE_LIVE_UPDATE_UNSUPPORTED` error code, and the schemas `core/live-update-policy.json`, `core/creative-staged-update.json`, `creative/creative-staged-update-changed-webhook.json`, `enums/creative-change-kind.json`, `enums/live-update-serving-mode.json`, and `error-details/creative-live-update-unsupported.json` ([#8404](https://github.com/adcontextprotocol/adcp/issues/8404), folds in staged activation from [#6780](https://github.com/adcontextprotocol/adcp/issues/6780)) | New mutation contract for creatives that are serving, with one reference implementation (the training agent) and no production sellers yet. The change-kind taxonomy, the serving-mode ranking, the declaration-as-floor model, and per-package review during a staged update need field evidence from ad servers with different trafficking models before they freeze. The `comply_test_controller` behaviour that `force_creative_status` resolves a pending staged update rides on this feature id. | +| `creative.live_updates` | The `creative.live_updates` capability block on [`get_adcp_capabilities`](/docs/protocol/get_adcp_capabilities#creative), `staged_update` on [`list_creatives`](/docs/creative/task-reference/list_creatives#staged-update-readback) and on [`sync_creatives`](/docs/creative/task-reference/sync_creatives#live-creative-updates) results, the `creative.staged_update_changed` notification type, the `CREATIVE_LIVE_UPDATE_UNSUPPORTED` error code, and the schemas `core/live-update-policy.json`, `core/creative-staged-update.json`, `creative/creative-staged-update-changed-webhook.json`, `enums/creative-change-kind.json`, `enums/live-update-serving-mode.json`, and `error-details/creative-live-update-unsupported.json` ([#8404](https://github.com/adcontextprotocol/adcp/issues/8404), folds in staged activation from [#6780](https://github.com/adcontextprotocol/adcp/issues/6780)). The 3.3 betas spelled the `clickthrough` change kind `click_through`; it took the `url_type` spelling before GA (#8901). | New mutation contract for creatives that are serving, with one reference implementation (the training agent) and no production sellers yet. The change-kind taxonomy, the serving-mode ranking, the declaration-as-floor model, and per-package review during a staged update need field evidence from ad servers with different trafficking models before they freeze. The `comply_test_controller` behaviour that `force_creative_status` resolves a pending staged update rides on this feature id. | | `media_buy.daypart_granularity` | The `start_time` / `end_time` clock-time fields on `core/daypart-target.json` (and so on every `daypart_targets` entry), the `time_granularity` field in `overlay_support.daypart_targets` and `required_overlay_support.daypart_targets`, and the `enums/daypart-time-granularity.json` enum ([#6707](https://github.com/adcontextprotocol/adcp/issues/6707)). See [Minute-resolution dayparts](/docs/media-buy/advanced-topics/targeting#minute-resolution-dayparts-experimental). | Minute-level schedules for linear TV (programme-junction breaks, overnight broadcast days) arrive with one named adopter and are expected to be reshaped by the network/station targeting work in [#5679](https://github.com/adcontextprotocol/adcp/issues/5679). The `HH:MM` representation, the overnight rule (a window belongs to its start day), the three-value granularity ladder, and the error code for an unsupported granularity (today `UNSUPPORTED_FEATURE`, which may be superseded by the targeting-dimension codes in [#6978](https://github.com/adcontextprotocol/adcp/issues/6978)) may change before they freeze. The whole-hour `start_hour` / `end_hour` fields stay stable, although they are no longer individually schema-required. | | `media_buy.measurement_provenance` | The optional `vendor_metric_provenance[]` array (`core/vendor-metric-provenance.json`: `vendor`, `metric_id`, `qualifier?`, `mechanisms[]`, `deduplicated?`) beside `vendor_metric_values` on `core/delivery-metrics.json`, and the `enums/collection-mechanism.json` enum (#6878, #2041; see [Collection-mechanism provenance on vendor values](/docs/media-buy/media-buys/optimization-reporting#collection-mechanism-provenance-on-vendor-values-experimental)) | New cross-party disclosure of what a count is built from, with no production adopter yet. The enum is closed and released values cannot be removed on a stable surface, so the vocabulary (for example whether `client_tracker` and `sdk` stay distinct, or whether a sensor value is needed for place-based media) is held experimental until two parties have used it. The `deduplicated` boolean may become a level enum, and the `impression_id` joinability and `seller_tracker_events[]` follow-ups may reshape the declaration. `measurement_source` on delivery metrics (the provider slug) is stable and unrelated. `canonical-measurement-terms.json`, held closed with the commercial terms until 4.0 (DR-0036), does not carry these fields in 3.x, and the `vendor-metric-value.json` row does not define them: provenance is a sibling array joined on the row key. The `billing_measurement.counting_mechanisms[]` declaration shipped in the 3.3 betas on `core/measurement-terms.json`, which only the deprecated `get_products`, `create_media_buy`, and `update_media_buy` use, and is withdrawn; it arrives on the canonical compact-lifecycle terms in 4.0 ([4.0 compatibility check](/docs/reference/migration/v4-compatibility#canonical_counting_mechanisms_required_in_4_0)). | | `media_buy.lineage` | Forward-only buy-level lineage: the `predecessor_media_buy_id` and `lineage_reason` fields on [`buy_products`](/docs/media-buy/task-reference/buy_products) and [`accept_proposal`](/docs/media-buy/task-reference/accept_proposal) requests and on [`get_media_buys`](/docs/media-buy/task-reference/get_media_buys) items (also `core/media-buy.json`), the `predecessor_media_buy_id` filter on `get_media_buys`, and the `enums/media-buy-lineage-reason.json` enum (#5849; see [Media Buy Lineage](/docs/media-buy/specification#media-buy-lineage)) | One production sales agent has run equivalent lineage operations for months, and IAB AAMP seller-agent 2.0 (`parent_deal_id`) is prior art for the same idea in deal-scoped form, not convergence evidence. The `lineage_reason` vocabulary is closed and may grow, and the `UNSUPPORTED_FEATURE` rejection and the same-account scope may change as more sellers implement it. | diff --git a/governance/decisions/DR-0029-delivery-projection-advisory.md b/governance/decisions/DR-0029-delivery-projection-advisory.md index 6ca658400c7..6fd6a1dbbf9 100644 --- a/governance/decisions/DR-0029-delivery-projection-advisory.md +++ b/governance/decisions/DR-0029-delivery-projection-advisory.md @@ -14,7 +14,7 @@ dissent: none A seller MAY report a forward-looking `projection` on top-level `by_package[]` rows of `get_media_buy_delivery`. A projection is an advisory forecast, not a delivery record. It carries no contractual meaning on its own, and any commercial use of it is governed by the parties' terms. Reported actuals remain the delivery record. The surface is experimental under `media_buy.delivery_projection`. -- `projected_end_state` is `on_pace`, `complete`, `underdelivery`, or `early_exhaustion`, judged against the package's binding constraint (whichever of goal or budget it would reach first) using the seller's own tolerance and materiality thresholds. +- `projected_end_state` is `on_pace`, `complete`, `under_delivery`, or `early_exhaustion`, judged against the package's binding constraint (whichever of goal or budget it would reach first) using the seller's own tolerance and materiality thresholds. *Amended: the record was written with `underdelivery`, the spelling of the 3.3 betas; the value took the `offer_reason` spelling `under_delivery` before GA (#8901).* - `basis` names the method class (`trailing_daily_avg`, `trailing_hourly_avg`, `platform_model`, `seller_defined`), not the window length or model. It is distinct from the commitment tier in `forecast_method`. - `is_final` on the enclosing row describes the actuals, not the projection. diff --git a/server/src/training-agent/live-creative-updates.ts b/server/src/training-agent/live-creative-updates.ts index f52b791ff7d..d145dbe9ddc 100644 --- a/server/src/training-agent/live-creative-updates.ts +++ b/server/src/training-agent/live-creative-updates.ts @@ -7,7 +7,7 @@ import { isDeepStrictEqual } from 'node:util'; import type { CreativeState, MediaBuyState, SessionState } from './types.js'; -export type CreativeChangeKind = 'click_through' | 'trackers' | 'asset_content' | 'format'; +export type CreativeChangeKind = 'clickthrough' | 'trackers' | 'asset_content' | 'format'; export type LiveUpdateServingMode = 'uninterrupted' | 'staged' | 'paused_for_review'; export interface LiveUpdatePolicyEntry { @@ -23,7 +23,7 @@ export interface LiveUpdatePolicyEntry { * and cannot change a trafficked creative's format. */ export const TRAINING_LIVE_UPDATE_POLICY: readonly LiveUpdatePolicyEntry[] = [ - { change: 'click_through', serving: 'uninterrupted' }, + { change: 'clickthrough', serving: 'uninterrupted' }, { change: 'trackers', serving: 'uninterrupted' }, { change: 'asset_content', serving: 'staged', on_rejection: 'keeps_prior' }, ]; @@ -65,7 +65,7 @@ export function isOpenMediaBuy(mb: MediaBuyState, now: Date = new Date()): boole const end = Date.parse(mb.endTime); return !(Number.isFinite(end) && end <= now.getTime()); } -const KIND_ORDER: readonly CreativeChangeKind[] = ['click_through', 'trackers', 'asset_content', 'format']; +const KIND_ORDER: readonly CreativeChangeKind[] = ['clickthrough', 'trackers', 'asset_content', 'format']; const MODE_ORDER: readonly LiveUpdateServingMode[] = ['uninterrupted', 'staged', 'paused_for_review']; const TRACKER_ASSET_TYPES = new Set(['pixel_tracker', 'vast_tracker', 'daast_tracker']); @@ -98,7 +98,7 @@ function assetKinds(value: unknown): CreativeChangeKind[] { if (!value || typeof value !== 'object') return ['asset_content']; const asset = value as Record; if (asset.asset_type === 'url') { - if (asset.url_type === 'clickthrough') return ['click_through']; + if (asset.url_type === 'clickthrough') return ['clickthrough']; if (asset.url_type === 'tracker_pixel' || asset.url_type === 'tracker_script') return ['trackers']; return ['asset_content']; } diff --git a/server/tests/unit/live-creative-updates.test.ts b/server/tests/unit/live-creative-updates.test.ts index c5c3f8fd031..9f755fccdeb 100644 --- a/server/tests/unit/live-creative-updates.test.ts +++ b/server/tests/unit/live-creative-updates.test.ts @@ -103,7 +103,7 @@ describe('live creative updates', () => { it('classifies change kinds by the assets that changed', () => { const before = { assets: creative().assets as any }; expect(classifyCreativeChange(before, { assets: creative({ tracker: 'https://m.example/i2' }).assets as any }, false)).toEqual(['trackers']); - expect(classifyCreativeChange(before, { assets: creative({ click: 'https://x.example' }).assets as any }, false)).toEqual(['click_through']); + expect(classifyCreativeChange(before, { assets: creative({ click: 'https://x.example' }).assets as any }, false)).toEqual(['clickthrough']); expect(classifyCreativeChange(before, { assets: creative({ image: 'https://x.example/v2.png' }).assets as any }, false)).toEqual(['asset_content']); expect(classifyCreativeChange(before, { assets: creative().assets as any }, true)).toEqual(['format']); expect(decideLiveUpdate(['trackers', 'asset_content'])).toEqual({ outcome: 'apply', mode: 'staged' }); diff --git a/static/schemas/source/core/delivery-projection.json b/static/schemas/source/core/delivery-projection.json index e42f4ab5c50..41cc4cc3ebb 100644 --- a/static/schemas/source/core/delivery-projection.json +++ b/static/schemas/source/core/delivery-projection.json @@ -19,7 +19,7 @@ }, "projected_end_state": { "$ref": "/schemas/enums/delivery-projection-end-state.json", - "description": "The package-level judgment of where delivery lands, measured against the package's binding constraint: whichever of its goal or budget the package would reach first. The two projected totals are supporting numbers and are not classified independently. `on_pace`: the package is projected to reach the binding constraint at flight end, within the seller's tolerance. `complete`: the package is projected to reach it before flight end, with the undelivered share of the flight below the seller's materiality threshold, so it simply finishes early. `early_exhaustion`: the package is projected to reach it before flight end, with the undelivered share of the flight at or above the materiality threshold. `underdelivery`: the package is projected to fall short of the binding constraint at flight end by more than the seller's tolerance. There is no over-delivery state: a package projected to exceed its constraint is projected to reach it early. Tolerance and materiality are the seller's own thresholds, which it SHOULD also use for the `pacing_risk` indicator when it emits that indicator. They are not exposed, so buyers SHOULD NOT compare end states across sellers as if the thresholds matched. `complete` is a projected early finish, unrelated to `delivery_status: completed`. Clients on an older schema version SHOULD tolerate an unrecognized value." + "description": "The package-level judgment of where delivery lands, measured against the package's binding constraint: whichever of its goal or budget the package would reach first. The two projected totals are supporting numbers and are not classified independently. `on_pace`: the package is projected to reach the binding constraint at flight end, within the seller's tolerance. `complete`: the package is projected to reach it before flight end, with the undelivered share of the flight below the seller's materiality threshold, so it simply finishes early. `early_exhaustion`: the package is projected to reach it before flight end, with the undelivered share of the flight at or above the materiality threshold. `under_delivery`: the package is projected to fall short of the binding constraint at flight end by more than the seller's tolerance. There is no over-delivery state: a package projected to exceed its constraint is projected to reach it early. Tolerance and materiality are the seller's own thresholds, which it SHOULD also use for the `pacing_risk` indicator when it emits that indicator. They are not exposed, so buyers SHOULD NOT compare end states across sellers as if the thresholds matched. `complete` is a projected early finish, unrelated to `delivery_status: completed`. Clients on an older schema version SHOULD tolerate an unrecognized value." }, "basis": { "$ref": "/schemas/enums/delivery-projection-basis.json", diff --git a/static/schemas/source/core/execution-requirement-readiness.json b/static/schemas/source/core/execution-requirement-readiness.json index ed3817838db..9fa4179ab38 100644 --- a/static/schemas/source/core/execution-requirement-readiness.json +++ b/static/schemas/source/core/execution-requirement-readiness.json @@ -11,7 +11,7 @@ }, "readiness_status": { "$ref": "/schemas/enums/execution-readiness-status.json", - "description": "Readiness of this requirement. For `downstream_connection`, derived from `connection_state.status`: `connected` and `not_required` are `ready`; `missing`, `pending`, `expired`, `revoked` are `setup_required`; `unknown` is `unknown`. `selection_required` and `input_required` apply only to `event_source` and `catalog`." + "description": "Readiness of this requirement. For `downstream_connection`, derived from `connection_state.status`: `connected` and `not_required` are `ready`; `missing`, `pending`, `expired`, `revoked` are `setup_required`; `unknown` is `unknown`. `selection_required` and `input-required` apply only to `event_source` and `catalog`." }, "binding_field": { "type": "string", diff --git a/static/schemas/source/core/product-execution-readiness.json b/static/schemas/source/core/product-execution-readiness.json index b2e5d2dc0c1..9aa0dd95062 100644 --- a/static/schemas/source/core/product-execution-readiness.json +++ b/static/schemas/source/core/product-execution-readiness.json @@ -7,7 +7,7 @@ "properties": { "readiness_status": { "$ref": "/schemas/enums/execution-readiness-status.json", - "description": "Rollup of `requirements[].readiness_status`, derivable from it: the first of `setup_required`, `input_required`, `selection_required`, `unknown` that any requirement reports, else `ready`. The rollup is the most blocking state the seller could establish, so `unknown` is reported only when no requirement is definitely blocking and at least one could not be checked. Buyers needing detail MUST read `requirements[]`." + "description": "Rollup of `requirements[].readiness_status`, derivable from it: the first of `setup_required`, `input-required`, `selection_required`, `unknown` that any requirement reports, else `ready`. The rollup is the most blocking state the seller could establish, so `unknown` is reported only when no requirement is definitely blocking and at least one could not be checked. Buyers needing detail MUST read `requirements[]`." }, "requirements": { "type": "array", diff --git a/static/schemas/source/enums/creative-change-kind.json b/static/schemas/source/enums/creative-change-kind.json index d47161bc52b..f86d270b0ea 100644 --- a/static/schemas/source/enums/creative-change-kind.json +++ b/static/schemas/source/enums/creative-change-kind.json @@ -5,13 +5,13 @@ "description": "A kind of change a sync_creatives item makes to a creative, classified from the item's diff against the version of the creative that serves. Buyer and seller compute the same classification, so a buyer can tell before it sends a change which live-update rules apply. Fields excluded from canonical revision content (name, tags, status, weight, placement_refs, placement_ids) are not a change kind.", "type": "string", "enum": [ - "click_through", + "clickthrough", "trackers", "asset_content", "format" ], "enumDescriptions": { - "click_through": "A url asset with url_type clickthrough was added, removed, or changed.", + "clickthrough": "A url asset with url_type clickthrough was added, removed, or changed.", "trackers": "A url asset with url_type tracker_pixel or tracker_script, or a pixel_tracker, vast_tracker, or daast_tracker asset, was added, removed, or changed.", "asset_content": "Any other asset was added, removed, or changed, including markup or VAST that embeds clicks or trackers; or localization or representation_selection changed.", "format": "format_id, format_kind, or format_option_ref changed." diff --git a/static/schemas/source/enums/delivery-projection-end-state.json b/static/schemas/source/enums/delivery-projection-end-state.json index 0e8b1be450a..86276fbafd4 100644 --- a/static/schemas/source/enums/delivery-projection-end-state.json +++ b/static/schemas/source/enums/delivery-projection-end-state.json @@ -9,13 +9,13 @@ "enum": [ "on_pace", "complete", - "underdelivery", + "under_delivery", "early_exhaustion" ], "enumDescriptions": { "on_pace": "Projected to reach the binding constraint (goal or budget) at flight end, within the seller's tolerance.", "complete": "Projected to reach the binding constraint before flight end, with the undelivered share of the flight below the seller's materiality threshold. A benign early finish.", - "underdelivery": "Projected to fall short of the binding constraint at flight end by more than the seller's tolerance.", + "under_delivery": "Projected to fall short of the binding constraint at flight end by more than the seller's tolerance.", "early_exhaustion": "Projected to reach the binding constraint before flight end, with the undelivered share of the flight at or above the seller's materiality threshold." } } diff --git a/static/schemas/source/enums/execution-readiness-status.json b/static/schemas/source/enums/execution-readiness-status.json index d0fdf0a575c..18d1e970389 100644 --- a/static/schemas/source/enums/execution-readiness-status.json +++ b/static/schemas/source/enums/execution-readiness-status.json @@ -8,14 +8,14 @@ "ready", "setup_required", "selection_required", - "input_required", + "input-required", "unknown" ], "enumDescriptions": { "ready": "One suitable resource (for a connection, it is connected). Does not waive binding: for `event_source` and `catalog` the buyer still binds `resolved.resource_id`.", "setup_required": "No suitable resource, or the connection is `missing`, `pending`, `expired`, or `revoked`. The buyer can fix it (`sync_event_sources`, `sync_catalogs`, `authorization_url`).", "selection_required": "Several suitable resources; the buyer chooses one from `candidates[]` and binds it.", - "input_required": "The seller cannot discover a suitable resource; the buyer supplies one on the request, such as an inline catalog.", + "input-required": "The seller cannot discover a suitable resource; the buyer supplies one on the request, such as an inline catalog.", "unknown": "The seller could not read the account's resources. Distinct from `setup_required`; the product may be runnable. `unknown_reason` says what to do." }, "x-status": "experimental", diff --git a/static/schemas/source/index.json b/static/schemas/source/index.json index 5085ec4182b..75d712b708b 100644 --- a/static/schemas/source/index.json +++ b/static/schemas/source/index.json @@ -1368,7 +1368,7 @@ }, "execution-readiness-status": { "$ref": "/schemas/enums/execution-readiness-status.json", - "description": "Experimental: whether an account can satisfy a product's execution requirement (ready, setup_required, selection_required, input_required, unknown)." + "description": "Experimental: whether an account can satisfy a product's execution requirement (ready, setup_required, selection_required, input-required, unknown)." }, "pricing-structure": { "$ref": "/schemas/enums/pricing-structure.json", @@ -1440,7 +1440,7 @@ }, "creative-change-kind": { "$ref": "/schemas/enums/creative-change-kind.json", - "description": "Kind of change a sync_creatives item makes to a creative (click_through, trackers, asset_content, format)" + "description": "Kind of change a sync_creatives item makes to a creative (clickthrough, trackers, asset_content, format)" }, "live-update-serving-mode": { "$ref": "/schemas/enums/live-update-serving-mode.json", @@ -1600,7 +1600,7 @@ }, "delivery-projection-end-state": { "$ref": "/schemas/enums/delivery-projection-end-state.json", - "description": "Projected end-of-flight state of a package (on_pace, complete, underdelivery, early_exhaustion)" + "description": "Projected end-of-flight state of a package (on_pace, complete, under_delivery, early_exhaustion)" }, "delivery-projection-basis": { "$ref": "/schemas/enums/delivery-projection-basis.json", diff --git a/tests/creative-live-updates.test.cjs b/tests/creative-live-updates.test.cjs index 316577c8074..9d5cc46464c 100644 --- a/tests/creative-live-updates.test.cjs +++ b/tests/creative-live-updates.test.cjs @@ -24,7 +24,7 @@ async function compile(relativePath) { const policy = { in_place: [ { change: 'trackers', serving: 'uninterrupted' }, - { change: 'click_through', serving: 'uninterrupted' }, + { change: 'clickthrough', serving: 'uninterrupted' }, { change: 'asset_content', serving: 'staged', on_rejection: 'keeps_prior' } ] }; diff --git a/tests/delivery-projection.test.cjs b/tests/delivery-projection.test.cjs index 1f655e682b1..30d08d0e0b5 100644 --- a/tests/delivery-projection.test.cjs +++ b/tests/delivery-projection.test.cjs @@ -21,7 +21,7 @@ async function compile(uri) { const projection = { projected_impressions: 1180000, projected_spend: 9440.0, - projected_end_state: 'underdelivery', + projected_end_state: 'under_delivery', basis: 'trailing_daily_avg', as_of: '2026-10-01T00:00:00Z', }; @@ -53,7 +53,7 @@ test('projection requires end state, basis, and as_of; volumes are optional', () assert.deepEqual(readSchema('/schemas/enums/delivery-projection-end-state.json').enum, [ 'on_pace', 'complete', - 'underdelivery', + 'under_delivery', 'early_exhaustion', ]); assert.equal(core.properties.basis.$ref, '/schemas/enums/delivery-projection-basis.json'); diff --git a/tests/execution-readiness.test.cjs b/tests/execution-readiness.test.cjs index f49c448f039..1e4ce44df7c 100644 --- a/tests/execution-readiness.test.cjs +++ b/tests/execution-readiness.test.cjs @@ -76,7 +76,7 @@ const SETUP_EVENT_SOURCE = { }; const INPUT_CATALOG = { requirement: CATALOG, - readiness_status: "input_required", + readiness_status: "input-required", issues: [ { severity: "info", @@ -112,7 +112,7 @@ const connection = (status, extra = {}) => ({ // The documented rollup: the first blocking state any requirement reports. const ROLLUP_ORDER = [ "setup_required", - "input_required", + "input-required", "selection_required", "unknown", ]; @@ -129,7 +129,7 @@ test("status enum is the five-value readiness vocabulary", () => { "ready", "setup_required", "selection_required", - "input_required", + "input-required", "unknown", ]); assert.deepEqual(Object.keys(schema.enumDescriptions), schema.enum); @@ -300,8 +300,8 @@ test("downstream_connection readiness derives from connection_state and has no c "no identity candidates in this slice", ], [ - { ...connected, readiness_status: "input_required" }, - "input_required applies to bound kinds only", + { ...connected, readiness_status: "input-required" }, + "input-required applies to bound kinds only", ], [ { ...connected, resolved: { resource_id: "pg_1" } }, @@ -337,7 +337,7 @@ test("product rollup is the first blocking state and is derivable", async () => [[READY_EVENT_SOURCE, SELECTION_EVENT_SOURCE], "selection_required"], [[SELECTION_EVENT_SOURCE, UNKNOWN_CATALOG], "selection_required"], [[READY_EVENT_SOURCE, UNKNOWN_CATALOG], "unknown"], - [[SELECTION_EVENT_SOURCE, INPUT_CATALOG], "input_required"], + [[SELECTION_EVENT_SOURCE, INPUT_CATALOG], "input-required"], [[INPUT_CATALOG, SETUP_EVENT_SOURCE, UNKNOWN_CATALOG], "setup_required"], ]; for (const [requirements, expected] of cases) { From f929a6af1ef9d8707e589092461288deadd50d02 Mon Sep 17 00:00:00 2001 From: "Constantine.mirin" Date: Fri, 13 Nov 2026 18:55:17 +0100 Subject: [PATCH 2/3] fix(training-agent): serve the change kind in the pinned release's spelling The hosted 3.3-beta.5 preview validates responses against the beta.5 bundle, whose creative-change-kind enum has click_through. A caller pinned to it now gets click_through in live_updates.in_place, staged_update change_kinds and the CREATIVE_LIVE_UPDATE_UNSUPPORTED details; every other caller gets clickthrough. --- .../training-agent/live-creative-updates.ts | 24 +++++++++++++++---- server/src/training-agent/task-handlers.ts | 13 +++++----- server/src/training-agent/tenants/router.ts | 2 +- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/server/src/training-agent/live-creative-updates.ts b/server/src/training-agent/live-creative-updates.ts index d145dbe9ddc..2b742ad88c4 100644 --- a/server/src/training-agent/live-creative-updates.ts +++ b/server/src/training-agent/live-creative-updates.ts @@ -6,6 +6,7 @@ */ import { isDeepStrictEqual } from 'node:util'; import type { CreativeState, MediaBuyState, SessionState } from './types.js'; +import { servedReleaseEnum } from './served-release-fields.js'; export type CreativeChangeKind = 'clickthrough' | 'trackers' | 'asset_content' | 'format'; export type LiveUpdateServingMode = 'uninterrupted' | 'staged' | 'paused_for_review'; @@ -30,9 +31,24 @@ export const TRAINING_LIVE_UPDATE_POLICY: readonly LiveUpdatePolicyEntry[] = [ export const TRAINING_LIVE_UPDATE_MAX_PROPAGATION_SECONDS = 0; -export function liveUpdatesCapability(): Record { +/** + * The 3.3 betas spelled `clickthrough` as `click_through` (#8901). A caller + * pinned to a release whose `enums/creative-change-kind.json` has only the + * beta spelling, such as the `3.3-beta.5` preview, gets that spelling. + */ +const BETA_CHANGE_KIND_SPELLING: Partial> = { clickthrough: 'click_through' }; + +/** A change kind as the served release's enum spells it. */ +export function servedChangeKind(kind: CreativeChangeKind, servedAdcpVersion?: string): string { + const beta = BETA_CHANGE_KIND_SPELLING[kind]; + if (!beta || servedAdcpVersion === undefined) return kind; + const served = servedReleaseEnum('enums/creative-change-kind.json', servedAdcpVersion); + return served && !served.has(kind) && served.has(beta) ? beta : kind; +} + +export function liveUpdatesCapability(servedAdcpVersion?: string): Record { return { - in_place: TRAINING_LIVE_UPDATE_POLICY.map(entry => ({ ...entry })), + in_place: TRAINING_LIVE_UPDATE_POLICY.map(entry => ({ ...entry, change: servedChangeKind(entry.change, servedAdcpVersion) })), max_propagation_seconds: TRAINING_LIVE_UPDATE_MAX_PROPAGATION_SECONDS, }; } @@ -156,10 +172,10 @@ export function stagedOnRejection(): 'keeps_prior' | 'stops' { return TRAINING_LIVE_UPDATE_POLICY.find(e => e.serving === 'staged')?.on_rejection ?? 'keeps_prior'; } -export function stagedUpdateWire(staged: StagedCreativeUpdate): Record { +export function stagedUpdateWire(staged: StagedCreativeUpdate, servedAdcpVersion?: string): Record { return { status: staged.status, - change_kinds: [...staged.changeKinds], + change_kinds: staged.changeKinds.map(kind => servedChangeKind(kind, servedAdcpVersion)), staged_at: staged.stagedAt, ...(staged.status === 'rejected' && { rejection_reason: staged.rejectionReason ?? 'Rejected in review.' }), }; diff --git a/server/src/training-agent/task-handlers.ts b/server/src/training-agent/task-handlers.ts index d1a360d6586..491d6fe2133 100644 --- a/server/src/training-agent/task-handlers.ts +++ b/server/src/training-agent/task-handlers.ts @@ -60,7 +60,7 @@ import { loadHoldDocument, saveHoldDocument } from './holds/store.js'; import { TRAINING_AGENT_PREVIEW_ADCP_VERSION } from './candidate-version.js'; import { parseAdcpReleaseVersion, type ParsedAdcpReleaseVersion, servesReleaseBefore, supportsAccountSurfaces33, supportsBiddingPolicyCapability, TRAINING_BIDDING_POLICY_CAPABILITY, TRAINING_CONVERSION_TRACKING_SUPPORTED_TARGETS, supportsGetProductsRejected, supportsReliableReporting, supportsSalesFixedRate, supportsSellerGovernanceDiscovery, TRAINING_AGENT_CURRENT_ADCP_VERSION, TRAINING_AGENT_DEFAULT_ADCP_VERSION, TRAINING_AGENT_SUPPORTED_RELEASE_VERSIONS, type TrainingContext, type CatalogProduct, type MediaBuyState, type MediaBuyAvailableActionState, type MediaBuyProductAllowedActionState, type PackageState, type SignalActivationState, type CreativeState, type CreativeManifest, type ToolArgs, type ListReference, type PackageTargeting, type AccountRef, type BrandRef, type SessionState, type SeededProductAvailability, type PackageFrequencyCapEligibility, supportsAccountChangeFeed, atLeastAdcpVersion } from './types.js'; import { planAssignmentOperations, commitAssignmentPlan, assignmentReadbackFields, assignmentRow, isTrackerSlot, trackerAllocationProblem } from './assignment-operations.js'; -import { isOpenMediaBuy, classifyCreativeChange, decideLiveUpdate, livePackageIds, servingContent, stagedUpdateWire, liveUpdatesCapability, type CreativeChangeKind, type LiveUpdateServingMode, type StagedCreativeContent, type StagedCreativeUpdate } from './live-creative-updates.js'; +import { isOpenMediaBuy, classifyCreativeChange, decideLiveUpdate, livePackageIds, servingContent, stagedUpdateWire, liveUpdatesCapability, servedChangeKind, type CreativeChangeKind, type LiveUpdateServingMode, type StagedCreativeContent, type StagedCreativeUpdate } from './live-creative-updates.js'; import { AccountRefValidationError, accountScopeFromRef, @@ -18849,15 +18849,16 @@ export async function handleSyncCreatives(args: ToolArgs, ctx: TrainingContext) liveChangeKinds = classifyCreativeChange(servingContent(existingCreative), nextContent, formatChanged); const decision = decideLiveUpdate(liveChangeKinds); if (decision.outcome === 'refuse') { + const refusedChanges = decision.refusedChanges.map(kind => servedChangeKind(kind, ctx.servedAdcpVersion)); results.push({ creative_id: creativeId, action: 'failed', errors: [{ code: 'CREATIVE_LIVE_UPDATE_UNSUPPORTED', - message: `Creative ${creativeId} is live and this seller does not apply ${decision.refusedChanges.join(', ')} changes in place. Sync the change as a new creative_id and replace it on the live packages.`, + message: `Creative ${creativeId} is live and this seller does not apply ${refusedChanges.join(', ')} changes in place. Sync the change as a new creative_id and replace it on the live packages.`, field: `creatives[${creativeId}]`, recovery: 'correctable', - details: { refused_changes: decision.refusedChanges, live_package_ids: livePackages }, + details: { refused_changes: refusedChanges, live_package_ids: livePackages }, }] as TaskError[], }); stagedCreativeManifests.delete(creativeId); @@ -19013,7 +19014,7 @@ export async function handleSyncCreatives(args: ToolArgs, ctx: TrainingContext) action: existing ? 'updated' : 'created', status: servedStatus, ...(localization && { localization }), - ...(stagedUpdate && { staged_update: stagedUpdateWire(stagedUpdate) }), + ...(stagedUpdate && { staged_update: stagedUpdateWire(stagedUpdate, ctx.servedAdcpVersion) }), }); } @@ -19551,7 +19552,7 @@ export async function handleListCreatives(args: ToolArgs, ctx: TrainingContext) } if (declaresLiveCreativeUpdates(ctx)) { if (c.stagedUpdate && (!selectedFields || selectedFields.has('staged_update'))) { - base.staged_update = stagedUpdateWire(c.stagedUpdate); + base.staged_update = stagedUpdateWire(c.stagedUpdate, ctx.servedAdcpVersion); } } if (declaresAssignmentSettings(ctx) && req.include_assignments !== false @@ -21367,7 +21368,7 @@ export async function handleGetAdcpCapabilities(args: ToolArgs, ctx: TrainingCon supports_provenance: true, supports_registry_policy_enforcement: true, }), - ...(declaresLiveCreativeUpdates(ctx) && { live_updates: liveUpdatesCapability() }), + ...(declaresLiveCreativeUpdates(ctx) && { live_updates: liveUpdatesCapability(ctx.servedAdcpVersion) }), ...(declaresAssignmentSettings(ctx) && { supports_assignment_trackers: true, supports_assignment_landing_page: true, diff --git a/server/src/training-agent/tenants/router.ts b/server/src/training-agent/tenants/router.ts index d5e6664b1c3..5b4e78bfe99 100644 --- a/server/src/training-agent/tenants/router.ts +++ b/server/src/training-agent/tenants/router.ts @@ -1482,7 +1482,7 @@ function projectTenantCapabilities( } // Live creative updates (experimental, #8404) need the library and // the media buys that make a creative live, so only /sales declares. - structured.creative.live_updates = liveUpdatesCapability(); + structured.creative.live_updates = liveUpdatesCapability(servedVersion); // Assignment-scoped trackers and landing pages (3.3): sync_creatives // assign/replace, create/update media buy rows, and readback. structured.creative.supports_assignment_trackers = true; From e200442072ca985f289f1d9be47498c14541676f Mon Sep 17 00:00:00 2001 From: "Constantine.mirin" Date: Fri, 13 Nov 2026 19:00:26 +0100 Subject: [PATCH 3/3] docs: use the new spellings on the 3.3 release pages The execution readiness bullets in whats-new-in-3-3 and the 3.2-to-3.3 migration guide, and the live creative updates paragraph in the migration guide, now give input-required and clickthrough. --- docs/reference/migration/3-2-to-3-3.mdx | 4 ++-- docs/reference/whats-new-in-3-3.mdx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/reference/migration/3-2-to-3-3.mdx b/docs/reference/migration/3-2-to-3-3.mdx index d4955f688d3..172929631ff 100644 --- a/docs/reference/migration/3-2-to-3-3.mdx +++ b/docs/reference/migration/3-2-to-3-3.mdx @@ -712,7 +712,7 @@ The field is stable in 3.3, not experimental. It completes the basis vocabulary ### Live creative updates (experimental) -Optional (`creative.live_updates`, [#8488](https://github.com/adcontextprotocol/adcp/pull/8488), folding in staged activation from [#6780](https://github.com/adcontextprotocol/adcp/issues/6780)). A buyer changes a creative that is serving by syncing it again under the same `creative_id`. A seller that lists the feature id declares in `creative.live_updates` which kinds of change (`click_through`, `trackers`, `asset_content`, `format`) it applies in place and how the creative serves meanwhile: `uninterrupted` (decided inside the sync call), `staged` (the approved prior version keeps serving until the change is approved, then is promoted atomically; `on_rejection` is `keeps_prior` or `stops`), or `paused_for_review` (the existing lifecycle). Name and tag changes are always accepted. +Optional (`creative.live_updates`, [#8488](https://github.com/adcontextprotocol/adcp/pull/8488), folding in staged activation from [#6780](https://github.com/adcontextprotocol/adcp/issues/6780)). A buyer changes a creative that is serving by syncing it again under the same `creative_id`. A seller that lists the feature id declares in `creative.live_updates` which kinds of change (`clickthrough`, `trackers`, `asset_content`, `format`) it applies in place and how the creative serves meanwhile: `uninterrupted` (decided inside the sync call), `staged` (the approved prior version keeps serving until the change is approved, then is promoted atomically; `on_rejection` is `keeps_prior` or `stops`), or `paused_for_review` (the existing lifecycle). Name and tag changes are always accepted. - `list_creatives` and the `sync_creatives` item return `staged_update` while a change awaits promotion; `dry_run` returns the exact outcome of one change. There is no per-creative policy readback: the declaration is the general floor and `dry_run` answers for one change. - The new notification type `creative.staged_update_changed` reports a staged update's outcome (`promoted` or `rejected`) only to subscribers that register it. `creative.status_changed` is unchanged, so a buyer on an earlier release never receives an `approved` to `approved` event that its closed validator would reject. @@ -749,7 +749,7 @@ Each is optional and experimental: a seller that emits it lists the feature id i - **Seller offers (`media_buy.seller_offers`, [#8102](https://github.com/adcontextprotocol/adcp/pull/8102)).** A seller proposes a change to an accepted buy as a `committed` canonical proposal with `proposal_initiator: "seller"` and an `offer_reason` (`installment_postponed`, `installment_cancelled`, `preemption`, `under_delivery`, `seller_request`), a `media_buy_update` or `media_buy_cancellation` that carries `parent_proposal_id`, `media_buy_id`, and `base_media_buy_revision`. Buyers read offers through `list_proposals`, announced by the identifiers-only `proposal.offered` notification, and accept with `accept_proposal`, decline with `decline_proposals`, or revise with `refine_proposals`. `expires_at` is the response deadline; at it the signed terms stand and `accept_proposal` returns `PROPOSAL_EXPIRED`, so silence is never acceptance. A seller that lists the feature id MUST also list `media_buy.open_opportunities` and advertise `list_proposals`. Sellers MUST NOT send offers to callers served below 3.3. Pre-agreed seller changes inside `commercial_terms` are not part of 3.3. See [Seller offers](/docs/media-buy/product-discovery/proposal-negotiation#seller-offers). - **Price adjustments (`media_buy.price_adjustments`, [#7955](https://github.com/adcontextprotocol/adcp/pull/7955), [#7659](https://github.com/adcontextprotocol/adcp/issues/7659)).** A binding fixed-price pricing option (`cpm`, `vcpm`, `cpc`, `cpcv`, `cpv`, `cpp`, `flat_rate`) can carry an ordered table of `fee` and `discount` rows, each with a `rate` or a fixed `amount`, keyed by `format_option`, `placement_selection` (needs `overlay_support.placement_selection`), or `always`, and optionally a `valid_from` and `valid_until` window evaluated at the purchase `start_time`; a flight that crosses a window boundary is rejected. Selection rows fire at most once per dimension, then each firing row applies to one running total in declared order, rounding half away from zero at every step. A table is optional: without one, the price applies only to what was quoted. A seller never returns a table to a caller below 3.3. See [Selection-dependent price adjustments](/docs/media-buy/advanced-topics/pricing-models#selection-dependent-price-adjustments). -- **Execution readiness (`media_buy.execution_readiness`, [#7934](https://github.com/adcontextprotocol/adcp/pull/7934)).** For products that declare `execution_requirements`, a `get_products` request with `account` can set `include_execution_readiness: true`. The response carries an `execution_readiness` map keyed by `product_id`, versioned independently of `wholesale_feed_version` so public wholesale catalogs keep `cache_scope: "public"`. Each product and requirement reports `ready`, `setup_required`, `selection_required`, `input_required`, or `unknown` (the seller could not check, which is not the same as the account lacking the resource), with candidates whose `resource_id` is directly usable in the existing binding field. See [Per-account execution readiness](/docs/media-buy/specification#per-account-execution-readiness-experimental). +- **Execution readiness (`media_buy.execution_readiness`, [#7934](https://github.com/adcontextprotocol/adcp/pull/7934)).** For products that declare `execution_requirements`, a `get_products` request with `account` can set `include_execution_readiness: true`. The response carries an `execution_readiness` map keyed by `product_id`, versioned independently of `wholesale_feed_version` so public wholesale catalogs keep `cache_scope: "public"`. Each product and requirement reports `ready`, `setup_required`, `selection_required`, `input-required`, or `unknown` (the seller could not check, which is not the same as the account lacking the resource), with candidates whose `resource_id` is directly usable in the existing binding field. See [Per-account execution readiness](/docs/media-buy/specification#per-account-execution-readiness-experimental). - **Gender targeting (`media_buy.gender_targeting`, [#7925](https://github.com/adcontextprotocol/adcp/pull/7925)).** Core `demographics.gender` predicates with `female`, `male`, and `non_binary` value sets and an independent unknown group, whose `include_unknown` covers both unavailable gender and an explicit choice not to state it. Product-scoped capabilities, exact gender-only and mixed age and gender readback, and `sex_gender` governance classification come with it. Demographic delivery reporting stays age-only in 3.3, so a gender-targeted package has no gendered delivery readback. Buyers MUST NOT send core gender fields on a 3.2 contract. See [Core gender targeting](/docs/media-buy/advanced-topics/targeting#core-gender-targeting-3-3). - **Signal pricing groups (`signals.pricing_groups`, [#8058](https://github.com/adcontextprotocol/adcp/pull/8058), [#7898](https://github.com/adcontextprotocol/adcp/issues/7898)).** An optional `pricing_group` on `get_signals` results and on `Product.signal_targeting_options[]`, with `signal_pricing_group_rules[]` (`{pricing_group, pricing_combination: highest | each}`) on the response and on `signal_targeting_rules`, lets a seller price signals such as alternative age bands as one audience. A group with no rule and every ungrouped signal is charged `each`, and `flat_fee`, `per_unit`, `custom`, and `percent_of_media` options are always charged `each`. See [Signal pricing groups](/docs/media-buy/advanced-topics/targeting#signal-pricing-groups). - **Provider-initiated signal delivery (`signals.delivery_methods`, [#8044](https://github.com/adcontextprotocol/adcp/pull/8044), [#6552](https://github.com/adcontextprotocol/adcp/issues/6552)).** A signal agent declares `signals.delivery_methods[]` on `get_adcp_capabilities`, with the same pattern vocabulary as audience activation methods read from the send side. An `activate_signal` agent destination may pin a `delivery_method`; with no pattern and vendor intersection the destination fails with `UNSUPPORTED_FEATURE`, and a pin sent to an agent that doesn't list it is rejected the same way. Agent deployments gain `delivery_status` (`awaiting_read`, `delivered`, `failed`), a `delivery` descriptor, `delivery_error`, and an optional match outcome. A receiver field not yet established fails the destination with `ACCOUNT_SETUP_REQUIRED`. See [Declaring delivery methods](/docs/signals/tasks/activate_signal#declaring-delivery-methods). diff --git a/docs/reference/whats-new-in-3-3.mdx b/docs/reference/whats-new-in-3-3.mdx index db0425b15cf..45d4f3c0713 100644 --- a/docs/reference/whats-new-in-3-3.mdx +++ b/docs/reference/whats-new-in-3-3.mdx @@ -95,7 +95,7 @@ These shipped before beta.3 and are summarized here with the rest. Everything ma - **Review and escalation commitments ([#8417](https://github.com/adcontextprotocol/adcp/pull/8417), beta.1).** Products can declare creative-review timing in elapsed time or in business time on a seller calendar. Every other SLA stays elapsed time, as in 3.2. `report_media_buy_issue` and `get_media_buy_issue` give a human acknowledgement and resolution status for a critical report. - **Seller offers (experimental, `media_buy.seller_offers`, [#8102](https://github.com/adcontextprotocol/adcp/pull/8102), beta.2).** A seller can propose a change to an accepted buy: a postponed or canceled installment, a preemption, under-delivery. The offer is a `committed` canonical proposal with `proposal_initiator: "seller"` and an `offer_reason`, read through `list_proposals` and announced by the identifiers-only `proposal.offered` notification. The buyer accepts, declines, or revises it with the existing tasks. At `expires_at` the signed terms stand, and silence is never acceptance. Sellers never send offers to callers below 3.3. See [Seller offers](/docs/media-buy/product-discovery/proposal-negotiation#seller-offers). - **Price adjustments (experimental, `media_buy.price_adjustments`, [#7955](https://github.com/adcontextprotocol/adcp/pull/7955), beta.1).** A fixed-price pricing option can publish an ordered table of fee and discount rows keyed by format option, placement selection, or always, so a buyer computes the price of what it selects. The table is optional, and its resolution order and rounding are normative. See [Selection-dependent price adjustments](/docs/media-buy/advanced-topics/pricing-models#selection-dependent-price-adjustments). -- **Execution readiness (experimental, `media_buy.execution_readiness`, [#7934](https://github.com/adcontextprotocol/adcp/pull/7934), beta.1).** A `get_products` request with `account` can set `include_execution_readiness: true` to learn, per product and per `execution_requirements` entry, whether the account is `ready`, `setup_required`, `selection_required`, `input_required`, or `unknown`, with candidates whose `resource_id` fits the existing binding field. See [Per-account execution readiness](/docs/media-buy/specification#per-account-execution-readiness-experimental). +- **Execution readiness (experimental, `media_buy.execution_readiness`, [#7934](https://github.com/adcontextprotocol/adcp/pull/7934), beta.1).** A `get_products` request with `account` can set `include_execution_readiness: true` to learn, per product and per `execution_requirements` entry, whether the account is `ready`, `setup_required`, `selection_required`, `input-required`, or `unknown`, with candidates whose `resource_id` fits the existing binding field. See [Per-account execution readiness](/docs/media-buy/specification#per-account-execution-readiness-experimental). - **Signals (experimental).** Signal pricing groups (`signals.pricing_groups`, [#8058](https://github.com/adcontextprotocol/adcp/pull/8058)) let a seller price alternative age bands, for example, as one audience: `pricing_group` on a listing and `signal_pricing_group_rules[]` choose `highest` or `each`. See [Signal pricing groups](/docs/media-buy/advanced-topics/targeting#signal-pricing-groups). Provider-initiated delivery (`signals.delivery_methods`, [#8044](https://github.com/adcontextprotocol/adcp/pull/8044)) lets a signal agent declare the methods it can send over, a buyer pin one with `delivery_method`, and agent deployments report `delivery_status`. See [Declaring delivery methods](/docs/signals/tasks/activate_signal#declaring-delivery-methods). - **Gender targeting (experimental, `media_buy.gender_targeting`, [#7925](https://github.com/adcontextprotocol/adcp/pull/7925), beta.1).** Core `demographics.gender` targeting with female, male, and non_binary values, an independent unknown group, product-scoped capabilities, and exact readback for gender-only and mixed age and gender targeting. 3.3 reports demographic delivery by age only, so a gender-targeted package has no gendered delivery readback. Buyers never send core gender fields on a 3.2 contract. See [Core gender targeting](/docs/media-buy/advanced-topics/targeting#core-gender-targeting-3-3). - **Measurement provenance (experimental, `media_buy.measurement_provenance`, [#7940](https://github.com/adcontextprotocol/adcp/pull/7940), beta.2).** A shared `collection-mechanism` vocabulary (`set_top_box`, `acr`, `panel`, `server_logs`, `client_tracker`, `sdk`) says what a count is built from: `billing_measurement.counting_mechanisms[]` on `measurement_terms`, and `vendor_metric_provenance[]` on delivery metrics. Sellers serving an older pin omit both.