Repository navigation
fix(reporting-storage): upgrade 0.1.0b4/0.2.0b0 ledgers in place and pin the adcp storage contract (#1763) - #1787
Conversation
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — clean fix for #1763 with no blocking or medium findings.
What I checked:
- No findings from the reviewer (critical/high/medium all absent).
high_risk: false;gated_paths: false; no no-auto-approve team match; no prior decision.- The change moves
create_schema()to apply versioned migrations via the newapply_pendinginside its bootstrap transaction, and adds a storage-contract integer that fail-closes an incompatible adcp/storage pairing at construction rather than on first ledger read. The_applyextraction is behavior-equivalent to the old_migrateloop. - Concurrency, in-place upgrade, and evidence-preservation are covered by real-PostgreSQL conformance tests against the published wheels.
- No
adcp.*public-surface change, so SDK semver rules do not apply; the floor/contract break is documented 0.x storage-package policy (this is a separateadcp-reporting-storagepackage, not theadcpnamespace).
With no findings and no escalation trigger, rows 1–8 do not fire; the PR falls through to row 9 → approve.
…bed contracts The release configuration test expected the companion gate to run only for adcp publishes; it now runs for both packages after the build, with a PostgreSQL service. The smoke classification test stubs both declaration modules instead of assuming the workspace pair agrees, which it does not until the storage declaration (#1787) lands. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…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>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve.
Clean pass with no blocking or Medium findings. The reviewer verified the core migration-ownership logic: apply_pending(within=...) walks migrations in version order with correct break-not-skip semantics (0004 subscriptions/ stops the walk, leaving 0004/0005 for python -m adcp migrate), and the within posix paths exactly match the -- include: directives of 0001–0003. The conformance test correctly moves check_schema() after migrate() (fail-closed on behind-schema) and asserts the upgraded-vs-fresh catalog invariant. Public-surface changes are additive and snapshot-tracked: STORAGE_CONTRACT/require_adcp_contract exports, contract.py resolving to the public adcp.reporting export, and a consistent adcp>=10.0.0b4,<11 storage floor with matching test.
Checked against repo gates: no public-API break requiring a semver signal, no hand-edits to generated code, no import-layering or forward-compat regressions, no disabled tests or blanket type:ignores.
Decision table: no critical/high findings (row 1 n/a); gated_paths false and review_decision APPROVED (row 2 n/a); high_risk false (rows 3/5 n/a); no medium findings (rows 4/8 n/a); no no-auto-approve team match (row 7 n/a); prior decision was approve (row 6 n/a). Falls through to row 9 → approve.
…ks with (#1763) (#1788) * ci(release): gate publishes on a companion the built distribution works with scripts/check_companion_releases.py refused an adcp release only when no published adcp-reporting-storage *range* accepted it. adcp 10.0.0b4 changed the ledger column lists while the published storage (adcp>=9.0.0,<11) still accepted it, so the pair installed cleanly and failed every reporting turn (#1763). Range compatibility is not contract compatibility. The gate now proves the pair works. With --dist, it installs the wheel just built together with the published companion into a private environment and runs scripts/companion_smoke.py there, which compares the storage contract each side declares (adcp.reporting.ledger.storage_contract versus adcp_reporting_storage.contract; releases predating the declaration are classified by their column lists) and, with ADCP_PG_TEST_URL, creates the ledger schema and round-trips a configuration, obligation and revision through the installed stores on PostgreSQL, the calls that failed on the incompatible pair. Storage publishes are gated too, in the other direction: the built storage wheel is smoked with the oldest and the newest published adcp its own requirement accepts, so a floor that admits an adcp of another contract is refused. release-publish.yml builds first, runs the gate for both packages against a PostgreSQL service, then uploads. Verified locally against PyPI and PostgreSQL 16: the adcp wheel built from next is refused against the published 0.2.0b0 (contract 2 versus 1), the storage wheel from #1787 (floor 10.0.0b4) passes with 10.0.0b4, and the published 0.1.0b4 passes with adcp 9.0.0 and 9.3.1. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(release): assert the gate smokes both packages and classify stubbed contracts The release configuration test expected the companion gate to run only for adcp publishes; it now runs for both packages after the build, with a PostgreSQL service. The smoke classification test stubs both declaration modules instead of assuming the workspace pair agrees, which it does not until the storage declaration (#1787) lands. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(release): classify the smoke's undeclared adcp from patched column lists The test asserted this checkout's column lists carry offering_id, which is true on next and false on main; both classifications are now driven by patched lists so the test is branch-agnostic. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…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>
|
Ladon cannot review this PR until merge conflicts are resolved. |
…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>
…tions 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>
…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>
4e11f3d to
c596855
Compare
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — clean pass, no findings.
This PR pins a storage contract (version 2) between adcp and adcp-reporting-storage to close #1763 and upgrades the 0.1.0b4/0.2.0b0 ledgers in place. The reviewer confirmed:
- New public symbols (
STORAGE_CONTRACT,require_adcp_contract, thestorage_contractmodule) are additive with a matchingpublic_api_snapshot.jsonentry — no semver break. - The fail-closed contract guard is symmetric (adcp→store
require_storage_contractat service_initialize/postgres_storage; store→adcprequire_adcp_contractatPgReportingLedgerStore.__init__), skips in-memory/custom stores, and refuses both older and newer contracts (_Newer/_Oldertested). - Migration apply path (
apply_pending/_apply) holds the reporting schema lock, is re-runnable, bounded to owned migrations 0001-0003, and serializes concurrent boots (proven, count==5); test subprocesses carry explicit timeouts. - Largest-file delta (
test_reporting_storage_published_upgrade_pg.py) and_published_ledger_seed.pyboth reviewed and sound; the #1763 repro is asserted. - Test plan honest: 9 passed / 1 skipped on real PostgreSQL with actual published wheels.
Gate checks: no critical/high/medium findings (rows 1, 4–6, 8 do not fire). gated_paths false and review_decision APPROVED (row 2 n/a). high_risk false (rows 3, 5 n/a). No no-auto-approve team match (row 7 n/a). Prior decision was approve. Falls through to row 9 → approve.
… 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>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve.
The delta since the prior clean pass is docs wording and test infrastructure only. Docstrings/comments in pg.py and the migration/README docs update "0004–0005" to "0004 onward" to name the new migration 0006 (status checkpoint offerings), which lives outside this diff. Tests replace hardcoded ["0004","0005"] version lists with _shipped()/_rest() derived from the actual migrations() manifest, and add a notifications-enabled upgrade test covering the reporting_status_checkpoints/events tables. Helpers are internally consistent and order-preserving.
No product-code logic, public API, wire shape, or credential handling changed in the delta. No critical/high/medium findings surfaced, and no prior blockers to re-surface (prior decision was also approve).
Checks against decision table:
- No critical/high/medium findings (rows 1, 4, 5, 6, 8 don't fire).
gated_pathsfalse;review_decisionAPPROVED (row 2 n/a).high_riskfalse (rows 3, 5 n/a).- No no-auto-approve team match (row 7 n/a).
Falls through to row 9 → approve.
…ks with, on main (#1790) * ci(release): gate publishes on a companion the built distribution works with scripts/check_companion_releases.py refused an adcp release only when no published adcp-reporting-storage *range* accepted it. adcp 10.0.0b4 changed the ledger column lists while the published storage (adcp>=9.0.0,<11) still accepted it, so the pair installed cleanly and failed every reporting turn (#1763). Range compatibility is not contract compatibility. The gate now proves the pair works. With --dist, it installs the wheel just built together with the published companion into a private environment and runs scripts/companion_smoke.py there, which compares the storage contract each side declares (adcp.reporting.ledger.storage_contract versus adcp_reporting_storage.contract; releases predating the declaration are classified by their column lists) and, with ADCP_PG_TEST_URL, creates the ledger schema and round-trips a configuration, obligation and revision through the installed stores on PostgreSQL, the calls that failed on the incompatible pair. Storage publishes are gated too, in the other direction: the built storage wheel is smoked with the oldest and the newest published adcp its own requirement accepts, so a floor that admits an adcp of another contract is refused. release-publish.yml builds first, runs the gate for both packages against a PostgreSQL service, then uploads. Verified locally against PyPI and PostgreSQL 16: the adcp wheel built from next is refused against the published 0.2.0b0 (contract 2 versus 1), the storage wheel from #1787 (floor 10.0.0b4) passes with 10.0.0b4, and the published 0.1.0b4 passes with adcp 9.0.0 and 9.3.1. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit 7b40f34) * test(release): assert the gate smokes both packages and classify stubbed contracts The release configuration test expected the companion gate to run only for adcp publishes; it now runs for both packages after the build, with a PostgreSQL service. The smoke classification test stubs both declaration modules instead of assuming the workspace pair agrees, which it does not until the storage declaration (#1787) lands. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit 876faa5) * test(release): classify the smoke's undeclared adcp from patched column lists The test asserted this checkout's column lists carry offering_id, which is true on next and false on main; both classifications are now driven by patched lists so the test is branch-agnostic. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — clean delta since prior approve.
The only change since the prior approve (head 1522644) is a single comment reflow in packages/adcp-reporting-storage/src/adcp_reporting_storage/ledger/pg.py (L702-713): the "stay pending for python -m adcp migrate" comment rewrapped across lines with zero semantic change. No code, control flow, or wire surface touched.
Checked:
- No critical/high/medium findings in this run.
high_risk: false; no deletions in sensitive paths.gated_paths: falseandreview_decision: APPROVED— no path gate holding.- Author is not on any no-auto-approve team.
- Public-surface / semver, forward-compat union, import-layering, generated-code, ctx_metadata credential, and CI-gate concerns all N/A for a comment reflow.
Decision table: rows 1–8 do not fire (no findings, no gate, no team match, <3 mediums). Falls through to row 9 → approve.
Why
adcp==10.0.0b4with the only publishedadcp-reporting-storage(0.2.0b0) installs cleanly (adcp>=9.0.0,<11) and fails every PostgreSQL Reliable Reporting turn withcolumn "offering_id" does not exist(#1763). ESA has a production ledger created by 0.1.0b4/0.2.0b0 that cannot be recreated.Drift audit
Column lists adcp reads the ledger with (
adcp.reporting.ledger._record_rows), by release:_OBLIGATION_COLUMNS_CONFIGURATION_COLUMNS_STATUS_COLUMNSnextoffering_idoffering_idoffering_idThat is the only column-list drift. Storage on
nextalready ships matching DDL and a migration framework (0001–0005, unreleased); 0.1.0b4 and 0.2.0b0 are source-identical. The SDK 9 line has no drift (storage onmain== 0.1.0b4;_record_rowsunchanged since).What this PR does
create_schema()applies and records the versioned migrations inside its bootstrap transaction, under the reporting schema advisory lock, so thestore=startup path (what ESA runs) andpython -m adcp migrateconverge. A 0.1.0b4/0.2.0b0 ledger is upgraded in place:offering_idadded with''for every existing account-level row, generation key and period index widened, managed-delivery FKs re-pointed, versions recorded inadcp_schema_migrations. Re-runnable; concurrent boots serialize; no evidence row rewritten or dropped.adcp_reporting_storage.STORAGE_CONTRACT = 2, checked when aPgReportingLedgerStoreis built. A mismatched adcp is refused at startup withREPORTING_SCHEMA_BEHIND, naming both installed versions, both contracts and the release to install. adcp 10.0.0b4/b5 (no declaration yet) are classified by their column lists; the adcp-side declaration and check come in a follow-up PR.adcp>=10.0.0b4,<11, the first adcp release of contract 2, so the incompatible pair no longer resolves.Test evidence (real PostgreSQL 16, actual published wheels)
New
tests/conformance/reporting/test_reporting_storage_published_upgrade_pg.pyseeds ledgers withadcp==9.3.1 + adcp-reporting-storage==0.1.0b4andadcp==10.0.0b2 + 0.2.0b0(generations, obligations, revisions, rows), then with this checkout:UndefinedColumn: offering_id);create_schema()andmigrate()each upgrade in place: every pre-upgrade row survives byte-for-byte plusoffering_id='', all records read back, versions 0001–0005 recorded, re-run is a no-op;ReliableReportingService(store=...)restart rebinds both stored generations with no initialization errors and closes the next period with a new snapshot revision;create_schema()/migrate()boots serialize and record each version once;adcp==10.0.0b4+ this storage (the pair a seller upgrades to) upgrades the 0.2.0b0 ledger and reads everything.Plus
make lint,make typecheck-all, and the existing storage facade / migration / PG ledger tests pass locally.Operator note
The upgrade is a stopped-worker upgrade (0.2.0b0 workers' configuration writes name the four-column key): stop workers of the earlier release, start the new release (or run
python -m adcp migrate), done.🤖 Generated with Claude Code
Open workspace in Conductor