Skip to content

fix(release): dedup cleanup PRs, assert artifact invariant, SHA-pin upload - #85

Merged
szTheory merged 2 commits into
mainfrom
fix/release-pipeline-dedup-and-pin
Jul 28, 2026
Merged

fix(release): dedup cleanup PRs, assert artifact invariant, SHA-pin upload#85
szTheory merged 2 commits into
mainfrom
fix/release-pipeline-dedup-and-pin

Conversation

@szTheory

Copy link
Copy Markdown
Owner

Three related release-pipeline defects found while triaging the PR backlog. Independent of #84.

1. The cleanup job minted a duplicate PR on every release run

release-as-cleanup decides whether to open a PR by checking whether strip_release_as.py dirtied the working tree. But that runs against a freshly checked-out main, which still carries the pin until an earlier cleanup PR merges — so the check is structurally incapable of noticing one that's already open. And the branch name embeds ${{ github.run_id }}, so it never collides and gh pr create never no-ops.

Net effect: N release runs before a human merges ⇒ N identical PRs. That's exactly what produced #65/#68/#72 (now closed as obsolete — the pins they stripped had already landed via #67 and #74).

Adds an explicit open-PR guard before gh pr create. It uses the REST list endpoint, not --search — the two threadline duplicates were opened 80 minutes apart, well inside search-index lag, so a --search-based guard could still have let one through.

2. The integrity scanner hard-pinned an action version

native_status_artifact asserted the literal string actions/upload-artifact@v4. The real invariant is that the rollup always uploads the status file and fails closed when it's missing — not which version does it.

That frozen literal is why dependabot #50 (upload-artifact v4→v7) red-lines four merge-blocking lanes (phase41, phase132, phase34, phase43) plus hex-page-proof. The bump itself is functionally harmless — all six artifact names are unique, the inputs used are stable across v4–v7, and the download side is already on v8.

Now matches any version, and additionally asserts if: ${{ always() }} — which the old check never covered despite it being load-bearing for the fails-closed behavior.

3. release-please.yml:745 was the repo's only REL-05 SHA-pin violation

Every other uses: in the release-sensitive workflows is SHA-pinned per REL-05 (CVE-2025-30066-class supply-chain hardening). This one was a naked @v4 tag — kept that way by the assertion in #2, which required the literal string.

Now pinned to ea165f8d65b6e75b540449e92b4886f43607fa02 (v4.6.2). No version change — same code that was already running, now immune to tag re-pointing.

Mutation harness hardening

replace_in_job/4 used String.replace(..., global: false), which silently returns the block unchanged when the pattern is absent. A drifted workflow would leave the negative control asserting nothing about the check it names. It now raises at the mutation site with a message saying to update the control, not delete it.

The phase145 control is re-pointed at a version-agnostic pattern (it was coupled to @v4 and would have broken on the SHA pin), and a new control proves the dedup guard bites.

Verification

  • elixir script/check_release_workflow_integrity.exsexit 0 with the SHA pin in place. This is the load-bearing proof: the old frozen-tag check would have failed here.
  • mix test test/crosswake/proof/phase142_release_integrity_test.exs59 tests, 0 failures, including the new dedup negative control.

Unblocks

Once this lands, dependabot #50 can rebase and go green.

🤖 Generated with Claude Code

…pload

Three related release-pipeline defects surfaced while triaging the PR backlog.

1. The release-as-cleanup job minted a duplicate PR on every release run.

   Its dirty-worktree check runs against a freshly checked-out main, which still
   carries the pin until an EARLIER cleanup PR merges — so it structurally cannot
   notice one that is already open. The branch name embeds `github.run_id`, so it
   never collides and `gh pr create` never no-ops. Result: N release runs before a
   human merges the cleanup PR produced N identical PRs (#65/#68/#72, now closed).

   Adds an explicit open-PR guard before `gh pr create`. It uses the REST list
   endpoint rather than `--search`, so a lagging search index cannot let a
   duplicate slip through — the two threadline duplicates were opened 80 minutes
   apart, well inside index-lag territory.

2. The integrity scanner hard-pinned an action version.

   `native_status_artifact` asserted the literal string `actions/upload-artifact@v4`.
   The real invariant is that the rollup ALWAYS uploads the status file and fails
   closed when it is missing — not which version does it. The frozen literal made a
   routine dependabot bump (#50, v4 to v7) red-line four merge-blocking lanes, and
   made it impossible to SHA-pin the step. Now matches any version and additionally
   asserts `if: ${{ always() }}`, which the old check never covered.

3. `release-please.yml:745` was the repo's only REL-05 SHA-pin violation.

   Every other `uses:` in the release-sensitive workflows is SHA-pinned; this one
   was a naked `@v4` tag, kept that way by the assertion in (2). Now pinned to
   ea165f8d65b6e75b540449e92b4886f43607fa02 (v4.6.2). No version change — this is
   the same code that was already running, now immune to tag re-pointing.

Also hardens the mutation harness. `replace_in_job/4` silently returned the block
unchanged when its pattern was absent, so a drifted workflow would leave the
negative control asserting nothing about the check it names. It now raises at the
mutation site. The phase145 control is re-pointed at a version-agnostic pattern,
and a new control proves the dedup guard bites.

Verified: `elixir script/check_release_workflow_integrity.exs` exits 0 WITH the
SHA pin in place — the old frozen-tag check would have failed there.
phase142 suite: 59 tests, 0 failures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@szTheory
szTheory enabled auto-merge July 28, 2026 17:01
@szTheory
szTheory merged commit c28607f into main Jul 28, 2026
73 checks passed
@szTheory
szTheory deleted the fix/release-pipeline-dedup-and-pin branch July 28, 2026 20:18
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