fix(release): gate publishing on accepted main artifacts - #1200
Conversation
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — release pipeline hardening with no blocking findings.
This PR replaces the combined Release Please publisher with a three-stage guarded pipeline (proposal → read-only acceptance → environment-approved publication), covering four large non-generated scripts (release_gate.py, release_artifacts.py, release_publish.py), the workflows, base ci.yml, docs/releasing.md, and 1,335 lines of adversarial tests.
What I checked against repo-specific rules:
- No public
adcp.*surface changes; semver-signal rule not triggered (release/CI tooling only). - No edits to forward-compat/discriminated-union files or generated code.
- No type-system import-layering violations.
- No credentials introduced into ctx_metadata.
- CI gates stay armed — no disabled tests, dropped ruff rules, or blanket mypy ignores; in fact the pipeline adds fail-closed checks (main-tip rechecks, App-bound required checks, rerun refusal, digest-pinned artifacts, PyPI-first ordering with pre-mutation rechecks).
Reviewer surfaced no Critical, High, or Medium findings. high_risk is false, gated_paths is false, no no-auto-approve team match, no prior decision. Note review_decision is REVIEW_REQUIRED, but the gated-paths hard gate (row 2) only applies when gated_paths is true, which it is not here. No decision-table trigger (rows 1–8) fires, so this falls through to row 9 → approve.
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — subsequent pass on PR #1200, clean.
The only delta since the prior clean approve is a single test-only line in tests/test_release_guard.py: FakeGitHub now calls super().__init__("fixture-token") to satisfy the base GitHub constructor's token guard. FakeGitHub overrides repo/pages and serves from self.data, so the fixture token never reaches the wire — correct, minimal, no production impact.
No new findings. No prior critical/high blockers to re-evaluate. No blocking findings, no medium findings.
Gate check: gated_paths is false, so row 2 does not apply despite review_decision: REVIEW_REQUIRED. high_risk is false (no deleted/modified high-risk triggers). No author no-auto-approve team match. Zero medium findings. None of rows 1–8 fire → row 9 approve.
The combined Release Please workflow could create tags/releases from a main push or a
publish=falsedispatch. This change retires that entry point and separates manual proposal, read-only acceptance, and explicitly enabled publication. Every writer is tied to a full current-main SHA, its dispatch/workflow definition, fresh exact-main CI, and the immutable wheel/sdist accepted in its own first attempt.Implemented independently from
origin/mainatf6e9c15333db8e657ba96a79d806194dfc0e0447. Must merge last, after the reporting stack and #1172. Rebase and review the integrated reporting acceptance contract, then repeat validation and independent review on the final exact head. Refs #1198; this PR does not authorize integration or publication.Head:
8650ef3a9e3ac23e146ab7ab33d7b4546142a32bTree tested locally:
a7a9e249579b8d69e4c7a151410eca6f55e3e958The proposal action has
skip-github-release: truehardcoded, no PyPI/OIDC identity, and a dedicated environment-scoped App token minted only after acceptance and approval. Normalization uses the contents API on the normal release PR branch; it never executes release-branch code. Publication separately approves PyPI OIDC and GitHub writes, downloads by artifact ID/digest, checks the exact source inventory/version/install evidence, and creates only the literal accepted commit tag. Privileged jobs do not build or install packages.The shared gate rejects incomplete/skipped/failed protected checks, stale or moving targets, another CI attempt, missing/mismatched/expired artifacts, and new workflow reruns. A release-PR merge is a new target requiring new acceptance. Recovery is a fresh approved dispatch of a failed guarded run at unchanged main, preserving its exact distribution bytes but repeating installed acceptance. Existing destination files/tags must match; normal replay and
skip-existingare forbidden. Legacy workflow 204238826 stays disabled; publication also rejects its undrained or still-rerunnable historical runs.Validation:
make lint,make typecheck-all,make validate-generated: passed. Source typing covers 1,352 files and 26 adopter fixtures; the new scripts also pass explicit mypy, Ruff and Black checks.make test: 8,749 passed, 44 skipped, 9 deselected, 1 xfailed; 83.42% coverage.concurrency.queuecompatibility exclusion. Offline pedantic Zizmor 1.29.0: zero findings for all four release workflows; new paths are included in CI security scanning. Actions and the PostgreSQL service image are pinned.External requirements and blockers:
disabled_manually. New YAML does not repair its historical definitions. An authorized operator must preserve evidence, drain/retire rerunnable historical writers and their old publishing credentials before enabling the new writer. No such changes are made here.release-proposalandrelease-publishenvironments with required reviewers, no self-review/admin bypass, and an exact branch-main deployment policy; configure the dedicated proposal App, exact PyPI workflow/environment trust, release-tag protection, and the publication enable variable.RELEASE_MAIN_FREEZEattestation bound to the target, ruleset ID and revision. The gate verifies the live matching revision and rejects missing/stale/changed evidence; it does not grant administration permissions to builds.IPR Policy / Signatureis absent andValidate conventional commit formatis skipped on the main-push CI run. A separately reviewed change must produce real successful exact-main policy checks. IPR currently arrives as a PR-head commit status; this guard requires check-run evidence exposing the required App ID, so the exact-main policy work must supply that evidence. It does not infer App identity from a legacy status name. No PR/parent-check substitution or protection weakening is included. Classic protection and administrative rule-suite history returned 403 to this workspace integration; that is not treated as approval. PR reviews and CodeQL merge protection remain required through ordinary protected integration.contents:writeApp scope. The freeze attestation trusts authorized configuration administrators; proposal non-publication relies on pinned, reviewed code with the skip-release input fixed. A stronger requirement that arbitrary code holding the proposal App token cannot mutate GitHub releases needs a separate policy boundary. These platform limits are explicit in the design, not silently claimed away.The release runbook covers historical retirement, configuration, proposal, the new release-merge target, guarded enable, publication, recovery and rollback, with authoritative GitHub/PyPI references. No release workflow was enabled or dispatched, no package/tag/release was created, no secrets/environments/rules were changed, and no merge or admin bypass was used.