From c0476ef36be2007d91d79e0c6338a88a73468200 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Tue, 10 Nov 2026 20:50:20 -0500 Subject: [PATCH 1/2] feat(schema): add reserved-word-safe replacements for 10 enum positions and deprecate the originals (#8791) Step 1 of #8791. Each enum position whose value collides with an SDK reserved word (`pass`, `global`) or with the enum member protocol (`name`, `value`) now accepts a replacement beside the original: - pass -> passed: enums/binary-verdict.json, get_media_buy_artifacts local_verdict, registry feed compliance tracks - global -> agent: RATE_LIMITED details scope - name -> full_name: signal-definition and signal-definition-enrichment onboarder.match_keys - brand.json color_ref kind: name -> palette_key, value -> hex, in the property enum and in each oneOf variant (const becomes a two-value enum, so both spellings select the same variant) Each original is listed in x-deprecated-enum-values and carries deprecated, x-deprecated-in 3.3.0, x-removed-in 4.0.0 and x-superseded-by in a per-value enumMetadata entry. The 4.0 compatibility registry lists every site. Docs that enumerate the values name the replacement and the deprecation. Co-Authored-By: Claude Opus 5.5 --- .changeset/reserved-word-enum-replacements.md | 12 ++ docs/brand-protocol/brand-json.mdx | 4 +- docs/building/operating/transport-errors.mdx | 2 +- .../tasks/calibrate_content.mdx | 2 +- .../tasks/get_media_buy_artifacts.mdx | 12 +- .../tasks/validate_content_delivery.mdx | 2 + docs/reference/migration/v4-compatibility.mdx | 2 + package.json | 2 +- .../source/universal/v4-compatibility.yaml | 17 +++ static/schemas/source/brand.json | 31 ++++- .../get-media-buy-artifacts-response.json | 15 ++- .../schemas/source/core/registry-event.json | 14 ++- .../core/signal-definition-enrichment.json | 13 +- .../source/core/signal-definition.json | 13 +- .../schemas/source/enums/binary-verdict.json | 19 ++- .../source/error-details/rate-limited.json | 18 ++- tests/mcp-schema-projection.test.cjs | 5 +- .../reserved-word-enum-replacements.test.cjs | 117 ++++++++++++++++++ 18 files changed, 275 insertions(+), 25 deletions(-) create mode 100644 .changeset/reserved-word-enum-replacements.md create mode 100644 tests/reserved-word-enum-replacements.test.cjs diff --git a/.changeset/reserved-word-enum-replacements.md b/.changeset/reserved-word-enum-replacements.md new file mode 100644 index 00000000000..b3cca6abc66 --- /dev/null +++ b/.changeset/reserved-word-enum-replacements.md @@ -0,0 +1,12 @@ +--- +"adcontextprotocol": minor +--- + +Schema: add reserved-word-safe replacements for eight enum values that collide with SDK target languages, and deprecate the originals (#8791). Each enum now accepts both spellings through 3.x; the original is deprecated in 3.3 and removed in 4.0. + +- `pass` → `passed` in `enums/binary-verdict.json`, `get_media_buy_artifacts` `local_verdict`, and the registry feed's compliance `tracks` status. `pass` is a Python keyword. +- `global` → `agent` in the `RATE_LIMITED` details `scope`. `global` is a Python keyword. +- `name` → `full_name` in the signal definition `onboarder.match_keys`, on both `core/signal-definition.json` and `core/signal-definition-enrichment.json`. `name` collides with the enum member protocol in Python and Java. +- `brand.json` `color_ref.kind`: `name` → `palette_key` and `value` → `hex`. Each `oneOf` variant's `kind` changes from `const` to a two-value `enum`, so the old and new kinds select the same variant. The payload fields stay `name` and `value`. + +Each deprecated value is listed in `x-deprecated-enum-values` and carries `deprecated: true`, `x-deprecated-in: "3.3.0"`, `x-removed-in: "4.0.0"`, and `x-superseded-by` in a per-value `enumMetadata` entry. The 4.0 compatibility registry lists every site. A 3.2 validator rejects the new values, so senders keep the original until the counterparty supports 3.3. diff --git a/docs/brand-protocol/brand-json.mdx b/docs/brand-protocol/brand-json.mdx index d1e88c7a859..c80c4c0c058 100644 --- a/docs/brand-protocol/brand-json.mdx +++ b/docs/brand-protocol/brand-json.mdx @@ -954,7 +954,9 @@ Use `visual_guidelines.color_constraints[]` for machine-readable color usage and } ``` -Each `color_ref` uses an explicit `kind` discriminator: `kind: "name"` looks up a key in `colors{}`, `kind: "value"` carries a literal hex color, and `kind: "surface"` identifies a layout surface such as `background`, `text`, or `logo_background`. +Each `color_ref` uses an explicit `kind` discriminator: `kind: "palette_key"` looks up the key in `name` in `colors{}`, `kind: "hex"` carries a literal hex color in `value`, and `kind: "surface"` identifies a layout surface such as `background`, `text`, or `logo_background`. + +`kind: "name"` and `kind: "value"` are deprecated in 3.3 and removed in 4.0, because `name` and `value` collide with the enum member protocol in generated Python and Java SDKs ([#8791](https://github.com/adcontextprotocol/adcp/issues/8791)). They select the same variants as `palette_key` and `hex`, and readers accept both spellings through 3.x. The payload fields stay `name` and `value`. A 3.2 reader rejects the new kinds, so keep `name` and `value` in a `brand.json` that 3.2 readers validate. ### Mark lockups diff --git a/docs/building/operating/transport-errors.mdx b/docs/building/operating/transport-errors.mdx index f206b3528eb..fb8adbc41ff 100644 --- a/docs/building/operating/transport-errors.mdx +++ b/docs/building/operating/transport-errors.mdx @@ -432,7 +432,7 @@ The `details` field is an open object. To prevent interoperability divergence, s | `limit` | number | Maximum requests allowed in the window | | `remaining` | number | Requests remaining in the current window | | `window_seconds` | number | Duration of the rate-limit window | -| `scope` | string | What the limit applies to: `account`, `tool`, or `global` | +| `scope` | string | What the limit applies to: `account`, `tool`, or `agent` (the whole agent). `global` is the deprecated spelling of `agent`: deprecated in 3.3 and removed in 4.0 ([#8791](https://github.com/adcontextprotocol/adcp/issues/8791)). Receivers accept both through 3.x; a 3.2 receiver rejects `agent`. | ### `BUDGET_TOO_LOW` diff --git a/docs/governance/content-standards/tasks/calibrate_content.mdx b/docs/governance/content-standards/tasks/calibrate_content.mdx index a46dfa8b915..95558bbe085 100644 --- a/docs/governance/content-standards/tasks/calibrate_content.mdx +++ b/docs/governance/content-standards/tasks/calibrate_content.mdx @@ -100,7 +100,7 @@ An artifact represents content context where ad placements occur - identified by | Field | Required | Description | |-------|----------|-------------| -| `verdict` | Yes | Overall `pass` or `fail` decision | +| `verdict` | Yes | Overall `passed` or `fail` decision. `pass` is the deprecated spelling of `passed`: deprecated in 3.3 and removed in 4.0 ([#8791](https://github.com/adcontextprotocol/adcp/issues/8791)). Receivers accept both through 3.x; a 3.2 receiver rejects `passed`. | | `explanation` | No | Detailed natural language explanation of the decision | | `features` | No | Per-feature breakdown with explanations | | `confidence` | No | Model confidence in the verdict (0-1), when available | diff --git a/docs/governance/content-standards/tasks/get_media_buy_artifacts.mdx b/docs/governance/content-standards/tasks/get_media_buy_artifacts.mdx index e0bb03955d5..409c3726aa6 100644 --- a/docs/governance/content-standards/tasks/get_media_buy_artifacts.mdx +++ b/docs/governance/content-standards/tasks/get_media_buy_artifacts.mdx @@ -121,7 +121,7 @@ Uses higher limits than standard pagination because artifact result sets can be | `artifacts[].country` | ISO 3166-1 alpha-2 country code where delivery occurred | | `artifacts[].channel` | Channel type (display, olv, ctv, podcast, social, etc.) | | `artifacts[].brand_context` | Brand/SKU information for policy evaluation (schema TBD) | -| `artifacts[].local_verdict` | Seller's local model verdict (pass/fail/unevaluated) | +| `artifacts[].local_verdict` | Seller's local model verdict: `passed`, `fail`, or `unevaluated`. `pass` is the deprecated spelling of `passed` (deprecated in 3.3, removed in 4.0, [#8791](https://github.com/adcontextprotocol/adcp/issues/8791)); treat the two as the same verdict. | | `collection_info` | Artifact collection statistics — what the seller collected per the buy agreement | | `pagination` | Cursor for fetching more results | @@ -156,9 +156,11 @@ validation = verification_agent.validate_content_delivery( ) # Check for drift between local and verified verdicts +# "pass" is the deprecated spelling of "passed"; compare them as one verdict. +normalize = lambda verdict: "passed" if verdict == "pass" else verdict for i, result in enumerate(validation["results"]): - local = artifacts_response["artifacts"][i]["local_verdict"] - verified = result["verdict"] + local = normalize(artifacts_response["artifacts"][i]["local_verdict"]) + verified = normalize(result["verdict"]) if local != verified: print(f"Drift detected: {result['record_id']} - local={local}, verified={verified}") ``` @@ -181,7 +183,7 @@ validation = verification_agent.validate_content_delivery( ) # Check false positive rate -false_positives = sum(1 for r in validation["results"] if r["verdict"] == "pass") +false_positives = sum(1 for r in validation["results"] if r["verdict"] in ("passed", "pass")) print(f"False positive rate: {false_positives / len(failures['artifacts']):.1%}") ``` @@ -194,7 +196,7 @@ Governance agents receiving validation results where every `local_verdict` is `" | `local_verdict` | `failures_only` returns? | Drift comparison possible? | |-----------------|--------------------------|---------------------------| | `fail` | Yes | Yes | -| `pass` | No | N/A (not in result set) | +| `passed` (or the deprecated `pass`) | No | N/A (not in result set) | | `unevaluated` | No | No — omit `failures_only` to retrieve all collected artifacts | ## Non-Web Artifact Examples diff --git a/docs/governance/content-standards/tasks/validate_content_delivery.mdx b/docs/governance/content-standards/tasks/validate_content_delivery.mdx index 6c913124711..47ad2f36575 100644 --- a/docs/governance/content-standards/tasks/validate_content_delivery.mdx +++ b/docs/governance/content-standards/tasks/validate_content_delivery.mdx @@ -122,6 +122,8 @@ This keeps responsibilities clear: sellers provide content samples via `get_medi } ``` +Each result's `verdict` is `passed` or `fail`. `pass` is the deprecated spelling of `passed`: deprecated in 3.3 and removed in 4.0 ([#8791](https://github.com/adcontextprotocol/adcp/issues/8791)). Receivers accept both through 3.x; a 3.2 receiver rejects `passed`. + ## Use Cases ### Post-Campaign Audit diff --git a/docs/reference/migration/v4-compatibility.mdx b/docs/reference/migration/v4-compatibility.mdx index 0dd91d3d139..6f90a063264 100644 --- a/docs/reference/migration/v4-compatibility.mdx +++ b/docs/reference/migration/v4-compatibility.mdx @@ -232,6 +232,8 @@ A check is added once its 4.0 change is decided. These changes are still proposa The schemas also mark 46 deprecated fields with `x-removed-in: "4.0.0"`. The registry lists each one, either under the check that covers it or in `schema_removals_not_checked` with the reason no check covers it yet. Most uncovered fields are sent by buyers, so a seller run can't observe them. A few are seller-emitted and checkable but not graded yet: flat `valid_actions`, response-wide delivery `currency` and `aggregated_totals`, `inline_creative_management`, and the format-level build and pricing surface that `list_transformers` replaces. +Deprecated enum values carry the same marker on a per-value `enumMetadata` entry. 4.0 removes the values that collide with SDK reserved words or the enum member protocol ([#8791](https://github.com/adcontextprotocol/adcp/issues/8791)), each replaced in 3.3: `pass` by `passed`, `global` by `agent`, the signal `match_keys` value `name` by `full_name`, and the `brand.json` `color_ref` kinds `name` and `value` by `palette_key` and `hex`. Send the replacement once your counterparty supports 3.3 or later. + The full list of planned 4.0 work is the [4.0 milestone](https://github.com/adcontextprotocol/adcp/milestone/5). ## Adding a check diff --git a/package.json b/package.json index ada67a9aa4f..81845f44703 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "verify:schema-discovery": "node scripts/verify-schema-discovery.mjs", "typecheck": "tsc --project server/tsconfig.json --noEmit && npm run typecheck:fixed-trace-rollout-tests", "typecheck:fixed-trace-rollout-tests": "node scripts/typecheck-fixed-trace-rollout-tests.mjs", - "test:schemas": "node tests/schema-validation.test.cjs && node --test tests/seller-offers-contract.test.cjs tests/service-commitments.test.cjs tests/sync-platform-id.test.cjs tests/assignment-landing-page.test.cjs tests/assignment-trackers.test.cjs tests/assignment-replace.test.cjs tests/outcome-target.test.cjs tests/trusted-match-offer-creative-data.test.cjs tests/accessibility-violation-details.test.cjs tests/portfolio-routing-scope.test.cjs tests/catalog-item-availability-updates.test.cjs tests/catalog-ingestion-capabilities.test.cjs tests/sdk-implementation-metadata.test.cjs tests/seller-optimized-package-controls.test.cjs tests/compact-product-lifecycle-storyboards.test.cjs tests/timezone-resolution-storyboards.test.cjs tests/terminal-package-retention-storyboard.test.cjs tests/total-budget-redistribution-currency.test.cjs tests/dooh-allocation.test.cjs tests/x-added-in-3-3-sweep.test.cjs tests/performance-standard-metric-id.test.cjs tests/identity-absence-coherence.test.cjs tests/match-keys.test.cjs tests/execution-requirements.test.cjs tests/execution-readiness.test.cjs tests/schema-deprecation-metadata.test.cjs tests/products-only-brief-compatibility.test.cjs tests/price-adjustments-vectors.test.cjs tests/async-identity-convergence.test.cjs tests/creative-rotation.test.cjs tests/creative-start-policy.test.cjs tests/canonical-forecast-point-parity.test.cjs tests/creative-revisions.test.cjs tests/creative-live-updates.test.cjs tests/creative-delivery-contracts.test.cjs tests/tracker-execution-contracts.test.cjs tests/tracker-execution-package-integration.test.cjs tests/metric-identity-coherence.test.cjs tests/print-metrics.test.cjs tests/installment-lifecycle-reporting.test.cjs tests/rights-channel-restrictions.test.cjs tests/daypart-time-of-day.test.cjs tests/audience-size-range.test.cjs tests/media-buy-lineage.test.cjs tests/delivery-projection.test.cjs tests/viewable-rate-optimization-goal.test.cjs tests/sort-contract-delivery-reporting.test.cjs tests/time-based-views-contract.test.cjs tests/metric-qualifier-parity.test.cjs tests/collection-mechanism.test.cjs tests/requested-metrics-contract.test.cjs tests/auto-breakdown-negotiation-contract.test.cjs tests/format-delivery-reporting-contract.test.cjs tests/inventory-delivery-reporting-contract.test.cjs tests/property-delivery-conformance.test.cjs tests/lint-schema-enum-drift.test.cjs tests/lint-schema-required-declared.test.cjs tests/format-asset-type-parity.test.cjs tests/canonical-manifest-constructibility.test.cjs tests/synthetic-depiction.test.cjs tests/creative-rendering-authority.test.cjs tests/buyer-reason.test.cjs tests/mutation-outcome.test.cjs tests/error-recovery-vectors.test.cjs tests/reporting-status-contract.test.cjs tests/reporting-reconciliation-fixture.test.cjs tests/reporting-core-fixture.test.cjs tests/reporting-native-version-ref-bounds.test.cjs tests/trust-json-v1.test.cjs tests/refine-proposals-expected-confirmation-at.test.cjs tests/structured-package-refine.test.cjs tests/hold-policy.test.cjs tests/hold-policy-vectors.test.cjs tests/canonical-installments.test.cjs tests/withdrawn-to-4-0.test.cjs tests/collection-catalog.test.cjs tests/collection-copies-vectors.test.cjs tests/account-change-record.test.cjs tests/notification-config-payload-view.test.cjs tests/account-requests.test.cjs tests/reporting-subscriptions.test.cjs tests/reporting-subscription-probe.test.cjs tests/reporting-subscription-coverage-vectors.test.cjs tests/reporting-subscription-coverage-properties.test.cjs tests/product-roster-coverage-vectors.test.cjs && npm run test:premium-display-formats && npm run test:geo-region-targeting", + "test:schemas": "node tests/schema-validation.test.cjs && node --test tests/seller-offers-contract.test.cjs tests/service-commitments.test.cjs tests/sync-platform-id.test.cjs tests/assignment-landing-page.test.cjs tests/assignment-trackers.test.cjs tests/assignment-replace.test.cjs tests/outcome-target.test.cjs tests/trusted-match-offer-creative-data.test.cjs tests/accessibility-violation-details.test.cjs tests/portfolio-routing-scope.test.cjs tests/catalog-item-availability-updates.test.cjs tests/catalog-ingestion-capabilities.test.cjs tests/sdk-implementation-metadata.test.cjs tests/seller-optimized-package-controls.test.cjs tests/compact-product-lifecycle-storyboards.test.cjs tests/timezone-resolution-storyboards.test.cjs tests/terminal-package-retention-storyboard.test.cjs tests/total-budget-redistribution-currency.test.cjs tests/dooh-allocation.test.cjs tests/x-added-in-3-3-sweep.test.cjs tests/performance-standard-metric-id.test.cjs tests/identity-absence-coherence.test.cjs tests/match-keys.test.cjs tests/execution-requirements.test.cjs tests/execution-readiness.test.cjs tests/schema-deprecation-metadata.test.cjs tests/reserved-word-enum-replacements.test.cjs tests/products-only-brief-compatibility.test.cjs tests/price-adjustments-vectors.test.cjs tests/async-identity-convergence.test.cjs tests/creative-rotation.test.cjs tests/creative-start-policy.test.cjs tests/canonical-forecast-point-parity.test.cjs tests/creative-revisions.test.cjs tests/creative-live-updates.test.cjs tests/creative-delivery-contracts.test.cjs tests/tracker-execution-contracts.test.cjs tests/tracker-execution-package-integration.test.cjs tests/metric-identity-coherence.test.cjs tests/print-metrics.test.cjs tests/installment-lifecycle-reporting.test.cjs tests/rights-channel-restrictions.test.cjs tests/daypart-time-of-day.test.cjs tests/audience-size-range.test.cjs tests/media-buy-lineage.test.cjs tests/delivery-projection.test.cjs tests/viewable-rate-optimization-goal.test.cjs tests/sort-contract-delivery-reporting.test.cjs tests/time-based-views-contract.test.cjs tests/metric-qualifier-parity.test.cjs tests/collection-mechanism.test.cjs tests/requested-metrics-contract.test.cjs tests/auto-breakdown-negotiation-contract.test.cjs tests/format-delivery-reporting-contract.test.cjs tests/inventory-delivery-reporting-contract.test.cjs tests/property-delivery-conformance.test.cjs tests/lint-schema-enum-drift.test.cjs tests/lint-schema-required-declared.test.cjs tests/format-asset-type-parity.test.cjs tests/canonical-manifest-constructibility.test.cjs tests/synthetic-depiction.test.cjs tests/creative-rendering-authority.test.cjs tests/buyer-reason.test.cjs tests/mutation-outcome.test.cjs tests/error-recovery-vectors.test.cjs tests/reporting-status-contract.test.cjs tests/reporting-reconciliation-fixture.test.cjs tests/reporting-core-fixture.test.cjs tests/reporting-native-version-ref-bounds.test.cjs tests/trust-json-v1.test.cjs tests/refine-proposals-expected-confirmation-at.test.cjs tests/structured-package-refine.test.cjs tests/hold-policy.test.cjs tests/hold-policy-vectors.test.cjs tests/canonical-installments.test.cjs tests/withdrawn-to-4-0.test.cjs tests/collection-catalog.test.cjs tests/collection-copies-vectors.test.cjs tests/account-change-record.test.cjs tests/notification-config-payload-view.test.cjs tests/account-requests.test.cjs tests/reporting-subscriptions.test.cjs tests/reporting-subscription-probe.test.cjs tests/reporting-subscription-coverage-vectors.test.cjs tests/reporting-subscription-coverage-properties.test.cjs tests/product-roster-coverage-vectors.test.cjs && npm run test:premium-display-formats && npm run test:geo-region-targeting", "test:performance-feedback": "node --test --test-force-exit --test-timeout=30000 tests/performance-feedback-contract.test.cjs", "test:dist-schema-version-ids": "node --test --test-force-exit --test-timeout=30000 tests/dist-schema-version-ids.test.cjs", "test:examples": "node tests/example-validation-simple.test.cjs && npm run test:tmp-context-merge", diff --git a/static/compliance/source/universal/v4-compatibility.yaml b/static/compliance/source/universal/v4-compatibility.yaml index c944ef069a3..2be69b4bdda 100644 --- a/static/compliance/source/universal/v4-compatibility.yaml +++ b/static/compliance/source/universal/v4-compatibility.yaml @@ -465,3 +465,20 @@ schema_removals_not_checked: sites: - "core/media-buy-legacy-terms-ref.json#" - "media-buy/request-proposals-response.json#/properties/purchase_continuation" + + - change: "Enum values that collide with SDK reserved words, replaced in 3.3" + issue: 8791 + reason: >- + 3.3 adds a replacement beside each value (pass to passed, global to + agent, name to full_name, and color_ref kinds name and value to + palette_key and hex), so both are accepted through 3.x. Senders keep the + old value for 3.2 counterparties, so a 3.x run cannot grade its use. + sites: + - "brand.json#/definitions/color_ref/properties/kind/enumMetadata/name" + - "brand.json#/definitions/color_ref/properties/kind/enumMetadata/value" + - "content-standards/get-media-buy-artifacts-response.json#/oneOf/0/properties/artifacts/items/properties/local_verdict/enumMetadata/pass" + - "core/registry-event.json#/$defs/compliancePayload/properties/tracks/additionalProperties/enumMetadata/pass" + - "core/signal-definition-enrichment.json#/properties/onboarder/properties/match_keys/items/enumMetadata/name" + - "core/signal-definition.json#/properties/onboarder/properties/match_keys/items/enumMetadata/name" + - "enums/binary-verdict.json#/enumMetadata/pass" + - "error-details/rate-limited.json#/properties/scope/enumMetadata/global" diff --git a/static/schemas/source/brand.json b/static/schemas/source/brand.json index 840036e4ced..cd1656df02b 100644 --- a/static/schemas/source/brand.json +++ b/static/schemas/source/brand.json @@ -213,11 +213,34 @@ }, "color_ref": { "type": "object", - "description": "Reference to a brand color or usage surface for machine-readable guideline constraints. Use kind=name for palette lookup keys in colors, kind=value for a literal hex color, and kind=surface for layout surfaces such as background or text.", + "description": "Reference to a brand color or usage surface for machine-readable guideline constraints. Use kind=palette_key for palette lookup keys in colors, kind=hex for a literal hex color, and kind=surface for layout surfaces such as background or text. kind=name and kind=value are deprecated in 3.3 and removed in 4.0; they select the same variants as palette_key and hex.", "properties": { "kind": { "type": "string", - "enum": ["name", "value", "surface"], + "x-deprecated-enum-values": ["name", "value"], + "x-deprecated-enum-values-doc": "Deprecated in 3.3 and removed in 4.0 (#8791): `name` and `value` collide with the enum member protocol in Python and Java (`Enum.name`, `Enum.value`). Use `palette_key` and `hex`. The payload fields stay `name` and `value`. Receivers accept both values through 3.x. Send the replacement only to a counterparty on AdCP 3.3 or later, because a 3.2 validator rejects it.", + "enum": ["name", "value", "surface", "palette_key", "hex"], + "enumDescriptions": { + "name": "**Deprecated in 3.3**, removed in 4.0: use `palette_key`. Selects the palette-key variant, which carries `name`.", + "value": "**Deprecated in 3.3**, removed in 4.0: use `hex`. Selects the literal-color variant, which carries `value`.", + "surface": "Selects the usage-surface variant, which carries `surface`.", + "palette_key": "Selects the palette-key variant, which carries `name`: a key from colors{}. Replaces the deprecated `name`; a receiver on AdCP 3.2 or earlier rejects it.", + "hex": "Selects the literal-color variant, which carries `value`: a hex color. Replaces the deprecated `value`; a receiver on AdCP 3.2 or earlier rejects it." + }, + "enumMetadata": { + "name": { + "deprecated": true, + "x-deprecated-in": "3.3.0", + "x-removed-in": "4.0.0", + "x-superseded-by": "palette_key" + }, + "value": { + "deprecated": true, + "x-deprecated-in": "3.3.0", + "x-removed-in": "4.0.0", + "x-superseded-by": "hex" + } + }, "description": "Discriminator for the color reference variant." }, "name": { @@ -236,11 +259,11 @@ }, "oneOf": [ { - "properties": { "kind": { "const": "name" } }, + "properties": { "kind": { "enum": ["name", "palette_key"], "x-deprecated-enum-values": ["name"] } }, "required": ["kind", "name"] }, { - "properties": { "kind": { "const": "value" } }, + "properties": { "kind": { "enum": ["value", "hex"], "x-deprecated-enum-values": ["value"] } }, "required": ["kind", "value"] }, { diff --git a/static/schemas/source/content-standards/get-media-buy-artifacts-response.json b/static/schemas/source/content-standards/get-media-buy-artifacts-response.json index ea1da8f4f31..2f6e39b9a78 100644 --- a/static/schemas/source/content-standards/get-media-buy-artifacts-response.json +++ b/static/schemas/source/content-standards/get-media-buy-artifacts-response.json @@ -65,12 +65,23 @@ }, "local_verdict": { "type": "string", + "x-deprecated-enum-values": ["pass"], + "x-deprecated-enum-values-doc": "Deprecated in 3.3 and removed in 4.0 (#8791): `pass` is a Python keyword, so generated SDKs must rename it. Use `passed`. Receivers accept both values through 3.x. Send the replacement only to a counterparty on AdCP 3.3 or later, because a 3.2 validator rejects it.", "enum": [ "pass", "fail", - "unevaluated" + "unevaluated", + "passed" ], - "description": "Seller's local model verdict for this artifact" + "description": "Seller's local model verdict for this artifact. `passed` replaces `pass`, which is deprecated in 3.3 and removed in 4.0; buyers treat the two as the same verdict.", + "enumMetadata": { + "pass": { + "deprecated": true, + "x-deprecated-in": "3.3.0", + "x-removed-in": "4.0.0", + "x-superseded-by": "passed" + } + } } }, "required": [ diff --git a/static/schemas/source/core/registry-event.json b/static/schemas/source/core/registry-event.json index 9f062fdd689..24eb81f6385 100644 --- a/static/schemas/source/core/registry-event.json +++ b/static/schemas/source/core/registry-event.json @@ -758,10 +758,20 @@ "headline": { "type": ["string", "null"] }, "tracks": { "type": "object", - "description": "Map of compliance track id to track status.", + "description": "Map of compliance track id to track status. `passed` replaces `pass`, which is deprecated in 3.3 and removed in 4.0; consumers treat the two as the same status.", "additionalProperties": { "type": "string", - "enum": ["pass", "fail", "partial", "skip", "silent", "warning", "unknown", "skipped"] + "x-deprecated-enum-values": ["pass"], + "x-deprecated-enum-values-doc": "Deprecated in 3.3 and removed in 4.0 (#8791): `pass` is a Python keyword, so generated SDKs must rename it. Use `passed`. Receivers accept both values through 3.x. Send the replacement only to a counterparty on AdCP 3.3 or later, because a 3.2 validator rejects it.", + "enum": ["pass", "fail", "partial", "skip", "silent", "warning", "unknown", "skipped", "passed"], + "enumMetadata": { + "pass": { + "deprecated": true, + "x-deprecated-in": "3.3.0", + "x-removed-in": "4.0.0", + "x-superseded-by": "passed" + } + } } }, "storyboards_passing": { "type": "integer", "minimum": 0 }, diff --git a/static/schemas/source/core/signal-definition-enrichment.json b/static/schemas/source/core/signal-definition-enrichment.json index 8d6c9730b92..50ecdf10fa2 100644 --- a/static/schemas/source/core/signal-definition-enrichment.json +++ b/static/schemas/source/core/signal-definition-enrichment.json @@ -167,9 +167,20 @@ "match_keys": { "type": "array", "minItems": 1, + "description": "Identifiers the onboarder matched on. `full_name` replaces `name`, which is deprecated in 3.3 and removed in 4.0; receivers treat the two as the same key.", "items": { "type": "string", - "enum": ["name", "address", "email", "postal", "lat_long", "mobile_id", "cookie_id", "ip", "customer_id", "phone"] + "x-deprecated-enum-values": ["name"], + "x-deprecated-enum-values-doc": "Deprecated in 3.3 and removed in 4.0 (#8791): `name` collides with the enum member protocol in Python and Java (`Enum.name`). Use `full_name`. Receivers accept both values through 3.x. Send the replacement only to a counterparty on AdCP 3.3 or later, because a 3.2 validator rejects it.", + "enum": ["name", "address", "email", "postal", "lat_long", "mobile_id", "cookie_id", "ip", "customer_id", "phone", "full_name"], + "enumMetadata": { + "name": { + "deprecated": true, + "x-deprecated-in": "3.3.0", + "x-removed-in": "4.0.0", + "x-superseded-by": "full_name" + } + } } }, "pre_onboarding_audience_expansion": { diff --git a/static/schemas/source/core/signal-definition.json b/static/schemas/source/core/signal-definition.json index a0e350c1b7d..ea74e27138f 100644 --- a/static/schemas/source/core/signal-definition.json +++ b/static/schemas/source/core/signal-definition.json @@ -250,9 +250,20 @@ "match_keys": { "type": "array", "minItems": 1, + "description": "Identifiers the onboarder matched on. `full_name` replaces `name`, which is deprecated in 3.3 and removed in 4.0; receivers treat the two as the same key.", "items": { "type": "string", - "enum": ["name", "address", "email", "postal", "lat_long", "mobile_id", "cookie_id", "ip", "customer_id", "phone"] + "x-deprecated-enum-values": ["name"], + "x-deprecated-enum-values-doc": "Deprecated in 3.3 and removed in 4.0 (#8791): `name` collides with the enum member protocol in Python and Java (`Enum.name`). Use `full_name`. Receivers accept both values through 3.x. Send the replacement only to a counterparty on AdCP 3.3 or later, because a 3.2 validator rejects it.", + "enum": ["name", "address", "email", "postal", "lat_long", "mobile_id", "cookie_id", "ip", "customer_id", "phone", "full_name"], + "enumMetadata": { + "name": { + "deprecated": true, + "x-deprecated-in": "3.3.0", + "x-removed-in": "4.0.0", + "x-superseded-by": "full_name" + } + } } }, "pre_onboarding_audience_expansion": { diff --git a/static/schemas/source/enums/binary-verdict.json b/static/schemas/source/enums/binary-verdict.json index ea721f452f0..029b8c3d500 100644 --- a/static/schemas/source/enums/binary-verdict.json +++ b/static/schemas/source/enums/binary-verdict.json @@ -2,11 +2,22 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/enums/binary-verdict.json", "title": "Binary Verdict", - "description": "Strictly two-outcome evaluation result used for overall record-level verdicts in content standards tasks. For per-feature breakdowns that include warning and unevaluated states, see feature-check-status.", + "description": "Strictly two-outcome evaluation result used for overall record-level verdicts in content standards tasks. For per-feature breakdowns that include warning and unevaluated states, see feature-check-status. `passed` replaces `pass`, which is deprecated in 3.3 and removed in 4.0 (see `x-deprecated-enum-values`).", "type": "string", - "enum": ["pass", "fail"], + "x-deprecated-enum-values": ["pass"], + "x-deprecated-enum-values-doc": "Deprecated in 3.3 and removed in 4.0 (#8791): `pass` is a Python keyword, so generated SDKs must rename it. Use `passed`. Receivers accept both values through 3.x. Send the replacement only to a counterparty on AdCP 3.3 or later, because a 3.2 validator rejects it.", + "enum": ["pass", "fail", "passed"], "enumDescriptions": { - "pass": "The evaluated record meets all applicable content standards", - "fail": "The evaluated record failed one or more content standard checks" + "pass": "**Deprecated in 3.3**, removed in 4.0: use `passed`. The evaluated record meets all applicable content standards.", + "fail": "The evaluated record failed one or more content standard checks", + "passed": "The evaluated record meets all applicable content standards. Replaces the deprecated `pass`; a receiver on AdCP 3.2 or earlier rejects it." + }, + "enumMetadata": { + "pass": { + "deprecated": true, + "x-deprecated-in": "3.3.0", + "x-removed-in": "4.0.0", + "x-superseded-by": "passed" + } } } diff --git a/static/schemas/source/error-details/rate-limited.json b/static/schemas/source/error-details/rate-limited.json index 4c63669ea29..17aeba091bb 100644 --- a/static/schemas/source/error-details/rate-limited.json +++ b/static/schemas/source/error-details/rate-limited.json @@ -19,7 +19,23 @@ }, "scope": { "type": "string", - "enum": ["account", "tool", "global"], + "x-deprecated-enum-values": ["global"], + "x-deprecated-enum-values-doc": "Deprecated in 3.3 and removed in 4.0 (#8791): `global` is a Python keyword, so generated SDKs must rename it. Use `agent`. Receivers accept both values through 3.x. Send the replacement only to a counterparty on AdCP 3.3 or later, because a 3.2 validator rejects it.", + "enum": ["account", "tool", "global", "agent"], + "enumDescriptions": { + "account": "The limit applies to the calling account.", + "tool": "The limit applies to the tool that was called.", + "global": "**Deprecated in 3.3**, removed in 4.0: use `agent`. The limit applies across the whole agent.", + "agent": "The limit applies across the whole agent, every account and tool. Replaces the deprecated `global`; a receiver on AdCP 3.2 or earlier rejects it." + }, + "enumMetadata": { + "global": { + "deprecated": true, + "x-deprecated-in": "3.3.0", + "x-removed-in": "4.0.0", + "x-superseded-by": "agent" + } + }, "description": "What the limit applies to" } }, diff --git a/tests/mcp-schema-projection.test.cjs b/tests/mcp-schema-projection.test.cjs index 07d6ff758ff..6e8a3333c6b 100644 --- a/tests/mcp-schema-projection.test.cjs +++ b/tests/mcp-schema-projection.test.cjs @@ -201,7 +201,10 @@ const MODEL_CONTEXT_BUDGET_KIB = { // 1_376_000. // Experimental match keys (#8375) reach the seeded Product through // Product.identity.match_keys: 1_375_833 → 1_383_804 bytes. Bound at 1_384_000. -const PARITY_COMPILE_LIMIT = 1_384_000; +// Reserved-word enum replacements (#8791) add a replacement value and its +// deprecation metadata to enums in the controller closure; the controller +// measures 1_384_466 bytes. Bound at 1_384_500. +const PARITY_COMPILE_LIMIT = 1_384_500; function readJson(filename) { return JSON.parse(fs.readFileSync(filename, 'utf8')); diff --git a/tests/reserved-word-enum-replacements.test.cjs b/tests/reserved-word-enum-replacements.test.cjs new file mode 100644 index 00000000000..8836203ef0d --- /dev/null +++ b/tests/reserved-word-enum-replacements.test.cjs @@ -0,0 +1,117 @@ +'use strict'; + +// #8791 step 1: enum values that collide with SDK reserved words or with the +// enum member protocol (`pass`, `global`, `name`, `value`) gain a replacement +// in 3.3. Both values are accepted through 3.x; the old one is deprecated and +// removed in 4.0. + +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const test = require('node:test'); +const Ajv = require('ajv'); +const addFormats = require('ajv-formats'); + +const SCHEMA_ROOT = path.join(__dirname, '../static/schemas/source'); + +const REPLACEMENTS = [ + ['enums/binary-verdict.json', '', 'pass', 'passed'], + [ + 'content-standards/get-media-buy-artifacts-response.json', + '/oneOf/0/properties/artifacts/items/properties/local_verdict', + 'pass', + 'passed', + ], + ['core/registry-event.json', '/$defs/compliancePayload/properties/tracks/additionalProperties', 'pass', 'passed'], + ['error-details/rate-limited.json', '/properties/scope', 'global', 'agent'], + ['core/signal-definition.json', '/properties/onboarder/properties/match_keys/items', 'name', 'full_name'], + ['core/signal-definition-enrichment.json', '/properties/onboarder/properties/match_keys/items', 'name', 'full_name'], + ['brand.json', '/definitions/color_ref/properties/kind', 'name', 'palette_key'], + ['brand.json', '/definitions/color_ref/properties/kind', 'value', 'hex'], +]; + +function readSchema(relativePath) { + return JSON.parse(fs.readFileSync(path.join(SCHEMA_ROOT, relativePath), 'utf8')); +} + +function at(document, pointer) { + return pointer + .split('/') + .slice(1) + .map(segment => segment.replaceAll('~1', '/').replaceAll('~0', '~')) + .reduce((node, segment) => node[segment], document); +} + +async function compile(schema) { + const ajv = new Ajv({ + allErrors: true, + strict: false, + loadSchema: async uri => readSchema(uri.replace(/^\/schemas\//, '')), + }); + addFormats(ajv); + return ajv.compileAsync(schema); +} + +test('each replaced value is accepted beside its deprecated original', () => { + for (const [file, pointer, oldValue, newValue] of REPLACEMENTS) { + const node = at(readSchema(file), pointer); + const site = `${file}#${pointer}`; + assert.ok(node.enum.includes(oldValue), `${site} still accepts ${oldValue}`); + assert.ok(node.enum.includes(newValue), `${site} accepts ${newValue}`); + assert.ok(node['x-deprecated-enum-values'].includes(oldValue), `${site} lists ${oldValue} as deprecated`); + assert.ok(!node['x-deprecated-enum-values'].includes(newValue), `${site} does not deprecate ${newValue}`); + assert.deepEqual( + node.enumMetadata[oldValue], + { deprecated: true, 'x-deprecated-in': '3.3.0', 'x-removed-in': '4.0.0', 'x-superseded-by': newValue }, + `${site} enumMetadata.${oldValue}`, + ); + } +}); + +test('x-deprecated-enum-values only lists values the enum still accepts', () => { + const offenders = []; + const visit = (node, site) => { + if (!node || typeof node !== 'object') return; + if (Array.isArray(node['x-deprecated-enum-values'])) { + for (const value of node['x-deprecated-enum-values']) { + if (!Array.isArray(node.enum) || !node.enum.includes(value)) offenders.push(`${site}: ${value}`); + } + } + for (const [key, child] of Object.entries(node)) visit(child, `${site}/${key}`); + }; + const walk = dir => { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) walk(full); + else if (entry.name.endsWith('.json')) { + visit(JSON.parse(fs.readFileSync(full, 'utf8')), `${path.relative(SCHEMA_ROOT, full)}#`); + } + } + }; + walk(SCHEMA_ROOT); + assert.deepEqual(offenders, []); +}); + +test('color_ref: old and new kinds select the same variant', async () => { + const validate = await compile({ $ref: '/schemas/brand.json#/definitions/color_ref' }); + const accepts = value => assert.equal(validate(value), true, `${JSON.stringify(value)}: ${JSON.stringify(validate.errors)}`); + const rejects = value => assert.equal(validate(value), false, `${JSON.stringify(value)} should be rejected`); + + accepts({ kind: 'name', name: 'market_yellow' }); + accepts({ kind: 'palette_key', name: 'market_yellow' }); + accepts({ kind: 'value', value: '#FFD200' }); + accepts({ kind: 'hex', value: '#FFD200' }); + accepts({ kind: 'surface', surface: 'background' }); + + rejects({ kind: 'palette_key', value: '#FFD200' }); + rejects({ kind: 'hex', name: 'market_yellow' }); + rejects({ kind: 'palette_key' }); + rejects({ kind: 'hex' }); +}); + +test('rate-limited scope accepts agent and the deprecated global', async () => { + const validate = await compile(readSchema('error-details/rate-limited.json')); + for (const scope of ['account', 'tool', 'global', 'agent']) { + assert.equal(validate({ limit: 10, remaining: 0, window_seconds: 60, scope }), true, scope); + } +}); From f6da65e8f55c9d6e50184905758aa1d8cc356df2 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Tue, 10 Nov 2026 21:05:59 -0500 Subject: [PATCH 2/2] fix(schema): drop the brand.json color_ref kinds from #8791 step 1 The oneOf discriminator audit (#3917, scripts/audit-oneof.mjs) accepts a discriminator only when each variant pins it to one const value, either directly or under a schema-level `discriminator`. Turning the color_ref variants' `kind` const into a two-value enum fails that check. Passing it means either restructuring color_ref into five single-const variants or accepting an undiscriminated oneOf. Both need a decision, so the color_ref kinds (name -> palette_key, value -> hex) come out of this PR. The six remaining positions are unchanged. Co-Authored-By: Claude Opus 5.5 --- .changeset/reserved-word-enum-replacements.md | 3 +- docs/brand-protocol/brand-json.mdx | 4 +-- docs/reference/migration/v4-compatibility.mdx | 2 +- .../source/universal/v4-compatibility.yaml | 8 ++--- static/schemas/source/brand.json | 31 +++---------------- .../reserved-word-enum-replacements.test.cjs | 23 ++------------ 6 files changed, 12 insertions(+), 59 deletions(-) diff --git a/.changeset/reserved-word-enum-replacements.md b/.changeset/reserved-word-enum-replacements.md index b3cca6abc66..7bb24c3d289 100644 --- a/.changeset/reserved-word-enum-replacements.md +++ b/.changeset/reserved-word-enum-replacements.md @@ -2,11 +2,10 @@ "adcontextprotocol": minor --- -Schema: add reserved-word-safe replacements for eight enum values that collide with SDK target languages, and deprecate the originals (#8791). Each enum now accepts both spellings through 3.x; the original is deprecated in 3.3 and removed in 4.0. +Schema: add reserved-word-safe replacements for six enum positions that collide with SDK target languages, and deprecate the originals (#8791). Each enum now accepts both spellings through 3.x; the original is deprecated in 3.3 and removed in 4.0. - `pass` → `passed` in `enums/binary-verdict.json`, `get_media_buy_artifacts` `local_verdict`, and the registry feed's compliance `tracks` status. `pass` is a Python keyword. - `global` → `agent` in the `RATE_LIMITED` details `scope`. `global` is a Python keyword. - `name` → `full_name` in the signal definition `onboarder.match_keys`, on both `core/signal-definition.json` and `core/signal-definition-enrichment.json`. `name` collides with the enum member protocol in Python and Java. -- `brand.json` `color_ref.kind`: `name` → `palette_key` and `value` → `hex`. Each `oneOf` variant's `kind` changes from `const` to a two-value `enum`, so the old and new kinds select the same variant. The payload fields stay `name` and `value`. Each deprecated value is listed in `x-deprecated-enum-values` and carries `deprecated: true`, `x-deprecated-in: "3.3.0"`, `x-removed-in: "4.0.0"`, and `x-superseded-by` in a per-value `enumMetadata` entry. The 4.0 compatibility registry lists every site. A 3.2 validator rejects the new values, so senders keep the original until the counterparty supports 3.3. diff --git a/docs/brand-protocol/brand-json.mdx b/docs/brand-protocol/brand-json.mdx index c80c4c0c058..d1e88c7a859 100644 --- a/docs/brand-protocol/brand-json.mdx +++ b/docs/brand-protocol/brand-json.mdx @@ -954,9 +954,7 @@ Use `visual_guidelines.color_constraints[]` for machine-readable color usage and } ``` -Each `color_ref` uses an explicit `kind` discriminator: `kind: "palette_key"` looks up the key in `name` in `colors{}`, `kind: "hex"` carries a literal hex color in `value`, and `kind: "surface"` identifies a layout surface such as `background`, `text`, or `logo_background`. - -`kind: "name"` and `kind: "value"` are deprecated in 3.3 and removed in 4.0, because `name` and `value` collide with the enum member protocol in generated Python and Java SDKs ([#8791](https://github.com/adcontextprotocol/adcp/issues/8791)). They select the same variants as `palette_key` and `hex`, and readers accept both spellings through 3.x. The payload fields stay `name` and `value`. A 3.2 reader rejects the new kinds, so keep `name` and `value` in a `brand.json` that 3.2 readers validate. +Each `color_ref` uses an explicit `kind` discriminator: `kind: "name"` looks up a key in `colors{}`, `kind: "value"` carries a literal hex color, and `kind: "surface"` identifies a layout surface such as `background`, `text`, or `logo_background`. ### Mark lockups diff --git a/docs/reference/migration/v4-compatibility.mdx b/docs/reference/migration/v4-compatibility.mdx index 6f90a063264..619e3d9fc27 100644 --- a/docs/reference/migration/v4-compatibility.mdx +++ b/docs/reference/migration/v4-compatibility.mdx @@ -232,7 +232,7 @@ A check is added once its 4.0 change is decided. These changes are still proposa The schemas also mark 46 deprecated fields with `x-removed-in: "4.0.0"`. The registry lists each one, either under the check that covers it or in `schema_removals_not_checked` with the reason no check covers it yet. Most uncovered fields are sent by buyers, so a seller run can't observe them. A few are seller-emitted and checkable but not graded yet: flat `valid_actions`, response-wide delivery `currency` and `aggregated_totals`, `inline_creative_management`, and the format-level build and pricing surface that `list_transformers` replaces. -Deprecated enum values carry the same marker on a per-value `enumMetadata` entry. 4.0 removes the values that collide with SDK reserved words or the enum member protocol ([#8791](https://github.com/adcontextprotocol/adcp/issues/8791)), each replaced in 3.3: `pass` by `passed`, `global` by `agent`, the signal `match_keys` value `name` by `full_name`, and the `brand.json` `color_ref` kinds `name` and `value` by `palette_key` and `hex`. Send the replacement once your counterparty supports 3.3 or later. +Deprecated enum values carry the same marker on a per-value `enumMetadata` entry. 4.0 removes the values that collide with SDK reserved words or the enum member protocol ([#8791](https://github.com/adcontextprotocol/adcp/issues/8791)), each replaced in 3.3: `pass` by `passed`, `global` by `agent`, and the signal `match_keys` value `name` by `full_name`. Send the replacement once your counterparty supports 3.3 or later. The full list of planned 4.0 work is the [4.0 milestone](https://github.com/adcontextprotocol/adcp/milestone/5). diff --git a/static/compliance/source/universal/v4-compatibility.yaml b/static/compliance/source/universal/v4-compatibility.yaml index 2be69b4bdda..01040a23e3e 100644 --- a/static/compliance/source/universal/v4-compatibility.yaml +++ b/static/compliance/source/universal/v4-compatibility.yaml @@ -470,12 +470,10 @@ schema_removals_not_checked: issue: 8791 reason: >- 3.3 adds a replacement beside each value (pass to passed, global to - agent, name to full_name, and color_ref kinds name and value to - palette_key and hex), so both are accepted through 3.x. Senders keep the - old value for 3.2 counterparties, so a 3.x run cannot grade its use. + agent, name to full_name), so both are accepted through 3.x. Senders + keep the old value for 3.2 counterparties, so a 3.x run cannot grade its + use. sites: - - "brand.json#/definitions/color_ref/properties/kind/enumMetadata/name" - - "brand.json#/definitions/color_ref/properties/kind/enumMetadata/value" - "content-standards/get-media-buy-artifacts-response.json#/oneOf/0/properties/artifacts/items/properties/local_verdict/enumMetadata/pass" - "core/registry-event.json#/$defs/compliancePayload/properties/tracks/additionalProperties/enumMetadata/pass" - "core/signal-definition-enrichment.json#/properties/onboarder/properties/match_keys/items/enumMetadata/name" diff --git a/static/schemas/source/brand.json b/static/schemas/source/brand.json index cd1656df02b..840036e4ced 100644 --- a/static/schemas/source/brand.json +++ b/static/schemas/source/brand.json @@ -213,34 +213,11 @@ }, "color_ref": { "type": "object", - "description": "Reference to a brand color or usage surface for machine-readable guideline constraints. Use kind=palette_key for palette lookup keys in colors, kind=hex for a literal hex color, and kind=surface for layout surfaces such as background or text. kind=name and kind=value are deprecated in 3.3 and removed in 4.0; they select the same variants as palette_key and hex.", + "description": "Reference to a brand color or usage surface for machine-readable guideline constraints. Use kind=name for palette lookup keys in colors, kind=value for a literal hex color, and kind=surface for layout surfaces such as background or text.", "properties": { "kind": { "type": "string", - "x-deprecated-enum-values": ["name", "value"], - "x-deprecated-enum-values-doc": "Deprecated in 3.3 and removed in 4.0 (#8791): `name` and `value` collide with the enum member protocol in Python and Java (`Enum.name`, `Enum.value`). Use `palette_key` and `hex`. The payload fields stay `name` and `value`. Receivers accept both values through 3.x. Send the replacement only to a counterparty on AdCP 3.3 or later, because a 3.2 validator rejects it.", - "enum": ["name", "value", "surface", "palette_key", "hex"], - "enumDescriptions": { - "name": "**Deprecated in 3.3**, removed in 4.0: use `palette_key`. Selects the palette-key variant, which carries `name`.", - "value": "**Deprecated in 3.3**, removed in 4.0: use `hex`. Selects the literal-color variant, which carries `value`.", - "surface": "Selects the usage-surface variant, which carries `surface`.", - "palette_key": "Selects the palette-key variant, which carries `name`: a key from colors{}. Replaces the deprecated `name`; a receiver on AdCP 3.2 or earlier rejects it.", - "hex": "Selects the literal-color variant, which carries `value`: a hex color. Replaces the deprecated `value`; a receiver on AdCP 3.2 or earlier rejects it." - }, - "enumMetadata": { - "name": { - "deprecated": true, - "x-deprecated-in": "3.3.0", - "x-removed-in": "4.0.0", - "x-superseded-by": "palette_key" - }, - "value": { - "deprecated": true, - "x-deprecated-in": "3.3.0", - "x-removed-in": "4.0.0", - "x-superseded-by": "hex" - } - }, + "enum": ["name", "value", "surface"], "description": "Discriminator for the color reference variant." }, "name": { @@ -259,11 +236,11 @@ }, "oneOf": [ { - "properties": { "kind": { "enum": ["name", "palette_key"], "x-deprecated-enum-values": ["name"] } }, + "properties": { "kind": { "const": "name" } }, "required": ["kind", "name"] }, { - "properties": { "kind": { "enum": ["value", "hex"], "x-deprecated-enum-values": ["value"] } }, + "properties": { "kind": { "const": "value" } }, "required": ["kind", "value"] }, { diff --git a/tests/reserved-word-enum-replacements.test.cjs b/tests/reserved-word-enum-replacements.test.cjs index 8836203ef0d..9850ab8a3ba 100644 --- a/tests/reserved-word-enum-replacements.test.cjs +++ b/tests/reserved-word-enum-replacements.test.cjs @@ -1,8 +1,8 @@ 'use strict'; // #8791 step 1: enum values that collide with SDK reserved words or with the -// enum member protocol (`pass`, `global`, `name`, `value`) gain a replacement -// in 3.3. Both values are accepted through 3.x; the old one is deprecated and +// enum member protocol (`pass`, `global`, `name`) gain a replacement in +// 3.3. Both values are accepted through 3.x; the old one is deprecated and // removed in 4.0. const assert = require('node:assert/strict'); @@ -26,8 +26,6 @@ const REPLACEMENTS = [ ['error-details/rate-limited.json', '/properties/scope', 'global', 'agent'], ['core/signal-definition.json', '/properties/onboarder/properties/match_keys/items', 'name', 'full_name'], ['core/signal-definition-enrichment.json', '/properties/onboarder/properties/match_keys/items', 'name', 'full_name'], - ['brand.json', '/definitions/color_ref/properties/kind', 'name', 'palette_key'], - ['brand.json', '/definitions/color_ref/properties/kind', 'value', 'hex'], ]; function readSchema(relativePath) { @@ -92,23 +90,6 @@ test('x-deprecated-enum-values only lists values the enum still accepts', () => assert.deepEqual(offenders, []); }); -test('color_ref: old and new kinds select the same variant', async () => { - const validate = await compile({ $ref: '/schemas/brand.json#/definitions/color_ref' }); - const accepts = value => assert.equal(validate(value), true, `${JSON.stringify(value)}: ${JSON.stringify(validate.errors)}`); - const rejects = value => assert.equal(validate(value), false, `${JSON.stringify(value)} should be rejected`); - - accepts({ kind: 'name', name: 'market_yellow' }); - accepts({ kind: 'palette_key', name: 'market_yellow' }); - accepts({ kind: 'value', value: '#FFD200' }); - accepts({ kind: 'hex', value: '#FFD200' }); - accepts({ kind: 'surface', surface: 'background' }); - - rejects({ kind: 'palette_key', value: '#FFD200' }); - rejects({ kind: 'hex', name: 'market_yellow' }); - rejects({ kind: 'palette_key' }); - rejects({ kind: 'hex' }); -}); - test('rate-limited scope accepts agent and the deprecated global', async () => { const validate = await compile(readSchema('error-details/rate-limited.json')); for (const scope of ['account', 'tool', 'global', 'agent']) {