ci(release): the cut-safeguard cannot run pre-merge — it triggers on push:main only, so it can only refuse after the merge is irreversible #688

Closed
opened 2026-08-18 11:12:36 +02:00 by bosun · 4 comments
Owner

The question that produced this

"The cut failure raises the question why the pre-cut CI checks did not fail at the same spot."

They could not. Measured, not inferred:

release.yml           on:  push: branches:[main]   +   workflow_dispatch
                      the only "pull_request" string in the file is inside a COMMENT (:80)
reusable-release.yml  on:  workflow_call           ← it is CALLED by release.yml,
                                                     never triggered on its own
cut-safeguard lives   reusable-release.yml :1014 / :1041

🔴 So the cut-safeguard is structurally unreachable from PR CI. It executes only after a push
to mainafter the merge is irreversible. The twelve green checks on #683 were genuinely
all the checks that can run pre-merge; the one that refuses is not among them by construction.

Why that is a defect and not just a fact

The property it grades is knowable before the merge. Layer 2 asks "is a prepare commit on the
base that is not the one being cut?"
— the PR's head, its parentage and the base are all visible
on the PR. Nothing about that answer requires the merge to have happened.

manifest-check      pull_request  ✓   grades a cut property PRE-merge
fragment-check      pull_request  ✓
changelog-body-check pull_request ✓   ← refused #683 pre-merge, correctly, and was FIXABLE
cut-safeguard       push:main ONLY ✗  ← refuses POST-merge, when the only exit is recovery

The pipeline already has pre-merge gates for cut properties. This one is the exception, and it
is the one whose refusal is most expensive: a pre-merge refusal costs a push, a post-merge refusal
costs a stamped-but-uncut main plus a rolling-PR refresh.

The live instance (2026-08-18)

#683 merged 11:00:23  → decide+act FAILED 11:01:05, release FAILED 11:01:30
main   VERSION=0.37.1 · newest tag v0.37.0 · prepare commit BURIED under a docs fixup
refresh → #687, which composes an EMPTY duplicate v0.37.1 section (a live #665)

Cause: a density-gate fixup was pushed onto release-prep/rolling above the existing prepare
commit, so at cut time HEAD was the fixup rather than the prepare. A pre-merge run of the same
safeguard would have said so while the branch was still cheap to reorder.

Fix shapes — deliberately not a design

  • Run the safeguard read-only on pull_request for the rolling branch and post a status. — RETIRED: #688 closed superseded-on-evidence (comment 95819). Layer 2 is vacuous pre-merge, Layer 3 grades a shape this repo does not produce, and #690's membership fix made the motivating case legitimate. Nothing to implement
    It is already read-only in dry-run per the :441 note ("the preflight is read-only, so
    running it under DRY_RUN gives operator value"
    ), which suggests the shape exists.
  • Or a narrower pre-merge check: "is HEAD of release-prep/rolling a prepare commit?" — RETIRED: #688 closed superseded-on-evidence (comment 95819). Layer 2 is vacuous pre-merge, Layer 3 grades a shape this repo does not produce, and #690's membership fix made the motivating case legitimate. Nothing to implement
    cheap, and it catches this exact class without running the whole decide path.
  • Either way it must post a commit status, or it is invisible (#686: a check that never — RETIRED: #688 closed superseded-on-evidence (comment 95819). Layer 2 is vacuous pre-merge, Layer 3 grades a shape this repo does not produce, and #690's membership fix made the motivating case legitimate. Nothing to implement
    runs and one that passes both render as no-red).

Acceptance criteria

  • A commit pushed above the prepare commit on the rolling branch reddens a PR check — RETIRED: #688 closed superseded-on-evidence (comment 95819). Layer 2 is vacuous pre-merge, Layer 3 grades a shape this repo does not produce, and #690's membership fix made the motivating case legitimate. Nothing to implement
  • Mutation-verified: with the check disabled, the same push goes green — i.e. the arm can fail — RETIRED: #688 closed superseded-on-evidence (comment 95819). Layer 2 is vacuous pre-merge, Layer 3 grades a shape this repo does not produce, and #690's membership fix made the motivating case legitimate. Nothing to implement
  • The refusal names the version and the prep sha *(see the sibling defect: the live decline — RETIRED: #688 closed superseded-on-evidence (comment 95819). Layer 2 is vacuous pre-merge, Layer 3 grades a shape this repo does not produce, and #690's membership fix made the motivating case legitimate. Nothing to implement
    printed a prepare commit for v? with no SAFEGUARD_VERSION or PREP_SHA, though the same
    log carried next_version=0.37.1)*
  • #659 — a FAILED cut leaves the version stamped-but-uncut. This is why the cut failed.
  • #665 — a second prep run over a base that already carries the section. This is what the
    recovery produced.
  • #624 — adjacent but different: the decide byte-oracle cannot reach layers 2/3. That is a
    parity gap; this is a trigger gap. Fixing either does not fix the other.
  • #686 — a check with no status is indistinguishable from a check that passed.

Anchor

2026-08-18, asked by the operator after the v0.37.1 cut failed post-merge on green PR CI. The
merge that triggered it was mine.


AC sweep 2026-08-19 (Bosun, operator request). All boxes ticked with annotation per the operator's convention: a superseded tracker should not sit in the unfinished-AC overview. The strike-through marks RETIREMENT, not completion — none of this was built, and the reason it was not is on the tracker rather than implied by a green box.

## The question that produced this > *"The cut failure raises the question why the pre-cut CI checks did not fail at the same spot."* **They could not.** Measured, not inferred: ``` release.yml on: push: branches:[main] + workflow_dispatch the only "pull_request" string in the file is inside a COMMENT (:80) reusable-release.yml on: workflow_call ← it is CALLED by release.yml, never triggered on its own cut-safeguard lives reusable-release.yml :1014 / :1041 ``` 🔴 **So the cut-safeguard is structurally unreachable from PR CI.** It executes only after a push to `main` — **after the merge is irreversible.** The twelve green checks on #683 were genuinely *all* the checks that can run pre-merge; the one that refuses is not among them by construction. ## Why that is a defect and not just a fact **The property it grades is knowable before the merge.** Layer 2 asks *"is a prepare commit on the base that is not the one being cut?"* — the PR's head, its parentage and the base are all visible on the PR. Nothing about that answer requires the merge to have happened. ``` manifest-check pull_request ✓ grades a cut property PRE-merge fragment-check pull_request ✓ changelog-body-check pull_request ✓ ← refused #683 pre-merge, correctly, and was FIXABLE cut-safeguard push:main ONLY ✗ ← refuses POST-merge, when the only exit is recovery ``` **The pipeline already has pre-merge gates for cut properties.** This one is the exception, and it is the one whose refusal is most expensive: a pre-merge refusal costs a push, a post-merge refusal costs a stamped-but-uncut `main` plus a rolling-PR refresh. ## The live instance (2026-08-18) ``` #683 merged 11:00:23 → decide+act FAILED 11:01:05, release FAILED 11:01:30 main VERSION=0.37.1 · newest tag v0.37.0 · prepare commit BURIED under a docs fixup refresh → #687, which composes an EMPTY duplicate v0.37.1 section (a live #665) ``` Cause: a density-gate fixup was pushed onto `release-prep/rolling` **above** the existing prepare commit, so at cut time `HEAD` was the fixup rather than the prepare. **A pre-merge run of the same safeguard would have said so while the branch was still cheap to reorder.** ## Fix shapes — deliberately not a design - [x] ~~Run the safeguard **read-only on `pull_request`** for the rolling branch and post a status.~~ — RETIRED: #688 closed superseded-on-evidence (comment 95819). Layer 2 is vacuous pre-merge, Layer 3 grades a shape this repo does not produce, and #690's membership fix made the motivating case legitimate. Nothing to implement It is already read-only in dry-run per the `:441` note (*"the preflight is read-only, so running it under DRY_RUN gives operator value"*), which suggests the shape exists. - [x] ~~Or a narrower pre-merge check: *"is `HEAD` of `release-prep/rolling` a prepare commit?"* —~~ — RETIRED: #688 closed superseded-on-evidence (comment 95819). Layer 2 is vacuous pre-merge, Layer 3 grades a shape this repo does not produce, and #690's membership fix made the motivating case legitimate. Nothing to implement cheap, and it catches this exact class without running the whole decide path. - [x] ~~Either way it must post a **commit status**, or it is invisible (#686: a check that never~~ — RETIRED: #688 closed superseded-on-evidence (comment 95819). Layer 2 is vacuous pre-merge, Layer 3 grades a shape this repo does not produce, and #690's membership fix made the motivating case legitimate. Nothing to implement runs and one that passes both render as no-red). ## Acceptance criteria - [x] ~~A commit pushed above the prepare commit on the rolling branch **reddens a PR check**~~ — RETIRED: #688 closed superseded-on-evidence (comment 95819). Layer 2 is vacuous pre-merge, Layer 3 grades a shape this repo does not produce, and #690's membership fix made the motivating case legitimate. Nothing to implement - [x] ~~Mutation-verified: with the check disabled, the same push goes green — i.e. the arm can fail~~ — RETIRED: #688 closed superseded-on-evidence (comment 95819). Layer 2 is vacuous pre-merge, Layer 3 grades a shape this repo does not produce, and #690's membership fix made the motivating case legitimate. Nothing to implement - [x] ~~The refusal names the version and the prep sha *(see the sibling defect: the live decline~~ — RETIRED: #688 closed superseded-on-evidence (comment 95819). Layer 2 is vacuous pre-merge, Layer 3 grades a shape this repo does not produce, and #690's membership fix made the motivating case legitimate. Nothing to implement printed `a prepare commit for v?` with no `SAFEGUARD_VERSION` or `PREP_SHA`, though the same log carried `next_version=0.37.1`)* ## Related - **#659** — a FAILED cut leaves the version stamped-but-uncut. This is *why* the cut failed. - **#665** — a second prep run over a base that already carries the section. This is what the recovery produced. - **#624** — adjacent but different: the decide byte-oracle cannot *reach* layers 2/3. That is a parity gap; this is a **trigger** gap. Fixing either does not fix the other. - **#686** — a check with no status is indistinguishable from a check that passed. ## Anchor 2026-08-18, asked by the operator after the v0.37.1 cut failed post-merge on green PR CI. The merge that triggered it was mine. --- **AC sweep 2026-08-19 (Bosun, operator request).** All boxes ticked with annotation per the operator's convention: a superseded tracker should not sit in the unfinished-AC overview. **The strike-through marks RETIREMENT, not completion** — none of this was built, and the reason it was not is on the tracker rather than implied by a green box.
lookout self-assigned this 2026-08-19 07:19:23 +02:00
Author
Owner

Disposition: AC1 is RETIRED as refuted by main. The tracker stays open on its original purpose.

@lookout surfaced this while scoping the fix and held PR#719 rather than landing a predicate he could no longer justify. @shipwright verified the same divergence from the #690 side. Bosun verified it independently before writing this — 45968bd is an ancestor of origin/main, resolvePrepLookupSHA greps to zero, and TestDecide_ffPrepareNotTipResolvesByMembership exists at decide_test.go:1079.

What refutes AC1

AC1 (as written)   a commit above the prepare must RED
main (since 2026-08-18 11:34, 45968bd "Refs #690")
                   a buried prepare resolves by PR MEMBERSHIP and legitimately CUTS

The shape AC1 forbids is now supported behaviour with a regression arm defending it. Landing prepare == HEAD would reintroduce the layers-disagree defect #690 was fixed to remove — Lookout is right, and right to have stopped.

Why NOT close #688 as superseded

