diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 441cd083..ef64638d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -280,10 +280,50 @@ jobs: if: steps.version-check.outputs.is_prerelease != 'true' run: python scripts/fix_schema_refs.py + - name: Strict drift check — schemas/cache/ byte-equality + if: steps.version-check.outputs.is_prerelease != 'true' + run: | + # Compares committed cache against the post-pipeline state + # (sync_schemas.py + fix_schema_refs.py). The repo convention + # — solidified at the 3.0.5 sync (008fa3c8) — is to commit + # the post-fix form: relative $refs (../core/...) and stripped + # $id fields. The runtime schema_loader's RefResolver depends + # on this form: absolute refs like /schemas/3.0.7/core/x.json + # don't resolve against a file:// base_uri, breaking the + # storyboard runner. + # + # PR #429-style hand-edits and source-of-truth confusion + # (cache built from static/schemas/source/, next major's WIP) + # show up here as a real diff. The bundle + fix_schema_refs + # output is byte-stable, so this check has no false-positive + # surface. + if ! git diff --exit-code -- schemas/cache/; then + echo "::error::schemas/cache/ differs from the post-pipeline bundle for ADCP_VERSION=$(cat src/adcp/ADCP_VERSION)." + echo "Likely causes:" + echo " - locally synced but didn't run scripts/fix_schema_refs.py before committing" + echo " - hand-edits to schemas/cache/ files (the bundle is the source of truth)" + echo " - cache built from static/schemas/source/ (next major's WIP) instead of dist" + echo " - committed cache predates the pinned ADCP_VERSION" + echo "Fix: run 'python scripts/sync_schemas.py && python scripts/fix_schema_refs.py' locally and commit the result." + exit 1 + fi + echo "✓ schemas/cache/ matches post-pipeline bundle" + - name: Bundle schemas into package if: steps.version-check.outputs.is_prerelease != 'true' run: python scripts/bundle_schemas.py + - name: Snapshot committed types (for drift check) + if: steps.version-check.outputs.is_prerelease != 'true' + run: | + # Capture the field/enum-member shape of the committed tree + # *before* generate_types overwrites it, so the strict drift + # check after regen can compare regenerated output against + # what's checked in. + python scripts/diff_generated_types.py snapshot \ + src/adcp/types/generated_poc/ \ + /tmp/committed_types_snapshot.json + - name: Generate models if: steps.version-check.outputs.is_prerelease != 'true' run: python scripts/generate_types.py @@ -304,38 +344,22 @@ jobs: echo "Running code generation test suite..." pytest tests/test_code_generation.py -v --tb=short - - name: Check for schema drift + - name: Strict drift check — generated_poc/ field signatures if: steps.version-check.outputs.is_prerelease != 'true' run: | - # datamodel-codegen's numbered-variant class names - # (Pass1/Pass4, Status16/Status17, StatusFilter1/StatusFilter4, - # Type80, etc.) shift between regens because the generator - # walks the schema graph in filesystem-iteration order and - # APFS (macOS) vs. ext4 (Linux CI) sort differently. The - # numbers are an implementation detail; semantic aliases in - # ``src/adcp/types/aliases.py`` pin the names downstream - # actually uses. - # - # The real drift guarantees we need are enforced elsewhere: - # * ``tests/test_schemas_version_pin.py`` — ADCP_VERSION - # matches ``schemas/cache/index.json.adcp_version`` on - # every test run. - # * This job's "Validate generated code syntax/imports" - # steps above — the regenerated code compiles and imports. - # * ``tests/test_asset_aliases_stable.py`` — the semantic - # aliases still point at valid classes. - # - # We keep this step as a "regen runs without error on stable - # tags" smoke — but don't fail on line-level diff, because - # the non-determinism produces false positives that block - # release PRs for cosmetic churn. - if git diff --quiet src/adcp/types/_generated.py schemas/cache/; then - echo "✓ Schemas are up-to-date (no diff)" - else - echo "ℹ Regen produced cosmetic diff — see aliases.py for stable names" - echo " Numbered-variant class-name churn is expected; the semantic" - echo " alias tests and drift-version-pin test guard the real surface." - fi + # Compares regenerated tree against the committed snapshot + # captured before `generate_types.py` ran. The signature is + # the multiset of frozensets of field names per file, so + # `PackageUpdate1` vs `PackageUpdate4` (datamodel-codegen's + # filesystem-order-dependent variant numbering) is invisible + # — only real semantic changes (added/removed field, added/ + # removed class, added/removed enum member) cause failure. + # Hand-edits like 1a6ab9a1 ("rename format_ to format in + # FieldModel enum"), and forward-state leaks like PR #429, + # both surface here. + python scripts/diff_generated_types.py check \ + /tmp/committed_types_snapshot.json \ + src/adcp/types/generated_poc/ storyboard: name: AdCP storyboard runner — examples/seller_agent.py diff --git a/SCHEMA_DELTAS.md b/SCHEMA_DELTAS.md new file mode 100644 index 00000000..d3ef8d1b --- /dev/null +++ b/SCHEMA_DELTAS.md @@ -0,0 +1,6 @@ +# Generated-types delta + +## Field changes + +- `extensions/extension_meta.py` + - `AdcpExtensionFileSchema`: `-field_id` diff --git a/schemas/cache/adagents.json b/schemas/cache/adagents.json index fdc82c08..f942c2a6 100644 --- a/schemas/cache/adagents.json +++ b/schemas/cache/adagents.json @@ -785,12 +785,12 @@ ], "examples": [ { - "$schema": "/schemas/3.0.5/adagents.json", + "$schema": "/schemas/3.0.7/adagents.json", "authoritative_location": "https://cdn.example.com/adagents/v2/adagents.json", "last_updated": "2025-01-15T10:00:00Z" }, { - "$schema": "/schemas/3.0.5/adagents.json", + "$schema": "/schemas/3.0.7/adagents.json", "properties": [ { "property_id": "example_site", @@ -868,7 +868,7 @@ "last_updated": "2025-01-10T12:00:00Z" }, { - "$schema": "/schemas/3.0.5/adagents.json", + "$schema": "/schemas/3.0.7/adagents.json", "contact": { "name": "Meta Advertising Operations", "email": "adops@meta.com", @@ -977,7 +977,7 @@ "last_updated": "2025-01-10T15:30:00Z" }, { - "$schema": "/schemas/3.0.5/adagents.json", + "$schema": "/schemas/3.0.7/adagents.json", "contact": { "name": "Tumblr Advertising" }, @@ -1016,7 +1016,7 @@ "last_updated": "2025-01-10T16:00:00Z" }, { - "$schema": "/schemas/3.0.5/adagents.json", + "$schema": "/schemas/3.0.7/adagents.json", "contact": { "name": "Example Third-Party Sales Agent", "email": "sales@agent.example", @@ -1062,7 +1062,7 @@ "last_updated": "2025-01-10T17:00:00Z" }, { - "$schema": "/schemas/3.0.5/adagents.json", + "$schema": "/schemas/3.0.7/adagents.json", "contact": { "name": "Premium News Publisher", "email": "adops@news.example.com", @@ -1137,7 +1137,7 @@ "last_updated": "2025-01-10T18:00:00Z" }, { - "$schema": "/schemas/3.0.5/adagents.json", + "$schema": "/schemas/3.0.7/adagents.json", "contact": { "name": "Polk Automotive Data", "email": "partnerships@polk.com", diff --git a/schemas/cache/brand.json b/schemas/cache/brand.json index 12a9882c..9f22df60 100644 --- a/schemas/cache/brand.json +++ b/schemas/cache/brand.json @@ -2032,16 +2032,16 @@ ], "examples": [ { - "$schema": "/schemas/3.0.5/brand.json", + "$schema": "/schemas/3.0.7/brand.json", "authoritative_location": "https://adcontextprotocol.org/brand/abc123/brand.json" }, { - "$schema": "/schemas/3.0.5/brand.json", + "$schema": "/schemas/3.0.7/brand.json", "house": "nikeinc.com", "note": "Redirect to house domain for full brand portfolio" }, { - "$schema": "/schemas/3.0.5/brand.json", + "$schema": "/schemas/3.0.7/brand.json", "version": "1.0", "agents": [ { @@ -2052,7 +2052,7 @@ ] }, { - "$schema": "/schemas/3.0.5/brand.json", + "$schema": "/schemas/3.0.7/brand.json", "version": "1.0", "house": { "domain": "pg.com", @@ -2358,7 +2358,7 @@ "last_updated": "2026-01-15T10:00:00Z" }, { - "$schema": "/schemas/3.0.5/brand.json", + "$schema": "/schemas/3.0.7/brand.json", "version": "1.0", "house": { "domain": "nikeinc.com", @@ -2527,7 +2527,7 @@ "last_updated": "2026-01-15T10:00:00Z" }, { - "$schema": "/schemas/3.0.5/brand.json", + "$schema": "/schemas/3.0.7/brand.json", "version": "1.0", "house": { "domain": "mediavine.com", diff --git a/schemas/cache/bundled/content-standards/calibrate-content-request.json b/schemas/cache/bundled/content-standards/calibrate-content-request.json index c40d40ea..f58977f9 100644 --- a/schemas/cache/bundled/content-standards/calibrate-content-request.json +++ b/schemas/cache/bundled/content-standards/calibrate-content-request.json @@ -1705,7 +1705,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.265Z", + "generatedAt": "2026-05-08T11:43:56.422Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/content-standards/calibrate-content-response.json b/schemas/cache/bundled/content-standards/calibrate-content-response.json index 09c4ab9c..8d8fdec5 100644 --- a/schemas/cache/bundled/content-standards/calibrate-content-response.json +++ b/schemas/cache/bundled/content-standards/calibrate-content-response.json @@ -204,7 +204,7 @@ } ], "_bundled": { - "generatedAt": "2026-05-02T21:51:30.266Z", + "generatedAt": "2026-05-08T11:43:56.423Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/content-standards/create-content-standards-request.json b/schemas/cache/bundled/content-standards/create-content-standards-request.json index 95c3bf51..ad603087 100644 --- a/schemas/cache/bundled/content-standards/create-content-standards-request.json +++ b/schemas/cache/bundled/content-standards/create-content-standards-request.json @@ -3615,7 +3615,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.272Z", + "generatedAt": "2026-05-08T11:43:56.429Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/content-standards/create-content-standards-response.json b/schemas/cache/bundled/content-standards/create-content-standards-response.json index 63d1e8ef..2bd9f51c 100644 --- a/schemas/cache/bundled/content-standards/create-content-standards-response.json +++ b/schemas/cache/bundled/content-standards/create-content-standards-response.json @@ -140,7 +140,7 @@ } ], "_bundled": { - "generatedAt": "2026-05-02T21:51:30.275Z", + "generatedAt": "2026-05-08T11:43:56.432Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/content-standards/get-content-standards-request.json b/schemas/cache/bundled/content-standards/get-content-standards-request.json index 834f5bc3..f1cf7b92 100644 --- a/schemas/cache/bundled/content-standards/get-content-standards-request.json +++ b/schemas/cache/bundled/content-standards/get-content-standards-request.json @@ -31,7 +31,7 @@ "standards_id" ], "_bundled": { - "generatedAt": "2026-05-02T21:51:30.275Z", + "generatedAt": "2026-05-08T11:43:56.432Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/content-standards/get-content-standards-response.json b/schemas/cache/bundled/content-standards/get-content-standards-response.json index 9e7beea3..754b92cd 100644 --- a/schemas/cache/bundled/content-standards/get-content-standards-response.json +++ b/schemas/cache/bundled/content-standards/get-content-standards-response.json @@ -3875,7 +3875,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.283Z", + "generatedAt": "2026-05-08T11:43:56.439Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/content-standards/get-media-buy-artifacts-request.json b/schemas/cache/bundled/content-standards/get-media-buy-artifacts-request.json index 74739518..63819602 100644 --- a/schemas/cache/bundled/content-standards/get-media-buy-artifacts-request.json +++ b/schemas/cache/bundled/content-standards/get-media-buy-artifacts-request.json @@ -227,7 +227,7 @@ "media_buy_id" ], "_bundled": { - "generatedAt": "2026-05-02T21:51:30.286Z", + "generatedAt": "2026-05-08T11:43:56.441Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/content-standards/get-media-buy-artifacts-response.json b/schemas/cache/bundled/content-standards/get-media-buy-artifacts-response.json index 6ee44654..2f49712a 100644 --- a/schemas/cache/bundled/content-standards/get-media-buy-artifacts-response.json +++ b/schemas/cache/bundled/content-standards/get-media-buy-artifacts-response.json @@ -1904,7 +1904,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.289Z", + "generatedAt": "2026-05-08T11:43:56.444Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/content-standards/list-content-standards-request.json b/schemas/cache/bundled/content-standards/list-content-standards-request.json index 72c749b8..33ce1c68 100644 --- a/schemas/cache/bundled/content-standards/list-content-standards-request.json +++ b/schemas/cache/bundled/content-standards/list-content-standards-request.json @@ -114,7 +114,7 @@ }, "additionalProperties": true, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.290Z", + "generatedAt": "2026-05-08T11:43:56.445Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/content-standards/list-content-standards-response.json b/schemas/cache/bundled/content-standards/list-content-standards-response.json index e1e2316a..bf5ca361 100644 --- a/schemas/cache/bundled/content-standards/list-content-standards-response.json +++ b/schemas/cache/bundled/content-standards/list-content-standards-response.json @@ -3904,7 +3904,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.295Z", + "generatedAt": "2026-05-08T11:43:56.453Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/content-standards/update-content-standards-request.json b/schemas/cache/bundled/content-standards/update-content-standards-request.json index 52c5565d..014b6766 100644 --- a/schemas/cache/bundled/content-standards/update-content-standards-request.json +++ b/schemas/cache/bundled/content-standards/update-content-standards-request.json @@ -3604,7 +3604,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.302Z", + "generatedAt": "2026-05-08T11:43:56.458Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/content-standards/update-content-standards-response.json b/schemas/cache/bundled/content-standards/update-content-standards-response.json index 594bed02..fda4dfc9 100644 --- a/schemas/cache/bundled/content-standards/update-content-standards-response.json +++ b/schemas/cache/bundled/content-standards/update-content-standards-response.json @@ -155,7 +155,7 @@ } ], "_bundled": { - "generatedAt": "2026-05-02T21:51:30.303Z", + "generatedAt": "2026-05-08T11:43:56.459Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/content-standards/validate-content-delivery-request.json b/schemas/cache/bundled/content-standards/validate-content-delivery-request.json index 0013df4c..82434c81 100644 --- a/schemas/cache/bundled/content-standards/validate-content-delivery-request.json +++ b/schemas/cache/bundled/content-standards/validate-content-delivery-request.json @@ -1759,7 +1759,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.305Z", + "generatedAt": "2026-05-08T11:43:56.461Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/content-standards/validate-content-delivery-response.json b/schemas/cache/bundled/content-standards/validate-content-delivery-response.json index aca766a5..c4517691 100644 --- a/schemas/cache/bundled/content-standards/validate-content-delivery-response.json +++ b/schemas/cache/bundled/content-standards/validate-content-delivery-response.json @@ -232,7 +232,7 @@ } ], "_bundled": { - "generatedAt": "2026-05-02T21:51:30.305Z", + "generatedAt": "2026-05-08T11:43:56.461Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/core/tasks-get-request.json b/schemas/cache/bundled/core/tasks-get-request.json index 3feae020..9dac183d 100644 --- a/schemas/cache/bundled/core/tasks-get-request.json +++ b/schemas/cache/bundled/core/tasks-get-request.json @@ -53,7 +53,7 @@ } ], "_bundled": { - "generatedAt": "2026-05-02T21:51:30.306Z", + "generatedAt": "2026-05-08T11:43:56.462Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/core/tasks-get-response.json b/schemas/cache/bundled/core/tasks-get-response.json index d9ceaf59..86e37c9d 100644 --- a/schemas/cache/bundled/core/tasks-get-response.json +++ b/schemas/cache/bundled/core/tasks-get-response.json @@ -257,7 +257,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.307Z", + "generatedAt": "2026-05-08T11:43:56.463Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/core/tasks-list-request.json b/schemas/cache/bundled/core/tasks-list-request.json index 81e12884..7d3b242c 100644 --- a/schemas/cache/bundled/core/tasks-list-request.json +++ b/schemas/cache/bundled/core/tasks-list-request.json @@ -311,7 +311,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.307Z", + "generatedAt": "2026-05-08T11:43:56.463Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/core/tasks-list-response.json b/schemas/cache/bundled/core/tasks-list-response.json index 22413c8b..3b199c34 100644 --- a/schemas/cache/bundled/core/tasks-list-response.json +++ b/schemas/cache/bundled/core/tasks-list-response.json @@ -251,7 +251,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.308Z", + "generatedAt": "2026-05-08T11:43:56.464Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/creative/get-creative-delivery-request.json b/schemas/cache/bundled/creative/get-creative-delivery-request.json index bb59a696..4fb55a5c 100644 --- a/schemas/cache/bundled/creative/get-creative-delivery-request.json +++ b/schemas/cache/bundled/creative/get-creative-delivery-request.json @@ -238,7 +238,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.308Z", + "generatedAt": "2026-05-08T11:43:56.465Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/creative/get-creative-delivery-response.json b/schemas/cache/bundled/creative/get-creative-delivery-response.json index 95c96710..81bfb57c 100644 --- a/schemas/cache/bundled/creative/get-creative-delivery-response.json +++ b/schemas/cache/bundled/creative/get-creative-delivery-response.json @@ -5994,7 +5994,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.321Z", + "generatedAt": "2026-05-08T11:43:56.475Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/creative/get-creative-features-request.json b/schemas/cache/bundled/creative/get-creative-features-request.json index 938eac2c..4ca627d4 100644 --- a/schemas/cache/bundled/creative/get-creative-features-request.json +++ b/schemas/cache/bundled/creative/get-creative-features-request.json @@ -5087,7 +5087,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.331Z", + "generatedAt": "2026-05-08T11:43:56.484Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/creative/get-creative-features-response.json b/schemas/cache/bundled/creative/get-creative-features-response.json index 8f02aed4..27976874 100644 --- a/schemas/cache/bundled/creative/get-creative-features-response.json +++ b/schemas/cache/bundled/creative/get-creative-features-response.json @@ -254,7 +254,7 @@ } ], "_bundled": { - "generatedAt": "2026-05-02T21:51:30.334Z", + "generatedAt": "2026-05-08T11:43:56.487Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/creative/list-creative-formats-request.json b/schemas/cache/bundled/creative/list-creative-formats-request.json index 602244be..5989a37f 100644 --- a/schemas/cache/bundled/creative/list-creative-formats-request.json +++ b/schemas/cache/bundled/creative/list-creative-formats-request.json @@ -478,7 +478,7 @@ } ], "_bundled": { - "generatedAt": "2026-05-02T21:51:30.334Z", + "generatedAt": "2026-05-08T11:43:56.489Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/creative/list-creative-formats-response.json b/schemas/cache/bundled/creative/list-creative-formats-response.json index bd57acc2..b93827c5 100644 --- a/schemas/cache/bundled/creative/list-creative-formats-response.json +++ b/schemas/cache/bundled/creative/list-creative-formats-response.json @@ -4100,7 +4100,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.343Z", + "generatedAt": "2026-05-08T11:43:56.494Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/creative/list-creatives-request.json b/schemas/cache/bundled/creative/list-creatives-request.json index f2a1e42b..09163753 100644 --- a/schemas/cache/bundled/creative/list-creatives-request.json +++ b/schemas/cache/bundled/creative/list-creatives-request.json @@ -680,7 +680,7 @@ } ], "_bundled": { - "generatedAt": "2026-05-02T21:51:30.345Z", + "generatedAt": "2026-05-08T11:43:56.496Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/creative/list-creatives-response.json b/schemas/cache/bundled/creative/list-creatives-response.json index 0fc4f502..92ec660d 100644 --- a/schemas/cache/bundled/creative/list-creatives-response.json +++ b/schemas/cache/bundled/creative/list-creatives-response.json @@ -6004,7 +6004,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.353Z", + "generatedAt": "2026-05-08T11:43:56.504Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/creative/preview-creative-request.json b/schemas/cache/bundled/creative/preview-creative-request.json index 43f22260..e9ad405f 100644 --- a/schemas/cache/bundled/creative/preview-creative-request.json +++ b/schemas/cache/bundled/creative/preview-creative-request.json @@ -9623,7 +9623,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.368Z", + "generatedAt": "2026-05-08T11:43:56.514Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/creative/preview-creative-response.json b/schemas/cache/bundled/creative/preview-creative-response.json index eae5e66b..0519a252 100644 --- a/schemas/cache/bundled/creative/preview-creative-response.json +++ b/schemas/cache/bundled/creative/preview-creative-response.json @@ -5958,7 +5958,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.377Z", + "generatedAt": "2026-05-08T11:43:56.523Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/creative/sync-creatives-request.json b/schemas/cache/bundled/creative/sync-creatives-request.json index f680d08f..f2554b32 100644 --- a/schemas/cache/bundled/creative/sync-creatives-request.json +++ b/schemas/cache/bundled/creative/sync-creatives-request.json @@ -5264,7 +5264,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.385Z", + "generatedAt": "2026-05-08T11:43:56.530Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/creative/sync-creatives-response.json b/schemas/cache/bundled/creative/sync-creatives-response.json index 20674280..2ea6bb54 100644 --- a/schemas/cache/bundled/creative/sync-creatives-response.json +++ b/schemas/cache/bundled/creative/sync-creatives-response.json @@ -1232,7 +1232,7 @@ } ], "_bundled": { - "generatedAt": "2026-05-02T21:51:30.387Z", + "generatedAt": "2026-05-08T11:43:56.532Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/build-creative-request.json b/schemas/cache/bundled/media-buy/build-creative-request.json index 572b780a..23e27a62 100644 --- a/schemas/cache/bundled/media-buy/build-creative-request.json +++ b/schemas/cache/bundled/media-buy/build-creative-request.json @@ -5365,7 +5365,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.393Z", + "generatedAt": "2026-05-08T11:43:56.537Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/build-creative-response.json b/schemas/cache/bundled/media-buy/build-creative-response.json index 1a89ae5d..45d43d23 100644 --- a/schemas/cache/bundled/media-buy/build-creative-response.json +++ b/schemas/cache/bundled/media-buy/build-creative-response.json @@ -10378,7 +10378,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.405Z", + "generatedAt": "2026-05-08T11:43:56.547Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/create-media-buy-request.json b/schemas/cache/bundled/media-buy/create-media-buy-request.json index 03a0aece..d88999ac 100644 --- a/schemas/cache/bundled/media-buy/create-media-buy-request.json +++ b/schemas/cache/bundled/media-buy/create-media-buy-request.json @@ -7889,7 +7889,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.417Z", + "generatedAt": "2026-05-08T11:43:56.557Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/create-media-buy-response.json b/schemas/cache/bundled/media-buy/create-media-buy-response.json index f1914cc8..b4082719 100644 --- a/schemas/cache/bundled/media-buy/create-media-buy-response.json +++ b/schemas/cache/bundled/media-buy/create-media-buy-response.json @@ -4225,7 +4225,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.424Z", + "generatedAt": "2026-05-08T11:43:56.563Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/get-media-buy-delivery-request.json b/schemas/cache/bundled/media-buy/get-media-buy-delivery-request.json index 8998abf4..b7ac2c73 100644 --- a/schemas/cache/bundled/media-buy/get-media-buy-delivery-request.json +++ b/schemas/cache/bundled/media-buy/get-media-buy-delivery-request.json @@ -487,7 +487,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.426Z", + "generatedAt": "2026-05-08T11:43:56.565Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/get-media-buy-delivery-response.json b/schemas/cache/bundled/media-buy/get-media-buy-delivery-response.json index ebda428a..dcc9029d 100644 --- a/schemas/cache/bundled/media-buy/get-media-buy-delivery-response.json +++ b/schemas/cache/bundled/media-buy/get-media-buy-delivery-response.json @@ -4280,7 +4280,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.429Z", + "generatedAt": "2026-05-08T11:43:56.568Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/get-media-buys-request.json b/schemas/cache/bundled/media-buy/get-media-buys-request.json index 66ffb51f..668d4c26 100644 --- a/schemas/cache/bundled/media-buy/get-media-buys-request.json +++ b/schemas/cache/bundled/media-buy/get-media-buys-request.json @@ -254,7 +254,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.431Z", + "generatedAt": "2026-05-08T11:43:56.570Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/get-media-buys-response.json b/schemas/cache/bundled/media-buy/get-media-buys-response.json index 8af14543..aeb01f38 100644 --- a/schemas/cache/bundled/media-buy/get-media-buys-response.json +++ b/schemas/cache/bundled/media-buy/get-media-buys-response.json @@ -2381,7 +2381,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.433Z", + "generatedAt": "2026-05-08T11:43:56.572Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/get-products-request.json b/schemas/cache/bundled/media-buy/get-products-request.json index db3edc8e..34a57925 100644 --- a/schemas/cache/bundled/media-buy/get-products-request.json +++ b/schemas/cache/bundled/media-buy/get-products-request.json @@ -2039,7 +2039,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.436Z", + "generatedAt": "2026-05-08T11:43:56.574Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/get-products-response.json b/schemas/cache/bundled/media-buy/get-products-response.json index 906557e4..046d7704 100644 --- a/schemas/cache/bundled/media-buy/get-products-response.json +++ b/schemas/cache/bundled/media-buy/get-products-response.json @@ -6833,7 +6833,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.442Z", + "generatedAt": "2026-05-08T11:43:56.580Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/list-creative-formats-request.json b/schemas/cache/bundled/media-buy/list-creative-formats-request.json index 37b1ff3b..cc265cf7 100644 --- a/schemas/cache/bundled/media-buy/list-creative-formats-request.json +++ b/schemas/cache/bundled/media-buy/list-creative-formats-request.json @@ -304,7 +304,7 @@ }, "additionalProperties": true, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.444Z", + "generatedAt": "2026-05-08T11:43:56.582Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/list-creative-formats-response.json b/schemas/cache/bundled/media-buy/list-creative-formats-response.json index 875da918..cd4bc435 100644 --- a/schemas/cache/bundled/media-buy/list-creative-formats-response.json +++ b/schemas/cache/bundled/media-buy/list-creative-formats-response.json @@ -4104,7 +4104,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.448Z", + "generatedAt": "2026-05-08T11:43:56.585Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/log-event-request.json b/schemas/cache/bundled/media-buy/log-event-request.json index 30e7a53c..2dec657d 100644 --- a/schemas/cache/bundled/media-buy/log-event-request.json +++ b/schemas/cache/bundled/media-buy/log-event-request.json @@ -387,7 +387,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.450Z", + "generatedAt": "2026-05-08T11:43:56.587Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/log-event-response.json b/schemas/cache/bundled/media-buy/log-event-response.json index 5a84034c..f43dff71 100644 --- a/schemas/cache/bundled/media-buy/log-event-response.json +++ b/schemas/cache/bundled/media-buy/log-event-response.json @@ -217,7 +217,7 @@ } ], "_bundled": { - "generatedAt": "2026-05-02T21:51:30.450Z", + "generatedAt": "2026-05-08T11:43:56.587Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/package-request.json b/schemas/cache/bundled/media-buy/package-request.json index 1c9c276b..1136a74b 100644 --- a/schemas/cache/bundled/media-buy/package-request.json +++ b/schemas/cache/bundled/media-buy/package-request.json @@ -6919,7 +6919,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.457Z", + "generatedAt": "2026-05-08T11:43:56.593Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/provide-performance-feedback-request.json b/schemas/cache/bundled/media-buy/provide-performance-feedback-request.json index 3313f37c..147fb827 100644 --- a/schemas/cache/bundled/media-buy/provide-performance-feedback-request.json +++ b/schemas/cache/bundled/media-buy/provide-performance-feedback-request.json @@ -112,7 +112,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.460Z", + "generatedAt": "2026-05-08T11:43:56.595Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/provide-performance-feedback-response.json b/schemas/cache/bundled/media-buy/provide-performance-feedback-response.json index b1d7ff16..369010f6 100644 --- a/schemas/cache/bundled/media-buy/provide-performance-feedback-response.json +++ b/schemas/cache/bundled/media-buy/provide-performance-feedback-response.json @@ -166,7 +166,7 @@ } ], "_bundled": { - "generatedAt": "2026-05-02T21:51:30.461Z", + "generatedAt": "2026-05-08T11:43:56.595Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/sync-audiences-request.json b/schemas/cache/bundled/media-buy/sync-audiences-request.json index e1e36f00..92374b75 100644 --- a/schemas/cache/bundled/media-buy/sync-audiences-request.json +++ b/schemas/cache/bundled/media-buy/sync-audiences-request.json @@ -447,7 +447,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.462Z", + "generatedAt": "2026-05-08T11:43:56.596Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/sync-audiences-response.json b/schemas/cache/bundled/media-buy/sync-audiences-response.json index bbf8ecd4..9d100870 100644 --- a/schemas/cache/bundled/media-buy/sync-audiences-response.json +++ b/schemas/cache/bundled/media-buy/sync-audiences-response.json @@ -390,7 +390,7 @@ } ], "_bundled": { - "generatedAt": "2026-05-02T21:51:30.462Z", + "generatedAt": "2026-05-08T11:43:56.597Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/sync-catalogs-request.json b/schemas/cache/bundled/media-buy/sync-catalogs-request.json index aaaf1c6d..8f89ab6d 100644 --- a/schemas/cache/bundled/media-buy/sync-catalogs-request.json +++ b/schemas/cache/bundled/media-buy/sync-catalogs-request.json @@ -829,7 +829,7 @@ } ], "_bundled": { - "generatedAt": "2026-05-02T21:51:30.464Z", + "generatedAt": "2026-05-08T11:43:56.598Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/sync-catalogs-response.json b/schemas/cache/bundled/media-buy/sync-catalogs-response.json index 57f7f6af..7751c069 100644 --- a/schemas/cache/bundled/media-buy/sync-catalogs-response.json +++ b/schemas/cache/bundled/media-buy/sync-catalogs-response.json @@ -399,7 +399,7 @@ } ], "_bundled": { - "generatedAt": "2026-05-02T21:51:30.465Z", + "generatedAt": "2026-05-08T11:43:56.598Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/sync-event-sources-request.json b/schemas/cache/bundled/media-buy/sync-event-sources-request.json index dc7613eb..1d100958 100644 --- a/schemas/cache/bundled/media-buy/sync-event-sources-request.json +++ b/schemas/cache/bundled/media-buy/sync-event-sources-request.json @@ -292,7 +292,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.466Z", + "generatedAt": "2026-05-08T11:43:56.599Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/sync-event-sources-response.json b/schemas/cache/bundled/media-buy/sync-event-sources-response.json index 4317f3f6..9c7d1b22 100644 --- a/schemas/cache/bundled/media-buy/sync-event-sources-response.json +++ b/schemas/cache/bundled/media-buy/sync-event-sources-response.json @@ -516,7 +516,7 @@ } ], "_bundled": { - "generatedAt": "2026-05-02T21:51:30.467Z", + "generatedAt": "2026-05-08T11:43:56.599Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/update-media-buy-request.json b/schemas/cache/bundled/media-buy/update-media-buy-request.json index 02968260..0ed3192a 100644 --- a/schemas/cache/bundled/media-buy/update-media-buy-request.json +++ b/schemas/cache/bundled/media-buy/update-media-buy-request.json @@ -13547,7 +13547,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.483Z", + "generatedAt": "2026-05-08T11:43:56.611Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/media-buy/update-media-buy-response.json b/schemas/cache/bundled/media-buy/update-media-buy-response.json index ae1c8e12..19a3183b 100644 --- a/schemas/cache/bundled/media-buy/update-media-buy-response.json +++ b/schemas/cache/bundled/media-buy/update-media-buy-response.json @@ -2823,7 +2823,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.491Z", + "generatedAt": "2026-05-08T11:43:56.618Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/property/create-property-list-request.json b/schemas/cache/bundled/property/create-property-list-request.json index b3aa9108..47573434 100644 --- a/schemas/cache/bundled/property/create-property-list-request.json +++ b/schemas/cache/bundled/property/create-property-list-request.json @@ -654,7 +654,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.493Z", + "generatedAt": "2026-05-08T11:43:56.620Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/property/create-property-list-response.json b/schemas/cache/bundled/property/create-property-list-response.json index d31d82ae..401bd86d 100644 --- a/schemas/cache/bundled/property/create-property-list-response.json +++ b/schemas/cache/bundled/property/create-property-list-response.json @@ -921,7 +921,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.494Z", + "generatedAt": "2026-05-08T11:43:56.621Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/property/delete-property-list-request.json b/schemas/cache/bundled/property/delete-property-list-request.json index 485a4045..8358b5db 100644 --- a/schemas/cache/bundled/property/delete-property-list-request.json +++ b/schemas/cache/bundled/property/delete-property-list-request.json @@ -191,7 +191,7 @@ ], "additionalProperties": false, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.495Z", + "generatedAt": "2026-05-08T11:43:56.621Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/property/delete-property-list-response.json b/schemas/cache/bundled/property/delete-property-list-response.json index d956e412..536c5850 100644 --- a/schemas/cache/bundled/property/delete-property-list-response.json +++ b/schemas/cache/bundled/property/delete-property-list-response.json @@ -37,7 +37,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.495Z", + "generatedAt": "2026-05-08T11:43:56.621Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/property/get-property-list-request.json b/schemas/cache/bundled/property/get-property-list-request.json index ef1f6714..fddc5845 100644 --- a/schemas/cache/bundled/property/get-property-list-request.json +++ b/schemas/cache/bundled/property/get-property-list-request.json @@ -205,7 +205,7 @@ ], "additionalProperties": false, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.495Z", + "generatedAt": "2026-05-08T11:43:56.622Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/property/get-property-list-response.json b/schemas/cache/bundled/property/get-property-list-response.json index 7c2f0afe..00d828d5 100644 --- a/schemas/cache/bundled/property/get-property-list-response.json +++ b/schemas/cache/bundled/property/get-property-list-response.json @@ -994,7 +994,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.496Z", + "generatedAt": "2026-05-08T11:43:56.623Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/property/list-property-lists-request.json b/schemas/cache/bundled/property/list-property-lists-request.json index 4ee34192..98aede86 100644 --- a/schemas/cache/bundled/property/list-property-lists-request.json +++ b/schemas/cache/bundled/property/list-property-lists-request.json @@ -197,7 +197,7 @@ }, "additionalProperties": false, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.497Z", + "generatedAt": "2026-05-08T11:43:56.624Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/property/list-property-lists-response.json b/schemas/cache/bundled/property/list-property-lists-response.json index 0b9ef613..f0d75593 100644 --- a/schemas/cache/bundled/property/list-property-lists-response.json +++ b/schemas/cache/bundled/property/list-property-lists-response.json @@ -939,7 +939,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.498Z", + "generatedAt": "2026-05-08T11:43:56.625Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/property/update-property-list-request.json b/schemas/cache/bundled/property/update-property-list-request.json index df08fb4a..a991cc92 100644 --- a/schemas/cache/bundled/property/update-property-list-request.json +++ b/schemas/cache/bundled/property/update-property-list-request.json @@ -663,7 +663,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.498Z", + "generatedAt": "2026-05-08T11:43:56.625Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/property/update-property-list-response.json b/schemas/cache/bundled/property/update-property-list-response.json index 56f7833a..1159f8d6 100644 --- a/schemas/cache/bundled/property/update-property-list-response.json +++ b/schemas/cache/bundled/property/update-property-list-response.json @@ -916,7 +916,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.499Z", + "generatedAt": "2026-05-08T11:43:56.626Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/property/validate-property-delivery-request.json b/schemas/cache/bundled/property/validate-property-delivery-request.json index 9e624943..3d85292c 100644 --- a/schemas/cache/bundled/property/validate-property-delivery-request.json +++ b/schemas/cache/bundled/property/validate-property-delivery-request.json @@ -301,7 +301,7 @@ ], "additionalProperties": false, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.500Z", + "generatedAt": "2026-05-08T11:43:56.627Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/property/validate-property-delivery-response.json b/schemas/cache/bundled/property/validate-property-delivery-response.json index dbc8a5e2..57531b17 100644 --- a/schemas/cache/bundled/property/validate-property-delivery-response.json +++ b/schemas/cache/bundled/property/validate-property-delivery-response.json @@ -411,7 +411,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.500Z", + "generatedAt": "2026-05-08T11:43:56.627Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/protocol/get-adcp-capabilities-request.json b/schemas/cache/bundled/protocol/get-adcp-capabilities-request.json index 21f0e22c..ca0c2d79 100644 --- a/schemas/cache/bundled/protocol/get-adcp-capabilities-request.json +++ b/schemas/cache/bundled/protocol/get-adcp-capabilities-request.json @@ -40,7 +40,7 @@ }, "additionalProperties": true, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.501Z", + "generatedAt": "2026-05-08T11:43:56.628Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/protocol/get-adcp-capabilities-response.json b/schemas/cache/bundled/protocol/get-adcp-capabilities-response.json index 5ae731a7..ab880eed 100644 --- a/schemas/cache/bundled/protocol/get-adcp-capabilities-response.json +++ b/schemas/cache/bundled/protocol/get-adcp-capabilities-response.json @@ -1753,7 +1753,7 @@ } }, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.503Z", + "generatedAt": "2026-05-08T11:43:56.629Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/signals/activate-signal-request.json b/schemas/cache/bundled/signals/activate-signal-request.json index e2f413c0..d4ad4b0f 100644 --- a/schemas/cache/bundled/signals/activate-signal-request.json +++ b/schemas/cache/bundled/signals/activate-signal-request.json @@ -262,7 +262,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.504Z", + "generatedAt": "2026-05-08T11:43:56.630Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/signals/activate-signal-response.json b/schemas/cache/bundled/signals/activate-signal-response.json index 57778af2..f68c9e20 100644 --- a/schemas/cache/bundled/signals/activate-signal-response.json +++ b/schemas/cache/bundled/signals/activate-signal-response.json @@ -344,7 +344,7 @@ } ], "_bundled": { - "generatedAt": "2026-05-02T21:51:30.505Z", + "generatedAt": "2026-05-08T11:43:56.630Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/signals/get-signals-request.json b/schemas/cache/bundled/signals/get-signals-request.json index 32ae7cbe..5c397f43 100644 --- a/schemas/cache/bundled/signals/get-signals-request.json +++ b/schemas/cache/bundled/signals/get-signals-request.json @@ -402,7 +402,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.505Z", + "generatedAt": "2026-05-08T11:43:56.631Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/signals/get-signals-response.json b/schemas/cache/bundled/signals/get-signals-response.json index 650f374c..4b96a4d1 100644 --- a/schemas/cache/bundled/signals/get-signals-response.json +++ b/schemas/cache/bundled/signals/get-signals-response.json @@ -713,7 +713,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.506Z", + "generatedAt": "2026-05-08T11:43:56.631Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/sponsored-intelligence/si-get-offering-request.json b/schemas/cache/bundled/sponsored-intelligence/si-get-offering-request.json index 73c219c6..4144d8b4 100644 --- a/schemas/cache/bundled/sponsored-intelligence/si-get-offering-request.json +++ b/schemas/cache/bundled/sponsored-intelligence/si-get-offering-request.json @@ -50,7 +50,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.506Z", + "generatedAt": "2026-05-08T11:43:56.632Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/sponsored-intelligence/si-get-offering-response.json b/schemas/cache/bundled/sponsored-intelligence/si-get-offering-response.json index 191102d4..b73ab6e7 100644 --- a/schemas/cache/bundled/sponsored-intelligence/si-get-offering-response.json +++ b/schemas/cache/bundled/sponsored-intelligence/si-get-offering-response.json @@ -231,7 +231,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.507Z", + "generatedAt": "2026-05-08T11:43:56.632Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/sponsored-intelligence/si-initiate-session-request.json b/schemas/cache/bundled/sponsored-intelligence/si-initiate-session-request.json index ea3c88d6..6adbe281 100644 --- a/schemas/cache/bundled/sponsored-intelligence/si-initiate-session-request.json +++ b/schemas/cache/bundled/sponsored-intelligence/si-initiate-session-request.json @@ -315,7 +315,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.507Z", + "generatedAt": "2026-05-08T11:43:56.632Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/sponsored-intelligence/si-initiate-session-response.json b/schemas/cache/bundled/sponsored-intelligence/si-initiate-session-response.json index c4a6c616..73ed7c02 100644 --- a/schemas/cache/bundled/sponsored-intelligence/si-initiate-session-response.json +++ b/schemas/cache/bundled/sponsored-intelligence/si-initiate-session-response.json @@ -642,7 +642,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.508Z", + "generatedAt": "2026-05-08T11:43:56.633Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/sponsored-intelligence/si-send-message-request.json b/schemas/cache/bundled/sponsored-intelligence/si-send-message-request.json index d98be5f0..315eb243 100644 --- a/schemas/cache/bundled/sponsored-intelligence/si-send-message-request.json +++ b/schemas/cache/bundled/sponsored-intelligence/si-send-message-request.json @@ -74,7 +74,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.508Z", + "generatedAt": "2026-05-08T11:43:56.633Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/sponsored-intelligence/si-send-message-response.json b/schemas/cache/bundled/sponsored-intelligence/si-send-message-response.json index 680a6506..779660e1 100644 --- a/schemas/cache/bundled/sponsored-intelligence/si-send-message-response.json +++ b/schemas/cache/bundled/sponsored-intelligence/si-send-message-response.json @@ -613,7 +613,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.508Z", + "generatedAt": "2026-05-08T11:43:56.634Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/sponsored-intelligence/si-terminate-session-request.json b/schemas/cache/bundled/sponsored-intelligence/si-terminate-session-request.json index 105eadac..838a67bf 100644 --- a/schemas/cache/bundled/sponsored-intelligence/si-terminate-session-request.json +++ b/schemas/cache/bundled/sponsored-intelligence/si-terminate-session-request.json @@ -80,7 +80,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.509Z", + "generatedAt": "2026-05-08T11:43:56.634Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/bundled/sponsored-intelligence/si-terminate-session-response.json b/schemas/cache/bundled/sponsored-intelligence/si-terminate-session-response.json index 40201e6a..91ab1419 100644 --- a/schemas/cache/bundled/sponsored-intelligence/si-terminate-session-response.json +++ b/schemas/cache/bundled/sponsored-intelligence/si-terminate-session-response.json @@ -180,7 +180,7 @@ ], "additionalProperties": true, "_bundled": { - "generatedAt": "2026-05-02T21:51:30.509Z", + "generatedAt": "2026-05-08T11:43:56.634Z", "note": "This is a bundled schema with all $ref resolved inline. For the modular version with references, use the parent directory." } } \ No newline at end of file diff --git a/schemas/cache/creative/asset-types/index.json b/schemas/cache/creative/asset-types/index.json index b66fcee9..2d0f1987 100644 --- a/schemas/cache/creative/asset-types/index.json +++ b/schemas/cache/creative/asset-types/index.json @@ -3,7 +3,7 @@ "title": "AdCP Asset Type Registry", "description": "Registry of asset types used in AdCP creative manifests. Each asset type defines the structure of actual content payloads (what you send), not requirements or constraints (which belong in format specifications).", "version": "1.0.0", - "lastUpdated": "2026-05-02", + "lastUpdated": "2026-05-08", "asset_types": { "image": { "description": "Static image asset (JPG, PNG, GIF, WebP, SVG)", @@ -109,9 +109,9 @@ "creative_manifests": "Creative manifests provide actual asset content, keyed by asset_id from the format. Each asset value carries an `asset_type` discriminator (one of the registry keys) so validators can select the matching asset schema and report errors against only that branch. The format specification also defines what asset_type each asset_id should have \u2014 payload and format should agree.", "example_flow": "Format says 'hero_image' must be type 'image' with width 1200, height 627. Manifest provides hero_image: {url: '...', width: 1200, height: 627}. The format spec tells us it's an image type." }, - "adcp_version": "3.0.5", - "baseUrl": "/schemas/3.0.5", + "adcp_version": "3.0.7", + "baseUrl": "/schemas/3.0.7", "versioning": { - "note": "AdCP uses build-time versioning. This directory contains schemas for AdCP 3.0.5. Full semantic versions are available at /schemas/{version}/ (e.g., /schemas/2.5.0/). Major version aliases point to the latest release: /schemas/v3/ \u2192 /schemas/3.0.5/." + "note": "AdCP uses build-time versioning. This directory contains schemas for AdCP 3.0.7. Full semantic versions are available at /schemas/{version}/ (e.g., /schemas/2.5.0/). Major version aliases point to the latest release: /schemas/v3/ \u2192 /schemas/3.0.7/." } } \ No newline at end of file diff --git a/schemas/cache/enums/error-code.json b/schemas/cache/enums/error-code.json index 1e596252..51cb7a39 100644 --- a/schemas/cache/enums/error-code.json +++ b/schemas/cache/enums/error-code.json @@ -1,8 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "/schemas/latest/enums/error-code.json", "title": "Error Code", - "description": "Standard error code vocabulary for AdCP. Codes are machine-readable so agents can apply autonomous recovery strategies based on the recovery classification. Sellers MAY return codes not listed here for platform-specific errors — the error.json code field accepts any string. Agents MUST handle unknown codes by falling back to the recovery classification.", + "description": "Standard error code vocabulary for AdCP. Codes are machine-readable so agents can apply autonomous recovery strategies based on the recovery classification. Sellers MAY return codes not listed here for platform-specific errors \u2014 the error.json code field accepts any string. Agents MUST handle unknown codes by falling back to the recovery classification.", "type": "string", "enum": [ "INVALID_REQUEST", @@ -49,26 +48,11 @@ "VERSION_UNSUPPORTED", "CAMPAIGN_SUSPENDED", "GOVERNANCE_UNAVAILABLE", - "PERMISSION_DENIED", - "SCOPE_INSUFFICIENT", - "READ_ONLY_SCOPE", - "FIELD_NOT_PERMITTED", - "PROVENANCE_REQUIRED", - "PROVENANCE_DIGITAL_SOURCE_TYPE_MISSING", - "PROVENANCE_DISCLOSURE_MISSING", - "PROVENANCE_EMBEDDED_MISSING", - "PROVENANCE_VERIFIER_NOT_ACCEPTED", - "PROVENANCE_CLAIM_CONTRADICTED", - "BILLING_NOT_SUPPORTED", - "BILLING_NOT_PERMITTED_FOR_AGENT", - "PAYMENT_TERMS_NOT_SUPPORTED", - "BRAND_REQUIRED", - "AGENT_SUSPENDED", - "AGENT_BLOCKED" + "PERMISSION_DENIED" ], "enumDescriptions": { "INVALID_REQUEST": "Request is malformed, missing required fields, or violates schema constraints. Recovery: correctable (check request parameters and fix).", - "AUTH_REQUIRED": "Authentication is required to access this resource. Recovery: correctable (provide credentials via auth header).", + "AUTH_REQUIRED": "Authentication is required, or presented credentials were rejected. Two operational sub-cases share this code: (a) credentials missing \u2014 agent provides credentials and retries; (b) credentials presented but rejected (expired / revoked / malformed signature) \u2014 agent SHOULD NOT auto-retry, since re-presenting a rejected credential against an SSO endpoint creates retry-storm patterns indistinguishable from brute-force probes. In sub-case (b) the agent SHOULD escalate to operator for credential rotation rather than loop. A future minor release splits this code into AUTH_MISSING (correctable) and AUTH_INVALID (terminal); agents handling 3.0.x sellers SHOULD apply the same operational distinction at the application layer. Recovery: correctable (provide credentials via auth header \u2014 but only when the credential was missing, not when it was presented and rejected).", "RATE_LIMITED": "Request rate exceeded. Retry after the retry_after interval. Recovery: transient.", "SERVICE_UNAVAILABLE": "Seller service is temporarily unavailable. Retry with exponential backoff. Recovery: transient.", "POLICY_VIOLATION": "Request violates the seller's content or advertising policies. Recovery: correctable (review policy requirements in the error details).", @@ -84,23 +68,23 @@ "ACCOUNT_AMBIGUOUS": "Natural key resolves to multiple accounts. Recovery: correctable (pass explicit account_id or a more specific natural key).", "ACCOUNT_PAYMENT_REQUIRED": "Account has an outstanding balance requiring payment before new buys. Recovery: terminal (buyer must resolve billing).", "ACCOUNT_SUSPENDED": "Account has been suspended. Recovery: terminal (contact seller to resolve suspension).", - "COMPLIANCE_UNSATISFIED": "A required disclosure from the brief's compliance section cannot be satisfied by the target format — either the required position or the required persistence mode is not in the format's disclosure_capabilities. Recovery: correctable (choose a format that supports the required disclosure positions and persistence modes, or remove the disclosure requirement).", - "GOVERNANCE_DENIED": "A registered governance agent denied the transaction. Sellers MUST place the denial in the task's structured rejection arm when one exists (e.g., `acquire_rights` → `AcquireRightsRejected`, `creative_approval` → `CreativeRejected`); otherwise in `errors[]` + `adcp_error`. Buyers MUST dispatch on the response's discriminated `status` first and fall back to `errors[].code` / `adcp_error.code` only when no rejection arm exists for that task. The buyer may restructure the buy (e.g., reduce budget, split into smaller transactions), escalate to human spending authority, or contact the governance agent for details. Recovery: correctable.\n\nWire placement (full guidance). Governance denial is a structured business outcome, not a system error — the governance call SUCCEEDED and the agent returned a denial verdict. Two cases:\n\n1. Task response defines a structured rejection arm. The arm IS the canonical denial shape. The seller populates `reason` (human-readable, propagating governance findings) and `suggestions` (optional) and does NOT additionally emit `GOVERNANCE_DENIED` in `errors[]` or `adcp_error`. The rejection arms enforce this at the schema layer: e.g., `AcquireRightsRejected` and `CreativeRejected` both declare `not: { required: [errors] }`, so dual-emission is already a schema violation. The code does not appear on the wire when the rejection arm is used. Transport-level success markers MUST NOT be flipped (HTTP 200, MCP `isError: false`, A2A `succeeded`) — the task ran successfully and produced a structured response.\n\n2. Task response has no rejection arm (e.g., `create_media_buy` returns Success / Error / Submitted arms only). The seller populates `errors[].code: GOVERNANCE_DENIED` in the payload AND `adcp_error.code: GOVERNANCE_DENIED` on the envelope per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`. Transport-level failure markers DO flip in this case (HTTP 4xx, MCP `isError: true`, A2A `failed`) — the task could not produce a success artifact.\n\nThe rule generalizes to any current or future task whose response defines a discriminated rejection arm. In either placement, sellers SHOULD propagate governance findings verbatim — buyers' recovery decisions depend on what specifically was rejected. `GOVERNANCE_DENIED` is reserved for verdicts received from a reachable governance agent; if the governance call itself failed (timeout, network, config error), use `GOVERNANCE_UNAVAILABLE` instead.", + "COMPLIANCE_UNSATISFIED": "A required disclosure from the brief's compliance section cannot be satisfied by the target format \u2014 either the required position or the required persistence mode is not in the format's disclosure_capabilities. Recovery: correctable (choose a format that supports the required disclosure positions and persistence modes, or remove the disclosure requirement).", + "GOVERNANCE_DENIED": "A registered governance agent denied the transaction. Sellers MUST place the denial in the task's structured rejection arm when one exists (e.g., `acquire_rights` \u2192 `AcquireRightsRejected`, `creative_approval` \u2192 `CreativeRejected`); otherwise in `errors[]` + `adcp_error`. Buyers MUST dispatch on the response's discriminated `status` first and fall back to `errors[].code` / `adcp_error.code` only when no rejection arm exists for that task. The buyer may restructure the buy (e.g., reduce budget, split into smaller transactions), escalate to human spending authority, or contact the governance agent for details. Recovery: correctable.\n\nWire placement (full guidance). Governance denial is a structured business outcome, not a system error \u2014 the governance call SUCCEEDED and the agent returned a denial verdict. Two cases:\n\n1. Task response defines a structured rejection arm. The arm IS the canonical denial shape. The seller populates `reason` (human-readable, propagating governance findings) and `suggestions` (optional) and does NOT additionally emit `GOVERNANCE_DENIED` in `errors[]` or `adcp_error`. The rejection arms enforce this at the schema layer: e.g., `AcquireRightsRejected` and `CreativeRejected` both declare `not: { required: [errors] }`, so dual-emission is already a schema violation. The code does not appear on the wire when the rejection arm is used. Transport-level success markers MUST NOT be flipped (HTTP 200, MCP `isError: false`, A2A `succeeded`) \u2014 the task ran successfully and produced a structured response.\n\n2. Task response has no rejection arm (e.g., `create_media_buy` returns Success / Error / Submitted arms only). The seller populates `errors[].code: GOVERNANCE_DENIED` in the payload AND `adcp_error.code: GOVERNANCE_DENIED` on the envelope per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`. Transport-level failure markers DO flip in this case (HTTP 4xx, MCP `isError: true`, A2A `failed`) \u2014 the task could not produce a success artifact.\n\nThe rule generalizes to any current or future task whose response defines a discriminated rejection arm. In either placement, sellers SHOULD propagate governance findings verbatim \u2014 buyers' recovery decisions depend on what specifically was rejected. `GOVERNANCE_DENIED` is reserved for verdicts received from a reachable governance agent; if the governance call itself failed (timeout, network, config error), use `GOVERNANCE_UNAVAILABLE` instead.", "BUDGET_EXHAUSTED": "Account or campaign budget has been fully spent. Distinct from BUDGET_TOO_LOW (rejected at submission). Recovery: terminal (buyer must add funds or increase budget cap).", "BUDGET_EXCEEDED": "Operation would exceed the allocated budget for the media buy or package. Distinct from BUDGET_EXHAUSTED (already spent) and BUDGET_TOO_LOW (below minimum). Recovery: correctable (reduce requested amount or increase budget allocation).", "CREATIVE_DEADLINE_EXCEEDED": "Creative change submitted after the package's creative_deadline. Distinct from CREATIVE_REJECTED (content policy failure). Recovery: correctable (check creative_deadline via get_media_buys before submitting changes, or negotiate a deadline extension with the seller).", "CONFLICT": "Concurrent modification detected. The resource was modified by another request between read and write. Recovery: transient (re-read the resource and retry with current state).", - "IDEMPOTENCY_CONFLICT": "An earlier request with the same idempotency_key was processed with a different canonical payload within the seller's replay window. Distinct from CONFLICT (concurrent write) — this indicates the client reused a key across semantically different requests. Recovery: correctable (use a fresh UUID v4 for the new request, or resend the exact original payload to get the cached response).", - "IDEMPOTENCY_EXPIRED": "The idempotency_key was seen previously but its cached response has been evicted because it is past the seller's declared replay_ttl_seconds. Distinct from IDEMPOTENCY_CONFLICT (different payload within window) — this indicates the retry arrived too late for at-most-once guarantees. Recovery: correctable (perform a natural-key check — e.g., get_media_buys by context.internal_campaign_id — to determine whether the original request succeeded, then either accept that result or generate a fresh idempotency_key for a new attempt). If the buyer has any evidence the prior call succeeded (partial response received before crash, entry in the buyer's own DB, a webhook fired), the buyer MUST do the natural-key check BEFORE minting a new key — minting a new key in that situation is exactly how double-creation happens.", + "IDEMPOTENCY_CONFLICT": "An earlier request with the same idempotency_key was processed with a different canonical payload within the seller's replay window. Distinct from CONFLICT (concurrent write) \u2014 this indicates the client reused a key across semantically different requests. Recovery: correctable (use a fresh UUID v4 for the new request, or resend the exact original payload to get the cached response).", + "IDEMPOTENCY_EXPIRED": "The idempotency_key was seen previously but its cached response has been evicted because it is past the seller's declared replay_ttl_seconds. Distinct from IDEMPOTENCY_CONFLICT (different payload within window) \u2014 this indicates the retry arrived too late for at-most-once guarantees. Recovery: correctable (perform a natural-key check \u2014 e.g., get_media_buys by context.internal_campaign_id \u2014 to determine whether the original request succeeded, then either accept that result or generate a fresh idempotency_key for a new attempt). If the buyer has any evidence the prior call succeeded (partial response received before crash, entry in the buyer's own DB, a webhook fired), the buyer MUST do the natural-key check BEFORE minting a new key \u2014 minting a new key in that situation is exactly how double-creation happens.", "INVALID_STATE": "Operation is not permitted for the resource's current status (e.g., updating a completed or canceled media buy, or modifying a canceled package). Recovery: correctable (check current status via get_media_buys and adjust request).", "MEDIA_BUY_NOT_FOUND": "Referenced media buy does not exist or is not accessible to the requesting agent. Recovery: correctable (verify media_buy_id or buyer_ref).", "NOT_CANCELLABLE": "The media buy or package cannot be canceled in its current state. The seller may have contractual or operational constraints that prevent cancellation. Recovery: correctable (check the seller's cancellation policy or contact the seller).", "PACKAGE_NOT_FOUND": "Referenced package does not exist within the specified media buy. Recovery: correctable (verify package_id or buyer_ref via get_media_buys).", - "CREATIVE_NOT_FOUND": "Referenced creative does not exist in the agent's creative library. Recovery: correctable (verify creative_id via list_creatives, or sync_creatives to register it). Sellers MUST return this code uniformly for any creative_id not owned by the calling account — never distinguish 'exists in another tenant' from 'does not exist', which would enable cross-tenant enumeration.", - "SIGNAL_NOT_FOUND": "Referenced signal does not exist in the agent's catalog. Recovery: correctable (verify signal_id via get_signals, or confirm the signal is available from this agent). Sellers MUST return this code uniformly for any signal_id not accessible to the calling account — never distinguish 'exists but unauthorized' from 'does not exist', which would enable cross-tenant enumeration.", - "REFERENCE_NOT_FOUND": "Generic fallback for a referenced identifier, grant, session, or other resource that does not exist or is not accessible by the caller. Use when no resource-specific not-found code applies (e.g., property lists, content standards, rights grants, SI offerings, proposals, catalogs, event sources, collection lists, brands, individual properties). Typed parameters that lack a dedicated standard code MUST also use REFERENCE_NOT_FOUND rather than minting a custom *_NOT_FOUND code. See 'Uniform response for inaccessible references' in error-handling.mdx for the full MUST list. Recovery: correctable. Summary of the uniform-response MUST: sellers MUST return the same response for 'exists but the caller lacks access' as for 'does not exist' across every observable channel — error.code/message/field/details (message MUST be generic; error.field MUST be identical across both cases on typed parameters); HTTP status, A2A task.status.state, and MCP isError; response headers (ETag, Cache-Control, per-type rate-limit buckets, CDN tags); side effects (webhook/audit writes, background-job enqueues, per-type quota counters, DB-shard routing); and observability (logs, APM spans, third-party error telemetry like Sentry/Datadog). Sellers MUST perform the same resolution-and-authorization work on both paths (resolve-then-authorize; on true-miss still run an authorization decision of equivalent shape against an empty principal set so authorizer latency is not a side channel). Cache population MUST NOT be gated on authorization. Polymorphism is evaluated against the tool-schema's declared parameter shape before any lookup, and a tool's declared shape MUST be identical across all callers.", + "CREATIVE_NOT_FOUND": "Referenced creative does not exist in the agent's creative library. Recovery: correctable (verify creative_id via list_creatives, or sync_creatives to register it). Sellers MUST return this code uniformly for any creative_id not owned by the calling account \u2014 never distinguish 'exists in another tenant' from 'does not exist', which would enable cross-tenant enumeration.", + "SIGNAL_NOT_FOUND": "Referenced signal does not exist in the agent's catalog. Recovery: correctable (verify signal_id via get_signals, or confirm the signal is available from this agent). Sellers MUST return this code uniformly for any signal_id not accessible to the calling account \u2014 never distinguish 'exists but unauthorized' from 'does not exist', which would enable cross-tenant enumeration.", + "REFERENCE_NOT_FOUND": "Generic fallback for a referenced identifier, grant, session, or other resource that does not exist or is not accessible by the caller. Use when no resource-specific not-found code applies (e.g., property lists, content standards, rights grants, SI offerings, proposals, catalogs, event sources, collection lists, brands, individual properties). Typed parameters that lack a dedicated standard code MUST also use REFERENCE_NOT_FOUND rather than minting a custom *_NOT_FOUND code. See 'Uniform response for inaccessible references' in error-handling.mdx for the full MUST list. Recovery: correctable. Summary of the uniform-response MUST: sellers MUST return the same response for 'exists but the caller lacks access' as for 'does not exist' across every observable channel \u2014 error.code/message/field/details (message MUST be generic; error.field MUST be identical across both cases on typed parameters); HTTP status, A2A task.status.state, and MCP isError; response headers (ETag, Cache-Control, per-type rate-limit buckets, CDN tags); side effects (webhook/audit writes, background-job enqueues, per-type quota counters, DB-shard routing); and observability (logs, APM spans, third-party error telemetry like Sentry/Datadog). Sellers MUST perform the same resolution-and-authorization work on both paths (resolve-then-authorize; on true-miss still run an authorization decision of equivalent shape against an empty principal set so authorizer latency is not a side channel). Cache population MUST NOT be gated on authorization. Polymorphism is evaluated against the tool-schema's declared parameter shape before any lookup, and a tool's declared shape MUST be identical across all callers.", "SESSION_NOT_FOUND": "SI session ID is invalid, expired, or does not exist. Recovery: correctable (initiate a new session via si_initiate_session).", - "PLAN_NOT_FOUND": "Referenced governance plan does not exist or is not accessible to the requesting agent. Recovery: correctable (verify plan_id via sync_plans, or register the plan first). Sellers MUST return this code uniformly for any plan_id not accessible to the calling account — never distinguish 'exists but unauthorized' from 'does not exist', which would enable cross-tenant enumeration of governance plans.", + "PLAN_NOT_FOUND": "Referenced governance plan does not exist or is not accessible to the requesting agent. Recovery: correctable (verify plan_id via sync_plans, or register the plan first). Sellers MUST return this code uniformly for any plan_id not accessible to the calling account \u2014 never distinguish 'exists but unauthorized' from 'does not exist', which would enable cross-tenant enumeration of governance plans.", "SESSION_TERMINATED": "SI session has already been terminated and cannot accept further messages. Recovery: correctable (initiate a new session via si_initiate_session).", "VALIDATION_ERROR": "Request contains invalid field values or violates business rules beyond schema validation. Recovery: correctable (review error details and fix field values).", "PRODUCT_EXPIRED": "One or more referenced products have passed their expires_at timestamp and are no longer available for purchase. Recovery: correctable (re-discover with get_products to find current inventory).", @@ -108,35 +92,20 @@ "IO_REQUIRED": "The committed proposal requires a signed insertion order but no io_acceptance was provided. Recovery: correctable (review the proposal's insertion_order, accept terms, and include io_acceptance on create_media_buy).", "TERMS_REJECTED": "Buyer-proposed measurement_terms were rejected by the seller. The error details SHOULD identify which specific term was rejected and the seller's acceptable range or supported vendors. Recovery: correctable (adjust the proposed terms and retry, or omit measurement_terms to accept the product's defaults).", "REQUOTE_REQUIRED": "An update_media_buy request changes the parameter envelope (budget, flight dates, volume, targeting) the original quote was priced against. The pricing_option remains locked; the seller is declining the requested shape at that price. Distinct from TERMS_REJECTED (measurement) and POLICY_VIOLATION (content). Sellers SHOULD populate error.details.envelope_field with the field path(s) that breached the envelope (e.g., 'packages[0].budget', 'end_time') so the buyer's agent can autonomously re-discover. Recovery: correctable (re-negotiate via get_products in 'refine' mode against the existing proposal_id to obtain a fresh quote reflecting the new parameters, then resubmit the update against the new proposal_id).", - "VERSION_UNSUPPORTED": "The declared adcp_version (release-precision) or adcp_major_version (deprecated) is not supported by this seller. The error details SHOULD follow `error-details/version-unsupported.json` — `supported_versions` (release-precision strings) is authoritative for retry; `supported_majors` is deprecated. Recovery: correctable (re-pin to a release in supported_versions and retry; or call get_adcp_capabilities without a version pin to discover supported_versions).", - "CAMPAIGN_SUSPENDED": "Campaign governance has been suspended pending human review; the governance agent MUST reject `check_governance` and `report_plan_outcome` calls on the affected plan until the escalation is resolved. Distinct from `ACCOUNT_SUSPENDED` (account-wide) — this is scoped to a single plan/campaign. Recovery: transient (wait for the escalation to resolve; contact the plan operator if the suspension persists).", - "GOVERNANCE_UNAVAILABLE": "A registered governance agent is unreachable. Sellers MUST place this code in `errors[]` + `adcp_error` (never a structured rejection arm) and flip transport-level failure markers (HTTP 5xx, MCP `isError: true`, A2A `failed`). Distinct from `GOVERNANCE_DENIED` (agent reachable and explicitly denied — see that code's wire-placement guidance). Recovery: transient (retry with backoff; if the agent remains unreachable, the buyer MUST contact the plan's governance operator — the seller MUST NOT proceed with the media buy without a valid decision).\n\nWire placement (full guidance). Governance unavailability is a system error — the governance call FAILED (timeout, network, config error) and the seller could not get a verdict at all. Always populate both layers per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`. Do NOT use a structured rejection arm for unavailability even when the task offers one — the buyer's recovery semantics differ (retry-with-backoff for unavailability vs. restructure-or-escalate for denial), and conflating them masks the system-error signal.", - "PERMISSION_DENIED": "The authenticated caller is not authorized for the requested action under the seller's own policies, or a required signed credential (e.g., a `governance_context` token on a spend-commit) is missing, fails verification, or was issued for a different plan, seller, or phase. Distinct from `AUTH_REQUIRED` (no credentials presented), `GOVERNANCE_DENIED` (governance agent denied), `AGENT_SUSPENDED` (agent's relationship temporarily paused), and `AGENT_BLOCKED` (agent's relationship permanently denied). When the gate that fired is specifically a non-status per-agent provisioning constraint — e.g., the agent is provisioned for sandbox traffic only and the request was against a non-sandbox account — `error.details` SHOULD conform to `error-details/agent-permission-denied.json` (`scope: \"agent\"` plus `reason: \"sandbox_only\"`) so callers can dispatch without parsing prose. Sellers MUST emit `scope: \"agent\"` only when buyer-agent identity has been established via signed-request derivation or a credential-to-agent mapping in the seller's onboarding record; in all other cases (including bearer credentials not mapped to a specific agent record) sellers MUST return `PERMISSION_DENIED` and MUST omit `error.details.scope` — emitting the per-agent scope without established identity is a cross-tenant onboarding oracle, and the omit MUST be enforced across every observable channel (response shape, HTTP/A2A/MCP status, headers, side effects, observability, latency parity) per the channel-coverage rules in error-handling.mdx Per-Agent Authorization Gate, mirroring the `*_NOT_FOUND` uniform-response rule and `BILLING_NOT_PERMITTED_FOR_AGENT`. The `suspended` and `blocked` per-agent states are NOT carried on this code — sellers MUST emit `AGENT_SUSPENDED` / `AGENT_BLOCKED` instead, each of which is its own discriminator. Recovery: correctable (call `check_governance` to mint a valid token, or contact the seller to resolve the underlying permission); when `details.reason` is present the rejection is terminal-pending-onboarding — the agent MUST surface to a human at the buyer rather than auto-retrying, since the agent cannot unilaterally extend its sandbox-only provisioning.", - "SCOPE_INSUFFICIENT": "The authenticated caller is not authorized for the invoked task — the task is not in the caller's `allowed_tasks` for this account (discoverable via the `authorization` object on sync_accounts / list_accounts responses). Distinct from `PERMISSION_DENIED` (generic authz failure, often credential-shaped) by being narrowly about task-level scope. Sellers SHOULD populate `error.details.introspection_hint` pointing at where the caller can re-read its scope (strawman: `{ task: 'list_accounts', account: {...} }`). Recovery: correctable in the sense that the request can be re-sent after the scope is broadened, but the agent cannot broaden its own scope — this requires operator intervention, and agents SHOULD surface rather than auto-retry.", - "READ_ONLY_SCOPE": "The caller's scope is read-only; the invoked task would mutate state and was rejected. Distinct from `SCOPE_INSUFFICIENT` (task not in scope at all) — the task is in some scopes this seller supports, just not this caller's. Recovery: correctable but not agent-autonomous — use a non-mutating alternative, or surface to the operator to request a scope that permits mutation.", - "FIELD_NOT_PERMITTED": "A request field is not in the caller's `field_scopes` allowlist for this task. Sellers declaring `field_scopes` on the account's `authorization` object MUST reject any request that sets a non-allowlisted field with this code. Distinct from `VALIDATION_ERROR` (schema/business-rule violation) - the field is valid, just not writable by this caller. `error.field` MUST identify the exact offending field path (e.g., `packages[0].budget`); when multiple fields are disallowed, sellers SHOULD return one error per field, or MAY enumerate them in `error.details.fields`. Recovery: correctable and agent-autonomous - agent may drop the disallowed field(s) and retry.", - "PROVENANCE_REQUIRED": "Seller's `creative_policy.provenance_required` is true and the submitted creative has no `provenance` object on the manifest, on the creative-asset, or on any individual asset. Distinct from `CREATIVE_REJECTED` (generic content-policy failure) by being narrowly about provenance presence. Recovery: correctable (attach a provenance object - at minimum `digital_source_type` - and resubmit). `error.field` MUST point at the path where provenance was expected (e.g., `creatives[0].creative_manifest`).", - "PROVENANCE_DIGITAL_SOURCE_TYPE_MISSING": "Seller's `creative_policy.provenance_requirements.require_digital_source_type` is true and the submitted creative's resolved provenance (after inheritance) has no `digital_source_type` value, or has it set to null. Distinct from `PROVENANCE_REQUIRED` (no provenance object at all) - provenance is present, just missing this specific field. Recovery: correctable (set `provenance.digital_source_type` to a value from the `digital-source-type` enum and resubmit). `error.field` MUST point at the resolved provenance path that was inspected (e.g., `creatives[0].creative_manifest.provenance.digital_source_type`).", - "PROVENANCE_DISCLOSURE_MISSING": "Seller's `creative_policy.provenance_requirements.require_disclosure_metadata` is true and the submitted creative's resolved provenance has no `disclosure.required` boolean, or `disclosure.required` is true with no `disclosure.jurisdictions` entries. Recovery: correctable (set `provenance.disclosure.required` and, when true, populate `disclosure.jurisdictions`). `error.field` MUST point at `provenance.disclosure` (e.g., `creatives[0].creative_manifest.provenance.disclosure`).", - "PROVENANCE_EMBEDDED_MISSING": "Seller's `creative_policy.provenance_requirements.require_embedded_provenance` is true and the submitted creative's resolved provenance has no `embedded_provenance` array, or has it as an empty array. Used in pipelines where sidecar `c2pa.manifest_url` is stripped by intermediaries and the seller requires content-stream-resilient provenance. Recovery: correctable (attach at least one `embedded_provenance` entry from a supported provider and resubmit, optionally with a `verify_agent` pointer matching one of the seller's `creative_policy.accepted_verifiers`). `error.field` MUST point at `provenance.embedded_provenance` on the resolved manifest.", - "PROVENANCE_VERIFIER_NOT_ACCEPTED": "Buyer attached a `verify_agent.agent_url` on `embedded_provenance[]` or `watermarks[]` that does not match (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments) any entry in the seller's `creative_policy.accepted_verifiers[].agent_url`. The seller does not call buyer-asserted endpoints outside its allowlist; this is the cross-check that closes the buyer-controlled-URL trust gap. `error.field` MUST point at the offending `verify_agent.agent_url` path; `error.details` SHOULD include a reference to the product whose `creative_policy.accepted_verifiers` the buyer should consult (the buyer already has this from `get_products`). Recovery: correctable (replace `verify_agent.agent_url` with one from the seller's published `accepted_verifiers`, drop the `verify_agent` entirely if the embedding is self-verifiable, or re-embed evidence using a verifier the seller accepts).", - "PROVENANCE_CLAIM_CONTRADICTED": "Seller invoked a governance agent from `creative_policy.accepted_verifiers` via `get_creative_features` and the verifier's result contradicts the buyer's provenance claim - e.g., buyer claims `digital_source_type: digital_capture` but the AI-detection feature returns `ai_generated: true` above the seller's confidence threshold. Distinct from the `PROVENANCE_*_MISSING` family (structural absence) by being an active refutation. `error.details` SHOULD be limited to the audit-safe allowlist `{ agent_url, feature_id, claimed_value, observed_value, confidence }`; sellers MUST NOT forward arbitrary verifier extension fields, `detail_url`, or any verifier response shape that may carry cross-tenant or PII data. When the seller calls a different on-list agent than the buyer nominated (the seller is the verifier-of-record), `error.details.agent_url` is the agent the seller actually called and `error.details.substituted_for` SHOULD carry the buyer's nominated `agent_url` so the buyer can reconcile. Recovery: correctable - buyer revises the provenance claim to match reality (or replaces the creative); auto-retry without correction will not pass.", - "BILLING_NOT_SUPPORTED": "The seller declines the requested `billing` value either at the seller-wide capability level (`supported_billing` does not include the value) or at the per-account-relationship level (e.g., the seller accepts `operator` billing in general but has no direct billing relationship with the operator on this specific account). The default reject code for billing-value mismatches; `error.details` SHOULD conform to `error-details/billing-not-supported.json` (`scope` ∈ `{\"capability\", \"account\"}` plus optional `supported_billing` echo for the `\"capability\"` scope) so callers can dispatch without parsing prose. Distinct from `BILLING_NOT_PERMITTED_FOR_AGENT`, which is narrowly scoped to the calling buyer agent's commercial relationship with the seller (passthrough-only vs agent-billable) rather than to the seller's capability or per-account state. Sellers MUST emit `BILLING_NOT_PERMITTED_FOR_AGENT` only when agent identity has been established via signed-request derivation or a credential-to-agent mapping in the seller's onboarding record; in all other cases (unauthenticated callers and bearer credentials not mapped to a specific agent record) sellers MUST return `BILLING_NOT_SUPPORTED` and MUST omit `error.details.scope` — emitting the per-agent code or the `\"account\"`-scope hint without established identity is a cross-tenant onboarding oracle (same uniform-response shape required by the `*_NOT_FOUND` family). Recovery: correctable (check `get_adcp_capabilities` for `supported_billing` and resubmit with a value the seller supports, or omit `billing` to accept the seller's default).", - "BILLING_NOT_PERMITTED_FOR_AGENT": "The seller's `supported_billing` capability accepts the requested model, but the calling buyer agent's commercial relationship with the seller does not — e.g., the agent is onboarded as passthrough-only (no payments relationship — only the operator can be invoiced) and `billing: 'agent'` or `billing: 'advertiser'` is rejected even though the seller supports both at the capability level. Distinct from `BILLING_NOT_SUPPORTED` (seller-wide capability) by being narrowly per-buyer-agent: the gate is the seller's onboarding record for this caller, not the seller's global wire capability. Sellers MUST emit this code only after agent identity has been established via signed-request derivation or a credential-to-agent mapping in the seller's onboarding record; callers without established identity MUST receive `BILLING_NOT_SUPPORTED` instead, to prevent the distinct code from acting as an onboarding oracle. The recovery shape is deliberately minimal — `error.details` MUST conform to `error-details/billing-not-permitted-for-agent.json` (`rejected_billing` plus an optional single `suggested_billing` retry value, typically `operator`) and MUST NOT carry the agent's full permitted-billing subset, rate cards, payment terms, credit limit, billing entity, or any other per-agent commercial state. Recovery: correctable (retry with `error.details.suggested_billing` when present; when absent, surface to a human at the buyer — the agent cannot unilaterally extend its commercial relationship and MUST NOT auto-retry, since payments-relationship onboarding with the seller is offline).", - "PAYMENT_TERMS_NOT_SUPPORTED": "The seller does not accept the requested `payment_terms` value for this account. Payment terms are never silently remapped — sellers either accept or reject. Distinct from `BILLING_NOT_SUPPORTED` (the `billing` enum) by being narrowly about the `payment_terms` enum on the same account. Recovery: correctable (omit `payment_terms` to accept the seller's default, retry with a different value the seller supports, or negotiate offline).", - "BRAND_REQUIRED": "A billable operation was attempted without a brand reference. Every billable operation requires either a seller-assigned `account_id` or a natural key including `brand`. Recovery: correctable (include `brand` — `domain` plus optional `brand_id` — on the request).", - "AGENT_SUSPENDED": "The calling buyer agent's commercial relationship with the seller is temporarily paused — the agent is onboarded but currently suspended. Sibling to `ACCOUNT_SUSPENDED` (account-wide) and `CAMPAIGN_SUSPENDED` (per-plan) but scoped to the agent-relationship axis (orthogonal to any specific account on that agent). The code itself is the discriminator — it does NOT carry an `error.details` payload (mirroring `BILLING_NOT_PERMITTED_FOR_AGENT`'s discriminator-by-code pattern), and MUST NOT carry per-agent commercial state (rate cards, payment terms, credit limit, billing entity, contact channels) since full disclosure of per-agent state in a single probe is a per-agent oracle. Cross-tenant onboarding oracle clamp + channel-coverage requirements (response shape, HTTP/A2A/MCP status, headers, side effects, observability, latency parity, retry-counter side channel) are normative in error-handling.mdx Per-Agent Authorization Gate; this description does not restate them to avoid drift. Recovery: terminal (re-onboarding may resolve the suspension; the agent MUST surface to a human at the buyer rather than auto-retrying — the agent cannot unilaterally lift a suspension, and re-attempts only reinforce the gate).", - "AGENT_BLOCKED": "The calling buyer agent's commercial relationship with the seller is permanently denied — the agent is blocked. Sibling to `AGENT_SUSPENDED` on the agent-relationship axis but with no recovery path (a suspension may lift via re-onboarding; a block does not). The code itself is the discriminator — same posture as `AGENT_SUSPENDED`: no `error.details` payload, no per-agent commercial state, cross-tenant onboarding oracle clamp + channel-coverage requirements normative in error-handling.mdx Per-Agent Authorization Gate. Recovery: terminal (no autonomous recovery — the agent MUST surface to a human at the buyer; relationships are reinstated only through offline operator action with the seller, not via any seller-callable AdCP task)." + "VERSION_UNSUPPORTED": "The declared adcp_major_version is not supported by this seller. Recovery: correctable (call get_adcp_capabilities without adcp_major_version to discover supported major_versions, then retry with a supported version).", + "CAMPAIGN_SUSPENDED": "Campaign governance has been suspended pending human review; the governance agent MUST reject `check_governance` and `report_plan_outcome` calls on the affected plan until the escalation is resolved. Distinct from `ACCOUNT_SUSPENDED` (account-wide) \u2014 this is scoped to a single plan/campaign. Recovery: transient (wait for the escalation to resolve; contact the plan operator if the suspension persists).", + "GOVERNANCE_UNAVAILABLE": "A registered governance agent is unreachable. Sellers MUST place this code in `errors[]` + `adcp_error` (never a structured rejection arm) and flip transport-level failure markers (HTTP 5xx, MCP `isError: true`, A2A `failed`). Distinct from `GOVERNANCE_DENIED` (agent reachable and explicitly denied \u2014 see that code's wire-placement guidance). Recovery: transient (retry with backoff; if the agent remains unreachable, the buyer MUST contact the plan's governance operator \u2014 the seller MUST NOT proceed with the media buy without a valid decision).\n\nWire placement (full guidance). Governance unavailability is a system error \u2014 the governance call FAILED (timeout, network, config error) and the seller could not get a verdict at all. Always populate both layers per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`. Do NOT use a structured rejection arm for unavailability even when the task offers one \u2014 the buyer's recovery semantics differ (retry-with-backoff for unavailability vs. restructure-or-escalate for denial), and conflating them masks the system-error signal.", + "PERMISSION_DENIED": "The authenticated caller is not authorized for the requested action under the seller's own policies, or a required signed credential (e.g., a `governance_context` token on a spend-commit) is missing, fails verification, or was issued for a different plan, seller, or phase. Distinct from `AUTH_REQUIRED` (no credentials presented) and `GOVERNANCE_DENIED` (governance agent denied). Recovery: correctable (call `check_governance` to mint a valid token, or contact the seller to resolve the underlying permission)." }, "enumMetadata": { - "$comment": "Structured recovery classification and remediation hints for each error code. SDKs MUST consume this block instead of parsing 'Recovery: X' from enumDescriptions prose. Each entry is { recovery, suggestion }. recovery is one of: correctable (caller can fix and retry), transient (retry with backoff), terminal (no autonomous recovery - operator intervention required). enumDescriptions is retained for human readability and will continue to carry the canonical narrative; the recovery classification embedded in that prose is normative and MUST match the value here.", + "$comment": "Structured recovery classification and remediation hints for each error code. SDKs MUST consume this block instead of parsing 'Recovery: X' from enumDescriptions prose. Each entry is { recovery, suggestion }. recovery is one of: correctable (caller can fix and retry), transient (retry with backoff), terminal (no autonomous recovery \u2014 operator intervention required). enumDescriptions is retained for human readability and will continue to carry the canonical narrative; the recovery classification embedded in that prose is normative and MUST match the value here.", "INVALID_REQUEST": { "recovery": "correctable", "suggestion": "check request parameters and fix" }, "AUTH_REQUIRED": { "recovery": "correctable", - "suggestion": "provide credentials via auth header" + "suggestion": "provide credentials via auth header on missing-credential case; do NOT auto-retry on presented-but-rejected credentials \u2014 escalate to operator for credential rotation (3.1+ splits this into AUTH_MISSING / AUTH_INVALID)" }, "RATE_LIMITED": { "recovery": "transient", @@ -308,67 +277,7 @@ }, "PERMISSION_DENIED": { "recovery": "correctable", - "suggestion": "call check_governance to mint a valid token, or contact the seller to resolve the underlying permission; when error.details.scope is 'agent' with reason 'sandbox_only' the rejection is terminal-pending-onboarding — surface to a human rather than auto-retrying. For suspended/blocked agent relationships, sellers emit AGENT_SUSPENDED / AGENT_BLOCKED instead (those codes carry recovery: terminal directly)." - }, - "SCOPE_INSUFFICIENT": { - "recovery": "correctable", - "suggestion": "the agent cannot broaden its own scope - surface to the operator rather than auto-retry" - }, - "READ_ONLY_SCOPE": { - "recovery": "correctable", - "suggestion": "use a non-mutating alternative, or surface to the operator to request a scope that permits mutation" - }, - "FIELD_NOT_PERMITTED": { - "recovery": "correctable", - "suggestion": "drop the disallowed field(s) and retry" - }, - "PROVENANCE_REQUIRED": { - "recovery": "correctable", - "suggestion": "attach a provenance object - at minimum digital_source_type - and resubmit" - }, - "PROVENANCE_DIGITAL_SOURCE_TYPE_MISSING": { - "recovery": "correctable", - "suggestion": "set provenance.digital_source_type to a value from the digital-source-type enum and resubmit" - }, - "PROVENANCE_DISCLOSURE_MISSING": { - "recovery": "correctable", - "suggestion": "set provenance.disclosure.required and, when true, populate disclosure.jurisdictions" - }, - "PROVENANCE_EMBEDDED_MISSING": { - "recovery": "correctable", - "suggestion": "attach at least one embedded_provenance entry from a supported provider and resubmit" - }, - "PROVENANCE_VERIFIER_NOT_ACCEPTED": { - "recovery": "correctable", - "suggestion": "replace verify_agent.agent_url with one from the seller's published accepted_verifiers, drop verify_agent if the embedding is self-verifiable, or re-embed with a verifier the seller accepts" - }, - "PROVENANCE_CLAIM_CONTRADICTED": { - "recovery": "correctable", - "suggestion": "revise the provenance claim to match the verifier's observation or replace the creative; auto-retry without correction will not pass" - }, - "BILLING_NOT_SUPPORTED": { - "recovery": "correctable", - "suggestion": "check get_adcp_capabilities for supported_billing and resubmit with a supported value, or omit billing to accept the seller's default" - }, - "BILLING_NOT_PERMITTED_FOR_AGENT": { - "recovery": "correctable", - "suggestion": "retry with error.details.suggested_billing (typically 'operator') when present; when absent, surface to a human at the buyer — the agent cannot unilaterally extend its commercial relationship and MUST NOT auto-retry" - }, - "PAYMENT_TERMS_NOT_SUPPORTED": { - "recovery": "correctable", - "suggestion": "omit payment_terms to accept the seller's default, retry with a different supported value, or negotiate offline" - }, - "BRAND_REQUIRED": { - "recovery": "correctable", - "suggestion": "include brand (domain plus optional brand_id) on the request" - }, - "AGENT_SUSPENDED": { - "recovery": "terminal", - "suggestion": "surface to a human at the buyer — the agent cannot unilaterally lift a suspension; re-onboarding with the seller offline may resolve" - }, - "AGENT_BLOCKED": { - "recovery": "terminal", - "suggestion": "surface to a human at the buyer — the relationship is permanently denied and is reinstated only through offline operator action with the seller, not via any seller-callable AdCP task" + "suggestion": "call check_governance to mint a valid token, or contact the seller to resolve the underlying permission" } } } \ No newline at end of file diff --git a/schemas/cache/extensions/index.json b/schemas/cache/extensions/index.json index 99ad74f3..09f061ac 100644 --- a/schemas/cache/extensions/index.json +++ b/schemas/cache/extensions/index.json @@ -3,6 +3,6 @@ "title": "AdCP Extension Registry", "description": "Auto-generated registry of formal AdCP extensions. Extensions provide typed schemas for vendor-specific or domain-specific data within the ext field. Agents declare which extensions they support in their agent card.", "_generated": true, - "_generatedAt": "2026-05-02T21:51:30.120Z", + "_generatedAt": "2026-05-08T11:43:56.277Z", "extensions": {} } \ No newline at end of file diff --git a/schemas/cache/index.json b/schemas/cache/index.json index 45f3acc3..2605280d 100644 --- a/schemas/cache/index.json +++ b/schemas/cache/index.json @@ -3,12 +3,12 @@ "title": "AdCP Schema Registry", "version": "1.0.0", "description": "Registry of all AdCP JSON schemas for validation and discovery", - "adcp_version": "3.0.5", + "adcp_version": "3.0.7", "versioning": { - "note": "AdCP uses build-time versioning. This directory contains schemas for AdCP 3.0.5. Full semantic versions are available at /schemas/{version}/ (e.g., /schemas/2.5.0/). Major version aliases point to the latest release: /schemas/v3/ \u2192 /schemas/3.0.5/." + "note": "AdCP uses build-time versioning. This directory contains schemas for AdCP 3.0.7. Full semantic versions are available at /schemas/{version}/ (e.g., /schemas/2.5.0/). Major version aliases point to the latest release: /schemas/v3/ \u2192 /schemas/3.0.7/." }, - "lastUpdated": "2026-05-02", - "baseUrl": "/schemas/3.0.5", + "lastUpdated": "2026-05-08", + "baseUrl": "/schemas/3.0.7", "schemas": { "core": { "description": "Core data models used throughout AdCP", diff --git a/schemas/cache/manifest.json b/schemas/cache/manifest.json index 4668a6a7..1115fdb3 100644 --- a/schemas/cache/manifest.json +++ b/schemas/cache/manifest.json @@ -1,7 +1,7 @@ { - "$schema": "/schemas/3.0.5/manifest.schema.json", - "adcp_version": "3.0.5", - "generated_at": "2026-05-02T21:51:30.257Z", + "$schema": "/schemas/3.0.7/manifest.schema.json", + "adcp_version": "3.0.7", + "generated_at": "2026-05-08T11:43:56.415Z", "tools": { "acquire_rights": { "protocol": "brand", @@ -741,7 +741,7 @@ }, "GOVERNANCE_DENIED": { "recovery": "correctable", - "description": "A registered governance agent denied the transaction. The buyer may restructure the buy (e.g., reduce budget, split into smaller transactions), escalate to human spending authority, or contact the governance agent for details.", + "description": "A registered governance agent denied the transaction. Sellers MUST place the denial in the task's structured rejection arm when one exists (e.g., `acquire_rights` \u2192 `AcquireRightsRejected`, `creative_approval` \u2192 `CreativeRejected`); otherwise in `errors[]` + `adcp_error`. Buyers MUST dispatch on the response's discriminated `status` first and fall back to `errors[].code` / `adcp_error.code` only when no rejection arm exists for that task. The buyer may restructure the buy (e.g., reduce budget, split into smaller transactions), escalate to human spending authority, or contact the governance agent for details. Wire placement (full guidance). Governance denial is a structured business outcome, not a system error \u2014 the governance call SUCCEEDED and the agent returned a denial verdict. Two cases: 1. Task response defines a structured rejection arm. The arm IS the canonical denial shape. The seller populates `reason` (human-readable, propagating governance findings) and `suggestions` (optional) and does NOT additionally emit `GOVERNANCE_DENIED` in `errors[]` or `adcp_error`. The rejection arms enforce this at the schema layer: e.g., `AcquireRightsRejected` and `CreativeRejected` both declare `not: { required: [errors] }`, so dual-emission is already a schema violation. The code does not appear on the wire when the rejection arm is used. Transport-level success markers MUST NOT be flipped (HTTP 200, MCP `isError: false`, A2A `succeeded`) \u2014 the task ran successfully and produced a structured response. 2. Task response has no rejection arm (e.g., `create_media_buy` returns Success / Error / Submitted arms only). The seller populates `errors[].code: GOVERNANCE_DENIED` in the payload AND `adcp_error.code: GOVERNANCE_DENIED` on the envelope per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`. Transport-level failure markers DO flip in this case (HTTP 4xx, MCP `isError: true`, A2A `failed`) \u2014 the task could not produce a success artifact. The rule generalizes to any current or future task whose response defines a discriminated rejection arm. In either placement, sellers SHOULD propagate governance findings verbatim \u2014 buyers' recovery decisions depend on what specifically was rejected. `GOVERNANCE_DENIED` is reserved for verdicts received from a reachable governance agent; if the governance call itself failed (timeout, network, config error), use `GOVERNANCE_UNAVAILABLE` instead.", "suggestion": "restructure the buy, escalate to human spending authority, or contact the governance agent for details" }, "BUDGET_EXHAUSTED": { @@ -866,7 +866,7 @@ }, "GOVERNANCE_UNAVAILABLE": { "recovery": "transient", - "description": "A registered governance agent is unreachable (timeout, network error, or repeated failure) and the seller cannot obtain a governance decision for the spend-commit. Distinct from `GOVERNANCE_DENIED` (agent reachable and explicitly denied).", + "description": "A registered governance agent is unreachable. Sellers MUST place this code in `errors[]` + `adcp_error` (never a structured rejection arm) and flip transport-level failure markers (HTTP 5xx, MCP `isError: true`, A2A `failed`). Distinct from `GOVERNANCE_DENIED` (agent reachable and explicitly denied \u2014 see that code's wire-placement guidance). Wire placement (full guidance). Governance unavailability is a system error \u2014 the governance call FAILED (timeout, network, config error) and the seller could not get a verdict at all. Always populate both layers per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`. Do NOT use a structured rejection arm for unavailability even when the task offers one \u2014 the buyer's recovery semantics differ (retry-with-backoff for unavailability vs. restructure-or-escalate for denial), and conflating them masks the system-error signal.", "suggestion": "retry with backoff; if the agent remains unreachable, the buyer MUST contact the plan's governance operator" }, "PERMISSION_DENIED": { diff --git a/scripts/diff_generated_types.py b/scripts/diff_generated_types.py new file mode 100644 index 00000000..8415657d --- /dev/null +++ b/scripts/diff_generated_types.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python3 +"""Diff Pydantic-model field shape between two generated_poc/ trees. + +Codegen lands as ``chore(schemas): sync …`` with hundreds of file changes, so +downstream consumers (salesagent, ad servers) can't tell from release notes +which model gained or lost a field. This script walks a generated tree, AST- +parses each module, and emits per-class field sets — then diffs two such +snapshots to produce a markdown report consumers read to know whether they can +shrink their schema-mismatch allowlists. + +Usage: + # Capture a snapshot of the current tree to JSON + python scripts/diff_generated_types.py snapshot \\ + src/adcp/types/generated_poc/ /tmp/before.json + + # After regen, write a markdown delta against the snapshot + python scripts/diff_generated_types.py diff \\ + /tmp/before.json src/adcp/types/generated_poc/ \\ + --output SCHEMA_DELTAS.md + +The library API (``snapshot``, ``format_diff``) is what ``generate_types.py`` +calls in-process to avoid temp files. +""" + +from __future__ import annotations + +import argparse +import ast +import json +import sys +from pathlib import Path + +# {relative_posix_path: {ClassName: [field_name, ...]}} +Snapshot = dict[str, dict[str, list[str]]] + + +def snapshot(root: Path) -> Snapshot: + """Walk ``root`` for .py files and return per-class member-name lists. + + Captures both Pydantic field declarations (``AnnAssign``: ``name: Annotated + [...] = default``) and plain class-level assignments (``Assign``: + ``NAME = 'NAME'``). The latter is required to track Enum members — without + it, deleted error codes or status values slip through the diff invisibly. + Module-level configuration assignments (``model_config = ConfigDict(...)``) + are excluded by capturing only the simple-name targets we care about, but + all such targets at the class body level are kept since Pydantic does not + use bare ``Assign`` for fields. + """ + out: Snapshot = {} + for py_file in sorted(root.rglob("*.py")): + if py_file.name == "__init__.py": + continue + rel = py_file.relative_to(root).as_posix() + try: + tree = ast.parse(py_file.read_text(encoding="utf-8")) + except (SyntaxError, UnicodeDecodeError): + continue + per_file: dict[str, list[str]] = {} + for node in ast.walk(tree): + if not isinstance(node, ast.ClassDef): + continue + fields: list[str] = [] + for child in node.body: + if isinstance(child, ast.AnnAssign) and isinstance(child.target, ast.Name): + if child.target.id != "model_config": + fields.append(child.target.id) + elif isinstance(child, ast.Assign): + for target in child.targets: + if isinstance(target, ast.Name) and target.id != "model_config": + fields.append(target.id) + if fields: + per_file[node.name] = fields + if per_file: + out[rel] = per_file + return out + + +def format_diff(before: Snapshot, after: Snapshot) -> str: + """Render the difference between two snapshots as Markdown. + + The report has three top-level sections: files added, files removed, and + per-file class/field changes. Empty sections are omitted so the report is + blank when codegen produced no semantic delta. + """ + files_added = sorted(set(after) - set(before)) + files_removed = sorted(set(before) - set(after)) + files_common = sorted(set(before) & set(after)) + + file_changes: list[tuple[str, list[str]]] = [] + for rel in files_common: + b_classes = before[rel] + a_classes = after[rel] + classes_added = sorted(set(a_classes) - set(b_classes)) + classes_removed = sorted(set(b_classes) - set(a_classes)) + classes_common = sorted(set(b_classes) & set(a_classes)) + + lines: list[str] = [] + if classes_added: + lines.append(f" - **classes added**: {', '.join(classes_added)}") + if classes_removed: + lines.append(f" - **classes removed**: {', '.join(classes_removed)}") + for cls in classes_common: + b_fields = set(b_classes[cls]) + a_fields = set(a_classes[cls]) + added = sorted(a_fields - b_fields) + removed = sorted(b_fields - a_fields) + if added or removed: + bits: list[str] = [] + if added: + bits.append(f"`+{'`, `+'.join(added)}`") + if removed: + bits.append(f"`-{'`, `-'.join(removed)}`") + lines.append(f" - `{cls}`: {' '.join(bits)}") + if lines: + file_changes.append((rel, lines)) + + parts: list[str] = ["# Generated-types delta", ""] + if not (files_added or files_removed or file_changes): + parts.append("_No field-shape changes detected._") + parts.append("") + return "\n".join(parts) + + if files_added: + parts.append("## Files added") + parts.append("") + for rel in files_added: + classes = ", ".join(sorted(after[rel])) + parts.append(f"- `{rel}` — {classes}") + parts.append("") + + if files_removed: + parts.append("## Files removed") + parts.append("") + for rel in files_removed: + classes = ", ".join(sorted(before[rel])) + parts.append(f"- `{rel}` — {classes}") + parts.append("") + + if file_changes: + parts.append("## Field changes") + parts.append("") + for rel, lines in file_changes: + parts.append(f"- `{rel}`") + parts.extend(lines) + parts.append("") + + return "\n".join(parts) + + +def _cmd_snapshot(args: argparse.Namespace) -> int: + snap = snapshot(Path(args.root)) + Path(args.output).write_text(json.dumps(snap, indent=2, sort_keys=True), encoding="utf-8") + print(f"Wrote snapshot of {len(snap)} files to {args.output}") + return 0 + + +def _file_signature(classes: dict[str, list[str]]) -> frozenset[frozenset[str]]: + """Per-file signature: the multiset of field-name sets, class-name-agnostic. + + datamodel-code-generator produces numbered-variant class names + (``PackageUpdate1`` vs ``PackageUpdate4``, ``Status16`` vs ``Status17``) + whose numbering depends on filesystem-iteration order — APFS on macOS + sorts differently than ext4 on Linux CI, so naive class-name diffs + flag every regen. The signature collapses ``{"PackageUpdate1": [a,b]}`` + and ``{"PackageUpdate4": [a,b]}`` to the same value, so renaming + without semantic change is invisible. A real change (added or removed + field, added or removed class) shifts the multiset of frozensets and + is caught. + """ + return frozenset(frozenset(fields) for fields in classes.values()) + + +def find_drift(before: Snapshot, after: Snapshot) -> list[tuple[str, str]]: + """List ``(path, reason)`` for files whose semantic signature changed. + + Returns the empty list when the two snapshots are semantically + equivalent — class-name renumbering is collapsed by + :func:`_file_signature`. + """ + drifted: list[tuple[str, str]] = [] + for path in sorted(set(before) | set(after)): + if path not in before: + drifted.append((path, "file added")) + continue + if path not in after: + drifted.append((path, "file removed")) + continue + if _file_signature(before[path]) != _file_signature(after[path]): + drifted.append((path, "field set changed")) + return drifted + + +def _cmd_diff(args: argparse.Namespace) -> int: + before_path = Path(args.before) + if not before_path.exists(): + print(f"Snapshot not found: {before_path}", file=sys.stderr) + return 1 + before: Snapshot = json.loads(before_path.read_text(encoding="utf-8")) + after = snapshot(Path(args.after_root)) + report = format_diff(before, after) + if args.output: + Path(args.output).write_text(report, encoding="utf-8") + print(f"Wrote delta report to {args.output}") + else: + print(report) + return 0 + + +def _cmd_check(args: argparse.Namespace) -> int: + before_path = Path(args.before) + if not before_path.exists(): + print(f"Snapshot not found: {before_path}", file=sys.stderr) + return 1 + before: Snapshot = json.loads(before_path.read_text(encoding="utf-8")) + after = snapshot(Path(args.after_root)) + drifted = find_drift(before, after) + if not drifted: + print("✓ No semantic drift (class-name renumbering ignored).") + return 0 + + sys.stderr.write(format_diff(before, after)) + sys.stderr.write("\n") + sys.stderr.write(f"\n✗ {len(drifted)} file(s) drift between committed tree and regen output:\n") + for path, reason in drifted[:50]: + sys.stderr.write(f" - {path} ({reason})\n") + if len(drifted) > 50: + sys.stderr.write(f" ... {len(drifted) - 50} more\n") + sys.stderr.write( + "\nThe committed src/adcp/types/generated_poc/ does not match what\n" + "scripts/generate_types.py produces from schemas/cache/. Either:\n" + " - re-run sync + regen and commit the result:\n" + " python scripts/sync_schemas.py && python scripts/generate_types.py\n" + " - or, if upstream changed, bump src/adcp/ADCP_VERSION and regen\n" + " - and never hand-edit generated_poc/ — see scripts/post_generate_fixes.py\n" + " for the sanctioned post-regen patch path.\n" + ) + return 1 + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0] if __doc__ else "") + sub = parser.add_subparsers(dest="cmd", required=True) + + p_snap = sub.add_parser("snapshot", help="Capture per-class field snapshot to JSON.") + p_snap.add_argument("root", help="generated_poc/ directory to walk") + p_snap.add_argument("output", help="JSON file to write") + p_snap.set_defaults(func=_cmd_snapshot) + + p_diff = sub.add_parser("diff", help="Diff a JSON snapshot against a current tree.") + p_diff.add_argument("before", help="JSON snapshot from `snapshot` command") + p_diff.add_argument("after_root", help="generated_poc/ directory to walk") + p_diff.add_argument("--output", help="Write markdown report to this path (default: stdout)") + p_diff.set_defaults(func=_cmd_diff) + + p_check = sub.add_parser( + "check", + help=( + "Strict CI gate: exit non-zero if a tree's per-file field signature " + "differs from a JSON snapshot. Class-name renumbering is ignored." + ), + ) + p_check.add_argument("before", help="JSON snapshot from `snapshot` command") + p_check.add_argument("after_root", help="generated_poc/ directory to walk") + p_check.set_defaults(func=_cmd_check) + + args = parser.parse_args() + rc: int = args.func(args) + return rc + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/generate_types.py b/scripts/generate_types.py index fa24090b..30451f42 100755 --- a/scripts/generate_types.py +++ b/scripts/generate_types.py @@ -15,11 +15,14 @@ import sys from pathlib import Path +import diff_generated_types + # Paths REPO_ROOT = Path(__file__).parent.parent SCHEMAS_DIR = REPO_ROOT / "schemas" / "cache" OUTPUT_DIR = REPO_ROOT / "src" / "adcp" / "types" / "generated_poc" TEMP_DIR = REPO_ROOT / ".schema_temp" +DELTAS_FILE = REPO_ROOT / "SCHEMA_DELTAS.md" def rewrite_refs(obj, current_schema_rel_path: Path): @@ -378,7 +381,17 @@ def main(): print(f"Output: {OUTPUT_DIR}\n") temp_schemas = None + before_snapshot: dict = {} try: + # Snapshot the current generated tree before wiping it. The wipe-and- + # regen pattern means we lose the only record of "what fields existed + # last release" unless we capture it now. The diff produced after + # generation lands in SCHEMA_DELTAS.md so consumers can shrink their + # known-mismatch allowlists without grepping the raw diff. + if OUTPUT_DIR.exists(): + before_snapshot = diff_generated_types.snapshot(OUTPUT_DIR) + print(f"Captured pre-regen snapshot: {len(before_snapshot)} files\n") + # Clean output directory to prevent stale files # This ensures old/renamed schema files don't persist if OUTPUT_DIR.exists(): @@ -452,6 +465,11 @@ def main(): print(f" Output: {OUTPUT_DIR}") print(f" Files: {len(py_files)}") + after_snapshot = diff_generated_types.snapshot(OUTPUT_DIR) + report = diff_generated_types.format_diff(before_snapshot, after_snapshot) + DELTAS_FILE.write_text(report, encoding="utf-8") + print(f" Delta report: {DELTAS_FILE.relative_to(REPO_ROOT)}") + return 0 except Exception as e: diff --git a/skills/call-adcp-agent/SKILL.md b/skills/call-adcp-agent/SKILL.md index 5c4fc97a..d3da9200 100644 --- a/skills/call-adcp-agent/SKILL.md +++ b/skills/call-adcp-agent/SKILL.md @@ -115,11 +115,22 @@ Every validation failure produces: } ``` +**Required fields — every conformant validator surfaces these:** + - `issues[].pointer` — RFC 6901 JSON Pointer to the field. - `issues[].keyword` — AJV keyword (`required`, `type`, `oneOf`, `anyOf`, `additionalProperties`, `format`, `enum`). - `issues[].variants` — when the keyword is `oneOf` or `anyOf`, each entry lists one variant's `required` + declared `properties`. **Pick ONE variant**, send only its `required` fields. This is the fastest recovery path when you didn't know the field was a union. -Patch the pointers, don't re-guess what the skill or the `variants` already told you, resend. Three attempts should cover every field. +**Spec-optional wire fields — sellers MAY emit per `error.json`:** + +- `issues[].schema_id` — `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). Diagnostic; the actionable lever is `discriminator` + `variants` + `pointer`. Sellers MUST omit when the rejection is against a private extension or pre-release element. See [error-handling.mdx](../docs/protocol/error-handling.mdx). +- `issues[].discriminator` — `[{property_name, value}, …]` pairs identifying the const-discriminated `oneOf`/`anyOf` variant the validator selected from values present in the payload. Reads as "you targeted this branch; the missing/wrong fields are at the same level." Compound discriminators like `audience-selector`'s `(type, value_type)` produce two-entry arrays. Example: `discriminator: [{property_name: 'type', value: 'key_value'}]` plus `pointer: '/deployments/0/activation_key/key'` and `keyword: 'required'` means "you picked the `key_value` activation_key variant and it requires top-level `key` and `value`." + +Both fields are optional in the spec — their presence shortens recovery; their absence just means falling back to `pointer` + `keyword` + `variants`. They are wire-level: a Python or Go caller reading the raw JSON sees them as `schema_id` and `discriminator`. SDKs that normalize keys (e.g. `@adcp/sdk` camelCases to `schemaId`) surface the SDK-shaped name. + +**Recovery order:** patch the `pointer`s using `keyword` + `variants`, resend. If `discriminator` is present, prefer it — it names the branch directly so you don't have to walk `variants`. If `schema_id` is present, use it for diagnostic logging only. Three attempts should cover every field. + +> **SDK-side enrichment.** Some SDKs synthesize additional fields client-side after parsing — e.g. `@adcp/sdk` adds `hint` (one-sentence curated recipes for known shape gotchas) and `allowedValues` (closed enum lists for `keyword: 'enum'`). These are **not** wire fields and are not emitted by sellers; if you're not using that SDK, you won't see them regardless of the seller. When present, prefer them over walking `variants`. See your SDK's docs for the full list. ## Minimal working examples @@ -225,7 +236,9 @@ Quick lookup before reading the full envelope. Match what you see in `adcp_error | Symptom | What it means | Fix | |---|---|---| | `keyword: 'oneOf'` with `variants[]` | Discriminated union — you sent fields from multiple variants, or none | Pick ONE variant from `variants[]`. Send only its `required` fields. | -| 2-3 `additionalProperties` errors at the same pointer | You merged `oneOf` variants ({account_id, brand, operator, …}) | Drop to one variant. Don't keep "extra" fields "for completeness". | +| `discriminator: [{property_name, value}]` on a `required` issue | Seller's validator inferred which branch you targeted; you missed required fields IN that branch | Read the `discriminator` pair, fill the missing required fields at the same level (don't nest under the discriminator property name). | +| `hint:` field present (SDK-side enrichment, not on the wire) | Your SDK matched a curated shape-gotcha rule | Apply the hint directly — it's the validated fix path. | +| 2-3 `additionalProperties` errors at the same pointer | You merged `oneOf` variants (`` `{account_id, brand, operator, …}` ``) | Drop to one variant. Don't keep "extra" fields "for completeness". | | `keyword: 'required'`, `pointer: '/idempotency_key'` | Mutating tool, no UUID | Generate fresh UUID per logical operation. Reuse it on retries. | | `keyword: 'type'` or `additionalProperties` at `/budget` | Sent `{amount, currency}` | `budget` is a number. Currency is implied by `pricing_option_id`. | | `additionalProperties` at `/format_id` (string passed) | Sent `"format_id": "video_..."` | `format_id` is `{agent_url, id}` — always an object. | diff --git a/src/adcp/ADCP_VERSION b/src/adcp/ADCP_VERSION index eca690e7..2451c27c 100644 --- a/src/adcp/ADCP_VERSION +++ b/src/adcp/ADCP_VERSION @@ -1 +1 @@ -3.0.5 +3.0.7 diff --git a/src/adcp/types/_generated.py b/src/adcp/types/_generated.py index 63a913b8..1a649a25 100644 --- a/src/adcp/types/_generated.py +++ b/src/adcp/types/_generated.py @@ -10,7 +10,7 @@ DO NOT EDIT MANUALLY. Generated from: https://github.com/adcontextprotocol/adcp/tree/main/schemas -Generation date: 2026-05-03 00:08:29 UTC +Generation date: 2026-05-08 18:05:19 UTC """ # ruff: noqa: E501, I001 diff --git a/src/adcp/types/generated_poc/adagents.py b/src/adcp/types/generated_poc/adagents.py index 6662f76d..2aa9fe05 100644 --- a/src/adcp/types/generated_poc/adagents.py +++ b/src/adcp/types/generated_poc/adagents.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: adagents.json -# timestamp: 2026-05-03T00:08:21+00:00 +# timestamp: 2026-05-08T13:40:47+00:00 from __future__ import annotations @@ -715,12 +715,12 @@ class AdcpAgentsAuthorization(RootModel[AdcpAgentsAuthorization1 | AdcpAgentsAut description='Declaration of authorized agents for advertising inventory and data signals. Hosted at /.well-known/adagents.json on publisher domains (for properties) or data provider domains (for signals). Can either contain the full structure inline or reference an authoritative URL.', examples=[ { - '$schema': '/schemas/3.0.5/adagents.json', + '$schema': '/schemas/3.0.7/adagents.json', 'authoritative_location': 'https://cdn.example.com/adagents/v2/adagents.json', 'last_updated': '2025-01-15T10:00:00Z', }, { - '$schema': '/schemas/3.0.5/adagents.json', + '$schema': '/schemas/3.0.7/adagents.json', 'properties': [ { 'property_id': 'example_site', @@ -780,7 +780,7 @@ class AdcpAgentsAuthorization(RootModel[AdcpAgentsAuthorization1 | AdcpAgentsAut 'last_updated': '2025-01-10T12:00:00Z', }, { - '$schema': '/schemas/3.0.5/adagents.json', + '$schema': '/schemas/3.0.7/adagents.json', 'contact': { 'name': 'Meta Advertising Operations', 'email': 'adops@meta.com', @@ -849,7 +849,7 @@ class AdcpAgentsAuthorization(RootModel[AdcpAgentsAuthorization1 | AdcpAgentsAut 'last_updated': '2025-01-10T15:30:00Z', }, { - '$schema': '/schemas/3.0.5/adagents.json', + '$schema': '/schemas/3.0.7/adagents.json', 'contact': {'name': 'Tumblr Advertising'}, 'properties': [ { @@ -877,7 +877,7 @@ class AdcpAgentsAuthorization(RootModel[AdcpAgentsAuthorization1 | AdcpAgentsAut 'last_updated': '2025-01-10T16:00:00Z', }, { - '$schema': '/schemas/3.0.5/adagents.json', + '$schema': '/schemas/3.0.7/adagents.json', 'contact': { 'name': 'Example Third-Party Sales Agent', 'email': 'sales@agent.example', @@ -917,7 +917,7 @@ class AdcpAgentsAuthorization(RootModel[AdcpAgentsAuthorization1 | AdcpAgentsAut 'last_updated': '2025-01-10T17:00:00Z', }, { - '$schema': '/schemas/3.0.5/adagents.json', + '$schema': '/schemas/3.0.7/adagents.json', 'contact': { 'name': 'Premium News Publisher', 'email': 'adops@news.example.com', @@ -975,7 +975,7 @@ class AdcpAgentsAuthorization(RootModel[AdcpAgentsAuthorization1 | AdcpAgentsAut 'last_updated': '2025-01-10T18:00:00Z', }, { - '$schema': '/schemas/3.0.5/adagents.json', + '$schema': '/schemas/3.0.7/adagents.json', 'contact': { 'name': 'Polk Automotive Data', 'email': 'partnerships@polk.com', diff --git a/src/adcp/types/generated_poc/brand/__init__.py b/src/adcp/types/generated_poc/brand/__init__.py index 25ab7c53..bdb244bd 100644 --- a/src/adcp/types/generated_poc/brand/__init__.py +++ b/src/adcp/types/generated_poc/brand/__init__.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: brand.json -# timestamp: 2026-05-03T00:08:21+00:00 +# timestamp: 2026-05-08T13:40:47+00:00 from __future__ import annotations @@ -1521,23 +1521,23 @@ class BrandDiscovery( description='Brand identity and discovery file. Hosted at /.well-known/brand.json on house domains. Contains the full brand portfolio with identity, creative assets, and digital properties. Brands are identified by house + brand_id (like properties are identified by publisher + property_id). Supports variants: house portfolio (full brand data), brand agent (agent provides brand info via MCP), house redirect (pointer to house domain), or authoritative location redirect.', examples=[ { - '$schema': '/schemas/3.0.5/brand.json', + '$schema': '/schemas/3.0.7/brand.json', 'authoritative_location': 'https://adcontextprotocol.org/brand/abc123/brand.json', }, { - '$schema': '/schemas/3.0.5/brand.json', + '$schema': '/schemas/3.0.7/brand.json', 'house': 'nikeinc.com', 'note': 'Redirect to house domain for full brand portfolio', }, { - '$schema': '/schemas/3.0.5/brand.json', + '$schema': '/schemas/3.0.7/brand.json', 'version': '1.0', 'agents': [ {'type': 'brand', 'url': 'https://agent.acme.com/mcp', 'id': 'acme_brand'} ], }, { - '$schema': '/schemas/3.0.5/brand.json', + '$schema': '/schemas/3.0.7/brand.json', 'version': '1.0', 'house': { 'domain': 'pg.com', @@ -1774,7 +1774,7 @@ class BrandDiscovery( 'last_updated': '2026-01-15T10:00:00Z', }, { - '$schema': '/schemas/3.0.5/brand.json', + '$schema': '/schemas/3.0.7/brand.json', 'version': '1.0', 'house': { 'domain': 'nikeinc.com', @@ -1870,7 +1870,7 @@ class BrandDiscovery( 'last_updated': '2026-01-15T10:00:00Z', }, { - '$schema': '/schemas/3.0.5/brand.json', + '$schema': '/schemas/3.0.7/brand.json', 'version': '1.0', 'house': { 'domain': 'mediavine.com', diff --git a/src/adcp/types/generated_poc/bundled/core/tasks_list_response.py b/src/adcp/types/generated_poc/bundled/core/tasks_list_response.py index 99ed2228..6cd03078 100644 --- a/src/adcp/types/generated_poc/bundled/core/tasks_list_response.py +++ b/src/adcp/types/generated_poc/bundled/core/tasks_list_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: bundled/core/tasks_list_response.json -# timestamp: 2026-05-02T19:36:29+00:00 +# timestamp: 2026-05-08T18:05:13+00:00 from __future__ import annotations diff --git a/src/adcp/types/generated_poc/bundled/creative/list_creative_formats_response.py b/src/adcp/types/generated_poc/bundled/creative/list_creative_formats_response.py index 2983761a..1ee99a65 100644 --- a/src/adcp/types/generated_poc/bundled/creative/list_creative_formats_response.py +++ b/src/adcp/types/generated_poc/bundled/creative/list_creative_formats_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: bundled/creative/list_creative_formats_response.json -# timestamp: 2026-05-02T19:36:29+00:00 +# timestamp: 2026-05-08T18:05:13+00:00 from __future__ import annotations diff --git a/src/adcp/types/generated_poc/core/store_item.py b/src/adcp/types/generated_poc/core/store_item.py index b569bc25..f8692149 100644 --- a/src/adcp/types/generated_poc/core/store_item.py +++ b/src/adcp/types/generated_poc/core/store_item.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: core/store_item.json -# timestamp: 2026-05-02T19:36:29+00:00 +# timestamp: 2026-05-08T18:05:13+00:00 from __future__ import annotations diff --git a/src/adcp/types/generated_poc/enums/error_code.py b/src/adcp/types/generated_poc/enums/error_code.py index 6172a763..1da0d1c5 100644 --- a/src/adcp/types/generated_poc/enums/error_code.py +++ b/src/adcp/types/generated_poc/enums/error_code.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: enums/error_code.json -# timestamp: 2026-05-03T02:59:35+00:00 +# timestamp: 2026-05-08T13:40:47+00:00 from __future__ import annotations @@ -53,18 +53,3 @@ class ErrorCode(Enum): CAMPAIGN_SUSPENDED = 'CAMPAIGN_SUSPENDED' GOVERNANCE_UNAVAILABLE = 'GOVERNANCE_UNAVAILABLE' PERMISSION_DENIED = 'PERMISSION_DENIED' - SCOPE_INSUFFICIENT = 'SCOPE_INSUFFICIENT' - READ_ONLY_SCOPE = 'READ_ONLY_SCOPE' - FIELD_NOT_PERMITTED = 'FIELD_NOT_PERMITTED' - PROVENANCE_REQUIRED = 'PROVENANCE_REQUIRED' - PROVENANCE_DIGITAL_SOURCE_TYPE_MISSING = 'PROVENANCE_DIGITAL_SOURCE_TYPE_MISSING' - PROVENANCE_DISCLOSURE_MISSING = 'PROVENANCE_DISCLOSURE_MISSING' - PROVENANCE_EMBEDDED_MISSING = 'PROVENANCE_EMBEDDED_MISSING' - PROVENANCE_VERIFIER_NOT_ACCEPTED = 'PROVENANCE_VERIFIER_NOT_ACCEPTED' - PROVENANCE_CLAIM_CONTRADICTED = 'PROVENANCE_CLAIM_CONTRADICTED' - BILLING_NOT_SUPPORTED = 'BILLING_NOT_SUPPORTED' - BILLING_NOT_PERMITTED_FOR_AGENT = 'BILLING_NOT_PERMITTED_FOR_AGENT' - PAYMENT_TERMS_NOT_SUPPORTED = 'PAYMENT_TERMS_NOT_SUPPORTED' - BRAND_REQUIRED = 'BRAND_REQUIRED' - AGENT_SUSPENDED = 'AGENT_SUSPENDED' - AGENT_BLOCKED = 'AGENT_BLOCKED' diff --git a/src/adcp/types/generated_poc/extensions/extension_meta.py b/src/adcp/types/generated_poc/extensions/extension_meta.py index 76f1a3ee..a2f3a966 100644 --- a/src/adcp/types/generated_poc/extensions/extension_meta.py +++ b/src/adcp/types/generated_poc/extensions/extension_meta.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: extensions/extension_meta.json -# timestamp: 2026-05-03T00:08:21+00:00 +# timestamp: 2026-05-08T18:05:13+00:00 from __future__ import annotations diff --git a/src/adcp/types/generated_poc/pricing_options/cpm_option.py b/src/adcp/types/generated_poc/pricing_options/cpm_option.py index 08ab4004..ec34bac1 100644 --- a/src/adcp/types/generated_poc/pricing_options/cpm_option.py +++ b/src/adcp/types/generated_poc/pricing_options/cpm_option.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: pricing_options/cpm_option.json -# timestamp: 2026-05-02T19:36:29+00:00 +# timestamp: 2026-05-08T18:05:13+00:00 from __future__ import annotations diff --git a/tests/test_error_code_conformance.py b/tests/test_error_code_conformance.py index be3d7f9b..562f39bd 100644 --- a/tests/test_error_code_conformance.py +++ b/tests/test_error_code_conformance.py @@ -102,6 +102,19 @@ "or cross-tenant proposal_id. Spec issue: " "https://github.com/adcontextprotocol/adcp/issues/4043." ), + # TODO: drop when ADCP_VERSION >= 3.1. + # Present in the spec's source-of-truth (`static/schemas/source/enums/ + # error-code.json` on adcontextprotocol/adcp `main`) but not in any + # tagged 3.0.x dist bundle. The 3.0.x bundle is frozen at 45 codes; + # the source has 62. The framework's `validate_billing_for_agent` + # raises this code with `error.details` shaped per the + # `error-details/billing-not-permitted-for-agent.json` schema — + # collapsing to PERMISSION_DENIED would erase the `rejected_billing` + # / `suggested_billing` discriminator the spec defines for this gate. + "BILLING_NOT_PERMITTED_FOR_AGENT": ( + "Per-agent billing gate raised by validate_billing_for_agent. " + "In source/main (3.1), absent from 3.0.x dist bundles." + ), } CANONICAL_CODES: frozenset[str] = frozenset(member.value for member in ErrorCode) @@ -191,9 +204,20 @@ def test_canonical_enum_is_loaded() -> None: """Sanity-check: the bundled enum has the expected shape. Pins the assumption that the generated ``ErrorCode`` enum mirrors - the schema. If this drifts (e.g. the schema gains a code), this - test surfaces the drift before the conformance walker silently - starts accepting it as canonical. + the dist-bundle schema for the pinned ``ADCP_VERSION``. If this + drifts (e.g. the schema gains a code), this test surfaces the drift + before the conformance walker silently starts accepting it as + canonical. + + Source of truth is the **dist bundle** for the pinned + ``ADCP_VERSION`` (``schemas/cache/enums/error-code.json`` after a + clean ``scripts/sync_schemas.py`` run). Never source from the spec + repo's ``static/schemas/source/`` directly — that tracks the next + major-version's WIP and will leak forward state into a maintenance- + line SDK. PR #429 made that mistake (pinned this count to 60 from + source/main while ``ADCP_VERSION=3.0.5`` only carried 45 in dist), + and the regression survived months because no CI step re-ran sync + and codegen against the pinned count. """ assert "PERMISSION_DENIED" in CANONICAL_CODES assert "ACCOUNT_SUSPENDED" in CANONICAL_CODES @@ -204,7 +228,7 @@ def test_canonical_enum_is_loaded() -> None: # If this assertion fails, the bundled error-code.json was resynced; # update both the count AND audit allowlist entries that may now be # in the canonical enum. - assert len(CANONICAL_CODES) == 60, f"Expected 60 spec error codes, got {len(CANONICAL_CODES)}" + assert len(CANONICAL_CODES) == 45, f"Expected 45 spec error codes, got {len(CANONICAL_CODES)}" def test_adcp_error_codes_are_spec_conformant() -> None: