diff --git a/.changeset/reserved-word-enum-replacements.md b/.changeset/reserved-word-enum-replacements.md new file mode 100644 index 00000000000..7bb24c3d289 --- /dev/null +++ b/.changeset/reserved-word-enum-replacements.md @@ -0,0 +1,11 @@ +--- +"adcontextprotocol": minor +--- + +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. + +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/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 9d0f49f12f9..7f43f93c13e 100644 --- a/docs/reference/migration/v4-compatibility.mdx +++ b/docs/reference/migration/v4-compatibility.mdx @@ -272,6 +272,8 @@ A check is added once its 4.0 change is decided. These changes are still proposa The schemas also mark 48 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`, boolean `demographics` in product support, 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`, 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). ## Adding a check diff --git a/package.json b/package.json index 341915eeb11..f73ff8d363a 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/account-ref.test.cjs tests/canonical-account-ref.test.cjs tests/image-bleed.test.cjs tests/sync-plans-reserved-names.test.cjs 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/url-pattern-loosening.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/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/hold-policy.test.cjs tests/hold-policy-vectors.test.cjs tests/canonical-installments.test.cjs tests/withdrawn-to-4-0.test.cjs tests/get-products-deprecated-withdrawals.test.cjs tests/deprecated-task-withdrawals.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/account-ref.test.cjs tests/canonical-account-ref.test.cjs tests/image-bleed.test.cjs tests/sync-plans-reserved-names.test.cjs 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/url-pattern-loosening.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/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/hold-policy.test.cjs tests/hold-policy-vectors.test.cjs tests/canonical-installments.test.cjs tests/withdrawn-to-4-0.test.cjs tests/get-products-deprecated-withdrawals.test.cjs tests/deprecated-task-withdrawals.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 fe0a4a7a4d5..00a3bf5ab9a 100644 --- a/static/compliance/source/universal/v4-compatibility.yaml +++ b/static/compliance/source/universal/v4-compatibility.yaml @@ -502,3 +502,18 @@ schema_removals_not_checked: sites: - "core/targeting-overlay-support.json#/properties/demographics/anyOf/0" - "core/targeting-overlay-requirements.json#/properties/demographics/anyOf/0" + + - 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), 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: + - "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/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 c7ded149f83..b9866a2d4e1 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 7a3c76d306f..9d4f20eb47e 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 56de34b6da5..1a351f4d662 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 16792db8b4c..863b99ae591 100644 --- a/tests/mcp-schema-projection.test.cjs +++ b/tests/mcp-schema-projection.test.cjs @@ -226,6 +226,9 @@ const MODEL_CONTEXT_BUDGET_KIB = { // #8501 entries 11-12 restore sla-window to its 3.2.8 text and give the // creative review SLA its own clock, durations and calendar constraints in // place of a $ref; the controller measures 1_387_457 bytes, bound at 1_388_000. +// Reserved-word enum replacements (#8791) add a replacement value and its +// deprecation metadata to enums in the controller closure; merged with the +// above they stay within 1_388_000. const PARITY_COMPILE_LIMIT = 1_388_000; function readJson(filename) { diff --git a/tests/reserved-word-enum-replacements.test.cjs b/tests/reserved-word-enum-replacements.test.cjs new file mode 100644 index 00000000000..9850ab8a3ba --- /dev/null +++ b/tests/reserved-word-enum-replacements.test.cjs @@ -0,0 +1,98 @@ +'use strict'; + +// #8791 step 1: enum values that collide with SDK reserved words or with the +// 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'); +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'], +]; + +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('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); + } +});