Skip to content

ci(release): gate publishes on a companion the built distribution works with, on main - #1790

Merged
bokelley merged 4 commits into
mainfrom
claude/issue-1763-release-gate-main
Nov 2, 2026
Merged

bokelley merged 4 commits into
mainfrom
claude/issue-1763-release-gate-main

Conversation

@bokelley

@bokelley bokelley commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

Port of #1788 to main (the SDK 9 release line), so release-publish.yml applies the same gate when it runs from main.

Why

scripts/check_companion_releases.py refused an adcp publish only when no published adcp-reporting-storage range accepted it. On next, 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

  • With --dist, the gate installs the wheel just built plus the published companion into a private environment and runs scripts/companion_smoke.py there (-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 with ADCP_PG_TEST_URL the 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.
  • Both directions: an adcp publish is smoked with the newest published storage its range admits; a storage publish is smoked with the oldest and the newest published adcp its own Requires-Dist accepts.
  • release-publish.yml builds first, runs the gate for both packages against a postgres:16 service, then uploads.

Verified locally (PyPI + PostgreSQL 16)

The next-specific release-configuration assertions are not ported; main gets a focused test_release_publish_gates_both_packages_on_a_working_companion.

🤖 Generated with Claude Code

bokelley and others added 2 commits November 1, 2026 21:50
…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)
Comment thread tests/test_check_companion_releases.py Outdated
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

@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: 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_COLUMNS has no offering_id, so it returns 1. The assert fails, breaking pytest 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>
@bokelley bokelley changed the title ci(release): gate publishes on a companion the built distribution works with (#1763, main) ci(release): gate publishes on a companion the built distribution works with, on main Nov 1, 2026
@aao-secretariat
aao-secretariat Bot dismissed their stale review November 1, 2026 23:37

Superseded by a later Ladon review that rechecked and resolved its blocking findings.

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

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

@bokelley
bokelley merged commit f49ffc5 into main Nov 2, 2026
47 checks passed
@bokelley
bokelley deleted the claude/issue-1763-release-gate-main branch November 2, 2026 10:50
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