Skip to content

RFC #3305 implementation preview — v2 creative formats (full spec, not for merge)#3307

Open
bokelley wants to merge 32 commits into
mainfrom
bokelley/v2-phase1-vocab-scenes-delivery
Open

RFC #3305 implementation preview — v2 creative formats (full spec, not for merge)#3307
bokelley wants to merge 32 commits into
mainfrom
bokelley/v2-phase1-vocab-scenes-delivery

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented Apr 26, 2026

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

Validate against the spec yourself

Five fully-valid reference Product fixtures at static/examples/products/v2/ pass strict schema validation:

npm run test:v2-fixtures

Fixtures:

  • meta_reels_us.jsonvideo_hosted (vertical), Meta-specific extensions
  • nytimes_homepage_mrec.jsonimage (IAB MREC 300×250)
  • nytimes_homepage_html5.jsonhtml5 (sibling product, different canonical because different tracking model)
  • the_daily_30s_host_read.jsonaudio_hosted with inline inputs (script + brand)
  • amazon_sponsored_products.jsonsponsored_placement (catalog-driven, ASIN-keyed)

What landed in this PR

Foundations (Phase 1)

  • asset-group-vocabulary.json — canonical asset_group_id registry (7 existing catalog entries + 12 audit-driven additions). Aliases expanded for headlines, descriptions, images_*, logo, video, audio. Canonicalizes landing_page_url over 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.
  • Doc fixes at docs/creative/channels/{video,audio}.mdx correcting VAST asset_type.

Canonical format catalog (Phase 2)

11 canonical format definitions at static/schemas/source/formats/canonical/:

Canonical What it is Tracking
image Static image Impression pixel + click URL
html5 Interactive HTML5 banner (zip asset) MRAID + OM-SDK + click-tag macro + backup image
display_tag Third-party JS/iframe tag Opaque to seller
image_carousel Multi-card swipe (polymorphic items) Per-card pixels + carousel engagement
video_hosted Direct video file (orientation parameter) OM-SDK + external trackers
video_vast VAST tag Inherent VAST events
audio_hosted Direct audio (or host-read produced via build_creative) Standard impression/completion
audio_daast DAAST tag Inherent DAAST events
sponsored_placement Retail-media catalog-driven (Amazon SP, Criteo SP, CitrusAd SP) Per-item catalog-keyed
responsive_creative Buyer asset pool, surface composes (Google RDA/RSA/PMax/Demand Gen, Meta Advantage+) Per-asset performance
agent_placement AI-surface composed sponsored placement (ChatGPT, Perplexity, voice assistants). Distinct from si_chat. Mention-level impression + attribution

Each canonical bakes in its tracking model. Format-keyed-by-name structure (no canonical discriminator field). _base.json defines 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 declaration
  • product.json — adds optional format field (v2 inline declaration); oneOf enforces v1 (format_ids) XOR v2 (format)
  • creative-manifest.json — adds optional brand (using canonical BrandRef) and brand_kit_override
  • format.json slot declarations — gain optional asset_group_id field (v1↔v2 migration bridge)

Tools (Phase 2)

  • validate-input-{request,response,result}.json — buyer dry-run primitive
  • creative.supported_formats field added to get_adcp_capabilities response — uniform replacement for list_creative_formats regardless of agent role
  • platform-extension-ref.json — URI + content-digest reference for platform extensions

Adopter 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 fixtures
  • tests/v2-fixture-validation.test.cjs — validates all fixtures against /schemas/core/product.json; runs via npm run test:v2-fixtures
  • Schema-validation test update: replaced static format_ids assertion on product.json with the new oneOf(format_ids, format) check

What's NOT in this PR (deferred or out of scope)

  • SDK codegen scaffolding — Phase 4, separate PR (TypeScript first; generates typed accessors from canonical catalog)
  • Server-side flatten wrapper reference impl — lets v2 sellers derive v1 list_creative_formats shape from v2 product declarations; Phase 4
  • Native canonical format — deferred to 3.2 after TemplateCreative + OpenRTB Native 1.2 audit

v1 backwards compatibility

  • v1 named formats (format_id as { agent_url, id }) continue to work through 3.x and 4.x
  • v1 list_creative_formats deprecated but functional; sellers SHOULD provide server-side flatten wrappers through 4.0; removed at 5.0
  • Existing schemas unchanged or modified backwards-compatibly; existing producers and consumers continue to validate
  • v2 product-bound declarations are opt-in; sellers migrate when their organizational reality permits

Hold 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 in dist/schemas/latest/
  • npm run test:schemas — 7/7
  • npm run test:json-schema — 255/255 MDX JSON blocks valid
  • npm run test:v2-fixtures — 5/5 reference fixtures pass strict Product validation
  • Pre-commit hooks pass (test:unit, test:test-dynamic-imports, typecheck)
  • Mintlify broken-link check passed

🤖 Generated with Claude Code

