Repository navigation
feat(reporting): adopt AdCP 3.3.0-beta.4 coverage, grant and controller rules and the signed bundle - #1851
Conversation
50dddb7 to
2c8548a
Compare
…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>
dfffa68 to
594a3d5
Compare
There was a problem hiding this comment.
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>
There was a problem hiding this comment.
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, notescalate— 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.
Adopts the AdCP 3.3.0-beta.4 reporting-subscription changes (adcontextprotocol/adcp#8718, #8717, #8724) on the SDK 10 /
nextline.Three commits:
feat(reporting): the coverage, grant and controller rules.fix(reporting): the TS-parity harness fixes.feat(protocol)!: the signed bundle pin, fromschemas/releases/3.3.0-beta.4.json. The source commit is8ee1bfa2e9e7, taken from the Fulcio certificate, which is also the commit the vendored coverage vectors come from. Bundle sha2563525423d25bc…, cosign-verified. The schema cache, packaged compliance tree, request-signing vectors, generated types and public API snapshot are all regenerated.Changes
added_at(rule change).SubscriptionBuyPackage.added_atis read fromget_media_buyspackages[].added_at, and the managed path keeps it, also underbuy_offerings.SubscriptionBuy.package_confirmed_at()returnsmax(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'saccepted_atand its own instant, and adding a package is never a cutover.explainhas the newbefore_package_addedreason, with the same facts (packages_added_at), sentence and buy facts as the TS SDK. The test kit gainskit.buys.add_package, which servesadded_at.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, soget_reporting_statusrefuses the still-visible account withSCOPE_INSUFFICIENT. A revoked account staysACCOUNT_NOT_FOUND.SUBSCRIPTIONS_REQUIRE_ACCOUNT_ID_KEY.check_subscription_offerings()runs the spec'soffering-checks.jsonand passes all 8 cases. The managed service now refuses a managed-delivery offering whoseprimary_keysomitaccount_id; beta.3 only logged it.ReliableReportingConfigurationError.contextcarriesoffering_idandmissing.get_media_buysgainsPackage.added_at, and the controller response gainsSetAccountTimezoneResult.local-duration-cannot-mix-date-and-timevector checks that.DestinationOverrideis still one model with all four fields (account_ids,operator_domains,destination_id,path_template).Status54→Status53, ...).create_media_buydropsOpportunity/Status(adcp#8709).tzdata==2026.6(2026f). The parity test ignores the host's zoneinfo. No runtime pin:zoneinfoprefers the system database over thetzdatapackage, so pinning would not make results deterministic. The vectors' dates also agree across tzdata 2026b through 2026f. This is documented indocs/reporting-subscriptions-coverage.md.reset_stateis a scenario and aTestControllerStorehook.register_test_controllerand the A2A server refuse a store that implementsreporting_subscription_probewithout it.simulated, asSimulationSuccessrequires.reset_state. It unpins the clock, restores the caller's default grant and the original timezone, and discards what the probe recorded viaReportingSubscriptionEngine.discard_sandbox_changes.set_account_timezonereturnsSetAccountTimezoneResult.owed_periods_match, so the new window rule has nothing to apply to.Parity fixes, matching adcontextprotocol/adcp-client#3620 (separate commit):
package_idsexception no longer changes resolution.excluded_by_buyerapplies in-place membership only to the versions it overlaps.cutovertext. The 8ee1bfa2 reference model emits none there; the spec fixes that for beta.5.Breaking
3.3-beta.4and no longer accepts a3.3-beta.3pin.account_idinprimary_keysis refused.SCOPE_INSUFFICIENTreplacesACCOUNT_NOT_FOUNDafter a reduction that dropsget_reporting_status.TestControllerStorewith the probe must implementreset_state.simulated.These are listed in the
MIGRATION_v9_to_v10.mdanddocs/adcp-3.3-sdk-inventory.mdsection 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_atis read from the host's buys on each computation and never persisted. The reduced grant's tasks use the existing optionalallowed_taskskey of the engine state document (STATE_DOCUMENT_VERSION1). No ledger column changes, so there is noREPORTING_STORAGE_CONTRACTbump and no migration.🤖 Generated with Claude Code