feat(creative): add CTV experience profiles for AdCP 3.2 - #6591
Conversation
Implements RFC #6428: the six IAB CTV Ad Portfolio experiences map onto existing canonicals through an experience x contract matrix instead of channel-named formats. Red-teaming published platform specs (Fire TV, Samsung, Roku, Disney, Netflix, Google TV) showed no platform ingests NonLinear VAST for these placements today, so the matrix is deliberately plural: menu pairs with native_in_feed (new video slot, menu_placement, focus_behavior) and sponsored_placement (catalog tiles); pause and screensaver pair with image and video contracts; overlay pairs with a native asset bundle or nonlinear VAST; squeezeback and in_scene are seller-composited or VAST. - Shared vocabularies: ctv_ad_experience, motion_level (AdCOM attrs 21-23), activation_method (activations are engagement events, never impressions) - video_vast: creative_type (linear|nonlinear|either) supersedes linear_required; per-experience floors (overlay/squeezeback >= 10s, in_scene >= 3s + no interactivity, pause unfloored) - Semantic matrix validator (ctv-experience-matrix.ts) enforcing every verifier constraint; 21 new unit tests incl. pause+image pass - OpenRTB/AdCOM signaling annex documents plcmt 5-9, playbackmethod 8-11, motion attrs, Native plcmttype 1/3; VAST 4.4 stays out of the enum per the documented IAB-publication gate - Worked menu/pause/overlay examples and a graded compliance storyboard Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Escalate to human review
Escalate — gated schema paths require human/CODEOWNERS review (review_decision: REVIEW_REQUIRED).
This PR modifies static/schemas/source/** (8 files: 3 new enums, 5 modified canonical formats), which is under the repo's deterministic hard approval gate (gated_paths: true). review_decision is REVIEW_REQUIRED, not APPROVED, so per decision-table row 2 the gate is not satisfied and the outcome must be escalate. No critical/high findings were reported, so this is not request-changes.
Medium findings
- server/src/training-agent/ctv-experience-matrix.ts:245 —
ctv_activation_copy_slotshard-fails spec-valid products with no schema basis, contradicting the docs'landing_page_urlfallback. A human reviewer should confirm the intended validation semantics before merge.
Escalation reasons
- Touches gated files under
static/schemas/source/**(activation-method.json, ctv-ad-experience.json, motion-level.json plus modified image/native_in_feed/sponsored_placement/video_hosted/video_vast canonical formats); requires human/CODEOWNERS approval —review_decisionis currentlyREVIEW_REQUIRED.
Note: reviewer confirmed schema↔docs↔TS↔storyboard coherence, a correct additive minor changeset, and no oneOf regression — but the human gate on schema-source changes still stands.
Medium findings
- server/src/training-agent/ctv-experience-matrix.ts:245 — ctv_activation_copy_slots hard-fails spec-valid products with no schema basis
Why human review
- Modifies gated schema-source files under static/schemas/source/** (3 new enums + 5 modified canonical formats); review_decision is REVIEW_REQUIRED, so the hard approval gate requires human/CODEOWNERS review before merge.
- One medium finding (ctv-experience-matrix.ts:245): runtime invents a hard ctv_activation_copy_slots failure with no schema basis that contradicts the docs' landing_page_url fallback — worth a human eye on validation semantics.
- This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/enums/activation-method.json (added) matches
static/schemas/source/**; static/schemas/source/enums/ctv-ad-experience.json (added) matchesstatic/schemas/source/**; static/schemas/source/enums/motion-level.json (added) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/image.json (modified) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/native_in_feed.json (modified) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/sponsored_placement.json (modified) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/video_hosted.json (modified) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/video_vast.json (modified) matchesstatic/schemas/source/**) and the current GitHub review decision is 'REVIEW_REQUIRED', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.
|
Proposal review — @ Does the tree deliver what it claims?Yes, with two declared deviations from RFC #6428 and one undeclared one. Every claim I could check against the tree holds: the three enums exist as described; all five canonicals carry their matrix row via IAB accuracy — verified, not taken on trust: AdCOM v1.0 FINAL on IAB main does now carry plcmt 5–9, playbackmethod 8–11 ("Initiated by user pausing content, Sound on/off"; "Initiated by idling"), and creative attributes 21–23 (Static Visual / Limited Motion (Cinemagraph) / Full-Motion Video) — the annex table is accurate, and calling them ratified is correct as of today. The Signaling Guidelines confirm Menu→Native RFC deviations: (1) RFC #6428 wanted "VAST 4.3/4.4 in What the proposal gets rightThe core design decision — treating the IAB table as an OpenRTB signaling standard rather than a creative-supply contract, and modeling supply on seller ingestion — is well-evidenced and correctly executed as a declared fork: the annex explicitly separates the two, and the enum Findings1. warning —
|
Schema fixes: - image.json: unconditionally narrow motion_level to static/limited_motion (full_motion is AdCOM attr 23 / Full-Motion Video, incoherent on any static-image canonical, not just when ctv_ad_experience is set) - image.json: add ctv_activation_copy_slots to x-adcp-validation annotation - native_in_feed.json: tighten menu-profile conditional to require ctv_ad_experience: menu when menu_placement or focus_behavior is present - native_in_feed.json: add ctv_activation_copy_slots annotation - activation-method.json: make slot IDs normative with per-method mapping (push_notification→activation_message, text_message→activation_text_message, email→activation_email_subject+activation_email_body) Docs fixes (ctv-experiences.mdx): - Correct enforcement-attribution paragraph: distinguish JSON Schema conditionals (categorical) from semantic-validator rules (duration floors, unlisted-canonical matrix, focus_video_pairing, activation copy slots) - Correct activation_methods paragraph: name each method's normative slot distinctly (was conflating push/SMS under "activation_message") - motion_level paragraph: make unconditional narrowing explicit Compliance storyboard (ctv-experience-validate-input.yaml): - Grading note: document that duration floors and unlisted-canonical matrix checks are also semantic-only (not just focus_video_pairing) - reject_pause_full_motion_image description: remove "whenever ctv_ad_experience is set" — rejection is unconditional - Add ctv_invalid_pause_html5 invalid product fixture (html5+pause, uses html5_bundle/zip slot) - Add reject_pause_on_html5 defense-in-depth step exercising the semantic-only unlisted-canonical matrix check - Update product/step counts (thirteen→fourteen, seven→eight)
There was a problem hiding this comment.
Ladon verdict: Escalate to human review
Escalate — gated schema-source files require human/CODEOWNERS approval.
This is a clean iteration on the CTV experience matrix. The prior medium finding (ctv-experience-matrix.ts:245 — ctv_activation_copy_slots hard-failing spec-valid products) has been resolved: the reviewer now confirms the activation-copy-slots semantics match the reference validator, and all four JSON-Schema-conditional claims plus semantic-only claims verify against the schemas. New activation slot names are consistent across the enum description, image.json, native_in_feed.json, and ACTIVATION_COPY_SLOTS. Changeset present (.changeset/add-ctv-experience-profiles.md), product counts reconcile (6 valid + 8 invalid = 14), and the reviewer surfaced zero findings this run.
However, this PR touches files under a hard, non-overridable approval gate (static/schemas/source/**) and the current review_decision is not APPROVED. Per decision-table row 2 (gated_paths true AND review_decision ≠ APPROVED), the outcome must be escalate — Ladon cannot auto-approve schema-source changes until a human/CODEOWNERS approval is recorded, no matter how clean the diff is.
Escalation reasons
- Adds 3 new enums (activation-method.json, ctv-ad-experience.json, motion-level.json) and modifies 5 canonical format schemas under static/schemas/source/** — a gated protocol-surface change. review_decision is unknown/not APPROVED, so the hard approval gate stands.
Note: the prior blocking medium finding is gone; this escalation is purely the gated-path/human-ratification requirement, not a code defect.
Why human review
- Touches gated schema-source files under static/schemas/source/** (3 new enums: activation-method.json, ctv-ad-experience.json, motion-level.json; 5 modified canonical formats) and review_decision is not APPROVED — hard approval gate requires human/CODEOWNERS review before merge.
- New enum values on published protocol surface are a normative/potentially breaking-class change; ratification is a human act per the constitution's decision-class hard gate — escalate unless review_decision is APPROVED.
- This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/enums/activation-method.json (added) matches
static/schemas/source/**; static/schemas/source/enums/ctv-ad-experience.json (added) matchesstatic/schemas/source/**; static/schemas/source/enums/motion-level.json (added) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/image.json (modified) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/native_in_feed.json (modified) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/sponsored_placement.json (modified) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/video_hosted.json (modified) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/video_vast.json (modified) matchesstatic/schemas/source/**) and the current GitHub review decision is 'unknown', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.
There was a problem hiding this comment.
Ladon verdict: Escalate to human review
Escalate — hard approval gate (gated schema-source paths) not satisfied, plus new-enum breaking-class governance gate.
This PR adds 3 new enums and modifies 5 canonical formats under static/schemas/source/**, which are under a deterministic hard approval gate (gated_paths: true) and review_decision is not APPROVED (unknown/none). Per decision-rule row 2, the outcome must be escalate until a human/CODEOWNERS approval is recorded. The repo's constitutional hard gate on new enum values (breaking-class ratification is a human act) independently requires the same posture.
Medium findings
- server/src/training-agent/ctv-experience-matrix.ts:236 —
ctv_activation_copy_slotscheck removed from the semantic validator, but docs/creative/ctv-experiences.mdx:132 still assert the validator enforces it. Schema↔docs coherence gap; align the docs to the SHOULD framing.
Escalation reasons
- Touches gated schema-source files under
static/schemas/source/**(3 new enums: activation-method.json, ctv-ad-experience.json, motion-level.json; 5 modified canonical formats) andreview_decisionis not APPROVED — human/CODEOWNERS review required before merge. - New enum values on the published protocol surface are a normative/breaking-class change; ratification is a human act per the constitution's decision-class hard gate — escalate unless review_decision is APPROVED.
The reviewer surfaced one fresh medium finding this run (docs↔validator drift), so the prior escalation legitimately persists (row 6) in addition to the standing gate.
Medium findings
- server/src/training-agent/ctv-experience-matrix.ts:236 — ctv_activation_copy_slots check removed but docs still claim the semantic validator enforces it
Why human review
- Touches gated schema-source files under static/schemas/source/** (3 new enums: activation-method.json, ctv-ad-experience.json, motion-level.json; 5 modified canonical formats) and review_decision is not APPROVED — hard approval gate requires human/CODEOWNERS review before merge.
- New enum values on the published protocol surface are a normative/breaking-class change; ratification is a human act per the constitution's decision-class hard gate — escalate unless review_decision is APPROVED.
- docs/creative/ctv-experiences.mdx:132 still claims the semantic validator enforces ctv_activation_copy_slots after the check was removed in ctv-experience-matrix.ts:236 — schema↔docs coherence gap to resolve before merge.
- This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/enums/activation-method.json (added) matches
static/schemas/source/**; static/schemas/source/enums/ctv-ad-experience.json (added) matchesstatic/schemas/source/**; static/schemas/source/enums/motion-level.json (added) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/image.json (modified) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/native_in_feed.json (modified) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/sponsored_placement.json (modified) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/video_hosted.json (modified) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/video_vast.json (modified) matchesstatic/schemas/source/**) and the current GitHub review decision is 'unknown', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.
There was a problem hiding this comment.
Ladon verdict: Escalate to human review
Escalate — hard approval gate on gated schema-source paths not yet satisfied.
This PR touches static/schemas/source/** (3 new enums: activation-method.json, ctv-ad-experience.json, motion-level.json; 5 modified canonical formats). gated_paths: true and review_decision: REVIEW_REQUIRED (not APPROVED), so Decision Row 2 forces escalate. Additionally, new enum values on the published protocol surface are a normative/breaking-class change whose ratification is a human act per the constitution's decision-class hard gate — escalate unless review_decision is APPROVED.
Iteration credit: the prior pass's sole finding (docs at ctv-experiences.mdx claiming the semantic validator enforced ctv_activation_copy_slots after that check was removed) has been resolved — the docs now correctly state the constraint is advisory until a warnings[] channel lands, matching the shipped validator and schema prose. No new findings surfaced this run. The diff is clean; the only reason for escalate is the unsatisfied hard gate, not code defects.
Escalation reasons
- Gated schema-source files touched (
static/schemas/source/enums/*.json+ 5 canonical formats understatic/schemas/source/formats/canonical/) andreview_decisionis REVIEW_REQUIRED, not APPROVED — human/CODEOWNERS approval required before merge. - New enum values on the published protocol surface are a Breaking/Normative-class change per the constitution's decision-class hard gate — ratification is a human act; escalate unless review_decision is APPROVED.
Why human review
- Gated paths: touches static/schemas/source/** (3 new enums + 5 modified canonical formats) and review_decision is REVIEW_REQUIRED, not APPROVED — human/CODEOWNERS approval required before merge.
- New enum values on the published protocol surface are a Breaking/Normative-class change per the constitution's decision-class hard gate — ratification is a human act; escalate unless review_decision is APPROVED.
- This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/enums/activation-method.json (added) matches
static/schemas/source/**; static/schemas/source/enums/ctv-ad-experience.json (added) matchesstatic/schemas/source/**; static/schemas/source/enums/motion-level.json (added) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/image.json (modified) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/native_in_feed.json (modified) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/sponsored_placement.json (modified) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/video_hosted.json (modified) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/video_vast.json (modified) matchesstatic/schemas/source/**) and the current GitHub review decision is 'REVIEW_REQUIRED', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.
pkras
left a comment
There was a problem hiding this comment.
Reviewing the implementation of RFC #6428. The plural-matrix design is the right call and largely lands well. One substantive issue (SIMID, inline below) and two design questions worth resolving before merge. Full context also on the RFC: #6428.
A. Buy-time vs. runtime experience selection. The matrix models ctv_ad_experience as a buyer-selected format option. That's correct for guaranteed/takeover deals (you buy the pause takeover deliberately), but for programmatic, CTV players (SpringServe, FreeWheel, Publica) resolve pause vs. screensaver vs. overlay from viewer state at serve time — a fixed per-option experience is a commitment the buyer can't honor. Worth stating whether ctv_ad_experience is a firm buy-time declaration (guaranteed) or an eligibility signal the player resolves at runtime (programmatic), ideally supporting both rather than defaulting to buy-time for all six.
B. Execution model for multi-canonical cells. For cells with two permitted canonicals (pause = image/video_vast, squeezeback = video_vast/sponsored_placement), validation is defined but execution isn't. Model A — seller publishes one format option per canonical it supports, consistent with get_products; buyer selection resolves it, no new wire surface — looks correct and composes with (A). Suggest confirming Model A explicitly so nobody builds a fallback_canonical field for a problem option-selection already solves.
Minor: for squeezeback/in_scene the two-canonical cell is really two delivery contracts — sponsored_placement = seller resolves a catalog reference at serve time; video_vast = seller receives a finished asset (however produced, including pre-baked catalog variants, invisible to AdCP). One doc line would help implementers pick on the resolution boundary rather than an "is there a catalog" test.
Coordination: this PR and #6279 both touch the warnings[]/violation channel and error-code.json; the activation-copy-slot check should downgrade from violation to warning once #6279's warnings channel lands. Flagging so whichever merges second reconciles it.
| }, | ||
| "ctv_ad_experience": { | ||
| "$ref": "/schemas/enums/ctv-ad-experience.json", | ||
| "description": "CTV experience this option serves. On video_vast only `pause`, `screensaver`, `overlay`, `squeezeback`, and `in_scene` are valid (`menu` routes to native_in_feed or sponsored_placement), and `creative_type` MUST be `nonlinear`. Per-experience floors: `overlay` and `squeezeback` require a 10s minimum duration; `in_scene` requires a 3s minimum brand-exposure duration and forbids interactivity (`vpaid_enabled`/`simid_supported` MUST NOT be true); `pause` has no duration floor and ends on viewer or device action. SIMID support is declared explicitly, never inferred, and NonLinear creatives MUST carry a non-interactive StaticResource or MediaFile fallback." |
There was a problem hiding this comment.
SIMID can't ride on nonlinear — this describes a creative VAST can't serialize. This mandates creative_type: nonlinear for all five experiences while the option still exposes simid_supported. But SIMID is carried only by <InteractiveCreativeFile>, which the VAST 4.2 XSD declares under <Linear><MediaFiles> (vast_4.2.xsd line 628); NonLinearAd_Inline_type has no such element. So nonlinear + simid_supported: true cannot be expressed in VAST, and the "NonLinear creatives MUST carry a non-interactive StaticResource/MediaFile fallback" rule describes a shape that can't exist (StaticResource is a NonLinear/companion construct, not a SIMID fallback).
The experiences are correctly nonlinear — the fix is to drop simid_supported from them, not relabel them linear. Interactivity on these surfaces (QR, deep link, push, tune-in) already flows through activation_method, which is the right home. (Nonlinear ads can still be interactive via HTMLResource + the nonlinear apiFramework attribute — this is specific to SIMID, which is Linear-bound.) The in_scene no-interactivity constraint below is correct and unaffected.
| @@ -3,7 +3,6 @@ | |||
| "$id": "/schemas/formats/canonical/video_vast.json", | |||
| "title": "Canonical Format: VAST Video", | |||
| "description": "VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).", | |||
There was a problem hiding this comment.
Wording: SIMID is a first-class VAST element, not a "VAST extension" — it's signaled as <InteractiveCreativeFile apiFramework="SIMID"> (VAST 4.1+). Suggest rewording "SIMID extensions for interactive video supported as VAST extensions." (VPAID rode on apiFramework on <MediaFile>, which is likely the source of the "extension" phrasing.)
| - **`overlay`, `squeezeback`** — minimum duration of 10 seconds (`duration_ms_exact` or the floor of `duration_ms_range` must be at least `10000`). | ||
| - **`in_scene`** — minimum 3-second brand-exposure duration, and **no interactivity**: `vpaid_enabled` and `simid_supported` must both be `false` (or absent) on the format option. | ||
| - **`pause`** — no duration floor. The unit ends on a viewer or device action (resume, timeout), not a fixed clock. | ||
| - **SIMID support is always explicit, never inferred.** A format option sets `simid_supported: true` deliberately; a nonlinear creative that supports SIMID still MUST carry a non-interactive `<StaticResource>` or `<MediaFile>` fallback so players without SIMID support still render something. |
There was a problem hiding this comment.
Same SIMID-on-nonlinear issue as the schema: a nonlinear creative can't carry SIMID at all (<InteractiveCreativeFile> is Linear-only in the VAST 4.2 XSD), so this fallback rule describes a creative that can't be serialized. Recommend dropping simid_supported from these experiences and routing interactivity through activation_method.
|
|
||
| ## VAST versioning | ||
|
|
||
| `vast_version` supports VAST 2.0 through 4.3. VAST 4.3 ships no XSD of its own — IAB's `vast_4.2.xsd` remains the authoritative schema for 4.3 documents, so a validator MUST NOT reject a tag declaring `version="4.3"` for want of a 4.3-specific schema. VAST 4.4 is deliberately absent from the enum: `vast_4.4.xsd` is still annotated "DRAFT for working group discussion," not a published specification, and AdCP adds it only once IAB publishes a final schema. The NonLinearAds pattern every non-`menu` experience relies on remains valid under 4.2 serialization — sellers don't need 4.3 or a future 4.4 to express `overlay`, `squeezeback`, `in_scene`, `pause`, or `screensaver` creative. |
There was a problem hiding this comment.
This reads as "a SIMID-bearing nonlinear creative serializes fine in 4.2," which isn't true — a plain <NonLinearAds> creative is valid in 4.2, but there's no <InteractiveCreativeFile> slot under NonLinearAds, so a SIMID-bearing one is not. If simid_supported is dropped from these experiences (see schema comment), this line is fine as-is for the non-interactive nonlinear pattern; just make sure it isn't read as endorsing SIMID+nonlinear.
Summary
Implements RFC #6428: the six IAB CTV Ad Portfolio experiences (menu, pause, screensaver, overlay, squeezeback, in_scene) become expressible in AdCP 3.2 through an experience × contract matrix on existing canonicals — no channel-named
ctv_*formats.The matrix is deliberately plural because published platform specs show sellers split on ingestion (red-team evidence: WG memo; no fetched platform ingests NonLinear VAST for these placements today — Disney's spec explicitly rejects VAST, Netflix pause is image-only, Fire TV/Samsung/Google TV menu heroes are video-mandatory):
menunative_in_feed(menu profile, video-capable),sponsored_placement(catalog tiles)pauseimage(+ copy),video_vast(nonlinear)screensaverimage,video_hosted,video_vast(nonlinear)overlaynative_in_feed(asset bundle),video_vast(nonlinear)squeezebackvideo_vast(nonlinear),sponsored_placementin_scenevideo_vast(nonlinear),sponsored_placementWhat's in it
ctv_ad_experience,motion_level(AdCOM attrs 21–23),activation_method(QR / deep link / push / email / tune-in / SMS — engagement events, never impressions)native_in_feedmenu profile: Native 1.2videoslot (vasttag),menu_placement(tile / headline_banner → plcmttype 1/3),focus_behavior(none / autoplay_muted / autoplay_sound)video_vast:creative_type(linear | nonlinear | either) supersedinglinear_required; experiences require nonlinear; per-experience floors from the IAB portfolio guidelines — overlay/squeezeback ≥ 10s, in_scene ≥ 3s + interactivity forbidden, pause unfloored; SIMID explicit-never-inferredserver/src/training-agent/ctv-experience-matrix.ts) enforcing every schemaverifier_constraintsentry, incl. matrix rejections, duration floors,linear_requiredcontradiction, focus/video pairingdocs/creative/ctv-experiences.mdxwith the OpenRTB/AdCOM signaling annex (plcmt 5–9, playbackmethod 8–11, motion attrs, Native plcmttype, VAST Extensions round-tripping). VAST 4.4 stays out of the version enum per the documented gate invast-version.json(IAB XSD still DRAFT); the nonlinear pattern is valid under 4.2 serializationctv-experience-validate-input.yaml— 6 positive matrix pairings, 7 graded negatives (menu+VAST, pause+native, 5s overlay, linear overlay, interactive in_scene, focus without video, full_motion on image)Test results
pause + image → pass)Notes for review
native_in_feedbecause the GatewayGo-class overlay contract (video + logo + imagery + copy + activation copy, explicitly not VAST) is the largest overlay seller's actual intake.warnings[]channel onvalidate_inputresults; whichever merges second should downgradectv_activation_copy_slotsto a warning.🤖 Generated with Claude Code