bokelley and others added 2 commits April 26, 2026 17:39
…_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>
bokelley and others added 8 commits April 26, 2026 18:25
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>
@bokelley bokelley changed the title feat(creative): v2 Phase 1 — asset_group vocabulary, scenes schema, delivery_type, video.mdx fix RFC #3305 implementation preview — v2 creative formats (full spec, not for merge) Apr 28, 2026
bokelley and others added 9 commits April 28, 2026 11:23
…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>
bokelley and others added 6 commits April 29, 2026 21:25
…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
@bokelley
Copy link
Copy Markdown
Contributor Author

Issue #3639 proposes aligning the buyer-side production-source enum default across all four canonicals (buyer_uploaded on image/video_hosted/audio_hosted vs buyer_pre_rendered on sponsored_placement — same concept, different name). Same surface as this PR; consider folding before merge or confirm as a follow-up.


Generated by Claude Code

@bokelley
Copy link
Copy Markdown
Contributor Author

Issue #3638 proposes adding an optional runtime_status field to product-format-declaration.json — same surface as this PR; consider folding before merge or confirm as a follow-up. See issue for full rationale (short version: adopters will declare seller_pre_rendered_from_brief aspirationally during v2 rollout; without this field there's no spec expression for "declared but not wired in the adapter").


Generated by Claude Code

@pkras
Copy link
Copy Markdown
Collaborator

pkras commented Apr 30, 2026

Ok so two things first:

  1. Production-source enum asymmetry: 'buyer_uploaded' (image/video/audio) vs 'buyer_pre_rendered' (sponsored_placement) — same concept, different name #3639 and naming inconsistency

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.

  1. Adopter runtime declaration honesty: 'declared on catalog, not wired in adapter' has no spec expression #3638 and coming soon

how about: adding a runtime_status with:

  • stable
  • preview
  • declared_only

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

  • stable aka buy with confidence, runtime will deliver
  • preview aka proceed carefully, you might hit rough edges
  • declared_only aka don't buy against this yet, the seller is telling you where they're headed not where they are

Sellers = honesty mechanism

  • stable aka I've built and tested this end to end
  • preview aka I've built it but not hardened it
  • declared_only aka I've put this in my catalog because it's my roadmap, but if you try to use it today it won't work

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:

  1. a custom canonical: An explicit escape hatch for formats that are genuinely novel. Seller declares canonical: "custom", provides their own full format definition, no baseline assumptions applied. Buyer agents know to read the seller's description rather than inferring behavior from the canonical.
  2. canonical_hint alongside display_name -> rather than one field doing two jobs, formally separate the human-facing name from the machine-readable classification:
    -display_name: "Hover by Publisher so what buyers and UIs see
    -canonical_hint: "html5" so the closest canonical for buyer agent reasoning, explicitly marked as approximate

…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>
Copy link
Copy Markdown
Collaborator

