From 8129aa49535ec0334f561502a7e09150e7cc6579 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Fri, 9 Oct 2026 01:18:28 +0000 Subject: [PATCH 1/4] =?UTF-8?q?docs(schema):=203.3=20coherence=20sweep=20?= =?UTF-8?q?=E2=80=94=20x-added-in=20markers,=20offline=20metrics=20registr?= =?UTF-8?q?y,=20provenance=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- .../print-delivery-evidence-circulation.md | 2 +- docs/creative/channels/ooh.mdx | 2 +- docs/creative/channels/print.mdx | 2 +- .../media-buys/optimization-reporting.mdx | 24 ++++++ docs/reference/experimental-status.mdx | 1 + package.json | 2 +- static/schemas/source/core/audience-size.json | 1 + .../source/core/canonical-placement.json | 2 +- .../source/core/canonical-product.json | 2 +- .../core/catalog-ingestion-capability.json | 1 + .../schemas/source/core/definition-pin.json | 1 + .../schemas/source/core/delivery-metrics.json | 1 + .../source/core/dooh-inventory-summary.json | 1 + .../source/core/media-buy-features.json | 3 + .../source/core/placement-definition.json | 2 +- .../source/core/placement-evidence.json | 1 + static/schemas/source/core/placement.json | 2 +- static/schemas/source/core/product.json | 1 + .../source/core/reporting-webhook.json | 1 + .../core/spot-reporting-capability.json | 2 + .../enums/audience-match-rate-basis.json | 1 + .../source/enums/audience-size-precision.json | 1 + .../source/enums/definition-pin-ref-kind.json | 1 + .../source/enums/demographic-system.json | 2 +- static/schemas/source/enums/spot-status.json | 1 + .../reference-definition-changed.json | 1 + .../formats/canonical/video_hosted.json | 5 +- .../source/governance/sync-plans-request.json | 1 + .../source/media-buy/commercial-terms.json | 1 + .../get-media-buy-delivery-response.json | 4 + .../media-buy/sync-audiences-response.json | 3 + .../get-adcp-capabilities-response.json | 2 + tests/x-added-in-3-3-sweep.test.cjs | 79 +++++++++++++++++++ 33 files changed, 145 insertions(+), 11 deletions(-) create mode 100644 tests/x-added-in-3-3-sweep.test.cjs diff --git a/.changeset/print-delivery-evidence-circulation.md b/.changeset/print-delivery-evidence-circulation.md index 0d06e0251f5..3083a28515a 100644 --- a/.changeset/print-delivery-evidence-circulation.md +++ b/.changeset/print-delivery-evidence-circulation.md @@ -2,4 +2,4 @@ "adcontextprotocol": minor --- -Add experimental `print_metrics` to `core/delivery-metrics.json` for print proof-of-insertion and circulation reporting: `page_delivered`, `position_delivered`, `circulation_delivered`, `circulation_basis` (`audited` | `seller_modeled`), and a `placement-evidence` tearsheet `evidence`. `by_installment` rows inherit it, so each issue reports its own evidence and circulation. All fields are optional; `print_metrics` is added to the `available-metric` enum so sellers can commit to it and report a gap through `missing_metrics`. Documents the SHOULD-populate guidance for guaranteed print products and the rule that circulation falls back to the product's declared `delivery_measurement` absent a per-issue figure in `print.mdx`. Closes #5684, #5687. +Add experimental `print_metrics` (registered with the existing `ooh_metrics` under experimental feature id `media_buy.offline_delivery_metrics`) to `core/delivery-metrics.json` for print proof-of-insertion and circulation reporting: `page_delivered`, `position_delivered`, `circulation_delivered`, `circulation_basis` (`audited` | `seller_modeled`), and a `placement-evidence` tearsheet `evidence`. `by_installment` rows inherit it, so each issue reports its own evidence and circulation. All fields are optional; `print_metrics` is added to the `available-metric` enum so sellers can commit to it and report a gap through `missing_metrics`. Documents the SHOULD-populate guidance for guaranteed print products and the rule that circulation falls back to the product's declared `delivery_measurement` absent a per-issue figure in `print.mdx`. Closes #5684, #5687. diff --git a/docs/creative/channels/ooh.mdx b/docs/creative/channels/ooh.mdx index 08ceef4883b..14f3d040cef 100644 --- a/docs/creative/channels/ooh.mdx +++ b/docs/creative/channels/ooh.mdx @@ -45,6 +45,6 @@ Static OOH artwork uses `custom`, like [print](/docs/creative/channels/print): p The product or publisher declaration should document what pixels cannot express: physical dimensions (a standard bulletin is 14'×48'), bleed, substrate, CMYK profile, production overage counts, and material deadlines (printed materials are customarily due five business days before the scheduled posting date; painted bulletins thirty days — OAAA Bulletin Net Contract §2.2). Panel location, posting period, and audience measurement remain media-buy facts. -Static units have no play event and cannot fire trackers. Flights run in posting periods against published industry calendars, with posting customarily completed within five business days of the scheduled date and display terms running from the average posting date when materials were timely (§2.2 — reported as `average_posted_date` + `materials_timely`). Delivery is a modeled audience estimate from the measurement currency declared on the product/package (`measurement_terms.billing_measurement` — Geopath, Route, MOVE, COMMB); reconciliation uses `ooh_metrics` in delivery reporting, whose posting records are the channel's settlement artifact. Evidence follows the OAAA Proof of Performance clause (§3.5, a separate obligation from §2.2's posting deadline): a photo per bulletin within five *calendar* days of posting and after each rotation, and one representative close-up per creative design variation for poster showings. +Static units have no play event and cannot fire trackers. Flights run in posting periods against published industry calendars, with posting customarily completed within five business days of the scheduled date and display terms running from the average posting date when materials were timely (§2.2 — reported as `average_posted_date` + `materials_timely`). Delivery is a modeled audience estimate from the measurement currency declared on the product/package (`measurement_terms.billing_measurement` — Geopath, Route, MOVE, COMMB); reconciliation uses `ooh_metrics` in delivery reporting (experimental, feature id `media_buy.offline_delivery_metrics` in [Experimental status](/docs/reference/experimental-status), shared with print's `print_metrics`), whose posting records are the channel's settlement artifact. Evidence follows the OAAA Proof of Performance clause (§3.5, a separate obligation from §2.2's posting deadline): a photo per bulletin within five *calendar* days of posting and after each rotation, and one representative close-up per creative design variation for poster showings. See [Print](/docs/creative/channels/print), [Canonical formats](/docs/creative/canonical-formats), [Creative manifests](/docs/creative/creative-manifests), and [Media products](/docs/media-buy/product-discovery/media-products). diff --git a/docs/creative/channels/print.mdx b/docs/creative/channels/print.mdx index 2ef7d939a57..daddbc06320 100644 --- a/docs/creative/channels/print.mdx +++ b/docs/creative/channels/print.mdx @@ -71,7 +71,7 @@ For an installment scheduled at **09:00 UTC on Monday, October 12, 2026**, the b ## Delivery evidence and circulation -Print has no impression event. What a buyer reconciles against an insertion order is proof that the insertion ran, where it ran, and the circulation it ran against. A seller reports these on the existing `by_installment` breakdown of [`get_media_buy_delivery`](/docs/media-buy/task-reference/get_media_buy_delivery), one row per issue, using the optional `print_metrics` object that every `by_installment` row inherits from `delivery-metrics.json`. `print_metrics` is [experimental](/docs/reference/experimental-status) in AdCP 3.3. +Print has no impression event. What a buyer reconciles against an insertion order is proof that the insertion ran, where it ran, and the circulation it ran against. A seller reports these on the existing `by_installment` breakdown of [`get_media_buy_delivery`](/docs/media-buy/task-reference/get_media_buy_delivery), one row per issue, using the optional `print_metrics` object that every `by_installment` row inherits from `delivery-metrics.json`. `print_metrics` is [experimental](/docs/reference/experimental-status) in AdCP 3.3 under the feature id `media_buy.offline_delivery_metrics`, which also covers `ooh_metrics`. A seller that reports it declares that id in `experimental_features`. | Field | Meaning | |-------|---------| diff --git a/docs/media-buy/media-buys/optimization-reporting.mdx b/docs/media-buy/media-buys/optimization-reporting.mdx index 1915e8bcd08..613da276a81 100644 --- a/docs/media-buy/media-buys/optimization-reporting.mdx +++ b/docs/media-buy/media-buys/optimization-reporting.mdx @@ -202,6 +202,30 @@ The seller's bidding stack steered toward higher Adelaide attention scores; the If Adelaide's coverage on the inventory dropped below the seller's confidence threshold and they couldn't populate `vendor_metric_values` for this period, the same `(vendor, metric_id)` shows up in `missing_metrics` — same key across the entire lifecycle, so buyer reconciliation is a row-level join. +## Where did this number come from + +A delivery or audience figure can carry several independent provenance fields. Each answers a different question, and none substitutes for another: the provider that produced a row is not the demographic notation it is written in, and neither says how the figure was derived. Read them together. + +| Field | Question it answers | Value | Where it rides | Scope | +|---|---|---|---|---| +| `measurement_source` | Which audience-measurement **provider's data** produced this row? | Lowercase provider slug (`nielsen`, `geopath`, `barb`) | Row-level on [`delivery-metrics.json`](https://adcontextprotocol.org/schemas/v3/core/delivery-metrics.json), mirroring `delivery-forecast.json` so a forecast and its delivery reconcile on the same declaration | One delivery row (measured channels: radio, broadcast, OOH) | +| `vendor` | Which **vendor defined and computed** this vendor-defined metric? | BrandRef resolving to the vendor's `brand.json` and its `measurement` agent catalog | `reporting_capabilities.vendor_metrics[]` (declaration), `committed_metrics[]`, and `vendor_metric_values[]` (delivery) | One `(vendor, metric_id)` metric | +| `vendor_relationship` | How is that vendor related to the **seller**? | `first_party`, `affiliated`, or `third_party`; absence means undeclared, never `third_party` | Declared on `reporting_capabilities.vendor_metrics[]`; optionally echoed on `vendor_metric_values[]` | One `(seller, vendor)` pair | +| `completion_source` | Who **attested to the completion event** behind `completion_rate`? | `seller_attested` or `vendor_attested` | `qualifier.completion_source` on commitments, missing metrics, and delivery values | The `completion_rate` metric only | +| `demographic_system` | What **notation** is the demographic code written in? | Notation enum (for example `nielsen`, `barb`, `nmo`, `videoamp`, `custom`) | `by_demographic` rows, demographic reporting capability, forecasts, and `cpp` pricing | One demographic code. Notation only: it does not say whose panel produced the number. | +| `estimation_basis` | How was `estimated_impressions` **derived** (static OOH)? | `currency_measured` or `seller_modeled` | `ooh_metrics.estimated_impressions` (experimental) | One OOH delivery row | +| `circulation_basis` | How was `circulation_delivered` **derived** (print)? | `audited` or `seller_modeled` | `print_metrics.circulation_basis` on `by_installment` rows (experimental) | One print installment | +| `match_rate_basis` | Did the platform **report** `effective_match_rate`, or did the seller compute it? | `platform_reported` or `derived` | Each audience in a [`sync_audiences`](/docs/media-buy/task-reference/sync_audiences) result, beside `audience_size` and `targetable_size` | One audience match result | + +How the fields combine: + +- **Provider versus vendor.** `measurement_source` names a provider as a slug on a measured-channel row. `vendor` names a vendor as a BrandRef on a vendor-defined metric, and `vendor_relationship` says how that vendor stands relative to the seller. Neither is a quality ranking, and neither implies the other. +- **Tier, not provider.** `estimation_basis` and `circulation_basis` record how a figure was derived. The provider rides `measurement_source`, so an `audited` circulation figure still names its auditor there. Currencies change over time, so tier values stay stable while provider slugs vary. +- **Notation, not provenance.** `demographic_system` only fixes how `P25-54` or `M25-54` is written. Report the provider that measured the audience in `measurement_source`. +- **Attestation.** `completion_source` and `vendor_relationship` both distinguish seller-produced from independent figures. `completion_source` applies to `completion_rate` only. `vendor_relationship` applies to every vendor-defined metric. +- **Basis of a rate.** `match_rate_basis` qualifies a rate, not a count: a `derived` rate computed from approximate or bucketed `audience_size` values is itself approximate. +- **Different provenance, different measurement.** Rows that differ on `measurement_source`, `vendor`, `completion_source`, or a `*_basis` value are different measurements. Compare them with care rather than summing them into one figure. + ## Performance Monitoring ### Real-Time Metrics diff --git a/docs/reference/experimental-status.mdx b/docs/reference/experimental-status.mdx index 175aa4d5bac..f91baa00fcc 100644 --- a/docs/reference/experimental-status.mdx +++ b/docs/reference/experimental-status.mdx @@ -133,6 +133,7 @@ Schemas marked with `x-status: experimental` are the authoritative source. The c | `identity.trust_json` | The `trust/v1/trust.json` and `trust/v1/trust-acknowledgements.json` schemas, described in [trust.json (experimental)](/docs/reference/trust-json) ([RFC #7809](https://github.com/adcontextprotocol/adcp/issues/7809), [#7819](https://github.com/adcontextprotocol/adcp/pull/7819)). **Schemas only: nothing consumes this in 3.3.** No task, capability field, resolver, or storyboard reads it, there is no `identity.trust_url` capability field, `brand.json` behavior is unchanged, and no one is required to publish `/.well-known/trust.json`. Because no seller implements anything from this surface, there is nothing to declare in `experimental_features`: publishing a `trust.json` is not implementing this surface, and declaring `identity.trust_json` has no defined meaning until a consumer lands. | The document shape for agents, grants, and acknowledgements is published ahead of the behavior that would use it, so implementers can review it. Agents are identified by origin, one per origin, and the record carries no keys ([`specs/agent-identity-3-3.md`](https://github.com/adcontextprotocol/adcp/blob/main/specs/agent-identity-3-3.md)). The document name and its `/.well-known` path may change before graduation: the JournalList `trust.txt` naming outreach is still open. The record's location is derived from the agent's origin, so no capability pointer is planned. Resolver and no-downgrade rules land with the first consumer and may reshape the fields. | | `media_buy.collection_cards` | The `images`, `sample_content` and `url` fields on `core/collection.json`, the `brand_ref` field on `core/talent.json`, the `sample_content` role on `core/product-card-reference-asset.json` (#8092), and the `followers` field on `core/collection-distribution.json` entries, a `core/audience-size.json` count (#8098); the top-level `collections` array (`core/response-collection.json`) on the `get_products`, `list_products`, `request_proposals` and `refine_proposals` responses, and the `include_collections` request flag that opts into it (#8137). See [collection cards](/docs/media-buy/product-discovery/collections-and-installments#collection-cards-experimental). | Display and identity slice of the creator-card RFC, with a single adopter so far (Availnet, a creator marketplace) and no field-testing across parties yet. The array bounds, the use of `published-post` references for samples, the `brand_ref`-over-`brand_url` precedence, the opt-in flag, and the closed list of fields (`images`, `sample_content`, `url`, `description`, `season`, `cadence`, `distribution[].followers`) on which a response copy wins over `adagents.json` may change. `adagents.json` publishers cannot declare `experimental_features`, so consumers treat the collection fields as experimental wherever they appear. Sellers that emit the `sample_content` role or response `collections`, or accept `include_collections`, declare the id. Whether a follower count belongs on the distribution entry, and how sellers report platform-rounded counts, may change. Audience composition (`audience_evidence`) and average views (forecast `views`) reuse existing surfaces and are not part of this surface. | | `brand.collection_links` | The `collections` field on the [`get_brand_identity`](/docs/brand-protocol/tasks/get_brand_identity) response and its `collections` value in the request `fields` and response `available_fields` lists, plus `publisher_domain` on `brand.json` brand-level collection links (#8137) | Brand-side half of the creator-card work: lets a buyer go from a talent's `brand_ref` to the person's channels and confirm the link from both sides. One adopter so far (Availnet, a creator marketplace). The link shape, the public visibility level, and whether `seller_agent_url` stays on the link may change. `brand.json` publishers cannot declare `experimental_features`, so consumers treat `publisher_domain` there as experimental. Brand agents that return `collections` declare the id. | +| `media_buy.offline_delivery_metrics` | The `ooh_metrics` delivery block on `core/delivery-metrics.json` (panels, posting periods, posting records, share of voice, modeled `estimated_impressions` with `estimation_basis`; added in 3.2 and documented in [Out-of-home](/docs/creative/channels/ooh)), and the `print_metrics` delivery block (`page_delivered`, `position_delivered`, `circulation_delivered`, `circulation_basis`, `evidence`; added in 3.3 and documented in [Print creative](/docs/creative/channels/print#delivery-evidence-and-circulation)). Both carry `x-status: experimental`, both are addressable as `ooh_metrics` / `print_metrics` in the `available-metric` enum, and both reuse the channel-neutral `core/placement-evidence.json` artifact. | Delivery reporting for channels with no play or impression event — static OOH posting records and print proof-of-insertion with circulation — rests on one named adopter per channel and on contract conventions (OAAA proof of performance, tearsheets, circulation audits) that have not been field-tested across parties. `ooh_metrics` shipped in 3.2 without a registry row; this row registers it with `print_metrics` as a clarification, so a seller on 3.3 that reports either block declares the id. A seller on a 3.2 schema that already reports `ooh_metrics` is not made non-conformant retroactively. Field shapes, the `estimation_basis` / `circulation_basis` tier vocabularies, and the shared evidence artifact may change before they freeze. | Graduation progress and notice of upcoming breaking changes will be called out in the [release notes](/docs/reference/release-notes) for each 3.x release starting with 3.0 GA. diff --git a/package.json b/package.json index 18869018dc7..bb4481f8f5e 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "verify:cdn-artifacts-cutover": "node scripts/verify-cdn-artifacts-cutover.mjs", "typecheck": "tsc --project server/tsconfig.json --noEmit && npm run typecheck:fixed-trace-rollout-tests", "typecheck:fixed-trace-rollout-tests": "node scripts/typecheck-fixed-trace-rollout-tests.mjs", - "test:schemas": "node tests/schema-validation.test.cjs && node --test tests/sync-platform-id.test.cjs tests/assignment-landing-page.test.cjs tests/assignment-trackers.test.cjs tests/outcome-target.test.cjs tests/trusted-match-offer-creative-data.test.cjs tests/accessibility-violation-details.test.cjs tests/portfolio-routing-scope.test.cjs tests/catalog-item-availability-updates.test.cjs tests/catalog-ingestion-capabilities.test.cjs tests/sdk-implementation-metadata.test.cjs tests/seller-optimized-package-controls.test.cjs tests/compact-product-lifecycle-storyboards.test.cjs tests/timezone-resolution-storyboards.test.cjs tests/total-budget-redistribution-currency.test.cjs tests/dooh-allocation.test.cjs tests/identity-absence-coherence.test.cjs tests/execution-requirements.test.cjs tests/schema-deprecation-metadata.test.cjs tests/products-only-brief-compatibility.test.cjs tests/async-identity-convergence.test.cjs tests/creative-rotation.test.cjs tests/canonical-forecast-point-parity.test.cjs tests/creative-revisions.test.cjs tests/creative-delivery-contracts.test.cjs tests/tracker-execution-contracts.test.cjs tests/tracker-execution-package-integration.test.cjs tests/metric-identity-coherence.test.cjs tests/print-metrics.test.cjs tests/daypart-time-of-day.test.cjs tests/audience-size-range.test.cjs tests/viewable-rate-optimization-goal.test.cjs tests/sort-contract-delivery-reporting.test.cjs tests/time-based-views-contract.test.cjs tests/metric-qualifier-parity.test.cjs tests/requested-metrics-contract.test.cjs tests/auto-breakdown-negotiation-contract.test.cjs tests/format-delivery-reporting-contract.test.cjs tests/inventory-delivery-reporting-contract.test.cjs tests/property-delivery-conformance.test.cjs tests/lint-schema-enum-drift.test.cjs tests/format-asset-type-parity.test.cjs tests/canonical-manifest-constructibility.test.cjs tests/synthetic-depiction.test.cjs tests/creative-rendering-authority.test.cjs tests/buyer-reason.test.cjs tests/error-recovery-vectors.test.cjs tests/reporting-status-contract.test.cjs tests/reporting-reconciliation-fixture.test.cjs tests/reporting-core-fixture.test.cjs tests/reporting-native-version-ref-bounds.test.cjs tests/definition-pin-vectors.test.cjs tests/trust-json-v1.test.cjs tests/refine-proposals-expected-confirmation-at.test.cjs tests/hold-policy.test.cjs tests/hold-policy-vectors.test.cjs tests/collection-catalog.test.cjs tests/collection-copies-vectors.test.cjs && npm run test:premium-display-formats && npm run test:geo-region-targeting", + "test:schemas": "node tests/schema-validation.test.cjs && node --test tests/sync-platform-id.test.cjs tests/assignment-landing-page.test.cjs tests/assignment-trackers.test.cjs tests/outcome-target.test.cjs tests/trusted-match-offer-creative-data.test.cjs tests/accessibility-violation-details.test.cjs tests/portfolio-routing-scope.test.cjs tests/catalog-item-availability-updates.test.cjs tests/catalog-ingestion-capabilities.test.cjs tests/sdk-implementation-metadata.test.cjs tests/seller-optimized-package-controls.test.cjs tests/compact-product-lifecycle-storyboards.test.cjs tests/timezone-resolution-storyboards.test.cjs tests/total-budget-redistribution-currency.test.cjs tests/dooh-allocation.test.cjs tests/x-added-in-3-3-sweep.test.cjs tests/identity-absence-coherence.test.cjs tests/execution-requirements.test.cjs tests/schema-deprecation-metadata.test.cjs tests/products-only-brief-compatibility.test.cjs tests/async-identity-convergence.test.cjs tests/creative-rotation.test.cjs tests/canonical-forecast-point-parity.test.cjs tests/creative-revisions.test.cjs tests/creative-delivery-contracts.test.cjs tests/tracker-execution-contracts.test.cjs tests/tracker-execution-package-integration.test.cjs tests/metric-identity-coherence.test.cjs tests/print-metrics.test.cjs tests/daypart-time-of-day.test.cjs tests/audience-size-range.test.cjs tests/viewable-rate-optimization-goal.test.cjs tests/sort-contract-delivery-reporting.test.cjs tests/time-based-views-contract.test.cjs tests/metric-qualifier-parity.test.cjs tests/requested-metrics-contract.test.cjs tests/auto-breakdown-negotiation-contract.test.cjs tests/format-delivery-reporting-contract.test.cjs tests/inventory-delivery-reporting-contract.test.cjs tests/property-delivery-conformance.test.cjs tests/lint-schema-enum-drift.test.cjs tests/format-asset-type-parity.test.cjs tests/canonical-manifest-constructibility.test.cjs tests/synthetic-depiction.test.cjs tests/creative-rendering-authority.test.cjs tests/buyer-reason.test.cjs tests/error-recovery-vectors.test.cjs tests/reporting-status-contract.test.cjs tests/reporting-reconciliation-fixture.test.cjs tests/reporting-core-fixture.test.cjs tests/reporting-native-version-ref-bounds.test.cjs tests/definition-pin-vectors.test.cjs tests/trust-json-v1.test.cjs tests/refine-proposals-expected-confirmation-at.test.cjs tests/hold-policy.test.cjs tests/hold-policy-vectors.test.cjs tests/collection-catalog.test.cjs tests/collection-copies-vectors.test.cjs && npm run test:premium-display-formats && npm run test:geo-region-targeting", "test:performance-feedback": "node --test --test-force-exit --test-timeout=30000 tests/performance-feedback-contract.test.cjs", "test:dist-schema-version-ids": "node --test --test-force-exit --test-timeout=30000 tests/dist-schema-version-ids.test.cjs", "test:examples": "node tests/example-validation-simple.test.cjs && npm run test:tmp-context-merge", diff --git a/static/schemas/source/core/audience-size.json b/static/schemas/source/core/audience-size.json index 55d6f8862b5..0f72a202f91 100644 --- a/static/schemas/source/core/audience-size.json +++ b/static/schemas/source/core/audience-size.json @@ -3,6 +3,7 @@ "$id": "/schemas/core/audience-size.json", "title": "Audience Size", "description": "A count of people reported as a range with a stated precision, so a seller never has to invent a point value for a size its platform only reports as an interval, an approximation, or a bucket. lower_bound MUST be less than or equal to upper_bound, and MUST equal upper_bound when precision is 'exact' (JSON Schema cannot express this cross-field rule; it is enforced by conformance, not validation). A seller MUST NOT use this object to disclose a size below its minimum audience size; follow the audience's too_small status and minimum_size instead. Defined once so every surface that reports an audience size uses the same shape.", + "x-added-in": "3.3.0", "type": "object", "properties": { "lower_bound": { diff --git a/static/schemas/source/core/canonical-placement.json b/static/schemas/source/core/canonical-placement.json index b901790d363..74dceba7ce7 100644 --- a/static/schemas/source/core/canonical-placement.json +++ b/static/schemas/source/core/canonical-placement.json @@ -70,7 +70,7 @@ "$ref": "/schemas/enums/dooh-motion-type.json", "description": "Physical motion capability of a visual DOOH screen, not an accepted-format declaration. Omit for audio-only placements." }, - "location": { "$ref": "#/definitions/CanonicalDoohLocation" } + "location": { "$ref": "#/definitions/CanonicalDoohLocation", "x-added-in": "3.3.0" } }, "x-adcp-validation": { "verifier_constraints": { diff --git a/static/schemas/source/core/canonical-product.json b/static/schemas/source/core/canonical-product.json index 66d9021dd49..53e88d2d617 100644 --- a/static/schemas/source/core/canonical-product.json +++ b/static/schemas/source/core/canonical-product.json @@ -46,7 +46,7 @@ "pricing_options": { "type": "array", "items": { "$ref": "/schemas/core/canonical-pricing-option.json" }, "minItems": 1 }, "forecast": { "$ref": "/schemas/core/canonical-delivery-forecast.json" }, "reporting_capabilities": { "$ref": "/schemas/core/canonical-reporting-capabilities.json" }, - "dooh_inventory_summary": { "$ref": "/schemas/core/dooh-inventory-summary.json" }, + "dooh_inventory_summary": { "$ref": "/schemas/core/dooh-inventory-summary.json", "x-added-in": "3.3.0" }, "measurement_terms": { "$ref": "/schemas/core/canonical-measurement-terms.json", "description": "Default billing measurement and makegood terms inherited by a direct purchase unless a negotiated proposal replaces them." diff --git a/static/schemas/source/core/catalog-ingestion-capability.json b/static/schemas/source/core/catalog-ingestion-capability.json index 04845ae4dae..96684556176 100644 --- a/static/schemas/source/core/catalog-ingestion-capability.json +++ b/static/schemas/source/core/catalog-ingestion-capability.json @@ -3,6 +3,7 @@ "$id": "/schemas/core/catalog-ingestion-capability.json", "title": "Catalog Ingestion Capability", "description": "Seller-wide ingestion contract for buyer-managed catalogs sent through sync_catalogs. Presence in media_buy.features requires catalog_management: true. A seller MUST reject an unlisted catalog type, ingestion mode, feed format, or explicitly supplied content_id_type outside a declared supported_content_id_types list with UNSUPPORTED_FEATURE before mutation, including on dry runs. A missing declaration means support is unknown, not unrestricted. Product catalog_types remains authoritative for serving eligibility.", + "x-added-in": "3.3.0", "x-adcp-hoist": true, "type": "object", "properties": { diff --git a/static/schemas/source/core/definition-pin.json b/static/schemas/source/core/definition-pin.json index 6e1ecb662f6..9f4807c64c5 100644 --- a/static/schemas/source/core/definition-pin.json +++ b/static/schemas/source/core/definition-pin.json @@ -3,6 +3,7 @@ "$id": "/schemas/core/definition-pin.json", "title": "Definition Pin", "description": "Seller-authored integrity pin over one third-party-published definition that a proposal's commercial_terms reference: a publisher-catalog format option, publisher-catalog placement, adagents.json collection, or data-provider signal. content_digest is the lowercase-hex SHA-256 of the RFC 8785 (JCS) serialization of the kind's closed projection of the published entry, defined normatively in docs/media-buy/specification.mdx#definition-pins. Pins live inside commercial_terms, so terms_digest covers the pin but not the definition it names: a published definition can change while terms_digest still verifies, and re-verifying the pin is what detects it. Request inputs never carry pins.", + "x-added-in": "3.3.0", "type": "object", "discriminator": { "propertyName": "ref_kind" diff --git a/static/schemas/source/core/delivery-metrics.json b/static/schemas/source/core/delivery-metrics.json index c3057932393..18b7a947608 100644 --- a/static/schemas/source/core/delivery-metrics.json +++ b/static/schemas/source/core/delivery-metrics.json @@ -445,6 +445,7 @@ "additionalProperties": true }, "print_metrics": { + "x-added-in": "3.3.0", "type": "object", "x-status": "experimental", "description": "Print-specific metrics (only included for print campaigns). Experimental in AdCP 3.3. Report one object per installment (issue) on `by_installment` rows, which inherit this schema; the object is not meaningful at package, daily, or total grain. It describes one insertion per row and carries the proof the insertion ran and the circulation it ran against. Sellers SHOULD populate `evidence` for guaranteed print products; when a seller cannot, the gap is reported through `by_package[].missing_metrics` rather than by silently omitting the field. Nothing here is schema-required.", diff --git a/static/schemas/source/core/dooh-inventory-summary.json b/static/schemas/source/core/dooh-inventory-summary.json index f4a26440442..33f55cd4c00 100644 --- a/static/schemas/source/core/dooh-inventory-summary.json +++ b/static/schemas/source/core/dooh-inventory-summary.json @@ -3,6 +3,7 @@ "$id": "/schemas/core/dooh-inventory-summary.json", "title": "DOOH Inventory Summary", "description": "Aggregate geographic footprint for a DOOH/OOH network product, for network/regional products where per-screen location disclosure via placement dooh_placement_attributes.location is not practical. The two are not mutually exclusive: a product may disclose concrete placement locations, an aggregate summary, or both.", + "x-added-in": "3.3.0", "type": "object", "definitions": { "DoohVenueCount": { diff --git a/static/schemas/source/core/media-buy-features.json b/static/schemas/source/core/media-buy-features.json index d50f339d516..ff65007cf5e 100644 --- a/static/schemas/source/core/media-buy-features.json +++ b/static/schemas/source/core/media-buy-features.json @@ -19,6 +19,7 @@ "description": "Supports sync_catalogs task for catalog feed management with platform review and approval" }, "catalog_ingestion": { + "x-added-in": "3.3.0", "allOf": [ { "$ref": "/schemas/core/catalog-ingestion-capability.json" } ], @@ -33,10 +34,12 @@ "description": "Seller has per-package snapshot infrastructure for the reporting contract. When true, the seller MUST populate `package.committed_metrics` on committed `create_media_buy` responses where `confirmed_at` is non-null, MUST omit `package.committed_metrics` while `confirmed_at` is null for a provisional buy, and MUST honor append-only mid-flight metric additions via `update_media_buy`. The unified `committed_metrics` array (per the metric-accountability design) covers both standard and vendor-defined metric entries, so a single flag is load-bearing. Buyers filtering on this flag are detecting 'this seller can stamp the reporting contract,' which closes the audit gap from PR #3510 where absence of `committed_metrics` was indistinguishable between 'didn't snapshot' and 'snapshot infrastructure not implemented.'" }, "supports_property_breakdown": { + "x-added-in": "3.3.0", "type": "boolean", "description": "Seller-wide rollup for property-grain delivery reporting. When true, the seller supports property-grain reporting and MUST return `by_property` (with `by_property_truncated`, `by_property_suppressed`, `by_property_sorted_by`, and `by_property_sort_direction`) from `get_media_buy_delivery` on every product whose `reporting_capabilities.supports_property_breakdown` is `true` (on sandbox accounts this includes products seeded by a test controller) when `reporting_dimensions.property` is requested. The per-product flag remains authoritative and binds on its own: a product declaring it is bound even when this rollup is false or absent, and a buyer MUST NOT infer support on a product from the rollup. The rollup lets a buyer or conformance runner detect support before discovering products and is the grading gate: when false or absent the seller makes no seller-wide claim and gated conformance scenarios grade `not_applicable`." }, "supports_installment_property_breakdown": { + "x-added-in": "3.3.0", "type": "boolean", "description": "Seller-wide rollup for installment × property delivery reporting. When true, the seller supports installment × property reporting and MUST return `by_installment_property` (with `by_installment_property_truncated`, `by_installment_property_suppressed`, `by_installment_property_sorted_by`, and `by_installment_property_sort_direction`) from `get_media_buy_delivery` on every product whose `reporting_capabilities.supports_installment_property_breakdown` is `true` (on sandbox accounts this includes products seeded by a test controller) when `reporting_dimensions.installment_property` is requested. The per-product flag remains authoritative and binds on its own, even when this rollup is false or absent. When false or absent the seller makes no seller-wide claim and gated conformance scenarios grade `not_applicable`." }, diff --git a/static/schemas/source/core/placement-definition.json b/static/schemas/source/core/placement-definition.json index 60aad92d915..6d3259c69e9 100644 --- a/static/schemas/source/core/placement-definition.json +++ b/static/schemas/source/core/placement-definition.json @@ -70,7 +70,7 @@ "$ref": "/schemas/enums/dooh-motion-type.json", "description": "Physical motion capability of a visual DOOH screen, not an accepted-format declaration. Omit for audio-only placements." }, - "location": { "$ref": "#/definitions/PublisherDoohLocation" } + "location": { "$ref": "#/definitions/PublisherDoohLocation", "x-added-in": "3.3.0" } }, "x-adcp-validation": { "verifier_constraints": { diff --git a/static/schemas/source/core/placement-evidence.json b/static/schemas/source/core/placement-evidence.json index 792a2111e66..3ff351fb983 100644 --- a/static/schemas/source/core/placement-evidence.json +++ b/static/schemas/source/core/placement-evidence.json @@ -4,6 +4,7 @@ "title": "Placement Evidence", "x-status": "experimental", "description": "Seller-attested evidence artifact proving a physical placement ran — a posting photograph for an OOH face, a tearsheet page for a print insertion. Channel-neutral: this record carries the artifact facts; what the artifact proves (posting, rotation, insertion) is declared by the containing record's event semantics. Third-party audit convention: the artifact should be geotagged and timestamped within the placement period, with the creative legible.", + "x-added-in": "3.2.0", "type": "object", "properties": { "url": { diff --git a/static/schemas/source/core/placement.json b/static/schemas/source/core/placement.json index 6deb321ac73..e01259c56d4 100644 --- a/static/schemas/source/core/placement.json +++ b/static/schemas/source/core/placement.json @@ -70,7 +70,7 @@ "$ref": "/schemas/enums/dooh-motion-type.json", "description": "Physical motion capability of a visual DOOH screen, not an accepted-format declaration. Omit for audio-only placements." }, - "location": { "$ref": "#/definitions/ProductDoohLocation" } + "location": { "$ref": "#/definitions/ProductDoohLocation", "x-added-in": "3.3.0" } }, "x-adcp-validation": { "verifier_constraints": { diff --git a/static/schemas/source/core/product.json b/static/schemas/source/core/product.json index 507e196c75b..ce9256b5b63 100644 --- a/static/schemas/source/core/product.json +++ b/static/schemas/source/core/product.json @@ -189,6 +189,7 @@ "$ref": "/schemas/core/reporting-capabilities.json" }, "dooh_inventory_summary": { + "x-added-in": "3.3.0", "$ref": "/schemas/core/dooh-inventory-summary.json", "description": "Aggregate DOOH/OOH geographic footprint for network/regional products where per-placement dooh_placement_attributes.location is not practical. See dooh-inventory-summary.json for the full description." }, diff --git a/static/schemas/source/core/reporting-webhook.json b/static/schemas/source/core/reporting-webhook.json index 8fab0d7a993..100941028c5 100644 --- a/static/schemas/source/core/reporting-webhook.json +++ b/static/schemas/source/core/reporting-webhook.json @@ -11,6 +11,7 @@ "description": "Webhook endpoint URL for reporting notifications" }, "operation_id": { + "x-added-in": "3.3.0", "type": "string", "description": "Buyer-supplied correlation identifier for the reporting registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field on each `media_buy_delivery` report sent to this registration (see `mcp-webhook-payload.json`). Sellers MUST NOT derive `operation_id` by parsing the reporting webhook URL — the URL is opaque to the seller. This is the canonical registration channel for `operation_id` on reporting deliveries; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the wire-level source of truth is this field. Buyers SHOULD generate a unique value per reporting registration (UUID recommended); it is independent of any `push_notification_config.operation_id` on the same request. The member is optional in 3.x for schema compatibility, but a seller MUST NOT emit `media_buy_delivery` webhooks to a registration that omits it (fail closed) — otherwise the required webhook envelope cannot be populated. The field is expected to become required in 4.0.", "minLength": 1, diff --git a/static/schemas/source/core/spot-reporting-capability.json b/static/schemas/source/core/spot-reporting-capability.json index 2a16d28f640..3aa4527df92 100644 --- a/static/schemas/source/core/spot-reporting-capability.json +++ b/static/schemas/source/core/spot-reporting-capability.json @@ -14,6 +14,7 @@ "uniqueItems": true }, "available_statuses": { + "x-added-in": "3.3.0", "type": "array", "x-status": "experimental", "description": "Spot lifecycle statuses this product reports in by_spot. Absent, or an empty array, means the seller reports only aired rows without a spot_status, the AdCP 3.2 behavior. When present, a status that is not listed is not reported: the absence of scheduled, preempted, or makegood rows then proves nothing about whether any such spots exist, and a buyer MUST NOT read it as nothing scheduled, nothing preempted, or no makegoods. 'aired' is always reported and need not be listed. Sellers MUST NOT emit this field to callers pinned below AdCP 3.3, or to callers with no declared pin.", @@ -23,6 +24,7 @@ "uniqueItems": true }, "supports_break_position": { + "x-added-in": "3.3.0", "type": "boolean", "x-status": "experimental", "description": "Whether the seller populates break_position on by_spot rows. When false or absent, a row without break_position means the position is not reported, not that the spot ran outside a commercial break. Sellers MUST NOT emit this field to callers pinned below AdCP 3.3, or to callers with no declared pin." diff --git a/static/schemas/source/enums/audience-match-rate-basis.json b/static/schemas/source/enums/audience-match-rate-basis.json index 28a2fdc758c..a0bb4c3e1b9 100644 --- a/static/schemas/source/enums/audience-match-rate-basis.json +++ b/static/schemas/source/enums/audience-match-rate-basis.json @@ -3,6 +3,7 @@ "$id": "/schemas/enums/audience-match-rate-basis.json", "title": "Audience Match Rate Basis", "description": "Where an audience match rate came from. Qualifies effective_match_rate on a sync_audiences result so a buyer can tell a rate the platform reported from one computed from approximate counts.", + "x-added-in": "3.3.0", "type": "string", "enum": [ "platform_reported", diff --git a/static/schemas/source/enums/audience-size-precision.json b/static/schemas/source/enums/audience-size-precision.json index 791acf56ff0..ef8aaaf04f9 100644 --- a/static/schemas/source/enums/audience-size-precision.json +++ b/static/schemas/source/enums/audience-size-precision.json @@ -3,6 +3,7 @@ "$id": "/schemas/enums/audience-size-precision.json", "title": "Audience Size Precision", "description": "How precisely a seller reports an audience size range. Describes the lower_bound and upper_bound of an audience-size object, not the quality of the underlying match.", + "x-added-in": "3.3.0", "type": "string", "enum": [ "exact", diff --git a/static/schemas/source/enums/definition-pin-ref-kind.json b/static/schemas/source/enums/definition-pin-ref-kind.json index 8f0eeea6dbc..243704f62d0 100644 --- a/static/schemas/source/enums/definition-pin-ref-kind.json +++ b/static/schemas/source/enums/definition-pin-ref-kind.json @@ -3,6 +3,7 @@ "$id": "/schemas/enums/definition-pin-ref-kind.json", "title": "Definition Pin Reference Kind", "description": "Kinds of third-party-published definition that a definition pin can cover. Each kind names the adagents.json array the pinned entry is read from. Property lists, collection lists, product-local format options, seller-inline placements, product and signal-source signals, and default-mode selections are not pinnable.", + "x-added-in": "3.3.0", "type": "string", "enum": [ "publisher_format_option", diff --git a/static/schemas/source/enums/demographic-system.json b/static/schemas/source/enums/demographic-system.json index 78718438615..255cb0f2668 100644 --- a/static/schemas/source/enums/demographic-system.json +++ b/static/schemas/source/enums/demographic-system.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/enums/demographic-system.json", "title": "Demographic System", - "description": "Audience measurement systems for demographic notation in GRP forecasts and pricing, across video and audio channels. Specifies how demographics are written, not whose panel produced the numbers — provider identity is measurement_source.", + "description": "Audience measurement systems for demographic notation in GRP forecasts and pricing, across video and audio channels. Specifies how demographics are written, not whose panel produced the numbers — provider identity is measurement_source. The `nmo` and `videoamp` values were added in AdCP 3.3.", "type": "string", "enum": [ "nielsen", diff --git a/static/schemas/source/enums/spot-status.json b/static/schemas/source/enums/spot-status.json index 712253e9c85..f8933e2ef36 100644 --- a/static/schemas/source/enums/spot-status.json +++ b/static/schemas/source/enums/spot-status.json @@ -3,6 +3,7 @@ "$id": "/schemas/enums/spot-status.json", "title": "Spot Status", "description": "Lifecycle status of a by_spot row. Experimental in AdCP 3.3. A spot_id moves scheduled → aired or scheduled → preempted; a makegood is a separate row, with its own spot_id, that replaces a preempted spot.", + "x-added-in": "3.3.0", "type": "string", "x-status": "experimental", "enum": [ diff --git a/static/schemas/source/error-details/reference-definition-changed.json b/static/schemas/source/error-details/reference-definition-changed.json index 1cceb3cc3e8..2205099066e 100644 --- a/static/schemas/source/error-details/reference-definition-changed.json +++ b/static/schemas/source/error-details/reference-definition-changed.json @@ -3,6 +3,7 @@ "$id": "/schemas/error-details/reference-definition-changed.json", "title": "Reference Definition Changed Details", "description": "Recommended details shape for REFERENCE_DEFINITION_CHANGED errors, one error per drifted pin. error.field identifies the failing pin as a path into the proposal's commercial_terms, such as commercial_terms.definition_pins[2]. The details object carries the digest the definition resolves to now so the buyer can compare it with the pin it holds. Producer-authored values MUST remain buyer-safe and MUST NOT contain fetch targets, resolver errors, internal identifiers, or other producer-private detail.", + "x-added-in": "3.3.0", "type": "object", "properties": { "current_content_digest": { diff --git a/static/schemas/source/formats/canonical/video_hosted.json b/static/schemas/source/formats/canonical/video_hosted.json index 7aa1706f340..0ae5430edef 100644 --- a/static/schemas/source/formats/canonical/video_hosted.json +++ b/static/schemas/source/formats/canonical/video_hosted.json @@ -58,7 +58,7 @@ "video_codecs": { "type": "array", "items": { "type": "string", "enum": ["h264", "h265", "vp8", "vp9", "av1", "prores", "mpeg2", "avc_intra", "xavc"] }, - "description": "Accepted video codecs. `mpeg2`, `avc_intra`, and `xavc` are broadcast mezzanine codec families: `mpeg2` covers MPEG-2 video (typically 4:2:2 in broadcast delivery, as in XDCAM HD422), `avc_intra` is AVC-Intra (H.264 intra-only), and `xavc` is XAVC (H.264 Intra / Long GOP). These are distinct from `h264`: a product that accepts `h264` does not thereby accept them. Wrapper is declared via `containers` and bitrate via the bitrate fields; chroma subsampling and profile are not encoded in the slug." + "description": "Accepted video codecs. `mpeg2`, `avc_intra`, and `xavc` are broadcast mezzanine codec families: `mpeg2` covers MPEG-2 video (typically 4:2:2 in broadcast delivery, as in XDCAM HD422), `avc_intra` is AVC-Intra (H.264 intra-only), and `xavc` is XAVC (H.264 Intra / Long GOP). These are distinct from `h264`: a product that accepts `h264` does not thereby accept them. Wrapper is declared via `containers` and bitrate via the bitrate fields; chroma subsampling and profile are not encoded in the slug. The `mpeg2`, `avc_intra`, and `xavc` values were added in AdCP 3.3." }, "audio_codecs": { "type": "array", @@ -67,9 +67,10 @@ "containers": { "type": "array", "items": { "type": "string", "enum": ["mp4", "webm", "mov", "mxf"] }, - "description": "Accepted container formats. `mxf` (SMPTE Material Exchange Format) is the delivery wrapper for broadcast television spots." + "description": "Accepted container formats. `mxf` (SMPTE Material Exchange Format) is the delivery wrapper for broadcast television spots. The `mxf` value was added in AdCP 3.3." }, "loudness": { + "x-added-in": "3.3.0", "type": "object", "x-status": "experimental", "description": "Audio loudness delivery requirement for the video asset's audio track, typical for broadcast television. Experimental in AdCP 3.3: shape may change in a 3.x release with notice per docs/reference/experimental-status. Field semantics match `loudness_lufs`, `loudness_tolerance_db`, and `true_peak_dbfs` on `audio_hosted`, plus a named `standard`. The numeric fields are the values a receiver checks; `standard` is a label for the requirement and does not imply numeric values when they are omitted. Sellers that populate this object MUST list `creative.broadcast_loudness` in `experimental_features`.", diff --git a/static/schemas/source/governance/sync-plans-request.json b/static/schemas/source/governance/sync-plans-request.json index d45c3f19388..4a9bfa744b0 100644 --- a/static/schemas/source/governance/sync-plans-request.json +++ b/static/schemas/source/governance/sync-plans-request.json @@ -98,6 +98,7 @@ } }, "periods": { + "x-added-in": "3.3.0", "type": "array", "minItems": 1, "maxItems": 366, diff --git a/static/schemas/source/media-buy/commercial-terms.json b/static/schemas/source/media-buy/commercial-terms.json index 671c3be165d..b50575442a3 100644 --- a/static/schemas/source/media-buy/commercial-terms.json +++ b/static/schemas/source/media-buy/commercial-terms.json @@ -124,6 +124,7 @@ } }, "definition_pins": { + "x-added-in": "3.3.0", "type": "array", "items": { "$ref": "/schemas/core/definition-pin.json" }, "minItems": 1, diff --git a/static/schemas/source/media-buy/get-media-buy-delivery-response.json b/static/schemas/source/media-buy/get-media-buy-delivery-response.json index bbf6855236b..88eca86d8de 100644 --- a/static/schemas/source/media-buy/get-media-buy-delivery-response.json +++ b/static/schemas/source/media-buy/get-media-buy-delivery-response.json @@ -942,6 +942,7 @@ "x-entity": "creative" }, "spot_status": { + "x-added-in": "3.3.0", "$ref": "/schemas/enums/spot-status.json", "x-status": "experimental", "description": "Lifecycle status of this row. Absent means aired, which preserves AdCP 3.2 behavior. A scheduled row later becomes aired (same spot_id) or preempted. A makegood row is a separate airing, with its own spot_id, that carries replaces_spot_id; a planned replacement is a scheduled row that already carries it. Sellers declare the statuses they report in supports_spot_breakdown.available_statuses; a status not declared there is not reported, which is different from no spot having that status." @@ -952,12 +953,14 @@ "description": "RFC 3339 timestamp when the spot actually aired. Its presence proves an airing; it is not inferred from impressions. Required when spot_status is aired or makegood, or when spot_status is absent. MUST be absent when spot_status is scheduled or preempted." }, "scheduled_at": { + "x-added-in": "3.3.0", "type": "string", "format": "date-time", "x-status": "experimental", "description": "RFC 3339 timestamp of the planned airing slot. Required when spot_status is scheduled or preempted (for a preempted row, the last slot that was scheduled). The seller may move a scheduled spot until it airs, so the value can change between reports while spot_id does not; a buyer that needs the original slot compares reports. Optional on aired and makegood rows, where it records the last slot scheduled before airing." }, "replaces_spot_id": { + "x-added-in": "3.3.0", "type": "string", "minLength": 1, "x-status": "experimental", @@ -965,6 +968,7 @@ "x-entity": "spot_airing" }, "break_position": { + "x-added-in": "3.3.0", "type": "object", "x-status": "experimental", "description": "Where the spot ran inside its commercial break. Sellers declare support in supports_spot_breakdown.supports_break_position; omission means the position is not reported. On a scheduled row the value is the planned position and may change before airing.", diff --git a/static/schemas/source/media-buy/sync-audiences-response.json b/static/schemas/source/media-buy/sync-audiences-response.json index 83304f466ba..967afd3d875 100644 --- a/static/schemas/source/media-buy/sync-audiences-response.json +++ b/static/schemas/source/media-buy/sync-audiences-response.json @@ -80,14 +80,17 @@ "maximum": 1 }, "match_rate_basis": { + "x-added-in": "3.3.0", "$ref": "/schemas/enums/audience-match-rate-basis.json", "description": "Where effective_match_rate came from: 'platform_reported' when the destination platform supplied it, 'derived' when the seller computed it, for example from approximate counts. Describes effective_match_rate, so it is only meaningful when that field is present. When absent, the basis is unspecified." }, "audience_size": { + "x-added-in": "3.3.0", "$ref": "/schemas/core/audience-size.json", "description": "People matched on the platform, as a range with a stated precision. Use this when the platform reports an interval, approximation, or bucket rather than an exact count. May accompany or replace matched_count: a seller that reports a size but no matched count omits matched_count. Populated when status is 'ready' and the seller can report it. A seller MUST NOT disclose a size below its minimum audience size, so this field MUST be omitted when status is 'too_small'; the existing too_small status and minimum_size govern that case." }, "targetable_size": { + "x-added-in": "3.3.0", "$ref": "/schemas/core/audience-size.json", "description": "People the platform can serve to now, as a range with a stated precision. Differs from audience_size when only part of the matched audience is eligible to be served, for example because of surface, consent, or recency limits. Reported as one value: how a platform with several serving surfaces combines them is left to the seller. Populated when status is 'ready' and the seller can report it. A seller MUST NOT disclose a size below its minimum audience size, so this field MUST be omitted when status is 'too_small'." }, diff --git a/static/schemas/source/protocol/get-adcp-capabilities-response.json b/static/schemas/source/protocol/get-adcp-capabilities-response.json index bef9cffcc93..8c6aabcbdb4 100644 --- a/static/schemas/source/protocol/get-adcp-capabilities-response.json +++ b/static/schemas/source/protocol/get-adcp-capabilities-response.json @@ -2468,6 +2468,7 @@ "description": "Trailing window (in days) over which this governance agent aggregates committed spend when evaluating dollar-valued thresholds (reallocation_threshold, human_review triggers, registry-policy floors). Required for fragmentation defense: without aggregation, a buyer can split a single large spend into many sub-threshold commits across plans / task surfaces / time and bypass every dollar-gated escalation. Aggregation is keyed on (buyer_agent, seller_agent, account_id) and spans all spend-commit task types. Upper bound 365 represents a one-year trailing window (fiscal-year alignment with grace); governance agents needing longer scopes negotiate via operator sign-off, not this capability. No schema default: absence of this field indicates the governance agent has not committed to any aggregation window and buyers MUST assume per-commit evaluation only (the fragmentation attack surface is open). A declared value of 30 is a common starting point but is not implied by omission. Buyers depending on a specific window for compliance MUST check this capability before relying on aggregation semantics — an agent declaring 7 days does not defend against fragmentation spread across a 30-day quarter-end push." }, "supports_budget_periods": { + "x-added-in": "3.3.0", "type": "boolean", "description": "Whether this governance agent accepts `plans[].budget.periods[]` on sync_plans and enforces it on check_governance: deriving the containing period for dated actions, denying straddling or over-period commitments, and returning `budget_period_id`. When false or absent, buyers MUST NOT rely on period limits being enforced and SHOULD NOT send `budget.periods`; an agent that does not implement the field cannot honor a time-partitioned budget." }, @@ -3165,6 +3166,7 @@ } }, "operation_sources": { + "x-added-in": "3.3.0", "type": "array", "description": "Transport bindings from which this verifier resolves the AdCP operation name it matches against supported_for, warn_for, and required_for. 'mcp_tools_call' resolves from the decoded params.name of a JSON-RPC tools/call request. 'a2a_invocation_skill' resolves from the decoded skill of the sole DataPart of an A2A message (SendMessage, SendStreamingMessage, message/send, message/stream, and the HTTP+JSON and gRPC equivalents), and commits the verifier to the fail-closed and skill-to-handler binding rules and the hardening clauses in docs/building/by-layer/L1/security.mdx#operation-resolution-over-a2a for every A2A version and binding the endpoint serves. A verifier MUST list a source only if it implements that source's rules and passes its conformance vectors, including the hardening tier, and a present array MUST list every source the verifier supports. Absent means a verifier that has not declared this: a counterparty MUST NOT infer that required_for is enforced over A2A. The field is self-attested and informational; buyers SHOULD sign any operation listed in required_for regardless of it. It never changes the protocol_methods_* namespace, which matches the JSON-RPC method only.", "items": { diff --git a/tests/x-added-in-3-3-sweep.test.cjs b/tests/x-added-in-3-3-sweep.test.cjs new file mode 100644 index 00000000000..22fea9f5bdc --- /dev/null +++ b/tests/x-added-in-3-3-sweep.test.cjs @@ -0,0 +1,79 @@ +/** + * 3.3 coherence: every property and enum file new in 3.3 carries + * `x-added-in: "3.3.0"`, the only machine-readable "new in 3.3" marker SDK + * generators have. Surfaces that shipped in 3.2 must not carry it. + */ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const SRC = path.join(__dirname, '..', 'static', 'schemas', 'source'); +const load = (f) => JSON.parse(fs.readFileSync(path.join(SRC, f), 'utf8')); +const at = (obj, keys) => keys.reduce((o, k) => o[k], obj); + +const WHOLE_FILE_3_3 = [ + 'core/audience-size.json', + 'enums/audience-size-precision.json', + 'enums/audience-match-rate-basis.json', + 'core/definition-pin.json', + 'enums/definition-pin-ref-kind.json', + 'error-details/reference-definition-changed.json', + 'enums/spot-status.json', + 'core/dooh-inventory-summary.json', + 'core/catalog-ingestion-capability.json', +]; + +const PROPERTIES_3_3 = [ + ['media-buy/sync-audiences-response.json', ['oneOf', 0, 'properties', 'audiences', 'items', 'properties', 'audience_size']], + ['media-buy/sync-audiences-response.json', ['oneOf', 0, 'properties', 'audiences', 'items', 'properties', 'targetable_size']], + ['media-buy/sync-audiences-response.json', ['oneOf', 0, 'properties', 'audiences', 'items', 'properties', 'match_rate_basis']], + ['media-buy/commercial-terms.json', ['properties', 'definition_pins']], + ['core/spot-reporting-capability.json', ['properties', 'available_statuses']], + ['core/spot-reporting-capability.json', ['properties', 'supports_break_position']], + ['core/media-buy-features.json', ['properties', 'catalog_ingestion']], + ['core/media-buy-features.json', ['properties', 'supports_property_breakdown']], + ['core/media-buy-features.json', ['properties', 'supports_installment_property_breakdown']], + ['core/canonical-placement.json', ['definitions', 'CanonicalDoohPlacementAttributes', 'properties', 'location']], + ['core/placement-definition.json', ['definitions', 'PublisherDoohPlacementAttributes', 'properties', 'location']], + ['core/placement.json', ['definitions', 'ProductDoohPlacementAttributes', 'properties', 'location']], + ['core/canonical-product.json', ['properties', 'dooh_inventory_summary']], + ['core/product.json', ['properties', 'dooh_inventory_summary']], + ['core/delivery-metrics.json', ['properties', 'print_metrics']], + ['core/reporting-webhook.json', ['properties', 'operation_id']], + ['governance/sync-plans-request.json', ['properties', 'plans', 'items', 'properties', 'budget', 'properties', 'periods']], + ['protocol/get-adcp-capabilities-response.json', ['properties', 'governance', 'properties', 'supports_budget_periods']], + ['protocol/get-adcp-capabilities-response.json', ['properties', 'request_signing', 'properties', 'operation_sources']], + ['formats/canonical/video_hosted.json', ['properties', 'loudness']], +]; + +test('whole-file 3.3 schemas and enums carry x-added-in 3.3.0', () => { + for (const f of WHOLE_FILE_3_3) assert.equal(load(f)['x-added-in'], '3.3.0', f); +}); + +test('3.3 properties carry x-added-in 3.3.0', () => { + for (const [f, keys] of PROPERTIES_3_3) { + assert.equal(at(load(f), keys)['x-added-in'], '3.3.0', `${f} ${keys.join('.')}`); + } +}); + +test('by_spot lifecycle fields carry x-added-in 3.3.0', () => { + const resp = load('media-buy/get-media-buy-delivery-response.json'); + const spot = resp.properties.media_buy_deliveries.items.properties.by_package.items.allOf[1].properties.by_spot.items.allOf[1].properties; + for (const k of ['spot_status', 'scheduled_at', 'replaces_spot_id', 'break_position']) { + assert.equal(spot[k]['x-added-in'], '3.3.0', k); + } +}); + +test('added enum values are noted in the enum description', () => { + assert.match(load('enums/demographic-system.json').description, /`nmo` and `videoamp`.*3\.3/); + const video = load('formats/canonical/video_hosted.json').properties; + assert.match(video.video_codecs.description, /`mpeg2`, `avc_intra`, and `xavc`.*3\.3/); + assert.match(video.containers.description, /`mxf`.*3\.3/); +}); + +test('surfaces shipped in 3.2 are not marked 3.3', () => { + // placement-evidence and ooh_metrics are in the 3.2 release artifacts. + assert.equal(load('core/placement-evidence.json')['x-added-in'], '3.2.0'); + assert.equal(load('core/delivery-metrics.json').properties.ooh_metrics['x-added-in'], undefined); +}); From 7029e2af838e5680f2d459b7a0426ada86b89902 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Fri, 9 Oct 2026 23:35:21 +0000 Subject: [PATCH 2/4] docs(schema): extend x-added-in sweep to newly merged 3.3 surfaces Marks the unsupported-targeting-dimension details and trust.json v1 schemas, and guards the expected_confirmation_at sites (including refine_proposals). Co-Authored-By: Claude Sonnet 5.5 --- .../unsupported-targeting-dimension.json | 1 + .../source/trust/v1/trust-acknowledgements.json | 1 + static/schemas/source/trust/v1/trust.json | 1 + tests/x-added-in-3-3-sweep.test.cjs | 14 ++++++++++++++ 4 files changed, 17 insertions(+) diff --git a/static/schemas/source/error-details/unsupported-targeting-dimension.json b/static/schemas/source/error-details/unsupported-targeting-dimension.json index bc1af6356f6..96e40d65d79 100644 --- a/static/schemas/source/error-details/unsupported-targeting-dimension.json +++ b/static/schemas/source/error-details/unsupported-targeting-dimension.json @@ -3,6 +3,7 @@ "$id": "/schemas/error-details/unsupported-targeting-dimension.json", "title": "Unsupported Targeting Dimension Details", "description": "Recommended details shape for UNSUPPORTED_FEATURE errors rejecting a targeting dimension that the product or seller does not support, or that was previously accepted and has been removed. Sellers MUST NOT silently drop the dimension. `error.field` SHOULD point at the targeting path (for example `packages[0].targeting_overlay.geo_countries`). Sellers echo only what the caller could already read from capabilities or Product `overlay_support`.", + "x-added-in": "3.3.0", "type": "object", "properties": { "unsupported_dimension": { diff --git a/static/schemas/source/trust/v1/trust-acknowledgements.json b/static/schemas/source/trust/v1/trust-acknowledgements.json index 0bd8e7e6c48..5d2e388bc04 100644 --- a/static/schemas/source/trust/v1/trust-acknowledgements.json +++ b/static/schemas/source/trust/v1/trust-acknowledgements.json @@ -4,6 +4,7 @@ "title": "trust.json acknowledgements sub-document", "x-status": "experimental", "description": "Bulk acknowledgements referenced from trust.json acknowledgements_url, for organizations (e.g. ad networks and SSPs) that accept thousands of grants. Kept off the signature-verification fetch path. Every entry follows the trust.json acknowledgement rules: agent_url MUST be an agents[] entry in the referencing trust.json, and terms come only from the grantor's document. Removing an entry withdraws the acknowledgement; verifiers SHOULD apply the grant source's retention rules (for adcp:adagents, the revoked-publisher-domain retention) before treating a removal as permanent. Normative-sounding language (MUST, SHOULD) in these descriptions records the intended design for a later release and is not enforced by anything in 3.3. Third parties, crawlers and registries MUST NOT treat a published trust.json as authority in 3.3. The v1 in the path is a namespace, not a stability promise: while x-status is experimental the schema may change in breaking ways. EXPERIMENTAL (x-status: experimental, feature id identity.trust_json): schemas only. Nothing in AdCP 3.3 reads, requires, or verifies this document, and brand.json behavior is unchanged. The document name and its /.well-known path may change before graduation (the JournalList trust.txt naming outreach is still open), so do not treat the file name as frozen. See RFC #7809.", + "x-added-in": "3.3.0", "type": "object", "properties": { "$schema": { diff --git a/static/schemas/source/trust/v1/trust.json b/static/schemas/source/trust/v1/trust.json index 207a5b35ed0..d6026727d37 100644 --- a/static/schemas/source/trust/v1/trust.json +++ b/static/schemas/source/trust/v1/trust.json @@ -4,6 +4,7 @@ "title": "trust.json", "x-status": "experimental", "description": "An organization's agent trust record, hosted at /.well-known/trust.json on the organization's own host. Protocol-neutral core: the agents the organization runs (agents[]), the authority it delegates to others (grants[]), and the grants it has accepted from others (acknowledgements[]). Protocol-specific semantics live in namespaced roles and scopes (e.g. 'adcp:sales') and in per-protocol `profiles` objects; this draft defines the `adcp` profile. The organization a record speaks for is the host serving /.well-known/trust.json — never a value inside the document. Versioned independently of any one protocol. Normative-sounding language (MUST, SHOULD) in these descriptions records the intended design for a later release and is not enforced by anything in 3.3. Third parties, crawlers and registries MUST NOT treat a published trust.json as authority in 3.3. The v1 in the path is a namespace, not a stability promise: while x-status is experimental the schema may change in breaking ways. EXPERIMENTAL (x-status: experimental, feature id identity.trust_json): schemas only. Nothing in AdCP 3.3 reads, requires, or verifies this document, and brand.json behavior is unchanged. The document name and its /.well-known path may change before graduation (the JournalList trust.txt naming outreach is still open), so do not treat the file name as frozen. An agent URL anywhere in this record identifies the agent by its canonical origin (see docs/reference/url-canonicalization.mdx): one agent per origin. See RFC #7809 and specs/agent-identity-3-3.md.", + "x-added-in": "3.3.0", "definitions": { "domain": { "type": "string", diff --git a/tests/x-added-in-3-3-sweep.test.cjs b/tests/x-added-in-3-3-sweep.test.cjs index 22fea9f5bdc..2598e01b95f 100644 --- a/tests/x-added-in-3-3-sweep.test.cjs +++ b/tests/x-added-in-3-3-sweep.test.cjs @@ -22,6 +22,9 @@ const WHOLE_FILE_3_3 = [ 'enums/spot-status.json', 'core/dooh-inventory-summary.json', 'core/catalog-ingestion-capability.json', + 'error-details/unsupported-targeting-dimension.json', + 'trust/v1/trust.json', + 'trust/v1/trust-acknowledgements.json', ]; const PROPERTIES_3_3 = [ @@ -51,6 +54,17 @@ test('whole-file 3.3 schemas and enums carry x-added-in 3.3.0', () => { for (const f of WHOLE_FILE_3_3) assert.equal(load(f)['x-added-in'], '3.3.0', f); }); +test('expected_confirmation_at sites carry x-added-in 3.3.0', () => { + const sites = [ + ['media-buy/create-media-buy-async-response-submitted.json', ['properties', 'expected_confirmation_at']], + ['media-buy/create-media-buy-response.json', ['oneOf', 2, 'properties', 'expected_confirmation_at']], + ['media-buy/media-buy-commitment-response.json', ['oneOf', 2, 'properties', 'expected_confirmation_at']], + ['media-buy/refine-proposals-async-response-submitted.json', ['properties', 'expected_confirmation_at']], + ['media-buy/refine-proposals-response.json', ['properties', 'expected_confirmation_at']], + ]; + for (const [f, keys] of sites) assert.equal(at(load(f), keys)['x-added-in'], '3.3.0', f); +}); + test('3.3 properties carry x-added-in 3.3.0', () => { for (const [f, keys] of PROPERTIES_3_3) { assert.equal(at(load(f), keys)['x-added-in'], '3.3.0', `${f} ${keys.join('.')}`); From aec8b28e22d731246fa0d5777390dab39bccf33f Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Mon, 19 Oct 2026 15:46:37 +0000 Subject: [PATCH 3/4] docs(schema): mark remaining newly merged 3.3 surfaces with x-added-in Co-Authored-By: Claude Sonnet 5.5 --- .../source/core/assets/display-tag-asset.json | 12 ++++++++++++ .../schemas/source/core/assets/html-asset.json | 3 +++ .../source/core/assets/video-brief-asset.json | 1 + .../schemas/source/core/assets/zip-asset.json | 3 +++ .../source/core/installment-deadlines.json | 1 + .../source/core/reporting-status-issue.json | 1 + .../governance/check-governance-request.json | 1 + .../governance/check-governance-response.json | 1 + .../media-buy/list-collections-request.json | 1 + .../media-buy/list-collections-response.json | 1 + .../media-buy/list-products-response.json | 2 +- .../media-buy/refine-proposals-response.json | 1 + .../media-buy/sync-audiences-response.json | 1 + .../media-buy/sync-event-sources-response.json | 1 + .../get-adcp-capabilities-response.json | 3 +++ tests/x-added-in-3-3-sweep.test.cjs | 17 +++++++++++++++++ 16 files changed, 49 insertions(+), 1 deletion(-) diff --git a/static/schemas/source/core/assets/display-tag-asset.json b/static/schemas/source/core/assets/display-tag-asset.json index 8910eddfcf1..bb847711e36 100644 --- a/static/schemas/source/core/assets/display-tag-asset.json +++ b/static/schemas/source/core/assets/display-tag-asset.json @@ -17,16 +17,19 @@ "description": "Exact macro tokens present anywhere in this representation. Tokens remain byte-preserved until the resolver named by each declaration substitutes them." }, "width": { + "x-added-in": "3.3.0", "type": "integer", "minimum": 1, "description": "Rendered width in logical pixels of the entry canvas, independent of target placement and embedded image density. For expandable units, this is the initial width. When set, height must also be set. Mutually exclusive with sizes." }, "height": { + "x-added-in": "3.3.0", "type": "integer", "minimum": 1, "description": "Rendered height in logical pixels of the entry canvas. For expandable units, this is the initial height. When set, width must also be set. Mutually exclusive with sizes." }, "sizes": { + "x-added-in": "3.3.0", "type": "array", "minItems": 1, "items": { @@ -65,16 +68,19 @@ }, "provenance": { "$ref": "/schemas/core/provenance.json" }, "width": { + "x-added-in": "3.3.0", "type": "integer", "minimum": 1, "description": "Rendered width in logical pixels of the entry canvas, independent of target placement and embedded image density. For expandable units, this is the initial width. When set, height must also be set. Mutually exclusive with sizes." }, "height": { + "x-added-in": "3.3.0", "type": "integer", "minimum": 1, "description": "Rendered height in logical pixels of the entry canvas. For expandable units, this is the initial height. When set, width must also be set. Mutually exclusive with sizes." }, "sizes": { + "x-added-in": "3.3.0", "type": "array", "minItems": 1, "items": { @@ -120,16 +126,19 @@ }, "provenance": { "$ref": "/schemas/core/provenance.json" }, "width": { + "x-added-in": "3.3.0", "type": "integer", "minimum": 1, "description": "Rendered width in logical pixels of the entry canvas, independent of target placement and embedded image density. For expandable units, this is the initial width. When set, height must also be set. Mutually exclusive with sizes." }, "height": { + "x-added-in": "3.3.0", "type": "integer", "minimum": 1, "description": "Rendered height in logical pixels of the entry canvas. For expandable units, this is the initial height. When set, width must also be set. Mutually exclusive with sizes." }, "sizes": { + "x-added-in": "3.3.0", "type": "array", "minItems": 1, "items": { @@ -182,16 +191,19 @@ }, "provenance": { "$ref": "/schemas/core/provenance.json" }, "width": { + "x-added-in": "3.3.0", "type": "integer", "minimum": 1, "description": "Rendered width in logical pixels of the entry canvas, independent of target placement and embedded image density. For expandable units, this is the initial width. When set, height must also be set. Mutually exclusive with sizes." }, "height": { + "x-added-in": "3.3.0", "type": "integer", "minimum": 1, "description": "Rendered height in logical pixels of the entry canvas. For expandable units, this is the initial height. When set, width must also be set. Mutually exclusive with sizes." }, "sizes": { + "x-added-in": "3.3.0", "type": "array", "minItems": 1, "items": { diff --git a/static/schemas/source/core/assets/html-asset.json b/static/schemas/source/core/assets/html-asset.json index d00e9db6ffa..942d6e63ee4 100644 --- a/static/schemas/source/core/assets/html-asset.json +++ b/static/schemas/source/core/assets/html-asset.json @@ -42,16 +42,19 @@ } }, "width": { + "x-added-in": "3.3.0", "type": "integer", "minimum": 1, "description": "Rendered width in logical pixels of the entry canvas, independent of target placement and embedded image density. For expandable units, this is the initial width. When set, height must also be set. Mutually exclusive with sizes." }, "height": { + "x-added-in": "3.3.0", "type": "integer", "minimum": 1, "description": "Rendered height in logical pixels of the entry canvas. For expandable units, this is the initial height. When set, width must also be set. Mutually exclusive with sizes." }, "sizes": { + "x-added-in": "3.3.0", "type": "array", "minItems": 1, "items": { diff --git a/static/schemas/source/core/assets/video-brief-asset.json b/static/schemas/source/core/assets/video-brief-asset.json index 849e64202a8..c38663810b4 100644 --- a/static/schemas/source/core/assets/video-brief-asset.json +++ b/static/schemas/source/core/assets/video-brief-asset.json @@ -3,6 +3,7 @@ "$id": "/schemas/core/assets/video-brief-asset.json", "title": "Video Brief Asset", "description": "Typed per-segment generation brief supplied in a creative manifest's video_brief slot. Carries the existing VideoBrief structure as an asset, independently of the generated video output.", + "x-added-in": "3.3.0", "type": "object", "properties": { "asset_type": { "type": "string", "const": "video_brief" }, diff --git a/static/schemas/source/core/assets/zip-asset.json b/static/schemas/source/core/assets/zip-asset.json index e93b404dfa4..2b7927572f8 100644 --- a/static/schemas/source/core/assets/zip-asset.json +++ b/static/schemas/source/core/assets/zip-asset.json @@ -63,16 +63,19 @@ } }, "width": { + "x-added-in": "3.3.0", "type": "integer", "minimum": 1, "description": "Rendered width in logical pixels of the entry canvas, independent of target placement and embedded image density. For expandable units, this is the initial width. When set, height must also be set. Mutually exclusive with sizes." }, "height": { + "x-added-in": "3.3.0", "type": "integer", "minimum": 1, "description": "Rendered height in logical pixels of the entry canvas. For expandable units, this is the initial height. When set, width must also be set. Mutually exclusive with sizes." }, "sizes": { + "x-added-in": "3.3.0", "type": "array", "minItems": 1, "items": { diff --git a/static/schemas/source/core/installment-deadlines.json b/static/schemas/source/core/installment-deadlines.json index bbf8ed7eae2..bfd7bff52d6 100644 --- a/static/schemas/source/core/installment-deadlines.json +++ b/static/schemas/source/core/installment-deadlines.json @@ -16,6 +16,7 @@ "description": "Last date/time to cancel without penalty (ISO 8601). Cancellations after this point may incur fees per the seller's terms." }, "change_cutoff": { + "x-added-in": "3.3.0", "type": "string", "format": "date-time", "description": "Last date/time at which a change to or stop of this installment (creative swap, pause, cancellation instruction) still takes operational effect: broadcast traffic/log close, OOH posting-cycle lock, print issue close, cinema reel lock. Distinct from `cancellation_deadline` (penalty-free cancel window; a cancellation after `change_cutoff` may still be billed even if within terms) and `booking_deadline` (new bookings). When both are present, `change_cutoff` SHOULD NOT precede `booking_deadline`. Absent means the seller makes no statement." diff --git a/static/schemas/source/core/reporting-status-issue.json b/static/schemas/source/core/reporting-status-issue.json index cb525cc9709..d6c3392cb12 100644 --- a/static/schemas/source/core/reporting-status-issue.json +++ b/static/schemas/source/core/reporting-status-issue.json @@ -98,6 +98,7 @@ "x-entity": "reporting_obligation" }, "reporting_revision_id": { + "x-added-in": "3.3.0", "type": "string", "minLength": 1, "maxLength": 255, diff --git a/static/schemas/source/governance/check-governance-request.json b/static/schemas/source/governance/check-governance-request.json index ed2f8e6bfcd..eb2a52feb44 100644 --- a/static/schemas/source/governance/check-governance-request.json +++ b/static/schemas/source/governance/check-governance-request.json @@ -244,6 +244,7 @@ "default": "media_buy" }, "budget_period_id": { + "x-added-in": "3.3.0", "type": "string", "minLength": 1, "maxLength": 64, diff --git a/static/schemas/source/governance/check-governance-response.json b/static/schemas/source/governance/check-governance-response.json index 61f960234b8..0f05ea1b921 100644 --- a/static/schemas/source/governance/check-governance-response.json +++ b/static/schemas/source/governance/check-governance-response.json @@ -130,6 +130,7 @@ "x-entity": "governance_plan" }, "budget_period_id": { + "x-added-in": "3.3.0", "type": "string", "minLength": 1, "maxLength": 64, diff --git a/static/schemas/source/media-buy/list-collections-request.json b/static/schemas/source/media-buy/list-collections-request.json index 5d454f6dce0..4d77f717e2e 100644 --- a/static/schemas/source/media-buy/list-collections-request.json +++ b/static/schemas/source/media-buy/list-collections-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/media-buy/list-collections-request.json", "title": "List Collections Request", "description": "Experimental (media_buy.collection_catalog), added in AdCP 3.3. Read the selling agent's catalog of collection cards: look up specific collections by key, or browse with filters and pages. Returns the same core/response-collection.json copies, under the same authority rules, as include_collections on product and proposal reads, and never returns products. Not to be confused with list_collection_lists, which lists buyer-owned collection targeting lists on a governance agent. Side-effect free: the call never provisions an account. It is the bootstrap and repair read for collection_card.* and collection_catalog.bulk_change webhooks, which are registered only through sync_accounts notification_configs; the request takes no push_notification_config, so a call never creates a subscription. Sellers implementing it MUST list media_buy.collection_catalog and media_buy.collection_cards in experimental_features. A seller MUST NOT serve this task to a caller whose adcp_version is below 3.3; it fails the request with UNSUPPORTED_FEATURE.", + "x-added-in": "3.3.0", "x-tool-summary": "List a seller's collection cards (shows, creators, podcasts) by key or with filters, without products.", "x-status": "experimental", "type": "object", diff --git a/static/schemas/source/media-buy/list-collections-response.json b/static/schemas/source/media-buy/list-collections-response.json index 17c27fd4cf4..cee4a0caebb 100644 --- a/static/schemas/source/media-buy/list-collections-response.json +++ b/static/schemas/source/media-buy/list-collections-response.json @@ -3,6 +3,7 @@ "$id": "/schemas/media-buy/list-collections-response.json", "title": "List Collections Response", "description": "Experimental (media_buy.collection_catalog), added in AdCP 3.3. One page of the selling agent's collection cards, or confirmation that the selected catalog has not changed. Entries are core/response-collection.json copies under the authority rules defined there and for include_collections: adagents.json stays the root of authorization and the source for definition pins, a copy wins only on images, sample_content, url, description, season, cadence and distribution[].followers, and a copy of an undeclared collection or from an agent adagents.json does not authorize is unverified display data that never feeds carriage verification. For the same key, account and moment, the copy MUST equal the one a product or proposal read with include_collections returns. The response carries no products, pricing, availability or account terms.", + "x-added-in": "3.3.0", "x-status": "experimental", "type": "object", "discriminator": { diff --git a/static/schemas/source/media-buy/list-products-response.json b/static/schemas/source/media-buy/list-products-response.json index a194234874a..8890629c134 100644 --- a/static/schemas/source/media-buy/list-products-response.json +++ b/static/schemas/source/media-buy/list-products-response.json @@ -8,7 +8,7 @@ "propertyName": "outcome" }, "properties": { - "adcp_version": { "$ref": "/schemas/core/version-envelope.json#/properties/adcp_version" }, + "adcp_version": { "$ref": "/schemas/core/version-envelope.json#/properties/adcp_version", "x-added-in": "3.3.0" }, "outcome": { "type": "string", "enum": ["listed", "unchanged"], diff --git a/static/schemas/source/media-buy/refine-proposals-response.json b/static/schemas/source/media-buy/refine-proposals-response.json index 3244651e379..e28f561c8c2 100644 --- a/static/schemas/source/media-buy/refine-proposals-response.json +++ b/static/schemas/source/media-buy/refine-proposals-response.json @@ -65,6 +65,7 @@ "description": "Requested product actions not satisfied by every returned draft. This is a subset of the request product_changes map and is valid only on partial or unable results." }, "drifted_pins": { + "x-added-in": "3.3.0", "type": "array", "description": "Pins from the source proposal's commercial_terms.definition_pins[] whose published definition no longer resolves to the pinned content_digest, in the pins' canonical order. Required on, and valid only with, reason_code definition_changed.", "items": { diff --git a/static/schemas/source/media-buy/sync-audiences-response.json b/static/schemas/source/media-buy/sync-audiences-response.json index 967afd3d875..19a804a569b 100644 --- a/static/schemas/source/media-buy/sync-audiences-response.json +++ b/static/schemas/source/media-buy/sync-audiences-response.json @@ -34,6 +34,7 @@ "description": "Name of the audience" }, "platform_id": { + "x-added-in": "3.3.0", "type": "string", "description": "Platform-specific ID assigned to the audience by the seller's ad platform. Introduced in AdCP 3.3. When reporting a platform identifier in 3.3 and later 3.x responses, sellers MUST emit both platform_id and the deprecated seller_id alias with identical values. Buyers MUST prefer platform_id and fall back to seller_id only when platform_id is absent. This identifies the object on the platform, not the seller.", "$comment": "x-entity deliberately omitted: seller-opaque platform identifier; AdCP requests reference audience_id instead." diff --git a/static/schemas/source/media-buy/sync-event-sources-response.json b/static/schemas/source/media-buy/sync-event-sources-response.json index 7ddf234e280..812bbee17ab 100644 --- a/static/schemas/source/media-buy/sync-event-sources-response.json +++ b/static/schemas/source/media-buy/sync-event-sources-response.json @@ -34,6 +34,7 @@ "description": "Name of the event source" }, "platform_id": { + "x-added-in": "3.3.0", "type": "string", "description": "Platform-specific ID assigned to the event source by the seller's ad platform. Introduced in AdCP 3.3. When reporting a platform identifier in 3.3 and later 3.x responses, sellers MUST emit both platform_id and the deprecated seller_id alias with identical values. Buyers MUST prefer platform_id and fall back to seller_id only when platform_id is absent. This identifies the object on the platform, not the seller.", "$comment": "x-entity deliberately omitted: seller-opaque platform identifier; AdCP requests reference event_source_id instead." diff --git a/static/schemas/source/protocol/get-adcp-capabilities-response.json b/static/schemas/source/protocol/get-adcp-capabilities-response.json index 8c6aabcbdb4..1fea4a705e2 100644 --- a/static/schemas/source/protocol/get-adcp-capabilities-response.json +++ b/static/schemas/source/protocol/get-adcp-capabilities-response.json @@ -555,6 +555,7 @@ ] }, "implementation": { + "x-added-in": "3.3.0", "type": "object", "description": "Optional, self-reported AdCP SDK identity and integration metadata, introduced in 3.3. Omission means SDK use is unknown. An empty sdks array explicitly reports no AdCP SDK use. This metadata is advisory: it does not establish SDK approval, prove deployed code execution, change capability obligations, or justify skipping conformance checks without independently trusted evidence covering this endpoint and integration. Buyers MUST NOT use it for version negotiation.", "properties": { @@ -1105,6 +1106,7 @@ "description": "OAuth authorization endpoint for obtaining operator-level credentials. Present when the seller supports OAuth for operator authentication. The agent directs the operator to this URL to authenticate and obtain a bearer token. If absent and require_operator_auth is true, operators obtain credentials out-of-band (e.g., seller portal, API key)." }, "stable_account_id": { + "x-added-in": "3.3.0", "type": "boolean", "description": "Whether the seller-assigned account_id is a durable AccountRef for every account the caller can operate, including buyer-declared accounts. When true, the seller MUST return account_id on every non-failed sync_accounts provisioning result, MUST accept { account_id } wherever an AccountRef is accepted, and MUST NOT change an account's account_id for the life of the account, including across identity reconciliation. A buyer-declared seller that declares true still accepts the account's current natural key throughout 3.x. Buyers holding an account_id from a seller that declares true SHOULD send it in place of the natural key. When false or absent, buyers follow the account model declared by require_operator_auth and MUST NOT assume a buyer-declared seller accepts its returned account_id. Sellers whose accounts already use account_id references (require_operator_auth: true) SHOULD declare true when their ids meet these rules. Using the natural key as the reference after provisioning is deprecated as of 3.3 and planned for removal in 4.0, so sellers SHOULD declare true during 3.x to give buyers a migration path.", "default": false @@ -2055,6 +2057,7 @@ "description": "Seller-level rollup of vendor-metric optimization capabilities supported by at least one product. Product-level vendor_metric_optimization.supported_metrics[] remains authoritative for the specific (vendor, metric_id) pairs and target kinds a buyer may bind on a package; this seller-level object exists so buyers and compliance runners can discover whether vendor_metric goals are in scope before walking the catalog. Sellers MUST keep this in sync with product-level vendor_metric_optimization declarations.", "properties": { "supported_metrics": { + "x-added-in": "3.3.0", "type": "array", "description": "(vendor, metric_id) pairs this seller can optimize toward on at least one of its products. Seller-level rollup of product-level vendor_metric_optimization.supported_metrics[] declarations (core/product.json); each entry has the product-level shape, and its supported_targets, when present, are the target kinds at least one product supports for the pair. Pairs are matched on vendor.domain and vendor.brand_id, as at the product level, and sellers MUST list each pair once, with supported_targets unioned across products; the object-level supported_targets covers the union of the per-pair values. Buyers SHOULD check this list before sending a vendor_metric goal, including an outcome_target vendor_metric goal, rather than learning an unsupported pair from an INVALID_REQUEST rejection. Product-level declarations remain authoritative for a specific package. Sellers MUST keep this in sync with their product catalog — if no product supports a pair, it must not appear here. Omitting this field means the seller declares no specific guarantees about which pairs it supports; buyers should fall back to per-product inspection of vendor_metric_optimization.supported_metrics.", "items": { diff --git a/tests/x-added-in-3-3-sweep.test.cjs b/tests/x-added-in-3-3-sweep.test.cjs index 2598e01b95f..43ac0ddbc8a 100644 --- a/tests/x-added-in-3-3-sweep.test.cjs +++ b/tests/x-added-in-3-3-sweep.test.cjs @@ -25,6 +25,9 @@ const WHOLE_FILE_3_3 = [ 'error-details/unsupported-targeting-dimension.json', 'trust/v1/trust.json', 'trust/v1/trust-acknowledgements.json', + 'media-buy/list-collections-request.json', + 'media-buy/list-collections-response.json', + 'core/assets/video-brief-asset.json', ]; const PROPERTIES_3_3 = [ @@ -48,6 +51,20 @@ const PROPERTIES_3_3 = [ ['protocol/get-adcp-capabilities-response.json', ['properties', 'governance', 'properties', 'supports_budget_periods']], ['protocol/get-adcp-capabilities-response.json', ['properties', 'request_signing', 'properties', 'operation_sources']], ['formats/canonical/video_hosted.json', ['properties', 'loudness']], + ['core/installment-deadlines.json', ['properties', 'change_cutoff']], + ['core/reporting-status-issue.json', ['properties', 'reporting_revision_id']], + ['governance/check-governance-request.json', ['properties', 'budget_period_id']], + ['governance/check-governance-response.json', ['properties', 'budget_period_id']], + ['media-buy/list-products-response.json', ['properties', 'adcp_version']], + ['media-buy/refine-proposals-response.json', ['properties', 'results', 'items', 'properties', 'drifted_pins']], + ['media-buy/sync-audiences-response.json', ['oneOf', 0, 'properties', 'audiences', 'items', 'properties', 'platform_id']], + ['media-buy/sync-event-sources-response.json', ['oneOf', 0, 'properties', 'event_sources', 'items', 'properties', 'platform_id']], + ['protocol/get-adcp-capabilities-response.json', ['properties', 'adcp', 'properties', 'implementation']], + ['protocol/get-adcp-capabilities-response.json', ['properties', 'account', 'properties', 'stable_account_id']], + ['protocol/get-adcp-capabilities-response.json', ['properties', 'media_buy', 'properties', 'vendor_metric_optimization', 'properties', 'supported_metrics']], + ['core/assets/zip-asset.json', ['properties', 'sizes']], + ['core/assets/html-asset.json', ['properties', 'sizes']], + ['core/assets/display-tag-asset.json', ['properties', 'sizes']], ]; test('whole-file 3.3 schemas and enums carry x-added-in 3.3.0', () => { From 008c2e87add3b2424841144e882e699612489bd2 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 21 Oct 2026 14:08:45 +0000 Subject: [PATCH 4/4] test(schema): extend the x-added-in sweep to 3.3 surfaces merged since the last rebase Marks the properties and files new since 3.2.5 that landed in the beta.0 window (webhook signing_profile and profiles, identity relying party, adcp extension, brand prohibited_terms, mutation_outcome, event source archive, creative provenance, evaluator agent_url, canonical projection delivery types, later proposals, validate_input warning field) and adds them to the sweep test. Task request schemas stay unmarked: x-added-in on a request makes the tool inactive in the 3.2.1 production MCP profile. Co-Authored-By: Claude Sonnet 5.5 --- static/schemas/source/brand.json | 3 ++- .../core/agent-notification-config.json | 3 ++- .../source/core/canonical-projection-ref.json | 3 ++- static/schemas/source/core/error.json | 3 ++- .../schemas/source/core/evaluator-spec.json | 3 ++- .../source/core/identity-relying-party.json | 3 ++- .../source/core/notification-config.json | 3 ++- .../source/core/push-notification-config.json | 3 ++- .../source/core/reporting-webhook.json | 3 ++- .../creative/list-creatives-response.json | 3 ++- .../creative/validate-input-result.json | 3 ++- .../source/enums/webhook-signing-profile.json | 3 ++- static/schemas/source/extensions/adcp.json | 3 ++- .../media-buy/create-media-buy-request.json | 3 ++- .../media-buy/list-collections-request.json | 1 - .../media-buy/list-collections-response.json | 1 - .../media-buy/request-proposals-response.json | 3 ++- .../media-buy/sync-event-sources-request.json | 3 ++- .../sync-event-sources-response.json | 3 ++- .../get-adcp-capabilities-response.json | 9 ++++--- tests/x-added-in-3-3-sweep.test.cjs | 25 +++++++++++++++++-- 21 files changed, 63 insertions(+), 24 deletions(-) diff --git a/static/schemas/source/brand.json b/static/schemas/source/brand.json index f3bf7a56f2d..840036e4ced 100644 --- a/static/schemas/source/brand.json +++ b/static/schemas/source/brand.json @@ -569,7 +569,8 @@ "prohibited_terms": { "type": "array", "items": { "type": "string", "minLength": 1 }, - "description": "Words or phrases the brand never uses in copy, across all formats and sellers (e.g., 'binge', a competitor's name). Plain strings that apply regardless of copy language; case-insensitive whole-term matching is the receiver's choice, and enforcement is advisory. For a text slot the effective list is the union of this list and the slot's `text-asset-requirements.prohibited_terms`; neither weakens the other. Distinct from a creative brief's `compliance.prohibited_claims`, which restricts statements judged by meaning. Not overridable per call through `brand_kit_override`." + "description": "Words or phrases the brand never uses in copy, across all formats and sellers (e.g., 'binge', a competitor's name). Plain strings that apply regardless of copy language; case-insensitive whole-term matching is the receiver's choice, and enforcement is advisory. For a text slot the effective list is the union of this list and the slot's `text-asset-requirements.prohibited_terms`; neither weakens the other. Distinct from a creative brief's `compliance.prohibited_claims`, which restricts statements judged by meaning. Not overridable per call through `brand_kit_override`.", + "x-added-in": "3.3.0" }, "tagline": { "oneOf": [ diff --git a/static/schemas/source/core/agent-notification-config.json b/static/schemas/source/core/agent-notification-config.json index ef1bd7d6ad1..b2382153104 100644 --- a/static/schemas/source/core/agent-notification-config.json +++ b/static/schemas/source/core/agent-notification-config.json @@ -79,7 +79,8 @@ "signing_profile": { "x-status": "experimental", "$ref": "/schemas/enums/webhook-signing-profile.json", - "description": "Webhook-signing profile the seller uses for every delivery to this `url`. When it is omitted, the seller signs with `adcp/webhook-signing/v1`, or with the legacy scheme when `authentication` is present. A seller whose `webhook_signing.profiles` does not list it rejects the entry with `INVALID_REQUEST` or `VALIDATION_ERROR`. MUST NOT be combined with `authentication`." + "description": "Webhook-signing profile the seller uses for every delivery to this `url`. When it is omitted, the seller signs with `adcp/webhook-signing/v1`, or with the legacy scheme when `authentication` is present. A seller whose `webhook_signing.profiles` does not list it rejects the entry with `INVALID_REQUEST` or `VALIDATION_ERROR`. MUST NOT be combined with `authentication`.", + "x-added-in": "3.3.0" }, "active": { "type": "boolean", diff --git a/static/schemas/source/core/canonical-projection-ref.json b/static/schemas/source/core/canonical-projection-ref.json index 92942961edc..e20976d711e 100644 --- a/static/schemas/source/core/canonical-projection-ref.json +++ b/static/schemas/source/core/canonical-projection-ref.json @@ -31,7 +31,8 @@ "type": "string", "enum": ["tag_url", "inline_markup", "paired_redirect"] }, - "description": "Delivery types the projected `display_tag` declaration accepts. Copied to the projected declaration's `params.supported_delivery_types` (rule 6). Only valid with `kind: \"display_tag\"`; uses the same closed set as the `display_tag` canonical parameter. Lets a catalog entry state that its legacy format takes byte-preserved inline markup (`inline_markup`) rather than the default single ad-request URL." + "description": "Delivery types the projected `display_tag` declaration accepts. Copied to the projected declaration's `params.supported_delivery_types` (rule 6). Only valid with `kind: \"display_tag\"`; uses the same closed set as the `display_tag` canonical parameter. Lets a catalog entry state that its legacy format takes byte-preserved inline markup (`inline_markup`) rather than the default single ad-request URL.", + "x-added-in": "3.3.0" } }, "if": { diff --git a/static/schemas/source/core/error.json b/static/schemas/source/core/error.json index 8c20aeaff1e..72f917cda39 100644 --- a/static/schemas/source/core/error.json +++ b/static/schemas/source/core/error.json @@ -113,7 +113,8 @@ "minLength": 1, "maxLength": 32, "examples": ["not_applied", "unknown", "applied"], - "description": "Whether the failed request changed seller state. Optional; meaningful only on an error that terminates a state-changing request (an envelope `adcp_error`, a task-level `errors[]` entry that fails the request, or the terminal error of an async task that ends in `failed`). Pure reads never carry it, and it MUST NOT be set on warnings or used to describe individual items of a partially successful response. Values: `not_applied`: the seller has pre-commit evidence that no local or downstream write was attempted or committed. 'Pre-commit evidence' means the failure was raised before the request's idempotency claim row was inserted and before any write was issued, or was a typed AdCP rejection returned by validation that runs strictly before the first write is issued. A timeout, cancellation, connection error, or exception raised from within or after a write (including an in-flight database write or the claim insert itself), or caught by a handler-level catch-all or framework wrapper, is NOT pre-commit evidence. `unknown`: the seller cannot rule out that some or all of the requested change committed, and has fenced the idempotency claim so a same-key resend cannot run the mutation again. On a compound request (several packages, creatives, or items), `unknown` covers the whole request envelope; per-item outcomes are out of scope. `applied`: the seller holds durable evidence that the requested change committed and the error reports a later failure, as with `COMMITTED_RESOURCE_PURGED`. Absent: the seller makes no claim; buyers apply the absent-field guidance in Request Safety → Idempotency. The wire type is open: receivers MUST treat an unrecognized, empty, or non-string value as `unknown`, never as a reason to discard the error. The field is independent of `code` and `recovery`. See `error-handling.mdx#mutation-outcome` for the seller and buyer obligations, which bind sellers declaring AdCP 3.3 or later." + "description": "Whether the failed request changed seller state. Optional; meaningful only on an error that terminates a state-changing request (an envelope `adcp_error`, a task-level `errors[]` entry that fails the request, or the terminal error of an async task that ends in `failed`). Pure reads never carry it, and it MUST NOT be set on warnings or used to describe individual items of a partially successful response. Values: `not_applied`: the seller has pre-commit evidence that no local or downstream write was attempted or committed. 'Pre-commit evidence' means the failure was raised before the request's idempotency claim row was inserted and before any write was issued, or was a typed AdCP rejection returned by validation that runs strictly before the first write is issued. A timeout, cancellation, connection error, or exception raised from within or after a write (including an in-flight database write or the claim insert itself), or caught by a handler-level catch-all or framework wrapper, is NOT pre-commit evidence. `unknown`: the seller cannot rule out that some or all of the requested change committed, and has fenced the idempotency claim so a same-key resend cannot run the mutation again. On a compound request (several packages, creatives, or items), `unknown` covers the whole request envelope; per-item outcomes are out of scope. `applied`: the seller holds durable evidence that the requested change committed and the error reports a later failure, as with `COMMITTED_RESOURCE_PURGED`. Absent: the seller makes no claim; buyers apply the absent-field guidance in Request Safety → Idempotency. The wire type is open: receivers MUST treat an unrecognized, empty, or non-string value as `unknown`, never as a reason to discard the error. The field is independent of `code` and `recovery`. See `error-handling.mdx#mutation-outcome` for the seller and buyer obligations, which bind sellers declaring AdCP 3.3 or later.", + "x-added-in": "3.3.0" }, "source": { "type": "string", diff --git a/static/schemas/source/core/evaluator-spec.json b/static/schemas/source/core/evaluator-spec.json index 477f34994c3..9f6b109e76e 100644 --- a/static/schemas/source/core/evaluator-spec.json +++ b/static/schemas/source/core/evaluator-spec.json @@ -117,7 +117,8 @@ "type": "string", "format": "uri", "pattern": "^https://", - "description": "URL of an external get_creative_features-capable judge agent the seller calls to score the produced leaves. MUST match an entry in the seller's `creative_policy.accepted_verifiers[].agent_url` (off-list → `EVALUATOR_AGENT_NOT_ACCEPTED`); an on-list agent that is unreachable or rejects the producing agent's transport authentication degrades to seller-default ranking (advisory errors[] note), not a failure. Authentication and trust material for this call belongs on the transport or in account provisioning, not in the evaluator payload." + "description": "URL of an external get_creative_features-capable judge agent the seller calls to score the produced leaves. MUST match an entry in the seller's `creative_policy.accepted_verifiers[].agent_url` (off-list → `EVALUATOR_AGENT_NOT_ACCEPTED`); an on-list agent that is unreachable or rejects the producing agent's transport authentication degrades to seller-default ranking (advisory errors[] note), not a failure. Authentication and trust material for this call belongs on the transport or in account provisioning, not in the evaluator payload.", + "x-added-in": "3.3.0" } }, "additionalProperties": true diff --git a/static/schemas/source/core/identity-relying-party.json b/static/schemas/source/core/identity-relying-party.json index c0492341ddb..3b2d914acc8 100644 --- a/static/schemas/source/core/identity-relying-party.json +++ b/static/schemas/source/core/identity-relying-party.json @@ -31,5 +31,6 @@ "issuer", "relying_party_id" ], - "additionalProperties": false + "additionalProperties": false, + "x-added-in": "3.3.0" } diff --git a/static/schemas/source/core/notification-config.json b/static/schemas/source/core/notification-config.json index 987037fc53a..13eff579a00 100644 --- a/static/schemas/source/core/notification-config.json +++ b/static/schemas/source/core/notification-config.json @@ -84,7 +84,8 @@ "signing_profile": { "x-status": "experimental", "$ref": "/schemas/enums/webhook-signing-profile.json", - "description": "Webhook-signing profile the seller uses for every delivery to this `url`. When it is omitted, the seller signs with `adcp/webhook-signing/v1`, or with the legacy scheme when `authentication` is present. A seller whose `webhook_signing.profiles` does not list it rejects the entry as a per-account validation failure with `INVALID_REQUEST` or `VALIDATION_ERROR` and `error.field` pointing at it. MUST NOT be combined with `authentication`." + "description": "Webhook-signing profile the seller uses for every delivery to this `url`. When it is omitted, the seller signs with `adcp/webhook-signing/v1`, or with the legacy scheme when `authentication` is present. A seller whose `webhook_signing.profiles` does not list it rejects the entry as a per-account validation failure with `INVALID_REQUEST` or `VALIDATION_ERROR` and `error.field` pointing at it. MUST NOT be combined with `authentication`.", + "x-added-in": "3.3.0" }, "active": { "type": "boolean", diff --git a/static/schemas/source/core/push-notification-config.json b/static/schemas/source/core/push-notification-config.json index 4000e6a5d83..4fd0b71d4e3 100644 --- a/static/schemas/source/core/push-notification-config.json +++ b/static/schemas/source/core/push-notification-config.json @@ -53,7 +53,8 @@ "signing_profile": { "x-status": "experimental", "$ref": "/schemas/enums/webhook-signing-profile.json", - "description": "Webhook-signing profile the seller uses for every delivery to this `url`. When it is omitted, the seller signs with `adcp/webhook-signing/v1`, or with the legacy scheme when `authentication` is present. A seller whose `webhook_signing.profiles` does not list it rejects the registration with `INVALID_REQUEST`. MUST NOT be combined with `authentication`." + "description": "Webhook-signing profile the seller uses for every delivery to this `url`. When it is omitted, the seller signs with `adcp/webhook-signing/v1`, or with the legacy scheme when `authentication` is present. A seller whose `webhook_signing.profiles` does not list it rejects the registration with `INVALID_REQUEST`. MUST NOT be combined with `authentication`.", + "x-added-in": "3.3.0" } }, "required": [ diff --git a/static/schemas/source/core/reporting-webhook.json b/static/schemas/source/core/reporting-webhook.json index 33ec5d13992..df5723c0eec 100644 --- a/static/schemas/source/core/reporting-webhook.json +++ b/static/schemas/source/core/reporting-webhook.json @@ -53,7 +53,8 @@ "signing_profile": { "x-status": "experimental", "$ref": "/schemas/enums/webhook-signing-profile.json", - "description": "Webhook-signing profile the seller uses for every delivery to this `url`. When it is omitted, the seller signs with `adcp/webhook-signing/v1`, or with the legacy scheme when `authentication` is present. A seller whose `webhook_signing.profiles` does not list it rejects the registration with `INVALID_REQUEST`. MUST NOT be combined with `authentication`." + "description": "Webhook-signing profile the seller uses for every delivery to this `url`. When it is omitted, the seller signs with `adcp/webhook-signing/v1`, or with the legacy scheme when `authentication` is present. A seller whose `webhook_signing.profiles` does not list it rejects the registration with `INVALID_REQUEST`. MUST NOT be combined with `authentication`.", + "x-added-in": "3.3.0" }, "reporting_frequency": { "type": "string", diff --git a/static/schemas/source/creative/list-creatives-response.json b/static/schemas/source/creative/list-creatives-response.json index b020d79e79a..7d7ce83da18 100644 --- a/static/schemas/source/creative/list-creatives-response.json +++ b/static/schemas/source/creative/list-creatives-response.json @@ -219,7 +219,8 @@ }, "provenance": { "$ref": "/schemas/core/provenance.json", - "description": "Read-only echo of the creative-level provenance accepted on sync_creatives for the returned revision. Default for assets in this creative; an asset's own provenance replaces it entirely. Omitted when none was declared." + "description": "Read-only echo of the creative-level provenance accepted on sync_creatives for the returned revision. Default for assets in this creative; an asset's own provenance replaces it entirely. Omitted when none was declared.", + "x-added-in": "3.3.0" }, "tags": { "type": "array", diff --git a/static/schemas/source/creative/validate-input-result.json b/static/schemas/source/creative/validate-input-result.json index 1679e89f95e..309fb8059c4 100644 --- a/static/schemas/source/creative/validate-input-result.json +++ b/static/schemas/source/creative/validate-input-result.json @@ -79,7 +79,8 @@ "rule": { "type": "string" }, "field": { "type": "string", - "description": "Path to the field the warning concerns, in the same form as `violations[].field` (e.g., 'assets.video_main.duration_ms')." + "description": "Path to the field the warning concerns, in the same form as `violations[].field` (e.g., 'assets.video_main.duration_ms').", + "x-added-in": "3.3.0" }, "expected": {}, "predicted": {} diff --git a/static/schemas/source/enums/webhook-signing-profile.json b/static/schemas/source/enums/webhook-signing-profile.json index 13d2711ed22..e0efab4d815 100644 --- a/static/schemas/source/enums/webhook-signing-profile.json +++ b/static/schemas/source/enums/webhook-signing-profile.json @@ -7,5 +7,6 @@ "enum": [ "adcp/webhook-signing/v1", "web-bot-auth" - ] + ], + "x-added-in": "3.3.0" } diff --git a/static/schemas/source/extensions/adcp.json b/static/schemas/source/extensions/adcp.json index 662cffb6522..b00edd57cfc 100644 --- a/static/schemas/source/extensions/adcp.json +++ b/static/schemas/source/extensions/adcp.json @@ -13,5 +13,6 @@ "description": "Planning-cycle association on get_products, using the unchanged 3.2.1 OpportunityContext shape and seller/account scope. Lifecycle fields are discovery snapshots, not independent transitions. See the documented task binding for account resolution, proposal inheritance, and migration." } }, - "additionalProperties": true + "additionalProperties": true, + "x-added-in": "3.3.0" } diff --git a/static/schemas/source/media-buy/create-media-buy-request.json b/static/schemas/source/media-buy/create-media-buy-request.json index f5a66b76b59..6f39c99fda9 100644 --- a/static/schemas/source/media-buy/create-media-buy-request.json +++ b/static/schemas/source/media-buy/create-media-buy-request.json @@ -302,7 +302,8 @@ "signing_profile": { "x-status": "experimental", "$ref": "/schemas/enums/webhook-signing-profile.json", - "description": "Webhook-signing profile the seller uses for every delivery to this `url`. When it is omitted, the seller signs with `adcp/webhook-signing/v1`, or with the legacy scheme when `authentication` is present. A seller whose `webhook_signing.profiles` does not list it rejects the registration with `INVALID_REQUEST`. MUST NOT be combined with `authentication`." + "description": "Webhook-signing profile the seller uses for every delivery to this `url`. When it is omitted, the seller signs with `adcp/webhook-signing/v1`, or with the legacy scheme when `authentication` is present. A seller whose `webhook_signing.profiles` does not list it rejects the registration with `INVALID_REQUEST`. MUST NOT be combined with `authentication`.", + "x-added-in": "3.3.0" }, "sampling_rate": { "type": "number", diff --git a/static/schemas/source/media-buy/list-collections-request.json b/static/schemas/source/media-buy/list-collections-request.json index 4d77f717e2e..5d454f6dce0 100644 --- a/static/schemas/source/media-buy/list-collections-request.json +++ b/static/schemas/source/media-buy/list-collections-request.json @@ -3,7 +3,6 @@ "$id": "/schemas/media-buy/list-collections-request.json", "title": "List Collections Request", "description": "Experimental (media_buy.collection_catalog), added in AdCP 3.3. Read the selling agent's catalog of collection cards: look up specific collections by key, or browse with filters and pages. Returns the same core/response-collection.json copies, under the same authority rules, as include_collections on product and proposal reads, and never returns products. Not to be confused with list_collection_lists, which lists buyer-owned collection targeting lists on a governance agent. Side-effect free: the call never provisions an account. It is the bootstrap and repair read for collection_card.* and collection_catalog.bulk_change webhooks, which are registered only through sync_accounts notification_configs; the request takes no push_notification_config, so a call never creates a subscription. Sellers implementing it MUST list media_buy.collection_catalog and media_buy.collection_cards in experimental_features. A seller MUST NOT serve this task to a caller whose adcp_version is below 3.3; it fails the request with UNSUPPORTED_FEATURE.", - "x-added-in": "3.3.0", "x-tool-summary": "List a seller's collection cards (shows, creators, podcasts) by key or with filters, without products.", "x-status": "experimental", "type": "object", diff --git a/static/schemas/source/media-buy/list-collections-response.json b/static/schemas/source/media-buy/list-collections-response.json index cee4a0caebb..17c27fd4cf4 100644 --- a/static/schemas/source/media-buy/list-collections-response.json +++ b/static/schemas/source/media-buy/list-collections-response.json @@ -3,7 +3,6 @@ "$id": "/schemas/media-buy/list-collections-response.json", "title": "List Collections Response", "description": "Experimental (media_buy.collection_catalog), added in AdCP 3.3. One page of the selling agent's collection cards, or confirmation that the selected catalog has not changed. Entries are core/response-collection.json copies under the authority rules defined there and for include_collections: adagents.json stays the root of authorization and the source for definition pins, a copy wins only on images, sample_content, url, description, season, cadence and distribution[].followers, and a copy of an undeclared collection or from an agent adagents.json does not authorize is unverified display data that never feeds carriage verification. For the same key, account and moment, the copy MUST equal the one a product or proposal read with include_collections returns. The response carries no products, pricing, availability or account terms.", - "x-added-in": "3.3.0", "x-status": "experimental", "type": "object", "discriminator": { diff --git a/static/schemas/source/media-buy/request-proposals-response.json b/static/schemas/source/media-buy/request-proposals-response.json index f030c8c7b22..95747cccee5 100644 --- a/static/schemas/source/media-buy/request-proposals-response.json +++ b/static/schemas/source/media-buy/request-proposals-response.json @@ -161,7 +161,8 @@ }, "later_proposals": { "$ref": "/schemas/core/later-proposals-state.json", - "description": "Experimental (media_buy.open_opportunities). Effective later-proposal consent for the request's opportunity, as the seller recorded it. Sellers that advertise media_buy.open_opportunities.later_proposals.supported MUST return it whenever the request carried opportunity.later_proposals. accepted false here means the seller will not send later proposals, whatever the request asked for." + "description": "Experimental (media_buy.open_opportunities). Effective later-proposal consent for the request's opportunity, as the seller recorded it. Sellers that advertise media_buy.open_opportunities.later_proposals.supported MUST return it whenever the request carried opportunity.later_proposals. accepted false here means the seller will not send later proposals, whatever the request asked for.", + "x-added-in": "3.3.0" }, "status": { "type": "string", "enum": ["completed", "submitted"] }, "task_id": { "type": "string", "minLength": 1, "x-entity": "task" }, diff --git a/static/schemas/source/media-buy/sync-event-sources-request.json b/static/schemas/source/media-buy/sync-event-sources-request.json index 3683bb917ef..2288c7b44e1 100644 --- a/static/schemas/source/media-buy/sync-event-sources-request.json +++ b/static/schemas/source/media-buy/sync-event-sources-request.json @@ -75,7 +75,8 @@ }, "archived": { "type": "boolean", - "description": "`true` archives the source and `false` undoes the archive. When present it is the item's only change: every field except `event_source_id` is ignored. An archived source keeps its configuration, stops accepting events, and stops resolving for new optimization goals. Buyers SHOULD omit `archived` unless changing archive state. Sellers that do not declare `media_buy.event_source_archive` in `get_adcp_capabilities` MUST reject the whole request with an operation-level `UNSUPPORTED_FEATURE` error and apply none of its items." + "description": "`true` archives the source and `false` undoes the archive. When present it is the item's only change: every field except `event_source_id` is ignored. An archived source keeps its configuration, stops accepting events, and stops resolving for new optimization goals. Buyers SHOULD omit `archived` unless changing archive state. Sellers that do not declare `media_buy.event_source_archive` in `get_adcp_capabilities` MUST reject the whole request with an operation-level `UNSUPPORTED_FEATURE` error and apply none of its items.", + "x-added-in": "3.3.0" } }, "required": [ diff --git a/static/schemas/source/media-buy/sync-event-sources-response.json b/static/schemas/source/media-buy/sync-event-sources-response.json index 23dcc318400..30a062bc11b 100644 --- a/static/schemas/source/media-buy/sync-event-sources-response.json +++ b/static/schemas/source/media-buy/sync-event-sources-response.json @@ -116,7 +116,8 @@ }, "archived": { "type": "boolean", - "description": "Whether the source is archived. Archived sources keep their configuration but stop accepting events and stop resolving for new optimization goals. Sellers MAY omit it for sources that are not archived." + "description": "Whether the source is archived. Archived sources keep their configuration but stop accepting events and stop resolving for new optimization goals. Sellers MAY omit it for sources that are not archived.", + "x-added-in": "3.3.0" }, "health": { "$ref": "/schemas/core/event-source-health.json", diff --git a/static/schemas/source/protocol/get-adcp-capabilities-response.json b/static/schemas/source/protocol/get-adcp-capabilities-response.json index 0050d1834e4..702c0b92b58 100644 --- a/static/schemas/source/protocol/get-adcp-capabilities-response.json +++ b/static/schemas/source/protocol/get-adcp-capabilities-response.json @@ -2190,7 +2190,8 @@ }, "event_source_archive": { "type": "boolean", - "description": "Whether this seller supports the per-source `archived` field on `sync_event_sources` request items. When true, `archived: true` archives an event source and `archived: false` undoes it. When false or absent, the seller MUST reject a request that sends `archived` with `UNSUPPORTED_FEATURE`, so a buyer never mistakes an ignored field for an archive." + "description": "Whether this seller supports the per-source `archived` field on `sync_event_sources` request items. When true, `archived: true` archives an event source and `archived: false` undoes it. When false or absent, the seller MUST reject a request that sends `archived` with `UNSUPPORTED_FEATURE`, so a buyer never mistakes an ignored field for an archive.", + "x-added-in": "3.3.0" }, "conversion_tracking": { "type": "object", @@ -3314,7 +3315,8 @@ "default": ["adcp/request-signing/v1"], "x-adcp-validation": { "spec": "docs/building/by-layer/L1/wba-profile.mdx#capability" - } + }, + "x-added-in": "3.3.0" } }, "required": ["supported"] @@ -3401,7 +3403,8 @@ "default": ["adcp/webhook-signing/v1"], "x-adcp-validation": { "spec": "docs/building/by-layer/L1/wba-profile.mdx#webhooks" - } + }, + "x-added-in": "3.3.0" } }, "required": ["supported"] diff --git a/tests/x-added-in-3-3-sweep.test.cjs b/tests/x-added-in-3-3-sweep.test.cjs index 43ac0ddbc8a..c6369043698 100644 --- a/tests/x-added-in-3-3-sweep.test.cjs +++ b/tests/x-added-in-3-3-sweep.test.cjs @@ -25,9 +25,10 @@ const WHOLE_FILE_3_3 = [ 'error-details/unsupported-targeting-dimension.json', 'trust/v1/trust.json', 'trust/v1/trust-acknowledgements.json', - 'media-buy/list-collections-request.json', - 'media-buy/list-collections-response.json', 'core/assets/video-brief-asset.json', + 'core/identity-relying-party.json', + 'enums/webhook-signing-profile.json', + 'extensions/adcp.json', ]; const PROPERTIES_3_3 = [ @@ -65,8 +66,28 @@ const PROPERTIES_3_3 = [ ['core/assets/zip-asset.json', ['properties', 'sizes']], ['core/assets/html-asset.json', ['properties', 'sizes']], ['core/assets/display-tag-asset.json', ['properties', 'sizes']], + ['brand.json', ['definitions', 'brand', 'properties', 'prohibited_terms']], + ['core/agent-notification-config.json', ['properties', 'signing_profile']], + ['core/notification-config.json', ['properties', 'signing_profile']], + ['core/push-notification-config.json', ['properties', 'signing_profile']], + ['core/reporting-webhook.json', ['properties', 'signing_profile']], + ['media-buy/create-media-buy-request.json', ['properties', 'artifact_webhook', 'properties', 'signing_profile']], + ['protocol/get-adcp-capabilities-response.json', ['properties', 'request_signing', 'properties', 'profiles']], + ['protocol/get-adcp-capabilities-response.json', ['properties', 'webhook_signing', 'properties', 'profiles']], + ['protocol/get-adcp-capabilities-response.json', ['properties', 'media_buy', 'properties', 'event_source_archive']], + ['media-buy/sync-event-sources-request.json', ['properties', 'event_sources', 'items', 'properties', 'archived']], + ['media-buy/sync-event-sources-response.json', ['oneOf', 0, 'properties', 'event_sources', 'items', 'properties', 'archived']], + ['creative/list-creatives-response.json', ['properties', 'creatives', 'items', 'properties', 'provenance']], + ['core/error.json', ['properties', 'mutation_outcome']], + ['core/evaluator-spec.json', ['oneOf', 1, 'properties', 'agent_url']], + ['core/canonical-projection-ref.json', ['properties', 'supported_delivery_types']], + ['media-buy/request-proposals-response.json', ['properties', 'later_proposals']], + ['creative/validate-input-result.json', ['properties', 'warnings', 'items', 'properties', 'field']], ]; +// Task request schemas are deliberately absent: `x-added-in` on a request makes the +// tool's `added_in` 3.3.0, and the 3.2.1 production MCP profile then rejects any +// role profile that names it (list_collections), so tools are gated by `x-status`. test('whole-file 3.3 schemas and enums carry x-added-in 3.3.0', () => { for (const f of WHOLE_FILE_3_3) assert.equal(load(f)['x-added-in'], '3.3.0', f); });