Skip to content

Commit 649a429

Browse files
bokelleyclaude
andcommitted
ci(storyboard): also vendor v1-canonical-mapping schema fixture
The AAO reference-formats fix in the previous commit cleared one of the two missing-fixture errors and unmasked a second one of the same shape: v1-canonical-mapping.json not found. Looked in: .../node_modules/@adcp/sdk/schemas/cache/3.1.0-beta.2/registries/v1-canonical-mapping.json .../node_modules/@adcp/sdk/schemas/cache/3.1.0-beta.1/registries/v1-canonical-mapping.json .../node_modules/@adcp/sdk/schemas/cache/3.1.0-beta.0/registries/v1-canonical-mapping.json .../node_modules/@adcp/sdk/schemas/cache/latest/registries/v1-canonical-mapping.json Run `npm run sync-schemas` for a 3.1+ AdCP version. Same root cause: ``@adcp/sdk`` published an npm tarball that omits the v1-canonical-mapping schema cache for 3.1.0-beta.* versions. The storyboard runner's v1→v2 format projection walks this registry; missing the file causes a cascade of step failures (and incorrectly-routed error codes like ``PRODUCT_NOT_FOUND`` instead of ``TERMS_REJECTED`` because the canonical mapping is consulted before product lookup). Vendor ``v1-canonical-mapping.json`` from ``adcontextprotocol/adcp:dist/schemas/3.1.0-beta.2/registries/`` (14 KiB) into ``tests/fixtures/`` and drop it into the SDK install path the SDK looks up first (the SDK's lookup order falls back through .2 → .1 → .0 → latest, so vendoring .2 is sufficient). Same four storyboard jobs as the prior fix: - examples/seller_agent.py - examples/multi_platform_seller (PlatformRouter) - v3 reference seller (translator) - sales-proposal-mode (proposal_finalize) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c9ea04e commit 649a429

2 files changed

Lines changed: 170 additions & 21 deletions

File tree