@nastassiafulconis nastassiafulconis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 },
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"],
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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" },
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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": {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread docs/creative/v2-overview.mdx Outdated
}
```

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.)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in fbb207b — rewrote the broken paragraph as a normative routing rule with a clean code block:

  • format_kind selects the canonical and its slot vocabulary
  • capability_id is REQUIRED on the manifest when the target product's format_options contains 2+ declarations sharing the same format_kind (without it the seller can't disambiguate)
  • capability_id is OPTIONAL when each format_kind in format_options is unique — format_kind alone routes the manifest. Buyers MAY still send capability_id as 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"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

few things off:

  • manifest ships assets.video (615) but violation says assets.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 😬

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_id removed; manifest now uses v2 format_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.

Comment thread tests/v2-fixture-validation.test.cjs Outdated
if (schema.$id) {
try {
ajv.addSchema(schema, schema.$id);
} catch (e) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 in params slips through silently. fixtures are normative — run them through the per-canonical schemas in strict mode too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
bokelley and others added 2 commits May 1, 2026 14:23
…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>
@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 1, 2026

@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 buyer_uploaded per your call (one rename vs three; agreed the framing nit on "uploaded" reads slightly off for catalog-keyed items but the framing is called out explicitly in the description so readers don't trip on it). Landed in b3c29bc17. All four production-source fields (audio_source, image_source, video_source, item_production_model) now share the same 4-value enum default; SDK codegen emits a single shared enum.

2. #3638 (runtime_status) — landed exactly as you proposed, b3c29bc17: runtime_status: stable | preview | declared_only on ProductFormatDeclaration, distinct from canonical-level status (which is about spec maturity). The trust-signal / honesty-mechanism framing you wrote made it into the description prose. Documented in v2-overview "Two stability axes" section and in the migration walkthrough's sales-agent step 3.

3. Custom format / canonical_hint / display_name (POV) — your proposal substantively shipped in 1cc7e2016. format_kind: "custom" is the explicit escape hatch you described for genuinely novel formats. format_shape (from a vocabulary registry — multi_placement_takeover, branded_content, ar_lens, etc.) is the canonical_hint analog: machine-readable classification, separate from human-facing name, explicitly marked as recognized-pattern-but-not-yet-canonical. The crucial addition (raised by a follow-up review): format_schema is a URI+digest reference to the seller's fetchable schema, so buyer agents can reason structurally about novel formats without per-seller integration code. Same hosting / caching mechanic as platform_extensions.

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 name already, and the format_shape registry entries carry description / typical_use, but I haven't added a display_name on the format declaration itself for UI presentation. Worth a follow-up if the lack surfaces in dashboard / catalog UIs. Want to scope that as a new issue? Easy add if there's adopter pull.

@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 1, 2026

Issue #3765 proposes relaxing product.json from oneOf(format_ids, format) to allow both fields co-present — v2 format as primary, format_ids as v1 fallback for mixed buyer fleets during transition. Same product.json surface as this PR; consider folding before merge or confirm follow-up.


Generated by Claude Code

@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 1, 2026

Issue #3767 proposes a v1↔v2 canonical mapping registry (static/registries/v1-canonical-mapping.json) + canonical / canonical_parameters fields on v1 format.json declarations — same surface as this PR (which already adds asset_group_id to format.json slot declarations and owns the schema surface this would extend). Consider folding before merge or confirm as a follow-up PR once the 3.1 beta cycle opens.


Generated by Claude Code

@pkras
Copy link
Copy Markdown
Collaborator

pkras commented May 3, 2026

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".

@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 3, 2026

@pkras @bokelley — consensus noted on adding a seller-controlled display field to product-format-declaration.json. Two questions before acting:

  1. Field name: display_name matches the naming @bokelley used in the prior comment and is self-describing on the wire; label is shorter but more generic. Which do you prefer?
  2. Where: fold into this PR before the 3.1 beta cycle opens, or open a follow-up issue now (as @bokelley originally suggested) and defer to a separate PR?

Happy to implement either way once you land on a name.


Generated by Claude Code

@pkras
Copy link
Copy Markdown
Collaborator

pkras commented May 3, 2026

  1. Display name it is then
  2. i'd say 3.1

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
@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 3, 2026

Done — display_name (optional string) added to ProductFormatDeclaration in d7faae4f. No machine semantics; buyer agents continue to route on format_kind and capability_id. The NYTimes custom-format example in the schema now shows it in use ("display_name": "Homepage Takeover — Premium Sponsorship").


Generated by Claude Code

@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 3, 2026

Noted — display_name landed in d7faae4 as an optional, non-semantic string on ProductFormatDeclaration. No wire impact; routing still on format_kind / capability_id. The NYTimes example in the schema makes the intended use clear.


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>
@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 3, 2026

@nastassiafulconis on the bigger thing — "the 3 surface-composed canonicals (sponsored_placement / responsive_creative / agent_placement) capture the creative envelope but miss the actual product" — addressed at the meta level in 4bbce5893.

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 stable right now is a category confusion — stable-within-preview is incoherent. The PR's previous status differentiation (9 stable + 2 preview) implied locked contracts that don't actually exist yet.

Concrete reframe:

  • Every canonical is preview while v2 is preview. The status field exists for future use; per-canonical differentiation kicks in at 3.1 GA, not before. Default flipped from stable to preview.
  • At GA, the working group promotes individual canonicals based on adopter evidence (2+ adopters, 90 days stable params, defined tracking model — same rubric we already documented).
  • The 9 IAB-anchored canonicals (image, html5, display_tag, image_carousel, video_hosted, video_vast, audio_hosted, audio_daast, sponsored_placement) are expected to clear the rubric quickly because they sit on top of established industry standards.
  • The 3 surface-composed canonicals (responsive_creative, agent_placement, sponsored_placement — the latter for exactly the gap you flagged: amazon_sponsored_products' negatives / match types / bid mods aren't in the canonical because the canonical is the creative envelope, not the bidding/matching layer; PMax's 3-surface collapse is the same shape) need adopter-shipped format_schema evidence before promotion. They stay preview past GA until 2 retail-media nets ship real declarations against them.

Your bar ("2 retail-media nets ship format_schema artifacts") is now structurally what the rubric enforces — no special-case demotion needed.

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 format. Adding them to format would conflate two things. But you're right that adopters reading amazon_sponsored_products will reasonably wonder "where do I declare match_type?" — that's a docs gap I'll close in a follow-up. Each surface-composed canonical needs an explicit scope note saying "this canonical describes [X creative envelope]; bidding/matching/inventory parameters live elsewhere."

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.

@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 14, 2026

@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 static/schemas/source/registries/v1-canonical-mapping.json + canonical / canonical_parameters on v1 format.json + normative SDK projection rules in v2-migration.mdx). No separate branch needed.

On pushing to the same branch: yes, fine. bokelley/v2-phase1-vocab-scenes-delivery is the v2 trunk for everything pre-GA. Direct push welcome.

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:

  • #4548 — Order-logic scoping (Product.targeting_capabilities for discovery + Package.targeting for buy-time configuration). Per-axis sub-objects (keyword_targeting / audience_targeting / bid_modifiers / geo_targeting / pacing), not one umbrella.
  • #4549 — Product topology (Product.sub_products[] for PMax-class meta-products that fan out across multiple surfaces with per-surface policies). Different shape, different RFC.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants