Skip to content

feat(reporting-storage): add PostgreSQL stores for reporting subscriptions - #1724

Merged
bokelley merged 2 commits into
nextfrom
claude/issue-1695-subscription-storage
Oct 30, 2026
Merged

bokelley merged 2 commits into
nextfrom
claude/issue-1695-subscription-storage

Conversation

@bokelley

@bokelley bokelley commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Part of #1695, item 6 (storage). The change is under packages/adcp-reporting-storage/, apart from one root file: tests/fixtures/public_api_snapshot.json gains the two new storage exports. The public-API snapshot test covers adcp_reporting_storage from the root test tree, so adding a storage export can't be a storage-only commit without turning next red, and rebase-merging is ruled out by the squash-only IPR policy (docs/releasing.md). The squash commit therefore also appears in adcp's next changelog. It builds on the SDK protocols merged in #1722.

  • PgReportingSubscriptionStateStore: implements adcp.reporting.subscriptions.ReportingSubscriptionStateStore, the engine's grants, exceptions, slots and version snapshots, in adcp_reporting_subscription_state.
  • PgPrincipalRecordStore: implements adcp.server.PrincipalRecordStore, the sync_principal records including reporting_subscriptions and their versions, in adcp_principal_records via principal_record_to_document.
  • One write path: each store keeps one JSONB document per subject and replaces it with a single conditional INSERT … ON CONFLICT DO NOTHING or UPDATE … WHERE revision = expected. The compare-and-swap is therefore atomic across every worker on the database.
  • Schema: create_schema() applies idempotent DDL, which reporting_subscriptions_schema_sql() also returns. Rows are never pruned here; the documents keep every subscription version for as long as obligations cite it.
  • Exports: lazy package exports and a subscriptions submodule. The pool is borrowed, never owned.
  • Compatibility: the stores need adcp 10.0.0b3 or later, and import the SDK codec lazily with a clear ImportError on older versions.

Tests: the PostgreSQL conformance test lands in a separate test: PR, so this commit stays storage-only. Against a local PostgreSQL 16 it covers schema idempotence, CAS semantics, 20 concurrent writers losing nothing, and a restarted seller (new PrincipalService and engine on the same database) serving the same readback and owing identical periods. That test plus tests/test_reporting_storage_boundary.py: 9 passed.

Refs #1695

🤖 Generated with Claude Code

…tions

AdCP 3.3 reporting subscriptions need their principal records and engine
state to survive restarts and agree across workers.

PgReportingSubscriptionStateStore implements the SDK's
ReportingSubscriptionStateStore and PgPrincipalRecordStore implements
PrincipalRecordStore. Each keeps one JSONB document per subject in
adcp_reporting_subscription_state or adcp_principal_records and replaces it
with one conditional statement on its revision, so the compare-and-swap is
atomic across workers. create_schema applies the idempotent DDL, which
reporting_subscriptions_schema_sql also returns. Rows are never pruned; the
documents keep every subscription version for as long as obligations cite
it. The stores need adcp 10.0.0b3 or later.

Refs #1695

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

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ladon verdict: Approve

Approve — clean, well-scoped storage-only PR.

This PR adds two Postgres CAS stores to the adcp-reporting-storage package (5 files, +218/-0). No findings were raised.

Checks performed:

  • No author no-auto-approve team match.
  • gated_paths: false — no hard path gate.
  • high_risk: false — no sensitive-path modifications/deletions.
  • Reviewer verified both compare_and_swap paths against the SDK protocols: state-store insert hardcodes revision=1, update does revision+1 WHERE revision=%s; principal-store writes record.store_revision satisfying CHECK >= 1; rowcount==1 → True correct on both ON CONFLICT DO NOTHING and WHERE-revision paths.
  • Boundary stays on public adcp names and is validated by the in-PR boundary test; compat uses lazy imports with a clear ImportError on <10.0.0b3.

No Critical/High/Medium findings. The only non-blocking note is a deferred live-Postgres conformance test, which follows package convention (cannot run in unit CI; import/boundary coverage lands here) and is not a blocking gap.

Decision table: rows 1–8 do not fire (no critical/high/medium findings, no gates, no team match) → row 9 approve.

…I snapshot

Refs #1695

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

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ladon verdict: Approve

Approve — subsequent pass, clean diff.

