Skip to content

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

Open
bokelley wants to merge 54 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 54 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_options array (v2 inline declarations); anyOf permits dual emission — products MAY carry format_ids, format_options, or BOTH (at least one required per RFC #3305 amendment: allow both format_ids and format on products; decouple wire shape from AdCP-Version #3765 fold-in)
  • 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 anyOf(format_ids, format_options) 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
… inline normative fixes

Six inline fixes from the SDK-team critical review (out of nine raised; #4 and #5 filed as follow-ups):

- IR1 v2→v1 projection: add `v2_only` boolean on ProductFormatDeclaration (required `true` when `format_kind: "custom"`). Producer + consumer divergence rules normative in canonical-formats.mdx. No synthetic v1 format_id namespace — explicit v2_only is the marker.
- IR2 non-projectable v1: SDKs MUST emit structured warning on resolution failure (carrying format_id, product_id, failure reason). Prevents silent v2-only inventory shrinkage.
- IR3 format_schema fetch contract: https-only, hard-fail on digest mismatch, ≤5s timeout, $ref sandbox (same-origin / AAO mirror / intra-doc; no file://; depth ≤8), graceful 404 degradation, invalid-schema hard-fail. In both schema description and canonical-formats.mdx.
- IR6 codegen vs runtime: doc callout that generated TS/Pydantic types lose if/then narrowing on format_kind:custom + result_kind; Ajv runtime validator is the gate.
- IR8 agent_placement: explicit 3.2-track stamp — tracking macro/postback/dedup intentionally underspecified for 3.1; adopters SHOULD ship as runtime_status: preview or declared_only.
- IR9 migration math: realistic-coverage paragraph (15 registry entries, ~76% projectable with seller/registry action, 71+ v1-only out of gate, dual-read realistic through 3.3).

Negative fixtures expanded: format_kind:custom now rejects without v2_only:true (added 2 new fixtures, total 13 passing).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…on ProductFormatDeclaration

Field name should follow the canonical-formats naming convention established by the
v2 → canonical-formats rename. v2_only perpetuated the v2 terminology that we agreed
to retire from machine-readable identifiers (path: file paths, field names, registry
identifiers use canonical-formats; v1↔v2 contrast remains only as narrative shorthand
inside schema descriptions).

Updates field name + schema description + if/then required-list + example in
product-format-declaration.json; mirrors the rename in canonical-formats.mdx,
negative-fixture test, and changeset.

All 13 positive + 13 negative fixtures still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley and others added 5 commits May 16, 2026 08:51
…-level, security, narrowing, auto-promote-stable

Triaged from 5 expert reviews + a second SDK-implementor review. 4 blocks of inline fixes; 3 follow-ups filed (#4591 scope extended, #4592, #4599).

Block A — security hardening on format_schema fetch:
- SSRF deny-list (RFC 1918, loopback, link-local, CGNAT, RFC 6761 names; cloud metadata IPs); DNS rebinding defense via IP pinning
- No HTTP redirects; 1 MiB response cap; schema-compile DoS controls (keyword count, $ref count, regex timeout, validation budget)
- Digest format pinned (sha256: + 64 lowercase hex); $ref sandbox normalized per RFC 3986
- Transport rules apply to BOTH format_schema (load-bearing) AND platform_extensions (informational); platform-extension-ref.json uri pattern: ^https://

Block B — wire-level load-bearing concerns:
- B1: per-slot consumed_for_production boolean on _base.json#slots — dispatch hint for build_creative and v1↔v2 translators
- B2: SHOULD-warn surfaces via errors[] envelope (not logger-only); 2 new error codes (FORMAT_PROJECTION_FAILED, FORMAT_DECLARATION_DIVERGENT)
- B3: open-enum guidance on canonical-format-kind.json — consumers MUST treat as open; unknown values retained + treated as runtime_status: declared_only
- B4: validate_input request renamed to discriminated targets[] mirroring response shape; eliminates wire-shape collision with Product.format_ids

Block C — auto-promote-stable + correctness + narrowing semantics:
- C1: 6 canonicals to status: stable at GA (image, display_tag, video_hosted, video_vast, audio_hosted, audio_daast — Track-A v1-translatable); 5 stay preview (html5, image_carousel, sponsored_placement, responsive_creative, agent_placement)
- C2: negative-test AJV now uses discriminator: true matching positive suite
- C3: new format_kind: custom fixture (nytimes_homepage_takeover_custom.json) for the riskiest oneOf branch
- C5: positive controls for canonical_formats_only on non-custom branches
- C6 (N1): formal "narrows" definition — parameter-by-parameter subsumption rules for dual-emission divergence detection
- C7 (N3): canonical_parameters drift contract — SDKs SHOULD lint-time check, producers SHOULD prefer derived over authored
- C8 (N7): asset_group_id alias collision precedence — declaration order authoritative, collisions surfaced via FORMAT_PROJECTION_FAILED
- C9 (N8): declared_only SHOULD-filter by default on buyer SDKs (opt-in to surface)

Block D — docs structural pass (selective):
- TL;DR block at top of canonical-formats.mdx — 6/11 stable at GA, 71+ v1-only out of gate, Phase 4 codegen is gating dep
- Codegen-vs-runtime promoted from H3-buried to its own H2
- "canonical formats" used as primary body term; v1↔v2 reserved for schema-description shorthand

Tests: 14 positive + 15 negative fixtures all green under discriminator: true strict mode.

Follow-ups: #4591 (storyboard matrix scope extended), #4592 (sponsored_placement adapter docs), #4599 (synthetic v1 format_id docs)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…4599)

Plugs a coherence hole on PR #3307: the spec rejected an aao-synth/* synthetic
namespace but didn't say what a v2-native seller actually does when they need
to emit format_ids for v1-only buyers. Adopters would hit this on day 1.

Two acceptable answers, both documented inline:

1. Default — canonical_formats_only: true, omit from format_ids. Product is
   functionally invisible to v1-only buyers but the v1 surface stays clean.

2. Synthesize seller-scoped IDs like acme.adcp/canonical_image_300x250 when the
   seller wants v1 reach. Constraints normative:
   - MUST be seller-scoped (never aao-synth/* or cross-seller namespace)
   - MUST be declared in the seller's published format catalog
   - Buyers MUST NOT pattern-match on the convention (catalog is authoritative)
   - Synthetic id + format_options entry must satisfy dual-emission narrowing

Closes #4599 (filed earlier this session as a follow-up; small enough to roll in
rather than carry as a separate PR).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…itions

Two CI failures on PR #3307:

1. Changeset CLI rejected both canonical-formats changesets because they
   declared package `@adcontextprotocol/adcp` which is not the workspace name.
   The actual package.json `name` is `adcontextprotocol`. Both changesets fixed.

2. error-code-drift lint rejected FORMAT_PROJECTION_FAILED and
   FORMAT_DECLARATION_DIVERGENT (added on this PR) because they're present
   in source but absent from origin/3.0.x and had no dispositions entry.
   Added both with `held-for-next-minor` + target_version=3.1 — this is wire
   change territory for the canonical-formats v1↔v2 surface; 3.0.x stays clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…o BrandRef

Addresses nas review on PR #3307: brand_kit_override on creative-manifest.json
duplicated a pattern BrandRef already establishes for inline overrides
(industries, data_subject_contestation). Move the field to BrandRef so all
per-call brand context lives in one place, eliminates the parallel vocabulary
on CreativeManifest, and adopters don't have to learn two override patterns.

Shape changes:
- brand-ref.json: add brand_kit_override property (logo, colors, voice, tagline)
  next to industries and data_subject_contestation. Same precedence rules: brand.json
  is canonical, inline override takes precedence for fields present.
- creative-manifest.json: drop brand_kit_override; the brand description points at
  BrandRef's inline override.
- docs (canonical-formats.mdx + canonical-formats-migration.mdx): updated the
  worked example to show brand_kit_override nested inside brand: { domain, brand_kit_override }.

Out-of-subset fields (voice_attributes, prohibited_terms, etc.) still require
publishing a different brand.json — the inline override is intentionally narrow
to a small high-traffic subset.

No fixture changes needed (no existing fixture used the old shape).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tor review

Addresses gaps surfaced by a third SDK-implementor review (Python/Pydantic angle).
Four small normative additions; three reviewer items filed separately as follow-ups.

1. Platform-extension collision precedence (_base.json#platform_extensions).
   When two platform_extensions[] entries extend the same target with overlapping
   field names, array order is authoritative — later entries override earlier
   ones per-field. SDKs surface via errors[] with structured code. Same flavor
   as the asset_group_id alias collision rule we just landed.

2. brand_kit_override field-level merge (brand-ref.json#brand_kit_override).
   Now explicit: merge is field-level, not whole-object replacement. Composite
   fields (colors.primary, colors.secondary, colors.accent) merge one level
   deeper. SDKs MUST NOT treat a present override.colors as wiping brand.json's
   colors block entirely; only per-slot fields present in the override take
   precedence.

3. validate_input scope clarification (validate-input-result.json).
   validate_input validates manifest STRUCTURE, not output rendering. For
   composition_model: algorithmic formats (responsive_creative, agent_placement),
   the asset pool is still structurally validatable (counts/sizes/lengths) →
   validated_pass / validated_fail. unvalidatable_nondeterministic is reserved
   for synthesis_nondeterministic: true cases where the production pipeline
   itself can't be evaluated upfront. Buyers calling validate_input on an
   algorithmic-composition target SHOULD expect a structural verdict, not a
   rendering preview.

4. SDK canonical-catalog version negotiation (get-adcp-capabilities-response).
   Optional canonical_catalog_version (semver-shaped) on capabilities.creative.
   Lets codegenned SDKs detect canonical-catalog skew between their generated
   types and the seller's actual support. Pairs with the open-enum semantics
   on canonical-format-kind.json (B3) — skew is soft-warn, not hard error.

All tests green: 7 schema + 14 positive fixtures + 15 negative fixtures + error-code drift.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley and others added 5 commits May 16, 2026 15:05
…eprecate canonical_parameters

Flips the v1↔v2 link direction per Brian's design pass: v2 declaration is the
source of truth for shape; the v1 named format is just the legacy identifier
the v2 declaration links back to via v1_format_ref. Eliminates the
canonical_parameters drift surface (v1 file mirroring the v2 shape) by removing
the parallel declaration entirely.

Schema changes:
- ProductFormatDeclaration: new optional v1_format_ref ($ref FormatId).
- Mutual exclusion with canonical_formats_only:true enforced via allOf/not.
- format_kind:"custom" relaxed: requires format_shape + format_schema AND
  EITHER canonical_formats_only:true OR v1_format_ref. One or the other.
- format.json#canonical_parameters: marked deprecated:true. Retained for 3.1
  backward-compat (SDKs MUST honor when present); removed at 4.0. New code
  SHOULD migrate to v1_format_ref on the v2 side.

Resolution order updated (v1-canonical-mapping.json):
1. v2 declaration with v1_format_ref pointing at this v1 format (authoritative)
2. v1 file's explicit canonical field (seller-asserted)
3. format_id_glob registry match
4. structural registry match
5. fail-closed with FORMAT_PROJECTION_FAILED

Negative fixtures expanded: 4 new cases (v1_format_ref on Track-A canonical
accepted; v1_format_ref on custom accepted; canonical_formats_only:true AND
v1_format_ref rejected; custom with neither rejected). 19 negative + 14
positive fixtures all passing.

Docs: new "v2 → v1 linking via v1_format_ref" subsection in canonical-formats.mdx.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mental; drop tracking_extensions; rename *_source to asset_source; pin version patterns

Big architectural cleanup per Brian's design pass on the punch list (items B/C/D/E/I).

D — collapse two stability axes to one experimental flag:
- Drop _base.json#status enum (was: stable | preview | deprecated)
- Drop ProductFormatDeclaration#runtime_status enum (was: stable | preview | declared_only)
- Add _base.json#experimental boolean (default false)
- Add _base.json#deprecated boolean (kept separate — different concept)
- Add ProductFormatDeclaration#experimental boolean (independent of canonical-level)
- Drop the Track A/B GA promotion rubric prose entirely
- Same semantic as 'experimental' on protocols: 'may not work, have a fallback'

E — mark four canonicals experimental: true:
- sponsored_placement (4 adapter contracts)
- responsive_creative (algorithmic, no v1 equivalent)
- agent_placement (3.2-track, tracking model underspecified)
- custom (handled via per-declaration experimental flag)
- The 6 IAB/VAST/DAAST re-encodings plus html5 and image_carousel ship non-experimental

B — drop tracking_extensions field:
- _base.json#tracking_extensions removed
- Buyers filter platform_extensions via extensions[uri].extends === 'tracking'
- Single source of truth; no parallel array

C — rename *_source fields to single asset_source:
- audio_source / image_source / video_source → asset_source (shared 5-value enum)
- buyer_audio_acceptance / buyer_image_acceptance / buyer_video_acceptance → buyer_asset_acceptance
- item_production_model on sponsored_placement keeps its name (semantically different)
- but now described as 'sharing the same enum' as asset_source
- Cross-canonical leak fixed (audio_source on image canonical no longer accepted in practice)
- Fixtures migrated (the_daily_30s_host_read.json, veo_generative_video_15s.json)

I — pin semver patterns:
- _base.json#since_version: ^\d+\.\d+$
- _base.json#migration_target_version: ^\d+\.\d+$
- Reject patch precision and placeholders like 'unknown' (omit field instead)

Docs: glossary entries updated; replaced 'Status / runtime_status' sections with single 'experimental' section; tracking_extensions references replaced with platform_extensions filter pattern.

Tests: 14 positive fixtures + 19 negative fixtures all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cription/cta

F (4c) — slot max_chars/max_size_kb mutex enforced via if/then on _base.json:
- text/markdown/brief asset_types: max_chars allowed, max_size_kb rejected
- image/video/audio/zip asset_types: max_size_kb allowed, max_chars rejected
- url/catalog/html/css/javascript/webhook/daast/vast/card/object: both rejected
- Field descriptions clarified to call out the mutex
- Producers can no longer ship {asset_type: text, max_chars: 100, max_size_kb: 50} — downstream picks-arbitrarily bug eliminated

H — card-asset.json expanded to cover Pinterest/Meta/TikTok/AI-surface multi-card patterns:
- Added `description: string` (longer body copy, 100-500 chars typical) — Pinterest pin description, Meta carousel description, AI-result body
- Added `cta: string` (per-card call-to-action label, MUST be in parent format's cta_values) — Meta and TikTok per-card CTA support
- image_carousel.json: added `card_description_max_chars` parameter (parallel to existing `card_headline_max_chars`)
- Description prose updated to call out Meta/Pinterest/Snap/TikTok/AI-surface coverage
- platform_extensions remains the home for genuinely platform-specific fields (Pinterest rich-pin, Snap price tags, source attribution)

Tests: 14 positive + 19 negative all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…er tighten, narrowing-check normative

Triaged from three expert reviews (code-reviewer, ad-tech-protocol, adtech-product):

1. Rename stragglers — audio_source/image_source/video_source/buyer_*_acceptance still in:
   - product-format-declaration.json (description prose + audio_hosted example)
   - audio_hosted.json (description + slots default description)
   - the_daily_30s_host_read fixture (description prose)
   - canonical-formats.mdx (production-source bullet list, generative DSP example)
   - canonical-formats-migration.mdx (multiple rows + prose)
   Schema accepted via additionalProperties:true but adopters reading the spec saw old names — worst kind of drift.

2. sponsored_placement.item_production_model now correctly says "4-value subset of asset_source's 5-value enum" instead of "shares the same enum" — the previous claim was structurally wrong (drops publisher_host_recorded). Glossary entry updated accordingly.

3. Glossary table had a sed-mishap "asset_source / asset_source" duplicate header — cleaned up.

4. tracking_extensions removed from product-format-declaration.json description prose (had been dropped from the schema but a reference survived in the field description).

5. since_version / migration_target_version pattern tightened from `^\d+\.\d+$` to `^[1-9]\d*\.(0|[1-9]\d*)$` — rejects `03.1`, `3.01`, leading-zero variants. MAJOR can't be 0 since AdCP is 3.x.

6. v1-canonical-mapping.json resolution-order step 1 now normatively requires SDKs SHOULD run the narrows check between the v2 declaration and the referenced v1 format's requirements; surfaces FORMAT_DECLARATION_DIVERGENT on conflict. Was prose-only ('Narrows — formal definition' section), now it's the registry step. Closes the gap that v1_format_ref was a hint, not a contract.

7. scripts/oneof-discriminators.baseline.json updated to accept 6 new undiscriminated oneOfs (all structurally distinguishable per code-reviewer: 4 single-vs-array assets patterns, 2 format_id-vs-format_kind mutexes, 1 v1_pattern variant).

All 14 positive + 19 negative fixtures green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lint surface + Phase 4 commitment

Two changes from the 4th SDK-implementor review's actionable items:

1. **Soft-warn surface clarified** — FORMAT_PROJECTION_FAILED and FORMAT_DECLARATION_DIVERGENT are now described as 'either side MAY emit; SDKs detecting on consumption SHOULD surface via lint-output channel OR errors[] augmentation, SDK's choice.' Previous wording 'MUST surface via errors[]' closed the door on the cleaner SDK-lint-output pattern. Both surfaces remain non-logger-only — the operator-actionable requirement is preserved. error-code.json + v1-canonical-mapping.json resolution-order step 5 + canonical-formats.mdx all updated consistently.

2. **Phase 4 commitment tightened** — was 'blocking adoption' (vague), now 'MUST ship alongside 3.1.0 beta, not after.' Expanded scope: SDK codegen + flatten wrapper + platform_extensions URI+digest fetch+cache helper + typed accessors for slot-level scheduling hints. Closes the risk that walled gardens never move because list_creative_formats reality skews while Phase 4 trails.

Filed as follow-up (not inline):
- platform_extensions fetch+cache reference helper as part of Phase 4 work (rolled into the Phase 4 row)
- typed accessor for production_window_business_days etc. (rolled into Phase 4 row)

Stale-draft items (key-as-discriminator vs string-discriminator, format_ids XOR oneOf) not addressed — reviewer was on a pre-#3765 draft.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley and others added 3 commits May 17, 2026 05:59
…RMAT_PROJECTION_FAILED + FORMAT_DECLARATION_DIVERGENT

Per 4th implementor review's nit on #6: "both surfaces" phrasing was ambiguous about whose lint channel a downstream consumer can subscribe to (it's whichever SDK processed the response — lint output isn't a wire artifact and doesn't propagate to subsequent consumers).

Explicit normative paragraph added to both error codes' enumDescriptions:
- Wire errors[] reflects only what the producer self-detected
- Consumer-SDK lint output is not visible to downstream consumers of the original wire response
- Downstream consumers needing full visibility MUST subscribe to the lint channel of the SDK that processed the response
- Absence in errors[] does NOT imply absence of the underlying condition

Closes the wire-boundary ambiguity that the previous "either side MAY emit; SDK MAY use lint" prose left open.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…erify Product 'neither' rejected

From 5th SDK-implementor review.

#1 — format_shape promotion migration story (real concern):
When a format_shape (e.g., multi_placement_takeover) graduates from custom to a first-class canonical, any client code branching on `format_kind == "custom"` silently stops matching that publisher's products. Spec now carries a normative migration contract on format-shape-vocabulary.json and canonical-formats.mdx:

1. Transition window ≥90 days where sellers MAY emit BOTH shapes (old custom + new canonical) on the same product's format_options[]
2. Consumer SDKs SHOULD emit a structured deprecation warning via lint channel when they see format_kind:"custom" with a promoted format_shape; payload: { format_shape, promoted_to, promotion_release, transition_end }
3. Registry's `promotion_status` field lifecycle: 'tracking — see adcp#3666' → 'promoted to <format_kind> in <version>; transition ends <date>'
4. Post-transition: sellers SHOULD drop the legacy custom declaration; buyers MAY then assume custom == long-tail/non-promoted

Without this contract every promotion event silently breaks adopter code. With it, the deprecation warning is the early signal during transition.

#2 — Product "neither format_ids nor format_options" case: verified the schema's anyOf already rejects this case (validate fails with 'must have required property format_ids' / 'must match a schema in anyOf'). No fix needed; existing prose on product.json's format_ids field already says 'MUST carry format_ids, format_options, or BOTH; at least one is required.'

Stale-draft items in the review (key-as-discriminator, runtime_status:declared_only) not addressed — reviewer was on pre-#3765 + pre-D-block drafts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tch format-id wire pattern

Dragon report bug #1: format-id.json id pattern is ^[a-zA-Z0-9_-]+$ (no slashes), but v1-canonical-mapping.json shipped 8 named-glob entries with 'iab/X' style prefixes. None could ever match a wire-valid v1 format_id — 8 of 15 registry entries were dead code. Only the 7 structural entries actually triggered.

Fix: rename all 'iab/X' globs to 'iab_X' (underscore namespace). Updated 8 entries: mrec_300x250, leaderboard_728x90, wide_skyscraper_160x600, billboard_970x250, half_page_300x600, mobile_banner_320x50, mobile_interstitial_320x480, large_rectangle_336x280. Registry description now spells out the namespace convention: prefix with source organization separated by underscore (e.g., iab_mrec_300x250, meta_reels, daast_audio_30s); the format_id wire pattern doesn't permit forward slashes.

Bugs #2 (SDK adcp_version emit) and #3 (PR body says oneOf / 'format') are not spec-scope — #2 is an SDK fix in adcp-client-python, #3 is a GitHub PR description edit. Both noted; this commit covers only the spec bug.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request May 17, 2026
…2260) (#4628)

* docs(creative): creatives outlast campaigns + signal state changes (#2260)

Replace the 3.0 "retention seller-defined" hedge with a normative rule:
library creatives MUST persist independently of the buys that reference
them, regardless of submission path (sync_creatives, inline, or
platform-native). Sellers MAY archive unassigned creatives for
inactivity, post-flight expiry, or storage policy, but MUST signal any
state change on a buyer-synced creative — via impairment on the buy when
live assignments exist, via a creative state-change notification
otherwise (#2261 owns transport).

Widen the creative-status archived enumDescription to acknowledge
seller-initiated archive and require the state-change signal. Additive
description-only change; no new enum values, no new fields.

Frame a library creative as the bundle of buyer-supplied inputs (assets,
brief, brand+catalog pointers, or any combination), sidestepping the
generative/non-generative fuzziness. Variant addressability is a
format-level concern handled by #3305/#3307.

Closes #2260. Refs #2261, #3305, #3307.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(creative): address expert review on retention contract (#2260)

Two blockers from protocol review:

1. Replace "MUST emit via buyer's registered notification channel" with
   the surface that exists today — list_creatives must reflect the new
   status on the next read. Aligns with the snapshot-and-log contract,
   which already names list_creatives as the conformant signal for
   resource state changes outside an active buy. #2261 owns the future
   push channel; promoted to SHOULD-additionally once it ships.

2. Add approved → archived (seller-initiated) edge to the creative
   state machine, scoped to creatives without active package
   assignments. Sellers MUST NOT seller-archive a creative with active
   assignments — the approved → rejected revocation path with an
   impairment is the only conformant route when active serving is
   involved.

Friction note from product review: add explicit "library can be a thin
view over per-buy storage" line for CTV/podcast vendors whose ad server
has no library object distinct from per-buy attachment.

Docs nits: tighten archived enumDescription wording; constrain
seller-archive to no-active-assignments; update changeset to reflect
the state-machine edit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley and others added 5 commits May 17, 2026 08:30
…ace for SDK-detected advisories

Per implementor review's option 3 (decide so everyone does it the same way): collapse the two-surface choice (lint-channel OR errors[] augmentation) down to a single mandate. errors[] augmentation wins because the multi-hop agent network needs warnings to propagate across SDK boundaries via the wire response. Lint-output channels are local to whoever processed and die at the SDK boundary — wrong abstraction for a federated network.

Schema additions (core/error.json): new 'source' field (enum: producer | sdk) on each error entry distinguishes seller-emitted from SDK-augmented; new 'sdk_id' field (format: <package>@<version>) identifies which intermediate processor inserted an entry. SDK-augmented entries MUST carry both fields. Multi-hop deduplication rule: each hop SHOULD dedup by (code, field) rather than re-emit.

Updated FORMAT_PROJECTION_FAILED and FORMAT_DECLARATION_DIVERGENT prose in error-code.json + the v1-canonical-mapping resolution-order step 5 + canonical-formats.mdx Dual-emission section. All four now consistently mandate errors[] augmentation; lint-output explicitly NOT acceptable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…type

Five spec-level fixes from the v2 SDK prototype work:

1. v1_translatable boolean on _base.json (default true). Override false on the 4 inherently-v2 canonicals: image_carousel, sponsored_placement, responsive_creative, agent_placement. Lets SDKs distinguish 'no v1 path possible' (structural) from 'registry not covered yet' (correctable). SDKs MUST NOT emit FORMAT_PROJECTION_FAILED on v1_translatable:false canonicals.

2. New error code FORMAT_DECLARATION_V1_AMBIGUOUS for the family-only registry-match case (e.g., video_vast has a structural entry but no invertible literal). 7 of 13 fixtures fall in this bucket. SDKs MUST surface this rather than synthesize an arbitrary v1 format_id.

3. Direction-of-truth: v1-canonical-mapping.json description now explicit that the registry is authoritative for v1 → v2 only. v2 → v1 projection MUST rely on v1_format_ref; SDKs MUST NOT synthesize v1 format_ids by inverting structural matches. Inter-SDK divergence on synthetic agent_url choices was the bug.

4. Resolution order gains a 5th step (ambiguous family) before fail-closed, surfacing FORMAT_DECLARATION_V1_AMBIGUOUS.

5. Reference fixtures now demonstrate v1_format_ref: nytimes_homepage_mrec → iab_mrec_300x250, triton_daast_audio_30s → daast_audio_30s_v1_1. Spec exemplars now model the dual-emission pattern the spec body recommends.

Refs: adcp-client #1815

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…_format_ref convention

Brian's three-point follow-up to the 'why don't more project cleanly' observation:

1. Annotated 4 more fixtures with v1_format_ref (was 2/13, now 6/13 with explicit v1↔v2 pairing): gam_3p_display_tag → iab_3p_tag_300x250, nytimes_homepage_html5 → iab_html5_300x250, youtube_vast_preroll → iab_vast_preroll_4x, meta_reels_us → meta_reels. Plus migrated the existing 2 (nytimes_homepage_mrec, triton_daast_audio_30s) from publisher-scoped agent_url to AAO-hosted.

2. Added 5 new literal registry entries (iab_3p_tag_300x250, iab_html5_300x250, iab_vast_preroll_4x, iab_daast_audio_30s, meta_reels) so SDKs have invertible lookups for these canonicals beyond just the IAB image sizes. SDK best-effort v2→v1 inversion now works for display_tag, html5, video_vast, audio_daast, and Meta-platform video_hosted in addition to image.

3. Normative AAO-hosted convention on v1_format_ref: IAB-standard formats SHOULD point at https://creative.adcontextprotocol.org with the registry-published id. Platform-specific formats point at the platform's agent_url. Seller-bespoke formats point at the seller. Converges the v1-wire namespace for IAB shapes — every seller's 300x250 MREC points at the same {agent_url, id} pair, so v1-only buyer allowlists work uniformly across publishers. Without this, per-publisher namespace sprawl reproduces exactly the fragmentation canonical-formats was designed to eliminate.

Schema validation: 14 positive + 19 negative fixtures still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…aming

The 13 literal registry entries I'd authored as 'iab_*' don't match what creative.adcontextprotocol.org actually serves. The catalog (server/src/creative-agent/reference-formats.json) uses display_<dim>_<delivery> naming (display_300x250_image, display_300x250_html, video_vast_30s, audio_standard_30s, etc.). My registry IDs were orphans that no wire traffic would ever match.

Part A — registry renames (v1-canonical-mapping.json):

- 8 iab_*_image → display_<dim>_image (mrec, leaderboard, wide_skyscraper, billboard, half_page, mobile_banner, large_rectangle)

- iab_html5_300x250 → display_300x250_html, added display_728x90_html

- iab_3p_tag_300x250 dropped (no standalone catalog entry); display_js generic added for the third-party-tag pattern

- iab_vast_preroll_4x dropped; video_vast + video_vast_30s added (matching catalog)

- iab_daast_audio_30s dropped (no DAAST entry in catalog yet)

- iab_mobile_interstitial_320x480 dropped (no catalog entry)

- audio_standard_15s/30s/60s + video_standard_15s/30s added (matching catalog)

- meta_reels kept (intentional non-AAO platform format on meta.adcp)

Part B — fixture v1_format_ref updates:

- nytimes_homepage_mrec → display_300x250_image

- nytimes_homepage_html5 → display_300x250_html

- gam_3p_display_tag → display_js

- youtube_vast_preroll → video_vast_30s

- triton_daast_audio_30s: dropped v1_format_ref, set canonical_formats_only:true (no DAAST entry in catalog)

- meta_reels_us kept as-is (platform-specific)

- agent_url corrected to https://creative.adcontextprotocol.org/ (trailing slash, matches catalog publish format)

Part C — AAO catalog (reference-formats.json):

- Added canonical field to 32 catalog entries (image/html5/display_tag/video_hosted/video_vast/audio_hosted/sponsored_placement)

- Catalog now fires resolution-order step 2 (seller-asserted canonical) directly; v2-aware SDKs read v1↔v2 projection inline without registry round-trip

- Native, DOOH, broadcast, and card-scaffolding entries unannotated — no clean canonical mapping yet

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ith RFC 2606 .example

Brian caught it: meta.adcp / nytimes.adcp / flashtalking.adcp / tiktok.adcp / triton.adcp / youtube.adcp / google.adcp / openai.adcp / audiostack.adcp / yourpub.adcp don't exist. These were placeholders dressed up as real URIs in examples and schema descriptions. Replaced all 17 PR-touched files with RFC 2606 example domains (.example).

Files updated: 7 fixture files, 5 schema files, 2 mdx docs, 2 protocol files, 1 docs/building file. All .adcp references converted to .example via sed. No semantic change — the URIs were illustrative placeholders in either case; the .example convention is the honest one.

Closed-platform AAO-mirror pattern (https://mirror.adcontextprotocol.org/translated/<platform>/) remains documented in canonical-formats.mdx Platform Extensions section as the normative production handling for walled gardens — examples just use .example placeholders rather than implying we run a real translation service today.

Out of scope (pre-existing on main): static/test-vectors/transport-error-mapping.json, static/compliance/source/universal/error-compliance.yaml, docs/protocol/get_adcp_capabilities.mdx, static/schemas/source/{media-buy,creative}/list-creative-formats-response.json — these have .adcp references that predate this PR. Tracking separately.

Tests: 14 positive + 19 negative fixtures all still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit to adcontextprotocol/adcp-client that referenced this pull request May 17, 2026
CI doesn't sync the 3.1.0-beta.0 cache — it's gitignored and only
exists in workspaces that have manually built the unreleased spec PR.
The projection layer's registry + canonical-properties loaders depend
on schemas/cache/3.1.0-beta.0/, so attempting to project without it
throws and the tests fail.

Skip cleanly with a clear reason via `{ skip: SKIP_REASON }` on every
describe. Locally (cache present) all 25 tests still pass; in CI all
5 suites report `# SKIP requires schemas/cache/3.1.0-beta.0/`.

Same pattern as `adcp-version-release-precision.test.js`'s wire-
prerelease guard. Will start running in CI when upstream
adcontextprotocol/adcp#3307 merges and `npm run sync-schemas` can
pull a real 3.1.0 tarball.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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