RFC #3305 implementation preview — v2 creative formats (full spec, not for merge)#3307
RFC #3305 implementation preview — v2 creative formats (full spec, not for merge)#3307bokelley wants to merge 32 commits into
Conversation
…_type, video.mdx fix First PR implementing the v2 creative formats RFC (#3305). Backwards-compatible additions only. - Add static/schemas/source/core/asset-group-vocabulary.json (canonical asset_group_id registry — 7 existing catalog vocab entries + 12 audit-driven additions, with landing_page_url canonicalizing 6 v1 alias names) - Add static/schemas/source/creative/scenes.json (typed scene-by-scene structure for build_creative input; renamed from "storyboard" to avoid collision with the testing-harness storyboard concept) - Add optional delivery_type discriminator to html-asset.json and javascript-asset.json via oneOf (inline branch matches v1 producers without delivery_type; url branch lets zip URLs and 3P tag URLs round-trip cleanly) - Fix docs/creative/channels/video.mdx VAST/VPAID format examples (asset_type "url"+asset_role "vast_url" → asset_type "vast"; VPAID uses asset_type "vast" with vpaid_enabled: true) Tracks #3305 (v2 RFC). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…register vocabulary in source index Two follow-ups from independent expert review of #3307: - audio.mdx:200 had the same bug pattern as video.mdx (caught by code-reviewer): the audio_30s_vast manifest example used asset_type "url" + url_type "tracker" for what should be a VAST audio tag. Corrected to asset_type "vast" with delivery_type "url"; renamed slot key from "vast_url" to "vast_tag" for clarity. - Register asset-group-vocabulary.json under core schemas and scenes.json under creative.build_inputs in static/schemas/source/index.json so the new schemas are discoverable via the public registry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI caught: when I changed asset_type from "url" to "vast" in video.mdx:410, the vast-asset-requirements.json schema started applying — it requires vast_version as a single string from the enum, not an array. Original doc had vast_version: ["3.0", "4.0", "4.1", "4.2"] which the looser "url" asset_type tolerated. Fix: vast_version: "4.2" (matches the manifest example at line 511). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New schemas (asset-group-vocabulary.json, scenes.json) and a new optional field (delivery_type on html/javascript) are additive features. Patch is reserved for bug fixes only. Aligns with the RFC's "3.1 preview track" framing — Phase 1 is the first PR toward 3.1.0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 1 design scrub. The delivery_type oneOf addition on html/javascript schemas wasn't earning its keep — URL-delivered HTML/JS already routes through url-asset.json with appropriate url_type. The real gap was a zip asset type for HTML5 banner bundles, which is genuinely missing from the registry today. - Revert delivery_type from html-asset.json and javascript-asset.json (back to inline-only, matching v1 behavior) - Add static/schemas/source/core/assets/zip-asset.json — new asset type for bundled HTML5 banners (url + max_file_size_kb + entry_point + allowed_inner_extensions + backup_image_url + sha256 digest) - Register zip in creative/asset-types/index.json - Add IndividualZipAsset / GroupZipAsset branches to format.json - Add zip-asset.json $ref to creative-manifest.json, creative-asset.json, creative/list-creatives-response.json, offering-asset-group.json - Clarify scenes.json description re: reference-asset.json purpose: "storyboard" (related but different concept — structured plan vs visual reference asset) - Rename changeset accordingly Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…eclaration, validate_input, build capabilities Foundation for the v2 RFC architecture (#3305). Backwards-compatible additions only — v1 named formats and v1 producers continue to work unchanged. **11 canonical format definitions** at static/schemas/source/formats/canonical/: - image (static), html5 (interactive bundle), display_tag (3P-served) - image_carousel (multi-card, polymorphic items) - video_hosted (direct file, OM-SDK + external trackers) - video_vast (VAST tag, inherent VAST event tracking) - audio_hosted (direct file) - audio_daast (DAAST tag) - sponsored_placement (retail-media catalog-driven, deterministic composition) - asset_pool_composed (Google PMax family, algorithmic composition) - brand_mention (text/audio AI-surface composition) - _base.json with shared fields (composition_model, provenance_required, platform_extensions, tracking_extensions) Each canonical bakes in its tracking model and references the asset_group_id vocabulary shipped in Phase 1. Format-keyed-by-name structure (no canonical discriminator field). **ProductFormatDeclaration** (static/schemas/source/core/product-format-declaration.json): keyed by canonical format name with minProperties: 1, maxProperties: 1, no additionalProperties — exactly one canonical key per declaration. Includes worked examples for Meta Reels, IAB MREC, podcast host-read. **Product/manifest additive fields:** - product.json: optional `format` field (v2 inline declaration), optional `build_capability_ref` (for products requiring agent-produced creative). v1 format_ids path remains supported. - creative-manifest.json: optional `brand` field (resolves brand.json) and `brand_kit_override` (explicit override for missing/stale brand.json). **Build capabilities:** - build-capability.json: schema for creative agents declaring what canonical formats they can build, with parameter narrowing and typed inputs. - build-capability-ref.json: reference type used on products. - get_adcp_capabilities response: added `creative.creative_build_capabilities` array. Replaces v1 list_creative_formats discovery surface for creative agents. **Platform extension references:** - platform-extension-ref.json: URI + content-digest reference to platform extension definitions. Bundled in get_products responses to avoid extra fetches; SDK caches by URI@digest. **validate_input tool:** - validate-input-request.json, validate-input-response.json, validate-input-result.json: cheap dry-run primitive for validating a manifest against canonical formats and/or specific products. predicted field carries pre-flight estimates; no protocol state for orphaned out-of-spec artifacts (nondeterministic platforms run their own QA loop). Tracks #3305 (v2 RFC). Phase 1 (#3307) primitives unblock this Phase 2 surface; together they let buyers and adopters point at one preview branch and build end-to-end against the v2 spec. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… MREC, podcast host-read) Adopter-facing documentation for the v2 RFC architecture (#3305) shipping in the #3307 preview branch. Walks through the canonical-formats-narrowed- by-products model with three concrete worked examples that illustrate the full surface: - Meta Reels narrowing video_hosted with platform extensions - IAB Medium Rectangle (300x250) narrowing image, plus a sibling NYTimes HTML5 banner narrowing html5 (different canonical for different tracking model) - The Daily 30s host-read narrowing audio_hosted with build_capability_ref pointing at the publisher's creative agent Includes worked validate_input flow, brand_kit_override usage, platform extension distribution explanation (URI+digest bundled in get_products), and explicit notes on what's NOT in v2 (brand safety frameworks, universal macros schema, destination_kinds schema, cta_vocabulary, list_build_ capabilities tool — all dropped per design scrub). Examples are marked test=false because they intentionally show only the v2-specific surface, not the full Product schema (which would clutter the illustrations with unrelated required fields like reporting_capabilities, delivery_type, etc.). Adopters can refer to actual reference fixtures for fully-valid product examples. Tracks #3305 (v2 RFC). Doc lives at /docs/creative/v2-overview alongside existing creative docs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… asset_group_id to format slots; expand vocabulary aliases Simplifications from the working-example walkthrough: 1. Drop build_capability and build_capability_ref schemas + product field. The input contract folds into the format declaration as a parameter. External creative agents are invisible to the buyer in the typical case — buyer-seller boundary is the only relationship the protocol models. 2. Add `inputs` field to canonical format _base.json. Tells the buyer what the format requires (script for host-read, creative_brief for generative, voice_id for TTS variation). When absent, format accepts only buyer-uploaded creative. 3. Rename creative.creative_build_capabilities → creative.supported_formats on get_adcp_capabilities response. Drops "build_" framing on discovery surface naming. Each entry uses ProductFormatDeclaration shape — one primitive, two homes (sales-side inline on products, creative-agent- side as supported_formats list). 4. Add `asset_group_id` field to format.json baseIndividualAsset and baseGroupAsset. Lets v1 reference creatives declare their canonical equivalents inline (e.g., slot click_url → asset_group_id landing_page_url). Strengthens v1↔v2 migration bridge. 5. Expand `aliases` arrays in asset-group-vocabulary.json with audit- grounded sets for headlines, descriptions, images_landscape, images_vertical, images_square, logo, video, audio. 6. Update v2-overview.mdx — host-read example uses inline inputs, two- flow explanation (buyer pre-produces via build_creative on a creative agent → sync_creatives, OR sync_creatives directly with inputs → seller produces internally). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… preview-as-verification section Diff comment review surfaced that the comparison table's "Generative formats" row was reasoning about a category that doesn't exist at the protocol level. Sellers take inputs (script, brief, voice_id) OR assets (image, video, audio uploads) per the format declaration. Whether the seller's internal production is generative AI, host recording, transcoding, or pixel-perfect asset rendering is invisible to the buyer. There is no "generative" axis to model. - Reframe line 20 as "Format input contract" — captures the v2 collapse without leaning on a generative special case - Add "Preview as the universal what-does-this-produce surface" section after validate_input — makes explicit that preview_creative shows output regardless of submission shape (asset-driven OR input-driven). Different sellers may produce differently internally; preview surface is uniform. - Tighten "nondeterministic generative platforms" wording to "nondeterministic synthesis" — same point, less reliance on the dropped category. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…responsive_creative; brand_mention → agent_placement; brand uses BrandRef
Diff comments surfaced two naming concerns:
1. asset_pool_composed → responsive_creative
"Responsive" is the industry-recognized term Google uses (RDA, RSA, PMax,
Demand Gen). Meta uses "Advantage+ creative" / older "Dynamic Creative"
for the same concept. The new name aligns with how ad-tech adopters
already think about this category. Files renamed:
formats/canonical/asset_pool_composed.json → responsive_creative.json
ProductFormatDeclaration key renamed; cross-references in _base.json and
sponsored_placement.json description updated.
2. brand_mention → agent_placement
"brand_mention" overloaded with affiliate / sponsored-podcast vocabulary
that predates AI surfaces by decades. Architectural property is "AI
surface composes a sponsored placement in its response" — distinct from
si_chat (user converses with brand-owned agent; existing SI track) and
from sponsored_placement (retail-media catalog-driven). agent_placement
parallels sponsored_placement structurally; both are surface-composed
placements differing by surface type.
formats/canonical/brand_mention.json → agent_placement.json
Description updated to make si_chat distinction explicit.
3. creative-manifest.json brand → BrandRef
Diff comment flagged that brand: { domain } should reference brand-ref.json
(the canonical BrandRef schema). Updated to use $ref instead of inlining.
BrandRef carries domain plus optional brand_id for house-of-brands plus
optional industries / data_subject_contestation overrides.
4. v2-overview.mdx
- Discovery row consolidated: list_creative_formats deprecated by
creative.supported_formats on get_adcp_capabilities (uniform
replacement regardless of agent role); sales agents additionally
expose get_products
- Brand identity row references BrandRef explicitly with house-of-brands
note
- Canonical format table updated with new names, industry-term
attributions, and si_chat distinction on agent_placement
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…son v1/v2 oneOf + fixture validation test Phase 3 deliverables for upstream implementor review: **Migration guide** at docs/creative/v2-migration.mdx (~1500 words): - Side-by-side v1 named format → v2 product format declaration - Slot name mapping table (v1 author-invented → canonical asset_group_id) - Generative format dissolution (~30 *_generated_* files collapse into format inputs) - Brand identity slots → BrandRef + brand_kit_override - Discovery surface migration (list_creative_formats → get_products + creative.supported_formats with server-side flatten wrapper guidance) - Adopter migration paths per role (sales agent / creative agent / buyer / publisher direct) **5 reference fixtures** at static/examples/products/v2/, fully-valid Product objects that pass strict schema validation: - meta_reels_us.json (video_hosted, vertical orientation, Meta-specific extensions) - nytimes_homepage_mrec.json (image, IAB MREC 300x250) - nytimes_homepage_html5.json (html5, sibling product on same placement — different canonical because different tracking model) - the_daily_30s_host_read.json (audio_hosted with inline inputs: script + brand + offering_ref; production_window_business_days: 7) - amazon_sponsored_products.json (sponsored_placement, catalog-driven, ASIN-keyed) **Schema fix on product.json**: format_ids was unconditionally required at the schema level, blocking v2 products. Restructured to oneOf: - v1 branch: requires format_ids (named-format reference path) - v2 branch: requires format (inline ProductFormatDeclaration path) A product is one or the other, not both. **New test:v2-fixtures script** at tests/v2-fixture-validation.test.cjs. Validates all fixtures in static/examples/products/v2/ against /schemas/core/product.json. Wired into package.json scripts. **Schema validation test update**: replaced static format_ids assertion on product.json with explicit oneOf(format_ids, format) check that matches the new shape. **Doc cross-link**: v2-overview.mdx Related section links to v2-migration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ng fixtures, vocabulary gaps, slot declarations Implementor review of #3307 surfaced concrete issues. All addressed: **Doc/schema mismatches (must-fix before adopters touch this):** - v2-overview.mdx: 'web' → 'display' on channels (web is not in the channels enum) - v2-overview.mdx: publisher_property_selector shape — replaced { type: "publisher", publisher_domain } with the schema-correct { publisher_domain, selection_type: "all" } across all four worked examples - audio_hosted.json description: removed stale build_capability_ref reference; reframed around inline inputs convention - product-format-declaration.json: host-read example description and data updated to use inline inputs (no build_capability_ref) **Missing fixtures (5/11 → 7/11 canonical coverage):** - google_performance_max.json — responsive_creative narrowing for Google PMax with full asset-pool min/max declarations and CPA pricing - chatgpt_brand_mention.json — agent_placement with text output, brand + offering_ref inputs, disclosure_required, advisory tone constraints **Vocabulary additions (filling gaps reviewer flagged):** - cta — call-to-action button label slot - price — product price slot for retail / shopping creative - disclaimer — legal disclaimer / fine print slot (regulated verticals) - phone_number — click-to-call slot - promo_code — coupon / offer code slot - subtitle_file — caption file URL slot - source_catalog — catalog reference slot for sponsored_placement - hero_asset — buyer-supplied hero/banner alongside catalog - Aliases added: cards (carousel_cards, slides, etc.), youtube_video_id (existing_yt_video_id, etc.) - Vocabulary header now explicitly defers input names — inputs use separate convention not yet canonicalized at spec level **Programmatic slot declarations:** - _base.json: new optional `slots` field — array of { asset_group_id, required, min, max } entries letting SDK codegen and validators enumerate format slots without parsing prose descriptions - responsive_creative: default slots array enumerates 9 canonical slots - agent_placement: default slots array (just landing_page_url since the format is composed-by-surface) **Other clarifications:** - _base.json synthesis_nondeterministic boolean — distinct axis from composition_model; covers Veo/Sora/Runway-class where predictive validate_input is impossible and the platform's own QA loop applies - agent_placement.tone_constraints — explicitly marked advisory (LLM/agentic surfaces have no protocol mechanism to enforce) - _base.json tracking_extensions description — clarified relationship to platform_extensions (subset for buyer convenience, not enforcement) Vocabulary version bumped 1.0.0 → 1.1.0; lastUpdated 2026-04-26 → 2026-04-28. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…xture; agent_placement landing_page_url moves to inputs; product_card carve-out documented
Continuing review-feedback work:
**Manifest `inputs` field:**
- creative-manifest.json gains optional `inputs` field for input-driven
submissions (script, creative_brief, voice_id, offering_ref,
landing_page_url, etc.). Buyers populate it for products whose format
declares an inputs contract; v1 consumers ignore it. Some formats
accept both — buyer can mix uploaded assets with inputs that drive
seller-side composition.
**agent_placement landing_page_url moves from slot to input:**
- agent_placement has no buyer-fillable creative slots by definition;
the format is composed-by-surface. Putting landing_page_url in the
manifest's assets map (slot semantic) muddled "rendered creative"
with "metadata the agent uses." It's now declared as an input on the
format, populated via the manifest's `inputs` field.
- agent_placement.json slots default is now empty; description added
explaining the inputs-only nature
- chatgpt_brand_mention.json fixture updated to declare landing_page_url
in inputs
**Bundled extensions schema + fixture:**
- get-products-response.json gains optional `extensions` field — keyed
by `<extension_uri>@<digest>` patternProperties matching SHA-256
digests; each value is { extends, fields, version, description }
- New fixture at static/examples/get_products_responses/v2/
meta_with_bundled_extensions.json — two Meta products (Reels + Feed
Image) sharing meta_pixel extension plus separate placements_reels /
placements_feed extensions, all bundled in the response under
uri@digest keys
- v2-fixture-validation test extended to cover get_products response
fixtures alongside Product fixtures
**product_card carve-out documented:**
- v2-migration.mdx adds a section explaining why product_card and
product_card_detailed stay on v1 format_id even on v2 products:
they're the UI rendering of the product itself, not the ad creative
the product accepts. Different purpose, different schema lifecycle.
v2-only clients can ignore product_card if they don't render product
UIs.
Validation: 8 fixtures pass (7 products + 1 get_products response with
bundled extensions). All schema/json-schema/v2-fixtures tests green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…r, type product_card inline
Three architectural simplifications from review feedback:
**1. Drop `inputs` as a separate concept.** Post build_capability collapse,
the inputs/assets distinction had lost its anchor — both maps were
"things the buyer ships." Adopters had to guess: is `script` an asset or
an input? Is `landing_page_url` rendered or contextual? The answer
varied by format and was invisible without reading prose.
The new model: format declares `slots`; manifest has one `assets` map.
Some assets are rendered verbatim (image, video); some are consumed
for production (script, creative_brief, scenes); the seller dispatches
per the format's slot declaration. Buyer mental model is uniform —
"here's what I'm shipping."
Concretely:
- _base.json: removed `inputs` field; kept production_window_business_days
- creative-manifest.json: removed `inputs` field
- audio_hosted.json description: rewritten around slot-based
submission ("buyer ships a script text asset to the script slot")
- agent_placement.json slots default: now declares
offering_ref + landing_page_url as text/url assets (no inputs map)
- asset-group-vocabulary.json: added canonical entries for `script`,
`creative_brief`, `scenes`, `voice_id`, `offering_ref`,
`style_reference`, `starter_assets`. Vocabulary header rewritten to
cover everything the buyer ships (rendered + consumed-for-production).
**2. format_kind discriminator on ProductFormatDeclaration.**
Was: keyed-union shape `{ format: { video_hosted: { ...params } } }`
generates awkward TS/Pydantic codegen (11 separate "is this image / html5
/ ... " probes per access).
Now: `{ format: { format_kind: "video_hosted", params: { ... } } }`
generates clean tagged unions. JSON Schema `discriminator` keyword
with oneOf branches. Each branch is `format_kind: const "<canonical>"`
+ `params: $ref to that canonical's schema`.
All 7 product fixtures + 1 get_products response fixture restructured
to the new shape. v2-overview.mdx + v2-migration.mdx examples
restructured.
**3. Typed product_card and product_card_detailed.**
Was: `format_id` + `manifest` indirection (referenced v1
product_card_standard format file).
Now: inline typed structure on Product:
- product_card: image + title + description + price_label + cta_label
- product_card_detailed: hero_image + carousel_images + title +
description + specifications array + price_label + cta_label
Drops the v1 format_id punt the reviewer flagged. product_card serves a
different purpose (UI rendering of the product itself, not the ad
creative the product accepts) — typing it inline avoids conflating
ad-creative formats with UI-display metadata.
Validation: 8 fixtures pass; all schema tests green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…elationship
Reviewer asked whether style_reference is redundant with brand.json.
Not redundant — different scope:
- brand.json: declarative brand-level style (hex colors, voice
description, logo, tagline) stable across campaigns
- style_reference: per-creative reference image ("make it look like
THIS") that ships in the manifest for image-to-image variation,
style transfer, hero-shot lighting reuse
For pure brand-style consistency, BrandRef → brand.json is sufficient
and style_reference isn't needed. style_reference is for the cases
where the buyer wants to convey style by example rather than by
declarative attribute.
Industry parallels: MidJourney --sref, Adobe Firefly structure/style
reference, Runway/Pika style image inputs. Without canonicalization
each platform invents its own slot name (reference_image, style_image,
inspiration_image, structure_reference).
Tightened the canonical entry's description to make the brand.json
relationship explicit; added aliases for the common platform-invented
slot names.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ondeterministic + provenance examples Address review feedback on the inputs→slots collapse: **Doc prose mismatches** (adopters reading docs got the wrong model): - v2-overview.mdx line 20 architectural-shift table: "inputs concept" → "slots array enumerating everything the buyer ships" - v2-overview.mdx host-read prose / Flow 1 / Flow 2 / preview section: rewritten around the slot/asset model. No more "input-driven" framing. - v2-overview.mdx "What's NOT in v2" build_capability bullet: collapsed into the canonical slot/asset model, not into a non-existent inputs map. - v2-migration.mdx *_generated_* collapse bullet, sales agent bullet, creative agent migration step 1, buyer migration step 4: all reframed around slots. - v2-migration.mdx product_card section: rewritten — product_card is typed inline now, not a v1 punt. Old framing was inconsistent with the typed-inline change in commit 53f8dbe. **Schema description mismatches** (codegen tools read these): - product.json `format` field description: removed "keyed by canonical format name" and "optional inputs"; describes the format_kind + params discriminator shape and the slots-on-format model. - creative-manifest.json `assets` description: covers both v1 (asset_id-keyed) and v2 (asset_group_id-keyed) paths with the current canonical example slot names; added zip to the asset_type list. **Slot schema typing** — _base.json's slots schema now defines asset_type (enum of 16 asset types), max_chars, max_size_kb, and description as first-class fields instead of slipping through additionalProperties: true. Codegen now sees asset_type info per slot. Existing fixtures + canonical defaults updated to include asset_type on every slot entry. **Vocabulary gap**: added long_headlines canonical entry. responsive_ creative.json's default slots referenced it but it wasn't in the registry — soft-warning case the registry was meant to catch caught the canonical itself. Now consistent. **sponsored_placement default slots** — added (was the only canonical without a default; reviewer flagged the asymmetry). Default slots: source_catalog (catalog asset, required), hero_asset (image, optional), landing_page_url (url, optional). **Canonical-policy doc** — added x-canonical-policy-required-params-not- enforced annotation on _base.json explaining the intentional choice: canonicals are loose contracts; products narrow them; required-param enforcement happens at the product level, not the canonical level. **4 missing canonical fixtures added** (5/11 → 11/11 canonical coverage): - gam_3p_display_tag.json (display_tag canonical) - meta_carousel.json (image_carousel canonical with polymorphic items) - youtube_vast_preroll.json (video_vast canonical with VPAID-disabled skippable pre-roll) - triton_daast_audio_30s.json (audio_daast canonical) **Veo fixture** (veo_generative_video_15s.json) exercises both synthesis_nondeterministic: true and provenance_required: true with a representative scenes-driven generative video shape. Closes the "no fixture demonstrates these flags" gap. **validate_input example slot key** — changed video_main → video to match v2 canonical vocabulary. **Phase status table** — Phase 3 now shows what actually shipped (the migration guide, fixtures, fixture-validation test); Phase 4 is the SDK codegen / flatten wrapper work. Validation: 13 fixtures pass (12 products + 1 get_products response with bundled extensions). All schema/json-schema tests green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s agents Reviewer flagged three implementation-side hooks v2 introduces that existing server implementations (e.g., the salesagent reference impl) don't have today. Added a "Server-side implementation considerations" subsection to the v2-migration.mdx sales-agent migration path: - sync_creatives provenance verification when format.params.provenance_ required: true. Natural extension of existing AI-provenance tracking (EU AI Act Article 50); the new piece is a validation hook that gates submission of unsigned synthesized assets. - get_products response gathers extension definitions when products carry platform_extensions. Trivial when no v2 declarations; only kicks in for opt-in tenants. - production_window_business_days on host-read / agent-produced products. Most server impls don't model production turnaround today; v2 makes it declarable. Pure docs change; no schema impact. 13 fixtures still pass strict validation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cab-scenes-delivery # Conflicts: # static/schemas/source/creative/list-creatives-response.json
…nical status, hosting paragraph, third-party creative-agent worked example Schema: - Rename product.format → product.format_options (array of ProductFormatDeclaration). Restores v1 format_ids cardinality on the v2 path. The 90% case is single-element; multi-element declares "accepts any of" (e.g., Flashtalking-served html5 OR internal display_tag; hosted video OR VAST tag). Mutually exclusive with format_ids. - Add status: stable | preview | deprecated to canonical _base.json. Default stable. Mark agent_placement and responsive_creative as preview in 3.1, with a note that schemas may break in 3.2 once 2-3 adopters land. Other 9 canonicals stay stable (anchored in IAB / platform standards). Docs: - New worked example in v2-overview.mdx: third-party creative agent path (Flashtalking + NYTimes display). Multi-actor walkthrough alongside the existing single-actor host-read. Documents that the seller validates against the canonical, not against the creative agent's narrowing — that's the creative agent's contract with the buyer. - Platform extension hosting paragraph added to v2-overview.mdx: publisher subdomain hosts canonical artifact; immutable caching enabled by digest pinning; ≥99.9% / 30-day availability target; 404 degrades gracefully (extension unavailable, don't fail the buy); AAO mirror is best-effort fallback. - Adoption-driven format_ids removal trigger documented in v2-migration.mdx: AAO computes format_options adoption ratio from cached get_products responses; 5.0 cut sequence opens when the ratio crosses 80% for 30 consecutive days. Replaces calendar trigger. Schema housekeeping: - validate-input-response.json description documents the intent behind the 3-schema split (Result reused by planned async-validation surfaces — build_creative async paths, sync_creatives async validation). - 12 v2 product fixtures + 1 get_products response fixture migrated to format_options array. All 13 still validate via npm run test:v2-fixtures. - tests/schema-validation.test.cjs core-required-fields rule updated to assert format_options on the v2 oneOf branch. Tracks #3305 (RFC) and #3307 (preview branch). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…y-creative-agent worked example The Flow 1 worked example I added in 19e6a30 fabricated a `creative_agents` field on the sales agent's `get_adcp_capabilities.creative` block. That field does not exist in v2: - `creative_agents[]` is a v1 field on `list_creative_formats` (recursive- discovery hint, not a list of "approved creative agents"). It's part of the deprecated v1 surface. - `creative.supported_formats` lives on the *creative agent's* capabilities response, declaring what that agent can produce. It's not a sales-agent- side list. - The v2 sales agent's authoritative declaration of accepted formats is the product catalog (`format_options` on each product). - Buyers choose creative agents independently — through brand-side relationships, AAO registry, or direct knowledge. Rewrites the worked example accordingly: buyer reads NYTimes products, picks Flashtalking out-of-band, calls Flashtalking's build_creative, ships the manifest to NYTimes. NYTimes validates against the canonical its product narrows; it knows nothing about Flashtalking and maintains no list of approved creative agents. Drops the bogus auto-projection prose ("SDK derives supported_formats by fetching each creative_agents[].agent_url and unioning") — that projection has no basis in the v2 schema. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ce / item_production_model) for generative-DSP and multi-output patterns Closes the asymmetry where audio_hosted handled "who renders" via audio_source but image and video_hosted had no analogous parameter. That left generative-DSP-shaped adopters (universalads, Pencil, AdCreative.ai-shaped tools, GenStudio-shaped tools) without a clean expression — they had to fudge composition_model or invent platform extensions for what's actually a common pattern. Added: - image_source on image canonical: buyer_uploaded | seller_pre_rendered_from_brief | seller_human_designed | agent_synthesized (default buyer_uploaded). Plus buyer_image_acceptance: accepted | rejected. - video_source on video_hosted canonical: same enum and pattern mirroring image_source. Plus buyer_video_acceptance. - item_production_model on sponsored_placement: same enum applied per catalog item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives — universalads_generated_offerings shape) under sponsored_placement without requiring a 12th canonical. These are informational, not the binding contract. The format's slots declaration is what binds; *_source describes how the product produces the rendered creative so buyers can pick products whose production model fits their workflow. v2-overview.mdx now explicitly differentiates the two orthogonal axes: - composition_model — how the surface composes per-impression (deterministic vs algorithmic per-impression). - production source — who renders, and when (per-canonical *_source parameters). Conflating them was the gap. A generative DSP that produces ONE rendered image from a brief is composition_model:deterministic + image_source:seller_pre_rendered_from_brief — not a new composition pattern, just a different production source. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… cards, audio_source widening, oneOf tightening, slots inline, status pathway, digest collision
Closes red-team Must-Fix items from the protocol-expert pass:
- M1: Manifest v2 path. creative-manifest.json and creative-asset.json now carry
oneOf(format_id v1 path | format_kind v2 path) with explicit not on each
branch. Adds /schemas/core/canonical-format-kind.json enum to back the v2 path.
Adds optional capability_id field to disambiguate when a product's
format_options carries multiple declarations sharing the same format_kind.
Without this, v2 products had no v2 manifest counterpart — every SDK author
would invent a different bridge.
- M2: format_options routing. ProductFormatDeclaration grows capability_id
(stable identifier for routing) and applies_to_channels (subset of the
product's channels this declaration applies to — covers S15 too). Lets a
multi-channel product carry channel-specific format_options.
- M3: Veo fixture used audio_source / buyer_audio_acceptance on a video_hosted
format. Renamed to video_source / buyer_video_acceptance.
- M4: audio_source enum was narrower than image_source / video_source.
Widened to match (added seller_pre_rendered_from_brief and
seller_human_designed). TTS-from-brief and studio-produced audio now
expressible.
- M5: product.json oneOf branches got explicit not: required: [other] so a
payload carrying both format_ids AND format_options fails closed under any
validator.
- M6: get-adcp-capabilities-response.json supported_formats descriptions
referenced the dropped 'inputs' concept (collapsed into slots in r4).
Replaced with format_kind + params + slots framing.
- M7: image_carousel slot model. Added a default slots declaration with cards
slot (asset_type: object, min/max bounds), plus a normative card_shape
parameter documenting the per-card object structure (media + headline +
landing_page_url). assets.cards is now the unambiguous array-under-one-key
contract; per-card key conventions (card_0_headline, cards.0.headline) are
forbidden.
- N: Slots inline default added to all 11 canonicals (previously only on 3).
SDK codegen now produces typed slot lists for every canonical.
- N: Synthesis_nondeterministic compatibility table added to _base.json
description. seller_pre_rendered_from_brief / seller_human_designed /
agent_synthesized may pair with synthesis_nondeterministic: true.
buyer_uploaded and publisher_host_recorded MUST NOT.
- N: platform-extension-ref digest collision behavior documented.
Within a single response, divergent digests for the same uri MUST fail
closed. Across responses, divergence is normal (extension version updates).
- S16: status:preview deprecation pathway. _base.json status field gets
since_version + migration_target_version siblings, plus a stabilization
rubric ("preview → stable when 2 adopters ship + 90 days no breaking
change"). Adopters get a schema-level signal of where each canonical is
in the lifecycle.
Test rule updated for the new oneOf shape on creative-asset.json.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…hosting reframe, worked examples, OpenRTB differential, decision rules, cross-doc banners Closes red-team Should-Fix and Nit items from the docs-expert and adtech-product-expert passes: - v2-overview.mdx: 25-term glossary at the top of the doc; asset_group_id vocabulary table (was only in JSON); refined "Two axes" section to show the unified 5-value production-source enum; tracker assembly under seller-rendered sources documented (macro-substituted vs sync-creatives tracker block); "Channels not yet canonicalized" section (native, linear TV, OOH, DAI, in-game, live). - v2-overview.mdx worked examples: generative DSP (universalads-class, image_source: seller_pre_rendered_from_brief), multi-format product (Flashtalking html5 OR internal display_tag), sponsored_placement with item_production_model (1 brief × N items → N creatives). Closes the gap where the new schema fields had no concrete worked-example coverage. - v2-overview.mdx hosting reframe: two normative paths. Open-ecosystem (publisher hosts the canonical artifact, immutable digest-pinned caching) vs closed-platform (AAO mirror translates walled-garden format docs into AdCP extension artifacts and hosts them under mirror.adcontextprotocol.org). Walled gardens were previously framed as a parenthetical fallback; reality is they ARE the AAO-mirror primary path. - v2-overview.mdx validate_input: "when to use" decision rule (pre-flight, multi-target dry-run, debug rejection) plus comparison table with build_creative and sync_creatives. Closes the gap where the doc showed an example without explaining when the primitive fits. Cross-link to /docs/creative/task-reference/build_creative. - v2-overview.mdx scaling: client-side filtering + multi-target validate_input as the high-product-count operational pattern. Addresses the per-product validate_input scaling concern. - v2-overview.mdx narrative tuning: generative-DSP fields (synthesis_nondeterministic, item_production_model: agent_synthesized) demoted to a forward-looking subsection. Universalads/Pencil are real adopters but small share of 2026 spend; the schema breadth must not read as AI-first. - v2-overview.mdx creative-agent business model: clarifies v2 disaggregation is conceptual — creative agents continue to host produced asset bytes and instrument tracking via platform extensions. - v2-overview.mdx preview canonicals stabilization rubric and Phase 4 SDK codegen blocker callout in the status banner. - v2-migration.mdx: v1 deprecation calendar floor (2027-Q4) and ceiling (2029-Q1) bounding the 80%/30-day adoption trigger; adoption-trigger metric defined with denominator + numerator + AAO publishing surface; creative_id stability invariant across v1 ↔ v2; "What v2 gives you that OpenRTB doesn't" subsection (canonical-as-contract decoupling, runtime discovery, declared production source, canonical tracking model). - v2-migration.mdx fixture count reconciled (12 product fixtures + 1 response fixture, all 11 canonicals covered). - Cross-doc v2 preview banners on formats.mdx, key-concepts.mdx, generative-creative.mdx, specification.mdx, implementing-creative-agents.mdx, asset-types.mdx so readers landing from search have a signpost back to v2. - asset-types.mdx updated for v2 with asset_group_id framing, full v2 asset_type table including brief, catalog, zip, markdown, webhook, object types. Validation: schema tests, example tests, v2 fixture tests all green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The migration doc was sales-agent-centric — creative agents (Flashtalking, AudioStack, Pencil, AdCreative.ai-shaped tools) got 3 short bullets. v2 reshapes their path enough to warrant a real walkthrough. Adds: - A what-changes table covering format catalog publishing, authoring, discovery, build_creative contract, production-source declaration, tracking integration, and hosting of produced bytes. - Concrete worked example for an ad-server-shaped creative agent (Flashtalking) showing how 30+ named formats collapse to a smaller supported_formats set keyed by canonical format_kind + capability_id + Flashtalking-specific platform_extensions for pixel IDs and viewability. - Concrete worked example for a transformation-shaped creative agent (AudioStack) showing two distinct capabilities (brief-to-audio vs script-to-audio) declared as separate supported_formats entries sharing format_kind: audio_hosted but with different audio_source values (seller_pre_rendered_from_brief vs agent_synthesized). capability_id disambiguates which capability the buyer is invoking on build_creative. - Server-side hooks specific to creative agents: supported_formats as public contract, synthesis_nondeterministic implying a QA-loop obligation, provenance_required requiring C2PA attestation. - Migration timing table: keep v1 list_creative_formats through 4.x; add supported_formats anytime in 3.1+ (additive); stop publishing new v1 named formats when 80% of your buyers read supported_formats; drop list_creative_formats coordinated with the v1 deprecation calendar (2027-Q4 / 2029-Q1). Closes the gap surfaced by review of the third-party-creative-agent worked example in v2-overview — that example showed the BUYER flow but didn't tell creative agents what THEY do to migrate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cab-scenes-delivery # Conflicts: # static/schemas/source/core/creative-asset.json # static/schemas/source/core/creative-manifest.json # static/schemas/source/creative/list-creatives-response.json
|
Issue #3639 proposes aligning the buyer-side production-source enum default across all four canonicals ( Generated by Claude Code |
|
Issue #3638 proposes adding an optional Generated by Claude Code |
|
Ok so two things first:
We should pick one and rename and do that before 3.1 so we don’t have to go through full deprecation cycle. I would go with buyer_uploaded due to the fact that it’s just one rename vs three renames elsewhere. But i do admmting that buyer_pre_rendered is a better description, but this is a philosophical personal nit. So yes, this one first - i think this consistency could be later on nicely updated with my proposal on the POV below.
how about: adding a runtime_status with:
preview means the runtime exists but is imperfect. A buyer who proceeds might get 80% of what they expected. declared_only means the runtime doesn't exist at all for this path. A buyer who proceeds will hit a wall at sync_creatives so the seller's adapter simply doesn't handle it. Buyers = trust signal
Sellers = honesty mechanism
My overall POV: v2 canonical format catalog doesn't account for proprietary publisher format names The 11 canonical format types (image, video_hosted, html5, etc.) are useful machine-readable categories for buyer agents to reason about. But real sellers don't organize their inventory this way. Premium publishers and ad networks sell proprietary named formats, high-impact units with their own branding, specs, and pricing. These don't map cleanly to any canonical. The spec has platform_extensions for seller-specific extras, but that assumes you're starting from a canonical baseline. There's no good answer for a format that is genuinely novel. Potential adoption risk: if the first question every premium publisher asks during v2 onboarding is "where does my flagship format go?" and the best answer is "extend html5 I guess," the canonical model loses credibility before it gains traction. Two additions that would close this without complicating the core model:
|
…m_production_model.buyer_uploaded Closes #3638 (runtime_status) and #3639 (production-source enum asymmetry) — both filed against #3307. #3639: rename item_production_model.buyer_pre_rendered → buyer_uploaded on sponsored_placement. Aligns the buyer-side default value name with image_source / video_source / audio_source so SDK codegen emits a single shared 4-value enum across all production-source fields. "Uploaded" reads slightly off for catalog-keyed items where the buyer didn't actively upload bytes (the catalog already supplied them), but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced. Description updated with the framing nit called out explicitly so readers don't trip on it. #3638: add runtime_status: stable | preview | declared_only on ProductFormatDeclaration. Distinct from canonical-level status (which describes spec-maturity). runtime_status describes whether THIS seller's runtime actually honors what they declared on THIS product. Closes the gap surfaced by the universalads _generated_offerings audit (scope3data/agentic-adapters#202): the adapter's catalog declares a generative-offerings format that would map to sponsored_placement with item_production_model: seller_pre_rendered_from_brief, but the runtime is media-upload-only. With runtime_status: declared_only, the adapter can be honest about the forward-looking declaration vs the current runtime; buyers filter on it; compliance storyboards skip-gate gracefully. The two stability axes are documented as independent in v2-overview: - canonical params.status: spec maturity (stable/preview/deprecated) - declaration runtime_status: adopter runtime (stable/preview/ declared_only) A stable canonical can have declared_only adopters (common during v2 migration when adopters port v1 catalog declarations forward faster than they wire the runtime path); a preview canonical can have stable adopters (built against the preview shape and runtime honors it). Migration walkthrough updated: sales-agent migration step 3 calls out runtime_status as a first-class deliverable, not an afterthought. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…chema URI+digest Reverses the wrong call from #3666 (which recommended ext-as-vehicle for novel shapes). ext-only puts interesting structure in a free-form bag with no schema, no required fields, no defined semantics — buyer agents can see the blob but can't interpret it reliably, regressing to human-in-the-loop. That breaks the load-bearing claim of v2: buyer agents reason structurally without per-seller integration code. Adds: - canonical-format-kind.json: 'custom' added to the enum (12 values). Description documents that custom requires format_shape + format_schema and points at the promotion queue (#3666). - New /schemas/core/format-shape-vocabulary.json registry. Same pattern as asset-group-vocabulary.json: governance-light entries, non-canonical values valid (soft-warn), promotion to canonical happens when 2+ adopters land + 90 days. Seeded with 9 entries: multi_placement_takeover, roadblock, branded_content, cross_screen_sponsorship, sponsorship_lockup, newsletter_sponsorship, ar_lens, playable, live_event_sponsorship. Each entry carries description, typical_use, tracking_model_hint, promotion_status. - product-format-declaration.json: format_shape (string, references registry) and format_schema (URI+digest, $ref to platform-extension-ref.json) fields. allOf / if/then enforces: when format_kind=custom, format_shape AND format_schema are required; when format_kind=anything-else, both MUST be absent. New 'Custom Format Declaration' branch in the discriminator oneOf. Worked example added (NYTimes Homepage Takeover narrowing multi_placement_takeover with format_schema URI+digest). Buyer agents fetch the schema by uri@digest (immutable per digest, aggressive caching, same mechanic as platform_extensions), validate params and slots against the fetched schema, reason about manifests structurally. No per-seller integration code. ext stays for genuinely experimental shapes that don't even fit a format_shape registry entry — but that's the rare case. The dominant path for novel shapes is custom + format_shape + format_schema. Doc additions: - v2-overview.mdx: 'Custom formats' section between canonicals and asset-group-vocabulary. Explains the mechanism, the three required pieces when format_kind=custom, why custom + format_schema beats ext for agentic-first protocols, the promotion path to canonical. Glossary updated with three new entries. - v2-migration.mdx: 'Shipping a custom format' subsection on sales-agent server-side considerations. Three steps: pick format_shape from registry (or PR a new entry), author a JSON Schema describing your params/slots, host at a stable URI with immutable caching. Open-ecosystem publishers host on their own subdomain; walled-garden sellers route through AAO mirror. Validation: all schema/example/v2-fixture tests green. The worked-example fixture in the schema validates against the schema itself (proves the discriminator + allOf if/then constraints work as intended). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nastassiafulconis
left a comment
There was a problem hiding this comment.
arch is solid — canonical-as-contract is the right call. inline below is the stuff i'd want fixed before 3.1 ga.
one bigger thing: the 3 surface-composed canonicals (sponsored_placement / responsive_creative / agent_placement) capture the creative envelope but miss the actual product. amazon_sponsored_products has no negatives / match types / bid mods, pmax collapses 3 surfaces into one. would treat those as rfc-grade until 2 retail-media nets ship format_schema artifacts against them. rest over slack 🙌
| "properties": { | ||
| "slots": { | ||
| "default": [ | ||
| { "asset_group_id": "cards", "asset_type": "object", "required": true, "min": 2, "max": 10 }, |
There was a problem hiding this comment.
cards is asset_type: "object" but asset-union doesn't have an object branch — pretty sure a real carousel manifest won't actually validate. plus card_shape (line 16) has no $id so there's nothing to $ref against. extract card_shape into core/assets/card-asset.json + add card to asset-union?
There was a problem hiding this comment.
Fixed in fbb207b — extracted card_shape into /schemas/core/assets/card-asset.json (asset_type: "card" with required media, optional headline, landing_page_url, platform_extensions, provenance), added card to asset-union.json and to the creative/asset-types/index.json registry, added card to the canonical _base.json slot asset_type enum, and changed image_carousel's cards slot from asset_type: "object" → "card".
Surfaced the structural problem you flagged was bigger than just cards: the manifest's assets patternProperties expected a single asset value per slot, which also broke responsive_creative multi-value slots (headlines, descriptions, images_landscape with min/max counts). Relaxed the patternProperties to oneOf(single asset | array of assets) on creative-manifest.json, creative-asset.json, and list-creatives-response.json — unblocks both carousel cards AND multi-value slots. Verified ad-hoc that a real carousel manifest now validates; previously it didn't.
| "title": "Validate Input Result", | ||
| "description": "Per-target result of a validate_input call.", | ||
| "type": "object", | ||
| "required": ["target", "ok"], |
There was a problem hiding this comment.
buyers can't tell deterministic-fail from "platform is nondeterministic, can't pre-validate". synthesis_failed is in description prose only (response.json:5) — not in any enum or as a discriminator here. could we add result_kind: "validated_pass" | "validated_fail" | "unvalidatable_nondeterministic" so callers can branch?
There was a problem hiding this comment.
Fixed in fbb207b — replaced boolean ok with result_kind: validated_pass | validated_fail | unvalidatable_nondeterministic discriminator. Buyers now branch on three meaningfully different outcomes instead of conflating "failed validation" with "platform is nondeterministic, can't pre-validate." allOf if/then enforces violations[] absent for the two non-failure result_kinds (validated_pass and unvalidatable_nondeterministic).
No consumers yet (v2 not shipped) so took the breaking shape — ok removed cleanly rather than additive. The validate-input-response description was updated to reference the three values. Negative-fixture regression test added to tests/v2-negative-fixtures.test.cjs covering: validated_pass + violations rejected, unvalidatable_nondeterministic + violations rejected, old-shape ok boolean payloads rejected.
| "description": "Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical's parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, tracking_extensions, etc.). Optional `capability_id` (stable identifier for routing when a product's `format_options` contains multiple declarations sharing the same `format_kind`) and `applies_to_channels` (subset of the product's declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1's named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Custom format_kind** (`format_kind: \"custom\"`): for adopter-defined shapes that don't fit the 11 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.", | ||
| "type": "object", | ||
| "required": ["format_kind", "params"], | ||
| "discriminator": { "propertyName": "format_kind" }, |
There was a problem hiding this comment.
composing top-level discriminator + the allOf if/then/else (35-51) + the 12-branch oneOf feels risky. what happens if someone ships format_kind: "image" with a stray format_schema — does the discriminator forward to the image branch before the else fires? worth a fixture, or just drop the top-level discriminator (the oneOf consts already discriminate).
There was a problem hiding this comment.
Tested in fbb207b — wrote tests/v2-negative-fixtures.test.cjs with 7 cases on product-format-declaration. All compose correctly under AJV with discriminator+strict:false:
- format_kind:"image" + stray format_schema → rejected ✓
- format_kind:"image" + stray format_shape → rejected ✓
- format_kind:"custom" missing format_shape → rejected ✓
- format_kind:"custom" missing format_schema → rejected ✓
- bogus format_kind value → rejected ✓
- format_kind:"custom" complete (positive control) → passes ✓
- format_kind:"image" clean (positive control) → passes ✓
The composition works. The else clause in the allOf if/then/else fires regardless of how the discriminator routes — discriminator is a hint for error-message attribution, not a validation gate. Wired up as npm run test:v2-negative so future schema changes that break this composition surface as regressions.
| "min_height": { "type": "integer", "minimum": 1 }, | ||
| "max_width": { "type": "integer", "minimum": 1 }, | ||
| "max_height": { "type": "integer", "minimum": 1 }, | ||
| "duration_ms_range": { |
There was a problem hiding this comment.
both duration fields are optional w/ no oneOf — what's precedence if a product ships both? same gap in audio_hosted, audio_daast, video_vast. either oneOf them or doc the rule.
There was a problem hiding this comment.
Fixed in fbb207b — documented exact-wins precedence in description prose on all four canonicals (video_hosted, audio_hosted, video_vast, audio_daast). duration_ms_exact takes precedence when both ship; SDKs SHOULD lint a warning when both fields appear; producers SHOULD pick one. Schema stays permissive (no oneOf) for back-compat with any product currently emitting both — option (b) from the punch-list discussion.
| } | ||
| ``` | ||
|
|
||
| Buyer's manifest: `{ "format_kind": "html5", "capability_id": "html5_flashtalking_hosted", "assets": { "html5_bundle": {...}, "backup_image": {...} } }` — `format_kind` selects the canonical's slot vocabulary; `capability_id` disambiguates because both options share `format_kind: html5`/`display_tag`-shaped slots are different anyway, but `capability_id` removes any ambiguity for the seller's router. (When `format_options` carries one element per `format_kind`, `capability_id` is optional — `format_kind` alone routes the manifest.) |
There was a problem hiding this comment.
this sentence broke somewhere — reads like two paragraphs got merged. the rule for when capability_id is required is the load-bearing thing here and it's buried in malformed prose, needs a rewrite.
There was a problem hiding this comment.
Fixed in fbb207b — rewrote the broken paragraph as a normative routing rule with a clean code block:
format_kindselects the canonical and its slot vocabularycapability_idis REQUIRED on the manifest when the target product'sformat_optionscontains 2+ declarations sharing the sameformat_kind(without it the seller can't disambiguate)capability_idis OPTIONAL when eachformat_kindinformat_optionsis unique —format_kindalone routes the manifest. Buyers MAY still sendcapability_idas a clarity hint.
The example annotates which case it is (each option here has a distinct format_kind, so capability_id is optional but recommended habit).
| "rule": "duration_ms_range", | ||
| "expected": "3000-90000", | ||
| "predicted": 30000, | ||
| "field": "assets.video_main.duration_ms" |
There was a problem hiding this comment.
few things off:
- manifest ships
assets.video(615) but violation saysassets.video_main.duration_ms expected: "3000-90000"+predicted: 30000— 30000 is inside the range, claims a fail that mathematically passes- manifest uses v1
format_id: {agent_url, id}(613) inside what should be a v2 example
adopters will copy this verbatim 😬
There was a problem hiding this comment.
Fixed in fbb207b — all three:
- assets.video → assets.video_main (matches the violation field path)
- predicted: 30000 → 95000 (was inside the expected 3000-90000 range, now clearly out-of-range so the validated_fail outcome is structurally consistent)
- v1
format_idremoved; manifest now uses v2format_kind: "video_hosted"shape with the proper canonical slot vocabulary
Adopters who copy the example verbatim now get a structurally honest example. Thanks for catching this — it was the showcase example for the whole validate_input primitive.
| if (schema.$id) { | ||
| try { | ||
| ajv.addSchema(schema, schema.$id); | ||
| } catch (e) { |
There was a problem hiding this comment.
few things in here:
- L33-44 swallows real load failures silently — would log+exit on parse errors, only swallow duplicate $id adds
- L99 truncate-at-10 hides the real error when the 12-branch oneOf in product-format-declaration emits noise from non-matching branches
- only validates against product envelope; canonicals have
additionalProperties:true(per_base.json:102) so a typo inparamsslips through silently. fixtures are normative — run them through the per-canonical schemas in strict mode too?
There was a problem hiding this comment.
Fixed in fbb207b — all three:
(a) Schema parse errors during load now log + exit 2 (was: silently swallowed in a bare catch {}). Only the addSchema duplicate-$id error stays tolerated — that's benign during partial dist/ rebuilds when the same schema lands in both source and dist. The new code distinguishes the two failure modes explicitly.
(b) Removed truncate-at-10. The 12-branch product-format-declaration emits one error per non-matching branch, and the relevant failure is often the LAST branch's error (the matching discriminator branch). Full output is necessary to find the actual cause. Each error now also reports the schemaPath so reviewers can trace which branch fired.
(c) Per-canonical strict-mode validation runs against each format_options[i].params using a separate AJV instance with additionalProperties-tolerance disabled. Surfaces typos in params that the product envelope (additionalProperties:true) lets pass. Custom format_kind excluded — its params shape is governed by the seller's fetched format_schema, not by AdCP-side schema. Strict-mode warnings are non-fatal today (they don't fail CI) because additionalProperties:true is load-bearing for platform_extensions and seller-specific narrowings; warnings let fixture authors catch typos before merge.
…cab-scenes-delivery # Conflicts: # docs/creative/channels/audio.mdx
…A2 canonical mapping registry + canonical field on v1 format.json Closes #3765 and #3767. Reverses the wrong direction of b3c29bc (which forced exactly-one of format_ids xor format_options on products) — that broke the migration story by tying wire shape to AdCP-Version. Sellers should be able to author once and let SDKs project to both wire shapes. A1 (#3765): relax product.json from oneOf-with-not to anyOf - product.json: oneOf → anyOf. Both branches still required at- least-one; not clauses dropped. Both shapes legal during migration. - format_ids and format_options descriptions document the dual- emission contract: same underlying declaration when both present; buyers prefer format_options; SDK derives one from the other to guarantee invariant; non-projectable formats ship format_ids only. - tests/schema-validation.test.cjs assertion updated to anyOf with no-not invariant — surfaces regression to the old shape. - Phase 2 status callout in v2-overview.mdx updated. A2 (#3767): canonical mapping registry + canonical field on v1 format declaration - New /schemas/registries/v1-canonical-mapping.json with 15 seed entries (IAB display sizes 300×250, 728×90, 160×600, 970×250, 300×600, 320×50, 320×480, 336×280; VAST 4.x and 2.x/3.x; DAAST 1.x; structural matches for video/audio/zip/url last-resort). Two match modes: format_id_glob and structural. Governance via same vocabulary rules as asset-group-vocabulary.json. Initial scope deliberately small per discussion — full ~50-100 entries land in follow-up PRs once SDKs consume. - format.json (v1 format declaration) gets canonical and canonical_parameters fields. canonical $refs canonical-format- kind.json; canonical_parameters $refs product-format-declaration for strict typing. allOf if/then enforces canonical is set whenever canonical_parameters is. - v2-migration.mdx new "v1 → v2 canonical mapping" section documenting the resolution order: explicit canonical → glob → structural → fail closed (SDK MUST NOT emit format_options). Reverse direction (v2 → v1 demotion) is the inverse projection through the same registry. Without these, the "publish both during transition" story isn't actually legal under the schema and SDKs would diverge on v1↔v2 projection. With them: buyers and sellers move independently, neither blocked by the other's pace, all SDKs project identically because the data is identical. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…esult_kind, capability_id rule, duration precedence, negative fixtures, test hygiene Closes review-line comments R1-R7 from nastassiafulconis on PR #3307. R6: validate_input worked example fixed - assets.video → assets.video_main (matched the violation field path) - predicted: 30000 → 95000 (was inside the expected 3000-90000 range, now clearly out-of-range) - v1 format_id removed; example now uses v2 format_kind shape - example uses new result_kind discriminator (R2) R5: capability_id required-when rule rewritten - v2-overview line ~572 sentence was garbled (two thoughts collided). Rewrote as a normative routing rule: capability_id REQUIRED when multiple format_options share the same format_kind; OPTIONAL when each format_kind in the product is unique. Recommended habit even when optional, for log/replay/tooling clarity. R1: image_carousel cards as a real asset type - New /schemas/core/assets/card-asset.json with asset_type "card", required `media` (image|video oneOf), optional headline, landing_page_url, platform_extensions, provenance. - Added card to asset-union.json oneOf and to creative/asset-types/index.json registry. - Added "card" to canonical _base.json slot asset_type enum. - image_carousel slot changed from asset_type:"object" → "card". - Renamed allowed_card_asset_types → allowed_card_media_asset_types (with deprecated alias retained for back-compat) to disambiguate what the field constrains. - Manifest assets patternProperties now accept oneOf(single asset | array of assets) — unblocks carousel cards AND responsive_creative multi-value slots (headlines, descriptions, etc.) that were structurally broken under the previous single-asset constraint. Same change in creative-asset.json and list-creatives-response.json. R3: negative fixtures regression test - Worry was: top-level discriminator + allOf if/then/else + 12-branch oneOf might allow format_kind:"image" with stray format_schema to slip through. Verified via 7 negative fixtures + 4 validate-input-result fixtures that the schema rejects every malformed case. New tests/v2-negative-fixtures.test.cjs surfaces regressions; wired up as test:v2-negative npm script. R4: duration_ms_range vs duration_ms_exact precedence - Documented exact-wins precedence in description prose on video_hosted, audio_hosted, video_vast, audio_daast canonicals. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one. Schema stays permissive (no oneOf) for back- compat with any product currently emitting both. R2: result_kind replaces ok on validate-input-result - Replaced boolean `ok` with discriminator `result_kind: validated_pass | validated_fail | unvalidatable_nondeterministic`. Lets buyers distinguish "manifest validates and fails" from "platform is nondeterministic, can't pre-validate" — previously conflated. allOf if/then enforces violations[] absent for the two non-failure result_kinds. - validate-input-response description updated to reference the three result_kind values. - No consumers yet (v2 not shipped); breaking shape change uncontroversial. R7: v2-fixture-validation.test.cjs hygiene - (a) Schema parse errors during load now log + exit 2 (was: silently swallowed). Only the addSchema duplicate-$id error stays tolerated (benign during partial dist/ rebuilds). - (b) Removed truncate-at-10 on error reporting. The 12-branch product-format-declaration emits one error per non-matching branch — full output is necessary to find the actual failure cause. - (c) Per-canonical params strict-mode validation runs against each format_options[i].params using a separate AJV instance with additionalProperties-tolerance disabled. Surfaces typos in params that the product envelope (additionalProperties:true) lets pass. Custom format_kind excluded — its params shape is governed by the seller's fetched format_schema. Validation: schema/example/v2-fixture/v2-negative tests all green. Carousel manifests now actually validate (verified ad-hoc). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@pkras Thanks for the substantive review — all three of your points landed across the recent commits. Quick map for each: 1. #3639 (naming inconsistency) — picked 2. #3638 (runtime_status) — landed exactly as you proposed, b3c29bc17: 3. Custom format / canonical_hint / display_name (POV) — your proposal substantively shipped in 1cc7e2016. The promotion path you implied — recognized patterns become canonicals as adoption matures — is in the format-shape registry's governance rules. Promotion to canonical happens when a format_shape entry sees 2+ adopters with substantively similar format_schema content for 90+ days. Tracking queue at #3666. The display_name idea (separate human-facing name field on declarations) is one I haven't landed yet — products carry |
|
Issue #3765 proposes relaxing Generated by Claude Code |
|
Issue #3767 proposes a v1↔v2 canonical mapping registry ( Generated by Claude Code |
|
on the question: should we add a field specifically for that so UIs have a clean, seller-controlled label to show buyers? - yes, maybe "label" or "display_name". |
|
@pkras @bokelley — consensus noted on adding a seller-controlled display field to
Happy to implement either way once you land on a name. Generated by Claude Code |
|
Adds seller-controlled human-readable label field for dashboard/catalog UI presentation, per @pkras + @bokelley consensus in PR #3307 thread. Field has no machine semantics; buyer agents continue to route on format_kind and capability_id. https://claude.ai/code/session_01SNqihgM9LyXtFDXHL9QAfW
|
Done — Generated by Claude Code |
|
Noted — Generated by Claude Code |
…ery canonical is preview while v2 is preview Resolves the meta-level concern surfaced by nastassiafulconis's review (top-level body of PRR_kwDOPPPQu876xAr-): the 3 surface-composed canonicals capture the creative envelope but miss the actual product (amazon_sponsored_products has no negatives / match types / bid mods, PMax collapses 3 surfaces into one). Her recommendation was to treat those as rfc-grade until 2 retail-media nets ship format_schema artifacts. The right resolution is meta, not per-canonical: v2 itself is in preview through the 3.1 beta cycle, so claiming any canonical is `stable` right now is a category confusion. Stable-within-preview is incoherent — nothing in v2 is locked yet. Concrete changes: - canonical _base.json `status` field default changed from `stable` to `preview`. Description rewritten to explain the two phases: (1) while v2 is in preview, every canonical is preview by default — the field has no per-canonical differentiation; (2) at 3.1 GA, the working group promotes individual canonicals to stable based on adopter evidence (2+ adopters, 90 days stable params, defined tracking model — same rubric). - agent_placement and responsive_creative had per-canonical "Stability: preview" carve-outs in their description and status-default-preview overrides in their properties. Both removed — they're no longer special; every canonical is preview until adopter validation at GA promotes specific ones. - v2-overview.mdx 11-canonicals table: dropped the Status column (was `stable` / `preview` differentiated). All 11 canonicals are now in one column with no per-canonical status visible. - v2-overview.mdx replaced the "Stabilization rubric for preview canonicals" paragraph with a "Status: every canonical is preview while v2 is preview" subsection that frames the two phases (preview-window vs at-GA) and lists the 9 IAB-anchored canonicals as expected-to-clear-the-rubric-quickly vs the 3 surface-composed ones (responsive_creative, agent_placement, sponsored_placement) as needing adopter-shipped format_schema evidence before promotion. - v2-overview.mdx "Two stability axes" subsection collapsed into a smaller "runtime_status on each product declaration — separate axis from canonical status" subsection. The runtime_status field is still per-product per-declaration; just less prose ceremony around the canonical-vs-runtime distinction now that the canonical axis is uniform pre-GA. - glossary entry for status reframed accordingly. nastassiafulconis's sponsored_placement concern is now structurally addressed: it's preview like the others, won't promote to stable at GA without 2 retail-media nets shipping format_schema. No special- case demotion needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@nastassiafulconis on the bigger thing — "the 3 surface-composed canonicals ( You were right but the fix isn't per-canonical demotion. v2 itself is in preview through the 3.1 beta cycle, so claiming ANY canonical is Concrete reframe:
Your bar ("2 retail-media nets ship On the scope question (creative envelope vs bidding rules / match types / bid mods): the canonical's job is to describe what assets the buyer ships and how the surface renders them. Bidding rules, match types, negatives, surface-specific behaviors are separate domains that live on the package / proposal / extension surfaces, not on Filing that as a follow-up so it doesn't gate this PR. Ping me when you want to chat through the bigger architectural question on Slack — happy to keep digging on whether/how AdCP should ever extend into the bidding layer. |
|
@nastassiafulconis a few replies in one: On #3765 / #3767: both already in this branch — landed in a8228b666f. A1 (anyOf relaxation + dual-emission contract) and A2 (canonical mapping registry at On pushing to the same branch: yes, fine. On bidding-layer scoping + cross-surface collapse: agree they're two separate axes (order logic vs product topology). Filed as two RFCs so they're independent design calls but tracked together:
Both targeted at 3.1 GA (not necessarily this PR). My lean on each is in the RFC body; open questions for design discussion are also there. Cross-linked between each other so they don't get conflated. On Pia sync: nothing else from me — see what comes out of the sync and ping me if you want me drafting anything specific on agentic SOW. |
Status: Preview branch implementing RFC #3305 end-to-end. Not for merge until the 3.1.0 beta cycle opens — merging earlier would force a 3.1.0 release before the rest of v2 work is ready. Adopters and upstream implementors can check this branch out and build against the v2 spec.
Scope: Phase 1 (foundational primitives) + Phase 2 (canonical format catalog, product format declarations, validate_input tool, supporting schemas) + Phase 3 (migration guide, reference fixtures, fixture-validation test). Backwards-compatible additions only — no v1 producers or consumers are affected.
Read this first
docs/creative/v2-overview.mdx— adopter-facing walkthrough with Meta Reels, IAB MREC + sibling HTML5, podcast host-read, and Amazon SP worked examplesdocs/creative/v2-migration.mdx— concrete v1 → v2 migration paths per adopter typeValidate against the spec yourself
Five fully-valid reference Product fixtures at
static/examples/products/v2/pass strict schema validation:Fixtures:
meta_reels_us.json—video_hosted(vertical), Meta-specific extensionsnytimes_homepage_mrec.json—image(IAB MREC 300×250)nytimes_homepage_html5.json—html5(sibling product, different canonical because different tracking model)the_daily_30s_host_read.json—audio_hostedwith inlineinputs(script + brand)amazon_sponsored_products.json—sponsored_placement(catalog-driven, ASIN-keyed)What landed in this PR
Foundations (Phase 1)
asset-group-vocabulary.json— canonicalasset_group_idregistry (7 existing catalog entries + 12 audit-driven additions). Aliases expanded for headlines, descriptions, images_*, logo, video, audio. Canonicalizeslanding_page_urlover 6 v1 alias names.scenes.json— typed scene-by-scene structure for generative video inputs.zip-asset.json— first-class asset type for HTML5 banner bundles.docs/creative/channels/{video,audio}.mdxcorrecting VAST asset_type.Canonical format catalog (Phase 2)
11 canonical format definitions at
static/schemas/source/formats/canonical/:imagehtml5display_tagimage_carouselvideo_hostedvideo_vastaudio_hostedaudio_daastsponsored_placementresponsive_creativeagent_placementsi_chat.Each canonical bakes in its tracking model. Format-keyed-by-name structure (no
canonicaldiscriminator field)._base.jsondefines shared parameters:composition_model,provenance_required,platform_extensions,tracking_extensions,inputs,production_window_business_days.Product/manifest declarations (Phase 2)
product-format-declaration.json— keyed by canonical format name, exactly one key per declarationproduct.json— adds optionalformatfield (v2 inline declaration);oneOfenforces v1 (format_ids) XOR v2 (format)creative-manifest.json— adds optionalbrand(using canonicalBrandRef) andbrand_kit_overrideformat.jsonslot declarations — gain optionalasset_group_idfield (v1↔v2 migration bridge)Tools (Phase 2)
validate-input-{request,response,result}.json— buyer dry-run primitivecreative.supported_formatsfield added toget_adcp_capabilitiesresponse — uniform replacement forlist_creative_formatsregardless of agent roleplatform-extension-ref.json— URI + content-digest reference for platform extensionsAdopter docs (Phase 2 + Phase 3)
docs/creative/v2-overview.mdx— full architecture walkthrough with three-layer model, 11 canonicals, three worked examples (Meta Reels / NYTimes IAB MREC + sibling HTML5 / podcast host-read), two flows for inputs-driven creative, preview as universal verification surface, brand identity via BrandRef.docs/creative/v2-migration.mdx(new) — concrete v1 → v2 migration paths: side-by-side translations, slot mapping table, generative format dissolution, discovery surface migration, adopter paths per role, realistic timelines.Fixtures + fixture-validation test (Phase 3)
static/examples/products/v2/*.json— 5 fully-valid Product reference fixturestests/v2-fixture-validation.test.cjs— validates all fixtures against/schemas/core/product.json; runs vianpm run test:v2-fixturesformat_idsassertion on product.json with the new oneOf(format_ids, format) checkWhat's NOT in this PR (deferred or out of scope)
list_creative_formatsshape from v2 product declarations; Phase 4v1 backwards compatibility
format_idas{ agent_url, id }) continue to work through 3.x and 4.xlist_creative_formatsdeprecated but functional; sellers SHOULD provide server-side flatten wrappers through 4.0; removed at 5.0Hold reason
Changeset is
minor(this lands as 3.1.0). Merging now would cut a 3.1.0 release before the rest of v2 work is ready. Branch sits open as a preview adopters can build against; merge timing aligns with the 3.1.0 beta cycle opening.Test plan
npm run build:schemas— clean; 510+ schemas indist/schemas/latest/npm run test:schemas— 7/7npm run test:json-schema— 255/255 MDX JSON blocks validnpm run test:v2-fixtures— 5/5 reference fixtures pass strict Product validationtest:unit,test:test-dynamic-imports,typecheck)🤖 Generated with Claude Code