.github/workflows/ci.yml

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -392,21 +392,24 @@ jobs:
392392
# surfaces protocol drift as soon as it ships, which is the
393393
# point of this job.
394394
#
395-
# Vendor the AAO reference-formats fixture into the SDK install:
396-
# ``@adcp/sdk`` does not ship ``aao-reference-formats.json`` in
397-
# its npm tarball (upstream adcontextprotocol/adcp#3307), so the
398-
# AAO catalog lookup fails for every storyboard step that touches
399-
# creative-format resolution. The SDK's own error message
400-
# recommends vendoring the file at this exact path. We keep a
401-
# copy under ``tests/fixtures/`` and drop it into the SDK's
402-
# expected location post-install; idempotent if upstream later
403-
# ships it.
395+
# Vendor missing fixtures into the SDK install:
396+
# ``@adcp/sdk`` does not ship two fixtures its storyboard runner
397+
# needs — ``aao-reference-formats.json`` (AAO format catalog,
398+
# upstream adcontextprotocol/adcp#3307) and
399+
# ``v1-canonical-mapping.json`` (v1→v2 format mapping registry
400+
# at schemas/cache/3.1.0-beta.2/registries/). The SDK's own
401+
# error messages recommend vendoring both at the exact paths
402+
# below. We keep copies under ``tests/fixtures/`` and drop them
403+
# into the SDK's expected locations post-install; idempotent if
404+
# upstream later ships them in the npm tarball.
404405
run: |
405406
npm install -g @adcp/sdk@latest
406407
adcp --version
407-
SDK_FIXTURE_DIR="$(npm root -g)/@adcp/sdk/test/lib/v2-projection-fixtures"
408-
mkdir -p "${SDK_FIXTURE_DIR}"
409-
cp tests/fixtures/aao-reference-formats.json "${SDK_FIXTURE_DIR}/aao-reference-formats.json"
408+
SDK_ROOT="$(npm root -g)/@adcp/sdk"
409+
mkdir -p "${SDK_ROOT}/test/lib/v2-projection-fixtures"
410+
cp tests/fixtures/aao-reference-formats.json "${SDK_ROOT}/test/lib/v2-projection-fixtures/aao-reference-formats.json"
411+
mkdir -p "${SDK_ROOT}/schemas/cache/3.1.0-beta.2/registries"
412+
cp tests/fixtures/v1-canonical-mapping.json "${SDK_ROOT}/schemas/cache/3.1.0-beta.2/registries/v1-canonical-mapping.json"
410413
411414
- name: Install dependencies
412415
run: |
@@ -566,9 +569,11 @@ jobs:
566569
run: |
567570
npm install -g @adcp/sdk@latest
568571
adcp --version
569-
SDK_FIXTURE_DIR="$(npm root -g)/@adcp/sdk/test/lib/v2-projection-fixtures"
570-
mkdir -p "${SDK_FIXTURE_DIR}"
571-
cp tests/fixtures/aao-reference-formats.json "${SDK_FIXTURE_DIR}/aao-reference-formats.json"
572+
SDK_ROOT="$(npm root -g)/@adcp/sdk"
573+
mkdir -p "${SDK_ROOT}/test/lib/v2-projection-fixtures"
574+
cp tests/fixtures/aao-reference-formats.json "${SDK_ROOT}/test/lib/v2-projection-fixtures/aao-reference-formats.json"
575+
mkdir -p "${SDK_ROOT}/schemas/cache/3.1.0-beta.2/registries"
576+
cp tests/fixtures/v1-canonical-mapping.json "${SDK_ROOT}/schemas/cache/3.1.0-beta.2/registries/v1-canonical-mapping.json"
572577
573578
- name: Start JS mock-server upstream
574579
run: |
@@ -780,9 +785,11 @@ jobs:
780785
run: |
781786
npm install -g @adcp/sdk@latest
782787
adcp --version
783-
SDK_FIXTURE_DIR="$(npm root -g)/@adcp/sdk/test/lib/v2-projection-fixtures"
784-
mkdir -p "${SDK_FIXTURE_DIR}"
785-
cp tests/fixtures/aao-reference-formats.json "${SDK_FIXTURE_DIR}/aao-reference-formats.json"
788+
SDK_ROOT="$(npm root -g)/@adcp/sdk"
789+
mkdir -p "${SDK_ROOT}/test/lib/v2-projection-fixtures"
790+
cp tests/fixtures/aao-reference-formats.json "${SDK_ROOT}/test/lib/v2-projection-fixtures/aao-reference-formats.json"
791+
mkdir -p "${SDK_ROOT}/schemas/cache/3.1.0-beta.2/registries"
792+
cp tests/fixtures/v1-canonical-mapping.json "${SDK_ROOT}/schemas/cache/3.1.0-beta.2/registries/v1-canonical-mapping.json"
786793
787794
- name: Install dependencies
788795
run: |
@@ -886,9 +893,11 @@ jobs:
886893
run: |
887894
npm install -g @adcp/sdk@latest
888895
adcp --version
889-
SDK_FIXTURE_DIR="$(npm root -g)/@adcp/sdk/test/lib/v2-projection-fixtures"
890-
mkdir -p "${SDK_FIXTURE_DIR}"
891-
cp tests/fixtures/aao-reference-formats.json "${SDK_FIXTURE_DIR}/aao-reference-formats.json"
896+
SDK_ROOT="$(npm root -g)/@adcp/sdk"
897+
mkdir -p "${SDK_ROOT}/test/lib/v2-projection-fixtures"
898+
cp tests/fixtures/aao-reference-formats.json "${SDK_ROOT}/test/lib/v2-projection-fixtures/aao-reference-formats.json"
899+
mkdir -p "${SDK_ROOT}/schemas/cache/3.1.0-beta.2/registries"
900+
cp tests/fixtures/v1-canonical-mapping.json "${SDK_ROOT}/schemas/cache/3.1.0-beta.2/registries/v1-canonical-mapping.json"
892901
893902
- name: Install dependencies
894903
run: |
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"$id": "/schemas/3.1.0-beta.2/registries/v1-canonical-mapping.json",
4+
"title": "v1 → v2 Canonical Format Mapping Registry",
5+
"description": "Authoritative AAO-published mapping from v1 named formats to v2 canonical declarations. Used by SDKs to project the v1 wire shape into v2 canonical declarations during the migration window.\n\n**Direction of truth (normative).** This registry is authoritative for **v1 → v2 projection only**. v2 → v1 projection MUST rely on `v1_format_ref` on the v2 `ProductFormatDeclaration` — sellers assert the v1 pairing explicitly. SDKs MUST NOT synthesize a v1 `format_id` from the registry by inverting structural matches: the registry's `id` slugs lack the `agent_url` half of a `FormatId`, and registry entries with `format_id_glob: '*'` or pure-structural matches have no single literal to invert to. SDKs that synthesize unilaterally produce inter-SDK divergence on structurally-equal values (different SDKs pick different `agent_url` and `id` patterns for the same v2 declaration). When a v2 declaration carries no `v1_format_ref` and a v1-only buyer queries the product, the SDK reports the canonical as v1-unreachable via `FORMAT_DECLARATION_V1_AMBIGUOUS` and lets the buyer surface it to the seller; the seller's path is to add `v1_format_ref` to disambiguate.\n\nBest-effort inversion is not available from this registry: as of 3.1 all published entries are pure-structural (family-level), and structural matches are not invertible to a specific v1 `format_id`. Future versions MAY add literal `format_id_glob` entries for platform-specific v1 conventions; in that case SDKs MAY do best-effort inversion for non-wildcarded literals only, AND only when the seller hasn't authored a contradicting `v1_format_ref` — even then, the projection is non-normative and downstream consumers MUST NOT depend on it.\n\n**Resolution order** (per RFC #3305 amendment #3767, normative):\n1. **Authoritative v2→v1 link**: if any v2 `ProductFormatDeclaration` on the same product carries `v1_format_ref` pointing at this v1 format_id, use that v2 declaration. Highest priority — seller asserts the link directly. SDKs SHOULD run the *narrows* check (canonical-formats.mdx 'Narrows — formal definition') between the v2 declaration's `params` and the referenced v1 format's `requirements`; on conflict, surface `FORMAT_DECLARATION_DIVERGENT` on the `get_products` response `errors[]`. Without the narrowing check, `v1_format_ref` is a hint rather than a contract.\n2. **Seller-asserted on the v1 file**: if the v1 format declaration carries an explicit `canonical` field, use it. (Note: `canonical_parameters` on the v1 file is deprecated for 3.1; SDKs reading 3.1 catalogs MUST still honor it when present, but `v1_format_ref` is the path forward.)\n3. **Registry glob**: look up `format_id` in this registry's `format_id_glob` entries.\n4. **Structural match**: attempt structural match against this registry's `structural` entries. A successful structural match yields a *family-level* identification only (e.g., 'this is a `video_vast`') — it does NOT yield a specific v1 `format_id`. SDKs use structural match for v1 → v2 projection (the inbound direction) but MUST NOT invert it to a v1 `format_id` on the outbound path.\n5. **Ambiguous family**: if step 4 succeeded but the matched entries are family-only (pure structural, no invertible literal), the canonical is **v1-unreachable for THIS specific product** unless the seller authors `v1_format_ref`. SDKs MUST surface `FORMAT_DECLARATION_V1_AMBIGUOUS` via `errors[]` augmentation rather than synthesize a plausible-but-arbitrary v1 `format_id`. Distinct from canonical-level v1-unreachability (a canonical with `v1_translatable: false` — `agent_placement`, `sponsored_placement`, `responsive_creative`, `image_carousel` — never has any v1 form regardless of registry coverage).\n6. **Fail closed**: SDK MUST NOT emit `format_options` for products carrying this format. SDKs MUST augment the response's `errors[]` array with an entry carrying `source: \"sdk\"`, `sdk_id: \"<package>@<version>\"`, `code: \"FORMAT_PROJECTION_FAILED\"`, `field: \"products[N].format_ids[K]\"`, and `error.details: { format_id, product_id, resolution_failure: \"no_explicit_canonical\" | \"no_registry_match\" | \"no_structural_match\" }`. Single mandated surface (`errors[]` augmentation) — lint-output channels are NOT acceptable; the multi-hop agent network needs warnings to propagate across SDK boundaries via the wire response. Logger-only warnings die in DEBUG. The advisory is non-fatal: the response stays 200/success, the product is still valid on the v1 path, only the v2 `format_options` projection is absent. Consumer-side counterpart to the producer SHOULD (sellers should add a v2 declaration with `v1_format_ref`, an explicit `canonical` field, or file a registry PR).\n\n**Match modes:**\n- `format_id_glob` — exact / glob match against the v1 `format_id.id`. **As of 3.1 the registry carries zero literal entries**: AAO-catalog-published formats (display_300x250_image, video_vast_30s, audio_standard_30s, etc.) project via resolution-order step 2 (catalog entry's `canonical:` annotation), and platform-specific formats (e.g., Meta Reels, TikTok Spark Ads) project via structural fallback or via the platform's own adagents.json `formats[]` block (#4620). A future literal entry is only justified when (a) the v1 name carries semantic narrowing not recoverable from slot shape AND (b) no platform-published adagents.json exists; such entries land via the AAO governance PR process with a documented rationale. The whole point of canonical-formats is parametrization: ONE `image` canonical with width/height params, not 8 per-size variants. Glob syntax: `*` matches any segment.\n- `structural` — match against the format's slot shape, asset types, and version constraints. The PRIMARY fallback for v1 wire traffic — catches custom v1 formats (a publisher's `acme_homepage_300x250` is structurally an IAB MREC) without enumerating every possible v1 name. v1 sellers in the wild naming things their own way are handled here, not by literal globs.\n\n**Alias collision precedence (normative).** When a v1 format's `assets[i]` carries multiple `asset_group_id` aliases that resolve to the same canonical asset_group (e.g., two slots both aliasing to `landing_page_url`), the SDK MUST resolve deterministically: the v1 format's `assets[*]` array order is authoritative — the first slot in declaration order wins, subsequent collisions are dropped from the projected v2 manifest and surfaced via `FORMAT_PROJECTION_FAILED` with `error.details: { collision_kind: \"asset_group_id_alias\", asset_group_id, winning_slot_id, dropped_slot_ids }`. SDKs MUST NOT silently pick one and discard the other without surfacing — silent picking creates inter-SDK divergence. Producers SHOULD avoid the collision by deduplicating aliased slots or using distinct `asset_group_id` values when both slots are semantically meaningful.\n\n**Governance**: same vocabulary-governance rules as `asset-group-vocabulary.json` and `format-shape-vocabulary.json` — additions land via PR with rationale + ≥1 reference adopter; AAO maintainer review; versioned + content-digested. Entries are additive; once published they are not removed (they may be marked `deprecated: true` if superseded).\n\n**Initial scope (3.1)**: 7 pure-structural fallback entries covering VAST 4.x / legacy VAST, DAAST 1.x, HTML5 zip bundles, hosted video, hosted audio, and url-shaped display tags. Per-size and per-duration literals (display_300x250_image, video_vast_30s, etc.) are NOT enumerated here — those project via catalog `canonical:` annotation (resolution-order step 2), keeping the registry aligned with the canonical-formats parametrization principle. Platform-specific formats (Meta Reels, TikTok Spark Ads, etc.) project via structural fallback or via the platform's own adagents.json `formats[]` block (#4620). The full v1-format audit dataset (~76% of formats from the 12-platform / 86-format audit in #3305) seeds the long-term roadmap and informs future literal-entry decisions.\n\nDigest the file content (sha256) when emitting in capabilities responses or referencing from SDK output. Buyers cache by `version` + `digest`.",
6+
"version": "1.0.0",
7+
"last_updated": "2026-05-01",
8+
"type": "object",
9+
"required": ["version", "mappings"],
10+
"properties": {
11+
"version": {
12+
"type": "string",
13+
"description": "Semver of this registry. Bumped on every published change."
14+
},
15+
"last_updated": {
16+
"type": "string",
17+
"format": "date",
18+
"description": "ISO date of the last published change."
19+
},
20+
"mappings": {
21+
"type": "array",
22+
"description": "Ordered list of v1 → v2 mappings. SDKs apply mappings in order and use the first match.",
23+
"items": {
24+
"type": "object",
25+
"required": ["v1_pattern", "v2"],
26+
"properties": {
27+
"v1_pattern": {
28+
"type": "object",
29+
"description": "Match pattern. Carries either format_id_glob OR structural, not both.",
30+
"oneOf": [
31+
{
32+
"required": ["format_id_glob"],
33+
"properties": {
34+
"format_id_glob": {
35+
"type": "string",
36+
"description": "Glob pattern matched against v1 format_id.id. Examples: 'iab_mrec_300x250', 'iab_leaderboard_*', 'meta_*_reels'."
37+
}
38+
}
39+
},
40+
{
41+
"required": ["structural"],
42+
"properties": {
43+
"structural": {
44+
"type": "object",
45+
"description": "Structural match against the format's slot shape, asset types, and version constraints.",
46+
"properties": {
47+
"asset_types": {
48+
"type": "array",
49+
"items": { "type": "string" },
50+
"description": "Set of asset_type values that must appear in the format's slots (in any order, any count)."
51+
},
52+
"vast_versions": {
53+
"type": "array",
54+
"items": { "type": "string" },
55+
"description": "VAST version constraints. Strings like '>=4.0', '4.x', '4.2'."
56+
},
57+
"daast_versions": {
58+
"type": "array",
59+
"items": { "type": "string" }
60+
},
61+
"dimensions": {
62+
"type": "object",
63+
"properties": {
64+
"width": { "type": "integer" },
65+
"height": { "type": "integer" }
66+
}
67+
}
68+
},
69+
"additionalProperties": true
70+
}
71+
}
72+
}
73+
]
74+
},
75+
"v2": {
76+
"type": "object",
77+
"required": ["canonical"],
78+
"properties": {
79+
"canonical": {
80+
"$ref": "/schemas/3.1.0-beta.2/core/canonical-format-kind.json",
81+
"description": "v2 canonical format the v1 pattern projects to."
82+
},
83+
"parameters": {
84+
"type": "object",
85+
"description": "Optional parameters that narrow the canonical (e.g., width/height, vast_version). When present, become the params on the projected v2 ProductFormatDeclaration. The shape MUST be valid params for the named canonical.",
86+
"additionalProperties": true
87+
}
88+
}
89+
},
90+
"deprecated": {
91+
"type": "boolean",
92+
"default": false,
93+
"description": "When true, this mapping is retained for backward-compatibility but should not be used for new mappings. SDKs SHOULD emit lint warnings when matching a deprecated entry."
94+
},
95+
"notes": {
96+
"type": "string",
97+
"description": "Optional human-readable explanation, examples, or rationale."
98+
}
99+
}
100+
}
101+
}
102+
},
103+
"mappings": [
104+
{
105+
"v1_pattern": { "structural": { "asset_types": ["vast"], "vast_versions": [">=4.0"] } },
106+
"v2": { "canonical": "video_vast", "parameters": { "vast_version": "4.2" } },
107+
"notes": "Any v1 format whose primary asset is a VAST 4.x tag. SDKs should narrow the parameters.vast_version to the lowest common denominator they support."
108+
},
109+
{
110+
"v1_pattern": { "structural": { "asset_types": ["vast"], "vast_versions": ["3.x", "2.x"] } },
111+
"v2": { "canonical": "video_vast", "parameters": { "vast_version": "3.0" } },
112+
"notes": "Legacy VAST 2.x/3.x — projected as video_vast with the lowest-supported version."
113+
},
114+
{
115+
"v1_pattern": { "structural": { "asset_types": ["daast"], "daast_versions": ["1.0", "1.1"] } },
116+
"v2": { "canonical": "audio_daast", "parameters": { "daast_version": "1.1" } },
117+
"notes": "DAAST 1.x audio tag → audio_daast canonical."
118+
},
119+
{
120+
"v1_pattern": { "structural": { "asset_types": ["zip"] } },
121+
"v2": { "canonical": "html5" },
122+
"notes": "Any v1 format whose primary asset is a zip bundle (HTML5 banner). Caller must add platform_extensions for OM-SDK / clickTag specifics from the v1 declaration."
123+
},
124+
{
125+
"v1_pattern": { "structural": { "asset_types": ["video"] } },
126+
"v2": { "canonical": "video_hosted" },
127+
"notes": "v1 format with a hosted video file as its primary asset → video_hosted. Caller infers orientation/dimensions from the v1 slot constraints."
128+
},
129+
{
130+
"v1_pattern": { "structural": { "asset_types": ["audio"] } },
131+
"v2": { "canonical": "audio_hosted" },
132+
"notes": "v1 format with a hosted audio file as its primary asset → audio_hosted. Distinct from audio_daast (which uses tag delivery)."
133+
},
134+
{
135+
"v1_pattern": { "structural": { "asset_types": ["url"] } },
136+
"v2": { "canonical": "display_tag" },
137+
"notes": "Last-resort structural match: v1 format whose primary asset is a URL pointing at a third-party-served creative → display_tag canonical. Lower confidence than other entries — sellers SHOULD declare an explicit `canonical` for url-shaped formats whenever possible."
138+
}
139+
]
140+
}

0 commit comments

Comments
 (0)