Skip to content

fix(reporting): refuse an adcp-reporting-storage of another storage contract at startup (#1763) - #1789

Merged
bokelley merged 1 commit into
claude/issue-1763-storage-migrationsfrom
claude/issue-1763-adcp-storage-contract
Nov 2, 2026
Merged

bokelley merged 1 commit into
claude/issue-1763-storage-migrationsfrom
claude/issue-1763-adcp-storage-contract

Conversation

@bokelley

@bokelley bokelley commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

Stacked on #1787 (storage side); base will be retargeted to next once #1787 merges. The diff against next is the top commit only.

Why

adcp 10.0.0b4 + adcp-reporting-storage 0.2.0b0 installs cleanly and fails every reporting turn with UndefinedColumn, logged only as Reliable Reporting worker component configuration failed (#1763, item 3(b)).

What

  • adcp.reporting.ledger.storage_contract.REPORTING_STORAGE_CONTRACT = 2 names the ledger layout this adcp reads (the _record_rows column lists). Contract history is in the module docstring (1 = SDK 9 and 10.0.0b0–b3; 2 = 10.0.0b4+).

  • Startup check in ReliableReportingService._initialize() (before any schema work, for both store= and storage= wiring) and in adcp.storage.postgres_storage() at construction. A store declares its contract with a storage_contract attribute (storage 0.2.0b1+); a store from adcp_reporting_storage without one is classified as contract 1 (0.1.0b4 / 0.2.0b0); in-memory and adopter stores are not subject to the check.

  • A mismatch raises ReliableReportingConfigurationError(code="REPORTING_SCHEMA_BEHIND") (correctable, config_field="adcp-reporting-storage"):

    adcp 10.0.0b6 reads the reporting ledger with storage contract 2, but the installed adcp-reporting-storage 0.2.0b0 implements contract 1; the pair would fail every reporting turn. Install adcp-reporting-storage>=0.2.0b1 (storage contract 2), then run "python -m adcp migrate" or start the service, whose create_schema() upgrades the ledger in place.

    The existing cross-SDK code is reused deliberately: the seller configuration vocabulary is pinned to error-codes.json, and "the storage schema is behind this SDK" is exactly the condition.

  • Fingerprint pin: tests/test_reporting_storage_contract.py hashes the shared column lists and pins the hash to the contract number, so a future _record_rows change without a contract bump (and the matching storage DDL/migration) fails the build.

  • Docs: MIGRATION_v9_to_v10.md, docs/reliable-reporting-service.md, docs/reporting-subscriptions-errors.md, docs/reporting-subscriptions-seller.md.

Tests

Classification (declared / older / newer / undeclared storage release / custom store), refusal messages, service startup refusing before create_schema() is touched, postgres_storage refusing at construction, and the real PgReportingLedgerStore agreeing with adcp. PG migration and core-recovery conformance tests pass locally with the workspace pair.

🤖 Generated with Claude Code

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

Adds a storage-contract version handshake between adcp and adcp-reporting-storage (#1763). The reviewer surfaced no medium-or-higher findings.

What was checked:

  • Fail-closed require_storage_contract runs at ReliableReportingService._initialize (service.py:213) before any create_schema/check_schema and at postgres_storage() construction (storage/init.py:231); both pass the ledger store, consistent with service.store → storage.ledger (service.py:549).
  • Six shared column lists in _record_rows exist and parse correctly for the fingerprint; _STATUS_COLUMNS covered via _STATUS_COLUMNS_BARE.
  • REPORTING_SCHEMA_BEHIND is a pre-existing valid seller-config code with correctable recovery, reused deliberately.
  • Provenance classification correct by construction; check is local-only (importlib.metadata, no I/O, no timeout needed) and fails once at startup.
  • Tests cover classification, both refusal directions, startup-before-schema, construction-time refusal, and real-storage agreement.
  • Additive public surface under a fix: commit — no semver break.

Decision table: no critical/high/medium findings; gated_paths false; high_risk false; no author team gate; no prior decision. Falls through to row 9 → approve.

…ontract at startup

adcp 10.0.0b4 added offering_id to the ledger column lists that
adcp-reporting-storage reads against its DDL; the published storage release
(0.2.0b0) lacked the column, installed cleanly beside 10.0.0b4, and failed
every reporting turn with UndefinedColumn, logged only as a configuration
failure (#1763).

adcp now names the ledger layout it reads with one integer,
adcp.reporting.ledger.storage_contract.REPORTING_STORAGE_CONTRACT (2), and
checks the installed ledger store against it at service startup
(ReliableReportingService._initialize, before any schema work) and when
postgres_storage() is built. A store declares its contract with a
storage_contract attribute (adcp-reporting-storage 0.2.0b1+, #1787); a store
from that distribution without one predates the declaration and is
contract 1; other stores are not subject to the check. A mismatch raises
ReliableReportingConfigurationError with REPORTING_SCHEMA_BEHIND, naming
both installed versions, both contracts, the release to install and the
migration to run, so the pair fails once and loudly instead of every turn.

tests/test_reporting_storage_contract.py pins a fingerprint of the shared
column lists to the contract number, so changing _record_rows without
bumping the contract (and the storage DDL with it) fails the build.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@bokelley
bokelley force-pushed the claude/issue-1763-adcp-storage-contract branch from fb53ff6 to ae145e3 Compare November 1, 2026 21:48

@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 pass, no blocking or medium findings.

This is an additive commit introducing adcp.reporting.ledger.storage_contract (REPORTING_STORAGE_CONTRACT=2) plus startup/construction consistency checks in ReliableReportingService._initialize and postgres_storage. Verified:

  • Both sites check the ledger store consistently (service.py:549 sets self.store = storage.ledger).
  • The check fails closed before any schema work.
  • REPORTING_SCHEMA_BEHIND is a valid correctable code.
  • Classification by declaration-then-provenance matches the stated contract history.
  • The fingerprint hashes six column lists that all exist in _record_rows.
  • The fix() prefix is appropriate for a purely additive guard.

No public-API surface removal/rename, no discriminated-union or forward-compat change, no generated-code hand-edits, no CI-gate disabling, no credential-in-ctx_metadata concerns. high_risk is false; gated_paths is false and review_decision is APPROVED. No no-auto-approve team match. Prior decision was also approve with no findings; current run adds nothing actionable. Falls through to row 9.

@bokelley
bokelley merged commit 4e11f3d into claude/issue-1763-storage-migrations Nov 2, 2026
4 checks passed
@bokelley
bokelley deleted the claude/issue-1763-adcp-storage-contract branch November 2, 2026 09:56
bokelley added a commit that referenced this pull request Nov 2, 2026
…ontract at startup (#1789)

adcp 10.0.0b4 added offering_id to the ledger column lists that
adcp-reporting-storage reads against its DDL; the published storage release
(0.2.0b0) lacked the column, installed cleanly beside 10.0.0b4, and failed
every reporting turn with UndefinedColumn, logged only as a configuration
failure (#1763).

adcp now names the ledger layout it reads with one integer,
adcp.reporting.ledger.storage_contract.REPORTING_STORAGE_CONTRACT (2), and
checks the installed ledger store against it at service startup
(ReliableReportingService._initialize, before any schema work) and when
postgres_storage() is built. A store declares its contract with a
storage_contract attribute (adcp-reporting-storage 0.2.0b1+, #1787); a store
from that distribution without one predates the declaration and is
contract 1; other stores are not subject to the check. A mismatch raises
ReliableReportingConfigurationError with REPORTING_SCHEMA_BEHIND, naming
both installed versions, both contracts, the release to install and the
migration to run, so the pair fails once and loudly instead of every turn.

tests/test_reporting_storage_contract.py pins a fingerprint of the shared
column lists to the contract number, so changing _record_rows without
bumping the contract (and the storage DDL with it) fails the build.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
bokelley added a commit that referenced this pull request Nov 2, 2026
…pin the adcp storage contract (#1763) (#1787)

* fix(reporting-storage): upgrade 0.1.0b4/0.2.0b0 ledgers in place and pin the adcp storage contract

adcp 10.0.0b4 added offering_id to the ledger column lists in
adcp.reporting.ledger._record_rows, which adcp-reporting-storage reads
against its own DDL. The only published storage release, 0.2.0b0, does not
create the column, declares adcp>=9.0.0,<11, and so installs cleanly beside
10.0.0b4 and fails every reporting turn with UndefinedColumn (#1763).

Storage side of the fix:

- create_schema() now applies the versioned migrations (0001-0005) that are
  not yet recorded in adcp_schema_migrations inside its own transaction,
  under the reporting schema advisory lock, before its optional DDL. A
  ledger created by 0.1.0b4 or 0.2.0b0 is upgraded in place through either
  entry point: offering_id is added with '' for every existing account-level
  generation, obligation and status, the generation key and period index
  are widened, the managed-delivery foreign keys are re-pointed, and the
  applied schema versions are recorded. Re-running is a no-op; concurrent
  boots serialize. No evidence row is rewritten or dropped.
- A storage contract number is declared on both sides
  (adcp_reporting_storage.STORAGE_CONTRACT = 2) and checked when a
  PostgreSQL ledger store is built: an adcp of another contract is refused
  at startup with REPORTING_SCHEMA_BEHIND naming both versions and the
  release to install, instead of failing on the first read. adcp releases
  without a declaration (10.0.0b4, b5) are classified by their column lists.
- The dependency floor moves to adcp>=10.0.0b4,<11, the first release of
  contract 2, so the incompatible pair no longer resolves.

tests/conformance/reporting/test_reporting_storage_published_upgrade_pg.py
seeds ledgers with the actual published wheel pairs (adcp 9.3.1 +
storage 0.1.0b4, adcp 10.0.0b2 + storage 0.2.0b0), reproduces the b4
failure on 0.2.0b0, and proves both upgrade paths keep every evidence row,
read everything back, rebind the stored generations on restart, close the
next period, record the version, re-run as a no-op, serialize concurrent
boots, and produce a catalog identical to a fresh migration.

Closes #1763 (storage side); the adcp-side startup check and the release
gate follow in their own PRs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(reporting-storage): bound create_schema to the ledger's own migrations

create_schema applies and records the migrations made of the DDL this
store owns (0001-0003) and stops there: 0004 (subscription state) and 0005
(inline storage) create reporting_* tables of other stores, and the A/B
catalog-identity suites assert that an older binary's installed object set
is exactly its own manifest. Those two stay pending for python -m adcp
migrate, which postgres_storage requires anyway.

Also: the contract module imports ReliableReportingConfigurationError
through the public adcp.reporting name, the public API snapshot records
STORAGE_CONTRACT and require_adcp_contract, and the release configuration
test pins next's storage floor to the first adcp of its storage contract.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(reporting): refuse an adcp-reporting-storage of another storage contract at startup (#1789)

adcp 10.0.0b4 added offering_id to the ledger column lists that
adcp-reporting-storage reads against its DDL; the published storage release
(0.2.0b0) lacked the column, installed cleanly beside 10.0.0b4, and failed
every reporting turn with UndefinedColumn, logged only as a configuration
failure (#1763).

adcp now names the ledger layout it reads with one integer,
adcp.reporting.ledger.storage_contract.REPORTING_STORAGE_CONTRACT (2), and
checks the installed ledger store against it at service startup
(ReliableReportingService._initialize, before any schema work) and when
postgres_storage() is built. A store declares its contract with a
storage_contract attribute (adcp-reporting-storage 0.2.0b1+, #1787); a store
from that distribution without one predates the declaration and is
contract 1; other stores are not subject to the check. A mismatch raises
ReliableReportingConfigurationError with REPORTING_SCHEMA_BEHIND, naming
both installed versions, both contracts, the release to install and the
migration to run, so the pair fails once and loudly instead of every turn.

tests/test_reporting_storage_contract.py pins a fingerprint of the shared
column lists to the contract number, so changing _record_rows without
bumping the contract (and the storage DDL with it) fails the build.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* test(reporting-storage): follow the shipped migration set and cover a notifications-enabled ledger

next added migration 0006 (status checkpoint offerings) after this branch
was cut; the published-wheel upgrade test now derives the versions it
expects from the shipped set instead of listing them, and seeds a
notifications-enabled 0.2.0b0 ledger (outbox and status stores) to prove the
in-place upgrade and migrate record every migration, 0006 included, with
the evidence unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* style(reporting-storage): wrap the create_schema migration comment

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <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