From 71b5df9f48323f533508d222cf2fb269dd02eca4 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Fri, 24 Apr 2026 13:58:01 -0400 Subject: [PATCH 1/6] feat(schema): add vast_tracker + daast_tracker asset types (#2915) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements Option B + Option B from the #2915 triage: 1. Dedicated asset types. Adds vast-tracker-asset.json and daast-tracker-asset.json as discriminated union members rather than extending the existing url-asset enum. The asset_type discriminator stays the single source of branching for sales agents — no two-field (url_type + vast_event) coupling. 2. Normative creative/sales boundary. Docs codify that: - The sales agent owns VAST construction; the creative agent MUST NOT emit pre-wrapped VAST. - URLs stay on url asset with url_type: 'tracker_pixel'. A vast_tracker MUST NOT have vast_event: 'impression' — that event lives in , not . - Complete-tag and decomposed-tracker shapes MAY be mixed; the sales agent merges them into one VAST document at serve time. 3. SSAI gap explicitly called out as a known follow-on RFC topic. Non-breaking: new asset types only. Schema validation tests pass. Closes #2915 Co-Authored-By: Claude Opus 4.7 (1M context) --- .changeset/vast-tracker-asset-type.md | 5 ++ docs/creative/asset-types.mdx | 87 +++++++++++++++++++ .../core/assets/daast-tracker-asset.json | 38 ++++++++ .../core/assets/vast-tracker-asset.json | 38 ++++++++ .../source/creative/asset-types/index.json | 10 +++ 5 files changed, 178 insertions(+) create mode 100644 .changeset/vast-tracker-asset-type.md create mode 100644 static/schemas/source/core/assets/daast-tracker-asset.json create mode 100644 static/schemas/source/core/assets/vast-tracker-asset.json diff --git a/.changeset/vast-tracker-asset-type.md b/.changeset/vast-tracker-asset-type.md new file mode 100644 index 0000000000..2227a5fa6b --- /dev/null +++ b/.changeset/vast-tracker-asset-type.md @@ -0,0 +1,5 @@ +--- +"adcontextprotocol": minor +--- + +Add `vast_tracker` and `daast_tracker` asset types for decomposed VAST/DAAST `` URLs. Creative agents can now emit per-event tracker URLs (start, quartiles, complete, etc.) as a discriminated-union alternative to a complete VAST tag; the sales agent assembles them into the VAST `` block at serve time. Adds normative creative/sales boundary: wrapper ownership belongs to the sales agent, and the `` URL stays on `url` asset with `url_type: "tracker_pixel"` (not `vast_tracker` with `vast_event: "impression"`). Non-breaking — new asset types only. diff --git a/docs/creative/asset-types.mdx b/docs/creative/asset-types.mdx index bfb59fe20b..7b9f5150a5 100644 --- a/docs/creative/asset-types.mdx +++ b/docs/creative/asset-types.mdx @@ -246,6 +246,34 @@ VAST (Video Ad Serving Template) tags for third-party video ad serving. - Video ad networks - VPAID interactive video ads +### VAST Tracker Asset + +A single URL bound to a VAST `` event. Use this when the creative agent emits decomposed per-event URLs and the sales agent is responsible for assembling the VAST `` block at serve time — as an alternative to shipping a complete VAST tag. + +**Example:** +```json +{ + "asset_type": "vast_tracker", + "vast_event": "start", + "url": "https://tracker.example.com/start?cid={CREATIVE_ID}&mb={MEDIA_BUY_ID}" +} +``` + +**Progress with offset:** +```json +{ + "asset_type": "vast_tracker", + "vast_event": "progress", + "offset": "00:00:15.000", + "url": "https://tracker.example.com/15s" +} +``` + +**Properties:** +- `vast_event`: The event this URL fires on. Any VAST tracking-event enum value **except** `impression` (see normative rule below). Common values: `start`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `pause`, `resume`, `mute`, `unmute`, `skip`, `clickTracking`, `viewableImpression`. +- `url`: Tracker URL. Supports [AdCP universal macros](/creative/universal-macros). +- `offset`: Required for `vast_event: "progress"`. VAST offset format: `HH:MM:SS[.mmm]` or an integer percentage (`50%`). + ### DAAST Asset DAAST (Digital Audio Ad Serving Template) tags for third-party audio ad serving. @@ -293,6 +321,65 @@ DAAST (Digital Audio Ad Serving Template) tags for third-party audio ad serving. - Streaming audio platforms - Radio-style digital audio ads +### DAAST Tracker Asset + +Audio-side analogue of `vast_tracker`: a single URL bound to a DAAST `` event. + +**Example:** +```json +{ + "asset_type": "daast_tracker", + "daast_event": "complete", + "url": "https://tracker.example.com/complete?cid={CREATIVE_ID}" +} +``` + +**Properties:** +- `daast_event`: Any DAAST tracking-event enum value **except** `impression`. +- `url`: Tracker URL. Supports AdCP universal macros. +- `offset`: Required for `daast_event: "progress"`. Same format as VAST offset. + +## Creative / Sales Agent Boundary (VAST Assembly) + +AdCP separates **creative agents** (produce the assets a creative is made of) from **sales agents** (assemble those assets into the serving format the ad server expects). For VAST and DAAST, the boundary is normative: + +### Complete tag vs decomposed trackers + +A creative manifest MAY emit VAST trackers in **one** of two shapes. The creative agent chooses; the sales agent handles either. + +**Complete tag** — the creative agent hands off a hosted or inline VAST tag and the sales agent wraps it: + +```json +{ + "asset_type": "vast", + "delivery_type": "url", + "url": "https://adserver.example.com/vast.xml", + "vast_version": "4.2" +} +``` + +**Decomposed trackers** — the creative agent emits per-event URLs; the sales agent assembles the `` block: + +```json +[ + { "asset_type": "vast_tracker", "vast_event": "start", "url": "..." }, + { "asset_type": "vast_tracker", "vast_event": "firstQuartile", "url": "..." }, + { "asset_type": "vast_tracker", "vast_event": "midpoint", "url": "..." }, + { "asset_type": "vast_tracker", "vast_event": "thirdQuartile", "url": "..." }, + { "asset_type": "vast_tracker", "vast_event": "complete", "url": "..." } +] +``` + +### Normative rules + +1. **Wrapper ownership.** The **sales agent** is responsible for constructing the VAST `` / `` envelope and any `` block. The **creative agent** MUST NOT emit pre-wrapped VAST. When the creative agent hands off a `vast` asset with `delivery_type: "url"`, the sales agent treats that URL as the wrapper target. +2. **Impression vs `` split.** The VAST `` URL is **not** a tracking event. It MUST be modeled as a `url` asset with `url_type: "tracker_pixel"`. A `vast_tracker` asset MUST NOT have `vast_event: "impression"`. The same rule applies to DAAST. +3. **Mixing allowed.** A creative manifest MAY mix a complete `vast` tag with additional `vast_tracker` and `url` (impression) assets — the sales agent merges them into one VAST document at serve time. + +### SSAI caveat + +In server-side ad insertion (SSAI) environments — Yospace, AWS Elemental, MediaTailor — the stream-stitcher rewrites and re-signs the VAST XML before it reaches the player. Trackers that must survive SSAI need to be in the VAST document **before** stitching. AdCP does not today specify an SSAI-specific manifest shape; this is a known gap and a follow-on RFC topic. + ## Common Properties All asset types share these common fields: diff --git a/static/schemas/source/core/assets/daast-tracker-asset.json b/static/schemas/source/core/assets/daast-tracker-asset.json new file mode 100644 index 0000000000..9178f946c6 --- /dev/null +++ b/static/schemas/source/core/assets/daast-tracker-asset.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/core/assets/daast-tracker-asset.json", + "title": "DAAST Tracker Asset", + "description": "A single tracker URL bound to a DAAST `` event. Audio-side analogue of vast-tracker-asset. The `` URL MUST be modeled as a `url` asset with `url_type: \"tracker_pixel\"`, not as a daast_tracker with `daast_event: \"impression\"`.", + "type": "object", + "properties": { + "asset_type": { + "type": "string", + "const": "daast_tracker", + "description": "Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry." + }, + "daast_event": { + "$ref": "/schemas/enums/daast-tracking-event.json", + "description": "The DAAST tracking event this URL fires on. Maps 1:1 to the DAAST `` attribute. MUST NOT be `impression` — use a `url` asset with `url_type: \"tracker_pixel\"` for impression trackers." + }, + "url": { + "type": "string", + "format": "uri-template", + "description": "Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." + }, + "offset": { + "type": "string", + "description": "DAAST `offset` attribute for `daast_event: \"progress\"`. Format: `HH:MM:SS[.mmm]` for absolute time, or an integer percentage suffixed with `%`. Ignored for other events.", + "pattern": "^(\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,3})?|\\d{1,3}%)$" + }, + "provenance": { + "$ref": "/schemas/core/provenance.json", + "description": "Provenance metadata for this asset, overrides manifest-level provenance." + } + }, + "required": [ + "asset_type", + "daast_event", + "url" + ], + "additionalProperties": true +} diff --git a/static/schemas/source/core/assets/vast-tracker-asset.json b/static/schemas/source/core/assets/vast-tracker-asset.json new file mode 100644 index 0000000000..75c129da6b --- /dev/null +++ b/static/schemas/source/core/assets/vast-tracker-asset.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/core/assets/vast-tracker-asset.json", + "title": "VAST Tracker Asset", + "description": "A single tracker URL bound to a VAST `` event. Emitted by the creative agent as a decomposed VAST-event URL; the sales agent assembles these into the VAST `` block at serve time. IMPORTANT: this asset type is for `` URLs only (start, quartiles, complete, pause, mute, etc.). The `` URL MUST be modeled as a `url` asset with `url_type: \"tracker_pixel\"`, not as a vast_tracker with `vast_event: \"impression\"`. Decomposed trackers let format requirements bind specific measurement events (e.g., MRC viewable) without forcing the buyer to construct a full VAST tag.", + "type": "object", + "properties": { + "asset_type": { + "type": "string", + "const": "vast_tracker", + "description": "Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry." + }, + "vast_event": { + "$ref": "/schemas/enums/vast-tracking-event.json", + "description": "The VAST tracking event this URL fires on. Maps 1:1 to the VAST `` attribute. MUST NOT be `impression` — use a `url` asset with `url_type: \"tracker_pixel\"` for impression trackers (they belong in ``, not ``)." + }, + "url": { + "type": "string", + "format": "uri-template", + "description": "Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." + }, + "offset": { + "type": "string", + "description": "VAST `offset` attribute for `vast_event: \"progress\"`. Format matches VAST: `HH:MM:SS[.mmm]` for absolute time, or an integer percentage suffixed with `%`. Ignored for other events.", + "pattern": "^(\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,3})?|\\d{1,3}%)$" + }, + "provenance": { + "$ref": "/schemas/core/provenance.json", + "description": "Provenance metadata for this asset, overrides manifest-level provenance." + } + }, + "required": [ + "asset_type", + "vast_event", + "url" + ], + "additionalProperties": true +} diff --git a/static/schemas/source/creative/asset-types/index.json b/static/schemas/source/creative/asset-types/index.json index 3f0abcb2a5..d28e4baa18 100644 --- a/static/schemas/source/creative/asset-types/index.json +++ b/static/schemas/source/creative/asset-types/index.json @@ -31,6 +31,16 @@ "schema": "/schemas/core/assets/daast-asset.json", "typical_use": "Third-party served audio ads with DAAST 1.0-1.1" }, + "vast_tracker": { + "description": "A single URL bound to a VAST `` event (start, quartile, complete, etc.). Assembled into the VAST `` block by the sales agent. For `` URLs, use `url` asset with `url_type: \"tracker_pixel\"` instead.", + "schema": "/schemas/core/assets/vast-tracker-asset.json", + "typical_use": "Decomposed VAST event trackers on CTV/video formats where the creative agent emits per-event URLs rather than a complete VAST tag" + }, + "daast_tracker": { + "description": "Audio-side analogue of vast_tracker: a single URL bound to a DAAST `` event.", + "schema": "/schemas/core/assets/daast-tracker-asset.json", + "typical_use": "Decomposed DAAST event trackers on audio formats" + }, "text": { "description": "Plain text content asset", "schema": "/schemas/core/assets/text-asset.json", From c5fbf49d6adbab2f0fb61808a3c245179014155e Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Fri, 24 Apr 2026 14:08:31 -0400 Subject: [PATCH 2/6] fix(docs): strip bare from prose in asset-types.mdx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mintlify's broken-links preview crashes with "Invalid hook call / Cannot read properties of null (reading 'useState')" when it hits bare capitalized angle-bracket tokens like even inside inline code spans. Existing docs avoid the pattern entirely — they write "TrackingEvents" (no brackets) or VAST XML inside a fenced `json` block. Strips `` → `Word` inside inline code in the three schema descriptions I added today plus the asset-types.mdx prose. Actual VAST XML in fenced JSON examples is unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/creative/asset-types.mdx | 10 +++++----- .../source/core/assets/daast-tracker-asset.json | 4 ++-- .../schemas/source/core/assets/vast-tracker-asset.json | 4 ++-- static/schemas/source/creative/asset-types/index.json | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/creative/asset-types.mdx b/docs/creative/asset-types.mdx index 7b9f5150a5..6728b58896 100644 --- a/docs/creative/asset-types.mdx +++ b/docs/creative/asset-types.mdx @@ -248,7 +248,7 @@ VAST (Video Ad Serving Template) tags for third-party video ad serving. ### VAST Tracker Asset -A single URL bound to a VAST `` event. Use this when the creative agent emits decomposed per-event URLs and the sales agent is responsible for assembling the VAST `` block at serve time — as an alternative to shipping a complete VAST tag. +A single URL bound to a VAST `TrackingEvents` event. Use this when the creative agent emits decomposed per-event URLs and the sales agent is responsible for assembling the VAST `TrackingEvents` block at serve time — as an alternative to shipping a complete VAST tag. **Example:** ```json @@ -323,7 +323,7 @@ DAAST (Digital Audio Ad Serving Template) tags for third-party audio ad serving. ### DAAST Tracker Asset -Audio-side analogue of `vast_tracker`: a single URL bound to a DAAST `` event. +Audio-side analogue of `vast_tracker`: a single URL bound to a DAAST `TrackingEvents` event. **Example:** ```json @@ -358,7 +358,7 @@ A creative manifest MAY emit VAST trackers in **one** of two shapes. The creativ } ``` -**Decomposed trackers** — the creative agent emits per-event URLs; the sales agent assembles the `` block: +**Decomposed trackers** — the creative agent emits per-event URLs; the sales agent assembles the `TrackingEvents` block: ```json [ @@ -372,8 +372,8 @@ A creative manifest MAY emit VAST trackers in **one** of two shapes. The creativ ### Normative rules -1. **Wrapper ownership.** The **sales agent** is responsible for constructing the VAST `` / `` envelope and any `` block. The **creative agent** MUST NOT emit pre-wrapped VAST. When the creative agent hands off a `vast` asset with `delivery_type: "url"`, the sales agent treats that URL as the wrapper target. -2. **Impression vs `` split.** The VAST `` URL is **not** a tracking event. It MUST be modeled as a `url` asset with `url_type: "tracker_pixel"`. A `vast_tracker` asset MUST NOT have `vast_event: "impression"`. The same rule applies to DAAST. +1. **Wrapper ownership.** The **sales agent** is responsible for constructing the VAST `Wrapper` / `InLine` envelope and any `AdVerifications` block. The **creative agent** MUST NOT emit pre-wrapped VAST. When the creative agent hands off a `vast` asset with `delivery_type: "url"`, the sales agent treats that URL as the wrapper target. +2. **Impression vs `TrackingEvents` split.** The VAST `Impression` URL is **not** a tracking event. It MUST be modeled as a `url` asset with `url_type: "tracker_pixel"`. A `vast_tracker` asset MUST NOT have `vast_event: "impression"`. The same rule applies to DAAST. 3. **Mixing allowed.** A creative manifest MAY mix a complete `vast` tag with additional `vast_tracker` and `url` (impression) assets — the sales agent merges them into one VAST document at serve time. ### SSAI caveat diff --git a/static/schemas/source/core/assets/daast-tracker-asset.json b/static/schemas/source/core/assets/daast-tracker-asset.json index 9178f946c6..f2aba2ed18 100644 --- a/static/schemas/source/core/assets/daast-tracker-asset.json +++ b/static/schemas/source/core/assets/daast-tracker-asset.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/core/assets/daast-tracker-asset.json", "title": "DAAST Tracker Asset", - "description": "A single tracker URL bound to a DAAST `` event. Audio-side analogue of vast-tracker-asset. The `` URL MUST be modeled as a `url` asset with `url_type: \"tracker_pixel\"`, not as a daast_tracker with `daast_event: \"impression\"`.", + "description": "A single tracker URL bound to a DAAST `TrackingEvents` event. Audio-side analogue of vast-tracker-asset. The `Impression` URL MUST be modeled as a `url` asset with `url_type: \"tracker_pixel\"`, not as a daast_tracker with `daast_event: \"impression\"`.", "type": "object", "properties": { "asset_type": { @@ -12,7 +12,7 @@ }, "daast_event": { "$ref": "/schemas/enums/daast-tracking-event.json", - "description": "The DAAST tracking event this URL fires on. Maps 1:1 to the DAAST `` attribute. MUST NOT be `impression` — use a `url` asset with `url_type: \"tracker_pixel\"` for impression trackers." + "description": "The DAAST tracking event this URL fires on. Maps 1:1 to the DAAST `Tracking event=\"...\"` attribute. MUST NOT be `impression` \u2014 use a `url` asset with `url_type: \"tracker_pixel\"` for impression trackers." }, "url": { "type": "string", diff --git a/static/schemas/source/core/assets/vast-tracker-asset.json b/static/schemas/source/core/assets/vast-tracker-asset.json index 75c129da6b..f0b91ca1ab 100644 --- a/static/schemas/source/core/assets/vast-tracker-asset.json +++ b/static/schemas/source/core/assets/vast-tracker-asset.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/core/assets/vast-tracker-asset.json", "title": "VAST Tracker Asset", - "description": "A single tracker URL bound to a VAST `` event. Emitted by the creative agent as a decomposed VAST-event URL; the sales agent assembles these into the VAST `` block at serve time. IMPORTANT: this asset type is for `` URLs only (start, quartiles, complete, pause, mute, etc.). The `` URL MUST be modeled as a `url` asset with `url_type: \"tracker_pixel\"`, not as a vast_tracker with `vast_event: \"impression\"`. Decomposed trackers let format requirements bind specific measurement events (e.g., MRC viewable) without forcing the buyer to construct a full VAST tag.", + "description": "A single tracker URL bound to a VAST `TrackingEvents` event. Emitted by the creative agent as a decomposed VAST-event URL; the sales agent assembles these into the VAST `TrackingEvents` block at serve time. IMPORTANT: this asset type is for `TrackingEvents` URLs only (start, quartiles, complete, pause, mute, etc.). The `Impression` URL MUST be modeled as a `url` asset with `url_type: \"tracker_pixel\"`, not as a vast_tracker with `vast_event: \"impression\"`. Decomposed trackers let format requirements bind specific measurement events (e.g., MRC viewable) without forcing the buyer to construct a full VAST tag.", "type": "object", "properties": { "asset_type": { @@ -12,7 +12,7 @@ }, "vast_event": { "$ref": "/schemas/enums/vast-tracking-event.json", - "description": "The VAST tracking event this URL fires on. Maps 1:1 to the VAST `` attribute. MUST NOT be `impression` — use a `url` asset with `url_type: \"tracker_pixel\"` for impression trackers (they belong in ``, not ``)." + "description": "The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event=\"...\"` attribute. MUST NOT be `impression` \u2014 use a `url` asset with `url_type: \"tracker_pixel\"` for impression trackers (they belong in `Impression`, not `TrackingEvents`)." }, "url": { "type": "string", diff --git a/static/schemas/source/creative/asset-types/index.json b/static/schemas/source/creative/asset-types/index.json index d28e4baa18..6bbc6d0ca1 100644 --- a/static/schemas/source/creative/asset-types/index.json +++ b/static/schemas/source/creative/asset-types/index.json @@ -32,12 +32,12 @@ "typical_use": "Third-party served audio ads with DAAST 1.0-1.1" }, "vast_tracker": { - "description": "A single URL bound to a VAST `` event (start, quartile, complete, etc.). Assembled into the VAST `` block by the sales agent. For `` URLs, use `url` asset with `url_type: \"tracker_pixel\"` instead.", + "description": "A single URL bound to a VAST `TrackingEvents` event (start, quartile, complete, etc.). Assembled into the VAST `TrackingEvents` block by the sales agent. For `Impression` URLs, use `url` asset with `url_type: \"tracker_pixel\"` instead.", "schema": "/schemas/core/assets/vast-tracker-asset.json", "typical_use": "Decomposed VAST event trackers on CTV/video formats where the creative agent emits per-event URLs rather than a complete VAST tag" }, "daast_tracker": { - "description": "Audio-side analogue of vast_tracker: a single URL bound to a DAAST `` event.", + "description": "Audio-side analogue of vast_tracker: a single URL bound to a DAAST `TrackingEvents` event.", "schema": "/schemas/core/assets/daast-tracker-asset.json", "typical_use": "Decomposed DAAST event trackers on audio formats" }, @@ -108,7 +108,7 @@ ] }, "nested_discriminator_pattern": { - "description": "Asset schemas with internal variants (e.g., VAST and DAAST support both URL-delivery and inline XML) use a two-level discriminator rather than splitting each variant into a separate top-level asset_type. The outer discriminator (`asset_type` on the composite map) selects the asset schema; an inner `oneOf` + `discriminator` inside that schema selects the variant. Future asset types with internal variants should follow this pattern — do not introduce sibling entries like `vast_url` and `vast_inline` at the registry level.", + "description": "Asset schemas with internal variants (e.g., VAST and DAAST support both URL-delivery and inline XML) use a two-level discriminator rather than splitting each variant into a separate top-level asset_type. The outer discriminator (`asset_type` on the composite map) selects the asset schema; an inner `oneOf` + `discriminator` inside that schema selects the variant. Future asset types with internal variants should follow this pattern \u2014 do not introduce sibling entries like `vast_url` and `vast_inline` at the registry level.", "examples": [ "vast-asset.json: outer asset_type='vast', inner delivery_type='url'|'inline'", "daast-asset.json: outer asset_type='daast', inner delivery_type='url'|'inline'" @@ -117,7 +117,7 @@ }, "usage_notes": { "format_specs": "Format specifications define what asset_ids are required (e.g., 'hero_image', 'logo'). Each defines its asset type and constraints (dimensions, file size, etc.).", - "creative_manifests": "Creative manifests provide actual asset content, keyed by asset_id from the format. Each asset value carries an `asset_type` discriminator (one of the registry keys) so validators can select the matching asset schema and report errors against only that branch. The format specification also defines what asset_type each asset_id should have — payload and format should agree.", + "creative_manifests": "Creative manifests provide actual asset content, keyed by asset_id from the format. Each asset value carries an `asset_type` discriminator (one of the registry keys) so validators can select the matching asset schema and report errors against only that branch. The format specification also defines what asset_type each asset_id should have \u2014 payload and format should agree.", "example_flow": "Format says 'hero_image' must be type 'image' with width 1200, height 627. Manifest provides hero_image: {url: '...', width: 1200, height: 627}. The format spec tells us it's an image type." } } From 70babd8ce0e0fb1e4f607bffeb96562d9fc7d680 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Fri, 24 Apr 2026 14:24:43 -0400 Subject: [PATCH 3/6] fix(docs): link to /docs/creative/universal-macros with /docs prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mintlify broken-links resolves internal links through its React-based navigation; an unmatched route cascades into an "Invalid hook call" React crash and non-zero exit. Every other doc in this repo that references universal-macros uses the /docs/ prefix (agentic-execution-engine.mdx, key-concepts.mdx, dooh.mdx, etc.). My new link was missing the prefix. Reproduced locally — with the prefix mintlify returns exit 0; without it, exit 1 and the React stack trace seen in CI. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/creative/asset-types.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/creative/asset-types.mdx b/docs/creative/asset-types.mdx index 6728b58896..953b054f2b 100644 --- a/docs/creative/asset-types.mdx +++ b/docs/creative/asset-types.mdx @@ -271,7 +271,7 @@ A single URL bound to a VAST `TrackingEvents` event. Use this when the creative **Properties:** - `vast_event`: The event this URL fires on. Any VAST tracking-event enum value **except** `impression` (see normative rule below). Common values: `start`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `pause`, `resume`, `mute`, `unmute`, `skip`, `clickTracking`, `viewableImpression`. -- `url`: Tracker URL. Supports [AdCP universal macros](/creative/universal-macros). +- `url`: Tracker URL. Supports [AdCP universal macros](/docs/creative/universal-macros). - `offset`: Required for `vast_event: "progress"`. VAST offset format: `HH:MM:SS[.mmm]` or an integer percentage (`50%`). ### DAAST Asset From cad0b41a1de716da53e57ee0c345ff56b4695a35 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Fri, 24 Apr 2026 14:32:28 -0400 Subject: [PATCH 4/6] fix(schema): enforce vast_event/daast_event exclusions + tighten offset regex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses protocol-expert review blockers on #3051: 1. vast_event/daast_event now use allOf + not:{enum:[...]} to hard- fail impression, clickTracking, customClick, error. Was prose-only before. Makes the routing rule schema-checkable. 2. offset pattern now matches VAST 4.x XSD: HH:MM:SS with optional exactly-3-digit fractional, negative allowed (from end of ad); percentage 0-100 only. Was permitting HH:MM:SS.X and 999%. 3. Rewrote "Wrapper ownership" normative rule: prohibits (multi-hop chain construct) specifically, not inline VAST. The previous wording conflicted with the existing inline-VAST example at universal-macros.mdx which shows Ad>InLine>Creatives content — that pattern remains valid. 4. Added "Tracker routing by element" rule — VAST's tracker URLs live in three elements (Impression, TrackingEvents, VideoClicks) and AdCP maps them to three asset shapes. Don't cross the streams. 5. Noted OMID/AdVerifications decomposition as planned follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/creative/asset-types.mdx | 18 +++++++++++------ .../core/assets/daast-tracker-asset.json | 20 ++++++++++++++++--- .../core/assets/vast-tracker-asset.json | 20 ++++++++++++++++--- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/docs/creative/asset-types.mdx b/docs/creative/asset-types.mdx index 953b054f2b..3a8a5f6c97 100644 --- a/docs/creative/asset-types.mdx +++ b/docs/creative/asset-types.mdx @@ -270,9 +270,9 @@ A single URL bound to a VAST `TrackingEvents` event. Use this when the creative ``` **Properties:** -- `vast_event`: The event this URL fires on. Any VAST tracking-event enum value **except** `impression` (see normative rule below). Common values: `start`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `pause`, `resume`, `mute`, `unmute`, `skip`, `clickTracking`, `viewableImpression`. +- `vast_event`: The event this URL fires on. Any VAST tracking-event enum value **except** `impression`, `clickTracking`, `customClick`, and `error` — those live in other VAST elements (see Tracker routing normative rule below). Common values: `start`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `pause`, `resume`, `mute`, `unmute`, `skip`, `viewableImpression`. - `url`: Tracker URL. Supports [AdCP universal macros](/docs/creative/universal-macros). -- `offset`: Required for `vast_event: "progress"`. VAST offset format: `HH:MM:SS[.mmm]` or an integer percentage (`50%`). +- `offset`: Required for `vast_event: "progress"`. VAST 4.x offset format: `HH:MM:SS` or `HH:MM:SS.mmm` (exactly 3 fractional digits) for absolute time, or an integer percentage 0–100 suffixed with `%`. Negative time offsets (measured from end of ad) are allowed per VAST 4.x. ### DAAST Asset @@ -335,7 +335,7 @@ Audio-side analogue of `vast_tracker`: a single URL bound to a DAAST `TrackingEv ``` **Properties:** -- `daast_event`: Any DAAST tracking-event enum value **except** `impression`. +- `daast_event`: Any DAAST tracking-event enum value **except** `impression`, `clickTracking`, `customClick`, and `error` (same routing rule as `vast_tracker`). - `url`: Tracker URL. Supports AdCP universal macros. - `offset`: Required for `daast_event: "progress"`. Same format as VAST offset. @@ -372,9 +372,15 @@ A creative manifest MAY emit VAST trackers in **one** of two shapes. The creativ ### Normative rules -1. **Wrapper ownership.** The **sales agent** is responsible for constructing the VAST `Wrapper` / `InLine` envelope and any `AdVerifications` block. The **creative agent** MUST NOT emit pre-wrapped VAST. When the creative agent hands off a `vast` asset with `delivery_type: "url"`, the sales agent treats that URL as the wrapper target. -2. **Impression vs `TrackingEvents` split.** The VAST `Impression` URL is **not** a tracking event. It MUST be modeled as a `url` asset with `url_type: "tracker_pixel"`. A `vast_tracker` asset MUST NOT have `vast_event: "impression"`. The same rule applies to DAAST. -3. **Mixing allowed.** A creative manifest MAY mix a complete `vast` tag with additional `vast_tracker` and `url` (impression) assets — the sales agent merges them into one VAST document at serve time. +1. **Wrapper is a sales-agent concern.** The creative agent MUST NOT emit a VAST `Wrapper` element. `Wrapper` is a multi-hop chaining construct — if the served VAST needs to be wrapped (for macro signing, SSAI-compatible envelope, or ad-server redirection), the **sales agent** constructs the wrapper at serve time around the creative agent's output. Emitting an inline VAST document with `Ad > InLine > Creatives` content (the pattern shown in [Universal Macros](/docs/creative/universal-macros)) is permitted and unchanged. +2. **Assembly responsibilities.** The sales agent is responsible for constructing VAST envelope elements that assemble resources from the creative agent: `TrackingEvents` (from `vast_tracker` assets), `AdVerifications` (from OMID verification resources), and `Wrapper` / `VASTAdTagURI` (if chaining). The creative agent supplies the resources; the sales agent places them in the correct element. A dedicated `omid_verification` asset type for decomposing OMID resources is a planned follow-up. +3. **Tracker routing by element.** VAST's tracker URLs live in three different elements and AdCP models them with three different asset shapes — don't cross the streams: + - `Impression` URL → `url` asset with `url_type: "tracker_pixel"` (not a `vast_tracker`). + - `TrackingEvents` events (start, quartiles, complete, pause, etc.) → `vast_tracker` asset with `vast_event`. + - `VideoClicks` URLs (`ClickThrough`, `ClickTracking`, `CustomClick`) → `url` asset (`url_type: "clickthrough"` for `ClickThrough`; click-tracker URLs use `url_type: "tracker_pixel"`). + + The `vast_tracker` schema enforces this split: `vast_event` MUST NOT be `impression`, `clickTracking`, `customClick`, or `error`. The same rule applies to DAAST. +4. **Mixing allowed.** A creative manifest MAY mix a complete `vast` tag with additional `vast_tracker` and `url` (impression / click) assets — the sales agent merges them into one VAST document at serve time. ### SSAI caveat diff --git a/static/schemas/source/core/assets/daast-tracker-asset.json b/static/schemas/source/core/assets/daast-tracker-asset.json index f2aba2ed18..7b6fb0f1b1 100644 --- a/static/schemas/source/core/assets/daast-tracker-asset.json +++ b/static/schemas/source/core/assets/daast-tracker-asset.json @@ -11,8 +11,22 @@ "description": "Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry." }, "daast_event": { - "$ref": "/schemas/enums/daast-tracking-event.json", - "description": "The DAAST tracking event this URL fires on. Maps 1:1 to the DAAST `Tracking event=\"...\"` attribute. MUST NOT be `impression` \u2014 use a `url` asset with `url_type: \"tracker_pixel\"` for impression trackers." + "allOf": [ + { + "$ref": "/schemas/enums/daast-tracking-event.json" + }, + { + "not": { + "enum": [ + "impression", + "clickTracking", + "customClick", + "error" + ] + } + } + ], + "description": "The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: \"tracker_pixel\"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), or `error`." }, "url": { "type": "string", @@ -22,7 +36,7 @@ "offset": { "type": "string", "description": "DAAST `offset` attribute for `daast_event: \"progress\"`. Format: `HH:MM:SS[.mmm]` for absolute time, or an integer percentage suffixed with `%`. Ignored for other events.", - "pattern": "^(\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,3})?|\\d{1,3}%)$" + "pattern": "^-?\\d+:\\d{2}:\\d{2}(\\.\\d{3})?$|^(100|\\d{1,2})%$" }, "provenance": { "$ref": "/schemas/core/provenance.json", diff --git a/static/schemas/source/core/assets/vast-tracker-asset.json b/static/schemas/source/core/assets/vast-tracker-asset.json index f0b91ca1ab..db7744eebf 100644 --- a/static/schemas/source/core/assets/vast-tracker-asset.json +++ b/static/schemas/source/core/assets/vast-tracker-asset.json @@ -11,8 +11,22 @@ "description": "Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry." }, "vast_event": { - "$ref": "/schemas/enums/vast-tracking-event.json", - "description": "The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event=\"...\"` attribute. MUST NOT be `impression` \u2014 use a `url` asset with `url_type: \"tracker_pixel\"` for impression trackers (they belong in `Impression`, not `TrackingEvents`)." + "allOf": [ + { + "$ref": "/schemas/enums/vast-tracking-event.json" + }, + { + "not": { + "enum": [ + "impression", + "clickTracking", + "customClick", + "error" + ] + } + } + ], + "description": "The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event=\"...\"` attribute inside `TrackingEvents`. MUST NOT be `impression` (that belongs in the VAST `Impression` element \u2014 model as a `url` asset with `url_type: \"tracker_pixel\"`), `clickTracking` / `customClick` (those belong in `VideoClicks`), or `error` (VAST `Error` element)." }, "url": { "type": "string", @@ -22,7 +36,7 @@ "offset": { "type": "string", "description": "VAST `offset` attribute for `vast_event: \"progress\"`. Format matches VAST: `HH:MM:SS[.mmm]` for absolute time, or an integer percentage suffixed with `%`. Ignored for other events.", - "pattern": "^(\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,3})?|\\d{1,3}%)$" + "pattern": "^-?\\d+:\\d{2}:\\d{2}(\\.\\d{3})?$|^(100|\\d{1,2})%$" }, "provenance": { "$ref": "/schemas/core/provenance.json", From e0a2b4f59ba9a789eb26d7dce852fd608a6f8c4d Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 29 Apr 2026 08:28:45 -0400 Subject: [PATCH 5/6] fix(schema): VAST/DAAST tracker offset matches XSD; carve out ViewableImpression MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses Baiyu's review on #3051. The `offset` pattern previously allowed a leading minus and used a permissive `\d+:\d{2}:\d{2}` shape; the VAST 4.2 XSD `Tracking@offset` pattern allows neither. Tighten to two-digit hours and 0–59 minute/second components, drop the negative branch, and add a JSON Schema `if/then` that requires `offset` whenever `vast_event` / `daast_event` is `progress`. Also bar `viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, and `viewableImpression` from `vast_event` / `daast_event`: those are children of the VAST `` element, not values that can appear under ``. Asset-types.mdx prose updated to match (removed the incorrect "negative offsets allowed" claim and dropped `viewableImpression` from the example value list). Co-Authored-By: Claude Opus 4.7 (1M context) --- .changeset/vast-tracker-asset-type.md | 6 ++++- docs/creative/asset-types.mdx | 8 +++---- .../core/assets/daast-tracker-asset.json | 24 +++++++++++++++---- .../core/assets/vast-tracker-asset.json | 24 +++++++++++++++---- 4 files changed, 49 insertions(+), 13 deletions(-) diff --git a/.changeset/vast-tracker-asset-type.md b/.changeset/vast-tracker-asset-type.md index 2227a5fa6b..3e5682308e 100644 --- a/.changeset/vast-tracker-asset-type.md +++ b/.changeset/vast-tracker-asset-type.md @@ -2,4 +2,8 @@ "adcontextprotocol": minor --- -Add `vast_tracker` and `daast_tracker` asset types for decomposed VAST/DAAST `` URLs. Creative agents can now emit per-event tracker URLs (start, quartiles, complete, etc.) as a discriminated-union alternative to a complete VAST tag; the sales agent assembles them into the VAST `` block at serve time. Adds normative creative/sales boundary: wrapper ownership belongs to the sales agent, and the `` URL stays on `url` asset with `url_type: "tracker_pixel"` (not `vast_tracker` with `vast_event: "impression"`). Non-breaking — new asset types only. +Add `vast_tracker` and `daast_tracker` asset types for decomposed VAST/DAAST `` URLs. Creative agents can now emit per-event tracker URLs (start, quartiles, complete, etc.) as a discriminated-union alternative to a complete VAST tag; the sales agent assembles them into the VAST `` block at serve time. Adds normative creative/sales boundary: wrapper ownership belongs to the sales agent, and the `` URL stays on `url` asset with `url_type: "tracker_pixel"` (not `vast_tracker` with `vast_event: "impression"`). + +The `offset` pattern aligns with the VAST 4.2 XSD `Tracking@offset` constraint: `HH:MM:SS[.mmm]` with two-digit hours and minutes/seconds 00–59, or an integer percentage 0–100 suffixed with `%`. Negative offsets are not permitted (the VAST XSD pattern does not allow a leading minus). Tracker assets enforce a JSON Schema `if/then` requiring `offset` whenever `vast_event` / `daast_event` is `progress`, and exclude both VAST/DAAST element-children that don't live under `TrackingEvents` (`impression`, `clickTracking`, `customClick`, `error`) and `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`). + +Non-breaking — new asset types only. diff --git a/docs/creative/asset-types.mdx b/docs/creative/asset-types.mdx index 3a8a5f6c97..8b99f95cc6 100644 --- a/docs/creative/asset-types.mdx +++ b/docs/creative/asset-types.mdx @@ -270,9 +270,9 @@ A single URL bound to a VAST `TrackingEvents` event. Use this when the creative ``` **Properties:** -- `vast_event`: The event this URL fires on. Any VAST tracking-event enum value **except** `impression`, `clickTracking`, `customClick`, and `error` — those live in other VAST elements (see Tracker routing normative rule below). Common values: `start`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `pause`, `resume`, `mute`, `unmute`, `skip`, `viewableImpression`. +- `vast_event`: The event this URL fires on. Any VAST tracking-event enum value **except** `impression`, `clickTracking`, `customClick`, `error`, or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` — those live in dedicated VAST elements (`Impression`, `VideoClicks`, `Error`, `ViewableImpression`), not under `TrackingEvents` (see Tracker routing normative rule below). Common values: `start`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `pause`, `resume`, `mute`, `unmute`, `skip`, `progress`. - `url`: Tracker URL. Supports [AdCP universal macros](/docs/creative/universal-macros). -- `offset`: Required for `vast_event: "progress"`. VAST 4.x offset format: `HH:MM:SS` or `HH:MM:SS.mmm` (exactly 3 fractional digits) for absolute time, or an integer percentage 0–100 suffixed with `%`. Negative time offsets (measured from end of ad) are allowed per VAST 4.x. +- `offset`: Required for `vast_event: "progress"`. VAST 4.2 offset format (`Tracking@offset`): `HH:MM:SS` or `HH:MM:SS.mmm` (two-digit hours, minutes 00–59, seconds 00–59, exactly 3 fractional digits when present) for absolute time, or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus. ### DAAST Asset @@ -335,9 +335,9 @@ Audio-side analogue of `vast_tracker`: a single URL bound to a DAAST `TrackingEv ``` **Properties:** -- `daast_event`: Any DAAST tracking-event enum value **except** `impression`, `clickTracking`, `customClick`, and `error` (same routing rule as `vast_tracker`). +- `daast_event`: Any DAAST tracking-event enum value **except** `impression`, `clickTracking`, `customClick`, `error`, or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (same element-routing rule as `vast_tracker` — those values belong in `Impression` / `VideoClicks` / `Error` / `ViewableImpression`, not `TrackingEvents`). - `url`: Tracker URL. Supports AdCP universal macros. -- `offset`: Required for `daast_event: "progress"`. Same format as VAST offset. +- `offset`: Required for `daast_event: "progress"`. Same format as VAST 4.2 `Tracking@offset`. Negative offsets are NOT permitted. ## Creative / Sales Agent Boundary (VAST Assembly) diff --git a/static/schemas/source/core/assets/daast-tracker-asset.json b/static/schemas/source/core/assets/daast-tracker-asset.json index 7b6fb0f1b1..c65b6f71b5 100644 --- a/static/schemas/source/core/assets/daast-tracker-asset.json +++ b/static/schemas/source/core/assets/daast-tracker-asset.json @@ -21,12 +21,17 @@ "impression", "clickTracking", "customClick", - "error" + "error", + "viewable", + "notViewable", + "viewUndetermined", + "measurableImpression", + "viewableImpression" ] } } ], - "description": "The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: \"tracker_pixel\"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), or `error`." + "description": "The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: \"tracker_pixel\"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`)." }, "url": { "type": "string", @@ -35,8 +40,8 @@ }, "offset": { "type": "string", - "description": "DAAST `offset` attribute for `daast_event: \"progress\"`. Format: `HH:MM:SS[.mmm]` for absolute time, or an integer percentage suffixed with `%`. Ignored for other events.", - "pattern": "^-?\\d+:\\d{2}:\\d{2}(\\.\\d{3})?$|^(100|\\d{1,2})%$" + "description": "DAAST `offset` attribute. Required when `daast_event` is `progress`; ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.", + "pattern": "^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$" }, "provenance": { "$ref": "/schemas/core/provenance.json", @@ -48,5 +53,16 @@ "daast_event", "url" ], + "allOf": [ + { + "if": { + "properties": { "daast_event": { "const": "progress" } }, + "required": ["daast_event"] + }, + "then": { + "required": ["offset"] + } + } + ], "additionalProperties": true } diff --git a/static/schemas/source/core/assets/vast-tracker-asset.json b/static/schemas/source/core/assets/vast-tracker-asset.json index db7744eebf..7784329b21 100644 --- a/static/schemas/source/core/assets/vast-tracker-asset.json +++ b/static/schemas/source/core/assets/vast-tracker-asset.json @@ -21,12 +21,17 @@ "impression", "clickTracking", "customClick", - "error" + "error", + "viewable", + "notViewable", + "viewUndetermined", + "measurableImpression", + "viewableImpression" ] } } ], - "description": "The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event=\"...\"` attribute inside `TrackingEvents`. MUST NOT be `impression` (that belongs in the VAST `Impression` element \u2014 model as a `url` asset with `url_type: \"tracker_pixel\"`), `clickTracking` / `customClick` (those belong in `VideoClicks`), or `error` (VAST `Error` element)." + "description": "The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event=\"...\"` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element \u2014 model as a `url` asset with `url_type: \"tracker_pixel\"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`)." }, "url": { "type": "string", @@ -35,8 +40,8 @@ }, "offset": { "type": "string", - "description": "VAST `offset` attribute for `vast_event: \"progress\"`. Format matches VAST: `HH:MM:SS[.mmm]` for absolute time, or an integer percentage suffixed with `%`. Ignored for other events.", - "pattern": "^-?\\d+:\\d{2}:\\d{2}(\\.\\d{3})?$|^(100|\\d{1,2})%$" + "description": "VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00\u201359, seconds 00\u201359), or an integer percentage 0\u2013100 suffixed with `%`. Negative offsets are NOT permitted \u2014 the VAST 4.2 XSD pattern does not allow a leading minus.", + "pattern": "^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$" }, "provenance": { "$ref": "/schemas/core/provenance.json", @@ -48,5 +53,16 @@ "vast_event", "url" ], + "allOf": [ + { + "if": { + "properties": { "vast_event": { "const": "progress" } }, + "required": ["vast_event"] + }, + "then": { + "required": ["offset"] + } + } + ], "additionalProperties": true } From 44cf6d3ec8aec9f45f6be20d36eadb53895e2879 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 29 Apr 2026 10:41:46 -0400 Subject: [PATCH 6/6] fix(schema): align VAST/DAAST tracking enums to spec; add target field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Layered corrective alignment to the IAB spec source documents: - VAST 4.2 XSD `TrackingEvents_type/Tracking/@event` (vast_4.2.xsd lines 112-136) is the authoritative enumeration. Add the five values that were missing from `vast-tracking-event.json`: `acceptInvitation`, `adExpand`, `adCollapse`, `minimize`, `overlayViewDuration`. Drop `notUsed`, which is not in the 4.2 XSD enumeration. Keep `fullscreen` / `exitFullscreen` and label them as VAST 2.x / 3.x compat (replaced by `playerExpand` / `playerCollapse` in 4.0+). - DAAST 1.1 §3.2.1.7 lists exactly 13 audio-applicable events. Add the one that was missing: `rewind`. Drop `loaded`, which is not in DAAST 1.1 §3.2.1.7. Retain `progress` per DAAST 1.1 §3.2.4.3 (with the `offset` attribute) and `close` per DAAST 1.1 §3.2.4.2. Update the enum description to call out which video-only VAST events are deliberately excluded. - Add `target` field to both tracker assets so the sales agent can place them under the correct `` parent during VAST/ DAAST assembly. VAST 4.2 has three locations (`` / `` / `/`) with different valid event sets — `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear. DAAST has no `` element, so its `target` enum is just `linear` | `companion`. Defaults to `linear` for both. asset-types.mdx prose updated with the new enum values, the audio-incompatible exclusion list for DAAST, and a `target` example on `vast_tracker`. Co-Authored-By: Claude Opus 4.7 (1M context) --- .changeset/vast-tracker-asset-type.md | 14 ++++++-- docs/creative/asset-types.mdx | 35 +++++++++++++------ .../core/assets/daast-tracker-asset.json | 8 ++++- .../core/assets/vast-tracker-asset.json | 6 ++++ .../source/enums/daast-tracking-event.json | 4 +-- .../source/enums/vast-tracking-event.json | 8 +++-- 6 files changed, 58 insertions(+), 17 deletions(-) diff --git a/.changeset/vast-tracker-asset-type.md b/.changeset/vast-tracker-asset-type.md index 3e5682308e..44db8d921b 100644 --- a/.changeset/vast-tracker-asset-type.md +++ b/.changeset/vast-tracker-asset-type.md @@ -4,6 +4,16 @@ Add `vast_tracker` and `daast_tracker` asset types for decomposed VAST/DAAST `` URLs. Creative agents can now emit per-event tracker URLs (start, quartiles, complete, etc.) as a discriminated-union alternative to a complete VAST tag; the sales agent assembles them into the VAST `` block at serve time. Adds normative creative/sales boundary: wrapper ownership belongs to the sales agent, and the `` URL stays on `url` asset with `url_type: "tracker_pixel"` (not `vast_tracker` with `vast_event: "impression"`). -The `offset` pattern aligns with the VAST 4.2 XSD `Tracking@offset` constraint: `HH:MM:SS[.mmm]` with two-digit hours and minutes/seconds 00–59, or an integer percentage 0–100 suffixed with `%`. Negative offsets are not permitted (the VAST XSD pattern does not allow a leading minus). Tracker assets enforce a JSON Schema `if/then` requiring `offset` whenever `vast_event` / `daast_event` is `progress`, and exclude both VAST/DAAST element-children that don't live under `TrackingEvents` (`impression`, `clickTracking`, `customClick`, `error`) and `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`). +**Tracker asset constraints (from authoritative spec):** -Non-breaking — new asset types only. +- `offset` pattern aligns with the VAST 4.2 XSD `Tracking@offset` constraint (`vast_4.2.xsd` line 146): `HH:MM:SS[.mmm]` with two-digit hours and minutes/seconds 00–59, or an integer percentage 0–100 suffixed with `%`. Negative offsets are not permitted — the VAST XSD pattern has no leading-minus branch. +- A JSON Schema `if/then` requires `offset` whenever `vast_event` / `daast_event` is `progress` (mirrors the XSD documentation: "Must be present for progress event"). +- `vast_event` / `daast_event` exclude both VAST/DAAST element-children that don't live under `` (`impression`, `clickTracking`, `customClick`, `error`) and ``-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`). +- Each tracker carries a `target` field (`linear` | `non_linear` | `companion` for VAST; `linear` | `companion` for DAAST, since DAAST has no `` element) so the sales agent places the tracker under the correct `` parent during XML assembly. + +**Tracking-event enum corrections (corrective alignment to spec):** + +- VAST: add the five VAST 4.2 events that were missing from `vast-tracking-event.json` (`acceptInvitation`, `adExpand`, `adCollapse`, `minimize`, `overlayViewDuration` — all in the XSD enumeration). Drop `notUsed`, which was incorrectly inherited from earlier draft work and is not in the VAST 4.2 XSD `Tracking@event` enumeration. `fullscreen` / `exitFullscreen` are kept and labeled as VAST 2.x / 3.x compat. +- DAAST: add `rewind` (DAAST 1.1 §3.2.1.7 lists it explicitly). Drop `loaded`, which is not in DAAST 1.1 §3.2.1.7. `progress` is retained per DAAST 1.1 §3.2.4.3. + +These enum corrections are nominally breaking for the existing `tracking_events` field on the `vast` / `daast` asset types, but the dropped values were never spec-correct (`notUsed` is not in the VAST 4.2 XSD; `loaded` is not in DAAST 1.1 §3.2.1.7) — fixing them up before the new tracker assets reference these enums avoids carrying the inconsistency forward. diff --git a/docs/creative/asset-types.mdx b/docs/creative/asset-types.mdx index 8b99f95cc6..217b9e3f2d 100644 --- a/docs/creative/asset-types.mdx +++ b/docs/creative/asset-types.mdx @@ -232,10 +232,11 @@ VAST (Video Ad Serving Template) tags for third-party video ad serving. - `vpaid_enabled`: Whether VPAID (Video Player-Ad Interface Definition) is supported - `max_wrapper_depth`: Maximum allowed wrapper/redirect depth - `duration_ms`: Expected video duration in milliseconds (if known) -- `tracking_events`: Array of supported tracking events. Valid values are defined by the [VAST Tracking Event](https://adcontextprotocol.org/schemas/v3/enums/vast-tracking-event.json) enum. Includes IAB VAST 4.2 TrackingEvents plus flattened representations of Impression, Error, VideoClicks, and ViewableImpression elements: +- `tracking_events`: Array of supported tracking events. Valid values are defined by the [VAST Tracking Event](https://adcontextprotocol.org/schemas/v3/enums/vast-tracking-event.json) enum. Aligned to the IAB VAST 4.2 XSD `TrackingEvents_type/Tracking/@event` enumeration (vast_4.2.xsd lines 112–136), plus AdCP-flattened representations of `Impression`, `Error`, `VideoClicks`, and `ViewableImpression` elements: - **Playback**: `impression` (billing event), `creativeView`, `loaded`, `start`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete` - - **Interaction**: `mute`, `unmute`, `pause`, `resume`, `rewind`, `skip`, `playerExpand`, `playerCollapse`, `fullscreen` (pre-4.0 compat), `exitFullscreen` (pre-4.0 compat), `otherAdInteraction`, `interactiveStart` (SIMID) - - **Progress**: `progress` (for custom progress points via VAST `offset` attribute), `notUsed` (prefetched but not played) + - **Interaction**: `mute`, `unmute`, `pause`, `resume`, `rewind`, `skip`, `playerExpand`, `playerCollapse`, `fullscreen` (VAST 2.x/3.x compat), `exitFullscreen` (VAST 2.x/3.x compat), `otherAdInteraction`, `interactiveStart` (SIMID) + - **Non-linear / companion**: `acceptInvitation`, `adExpand`, `adCollapse`, `minimize`, `overlayViewDuration` + - **Progress**: `progress` (for custom progress points via VAST `offset` attribute) - **Click & close**: `clickTracking`, `customClick`, `close`, `closeLinear` - **Verification**: `viewable`, `notViewable`, `viewUndetermined`, `measurableImpression` (AdCP extension), `viewableImpression` - **Errors**: `error` @@ -273,6 +274,17 @@ A single URL bound to a VAST `TrackingEvents` event. Use this when the creative - `vast_event`: The event this URL fires on. Any VAST tracking-event enum value **except** `impression`, `clickTracking`, `customClick`, `error`, or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` — those live in dedicated VAST elements (`Impression`, `VideoClicks`, `Error`, `ViewableImpression`), not under `TrackingEvents` (see Tracker routing normative rule below). Common values: `start`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `pause`, `resume`, `mute`, `unmute`, `skip`, `progress`. - `url`: Tracker URL. Supports [AdCP universal macros](/docs/creative/universal-macros). - `offset`: Required for `vast_event: "progress"`. VAST 4.2 offset format (`Tracking@offset`): `HH:MM:SS` or `HH:MM:SS.mmm` (two-digit hours, minutes 00–59, seconds 00–59, exactly 3 fractional digits when present) for absolute time, or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus. +- `target`: Which VAST creative element scopes this tracker — `linear` (default), `non_linear`, or `companion`. VAST 4.2 places `` under three different parents (``, ``, `/`) with different valid event sets — e.g., `acceptInvitation` is meaningful on `non_linear`/`companion`, `closeLinear` only on `linear`. Sales agents read this to place the tracker correctly during VAST assembly. + +**Target with non-linear event:** +```json +{ + "asset_type": "vast_tracker", + "vast_event": "acceptInvitation", + "target": "non_linear", + "url": "https://tracker.example.com/accept" +} +``` ### DAAST Asset @@ -306,13 +318,15 @@ DAAST (Digital Audio Ad Serving Template) tags for third-party audio ad serving. - `content`: Inline DAAST XML content (required when delivery_type is "inline") - `daast_version`: DAAST specification version (1.0, 1.1) - `duration_ms`: Expected audio duration in milliseconds (if known) -- `tracking_events`: Array of supported tracking events. Valid values are defined by the [DAAST Tracking Event](https://adcontextprotocol.org/schemas/v3/enums/daast-tracking-event.json) enum. Includes DAAST-applicable events plus flattened Impression, Error, and ViewableImpression elements: - - **Playback**: `impression` (billing event), `creativeView` (companion ad display), `loaded`, `start`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete` - - **Interaction**: `mute`, `unmute`, `pause`, `resume`, `skip` - - **Progress**: `progress` +- `tracking_events`: Array of supported tracking events. Valid values are defined by the [DAAST Tracking Event](https://adcontextprotocol.org/schemas/v3/enums/daast-tracking-event.json) enum. Aligned to DAAST 1.1 §3.2.1.7 `` values (the audio-applicable subset of VAST), plus `close` (DAAST 1.1 §3.2.4.2) and AdCP-flattened representations of `Impression`, `Error`, and the click children of ``: + - **Playback**: `impression` (billing event), `creativeView` (also used for companion display per DAAST 1.1 §3.2.2.7), `start`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete` + - **Interaction**: `mute`, `unmute`, `pause`, `resume`, `rewind`, `skip` + - **Progress**: `progress` (with `offset` attribute, DAAST 1.1 §3.2.4.3) - **Click & close**: `clickTracking`, `customClick`, `close` - - **Verification**: `viewable`, `notViewable`, `viewUndetermined`, `measurableImpression` (AdCP extension), `viewableImpression` + - **Verification (AdCP extensions, OM-SDK Audio)**: `viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression` - **Errors**: `error` + +Video-only VAST events (`loaded`, `playerExpand`/`playerCollapse`, `fullscreen`/`exitFullscreen`, `acceptInvitation`, `adExpand`/`adCollapse`, `minimize`, `overlayViewDuration`, `interactiveStart`) are deliberately excluded — they have no defined audio semantics in DAAST 1.1. - `companion_ads`: Whether companion display ads are included **Use Cases:** @@ -335,9 +349,10 @@ Audio-side analogue of `vast_tracker`: a single URL bound to a DAAST `TrackingEv ``` **Properties:** -- `daast_event`: Any DAAST tracking-event enum value **except** `impression`, `clickTracking`, `customClick`, `error`, or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (same element-routing rule as `vast_tracker` — those values belong in `Impression` / `VideoClicks` / `Error` / `ViewableImpression`, not `TrackingEvents`). +- `daast_event`: Any DAAST tracking-event enum value **except** `impression`, `clickTracking`, `customClick`, `error`, or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (same element-routing rule as `vast_tracker` — those values belong in `Impression` / `` / `Error` / AdCP-extension verification slots, not `TrackingEvents`). Common values: `start`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `pause`, `resume`, `mute`, `unmute`, `rewind`, `skip`, `progress`. - `url`: Tracker URL. Supports AdCP universal macros. -- `offset`: Required for `daast_event: "progress"`. Same format as VAST 4.2 `Tracking@offset`. Negative offsets are NOT permitted. +- `offset`: Required for `daast_event: "progress"` (DAAST 1.1 §3.2.4.3). Same format as VAST 4.2 `Tracking@offset`. Negative offsets are NOT permitted. +- `target`: Which DAAST creative element scopes this tracker — `linear` (default, DAAST 1.1 §3.2.1.7) or `companion` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element — audio is linear or accompanied by a visual companion. ## Creative / Sales Agent Boundary (VAST Assembly) diff --git a/static/schemas/source/core/assets/daast-tracker-asset.json b/static/schemas/source/core/assets/daast-tracker-asset.json index c65b6f71b5..9dfdc10234 100644 --- a/static/schemas/source/core/assets/daast-tracker-asset.json +++ b/static/schemas/source/core/assets/daast-tracker-asset.json @@ -40,9 +40,15 @@ }, "offset": { "type": "string", - "description": "DAAST `offset` attribute. Required when `daast_event` is `progress`; ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.", + "description": "DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.", "pattern": "^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$" }, + "target": { + "type": "string", + "enum": ["linear", "companion"], + "default": "linear", + "description": "Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly." + }, "provenance": { "$ref": "/schemas/core/provenance.json", "description": "Provenance metadata for this asset, overrides manifest-level provenance." diff --git a/static/schemas/source/core/assets/vast-tracker-asset.json b/static/schemas/source/core/assets/vast-tracker-asset.json index 7784329b21..4412e46868 100644 --- a/static/schemas/source/core/assets/vast-tracker-asset.json +++ b/static/schemas/source/core/assets/vast-tracker-asset.json @@ -43,6 +43,12 @@ "description": "VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00\u201359, seconds 00\u201359), or an integer percentage 0\u2013100 suffixed with `%`. Negative offsets are NOT permitted \u2014 the VAST 4.2 XSD pattern does not allow a leading minus.", "pattern": "^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$" }, + "target": { + "type": "string", + "enum": ["linear", "non_linear", "companion"], + "default": "linear", + "description": "Which VAST creative element this tracker scopes to \u2014 `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly." + }, "provenance": { "$ref": "/schemas/core/provenance.json", "description": "Provenance metadata for this asset, overrides manifest-level provenance." diff --git a/static/schemas/source/enums/daast-tracking-event.json b/static/schemas/source/enums/daast-tracking-event.json index bdce6bf188..404009f65e 100644 --- a/static/schemas/source/enums/daast-tracking-event.json +++ b/static/schemas/source/enums/daast-tracking-event.json @@ -2,12 +2,11 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/enums/daast-tracking-event.json", "title": "DAAST Tracking Event", - "description": "Tracking events for audio ads. Includes DAAST-applicable events from IAB VAST/DAAST conventions, plus flattened Impression, Error, and ViewableImpression elements. creativeView included for companion ad tracking. measurableImpression is an AdCP extension.", + "description": "Tracking events for audio ads. Aligned to the IAB DAAST 1.1 `Tracking@event` enumeration from §3.2.1.7 of the spec (creativeView, start, firstQuartile, midpoint, thirdQuartile, complete, mute, unmute, pause, rewind, resume, skip, progress) — plus `close` (referenced descriptively in DAAST 1.1 §3.2.4.2 contrasting it with `skip`), and AdCP-flattened representations of the `Impression`, `Error`, and click elements (``, ``, and the click children of ``) so a single declared list can cover everything a measurement vendor wants to track. `viewable` / `notViewable` / `viewUndetermined` and `measurableImpression` / `viewableImpression` are AdCP extensions for OM-SDK Audio measurability signals (DAAST 1.1 itself does not define a `` element). DAAST 1.1 audio-incompatible video events are deliberately omitted: no `loaded`, `playerExpand` / `playerCollapse`, `fullscreen` / `exitFullscreen`, `acceptInvitation`, `adExpand` / `adCollapse`, `minimize`, `overlayViewDuration`, or `interactiveStart`.", "type": "string", "enum": [ "impression", "creativeView", - "loaded", "start", "firstQuartile", "midpoint", @@ -17,6 +16,7 @@ "unmute", "pause", "resume", + "rewind", "skip", "progress", "clickTracking", diff --git a/static/schemas/source/enums/vast-tracking-event.json b/static/schemas/source/enums/vast-tracking-event.json index 5af940bd0d..bd264ee3cb 100644 --- a/static/schemas/source/enums/vast-tracking-event.json +++ b/static/schemas/source/enums/vast-tracking-event.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/enums/vast-tracking-event.json", "title": "VAST Tracking Event", - "description": "Tracking events for video ads. Includes IAB VAST 4.2 TrackingEvents, plus flattened representations of Impression, Error, VideoClicks, and ViewableImpression elements. fullscreen/exitFullscreen retained for VAST 2.x/3.x compatibility. measurableImpression is an AdCP extension for MRC measurability signals.", + "description": "Tracking events for video ads. Includes the IAB VAST 4.2 `Tracking@event` enumeration (vast_4.2.xsd `TrackingEvents_type`, lines 112–136), plus AdCP-flattened representations of the `Impression`, `Error`, `VideoClicks`, and `ViewableImpression` elements (which live in dedicated VAST elements, not under `` — they are surfaced here so a single declared list can cover everything a measurement vendor wants to track). `fullscreen` / `exitFullscreen` are retained for VAST 2.x / 3.x compatibility (VAST 4.0+ replaced them with `playerExpand` / `playerCollapse`). `measurableImpression` is an AdCP extension for MRC measurability signals.", "type": "string", "enum": [ "impression", @@ -24,7 +24,11 @@ "fullscreen", "exitFullscreen", "progress", - "notUsed", + "acceptInvitation", + "adExpand", + "adCollapse", + "minimize", + "overlayViewDuration", "otherAdInteraction", "interactiveStart", "clickTracking",