Skip to content

feat(reporting): conform the subscription engine to the AdCP 3.3.0-beta.3 rules - #3560

Merged
bokelley merged 6 commits into
mainfrom
bokelley/reporting-subscription-beta3-engine
Oct 31, 2026
Merged

bokelley merged 6 commits into
mainfrom
bokelley/reporting-subscription-beta3-engine

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Summary

T1(b) of the reporting-subscriptions DX plan (#3504): the coverage function, engine, principal runtime, ledger and managed delivery conform to the AdCP 3.3.0-beta.3 rules. All 120 canonical coverage vectors pass, and the 9 expected failures from #3559 are removed.

This PR stacks on #3559 (bokelley/reporting-subscription-beta3, the signed beta.3 adoption), which stacks on #3557 and the rest of the #3504 stack. It targets main so CI runs, so the diff against main includes everything below it.

Review only eeeb370d (on top of 4e881b08).

Each beta.3 rule and where it landed

Rule Where
#8617 cancellation_effective_at ends the flight coverage.ts flightTimeline; managed toBuy reads it from get_media_buys
#8617 billing cutover stub [E, first boundary), stub: true coverage.ts main walk via incomingHandoff; rows and groupOwedSubscriptionPeriodsV1; ledger ReportingSubscriptionLedgerObligationInputV1/ReportingLedgerObligationV1 stub; handler.ts wireObligation; service/index.ts plan mapping
#8618 flight_history replaces the non-wire prior_flights coverage.ts (ReportingSubscriptionFlightHistoryEntryV1); managed toBuy
#8618 regranted_at as the wire source for re-grants engine host fact (granted_at renamed to regranted_at); grant journal observeGrants returns {authorized_at, regranted_at?} and adopts a host value inside (revocation, now]; managed projectAccounts serves it on list_accounts/sync_accounts; managed roster passes it to facts
#8618 per-package offering snapshot managed toBuy uses packages[].reporting_delivery_offering_ids from get_media_buys (falls back to all registered offerings when absent)
#8618 offering availability (available_from/withdrawn_at) already read; now the generated wire type (ReportingSubscriptionOfferingV1 = ReportingDeliveryOffering)
#8619 sticky per-package resolution selection.ts resolveReportingSubscriptionPackageV1, shared by coverage (packageResolutionAt) and the engine (resolvePackage)
#8619 admission ordered by authorized_at, then account_id, sticky engine reconcile (principal document); exceptions keep their place; deactivation releases slots; exclude_account_ids are not candidates
#8619 several pacing/analytics feeds per purpose; lower subscription_id owes principal assertNoSameLevelOverlap (billing only); engine overriddenPackages (package-level overridden with package_ids/overridden_by); coverage honours package-level exceptions
#8619 billing match: exact + dimensions validateReportingSubscription (schema-enforced too)
#8619 destination_overrides principal validation (destination exists, same pattern/transport, one entry per account/domain, templates); readback destination_override_refs (gated); reportingSubscriptionDestinationForAccount; binder input destination; @adcp/reporting-storage binder binds the override's destination_ref and template
#8619 in-place membership principal fingerprint excludes membership; membershipEdits on the version; engine membershipOf, giving excluded_by_buyer from the edit's E, closing at the re-add's E, and a closed "joined" entry; removed accounts stay listed while open; coverage reads membership from excluded_by_buyer entries when published; buyer facade auto-versioning
#8619 paging get_principal reporting_subscription_page (cursor, max_results ≤ 100, total_count); full lists kept and served 1,000 inline with totals; onReadbackOverflow deprecated (never called)
#8619 partial grants and SCOPE_INSUFFICIENT assertReportingTaskGrantedV1; SDK service sync_reporting_status, storage service sync_reporting_receipts
#8619 account_id in primary_keys service creation refuses a managed-delivery offering without it
#8620 money terms on matched/created, effective_at, not_carried_over accounts.ensure AccountReady carries resolution, effective_at and not_carried_over (money terms on account); seller responses are validated by the beta.3 schema (fixtures in #3559)
#8622 day start calendar.ts sub-daily local calendar: the first instant of each local date is a boundary
#8622 reactivation never owes twice coverage.ts buildRegimes resumes + incomingHandoff
#8622 sub-daily billing_cycle already refused

Non-wire inputs removed

  • ReportingSubscriptionBuyV1.prior_flights and ReportingSubscriptionPriorFlightV1 are replaced by the wire flight_history.
  • ReportingSubscriptionAccountFactV1.granted_at becomes the wire regranted_at.
  • No deprecated acceptance is kept: neither the plan nor the vectors require it.

Canonical vectors

  • Pinned at a6106149a2b5c929eec0caa0042adbae4ccf3a9c (tag v3.3.0-beta.3); vectors.json sha256 is 56cc381bc336b31cb0abc589d73d0f1a8ecd52f5c42532fc47fb9112c3b594a4.
  • All 120 cases pass. The harness now also compares stub on rows and obligations.

Deviations and spec ambiguities

  • Package added after confirmation. The wire carries no add time, so its resolution instant is taken as the buy's first resolution.
  • Error vocabulary gap. error-codes.json has no code for the account_id primary-key rule, so the refusal uses the generic PLATFORM_CONFIG_INVALID until the spec adds one.
  • SCOPE_INSUFFICIENT scope. It relies on the host's resolver returning the account with its authorization. A resolver that refuses the task itself still answers as unknown.
  • Codegen gap. Generated destination_overrides items are empty objects (an anyOf/not/allOf codegen gap), so the principal module uses a local structural type. Follow-up: fix codegen.
  • Python fixture divergence. grant-cases.json renames granted_at to regranted_at, and the Python copy needs the same rename.
  • Engine buyRevisions. These internal snapshots are kept for package add/remove and for a host that changes a snapshot it shouldn't.

Checks

  • typecheck, typecheck:reporting-storage, lint:boundaries, and Prettier on the changed files all pass.
  • test:node:fast: 24,410 tests, 0 failures. test:node:slow and the @adcp/reporting-storage suite also pass (177 passed, 1 skipped). PostgreSQL cases run in CI.
  • check:package-size: 26,838,560 bytes packed, 8,528 files, within the budgets set in feat: adopt signed AdCP 3.3.0-beta.3 and serve the reporting-subscription surface #3559. @adcp/reporting-storage packs to 447,061 bytes.
  • Changeset: minor for both @adcp/sdk and @adcp/reporting-storage.
  • New tests:
    • engine: in-place membership, two analytics feeds, admission order, paged readback
    • principal: membership, overrides, billing-only overlap
    • delivery: override routing, account_id primary key
    • grants: SCOPE_INSUFFICIENT
    • ledger: stub to wire
    • accounts: #8620 pass-through

🤖 Generated with Claude Code

bokelley and others added 4 commits October 31, 2026 11:03
…tion surface

Replace the 3.3.0-beta.2 pin with the signed 3.3.0-beta.3 bundle (spec commit
a6106149, sha256 ba61919c..., cosign-verified). The SDK now advertises and
negotiates only 3.3-beta.3; beta.2 peers fall back to a shared stable release.

- Regenerate types, Zod schemas, manifest constants, wire-spec fields and docs.
- Delete the hand-modelled reporting wire types (wire.ts) and every
  TODO(3.3.0-beta.2): the generated types carry standard_dimensions,
  inactive_intervals, prior_versions[].configuration, previous_timezone,
  per-offering offering_id and status inactive with deactivated_at.
- authorization.regranted_at joins the account authorization wire keys;
  toWireAccount and windDownGrant keep it.
- Serve the readback totals (prior_versions_count, accounts_with_exceptions_count,
  accounts_unauthorized_count) and cap prior_versions at 16 inline (adcp#8619),
  gated by the served bundle like the other settled fields. The gate follows
  local $refs and treats an absent served release as the pin, as Python does.
- Re-pin the canonical coverage vectors to v3.3.0-beta.3; the nine cases the
  engine does not satisfy yet are named expected failures.
- Codegen: fold composed members of a referenced proposal arm so the submitted
  arm keeps status submitted and task_id, and restore envelope-inherited Zod
  constraints through core $refs.
- Commercial-terms semantics: beta.3 carries the beta.2 contracts.
- creative.staged_update_changed joins ACCOUNT_NOTIFICATION_TYPES.
- Tests follow the beta.3 contracts (status on every 3.3 response, account
  request money terms, seeded canonical-format products) and read ADCP_VERSION.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tures for beta.3

A terminal task webhook's result is the task's full response, so on 3.1+ it
carries the task status like a synchronous one; a media-buy lifecycle value in
status moves to media_buy_status as on the synchronous path. 3.2
list_products/decline_proposals and 3.0 results are left as returned.

Give the multi-tenant example's governance responses their now-required status,
and make the async discovery and webhook fixtures schema-complete.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ta.3 rules

The coverage function, engine, principal runtime, ledger and managed delivery
follow beta.3; all 120 canonical coverage vectors pass and the expected
failures are removed.

- adcp#8617: cancellation_effective_at ends the flight; a billing cutover owes
  the [E, first boundary) stub, carried as stub: true to the wire obligation.
- adcp#8618: flight_history replaces the non-wire prior_flights; regranted_at
  replaces the host fact's granted_at, and the grant journal serves it on
  list_accounts and sync_accounts; the managed path reads each package's
  offering snapshot and cancellation_effective_at from get_media_buys.
- adcp#8619: sticky per-package resolution (resolveReportingSubscriptionPackageV1);
  admission ordered by authorized_at then account_id; several pacing/analytics
  feeds per purpose with package-level overridden exceptions; billing must be
  match exact with dimensions; destination_overrides validated, read back and
  routed by the managed binder; in-place membership edits recorded as
  excluded_by_buyer intervals; get_principal reporting_subscription_page;
  partial grants refused with SCOPE_INSUFFICIENT; managed offerings must key
  rows by account_id.
- adcp#8620: accounts.ensure outcomes carry resolution, effective_at and
  not_carried_over.
- adcp#8622: the day start is always a sub-daily boundary; reactivation resumes
  no earlier than the end of the last period owed before deactivation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
aao-secretariat[bot]
aao-secretariat Bot previously approved these changes Oct 31, 2026

@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.

The reviewer verified commit eeeb370 (~1530 insertions across 39 files, diff reconstructed from git) and found no blocking or Medium findings that survived verification.

Checks performed:

  • coverage.ts billing stub (L344-392) traced against the main walk — no double-count; adjacent-billing-regime cutover edge (L1099 cutoverEnd) resumes at ceil(H) adjacent to, not overlapping, the prior stub.
  • engine.ts admission rewrite (L1143-1165) and overriddenPackages coherent; observeGrants regranted_at chain-continuation (L1858+) uses host value within (revocation, now].
  • Changeset correctness: minor for both packages, correct policy for @experimental reporting surfaces even with documented breaking removals (prior_flights, granted_at→regranted_at). Witness-faithful; official transport clients untouched.
  • 120 canonical vectors plus new unit tests cover the new branches.

Decision-table walk: no critical/high findings (row 1 n/a); gated_paths is false (row 2 n/a); high_risk is true but the only reason is bin/adcp.js (modified) with no medium finding on it (rows 3/5 n/a, no deletions); no medium data-loss/schema/infra findings (row 4 n/a); no prior decision (row 6 n/a); no no-auto-approve team match (row 7 n/a); zero medium findings (row 8 n/a). Falls through to row 9 — approve.

Note: review_decision is REVIEW_REQUIRED, but since gated_paths is false, no hard gate applies.

aao-secretariat[bot]
aao-secretariat Bot previously approved these changes Oct 31, 2026

@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.

Re-approval: no reviewable delta since the prior approval; re-submitting after stale-review dismissal.

@bokelley
bokelley merged commit 47e4f11 into main Oct 31, 2026
45 checks passed
@bokelley
bokelley dismissed aao-secretariat[bot]’s stale review October 31, 2026 19:33

The merge-base changed after approval.

@bokelley
bokelley deleted the bokelley/reporting-subscription-beta3-engine branch October 31, 2026 19:33
bokelley added a commit that referenced this pull request Oct 31, 2026
…it merges (#3565)

#3559/#3560 (AdCP 3.3.0-beta.3) and #3564 (creative package-fit decisions)
landed in parallel. #3564's test pinned adcpVersion 3.3.0-beta.2, which the SDK
no longer bundles, and its published guide put the package one file over
#3559's exact file-count budget. Read ADCP_VERSION in the test, count the guide,
and raise the packed cap to cover the combined size plus the release PR's
CHANGELOG growth.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request Nov 1, 2026
…adroom (#3568)

* fix(packaging): stop shipping duplicate compliance and declaration bytes

Trim @adcp/sdk from 26,862,556 to 24,993,401 packed bytes (8,529 to
8,176 files) without changing runtime behavior:

- Exclude compliance/cache/*/domains/ from the package. Every bundle's
  domains/ is a byte-identical copy of protocols/, the only directory the
  storyboard loader reads (-286,556 bytes, -353 files).
- Emit one-line ESM facades for the type-only generated declarations
  (types/core.generated, types/tools.generated, registry/types.generated),
  as types/schemas.generated already did, instead of copying 5.7 MB of
  declarations (-1,174,602 bytes).
- Store the self-contained MCP tools/list request projections as compact
  JSON. They are only ever JSON.parse'd (-407,997 bytes).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* ci(packaging): size budgets with headroom and a per-PR growth check

The packed-byte and file-count budgets were pinned to the measurement, so
two PRs that each added a file or a few KB passed alone and broke main
together (#3510 + #3541, #3564 + #3559/#3560). Replace the pins with:

- Ceilings with headroom in scripts/package-size-budget.json: @adcp/sdk
  26,000,000 packed bytes / 135,000,000 unpacked / 8,450 files against a
  measured 24,993,401 / 129,047,083 / 8,176 (about 4%), and
  @adcp/reporting-storage 490,000 / 2,250,000 / 150. A warning fires when
  less than two PRs' allowance of headroom is left.
- A per-PR growth check. main caches each commit's measurement; a pull
  request compares itself with the record for its exact merge base and may
  add 100 KB / 25 files to the SDK (15 KB / 8 files to the storage
  package). More needs the same PR to raise the ceiling, so the growth is
  reviewed in the diff. A missing record skips only this comparison.
- The focused-client eager graph count becomes a ceiling (230, measured
  216) instead of an exact pin; the named heavy-module exclusions stay.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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