Skip to content

feat(reporting): adopt AdCP 3.3.0-beta.4 coverage, grant, controller rules and bundle - #1851

Draft
bokelley wants to merge 2 commits into
nextfrom
claude/adopt-3-3-beta-4
Draft

bokelley wants to merge 2 commits into
nextfrom
claude/adopt-3-3-beta-4

Conversation

@bokelley

@bokelley bokelley commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Adopts the AdCP 3.3.0-beta.4 reporting-subscription changes (adcontextprotocol/adcp#8718, #8717, #8724) on the SDK 10 / next line.

Status: draft until the bundle pin lands. The signed 3.3.0-beta.4 bundle is not published yet: the spec release run is waiting on an approval gate. The bundle-pin commit (ADCP_VERSION, schema cache, packaged compliance tree, regenerated types, 3.3-beta.4 negotiation) follows once it is published. Until then, CI fails the 8 tests that read the packaged beta.4 tree or schemas: the before_package_added reason vocabulary, vector wire-validity under the new duration rule, and the reset_state scenario description. Every other test passes.

Changes

  1. Per-package added_at (rule change). SubscriptionBuyPackage.added_at is read from get_media_buys packages[].added_at, and the managed path keeps it, also under buy_offerings. SubscriptionBuy.package_confirmed_at() returns max(confirmed_at, added_at). A period is owed when it ends after that instant for at least one package resolving the offering. Each package resolves at the later of the version's accepted_at and its own instant, and adding a package is never a cutover. explain has the new before_package_added reason, with the same facts (packages_added_at), sentence and buy facts as the TS SDK. The test kit gains kit.buys.add_package, which serves added_at.
  2. Reduced grant without get_reporting_status. The engine already treated this as a loss at the reduction (accounts_unauthorized, withheld periods, re-grant when the task is added back). It now keeps the reduced grant's tasks, so get_reporting_status refuses the still-visible account with SCOPE_INSUFFICIENT. A revoked account stays ACCOUNT_NOT_FOUND.
  3. SUBSCRIPTIONS_REQUIRE_ACCOUNT_ID_KEY. check_subscription_offerings() runs the spec's offering-checks.json and passes all 8 cases. The managed service now refuses a managed-delivery offering whose primary_keys omit account_id; beta.3 only logged it. ReliableReportingConfigurationError.context carries offering_id and missing.
  4. Schema changes (no-mixed-durations, the destination_overrides if/then/else, added_at, SetAccountTimezoneResult): these come with the bundle-pin commit.
  5. tzdata. The vectors' new zones (Pacific/Auckland, Pacific/Chatham, Asia/Kolkata, America/Mexico_City) pass under the existing pin, tzdata==2026.6 (2026f). The parity test ignores the host's zoneinfo. No runtime pin: zoneinfo prefers the system database over the tzdata package, so pinning would not make results deterministic. The vectors' dates also agree across tzdata 2026b through 2026f. This is documented in docs/reporting-subscriptions-coverage.md.
  6. Controller.
    • reset_state is a scenario and a TestControllerStore hook. register_test_controller and the A2A server refuse a store that implements reporting_subscription_probe without it.
    • Probe results are nested under simulated, as SimulationSuccess requires.
    • The reference seller serves reset_state. It unpins the clock, restores the caller's default grant and the original timezone, and discards what the probe recorded via ReportingSubscriptionEngine.discard_sandbox_changes.
    • The reference seller's set_account_timezone returns SetAccountTimezoneResult.
    • The Python SDK has no storyboard runner implementing owed_periods_match, so the new window rule has nothing to apply to.

Parity fixes, matching adcontextprotocol/adcp-client#3620 (separate commit):

  • A package_ids exception no longer changes resolution.
  • excluded_by_buyer applies in-place membership only to the versions it overlaps.
  • The 3 differential regressions are vendored and pass.
  • At an account-timezone cutover, a billing feed owes a stub, following the cutover text. The 8ee1bfa2 reference model emits none there; the spec fixes that for beta.5.

Vectors

All 138 canonical coverage vectors pass, including the 18 not in the previously vendored file. Also passing: the 8 offering checks and the 3 differential regressions.

Storage contract

Unchanged. added_at is read from the host's buys on each computation and never persisted. The reduced grant's tasks use the existing optional allowed_tasks key of the engine state document (STATE_DOCUMENT_VERSION 1). No ledger column changes, so there is no REPORTING_STORAGE_CONTRACT bump and no migration.

🤖 Generated with Claude Code

…rules

Per-package added_at (adcp#8718). SubscriptionBuyPackage carries the
get_media_buys packages[].added_at, read by SubscriptionBuy.from_media_buy
and kept by the managed path also under buy_offerings. Each package is owed
from the later of the buy's confirmed_at and its added_at
(SubscriptionBuy.package_confirmed_at), and resolves at the later of the
version's accepted_at and that instant. A period of a resolved offering is
owed when it ends after that instant for at least one package resolving the
offering, so the period containing added_at is owed in full and adding a
package is never a cutover; package_ids exceptions compose with it. explain
reports the new before_package_added reason (rule confirmation), and judges a
package_ids exception against the packages owed the period, as the reference
model does. The test kit gains buys.add_package, which serves added_at.

Reduced grants (adcp#8718, #8724). A grant reduced to allowed_tasks without
get_reporting_status was already a loss of authority at the reduction
(accounts_unauthorized, withheld periods, a re-grant when it is added back).
The engine now keeps the reduced grant's tasks, so get_reporting_status
refuses the still-visible account with SCOPE_INSUFFICIENT; only a revoked
account is unknown (ACCOUNT_NOT_FOUND).

SUBSCRIPTIONS_REQUIRE_ACCOUNT_ID_KEY. check_subscription_offerings runs the
spec's offering-checks.json (standard_dimensions, then the primary_keys and
row-schema parts of the account_id rule, every failure in offering order).
The managed service now refuses a managed-delivery offering whose
primary_keys omit account_id with this code, where beta.3 only logged it.
ReliableReportingConfigurationError gains context (offering_id, missing).

Compliance controller (adcp#8717). reset_state is a scenario and a
TestControllerStore hook; register_test_controller and the A2A server refuse
a store that implements reporting_subscription_probe without it. Probe
results are nested under simulated as SimulationSuccess requires. The
reference seller serves reset_state (unpins its clock, restores the caller's
default grant and the original timezone and discards what the probe recorded
through ReportingSubscriptionEngine.discard_sandbox_changes) and returns
SetAccountTimezoneResult from set_account_timezone (account_id,
previous_timezone, timezone, changed_at when changed, timezone_cutovers[]).

The vendored coverage vectors move to adcontextprotocol/adcp 8ee1bfa2e9:
138 cases, 18 more than before, all passing, including the new
Pacific/Auckland, Pacific/Chatham, Asia/Kolkata and America/Mexico_City zones
under the pinned tzdata 2026.6 (2026f).

BREAKING CHANGE: a managed-delivery offering (one with method) whose
reporting_profile.primary_keys omit account_id is refused with
SUBSCRIPTIONS_REQUIRE_ACCOUNT_ID_KEY instead of logged. After a reduction
that drops get_reporting_status, get_reporting_status answers
SCOPE_INSUFFICIENT instead of ACCOUNT_NOT_FOUND. A TestControllerStore that
implements reporting_subscription_probe must implement reset_state, and
probe results reach the wire under simulated. The reference controller's
set_account_timezone result replaces cutovers with timezone_cutovers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bokelley
bokelley force-pushed the claude/adopt-3-3-beta-4 branch from 50dddb7 to 2c8548a Compare November 3, 2026 20:51
…reference model does

Two disagreements with the spec's reference model, found by its differential
generator (adcontextprotocol/adcp 8ee1bfa2) and fixed the same way in the
TypeScript SDK (adcontextprotocol/adcp-client#3620):

- A package_ids exception was modelled as a resolution change: it took the
  package out of resolution and started a regime, so a window naming every
  package of a buy reset the E a later resolution inherited. It now removes a
  period only when one entry covering the period start names every package
  resolving the offering that is owed the period, and never splits a regime.
- One excluded_by_buyer entry put every version under in-place membership.
  Only the versions whose span an entry overlaps are now; another version
  keeps its own account_ids and exclude_account_ids.

The three minimized differential scenarios are vendored from the TypeScript
SDK and pass. explain writes the before_package_added facts
(packages_added_at), sentence and buy facts (packages[].added_at) exactly as
the TypeScript SDK does, and reports a period a package_ids entry removes
under that entry's reason. A test pins the billing stub at an
account-timezone cutover, which both SDKs owe per the cutover text where the
8ee1bfa2 reference model emits none (fixed in the spec for beta.5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant