Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 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
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
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
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
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
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
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
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
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
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
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
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
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.
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.
55 changes: 55 additions & 0 deletions .changeset/reporting-subscription-runtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
'@adcp/sdk': minor
---

Serve AdCP 3.3 principal-level reporting subscriptions (experimental `media_buy.reporting_subscriptions`, #3504), opt-in. Without the new options every response is byte-for-byte unchanged.

**Principal runtime.** `createPrincipalLifecycle({ reportingSubscriptions: { listDeliveryConfigIds, replaceAccountConfigs?, maxSubscriptions?, accountConfigurations? } })` accepts the `sync_principal` `reporting_subscriptions` section. It validates the cross-field rules of `core/reporting-subscription.json`:

- scope shape and `exclude_account_ids`
- single-account `period_timezone`
- billing requiring official finality and `consumer_receipt`, and `consumer_receipt` requiring a destination
- a destination that exists after the same request, with `path_template` placeholders and `{account_id}` rules for multi-account and shared destinations
- duplicate ids, the `delivery_config_id` namespace clash, and same-level overlap

Versioning follows the spec: a version changes only for semantic changes; each version records `accepted_at`, and earlier versions stay readable in `prior_versions`; `active` and `replace_account_configs` change in place. Omission or `active: false` deactivates as a cutover, and a reactivation is recorded. `replace_account_configs` is applied once per version through the host hook. `dry_run` and `expected_configuration_version` behave as for the other sections. The readback is `core/reporting-subscription-state.json`, including `action_required` when the destination needs action. `principal.changed` gains reason `reporting_subscription_changed`. The runtime exposes `capabilities` (the `adcp.principal` block), `listReportingSubscriptions`, `reportingSubscriptionIds` (for the reverse namespace check in `sync_accounts`) and `recordReportingSubscriptionCoverage`.

**Coverage engine.** `createReportingSubscriptionEngineV1` (from `@adcp/sdk/reporting/service`) takes host hooks `listAuthorizedAccounts`, `listAccountBuys`, `resolveRevocationInstant`, `isPeriodDelivered` and, optionally, `listAccountDeliveryConfigs`, over a store sharded into one CAS document per principal and per account. From them it maintains:

- grants and revocations
- `max_accounts` slots, kept in a deterministic order (served accounts keep their slots, then byte order)
- precedence: the caller's own account-level configuration, then its own `account_ids` subscription
- `offering_unsupported` (buy- and package-level), `excluded_by_buyer`, `capacity`, `overridden` and `action_required` exceptions opened and closed at cutover instants
- timezone cutovers, and re-grant gaps unless `backfill_on_regrant`

It publishes `accounts_with_exceptions` and `accounts_unauthorized`, with `last_owed_period_end` and `withheld_period_count`. It serves `subscription_coverage[]`, `owedPeriods`/`owedObligations` (computed with the same `expectedSubscriptionPeriods` rule buyers run), and the receipt roster. There is no post-loss receipt window: once authority is lost the account is unknown to every task (adcontextprotocol/adcp#8607).

**Service and capabilities.** `createReliableReportingService({ reportingSubscriptions: { engine, resolvePrincipal } })` advertises `configuration_tasks` and the `subscriptions` block and adds `subscription_coverage[]` to summary and periods views. `createAdcpServerFromPlatform` passes `capabilities.principal` through as `adcp.principal`, adds `protocol.principal` and `media_buy.reporting_subscriptions` to `experimental_features`, and refuses to construct when subscriptions are advertised without the `reporting_subscriptions` section. Callers pinned below 3.3 see neither the 3.3 registration fields nor that section. `createAdcpServer` accepts `capabilities.principal`.

Conforms to adcontextprotocol/adcp#8585:

- The readback lists `inactive_intervals`.
- The engine publishes per-offering `gaps[]` and `timezone_cutovers[]`, with `previous_timezone`, when offerings' boundaries differ.
- A host's `authorized_at` is the start of the authorization chain, so a re-grant inside retention is stamped when authority is observed again.
- `createReliableReportingService({ reportingSubscriptions })` requires `standard_dimensions` on every offering.

Elapsed history is immutable:

- Owed periods are frozen as they elapse and never recomputed. Pruning retained exceptions and obligations past `status_retention_days` never re-owes or drops an elapsed period.
- A product or package edit, or a withdrawn offering, applies from the E of its observation; periods of that buy starting earlier keep resolving from the buy as it was.
- Change instants come from source data: `granted_at` on a re-grant, `timezone_changed_at` on a zone change, `resolveRevocationInstant` for a revocation, and the overriding configuration's `active_until`. Reconcile time is used only where the change itself is first observed.

Also in this release:

- The runtime requires `listDeliveryConfigIds`, the namespace clash check. Pass `accountConfigurations: false` to opt out.
- It applies `replace_account_configs` only after the section commits, and retries the hook on the next sync if it failed.
- It rejects a destination transport change without a version change.
- It rejects more than 64 inactive intervals rather than truncating.
- It rejects shared `path_template`s that can expand to the same path.
- `prior_versions[]` carries each version's `configuration` (adcp#8588).
- `recordReportingSubscriptionCoverage` rejects lists over the readback's 1000-entry limit and does not bump `configuration_version`. `engine.reconcile` isolates a subscription that exceeds the limit: that subscription keeps its last published lists, the others still update, and the pass reports it in `readback_overflow` and through `onReadbackOverflow`.
- The receipt roster is scoped by tenant.

A deactivated subscription reads `status: "inactive"` with `deactivated_at` in `subscription_coverage[]`, and is listed only while it still has retained obligations for the account (adcontextprotocol/adcp#8613, decided for beta.3). Callers served a release whose schema lacks that value see the account-level status as before, without `deactivated_at`.

Responses served at 3.3.0-beta.1 omit the fields that release's closed schemas don't define (`standard_dimensions`, `unmapped_dimensions`, `inactive_intervals`, `previous_timezone`, per-offering `offering_id`). Each field is emitted only when the schema bundle the caller is served defines it, so a bundle that lacks it, or an unknown served version, never emits it.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,7 @@ jobs:
test/examples/durable-buyer-writes.test.js
test/lib/reporting-consumer-postgres.test.js
test/lib/reporting-webhook-activity-pg.test.js
test/lib/reporting-subscription-postgres.test.js

# The Reliable Reporting storage package (`@adcp/reporting-storage`) holds the PostgreSQL ledger store, row
# storage, managed delivery, notification activity and the BigQuery sink, with their tests. It has its own job
Expand Down
Loading
Loading