Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
135 commits
Select commit Hold shift + click to select a range
3dcbf64
feat(reporting): buyer-computable coverage for reporting subscriptions
bokelley Oct 29, 2026
0f98ba1
fix(reporting): defer the subscriptions feature id until adcp.princip…
bokelley Oct 29, 2026
f8b2fdd
feat(reporting): conform subscription coverage to adcp#8585
bokelley Oct 29, 2026
a99597a
fix(reporting): settle coverage edge cases and adopt adcp#8588 calendars
bokelley Oct 29, 2026
f8b48c0
feat(reporting): the confirmation period is owed (adcp#8593 start rule)
bokelley Oct 29, 2026
b6d741d
feat(reporting): serve reporting subscriptions from the principal run…
bokelley Oct 29, 2026
08291bf
fix(reporting): fail closed on partial rosters and project 3.3 fields…
bokelley Oct 29, 2026
ed79ca3
fix(reporting): scan path_template placeholders in one linear pass
bokelley Oct 29, 2026
c3316b0
test(reporting): drop an unused constant from the subscription engine…
bokelley Oct 29, 2026
8843fab
feat(reporting): conform the subscription runtime to adcp#8585
bokelley Oct 29, 2026
4323ee4
fix(reporting): immutable subscription history, sourced change instan…
bokelley Oct 29, 2026
36e2e4c
test(reporting): the engine owes the confirmation period (adcp#8593)
bokelley Oct 29, 2026
f103466
fix(reporting): isolate a subscription whose readback exceeds 1000 ac…
bokelley Oct 29, 2026
9d917bc
feat(reporting): buyer runtime and durable storage for reporting subs…
bokelley Oct 29, 2026
2d6b4cd
test(reporting): cover multi-version readback ordering in version rec…
bokelley Oct 29, 2026
60c748a
feat(reporting): read inactive_intervals and standard_dimensions on t…
bokelley Oct 29, 2026
b35d3ff
fix(reporting): fail closed on an incomplete engine-store principal walk
bokelley Oct 29, 2026
800adfc
fix(reporting): buyer subscription runtime review fixes
bokelley Oct 29, 2026
a74b552
feat(reporting): hold subscription obligations in the reporting ledger
bokelley Oct 29, 2026
209ded8
feat(reporting): plan and execute subscription obligations in the ser…
bokelley Oct 29, 2026
5258eb5
test(reporting): keep the option-off transcript independent of the ru…
bokelley Oct 30, 2026
ff2feb6
feat(reporting): managed delivery of subscription obligations
bokelley Oct 30, 2026
b1d1c04
Merge origin/main into bokelley/reporting-subscription-ledger
bokelley Oct 30, 2026
2de8ee7
Merge bokelley/reporting-subscription-ledger (origin/main) into bokel…
bokelley Oct 30, 2026
7ecf3cd
Merge bokelley/reporting-subscription-ledger-service (origin/main) in…
bokelley Oct 30, 2026
f8fcd02
Merge remote-tracking branch 'origin/main' into bokelley/reporting-su…
bokelley Oct 30, 2026
2ad906d
chore(reporting): packed-size budget for the subscription coverage mo…
bokelley Oct 30, 2026
0af8b6f
Merge branch 'bokelley/reporting-subscription-coverage' into bokelley…
bokelley Oct 30, 2026
e37f6ab
chore(reporting): packed-size budget for the subscription runtime aft…
bokelley Oct 30, 2026
803023e
Merge branch 'bokelley/reporting-subscription-runtime' into bokelley/…
bokelley Oct 30, 2026
73120d5
fix(reporting): bound inactive intervals when omission deactivates a …
bokelley Oct 30, 2026
3005c07
Merge bokelley/reporting-subscription-runtime into bokelley/reporting…
bokelley Oct 30, 2026
3c3fb57
Merge bokelley/reporting-subscription-consumer into bokelley/reportin…
bokelley Oct 30, 2026
f65231b
Merge bokelley/reporting-subscription-ledger into bokelley/reporting-…
bokelley Oct 30, 2026
bb3c98c
Merge bokelley/reporting-subscription-ledger-service into bokelley/re…
bokelley Oct 30, 2026
331d732
feat(reporting): owner-scoped receipts and statuses, coverage that fo…
bokelley Oct 30, 2026
4084b81
feat(reporting): reporting-subscriptions storyboard and reference seller
bokelley Oct 30, 2026
a1c4fbb
feat(reporting): deactivated subscriptions read inactive in coverage …
bokelley Oct 30, 2026
6f5ae60
Merge branch 'bokelley/reporting-subscription-runtime' into bokelley/…
bokelley Oct 30, 2026
a214e0e
Merge branch 'bokelley/reporting-subscription-consumer' into bokelley…
bokelley Oct 30, 2026
2d9b28c
Merge branch 'bokelley/reporting-subscription-receipts' into bokelley…
bokelley Oct 30, 2026
936b3a3
Merge branch 'bokelley/reporting-subscription-ledger' into bokelley/r…
bokelley Oct 30, 2026
32cfbeb
Merge branch 'bokelley/reporting-subscription-ledger-service' into bo…
bokelley Oct 30, 2026
fbcbf99
Merge branch 'bokelley/reporting-subscription-delivery' into bokelley…
bokelley Oct 30, 2026
c5bc42f
fix(reporting): drop the post-loss receipt window; lock replace_accou…
bokelley Oct 30, 2026
0b83d9e
Merge branch 'bokelley/reporting-subscription-runtime' into bokelley/…
bokelley Oct 30, 2026
bcbe0a3
test(reporting): no post-loss receipt check in the PostgreSQL restart…
bokelley Oct 30, 2026
9c1ed22
docs(reporting): the engine has no post-loss receipt window
bokelley Oct 30, 2026
7dbc1bf
Merge branch 'bokelley/reporting-subscription-runtime' into bokelley/…
bokelley Oct 30, 2026
6efb751
Merge branch 'bokelley/reporting-subscription-consumer' into bokelley…
bokelley Oct 30, 2026
7f1aba2
Merge branch 'bokelley/reporting-subscription-receipts' into bokelley…
bokelley Oct 30, 2026
ae56440
Merge branch 'bokelley/reporting-subscription-ledger' into bokelley/r…
bokelley Oct 30, 2026
830ad35
Merge branch 'bokelley/reporting-subscription-ledger-service' into bo…
bokelley Oct 30, 2026
f6da30b
Merge branch 'bokelley/reporting-subscription-delivery' into bokelley…
bokelley Oct 30, 2026
18be800
Merge remote-tracking branch 'origin/main' into bokelley/reporting-su…
bokelley Oct 30, 2026
35c63ce
Merge branch 'bokelley/reporting-subscription-coverage' into bokelley…
bokelley Oct 30, 2026
1860db3
Merge branch 'bokelley/reporting-subscription-runtime' into bokelley/…
bokelley Oct 30, 2026
ce21aeb
chore(reporting): count the subscription wire-types module in the fil…
bokelley Oct 30, 2026
c55592b
Merge branch 'bokelley/reporting-subscription-coverage' into bokelley…
bokelley Oct 30, 2026
0c3de4b
Merge branch 'bokelley/reporting-subscription-runtime' into bokelley/…
bokelley Oct 30, 2026
5c57aa2
Merge branch 'bokelley/reporting-subscription-consumer' into bokelley…
bokelley Oct 30, 2026
9a59380
Merge branch 'bokelley/reporting-subscription-receipts' into bokelley…
bokelley Oct 30, 2026
e65ab09
Merge branch 'bokelley/reporting-subscription-ledger' into bokelley/r…
bokelley Oct 30, 2026
5428368
Merge branch 'bokelley/reporting-subscription-ledger-service' into bo…
bokelley Oct 30, 2026
221ba7d
Merge branch 'bokelley/reporting-subscription-delivery' into bokelley…
bokelley Oct 30, 2026
ffe7351
chore(reporting): count the example storyboard and reference seller i…
bokelley Oct 30, 2026
d942a1f
feat(reporting): move reporting-subscription building blocks to repor…
bokelley Oct 30, 2026
ed8ed37
feat(reporting): wind down with windDownGrant instead of revoking
bokelley Oct 30, 2026
adbbad4
Merge remote-tracking branch 'origin/main' into bokelley/reporting-su…
bokelley Oct 30, 2026
edff7a7
feat(reporting): typed reporting-subscription errors with code, recov…
bokelley Oct 30, 2026
c9b3318
fix(reporting): align ReportingSubscriptionError with the canonical e…
bokelley Oct 30, 2026
88977b2
chore(reporting): raise the packed-byte budget for the reporting-subs…
bokelley Oct 30, 2026
ba96a6f
Merge remote-tracking branch 'origin/main' into bokelley/reporting-su…
bokelley Oct 30, 2026
68e1563
test(reporting): validate the parity table against the pinned AdCP bu…
bokelley Oct 30, 2026
698c218
Merge branch 'bokelley/reporting-subscription-coverage' into bokelley…
bokelley Oct 30, 2026
be83582
test(reporting): serve the pinned AdCP release (3.3.0-beta.2) in subs…
bokelley Oct 30, 2026
0037ffb
Merge branch 'bokelley/reporting-subscription-runtime' into bokelley/…
bokelley Oct 30, 2026
e56c2a8
chore(reporting): packed-size budget for the buyer runtime after main…
bokelley Oct 30, 2026
dbd4993
Merge branch 'bokelley/reporting-subscription-consumer' into bokelley…
bokelley Oct 30, 2026
fb7f953
Merge branch 'bokelley/reporting-subscription-ledger' into bokelley/r…
bokelley Oct 30, 2026
ad1281a
Merge branch 'bokelley/reporting-subscription-ledger-service' into bo…
bokelley Oct 30, 2026
2635bd1
Merge branch 'bokelley/reporting-subscription-delivery' into bokelley…
bokelley Oct 30, 2026
79d5113
Merge branch 'bokelley/reporting-subscription-receipts' into bokelley…
bokelley Oct 30, 2026
fb2b279
test(reporting): the option-off golden transcript serves main's 3.3.0…
bokelley Oct 30, 2026
6e11b9c
Merge branch 'bokelley/reporting-subscription-ledger-service' into bo…
bokelley Oct 30, 2026
f520d3d
Merge branch 'bokelley/reporting-subscription-delivery' into bokelley…
bokelley Oct 30, 2026
4ad844c
Merge branch 'bokelley/reporting-subscription-receipts' into bokelley…
bokelley Oct 30, 2026
395f113
Merge remote-tracking branch 'origin/bokelley/reporting-subscription-…
bokelley Oct 30, 2026
2330106
Merge branch 'bokelley/reporting-subscription-dx-advanced' into bokel…
bokelley Oct 30, 2026
9ae1aaf
Merge branch 'bokelley/reporting-subscription-dx-revocation' into bok…
bokelley Oct 30, 2026
864a706
Merge remote-tracking branch 'origin/main' into bokelley/reporting-su…
bokelley Oct 30, 2026
91712f9
style(reporting): format the coverage parity test
bokelley Oct 30, 2026
df782e5
Merge branch 'bokelley/reporting-subscription-dx-advanced' into bokel…
bokelley Oct 30, 2026
27bf1e3
Merge branch 'bokelley/reporting-subscription-dx-revocation' into bok…
bokelley Oct 30, 2026
4fbfdac
style(reporting): format the parity-table schema check
bokelley Oct 30, 2026
315b82f
Merge branch 'bokelley/reporting-subscription-coverage' into bokelley…
bokelley Oct 30, 2026
a2f27cc
Merge branch 'bokelley/reporting-subscription-runtime' into bokelley/…
bokelley Oct 30, 2026
64b7a10
Merge branch 'bokelley/reporting-subscription-consumer' into bokelley…
bokelley Oct 30, 2026
373687f
Merge branch 'bokelley/reporting-subscription-ledger' into bokelley/r…
bokelley Oct 30, 2026
d6b83c9
Merge branch 'bokelley/reporting-subscription-ledger-service' into bo…
bokelley Oct 30, 2026
817e77e
Merge branch 'bokelley/reporting-subscription-delivery' into bokelley…
bokelley Oct 30, 2026
e0eb8c8
Merge branch 'bokelley/reporting-subscription-receipts' into bokelley…
bokelley Oct 30, 2026
0ca664b
Merge branch 'bokelley/reporting-subscription-conformance' into bokel…
bokelley Oct 30, 2026
1d6cbbc
Merge branch 'bokelley/reporting-subscription-dx-advanced' into bokel…
bokelley Oct 30, 2026
5ab5d7d
Merge branch 'bokelley/reporting-subscription-dx-revocation' into bok…
bokelley Oct 30, 2026
545db54
Merge remote-tracking branch 'origin/main' into bokelley/reporting-su…
bokelley Oct 30, 2026
7ea6162
Merge branch 'bokelley/reporting-subscription-dx-advanced' into bokel…
bokelley Oct 30, 2026
44420f2
Merge branch 'bokelley/reporting-subscription-dx-revocation' into bok…
bokelley Oct 30, 2026
f4b3020
chore(reporting): raise the packed-byte budget at the bottom of the D…
bokelley Oct 30, 2026
4d1e45a
Merge branch 'bokelley/reporting-subscription-dx-advanced' into bokel…
bokelley Oct 30, 2026
b927035
Merge branch 'bokelley/reporting-subscription-dx-revocation' into bok…
bokelley Oct 30, 2026
62473e9
Merge remote-tracking branch 'origin/main' into bokelley/reporting-su…
bokelley Oct 30, 2026
4b7e38f
Merge branch 'bokelley/reporting-subscription-coverage' into bokelley…
bokelley Oct 30, 2026
6c32dfd
Merge branch 'bokelley/reporting-subscription-runtime' into bokelley/…
bokelley Oct 30, 2026
a4484d6
Merge branch 'bokelley/reporting-subscription-consumer' into bokelley…
bokelley Oct 30, 2026
e77024d
Merge branch 'bokelley/reporting-subscription-ledger' into bokelley/r…
bokelley Oct 30, 2026
72fdbdd
Merge branch 'bokelley/reporting-subscription-ledger-service' into bo…
bokelley Oct 30, 2026
bf14d28
Merge branch 'bokelley/reporting-subscription-delivery' into bokelley…
bokelley Oct 30, 2026
62be3da
Merge branch 'bokelley/reporting-subscription-receipts' into bokelley…
bokelley Oct 30, 2026
f44c32c
Merge branch 'bokelley/reporting-subscription-conformance' into bokel…
bokelley Oct 30, 2026
53e76ef
Merge branch 'bokelley/reporting-subscription-dx-advanced' into bokel…
bokelley Oct 30, 2026
35e1168
Merge branch 'bokelley/reporting-subscription-dx-revocation' into bok…
bokelley Oct 30, 2026
3f5bf10
fix(reporting): refuse a sub-daily billing_cycle calendar
bokelley Oct 30, 2026
0dfb8dd
Merge branch 'bokelley/reporting-subscription-coverage' into bokelley…
bokelley Oct 30, 2026
64770b7
fix(reporting): only an explicit false clears a pending replace_accou…
bokelley Oct 30, 2026
a12175f
fix(reporting): refresh a changed pending action and report the lates…
bokelley Oct 30, 2026
33fc09d
Merge branch 'bokelley/reporting-subscription-runtime' into bokelley/…
bokelley Oct 30, 2026
b0ae1a0
Merge branch 'bokelley/reporting-subscription-consumer' into bokelley…
bokelley Oct 30, 2026
a582ff0
Merge branch 'bokelley/reporting-subscription-ledger' into bokelley/r…
bokelley Oct 30, 2026
3146133
Merge branch 'bokelley/reporting-subscription-ledger-service' into bo…
bokelley Oct 30, 2026
ce3bef7
Merge branch 'bokelley/reporting-subscription-delivery' into bokelley…
bokelley Oct 30, 2026
f52370d
Merge branch 'bokelley/reporting-subscription-receipts' into bokelley…
bokelley Oct 30, 2026
5155db8
Merge branch 'bokelley/reporting-subscription-conformance' into bokel…
bokelley Oct 30, 2026
9c3d12f
Merge branch 'bokelley/reporting-subscription-dx-advanced' into bokel…
bokelley Oct 30, 2026
0f76b46
Merge branch 'bokelley/reporting-subscription-dx-revocation' into bok…
bokelley Oct 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .changeset/reporting-subscription-consumer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@adcp/sdk': minor
---

Buyer runtime and durable storage for AdCP 3.3 principal-level reporting subscriptions (experimental `media_buy.reporting_subscriptions`, #3504).

- `syncReportingSubscriptionsV1(client, subscriptions, options)` writes the complete `reporting_subscriptions` set through the guarded `sync_principal` lifecycle.
- It refuses, before writing, to deactivate an active subscription by omission unless `allowDeactivation: true`. Sending `active: false` is always allowed.
- It checks that every submitted subscription reads back at its version and activation state, and that every submitted configuration field is stored unchanged.
- `reportingSubscriptionVersionRecordsV1({ state, configurations? })` rebuilds the coverage function's version records from the readback (`accepted_at`, `prior_versions`, `deactivated_at`). Each version's configuration comes from the buyer's own records, or else from the readback: the current `configuration`, and `prior_versions[].configuration` from sellers on 3.3.0-beta.2 (adcp#8588). Pass the readback's `inactive_intervals` to `expectedSubscriptionPeriods` unchanged.
- `subscriptionExpectedReportingPeriodsV1(obligations, template)` turns owed obligations into the consumer runtime's `ExpectedReportingPeriod`s. The template must supply `coverage`; no default could match an obligation that reports packages or partial coverage.
- The reliable reporting consumer runtime accepts a per-account `subscriptionExpectations` hook. It reads the summary view's `subscription_coverage[]` before each reconciliation and reconciles the returned periods alongside configuration-derived ones.
- The hook's `now` is the summary's `ledger_as_of` minus `subscriptionExpectationGraceMs`, never the wall clock.
- Use it only against a seller that materializes subscription obligations in its ledger.
- `handleAuthenticatedPrincipalChanged` calls `onPrincipalChanged` (for example to re-read `get_principal` and `accounts_unauthorized` and replace the roster) and then reconciles every account of the consumer scope.
- It drops a redelivery with an already seen `idempotency_key`.
- Notifications that arrive during a run share one trailing run.
- `createReportingSubscriptionEngineStateStoreV1({ store })` persists the engine's sharded state on any `AdcpStateStore`: one CAS document per principal and one per (principal, account), listed per tenant. With `PostgresStateStore` it is durable and needs no migration beyond `ADCP_STATE_MIGRATION`. Subscription version history stays in the principal row and is never pruned, so `prior_versions` remain answerable for retained obligations.
13 changes: 13 additions & 0 deletions .changeset/reporting-subscription-coverage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@adcp/sdk': minor
---

Add the buyer-computable coverage rule for AdCP 3.3 principal-level reporting subscriptions (experimental `media_buy.reporting_subscriptions`, #3504). `expectedSubscriptionPeriods(input)`, exported from `@adcp/sdk/reporting/consumer` and `@adcp/sdk/reporting/ledger`, is a pure function of the buyer's own records: subscription versions with their `accepted_at`, visible confirmed buys and their earlier flight values, the account's grants and timezone, the seller's published offerings (matching reads each offering's `standard_dimensions` and `unmapped_dimensions`), the readback's `inactive_intervals`, and the published `subscription_coverage` exceptions, including per-offering `gaps[]` and `timezone_cutovers[]` with `previous_timezone`. It returns every owed (buy, offering, period), and `groupExpectedSubscriptionPeriodsV1` groups them into the obligations that must carry them. It implements the start rule of adcp#8593 (the first boundary at or after the later of `accepted_at` and `authorized_at`, and every period ending after the buy's `confirmed_at`, so the period in which a buy is confirmed is owed in full), flight overlap as of each period's resolution, unclipped periods, the single cutover rule for new versions, deactivation, resolution changes and account timezone changes, revocation and re-grant with and without `backfill_on_regrant`, and exceptions that only remove periods.

Also exported: `createReportingPeriodCalendarV1`, the normative `period_generation` calendar for `utc`, `account_timezone`, `source_timezone` and `billing_cycle` schedules; `resolveReportingSubscriptionOfferingsV1` for `exact`, `finest` and `all` selector resolution with byte-order ties; `reportingSubscriptionScopeIncludesV1`; and `reportingConfigurationCapabilitiesV1`, which builds `configuration_tasks`, the deprecated `configuration_task` and the `subscriptions` block consistently.

`installConfiguration` now throws a typed `ReportingConfigurationImmutableError` (`code: 'REPORTING_CONFIGURATION_IMMUTABLE'`, with `accountId`, `deliveryConfigId` and `deliveryConfigVersion`) when an existing generation is reinstalled with different content. The message is unchanged; use `isReportingConfigurationImmutableError` instead of matching it.

The rules follow adcontextprotocol/adcp#8585. The fields it added after 3.3.0-beta.1 (`standard_dimensions`, `unmapped_dimensions`, `inactive_intervals`, `previous_timezone`, and per-offering `offering_id` on gaps and cutovers) are hand-modeled until 3.3.0-beta.2 ships. The cross-SDK parity table is `test/fixtures/reporting-subscriptions/coverage-cases.json`.

Sub-daily periods under a local alignment restart at each local midnight on wall-clock multiples (adcontextprotocol/adcp#8588). A gap yields no boundary and a fold one per offset, and the duration must divide 24 hours; mixed date and time components are refused. Calendars gain `next()`. An empty flight overlaps nothing, and a cutover carries the outgoing stretch's own start, so two cutovers in one period never owe a period twice. A billing anchor inside a DST fold is interval zero itself. A wire `authorized_at` equal to the chain start continues the chain. The walk has an iteration budget. The SDK's schema validation accepts dimension values outside the published `enums/reporting-dimension.json` vocabulary, which is extensible.
18 changes: 18 additions & 0 deletions .changeset/reporting-subscription-delivery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@adcp/sdk': minor
'@adcp/reporting-storage': minor
---

Managed delivery for AdCP 3.3 subscription obligations (experimental `media_buy.reporting_subscriptions`, #3504).

- `reportingSubscriptions.bindManagedGeneration` binds a managed subscription generation to its destination before the generation's first obligation is written. The planner calls it through `planSubscriptionObligations({ bindGeneration })`. A rejection writes nothing, and the next cycle tries again. A subscription that resolves a managed offering without a binding composition fails to plan.
- Every row of a subscription revision carries `account_id`. It is stamped before the revision is bound, and a row naming another account is refused. Account revisions are unchanged.
- `ReportingLedgerSubscriberV1.principal` scopes status subscribers: a subscription obligation's transitions reach only its owning principal.
- The engine gains `isAuthorized(principal, accountId)`, and `frozenOwedSets` versions carry the frozen `destination_method`. `withReportingSubscriptionCoverageV1` is exported for compositions.
- `@adcp/reporting-storage`:
- `createPostgresReliableReportingProductionService` delivers managed subscription obligations. Pass the principal runtime as `reportingSubscriptions.principal`; the engine must have `isAuthorized`.
- `createReportingSubscriptionManagedBindingV1` resolves the owner's current destination generation, authorizes it per account (`ensureDestinationAuthorization`), and installs a binding that freezes the version's `path_template`.
- The managed worker passes the expanded `delivery_path` to adapters. It re-checks the owner's account authority before delivering and before settling, and releases a lost claim with `holdMaterialization` without failing it. Reads are scoped with `resolveSubscriptionPrincipal`.
- `REPORTING_MANAGED_DELIVERY_MIGRATION` drops the three-column `adcp_reporting_managed_bindings` key, found by its columns, so bindings are keyed by their Core configuration. It is additive and idempotent.
- `reportingObjectWriteName` takes an optional delivery path that sits inside the generation prefix, and `isReportingObjectWriteName` recognizes it. The GCS adapter writes there, and status reads scope to the owner.
- Notification activity for a subscription obligation is fanned out only to its owning principal.
10 changes: 10 additions & 0 deletions .changeset/reporting-subscription-dx-advanced.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@adcp/sdk': minor
'@adcp/reporting-storage': minor
---

Reporting subscriptions (experimental `media_buy.reporting_subscriptions`, AdCP 3.3): the low-level building blocks move to a new `@adcp/sdk/reporting/advanced` subpath, tagged `@experimental`.

- `@adcp/sdk/reporting/advanced` exports the coverage engine (`createReportingSubscriptionEngineV1`) and its stores (`createInMemoryReportingSubscriptionEngineStoreV1`, `createReportingSubscriptionEngineStateStoreV1`), the coverage function (`expectedSubscriptionPeriods`, `groupExpectedSubscriptionPeriodsV1`, `reportingSubscriptionCoverageGrantedV1`), the period calendar, offering selection, the capability builder (`reportingConfigurationCapabilitiesV1`), the consumer bridge (`reportingSubscriptionVersionRecordsV1`, `subscriptionExpectedReportingPeriodsV1`), and the service's subscription hooks (`ReliableReportingSubscriptionsOptionsV1` and its input types, `withReportingSubscriptionCoverageV1`).
- `@adcp/sdk/reporting/service`, `/consumer` and `/ledger` no longer re-export them. Change the import path; the symbols are unchanged. The `reportingSubscriptions` option on `createReliableReportingService` is unchanged.
- `@adcp/reporting-storage` imports `withReportingSubscriptionCoverageV1` and `ReliableReportingSubscriptionBindingInputV1` from the new subpath, so it needs an `@adcp/sdk` that has it.
10 changes: 10 additions & 0 deletions .changeset/reporting-subscription-dx-errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@adcp/sdk': minor
---

Typed reporting-subscription errors (experimental `media_buy.reporting_subscriptions`, AdCP 3.3), using the shared error vocabulary (`error-codes.json` in the spec repository).

- `ReportingSubscriptionError` (package root) carries `code`, `recovery` and the vector context fields (`wouldDeactivate`, `wouldRevoke`, `deliveryConfigId`, `accountId`, `nearest`, `readback`). The five refusal codes the SDK raises before sending always carry `dispatched: false`. Its codes are `SUBSCRIPTION_WOULD_DEACTIVATE`, `DESTINATION_WOULD_REVOKE`, `SUBSCRIPTION_ID_CLASH`, `SUBSCRIPTION_OPERATOR_SCOPE_REQUIRED`, `SUBSCRIPTION_SELECTOR_UNMATCHED`, `SUBSCRIPTION_CONFLICT_EXHAUSTED` and `SUBSCRIPTION_READBACK_MISMATCH`. `syncReportingSubscriptionsV1` throws it for its deactivation guard (with `wouldDeactivate`), for conflict exhaustion and for a readback mismatch (with `readback`), where it used to throw `PrincipalLifecycleError`.
- `PrincipalLifecycleError` is now an `ADCPError`, with `code` (`PRINCIPAL_CONFLICT_EXHAUSTED`, `PRINCIPAL_TRANSPORT_FAILED`, `PRINCIPAL_SETUP_TIMEOUT`, ...), `recovery`, and `dispatched: false` when no `sync_principal` write was sent. `applied` is renamed `appliedResponse` so it can't be read as a boolean next to `dispatched`. `applied` stays as a deprecated alias, and the constructor still accepts it.
- `CoverageInputError` (`COVERAGE_INPUT_INCOMPLETE`, `COVERAGE_INPUT_INVALID`, `COVERAGE_BUDGET_EXCEEDED`, with a `detail` such as `missing_account_timezone` or `invalid_period_duration`) replaces the `TypeError` and `RangeError` the coverage function and the period calendar threw.
- The engine throws `ReportingSubscriptionEngineError` (`SUBSCRIPTION_STATE_CONFLICT`, `SUBSCRIPTION_STATE_CORRUPT`, `SUBSCRIPTION_ROSTER_INCOMPLETE`, `SUBSCRIPTION_HOST_FACT_INVALID`) at run time, and `PlatformConfigError` for configuration mistakes. `PlatformConfigError` gains `code` (`PLATFORM_CONFIG_INVALID` by default; `REPORTING_STORAGE_NOT_DURABLE` and `SUBSCRIPTIONS_REQUIRE_STANDARD_DIMENSIONS` where they apply), `configField` and `recovery: 'correctable'`.
8 changes: 8 additions & 0 deletions .changeset/reporting-subscription-dx-revocation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@adcp/sdk': minor
---

Reporting subscriptions (experimental `media_buy.reporting_subscriptions`, AdCP 3.3): wind down instead of revoking (adcp#8607).

- `windDownGrant(current?)` and `REPORTING_WIND_DOWN_TASKS` (`@adcp/sdk/server`). The grant lists only `get_reporting_status`, `sync_reporting_receipts` and `sync_reporting_status`, with `read_only: false`. Passing the current authorization keeps its `authorized_at`, because a reduced grant continues the chain. Revocation now means no access, receipts and statuses included, so set `account.authorization = windDownGrant(account.authorization)` to end a relationship while the buyer finishes reconciling. Coverage continues while the grant lists `get_reporting_status`.
- New shared vectors, `test/fixtures/reporting-subscriptions/grant-cases.json`, cover the coverage grant rule and grant transitions through the engine: wind-down, a reduced grant without `get_reporting_status` (which reads as a revocation), dropping it from a wind-down grant, and restoring it inside retention.
11 changes: 11 additions & 0 deletions .changeset/reporting-subscription-ledger-service.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@adcp/sdk': minor
---

`createReliableReportingService({ reportingSubscriptions })` now writes the subscription engine's obligations into the reporting ledger and executes them (experimental `media_buy.reporting_subscriptions`, #3504).

- Every `runCycle` plans each listed principal's frozen owed sets from `engine.frozenOwedSets` before it plans account configurations. It uses the same trusted path as `installConfiguration`: the subscription version gives identity, feed purpose and required finality, the resolved offering gives report definition and schedule, and `resolveSource`, `resolveCurrency` and `resolveCoverage` give route, currency and authorized constituents. Each obligation freezes the constituents of exactly the media buys it owes.
- New required options when subscriptions are advertised: `listSubscriptionPrincipals()` (for example `engine.listPrincipals(tenantId)` across your tenants) and `resolveSubscriptionConfiguration(...)` (requested metrics and dimensions, source settings, and the finality policy or canonicalization a subscription needs). New optional ones: `resolveSubscriptionAccount(accountId)` and `onPlanningError(error, context)`. A subscription that cannot be planned never stops another one or the account's own configurations. Without `onPlanningError`, `runCycle` rejects with an `AggregateError` after the rest of the cycle has run.
- The service scopes `get_reporting_status`, `get_media_buy_delivery` and `sync_reporting_status` with `resolvePrincipal`. A principal sees the account's configurations plus only its own subscription generations.
- The engine gains `frozenOwedSets(principal, { accountId? })` and `listPrincipals(tenantId)`.
- Without `reportingSubscriptions` the service is byte-for-byte unchanged. A golden transcript captured from the service before this change covers it.
17 changes: 17 additions & 0 deletions .changeset/reporting-subscription-ledger.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@adcp/sdk': minor
'@adcp/reporting-storage': minor
---

The reporting ledger now holds subscription obligations for AdCP 3.3 principal-level reporting subscriptions (experimental `media_buy.reporting_subscriptions`, #3504).

- `producer.planSubscriptionObligations({ account_id, principal, subscriptionId, configurations, obligations, owedThrough })` writes one subscription's frozen owed set into the ledger.
- It installs one generation per (account, owning principal, subscription_id, subscription_version, resolved offering). The generation's `delivery_config_id` is the `subscription_id` and its `delivery_config_version` is the `subscription_version`. A changed generation throws `ReportingConfigurationImmutableError`.
- Each obligation carries its own period, `media_buy_ids` frozen at its `scope_resolved_at` (the period end) and constituents. Its `periodOrdinal` is the period start in whole seconds. Periods are never grid-filled.
- It then advances the mutable `owedSet.through` horizon on every generation the subscription owns on the account.
- Subscription generations carry an immutable `subscription` block (`tenantId`, `principalId`, `subscriptionId`, `offeringId`, `ownership: 'owed_set'`) and a mutable `owedSet`. Account generations keep exactly their previous ids and fingerprints.
- Completeness of an owed-set generation is its `owedSet.through` horizon, not ordinal counting. A gap is a period that is not owed, so it never raises a continuity error. A scope whose end is past the horizon reports `scope_closed: false` and keeps `coverage_complete: true`.
- Caller isolation: `get_reporting_status`, `get_media_buy_delivery` and `sync_reporting_status` take `resolveSubscriptionPrincipal`. A caller sees account generations plus only its own subscription generations and obligations, and nothing of another principal's. Without it no subscription generation is visible. Snapshot queries carry `subscription_principal`.
- Subscription obligations emit `offering_id` to callers served AdCP 3.3. `sync_reporting_status` accepts a status without `reporting_obligation_id` against a subscription generation only when it names an existing owed obligation.
- `ReportingLedgerStore` gains optional `recordOwedSetProgress` and `findObligationByOrdinal` methods. A store must implement `recordOwedSetProgress` to hold subscription generations.
- `@adcp/reporting-storage`: `REPORTING_LEDGER_MIGRATION` adds `adcp_reporting_configurations.subscription_key` (empty for account generations). It replaces the three-column generation constraint with a unique index on `(account_id, delivery_config_id, delivery_config_version, subscription_key)`. The migration is additive and idempotent, and existing rows keep their uniqueness. `PostgresReportingLedgerStore` implements the new methods and applies `subscription_principal` to every snapshot.
14 changes: 14 additions & 0 deletions .changeset/reporting-subscription-receipts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'@adcp/sdk': minor
'@adcp/reporting-storage': minor
---

Owner-scoped receipts and statuses, and grant-aware coverage, for AdCP 3.3 reporting subscriptions (experimental `media_buy.reporting_subscriptions`, #3504).

- **Owner-only receipts and statuses.** A subscription obligation accepts `sync_reporting_receipts` and `sync_reporting_status` only from its owning principal. Account obligations stay open to every consumer of the account. `reportingSubscriptionReceiptAdmissibleV1` is the single rule.
- `createSyncReportingReceiptsHandler` takes `{ resolveSubscriptionPrincipal }` and passes an `admission` to stores that declare `receiptAdmission`.
- `PostgresReportingManagedDeliveryStore` enforces it, and a refused receipt reads exactly like an unknown reference.
- The production service wires it from `reportingSubscriptions.resolvePrincipal`.
- **No post-revocation window.** This follows the owner's decision, ahead of the 3.3.0-beta.3 spec text; beta.2 still describes the old window. Once authority for an account ends, every task treats the account as unknown, receipts and statuses included. A seller that wants a buyer to finish reconciling keeps a reduced grant instead, for example `allowed_tasks` limited to `get_reporting_status`, `sync_reporting_receipts` and `sync_reporting_status`.
- **Coverage follows the grant.** `ReportingSubscriptionAccountFactV1.allowed_tasks` carries the grant's `authorization.allowed_tasks`. The engine covers an account only while that list includes `get_reporting_status`; `reportingSubscriptionCoverageGrantedV1` holds the rule, and a grant with no task restriction counts as all tasks. Dropping `get_reporting_status` from the grant is a loss of authority for coverage at that instant, with an `accounts_unauthorized` entry. The same applies to an `authorizationChanged` push.
- **Subscriptions off.** Nothing changes.
Loading
Loading