Repository navigation
ci(release): gate publishes on a companion the built distribution works with, on main - #1790
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> (cherry picked from commit 7b40f34)
…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> (cherry picked from commit 876faa5)
| monkeypatch.setitem(sys.modules, "adcp.reporting.ledger.storage_contract", None) | ||
| monkeypatch.setitem(sys.modules, "adcp_reporting_storage.contract", None) | ||
| assert smoke.storage_contract() == 1 | ||
| assert smoke.adcp_contract() == 2 # this checkout's columns carry offering_id |
There was a problem hiding this comment.
MUST FIX: This assertion is ported from next verbatim and is false on main. The 9.x line's _OBLIGATION_COLUMNS has no offering_id (PR body: "_record_rows is unchanged since 9.3.1", "adcp 9.x ... are contract 1"), so adcp_contract() falls through to _record_rows and returns 1, not 2. This assert fails, and with it pytest tests/ — one of the three mandatory local gates. The following .replace(", offering_id", "") is also a no-op here. Rewrite the test for the 9.x reality: this checkout classifies as contract 1, and synthesize the contract-2 case by injecting offering_id into _OBLIGATION_COLUMNS rather than stripping it.
There was a problem hiding this comment.
Ladon verdict: Request changes
Request changes — 1 blocking finding.
Blocking findings
- tests/test_check_companion_releases.py:292 — Ported test asserts
adcp_contract()==2("columns carry offering_id"), but main's_OBLIGATION_COLUMNShas nooffering_id, so it returns 1. The assert fails, breakingpytest tests/— a mandatory CI gate.
The production gate and classifier (companion_smoke.py adcp_contract, lines 48-49) are correct for the 9.x line; only the test expectation needs adapting to main's schema. Fix the ported assertion to match main's actual contract value (1), then the gate passes.
Verified clean: smoke subprocess has an explicit 900s timeout and fails closed on install/timeout; workflow gates both packages after build against a postgres:16 service and before artifact upload.
Blocking findings
- tests/test_check_companion_releases.py:292 — Ported test asserts adcp_contract()==2, fails on main (no offering_id), breaking mandatory pytest gate
…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>
Superseded by a later Ladon review that rechecked and resolved its blocking findings.
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — prior blocker resolved, clean delta.
This is a subsequent review. The prior run requested changes on one high finding (tests/test_check_companion_releases.py:292 — ported test asserted adcp_contract()==2 and failed on main's offering_id-free _OBLIGATION_COLUMNS). That finding is resolved: the rewritten test strips ", offering_id" to prove contract 1 (a no-op on main) then appends it to prove contract 2, so it no longer depends on the base checkout's column list and passes the mandatory pytest gate on main.
Current run surfaces no findings. The diff is release tooling/docs plus test fixes (release-publish workflow, RELEASING.md, docs/releasing.md, companion release scripts, and their tests) — no public-surface changes, no generated-code hand edits, no import-layering violations, no CI-gate weakening, no credential concerns.
Decision table: no critical/high/medium findings; gated_paths false; high_risk false; no no-auto-approve team match. Prior decision was request-changes (not escalate), so row 6 does not apply. Falls through to row 9 → approve. Net improvement over the prior pass.
Note: review_decision is REVIEW_REQUIRED, but gated_paths is false, so the hard approval gate (row 2) does not apply.
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — clean release-infra pass, no blocking findings.
This PR refines the companion-release gate (#1763): adds scripts/companion_smoke.py (214 lines), a bidirectional gate in check_companion_releases.py, a Postgres service in release-publish.yml, and matching tests. The reviewer verified companion_smoke.py end to end — fail-closed exception handling (any driver/DDL error returns 1), try/finally schema cleanup, -I isolated execution, the offering_id contract-classification fallback matching this repo's _OBLIGATION_COLUMNS history, subprocess timeout=900 with stripped PYTHONPATH, and that every ledger/store API the smoke calls exists in both companion repos. No wire-shape, public-surface, type-layering, or credential surfaces are touched.
Decision-table walk: no critical/high/medium findings (row 1 n/a); gated_paths false (row 2 n/a); high_risk false (rows 3/5 n/a); no medium findings (row 4 n/a); prior decision was approve so row 6 doesn't apply; no no-auto-approve team match (row 7 n/a); zero medium findings (row 8 n/a). Falls through to row 9 → approve. This is a net-neutral/positive follow-up to a prior approve with no new findings.
Port of #1788 to
main(the SDK 9 release line), sorelease-publish.ymlapplies the same gate when it runs frommain.Why
scripts/check_companion_releases.pyrefused anadcppublish only when no publishedadcp-reporting-storagerange accepted it. Onnext, 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, and storage publishes were not gated at all.What
--dist, the gate installs the wheel just built plus the published companion into a private environment and runsscripts/companion_smoke.pythere (-I): both sides must classify to the same storage contract (adcp 9.x and storage 0.1.0b4 are contract 1; the declaration modules land with 10.0.0b6 / storage 0.2.0b1), and withADCP_PG_TEST_URLthe ledger must create its schema and round-trip a configuration, obligation and revision through the installed stores. Only APIs shared by adcp 9.x and 10.x are used.adcppublish is smoked with the newest published storage its range admits; a storage publish is smoked with the oldest and the newest publishedadcpits ownRequires-Distaccepts.release-publish.ymlbuilds first, runs the gate for both packages against apostgres:16service, then uploads.Verified locally (PyPI + PostgreSQL 16)
mainis source-identical to 0.1.0b4 and_record_rowsis unchanged since 9.3.1).next+ published storage 0.2.0b0: refused (storage contract 2 but the storage implements contract 1), which is 10.0.0b4 is incompatible with adcp-reporting-storage 0.2.0b0: the ledger selects offering_id, which 0.2.0b0's tables lack #1763.The
next-specific release-configuration assertions are not ported;maingets a focusedtest_release_publish_gates_both_packages_on_a_working_companion.🤖 Generated with Claude Code