feat(creative): add stateful display and coordinated placement formats - #6279
feat(creative): add stateful display and coordinated placement formats#6279bokelley wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
Ladon verdict: Escalate to human review
Escalate — gated-path change requires human/CODEOWNERS approval.
This PR modifies and adds files under static/schemas/source/**, which is a deterministic hard approval gate (gated_paths: true). The current review_decision is REVIEW_REQUIRED, not APPROVED, so the gate is not satisfied. Per decision-table row 2, the outcome must be escalate.
No critical/high findings were reported. The reviewer confirms schema↔docs coherence holds across canonical-format-kind, product-format-declaration, creative-manifest/asset, _base, and the two docs pages; the minor changeset matches the additive/experimental surface; the new page_takeover oneOf carries a format_kind discriminator; and no dist artifacts were touched. One Medium robustness gap and one Low remain.
Medium findings
- server/src/training-agent/task-handlers.ts:2859 — New
format_options.forEachincollectCanonicalFormatAdvisoriesruns before theArray.isArrayguard (potential runtime error on non-array input in the get_products advisory path).
Escalation reasons
- Touches gated paths under
static/schemas/source/**(multiple modified files plus new canvas-constraint.json, creative-assets.json, multi_state_display.json, page_takeover.json) whilereview_decisionisREVIEW_REQUIRED. Human/CODEOWNERS approval is required before merge.
Medium findings
- server/src/training-agent/task-handlers.ts:2859 — New format_options.forEach in collectCanonicalFormatAdvisories runs before the Array.isArray guard
Why human review
- Touches gated paths under static/schemas/source/** (schema source of truth) while review_decision is REVIEW_REQUIRED — human/CODEOWNERS approval required before merge.
- Medium robustness gap: server/src/training-agent/task-handlers.ts:2859 — format_options.forEach runs before the Array.isArray guard on the get_products advisory path.
- This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/core/asset-group-vocabulary.json (modified) matches
static/schemas/source/**; static/schemas/source/core/assets/image-asset.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/canonical-format-kind.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/canvas-constraint.json (added) matchesstatic/schemas/source/**; static/schemas/source/core/creative-asset.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/creative-assets.json (added) matchesstatic/schemas/source/**; static/schemas/source/core/creative-manifest.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/format-shape-vocabulary.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/product-format-declaration.json (modified) matchesstatic/schemas/source/**; static/schemas/source/creative/list-creatives-response.json (modified) matchesstatic/schemas/source/**; static/schemas/source/enums/error-code.json (modified) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/_base.json (modified) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/multi_state_display.json (added) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/page_takeover.json (added) 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.
There was a problem hiding this comment.
Ladon verdict: Escalate to human review
Escalate — gated schema-source paths require human/CODEOWNERS approval.
This PR modifies and adds files under static/schemas/source/**, which are under a hard, non-overridable approval gate (gated_paths: true). The current review_decision is not APPROVED (unknown/none), so decision rule row 2 fires: the gate is not satisfied and Ladon cannot auto-approve regardless of how clean the diff is.
The developer resolved the single prior Medium finding: collectCanonicalFormatAdvisories now guards if (!Array.isArray(product.format_options)) continue; before the FORMAT_SHAPE_PROMOTED forEach (task-handlers.ts:486-487), with the format_ids guard split out, plus a direct regression test seeding a non-array format_options. That is a net improvement — no current blocking or medium findings remain.
The only remaining reason this PR is not approvable is the gated-paths approval gate. Once a human/CODEOWNERS approval is recorded (review_decision: APPROVED), a re-run with no findings would fall through to approve.
Escalation reasons
- Touches gated paths under
static/schemas/source/**(schema source of truth, including added/modified core, enum, and canonical format schemas) whilereview_decisionis not APPROVED — human/CODEOWNERS approval required before merge.
Why human review
- Touches gated paths under static/schemas/source/** (schema source of truth) while review_decision is not APPROVED — human/CODEOWNERS approval required before merge.
- This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/core/asset-group-vocabulary.json (modified) matches
static/schemas/source/**; static/schemas/source/core/assets/image-asset.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/canonical-format-kind.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/canvas-constraint.json (added) matchesstatic/schemas/source/**; static/schemas/source/core/creative-asset.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/creative-assets.json (added) matchesstatic/schemas/source/**; static/schemas/source/core/creative-manifest.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/format-shape-vocabulary.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/product-format-declaration.json (modified) matchesstatic/schemas/source/**; static/schemas/source/creative/list-creatives-response.json (modified) matchesstatic/schemas/source/**; static/schemas/source/enums/error-code.json (modified) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/_base.json (modified) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/multi_state_display.json (added) matchesstatic/schemas/source/**; static/schemas/source/formats/canonical/page_takeover.json (added) 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.
|
Ladon cannot review this PR until merge conflicts are resolved. |
|
Went through 1. Nothing stops two transitions fighting over the same state. Each transition is locked to one trigger, love that. But you could still declare two arrows out of 2. 3. That |
|
Fixed 2 and 3 in the follow-up commit. 1. Ambiguous exits — by design. The schema's contract is graph disclosure: declared states are valid, all endpoints resolve, every non-initial state is reachable. Runtime arbitration among competing exits is seller territory. Two timer arrows from 2. 3. Generated by Claude Code |
|
Kept going into 4. The 5. The inline format menu is a hardcoded list that'll drift. The allowed inline 6. Small heads-up, not a change request: nesting/custom is blocked structurally on the inline path (bad formats aren't in the menu), but on the 7. Cross-cutting device question. Canvases are breakpoint-scoped, but transitions, |
|
Fixed 4 in a follow-up commit. On 5, 6, and 7: 4. 5. Hardcoded inline format menu — the enumeration is intentional rather than derived: draft-07 has no mechanism to project a sublist dynamically from the canonical registry, and the inline path needs to explicitly gate which formats can appear inside a bundle (bundles can't embed bundles; custom formats can't be inline). The drift risk @pkras names is real. The right mitigation is a build-time check that flags when a new 6. Guardrail asymmetry — correctly diagnosed, no action needed. The schema can prove 7. Device behavior model — "one behavior graph, art just resizes" is the intended model for 3.2. Canvases are breakpoint-scoped so artwork adapts, but transitions, Generated by Claude Code |
|
@pkras heads up before you review: this PR was substantially reshaped in |
59c1362 to
1e5f711
Compare
…ext snapshot Merge daily-budget-caps and synthetic-depictions from main; combine the test:schemas chains (premium-display + synthetic-depiction + geo) and re-pin the standalone context-bytes snapshot for the merged tree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…acros), re-pin snapshot Combine the test:schemas chains and re-pin the standalone context-bytes snapshot for the merged tree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-pin snapshot Combine the test:schemas chains and re-pin the standalone context-bytes snapshot for the merged tree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
Adds the experimental AdCP 3.2 premium display canonicals:
seller_rendered_stateful_displayandcoordinated_placements. Closes #5972.Design note for reviewers: after the original snapshot-based design was red-teamed against ~35 published specs (IAB NAP/CTV primary docs, Kargo, Playwire, Adnami, GumGum, Teads, NYT, Guardian, Concert, Bloomberg, plus CTV platform specs), the canonicals were reshaped around an executable template contract. Full evidence and rationale: WG memo on #5972. The companion CTV RFC was rewritten accordingly: #6428.
What the contract is
The seller's format declaration — slots with limits, safe zones, breakpoints, and a disclosed state/transition graph — is the shared truth.
supply_modeselects who renders:components(default): buyer supplies logo/copy/imagery (withfocal_pointcrop hints)/video; seller assembles every state × breakpoint deterministically. Matches how NYT/Concert/Bloomberg-class sellers ingest today, but makes the template machine-readable so assembly needs no studio loop.rendered_canvases: buyer authors externally against the published contract and supplies the full canvas matrix; seller validates mechanically and brand-reviews.layered_source: transitional for sellers without executable templates (PSD/fonts in,production_window_business_daysapplies).Sellers offering the canonical MUST support deterministic
preview_creativeof every state × breakpoint — the proof round becomes an API round-trip.Coverage additions from the red team
width_range,width_mode: full_bleed | gutter_residual,viewport_height_percent,canvas_aspect_ratio(skins, topscrolls, adhesion, IAB flexible units)reveal: clip_window | scroll_parallax+underlayanchoring (interscrollers/understitials — no more fabricated second states)in_view_timer,media_event(video_start/complete), scrolldirection,hoverclickthrough: required | optional | none+ per-statestate_click_urls(single URL remains the default)coordinated_placements:sequence(sequential messaging), per-componentserving_policyEnforcement hardening (red-team fixes)
params.slotscannot widen — closes buyer-script smuggling (javascript/html/webhookslots) on any canonicaldismissibleor a close affordancedelay_ms >= 1000scroll_linkedpinned toscroll_progressin every transition branchplacement_refpublisher-domain normalization (no double-supply via spelling variants)warnings[]onvalidate_inputresultsTest coverage
server/tests/unit/training-agent.test.ts: 636 passing incl. 12 new adversarial cases (js smuggling, undismissable strobe, fluid-canvas supply mismatch, sequence gaps, domain-alias double-supply)verifier_constraintskey it exercises🤖 Generated with Claude Code