From 964c756f31b74c9048534f4a466552f7109d2fac Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Fri, 30 Oct 2026 17:52:52 +0000 Subject: [PATCH] feat(compliance): reporting_subscription_probe controller scenario for grants, timezone, and clock Adds the experimental comply_test_controller scenario reporting_subscription_probe (media_buy.reporting_subscriptions) with revoke_grant, regrant, reduce_grant, set_account_timezone, and advance_time, plus the allowed_tasks and timezone params. Storyboards can then test reporting-subscription coverage across grants, wind-downs, and timezone cutovers against a remote seller on one virtual clock. Co-Authored-By: Claude Opus 5.5 (1M context) --- ...reporting-subscription-controller-probe.md | 5 + .../by-layer/L3/comply-test-controller.mdx | 28 ++++ package.json | 2 +- .../comply-test-controller-request.json | 150 +++++++++++++++++- .../comply-test-controller-response.json | 4 +- tests/reporting-subscription-probe.test.cjs | 70 ++++++++ tests/x-added-in-3-3-sweep.test.cjs | 2 + 7 files changed, 254 insertions(+), 7 deletions(-) create mode 100644 .changeset/reporting-subscription-controller-probe.md create mode 100644 tests/reporting-subscription-probe.test.cjs diff --git a/.changeset/reporting-subscription-controller-probe.md b/.changeset/reporting-subscription-controller-probe.md new file mode 100644 index 00000000000..489dafc3e83 --- /dev/null +++ b/.changeset/reporting-subscription-controller-probe.md @@ -0,0 +1,5 @@ +--- +"adcontextprotocol": minor +--- + +Compliance (experimental): `comply_test_controller` adds the `reporting_subscription_probe` scenario for `media_buy.reporting_subscriptions`. Its operations are `revoke_grant`, `regrant`, `reduce_grant` (new `allowed_tasks` param), `set_account_timezone` (new `timezone` param), and `advance_time` (`target_time`). They act only on the caller's own grant on sandbox accounts and on one virtual clock, `simulated_now`. With them, storyboards can test coverage, `accounts_unauthorized`, gaps, withheld periods, wind-down grants, and timezone cutovers against a remote seller without waiting on wall-clock boundaries. diff --git a/docs/building/by-layer/L3/comply-test-controller.mdx b/docs/building/by-layer/L3/comply-test-controller.mdx index 77d267e6629..3f7c53f0ac9 100644 --- a/docs/building/by-layer/L3/comply-test-controller.mdx +++ b/docs/building/by-layer/L3/comply-test-controller.mdx @@ -625,6 +625,34 @@ Reset with `prepare` before the negative fixture, then call: } ``` +### `reporting_subscription_probe` + +Experimental (`media_buy.reporting_subscriptions`). It drives the inputs that +[reporting-subscription coverage](/docs/media-buy/task-reference/get_reporting_status#subscription-coverage) +depends on but a buyer can't change itself: the caller's grant on an account, the +account's timezone, and the clock. A storyboard can then check, against a remote +seller, that coverage, `accounts_unauthorized[]`, `gaps[]`, `timezone_cutovers[]`, +and withheld periods follow the spec. Seed buys with `seed_media_buy`. + +The probe acts only on sandbox accounts and only on the authenticated caller's own +grant. It runs on one virtual clock, `simulated_now`, which every authorization +timestamp, cutover, period boundary, obligation, and delivery for the caller's +sandbox accounts uses. A revoked account is unknown to every other task, as the spec +requires, but the controller still accepts `regrant` and `advance_time` for it. + +| Operation | Params | Required observable result | +|---|---|---| +| **revoke_grant** | `account_id` | End the caller's authority for the account at `simulated_now`. The account reads as unknown to [`get_reporting_status`](/docs/media-buy/task-reference/get_reporting_status) and every other task. Each covering subscription lists it in `accounts_unauthorized[]` on [`get_principal`](/docs/protocol/get_principal), with `unauthorized_at` equal to `simulated_now`. Return `unauthorized_at`. | +| **regrant** | `account_id` | Restore the grant at `simulated_now`. Before `unauthorized_at` plus `status_retention_days`, this continues the chain: `authorization.authorized_at` is unchanged, the account leaves `accounts_unauthorized[]`, and withheld periods are owed again. Later, it starts a new chain with `authorized_at` equal to `simulated_now`. Return `authorized_at`, `chain` (`continued` or `new`), and `regranted_at` when the chain continued. | +| **reduce_grant** | `account_id`, `allowed_tasks` | Replace the caller's `allowed_tasks` on the account, with `read_only: false`, at `simulated_now`. A list that keeps `get_reporting_status` keeps the account covered (the wind-down grant). A list without it takes the account out of coverage. Tasks the list drops fail for the account with [`SCOPE_INSUFFICIENT`](/docs/building/verification/compliance-catalog#error-code-scope-insufficient). Return the applied `allowed_tasks`. | +| **set_account_timezone** | `account_id`, `timezone` | Change the account's timezone at `simulated_now`, as an applied account change: [`list_accounts`](/docs/accounts/tasks/list_accounts) shows the new `timezone` and a new `revision`. Account-timezone periods cut over at the first old-timezone boundary at or after the change, recorded in `timezone_cutovers[]`. Return `previous_timezone`, `timezone`, and the `effective_at` cutover instant for each affected offering. | +| **advance_time** | `target_time` | Move `simulated_now` forward to `target_time`. Reject a time at or before the current `simulated_now`. Every period boundary crossed is processed as in production: obligations are committed, subscriptions resolve, deliveries are attempted, and authority is re-checked. Return the applied `simulated_now`. | + +Every successful response is a `SimulationSuccess` whose `simulated` object carries +`simulated_now` and, except for `advance_time`, `account_id`. The operations are +convergent: repeating one at the same `simulated_now` returns the same result, and +none mutates state outside the caller's sandbox accounts. + ### `force_create_media_buy_arm` Shapes the next [`create_media_buy`](/docs/media-buy/task-reference/create_media_buy) call from the caller's authenticated sandbox account into a specific response arm. v1 supports two arms: `submitted` (the async task envelope, no `media_buy_id` yet) and `input-required` (the errors-branch). Unlike `force_media_buy_status`, no entity transitions — there is no media buy yet — so the response carries `forced.arm` rather than `previous_state`/`current_state`. diff --git a/package.json b/package.json index d95de7fbc5b..38661df18c7 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/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/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-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/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/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: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/schemas/source/compliance/comply-test-controller-request.json b/static/schemas/source/compliance/comply-test-controller-request.json index d3271d1806b..3fe01744a9f 100644 --- a/static/schemas/source/compliance/comply-test-controller-request.json +++ b/static/schemas/source/compliance/comply-test-controller-request.json @@ -994,13 +994,137 @@ } } } + }, + { + "if": { + "properties": { + "scenario": { + "const": "reporting_subscription_probe" + } + }, + "required": [ + "scenario" + ] + }, + "then": { + "required": [ + "params" + ], + "properties": { + "params": { + "required": [ + "operation" + ], + "properties": { + "operation": { + "enum": [ + "revoke_grant", + "regrant", + "reduce_grant", + "set_account_timezone", + "advance_time" + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "operation": { + "enum": [ + "revoke_grant", + "regrant", + "reduce_grant", + "set_account_timezone" + ] + } + }, + "required": [ + "operation" + ] + }, + "then": { + "required": [ + "account_id" + ] + } + }, + { + "if": { + "properties": { + "operation": { + "const": "reduce_grant" + } + }, + "required": [ + "operation" + ] + }, + "then": { + "required": [ + "allowed_tasks" + ] + }, + "else": { + "not": { + "required": [ + "allowed_tasks" + ] + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "set_account_timezone" + } + }, + "required": [ + "operation" + ] + }, + "then": { + "required": [ + "timezone" + ] + }, + "else": { + "not": { + "required": [ + "timezone" + ] + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "advance_time" + } + }, + "required": [ + "operation" + ] + }, + "then": { + "required": [ + "target_time" + ] + } + } + ] + } + } + } } ], "x-mutates-state": true, "properties": { "scenario": { "type": "string", - "description": "Test scenario to execute. 'list_scenarios' discovers supported scenarios. 'force_*' and 'simulate_*' trigger state transitions. 'reporting_core_lifecycle_probe' installs a caller/account-scoped Core fixture whose first elapsed obligation is visible before any report, advances its virtual clock into delayed or action_required, and can publish deterministic zero-row or non-empty revisions, restate a provisional revision, restate one the caller already received, or cross the consumer-status deadline and consumer-mismatch escalation boundaries, without waiting for wall-clock boundaries. 'force_hold_unavailable' (experimental, media_buy.hold_policy) makes the next refine_proposals finalize of one draft proposal fail with hold_unavailable, its batch siblings with batch_aborted, and is then consumed. Other scenarios provide deterministic sandbox probes for their documented lifecycle checks. Runners and sellers MUST accept unknown scenario strings - new scenarios may be added in additive releases." + "description": "Test scenario to execute. 'list_scenarios' discovers supported scenarios. 'reporting_subscription_probe' (experimental, media_buy.reporting_subscriptions) revokes, re-grants, or reduces the caller's own grant on a sandbox account, changes a sandbox account's timezone, or advances the sandbox clock, so storyboards can test reporting-subscription coverage across grants and cutovers against a remote seller. 'force_*' and 'simulate_*' trigger state transitions. 'reporting_core_lifecycle_probe' installs a caller/account-scoped Core fixture whose first elapsed obligation is visible before any report, advances its virtual clock into delayed or action_required, and can publish deterministic zero-row or non-empty revisions, restate a provisional revision, restate one the caller already received, or cross the consumer-status deadline and consumer-mismatch escalation boundaries, without waiting for wall-clock boundaries. 'force_hold_unavailable' (experimental, media_buy.hold_policy) makes the next refine_proposals finalize of one draft proposal fail with hold_unavailable, its batch siblings with batch_aborted, and is then consumed. Other scenarios provide deterministic sandbox probes for their documented lifecycle checks. Runners and sellers MUST accept unknown scenario strings - new scenarios may be added in additive releases." }, "params": { "type": "object", @@ -1071,8 +1195,8 @@ }, "operation": { "type": "string", - "enum": ["seed_inaccessible_item", "query_eligibility", "advance_time", "recreate_catalog", "prepare", "expire_proposal", "publish_zero_row", "publish_nonempty", "restate_snapshot", "restate_after_received", "omit_obligation", "advance_past_status_deadline", "advance_past_escalation", "publish_official_adjustment", "probe_scheduler_dst", "suppress_readiness", "advance_within_retention", "revoke_access", "publish_adjustment"], - "description": "Scenario-specific probe operation. catalog_item_availability_probe uses seed_inaccessible_item, query_eligibility, advance_time, and recreate_catalog. compact_product_lifecycle_probe uses prepare to make one seeded product's compact proposal, acceptance, operational-control, and MediaBuy readback path deterministic and expire_proposal to advance strictly beyond a committed proposal's stored expires_at and process the hold lapse. compact_direct_buy_lifecycle_probe uses prepare to make one seeded product's list, direct-purchase, operational-control, and readback path deterministic. reporting_core_lifecycle_probe uses prepare, advance_time, publish_zero_row, publish_nonempty, restate_snapshot, restate_after_received, omit_obligation, advance_past_status_deadline, and advance_past_escalation to exercise obligation availability, health deadlines, explicit reporting, provisional restatement, stale-received grace, buyer-side missing-obligation detection, counted consumer-status silence, and consumer-mismatch escalation. advance_past_status_deadline moves the clock strictly past expected_at plus the advertised automated_recovery_window_seconds without recording any consumer status, so obligation_counts.consumer_status_pending is gradable; advance_past_escalation moves it strictly past the open CONSUMER_STATUS_MISMATCH issue opened_at plus the advertised consumer_mismatch_escalation_seconds and returns both that boundary and any still-future stale_received_grace_deadline, so the escalation precedence rule is gradable. restate_snapshot publishes a new snapshot revision that immediately supersedes the current snapshot for the same logical slice and is invalid when the current revision is official. restate_after_received does the same restatement but only against the revision named in received_reporting_revision_id, so the stale-received grace projection can be graded live; it returns the grace deadline and advance_to positions the virtual clock inside or past it. Reliable Reporting tier probes use prepare plus publish_official_adjustment, probe_scheduler_dst, suppress_readiness, advance_within_retention, revoke_access, or publish_adjustment to seed deterministic Core-integrity, Managed Delivery, and Reconciled Billing lifecycle evidence." + "enum": ["seed_inaccessible_item", "query_eligibility", "advance_time", "recreate_catalog", "prepare", "expire_proposal", "publish_zero_row", "publish_nonempty", "restate_snapshot", "restate_after_received", "omit_obligation", "advance_past_status_deadline", "advance_past_escalation", "publish_official_adjustment", "probe_scheduler_dst", "suppress_readiness", "advance_within_retention", "revoke_access", "publish_adjustment", "revoke_grant", "regrant", "reduce_grant", "set_account_timezone"], + "description": "Scenario-specific probe operation. catalog_item_availability_probe uses seed_inaccessible_item, query_eligibility, advance_time, and recreate_catalog. compact_product_lifecycle_probe uses prepare to make one seeded product's compact proposal, acceptance, operational-control, and MediaBuy readback path deterministic and expire_proposal to advance strictly beyond a committed proposal's stored expires_at and process the hold lapse. compact_direct_buy_lifecycle_probe uses prepare to make one seeded product's list, direct-purchase, operational-control, and readback path deterministic. reporting_core_lifecycle_probe uses prepare, advance_time, publish_zero_row, publish_nonempty, restate_snapshot, restate_after_received, omit_obligation, advance_past_status_deadline, and advance_past_escalation to exercise obligation availability, health deadlines, explicit reporting, provisional restatement, stale-received grace, buyer-side missing-obligation detection, counted consumer-status silence, and consumer-mismatch escalation. advance_past_status_deadline moves the clock strictly past expected_at plus the advertised automated_recovery_window_seconds without recording any consumer status, so obligation_counts.consumer_status_pending is gradable; advance_past_escalation moves it strictly past the open CONSUMER_STATUS_MISMATCH issue opened_at plus the advertised consumer_mismatch_escalation_seconds and returns both that boundary and any still-future stale_received_grace_deadline, so the escalation precedence rule is gradable. restate_snapshot publishes a new snapshot revision that immediately supersedes the current snapshot for the same logical slice and is invalid when the current revision is official. restate_after_received does the same restatement but only against the revision named in received_reporting_revision_id, so the stale-received grace projection can be graded live; it returns the grace deadline and advance_to positions the virtual clock inside or past it. Reliable Reporting tier probes use prepare plus publish_official_adjustment, probe_scheduler_dst, suppress_readiness, advance_within_retention, revoke_access, or publish_adjustment to seed deterministic Core-integrity, Managed Delivery, and Reconciled Billing lifecycle evidence. reporting_subscription_probe (experimental, media_buy.reporting_subscriptions) uses revoke_grant, regrant, reduce_grant, set_account_timezone, and advance_time to drive grants, account timezone cutovers, and the sandbox clock for reporting-subscription coverage." }, "target_health": { "type": "string", @@ -1114,10 +1238,28 @@ "description": "Catalog item operated on by catalog_item_availability_probe.", "x-entity": "catalog_item" }, + "allowed_tasks": { + "type": "array", + "x-status": "experimental", + "x-added-in": "3.3.0", + "items": { "type": "string", "pattern": "^[a-z][a-z0-9_]*$", "maxLength": 64 }, + "minItems": 1, + "maxItems": 64, + "uniqueItems": true, + "description": "reporting_subscription_probe reduce_grant: the complete allowed_tasks the caller's grant on account_id keeps, for example only get_reporting_status, sync_reporting_receipts, and sync_reporting_status to model a wind-down." + }, + "timezone": { + "type": "string", + "x-status": "experimental", + "x-added-in": "3.3.0", + "minLength": 1, + "maxLength": 255, + "description": "reporting_subscription_probe set_account_timezone: the account's new IANA timezone." + }, "target_time": { "type": "string", "format": "date-time", - "description": "Deterministic sandbox clock target used by catalog_item_availability_probe advance_time." + "description": "Deterministic sandbox clock target used by catalog_item_availability_probe advance_time and reporting_subscription_probe advance_time." }, "status": { "type": "string", diff --git a/static/schemas/source/compliance/comply-test-controller-response.json b/static/schemas/source/compliance/comply-test-controller-response.json index 9b2a16a88e7..a78650cf68a 100644 --- a/static/schemas/source/compliance/comply-test-controller-response.json +++ b/static/schemas/source/compliance/comply-test-controller-response.json @@ -24,7 +24,7 @@ "items": { "type": "string" }, - "description": "Scenarios this seller has implemented. Runners and sellers MUST accept unknown scenario strings (open-for-extension) — new scenarios may be added in additive releases. Adopters who advertise `catalog_item_availability_probe` support deterministic cross-principal reference, eligibility-gate, expiry-clock, and catalog-generation tests for the catalog availability storyboard. Adopters who advertise `compact_product_lifecycle_probe` support deterministic synchronous list/request/finalize/decline/accept/control/readback behavior for a prepared product and strict post-deadline expiry of a committed proposal. Adopters who advertise `compact_direct_buy_lifecycle_probe` support deterministic synchronous list/buy/control/readback behavior for a prepared product. Adopters who advertise `reporting_core_lifecycle_probe` support deterministic obligation-before-report, clock-health, zero-row reporting, provisional-restatement, and post-received restatement-grace tests without wall-clock waits. `reliable_reporting_core_integrity_probe`, `reliable_reporting_managed_delivery_probe`, and `reliable_reporting_reconciled_billing_probe` seed the source-calendar/checkpoint, managed-resource, and receipt/adjustment workflows used by the Reliable Reporting tier storyboards. Adopters who advertise `force_creative_purge` opt in to deterministic creative purge coverage for account-level lifecycle webhooks. Adopters who advertise `force_media_buy_purge` opt in to deterministic deletion-independent idempotency replay coverage. Adopters who advertise `seed_measurement_catalog` opt in to deterministic measurement-catalog fixtures used by vendor_metric precondition storyboards. Adopters who advertise `query_upstream_traffic` opt in to the upstream-traffic conformance contract; storyboards that declare `check: upstream_traffic` grade not_applicable against adopters who do not advertise it. Adopters who advertise `query_provenance_audit_observations` opt in to sandbox-only audit-observation assertions for accepted creatives. Adopters who advertise `force_upstream_unavailable` opt in to stale-cache conformance testing via the `stale_response_advisory` storyboard. Adopters who advertise `force_hold_unavailable` opt in to deterministic hold-failure coverage for `refine_proposals` finalize (experimental, `media_buy.hold_policy`)." + "description": "Scenarios this seller has implemented. Runners and sellers MUST accept unknown scenario strings (open-for-extension) — new scenarios may be added in additive releases. Adopters who advertise `catalog_item_availability_probe` support deterministic cross-principal reference, eligibility-gate, expiry-clock, and catalog-generation tests for the catalog availability storyboard. Adopters who advertise `compact_product_lifecycle_probe` support deterministic synchronous list/request/finalize/decline/accept/control/readback behavior for a prepared product and strict post-deadline expiry of a committed proposal. Adopters who advertise `compact_direct_buy_lifecycle_probe` support deterministic synchronous list/buy/control/readback behavior for a prepared product. Adopters who advertise `reporting_core_lifecycle_probe` support deterministic obligation-before-report, clock-health, zero-row reporting, provisional-restatement, and post-received restatement-grace tests without wall-clock waits. Adopters who advertise `reporting_subscription_probe` (experimental) support deterministic grant revocation, re-grant, grant reduction, account timezone cutover, and sandbox clock tests for reporting-subscription coverage. `reliable_reporting_core_integrity_probe`, `reliable_reporting_managed_delivery_probe`, and `reliable_reporting_reconciled_billing_probe` seed the source-calendar/checkpoint, managed-resource, and receipt/adjustment workflows used by the Reliable Reporting tier storyboards. Adopters who advertise `force_creative_purge` opt in to deterministic creative purge coverage for account-level lifecycle webhooks. Adopters who advertise `force_media_buy_purge` opt in to deterministic deletion-independent idempotency replay coverage. Adopters who advertise `seed_measurement_catalog` opt in to deterministic measurement-catalog fixtures used by vendor_metric precondition storyboards. Adopters who advertise `query_upstream_traffic` opt in to the upstream-traffic conformance contract; storyboards that declare `check: upstream_traffic` grade not_applicable against adopters who do not advertise it. Adopters who advertise `query_provenance_audit_observations` opt in to sandbox-only audit-observation assertions for accepted creatives. Adopters who advertise `force_upstream_unavailable` opt in to stale-cache conformance testing via the `stale_response_advisory` storyboard. Adopters who advertise `force_hold_unavailable` opt in to deterministic hold-failure coverage for `refine_proposals` finalize (experimental, `media_buy.hold_policy`)." }, "context": { "$ref": "/schemas/core/context.json" @@ -140,7 +140,7 @@ }, { "title": "SimulationSuccess", - "description": "A simulate_delivery, simulate_budget_spend, simulate_later_proposal, catalog_item_availability_probe, compact_product_lifecycle_probe, compact_direct_buy_lifecycle_probe, reporting_core_lifecycle_probe, or Reliable Reporting tier-probe operation succeeded. For delivery: simulated contains the metrics injected by this call (impressions/clicks/plays/reported_spend/conversions plus optional DOOH, reach, frequency, reach-window, and viewability values) and cumulative contains running totals or latest non-additive metric state. For budget: simulated contains spend_percentage/computed_spend/budget. For a later proposal: simulated contains proposal_id, opportunity_id, and later_reason of the created snapshot. For catalog availability: simulated reports seeded foreign identity, actual eligibility gates, processed expiry time, or delete/recreate generation rotation according to params.operation. For compact product lifecycle: simulated reports deterministic preparation through MediaBuy control/readback or strict post-deadline proposal expiry. For compact direct-buy lifecycle: simulated reports deterministic preparation. For reporting Core: simulated reports the fixed virtual time plus stable account, configuration, obligation, and revision identifiers created or advanced by the operation; prepare also reports the complete resolved configuration, period, expected_at, and recovery_deadline, restate_snapshot reports the new revision plus supersedes_reporting_revision_id, and restate_after_received additionally reports received_reporting_revision_id, restated_at, and stale_received_grace_deadline; advance_past_status_deadline reports expected_at, automated_recovery_window_seconds, and the crossed consumer_status_deadline; and advance_past_escalation reports issue_id, issue_opened_at, consumer_mismatch_escalation_seconds, consumer_mismatch_escalation_deadline, expected_recommended_action, and any still-future stale_received_grace_deadline. Reliable Reporting tier probes return the exact seeded ledger IDs, checkpoints, notification order, resource lifecycle evidence, and receipt bodies required by their storyboards.", + "description": "A simulate_delivery, simulate_budget_spend, simulate_later_proposal, catalog_item_availability_probe, compact_product_lifecycle_probe, compact_direct_buy_lifecycle_probe, reporting_core_lifecycle_probe, reporting_subscription_probe, or Reliable Reporting tier-probe operation succeeded. For delivery: simulated contains the metrics injected by this call (impressions/clicks/plays/reported_spend/conversions plus optional DOOH, reach, frequency, reach-window, and viewability values) and cumulative contains running totals or latest non-additive metric state. For budget: simulated contains spend_percentage/computed_spend/budget. For a later proposal: simulated contains proposal_id, opportunity_id, and later_reason of the created snapshot. For catalog availability: simulated reports seeded foreign identity, actual eligibility gates, processed expiry time, or delete/recreate generation rotation according to params.operation. For compact product lifecycle: simulated reports deterministic preparation through MediaBuy control/readback or strict post-deadline proposal expiry. For compact direct-buy lifecycle: simulated reports deterministic preparation. For reporting Core: simulated reports the fixed virtual time plus stable account, configuration, obligation, and revision identifiers created or advanced by the operation; prepare also reports the complete resolved configuration, period, expected_at, and recovery_deadline, restate_snapshot reports the new revision plus supersedes_reporting_revision_id, and restate_after_received additionally reports received_reporting_revision_id, restated_at, and stale_received_grace_deadline; advance_past_status_deadline reports expected_at, automated_recovery_window_seconds, and the crossed consumer_status_deadline; and advance_past_escalation reports issue_id, issue_opened_at, consumer_mismatch_escalation_seconds, consumer_mismatch_escalation_deadline, expected_recommended_action, and any still-future stale_received_grace_deadline. For reporting subscriptions: simulated reports simulated_now and account_id; revoke_grant adds unauthorized_at; regrant adds authorized_at, regranted_at when the re-grant continued the chain, and chain (continued or new); reduce_grant adds the applied allowed_tasks; set_account_timezone adds previous_timezone, timezone, and the per-offering effective_at cutover instants; advance_time adds the applied simulated_now. Reliable Reporting tier probes return the exact seeded ledger IDs, checkpoints, notification order, resource lifecycle evidence, and receipt bodies required by their storyboards.", "type": "object", "properties": { "success": { diff --git a/tests/reporting-subscription-probe.test.cjs b/tests/reporting-subscription-probe.test.cjs new file mode 100644 index 00000000000..590fef08a2c --- /dev/null +++ b/tests/reporting-subscription-probe.test.cjs @@ -0,0 +1,70 @@ +'use strict'; + +/** + * comply_test_controller scenario reporting_subscription_probe (experimental, + * media_buy.reporting_subscriptions): grant, timezone, and clock operations so + * storyboards can test reporting-subscription coverage against a remote seller. + */ + +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 ROOT = path.resolve(__dirname, '..'); +const SCHEMAS = path.join(ROOT, 'static/schemas/source'); +const read = file => JSON.parse(fs.readFileSync(path.join(SCHEMAS, file), 'utf8')); + +const ajv = new Ajv({ allErrors: true, strict: false }); +addFormats(ajv); +(function 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')) { + const schema = JSON.parse(fs.readFileSync(full, 'utf8')); + if (schema.$id && !ajv.getSchema(schema.$id)) ajv.addSchema(schema); + } + } +})(SCHEMAS); +const request = ajv.getSchema('/schemas/compliance/comply-test-controller-request.json'); +const response = ajv.getSchema('/schemas/compliance/comply-test-controller-response.json'); +const call = params => ({ scenario: 'reporting_subscription_probe', account: { sandbox: true }, params }); + +test('each operation carries exactly its parameters', () => { + const ok = [ + { operation: 'revoke_grant', account_id: 'acc_sandbox' }, + { operation: 'regrant', account_id: 'acc_sandbox' }, + { operation: 'reduce_grant', account_id: 'acc_sandbox', allowed_tasks: ['get_reporting_status', 'sync_reporting_receipts', 'sync_reporting_status'] }, + { operation: 'set_account_timezone', account_id: 'acc_sandbox', timezone: 'America/Los_Angeles' }, + { operation: 'advance_time', target_time: '2026-11-04T05:00:00Z' }, + ]; + for (const params of ok) assert.ok(request(call(params)), `${params.operation}: ${JSON.stringify(request.errors)}`); + const bad = [ + ['revoke without an account', { operation: 'revoke_grant' }], + ['reduce without allowed_tasks', { operation: 'reduce_grant', account_id: 'acc_sandbox' }], + ['reduce to nothing', { operation: 'reduce_grant', account_id: 'acc_sandbox', allowed_tasks: [] }], + ['allowed_tasks on another operation', { operation: 'regrant', account_id: 'acc_sandbox', allowed_tasks: ['get_reporting_status'] }], + ['timezone change without a zone', { operation: 'set_account_timezone', account_id: 'acc_sandbox' }], + ['timezone on another operation', { operation: 'revoke_grant', account_id: 'acc_sandbox', timezone: 'UTC' }], + ['advance without a target', { operation: 'advance_time' }], + ['an operation of another probe', { operation: 'publish_zero_row', account_id: 'acc_sandbox' }], + ]; + for (const [name, params] of bad) assert.equal(request(call(params)), false, name); +}); + +test('results are simulation responses and the scenario is documented', () => { + const res = { status: 'completed', success: true, simulated: { simulated_now: '2026-11-20T09:00:00Z', account_id: 'acc_sandbox', unauthorized_at: '2026-11-20T09:00:00Z' } }; + assert.ok(response(res), JSON.stringify(response.errors)); + const req = read('compliance/comply-test-controller-request.json'); + assert.match(req.properties.scenario.description, /'reporting_subscription_probe' \(experimental, media_buy\.reporting_subscriptions\)/); + for (const k of ['allowed_tasks', 'timezone']) { + assert.equal(req.properties.params.properties[k]['x-status'], 'experimental', k); + assert.equal(req.properties.params.properties[k]['x-added-in'], '3.3.0', k); + } + const doc = fs.readFileSync(path.join(ROOT, 'docs/building/by-layer/L3/comply-test-controller.mdx'), 'utf8'); + for (const op of ['revoke_grant', 'regrant', 'reduce_grant', 'set_account_timezone', 'advance_time']) assert.match(doc, new RegExp(`\\*\\*${op}\\*\\*`), op); + assert.match(doc, /only on the authenticated caller's own\ngrant/); +}); diff --git a/tests/x-added-in-3-3-sweep.test.cjs b/tests/x-added-in-3-3-sweep.test.cjs index 36b1dd50a63..6803f60b45b 100644 --- a/tests/x-added-in-3-3-sweep.test.cjs +++ b/tests/x-added-in-3-3-sweep.test.cjs @@ -138,6 +138,8 @@ const PROPERTIES_3_3 = [ ['core/reporting-delivery-offering.json', ['properties', 'withdrawn_at']], ['core/reporting-consumer-status.json', ['properties', 'offering_id']], ['media-buy/get-reporting-status-response.json', ['properties', 'scope', 'properties', 'delivery_config_generations', 'items', 'properties', 'offering_id']], + ['compliance/comply-test-controller-request.json', ['properties', 'params', 'properties', 'allowed_tasks']], + ['compliance/comply-test-controller-request.json', ['properties', 'params', 'properties', 'timezone']], ['core/reporting-delivery-offering.json', ['properties', 'standard_dimensions']], ['core/reporting-delivery-offering.json', ['properties', 'unmapped_dimensions']], ['media-buy/get-media-buys-response.json', ['properties', 'media_buys', 'items', 'properties', 'completed_at']],