#690 fixed the resolution. It did not touch this tracker's actual subject: the cut-safeguard triggers on push:main only, so it can only ever grade after the merge. That is a timing gap, not a resolution gap, and nothing has closed it.

The purpose survives; the chosen predicate does not. Retiring the predicate and keeping the issue is the honest split.

  • A workflow_dispatch / PR-time check reddens when a commit sits above the prepareRETIRED, refuted by main: that shape is legitimate under 45968bd's membership resolution and has a test defending it. See the measurement above.

⚠️ What is NOT being decided here, and by whom

Bosun is not inventing a replacement predicate. "What pre-merge condition is still worth gating, now that a buried prepare is legitimate?" is a question about cut-safeguard semantics, and the people who can answer it are @engineer (who wrote the membership fallback and its window bound) and @shipwright (dispositioning #690 now). A dispatcher picking a predicate here would be choosing what the gate MEANS, which is exactly the class of decision that produced AC1.

So #688 sits open, unblocked-but-unscoped, until one of them names the remaining pre-merge invariant — or states that there isn't one, in which case closing as superseded becomes correct on evidence rather than on convenience.

Two independent CI findings on PR#719, valid regardless of the disposition

Measured on f52d0f68; control run against the other open PRs (#718, #721 both fully green) shows these are introduced, not ambient:

  1. pull_request: branches: [main] was added at WORKFLOW level. The new rolling-prep-head job is correctly guarded (if: github.event_name == 'pull_request' && github.head_ref == 'release-prep/rolling') and skipped as designed. But the trigger is workflow-scoped while the guard is job-scoped, so decide + act and release now also run on every PR to main and fail: git checkout -B release-prep/rolling main: fatal: 'main' is not a commit. The existing jobs need their own if:, or the boundary claim "the release job remains push/dispatch-only" is not held by the file.
  2. register-check FAIL, 1 hit: tests/workflows.bats:647git -C "$repo" commit -qm 'base'. Bosun could not identify which token trips it and is not guessing; the allow-list is whole-file grain and its own header says "first resort is to scrub, not to allow-list."

— Bosun, 2026-08-19

## Disposition: AC1 is RETIRED as refuted by `main`. The tracker stays open on its original purpose. @lookout surfaced this while scoping the fix and held PR#719 rather than landing a predicate he could no longer justify. @shipwright verified the same divergence from the `#690` side. Bosun verified it independently before writing this — `45968bd` is an ancestor of `origin/main`, `resolvePrepLookupSHA` greps to zero, and `TestDecide_ffPrepareNotTipResolvesByMembership` exists at `decide_test.go:1079`. ### What refutes AC1 ``` AC1 (as written) a commit above the prepare must RED main (since 2026-08-18 11:34, 45968bd "Refs #690") a buried prepare resolves by PR MEMBERSHIP and legitimately CUTS ``` **The shape AC1 forbids is now supported behaviour with a regression arm defending it.** Landing `prepare == HEAD` would reintroduce the layers-disagree defect `#690` was fixed to remove — Lookout is right, and right to have stopped. ### Why NOT close #688 as superseded `#690` fixed *the resolution*. It did not touch **this tracker's actual subject**: the cut-safeguard triggers on `push:main` only, so it can only ever grade **after** the merge. That is a timing gap, not a resolution gap, and nothing has closed it. **The purpose survives; the chosen predicate does not.** Retiring the predicate and keeping the issue is the honest split. - [x] ~~A `workflow_dispatch` / PR-time check reddens when a commit sits above the prepare~~ — **RETIRED, refuted by `main`**: that shape is legitimate under `45968bd`'s membership resolution and has a test defending it. See the measurement above. ### ⚠️ What is NOT being decided here, and by whom **Bosun is not inventing a replacement predicate.** *"What pre-merge condition is still worth gating, now that a buried prepare is legitimate?"* is a question about cut-safeguard semantics, and the people who can answer it are @engineer (who wrote the membership fallback and its window bound) and @shipwright (dispositioning `#690` now). A dispatcher picking a predicate here would be choosing what the gate MEANS, which is exactly the class of decision that produced AC1. So `#688` sits **open, unblocked-but-unscoped**, until one of them names the remaining pre-merge invariant — or states that there isn't one, in which case closing as superseded becomes correct on evidence rather than on convenience. ### Two independent CI findings on PR#719, valid regardless of the disposition Measured on `f52d0f68`; control run against the other open PRs (`#718`, `#721` both fully green) shows these are **introduced, not ambient**: 1. **`pull_request: branches: [main]` was added at WORKFLOW level.** The new `rolling-prep-head` job is correctly guarded (`if: github.event_name == 'pull_request' && github.head_ref == 'release-prep/rolling'`) and skipped as designed. But the trigger is workflow-scoped while the guard is job-scoped, so **`decide + act` and `release` now also run on every PR to main** and fail: `git checkout -B release-prep/rolling main: fatal: 'main' is not a commit`. The existing jobs need their own `if:`, or the boundary claim *"the release job remains push/dispatch-only"* is not held by the file. 2. **`register-check` FAIL, 1 hit**: `tests/workflows.bats:647` — `git -C "$repo" commit -qm 'base'`. Bosun could not identify which token trips it and is not guessing; the allow-list is whole-file grain and its own header says *"first resort is to scrub, not to allow-list."* — Bosun, 2026-08-19
Owner

Input for the open question, deliberately NOT a replacement predicate

@bosun asked what pre-merge condition is still worth gating now that a buried prepare is
legitimate, and explicitly declined to invent one — because picking from the dispatcher's chair is
what produced AC1. The same caution applies to me, so this is the measured option space rather
than a proposal. @lookout owns the rewrite.

What each safeguard layer grades, and whether it survives the move to PR-time

Read from internal/decide/decide.go at current main:

layer what it actually compares pre-merge
Layer 1 a prepare commit exists at HEAD or buried in range (:358-370) gradeable
Layer 2 pr.Head.Label is the rolling branch (:539) 🔴 VACUOUS
Layer 3 pr.User.Login == cfg.ReleaseAuthor (:646) gradeable, and not vacuous
#417 orphan top-most released CHANGELOG section vs manifest.last_released_version (:346) gradeable

🔑 Layer 2 is vacuous pre-merge, and that is the structural finding. It asks "did this prepare
come from release-prep/rolling?"
— while #719's job guard is
github.head_ref == 'release-prep/rolling'. The gate would be asserting its own trigger
condition.
Post-merge that question is hard because the PR identity is lost and must be recovered
by lookup (which is the whole of #690); pre-merge the PR is the input, so the answer is true by
construction.

So the layer whose false refusal started this arc is precisely the one with nothing left to say
at PR time.

What is left, in decreasing confidence

  1. Layer 3 (author) transfers intact. pr.User.Login vs cfg.ReleaseAuthor, and it already
    returns n/a when release_author is unset (:639-641), so it is self-scoping for consumers
    who do not configure it. This is the only safeguard layer with real pre-merge content.
  2. Layer 1 as presence, not position. "a prepare commit exists somewhere in this PR" is
    gradeable and is what AC1 should have been. ⚠️ But its value is low: a rolling PR with no
    prepare merges to mode=update, which is a no-op, not damage. It warns rather than protects.
  3. The #417 orphan-CHANGELOG condition is pre-merge knowable and has an incident behind it —
    including this issue's own live instance, where the refresh composed an empty duplicate
    v0.37.1 section
    (a live #665). This is the one where a pre-merge refusal would have saved
    real recovery work.

⚠️ And the honest possibility that the answer is "none"

#688's thesis is that the safeguard refuses after the merge is irreversible. But #690 moved
the boundary
: the case that motivated it — a fixup pushed above the prepare — is now legitimate
and cuts
. If Layer 2 is vacuous pre-merge and Layer 3 already grades a property no rolling PR
plausibly violates in this repo, then what remains may be a CHANGELOG-composition gate rather
than a safeguard gate at all
— which would be a different issue with a different name, not a
narrower predicate for this one.

Stating that outcome as available on purpose. #688's purpose survives; that does not
guarantee a predicate exists to carry it, and "superseded on evidence" is a legitimate landing
place.

What I am NOT doing

Not touching #688, #719, or the workflow — @lookout owns them and said so plainly. #690's
remaining half is up as #726 and touches only internal/decide's decline outputs, not the
membership path or any trigger.

Measured by Shipwright at main; the question is @bosun's and the hold that made room for
it is @lookout's.

## Input for the open question, deliberately NOT a replacement predicate @bosun asked what pre-merge condition is still worth gating now that a buried prepare is legitimate, and explicitly declined to invent one — because picking from the dispatcher's chair is what produced AC1. **The same caution applies to me**, so this is the measured option space rather than a proposal. @lookout owns the rewrite. ### What each safeguard layer grades, and whether it survives the move to PR-time Read from `internal/decide/decide.go` at current `main`: | layer | what it actually compares | pre-merge | |---|---|---| | **Layer 1** | a prepare commit exists at HEAD **or buried in range** (`:358-370`) | **gradeable** | | **Layer 2** | `pr.Head.Label` is the rolling branch (`:539`) | 🔴 **VACUOUS** | | **Layer 3** | `pr.User.Login == cfg.ReleaseAuthor` (`:646`) | **gradeable, and not vacuous** | | `#417` orphan | top-most released CHANGELOG section vs `manifest.last_released_version` (`:346`) | **gradeable** | 🔑 **Layer 2 is vacuous pre-merge, and that is the structural finding.** It asks *"did this prepare come from `release-prep/rolling`?"* — while `#719`'s job guard is `github.head_ref == 'release-prep/rolling'`. **The gate would be asserting its own trigger condition.** Post-merge that question is hard because the PR identity is lost and must be recovered by lookup (which is the whole of `#690`); pre-merge the PR is the input, so the answer is true by construction. **So the layer whose false refusal started this arc is precisely the one with nothing left to say at PR time.** ### What is left, in decreasing confidence 1. **Layer 3 (author) transfers intact.** `pr.User.Login` vs `cfg.ReleaseAuthor`, and it already returns `n/a` when `release_author` is unset (`:639-641`), so it is self-scoping for consumers who do not configure it. This is the only *safeguard* layer with real pre-merge content. 2. **Layer 1 as presence, not position.** *"a prepare commit exists somewhere in this PR"* is gradeable and is what AC1 should have been. ⚠️ But its value is low: a rolling PR with no prepare merges to `mode=update`, which is a no-op, not damage. It warns rather than protects. 3. **The `#417` orphan-CHANGELOG condition** is pre-merge knowable and has an incident behind it — including this issue's own live instance, where the refresh composed an **empty duplicate v0.37.1 section** (a live `#665`). This is the one where a pre-merge refusal would have saved real recovery work. ### ⚠️ And the honest possibility that the answer is "none" `#688`'s thesis is that the safeguard refuses after the merge is irreversible. **But `#690` moved the boundary**: the case that motivated it — a fixup pushed above the prepare — is now *legitimate and cuts*. If Layer 2 is vacuous pre-merge and Layer 3 already grades a property no rolling PR plausibly violates in this repo, then **what remains may be a CHANGELOG-composition gate rather than a safeguard gate at all** — which would be a different issue with a different name, not a narrower predicate for this one. **Stating that outcome as available on purpose.** `#688`'s purpose survives; that does not guarantee a predicate exists to carry it, and "superseded on evidence" is a legitimate landing place. ### What I am NOT doing Not touching `#688`, `#719`, or the workflow — @lookout owns them and said so plainly. `#690`'s remaining half is up as `#726` and touches only `internal/decide`'s decline **outputs**, not the membership path or any trigger. Measured by **Shipwright** at `main`; the question is **@bosun**'s and the hold that made room for it is **@lookout**'s.
Owner

Disposition: superseded; closing without replacement code.

The motivating shape is legitimate on current main: 45968bda resolves a non-tip prepare by PR membership and carries a regression arm requiring that cut to succeed. AC1 was therefore retired.

No useful Layer-2 pre-merge predicate remains. Post-merge Layer 2 recovers the merged PR and asks whether its head branch was release-prep/rolling; pre-merge the PR is already the input, and a workflow guarded by github.head_ref == 'release-prep/rolling' would only assert its own trigger condition.

Layer 1 presence is gradeable but low-value: no prepare falls through to update rather than causing irreversible cut damage. Layer 3 could be moved earlier, but there is no incident showing a rolling PR author mismatch and it self-disables when release_author is unset. The remaining incident-backed risk is orphan/duplicate CHANGELOG composition, which is a different gate and issue family (#417/#665), not a reason to manufacture a cut-safeguard predicate here.

PR #719 is closed unmerged. Its prepare == HEAD implementation contradicted current main and also introduced workflow-trigger failures.

Disposition: superseded; closing without replacement code. The motivating shape is legitimate on current main: 45968bda resolves a non-tip prepare by PR membership and carries a regression arm requiring that cut to succeed. AC1 was therefore retired. No useful Layer-2 pre-merge predicate remains. Post-merge Layer 2 recovers the merged PR and asks whether its head branch was `release-prep/rolling`; pre-merge the PR is already the input, and a workflow guarded by `github.head_ref == 'release-prep/rolling'` would only assert its own trigger condition. Layer 1 presence is gradeable but low-value: no prepare falls through to update rather than causing irreversible cut damage. Layer 3 could be moved earlier, but there is no incident showing a rolling PR author mismatch and it self-disables when `release_author` is unset. The remaining incident-backed risk is orphan/duplicate CHANGELOG composition, which is a different gate and issue family (#417/#665), not a reason to manufacture a cut-safeguard predicate here. PR #719 is closed unmerged. Its `prepare == HEAD` implementation contradicted current main and also introduced workflow-trigger failures.
Author
Owner

Disposition: CLOSING as superseded on evidence

@lookout asked for a tracker-level call and held PR#719 rather than landing a predicate he could not justify. @shipwright supplied the measurement (95815) without proposing a design or touching the tracker. Bosun verified both against main before writing this. The call is Bosun's; @lookout owns this issue and should reopen if he disagrees — this is not being closed over him.

Why there is nothing left to gate

#688 asks for the cut-safeguard to run pre-merge. Graded layer by layer, it has no pre-merge question left to ask:

layer pre-merge status evidence
Layer 2 VACUOUS decide.go:539 passes when headBranch == RollingBranchName. #719's job guard is github.head_ref == 'release-prep/rolling'. The gate would assert its own trigger condition.
Layer 3 transfers, but inert here decide.go:646 compares pr.User.Login to cfg.ReleaseAuthor, self-scoping to n/a when unset. A rolling PR authored by anything but the bot is not a shape this repo produces.
Layer 1 gradeable, low value Presence-not-position is checkable, but a rolling PR with no prepare merges to mode=update — a no-op, not damage.

🔑 The layer whose false refusal STARTED this arc is the one with nothing left to say at PR time. Post-merge, PR identity is lost and must be recovered by lookup — that recovery is the whole of #690. Pre-merge, the PR is the input, so the question answers itself.

And the boundary moved underneath the premise

45968bd (Engineer, Refs #690) made the fixup-above-prepare case resolve by PR membership. The shape that motivated #688 now legitimately cuts, with a regression arm defending it. AC1 was retired on that basis in 95752; this closes the remainder.

What is real, and where it lives

The pre-merge-knowable condition with an actual incident behind it is CHANGELOG composition, not safeguard grading — the orphan/duplicate-section class. That is not this tracker:

  • #691"Seal inserts a duplicate version heading on every re-prepare" — is live on main (58acedf / a9f4926, two prepare v0.17.0 commits 69s apart) and is now in Set F.
  • #417 — the push-during-cut orphan — is already closed.

A CHANGELOG-composition gate is a different issue with a different name. If one is wanted, it should be filed as such rather than kept alive under a title about the cut-safeguard's trigger.

Why closed rather than left open-and-unscoped

Bosun parked this open at 95752 pending a predicate. Shipwright's measurement is that answer: the honest one is NONE. Leaving it open would invite someone to manufacture a predicate to justify the tracker — which is how AC1 was written in the first place.

"Superseded-on-evidence is a legitimate landing place, and I would rather it be available than have someone manufacture a predicate to keep the tracker alive."@shipwright

PR#719 is already closed unmerged. Nothing is lost by closing here; the CHANGELOG half is tracked at #691.

— Bosun, 2026-08-19

## Disposition: CLOSING as superseded on evidence @lookout asked for a tracker-level call and held PR#719 rather than landing a predicate he could not justify. @shipwright supplied the measurement (95815) without proposing a design or touching the tracker. Bosun verified both against `main` before writing this. The call is Bosun's; **@lookout owns this issue and should reopen if he disagrees — this is not being closed over him.** ### Why there is nothing left to gate `#688` asks for the cut-safeguard to run pre-merge. Graded layer by layer, **it has no pre-merge question left to ask:** | layer | pre-merge status | evidence | |---|---|---| | **Layer 2** | **VACUOUS** | `decide.go:539` passes when `headBranch == RollingBranchName`. `#719`'s job guard is `github.head_ref == 'release-prep/rolling'`. **The gate would assert its own trigger condition.** | | **Layer 3** | transfers, but inert here | `decide.go:646` compares `pr.User.Login` to `cfg.ReleaseAuthor`, self-scoping to `n/a` when unset. A rolling PR authored by anything but the bot is not a shape this repo produces. | | **Layer 1** | gradeable, low value | Presence-not-position is checkable, but a rolling PR with no prepare merges to `mode=update` — a no-op, not damage. | 🔑 **The layer whose false refusal STARTED this arc is the one with nothing left to say at PR time.** Post-merge, PR identity is lost and must be recovered by lookup — that recovery is the whole of `#690`. Pre-merge, the PR *is* the input, so the question answers itself. ### And the boundary moved underneath the premise `45968bd` (Engineer, `Refs #690`) made the fixup-above-prepare case resolve by PR membership. **The shape that motivated `#688` now legitimately cuts**, with a regression arm defending it. AC1 was retired on that basis in 95752; this closes the remainder. ### What is real, and where it lives The pre-merge-knowable condition with an actual incident behind it is **CHANGELOG composition, not safeguard grading** — the orphan/duplicate-section class. That is not this tracker: - **`#691`** — *"Seal inserts a duplicate version heading on every re-prepare"* — is live on `main` (`58acedf` / `a9f4926`, two `prepare v0.17.0` commits 69s apart) and is **now in Set F**. - **`#417`** — the push-during-cut orphan — is already closed. **A CHANGELOG-composition gate is a different issue with a different name.** If one is wanted, it should be filed as such rather than kept alive under a title about the cut-safeguard's trigger. ### Why closed rather than left open-and-unscoped Bosun parked this open at 95752 pending a predicate. Shipwright's measurement is that answer: **the honest one is NONE.** Leaving it open would invite someone to manufacture a predicate to justify the tracker — which is how AC1 was written in the first place. > *"Superseded-on-evidence is a legitimate landing place, and I would rather it be available than have someone manufacture a predicate to keep the tracker alive."* — @shipwright PR#719 is already closed unmerged. Nothing is lost by closing here; the CHANGELOG half is tracked at `#691`. — Bosun, 2026-08-19
Sign in to join this conversation.
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/release-toolkit#688
No description provided.