Repository navigation
ci(release): gate publishes on a companion the built distribution works with (#1763) - #1788
Conversation
…ks 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>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — release-gate hardening only (#1763), no blocking findings.
What I checked:
- No no-auto-approve team match;
gated_paths: false;high_risk: false— none of the hard gates apply. - Changes are confined to release tooling and docs:
.github/workflows/release-publish.yml,RELEASING.md,docs/releasing.md,scripts/check_companion_releases.py,scripts/companion_smoke.py, andtests/test_check_companion_releases.py. - No public-API, wire-shape, or type-system surface touched — no
adcp.*export, Pydantic model, discriminated-union, import-layering, generated-code, orctx_metadataconcerns triggered. - The new gate is strictly stronger than the removed range-only step:
companion_smoke.pyfail-closes (main() L594-601 converts any SystemExit/driver/DDL/assertion failure to a nonzero exit; smoke()/gate_adcp/gate_companion only return 0 on an actual pass); subprocesstimeout=900is bounded under the job's 120-min cap; the oldest+newest endpoint strategy is sound given monotonic contract numbering. - CI gates stay armed — a new test covers both publish directions, install failure, wheel-metadata parsing, and contract classification.
Reviewer returned a clean report with zero findings. No rows 1–8 fire; falls through to row 9.
…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>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — test-only delta refining the companion-smoke gate suite, no actionable findings.
This subsequent push (prior decision was approve, no findings) continues to be clean. The delta is test-only: two files refining the suite for the new companion-smoke gate. The reviewer traced both rewritten tests in test_check_companion_releases.py against companion_smoke.py — the declared-contract path, the mismatch-rejection message, and the pre-declaration fallback classifier all wire correctly to the real code paths, including the sys.modules=None import-block sentinel. test_release_configuration.py assertions match the reworked release-publish.yml (both packages gated, build-before-gate ordering, postgres service, ADCP_PG_TEST_URL).
Decision path: no critical/high findings (row 1 n/a); gated_paths false (row 2 n/a); high_risk false (rows 3/5 n/a); no medium findings (rows 4/8 n/a); prior decision was approve, not escalate (row 6 n/a); no no-auto-approve team match (row 7 n/a). Falls through to row 9 → approve.
…mn 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>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — clean subsequent pass, no blocking or medium findings.
This PR touches release tooling, docs, and tests (release-publish workflow, RELEASING.md, docs/releasing.md, companion-release scripts, and their tests). No changes to the adcp.* public surface, generated types, forward-compat unions, import layering, or credential handling — none of the repo's mandatory governance gates are implicated.
The delta since the prior approve is a single test reorder in tests/test_check_companion_releases.py: test_the_smoke_script_classifies_releases_without_a_declaration now exercises both the contract-1 pass path and the contract-2-vs-1 fail path, where the previous version only hit the failure branch. Classification logic (offering_id presence → contract 2) is correctly driven and the monkeypatched _record_rows is read via the cached module. Sound improvement in test coverage.
Gates: high_risk false, gated_paths false, review_decision APPROVED, no no-auto-approve team match. No findings. Falls through to row 9.
Why
scripts/check_companion_releases.pyrefused anadcppublish only when no publishedadcp-reporting-storagerange 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. Storage publishes were not gated at all.Design
Two layers, one source of truth:
adcp_reporting_storage.contract.STORAGE_CONTRACT(fix(reporting-storage): upgrade 0.1.0b4/0.2.0b0 ledgers in place and pin the adcp storage contract (#1763) #1787) andadcp.reporting.ledger.storage_contract.REPORTING_STORAGE_CONTRACT(follow-up PR) name the ledger layout each side implements; they are bumped together when the shared column lists change. Releases that predate the declaration are classified deterministically: an adcp whose obligation column list carriesoffering_idis contract 2, else 1; a storage without the module is contract 1. The same comparison runs at service startup (runtime) and at release time (this PR).--dist, the gate creates a private environment, installs the wheel just built plus the published companion, and runsscripts/companion_smoke.pythere with-I. The smoke compares the contracts and, withADCP_PG_TEST_URL, creates the ledger schema and round-trips a configuration, obligation and revision through the installed stores on PostgreSQL, the exact calls that failed on the incompatible pair. Only APIs shared by adcp 9.x and 10.x are used, so the gate runs onmainandnext.Direction matters, so both are gated:
adcppublish: newest published storage whose range admits this adcp must pass the smoke.adcp-reporting-storagepublish: the built wheel must pass the smoke with the oldest and the newest published adcp its ownRequires-Distaccepts, so a floor that admits an adcp of another contract is refused.release-publish.ymlnow builds first, runs the gate for both packages against apostgres:16service, then uploads.Verified locally (PyPI + PostgreSQL 16)
next(contract 2) + published 0.2.0b0adcp reads the ledger with storage contract 2 but the storage implements contract 1>=10.0.0b4) + published adcp 10.0.0b4Unit tests cover the orchestration with a scripted runner (range-compatible-but-contract-incompatible companion blocks, install failure blocks, both directions, wheel metadata parsing).
Release ordering consequence
When the contract changes, publish the storage release that implements the new contract first (its floor is the first adcp of that contract), then the adcp release. Concretely for #1763: storage 0.2.0b1 (#1787) must be on PyPI before the next adcp beta can publish; this gate enforces that.
🤖 Generated with Claude Code