Skip to content

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

Merged
bokelley merged 4 commits into
nextfrom
claude/adopt-3-3-beta-4
Nov 5, 2026
Merged

bokelley merged 4 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.

Three commits:

  1. feat(reporting): the coverage, grant and controller rules.
  2. fix(reporting): the TS-parity harness fixes.
  3. feat(protocol)!: the signed bundle pin, from schemas/releases/3.3.0-beta.4.json. The source commit is 8ee1bfa2e9e7, taken from the Fulcio certificate, which is also the commit the vendored coverage vectors come from. Bundle sha256 3525423d25bc…, cosign-verified. The schema cache, packaged compliance tree, request-signing vectors, generated types and public API snapshot are all regenerated.

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 (regenerated):
    • get_media_buys gains Package.added_at, and the controller response gains SetAccountTimezoneResult.
    • Mixed date and time durations under a local alignment are now wire-invalid, so the local-duration-cannot-mix-date-and-time vector checks that.
    • DestinationOverride is still one model with all four fields (account_ids, operator_domains, destination_id, path_template).
    • Numbered names renumber (Status54 → Status53, ...).
    • create_media_buy drops Opportunity / Status (adcp#8709).
    • The schema-parity test synthesizer now merges a composition branch's own property schemas with the root's.
  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.

Breaking

  • The SDK speaks 3.3-beta.4 and no longer accepts a 3.3-beta.3 pin.
  • A managed-delivery offering without account_id in primary_keys is refused.
  • SCOPE_INSUFFICIENT replaces ACCOUNT_NOT_FOUND after a reduction that drops get_reporting_status.
  • A TestControllerStore with the probe must implement reset_state.
  • Probe results are nested under simulated.

These are listed in the MIGRATION_v9_to_v10.md and docs/adcp-3.3-sdk-inventory.md section 14.

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

@bokelley
bokelley force-pushed the claude/adopt-3-3-beta-4 branch from 50dddb7 to 2c8548a Compare November 3, 2026 20:51
bokelley and others added 3 commits November 5, 2026 09:45
…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>
…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>
Pin src/adcp/ADCP_VERSION to 3.3.0-beta.4 from the audited, cosign-verified
release: schemas/releases/3.3.0-beta.4.json, source commit 8ee1bfa2e9e7 from
the Fulcio certificate (the commit the vendored coverage vectors already come
from), bundle sha256 3525423d25bc. Resync the schema cache, packaged
compliance inputs and request-signing vectors with --require-signed-release,
then regenerate the types and the public API snapshot. The wheel ships the
3.3.0-beta.4 schemas and compliance tree; the beta.3 trees stay in the
repository as history and leave distributions.

Generated: get_media_buys Package.added_at, SetAccountTimezoneResult on the
controller response, the budget_too_low details and creative
supports_provenance; create_media_buy drops the Opportunity / Status
branches (adcp#8709). DestinationOverride stays one model with account_ids,
operator_domains, destination_id and path_template under the item's new
if/then/else. Schedules under a local alignment now reject durations mixing
date and time, so the vectors' local-duration-cannot-mix-date-and-time case
is wire-invalid. The reference seller advertises 3.3-beta.4. The schema-parity
synthesizer reads a composition branch with the root's property schemas even
when the branch declares its own (create_media_buy's branches now constrain
opportunity).

BREAKING CHANGE: the SDK no longer advertises or accepts AdCP 3.3-beta.3.
get_supported_adcp_versions is 3.0, 3.1, 3.2, 3.3-beta.4 and 3.2-rc.7; an
adcp_version pin of 3.3-beta.3 or 3.3.0-beta.3 raises ConfigurationError, and
a peer that advertises only 3.3-beta.3 for the 3.3 line negotiates down to
3.2. Re-pin to 3.3-beta.4 or omit the pin. Numbered generated names renumber
(Status54 to Status53, Status97 to Status96, Status77 to Status76).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bokelley
bokelley force-pushed the claude/adopt-3-3-beta-4 branch from dfffa68 to 594a3d5 Compare November 5, 2026 10:22
@bokelley bokelley changed the title feat(reporting): adopt AdCP 3.3.0-beta.4 coverage, grant, controller rules and bundle feat(reporting): adopt AdCP 3.3.0-beta.4 coverage, grant and controller rules and the signed bundle Nov 5, 2026
@bokelley
bokelley marked this pull request as ready for review November 5, 2026 10:23

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — AdCP 3.3.0-beta.4 adoption, clean review, no blocking findings.

The reviewer completed a thorough pass with zero critical/high/medium findings:

  • Semver signal is correct: breaking adoption ships under feat(protocol)! with a BREAKING CHANGE footer and migration notes.
  • Largest non-generated file (coverage.py, 183 changed lines) was studied: the new per-package owing filter (owing = regime.owing_packages(...), L1307-1314) is safe — the join-instant period stays owed in full and package addition is never a cutover, matching the TS SDK.
  • engine.py reduced-grant handling correctly distinguishes SCOPE_INSUFFICIENT vs ACCOUNT_NOT_FOUND; offering_checks.py and controller gating are sound.
  • A new beta4 test file (371 lines) covers the added branches.
  • No _generated-layer or domain-import violations, no forward-compat/alias/ergonomic edits, no credentials in metadata, no disabled tests.
  • The storage-migration-test deletion seen in base..head is a tooling artifact (provided base is ahead of the true merge-base); the PR does not touch that file.

high_risk is true, but all matching paths are schemas/** additions (new generated schema cache files) — new files are normal scaffolding, not an escalation trigger (no (modified)/(deleted) entries). gated_paths is false; no author team gate. Only a single non-blocking Low (orphaned SubscriptionException.removes_package), omitted from the gate.

Decision table: no critical/high (row 1 n/a), gated_paths false (row 2 n/a), no (deleted) high-risk reasons (row 3 n/a), no medium findings (rows 4/5/8 n/a), no prior escalate (row 6 n/a), no team gate (row 7 n/a) → row 9 approve.

…hemas no longer produce

test_every_accepted_class_still_occurs requires deleting an accepted
round-trip difference once it stops occurring; under the AdCP 3.3.0-beta.4
schemas no document routes into an arm whose discriminator it lacks.

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

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — test-only delta with no blocking or medium findings.

This PR's delta is a single test-only commit: it deletes the discriminator_emitted entry from the ACCEPTED set in tests/conformance/test_wire_round_trip.py, leaving it empty. Per the file's own stated contract, the set only shrinks, and a class that no longer occurs under the beta.4 schemas must be removed; two tests gate the claim in both directions, so CI validates the change. The remaining high-risk flag entries are all schemas/** cache files marked (added) — new-file scaffolding, not escalation-worthy on the flag alone.

Decision table walk-through:

  • Rows 1 (critical/high): no findings — skip.
  • Row 2 (gated_paths): gated_paths is false — skip. (review_decision is also APPROVED.)
  • Row 3 (deleted high-risk path): all high-risk reasons are (added), none (deleted) — skip.
  • Row 4 (medium data-loss/schema/infra): no findings — skip.
  • Row 5 (modified high-risk + medium): no (modified) entries and no medium findings — skip.
  • Row 6 (sticky escalate): prior decision was approve, not escalate — skip.
  • Row 7 (no-auto-approve team): no team match — skip.
  • Row 8 (≥3 medium): zero medium findings — skip.
  • Row 9: approve.

Prior pass was clean (approve, no findings); this subsequent run surfaces nothing new. Net clean.

@bokelley
bokelley merged commit a2fdc2b into next Nov 5, 2026
41 checks passed
@bokelley
bokelley deleted the claude/adopt-3-3-beta-4 branch November 5, 2026 11:35
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