This PR adds reporting-storage subscription support (new package files) plus a public API snapshot update. The delta since the prior approve is confined to tests/fixtures/public_api_snapshot.json: two additive entries (PgPrincipalRecordStore, PgReportingSubscriptionStateStore) both resolving to adcp_reporting_storage.subscriptions.pg. Both symbols are defined and exported there, so the snapshot update is consistent and additive. The change is correctly carried under feat(reporting-storage): with no breaking semver signal owed (additive public surface only).

No critical/high/medium findings in this run; no prior blockers to re-surface. high_risk is false, gated_paths is false, review_decision is APPROVED, and no no-auto-approve team match. None of decision rows 1–8 fire, so this falls through to row 9.

@bokelley
bokelley merged commit 46a1e01 into next Oct 30, 2026
41 checks passed
@bokelley
bokelley deleted the claude/issue-1695-subscription-storage branch October 30, 2026 04:34
bokelley added a commit that referenced this pull request Oct 30, 2026
ReliableReportingService(storage=...) takes one storage object (DX plan slice P7). memory_storage() and postgres_storage(pool= | dsn=) from adcp.storage each hold the reporting ledger, staged source objects and seals, sync_principal principal records and the reporting-subscription engine state; the managed subscription path uses its principal and state stores. Durability is a property of the storage, never a flag. postgres_storage ships in adcp-reporting-storage and folds in #1724's PostgreSQL principal and engine stores, which gain list_principals() and declare is_durable.

- Reporting subscriptions on process-local storage are refused outside development with REPORTING_STORAGE_NOT_DURABLE; the message names postgres_storage and ADCP_ENV=development. pytest and ADCP_ENV=development or test allow it. Without storage=, every store the managed path uses must declare is_durable.
- The PostgreSQL schema is managed by numbered migration files in adcp_reporting_storage.migrations, recorded in adcp_schema_migrations. Each file names its version, may include the package's idempotent DDL files, so every statement lives in one place, and may declare a stopped-worker upgrade. Each migration commits on its own under the reporting schema lock.
- python -m adcp migrate [--dsn URL] [--status | --dry-run] applies them; --status flags stopped-worker upgrades.
- Durable storage checks the schema at startup instead of changing it, and refuses one that is behind with REPORTING_SCHEMA_BEHIND, naming the command and the pending versions.
- 0002 reporting_subscription_generations, #1729's widened generation key, carries the stopped-worker note in its header, in --status and in the startup refusal.

Refs #1695

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request Oct 30, 2026
ReliableReportingService(storage=...) takes one storage object (DX plan slice P7). memory_storage() and postgres_storage(pool= | dsn=) from adcp.storage each hold the reporting ledger, staged source objects and seals, sync_principal principal records and the reporting-subscription engine state; the managed subscription path uses its principal and state stores. Durability is a property of the storage, never a flag. postgres_storage ships in adcp-reporting-storage and folds in #1724's PostgreSQL principal and engine stores, which gain list_principals() and declare is_durable.

- Reporting subscriptions on process-local storage are refused outside development with REPORTING_STORAGE_NOT_DURABLE; the message names postgres_storage and ADCP_ENV=development. pytest and ADCP_ENV=development or test allow it. Without storage=, every store the managed path uses must declare is_durable.
- The PostgreSQL schema is managed by numbered migration files in adcp_reporting_storage.migrations, recorded in adcp_schema_migrations. Each file names its version, may include the package's idempotent DDL files, so every statement lives in one place, and may declare a stopped-worker upgrade. Each migration commits on its own under the reporting schema lock.
- python -m adcp migrate [--dsn URL] [--status | --dry-run] applies them; --status flags stopped-worker upgrades.
- Durable storage checks the schema at startup instead of changing it, and refuses one that is behind with REPORTING_SCHEMA_BEHIND, naming the command and the pending versions.
- 0002 reporting_subscription_generations, #1729's widened generation key, carries the stopped-worker note in its header, in --status and in the startup refusal.

Refs #1695

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request Oct 30, 2026
ReliableReportingService(storage=...) takes one storage object (DX plan slice P7). memory_storage() and postgres_storage(pool= | dsn=) from adcp.storage each hold the reporting ledger, staged source objects and seals, sync_principal principal records and the reporting-subscription engine state; the managed subscription path uses its principal and state stores. Durability is a property of the storage, never a flag. postgres_storage ships in adcp-reporting-storage and folds in #1724's PostgreSQL principal and engine stores, which gain list_principals() and declare is_durable.

- Reporting subscriptions on process-local storage are refused outside development with REPORTING_STORAGE_NOT_DURABLE; the message names postgres_storage and ADCP_ENV=development. pytest and ADCP_ENV=development or test allow it. Without storage=, every store the managed path uses must declare is_durable.
- The PostgreSQL schema is managed by numbered migration files in adcp_reporting_storage.migrations, recorded in adcp_schema_migrations. Each file names its version, may include the package's idempotent DDL files, so every statement lives in one place, and may declare a stopped-worker upgrade. Each migration commits on its own under the reporting schema lock.
- python -m adcp migrate [--dsn URL] [--status | --dry-run] applies them; --status flags stopped-worker upgrades.
- Durable storage checks the schema at startup instead of changing it, and refuses one that is behind with REPORTING_SCHEMA_BEHIND, naming the command and the pending versions.
- 0002 reporting_subscription_generations, #1729's widened generation key, carries the stopped-worker note in its header, in --status and in the startup refusal.

Refs #1695

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request Oct 30, 2026
ReliableReportingService(storage=...) takes one storage object (DX plan slice P7). memory_storage() and postgres_storage(pool= | dsn=) from adcp.storage each hold the reporting ledger, staged source objects and seals, sync_principal principal records and the reporting-subscription engine state; the managed subscription path uses its principal and state stores. Durability is a property of the storage, never a flag. postgres_storage ships in adcp-reporting-storage and folds in #1724's PostgreSQL principal and engine stores, which gain list_principals() and declare is_durable.

- Reporting subscriptions on process-local storage are refused outside development with REPORTING_STORAGE_NOT_DURABLE; the message names postgres_storage and ADCP_ENV=development. pytest and ADCP_ENV=development or test allow it. Without storage=, every store the managed path uses must declare is_durable.
- The PostgreSQL schema is managed by numbered migration files in adcp_reporting_storage.migrations, recorded in adcp_schema_migrations. Each file names its version, may include the package's idempotent DDL files, so every statement lives in one place, and may declare a stopped-worker upgrade. Each migration commits on its own under the reporting schema lock.
- python -m adcp migrate [--dsn URL] [--status | --dry-run] applies them; --status flags stopped-worker upgrades.
- Durable storage checks the schema at startup instead of changing it, and refuses one that is behind with REPORTING_SCHEMA_BEHIND, naming the command and the pending versions.
- 0002 reporting_subscription_generations, #1729's widened generation key, carries the stopped-worker note in its header, in --status and in the startup refusal.

Refs #1695

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request Oct 30, 2026
ReliableReportingService(storage=...) takes one storage object (DX plan slice P7). memory_storage() and postgres_storage(pool= | dsn=) from adcp.storage each hold the reporting ledger, staged source objects and seals, sync_principal principal records and the reporting-subscription engine state; the managed subscription path uses its principal and state stores. Durability is a property of the storage, never a flag. postgres_storage ships in adcp-reporting-storage and folds in #1724's PostgreSQL principal and engine stores, which gain list_principals() and declare is_durable.

- Reporting subscriptions on process-local storage are refused outside development with REPORTING_STORAGE_NOT_DURABLE; the message names postgres_storage and ADCP_ENV=development. pytest and ADCP_ENV=development or test allow it. Without storage=, every store the managed path uses must declare is_durable.
- The PostgreSQL schema is managed by numbered migration files in adcp_reporting_storage.migrations, recorded in adcp_schema_migrations. Each file names its version, may include the package's idempotent DDL files, so every statement lives in one place, and may declare a stopped-worker upgrade. Each migration commits on its own under the reporting schema lock.
- python -m adcp migrate [--dsn URL] [--status | --dry-run] applies them; --status flags stopped-worker upgrades.
- Durable storage checks the schema at startup instead of changing it, and refuses one that is behind with REPORTING_SCHEMA_BEHIND, naming the command and the pending versions.
- 0002 reporting_subscription_generations, #1729's widened generation key, carries the stopped-worker note in its header, in --status and in the startup refusal.

Refs #1695

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