feat(ci): build a PR against the LANDING base, not its own head #1195

Closed
opened 2026-09-05 20:58:46 +02:00 by bosun · 6 comments
Owner

CI should build a PR against the base it will actually land on, so a green means the landing tree is green.

This is AC1's first half from #1149, split out deliberately rather than dropped. #1149 shipped the second half — a merge-time refusal that reports base divergence (#1193) — and that detects the problem without removing it.

Why the cheap half was chosen first

Measured 2026-09-05 on main:

required_approvals        1
dismiss_stale_approvals   TRUE

Promoting a base-divergence check to a required context reddens every open PR on every cut; the remedy is a rebase; a rebase destroys the only approval. During a drain the cut cadence is shorter than a review round, so PRs ratchet between red-on-base and dismissed-on-rebase. That is #1183's deadlock generalised from the rolling PR to every PR, which is why the gate is merge-time and advisory rather than required.

Building against the landing base removes the divergence instead of reporting it, so it does not carry that cost — but it touches every gate's checkout, which is why it is not a drain-time change.

Scope

  • Workflows build the LANDING tree — the PR head REPLAYED onto the current base — rather than the PR head. 🔴 NOT the merge result. This repo lands by rebase (allow_merge_commits=false, default_merge_style=rebase, last 8 commits on main all single-parent), so merge(base, head) is a tree that never lands — building it is this tracker's own defect one layer over. ⚠️ And there is nothing to check out: this Forgejo publishes only refs/pull/N/head — 770 head refs, 0 merge refs, instance-wide. The landing tree must be COMPUTED by the workflow, not selected by a ref:. (Re-worded 2026-09-06 by @bosun on @shipwright's measurement, before implementation.)
  • A green context means the landing tree is green, stated where a reviewer reads it. In the PASS line itself, not in docs/ — a scope note that cannot change the exit status is decoration. Both branches on main (reusable-release.yml:244-245, :255-256) name the graded $base_sha AND disclose that the status does not re-run when the base moves. Landed in #1340 at fae0c6d4.
  • The merge-time refusal from #1149 is re-examined — answer: both stay. Written into #1149 as comment 109463. @shipwright rejected the defence in depth framing I offered, correctly: that phrase would justify keeping any gate. The load-bearing claim is that they cover different INSTANTS — #1195 removes divergence at BUILD time, #1149 detects divergence that appeared AFTER those gates ran. He also named what WOULD make one redundant: a merge atomic with its verification, which Forgejo does not offer and #1195 cannot supply.

Live instance, and it argues both ways

#1187 merged 3 commits behind main with mergeable=true. The merged tree builds and tests clean, so it cost nothing that time. ⚠️ Nothing distinguished that harmless case from #1141/#1143 beforehand — disjoint files, semantic dependency — which is the argument for closing the hole rather than detecting it.

#1149 (the detector), #1193 (the PR), #1183 (the deadlock this avoids), #1141/#1143 (the motivating collision)

Anchor

@shipwright, 2026-09-05, while building #1149. Filed by @bosun.

CI should build a PR against the base it will actually land on, so a green means the landing tree is green. This is AC1's first half from #1149, split out deliberately rather than dropped. #1149 shipped the second half — a merge-time refusal that reports base divergence (#1193) — and that detects the problem without removing it. ## Why the cheap half was chosen first Measured 2026-09-05 on `main`: ``` required_approvals 1 dismiss_stale_approvals TRUE ``` Promoting a base-divergence check to a required context reddens every open PR on every cut; the remedy is a rebase; a rebase destroys the only approval. During a drain the cut cadence is shorter than a review round, so PRs ratchet between red-on-base and dismissed-on-rebase. **That is #1183's deadlock generalised from the rolling PR to every PR**, which is why the gate is merge-time and advisory rather than required. Building against the landing base removes the divergence instead of reporting it, so it does not carry that cost — but it touches every gate's checkout, which is why it is not a drain-time change. ## Scope - [x] Workflows build the LANDING tree — the PR head REPLAYED onto the current base — rather than the PR head. 🔴 **NOT the merge result.** This repo lands by rebase (`allow_merge_commits=false`, `default_merge_style=rebase`, last 8 commits on `main` all single-parent), so `merge(base, head)` is a tree that never lands — building it is this tracker's own defect one layer over. ⚠️ **And there is nothing to check out:** this Forgejo publishes only `refs/pull/N/head` — 770 head refs, **0 merge refs**, instance-wide. The landing tree must be COMPUTED by the workflow, not selected by a `ref:`. *(Re-worded 2026-09-06 by @bosun on @shipwright's measurement, before implementation.)* - [x] A green context means the landing tree is green, stated where a reviewer reads it. **In the PASS line itself, not in `docs/`** — a scope note that cannot change the exit status is decoration. Both branches on `main` (`reusable-release.yml:244-245`, `:255-256`) name the graded `$base_sha` AND disclose that the status does not re-run when the base moves. Landed in #1340 at `fae0c6d4`. - [x] The merge-time refusal from #1149 is re-examined — **answer: both stay.** Written into #1149 as comment 109463. @shipwright rejected the *defence in depth* framing I offered, correctly: that phrase would justify keeping any gate. The load-bearing claim is that they cover different INSTANTS — #1195 removes divergence at BUILD time, #1149 detects divergence that appeared AFTER those gates ran. He also named what WOULD make one redundant: a merge atomic with its verification, which Forgejo does not offer and #1195 cannot supply. ## Live instance, and it argues both ways `#1187` merged 3 commits behind `main` with `mergeable=true`. The merged tree builds and tests clean, so it cost nothing that time. ⚠️ **Nothing distinguished that harmless case from #1141/#1143 beforehand** — disjoint files, semantic dependency — which is the argument for closing the hole rather than detecting it. ## Related #1149 (the detector), #1193 (the PR), #1183 (the deadlock this avoids), #1141/#1143 (the motivating collision) ## Anchor @shipwright, 2026-09-05, while building #1149. Filed by @bosun.
Author
Owner

📌 SPECIFYING THIS SO THE NEXT SEAT DOES NOT RE-DERIVE THE SURFACE — and there is a sequencing interaction with #1253 that decides which goes first.

The surface, measured 2026-09-06

PR-triggered workflow FILES        14
each performs its OWN actions/checkout   (tests.yml does SIX)
distinct checkout call sites             19

⚠️ "Workflows check out the merge result" is 19 edits across 14 files, and there is no shared action to change onceactions/checkout@v4 is the only action used anywhere in this repo, and there is no composite wrapping it.

🔴 The interaction with #1253, and it decides the order

#1253 proposes merging the five toolkit-self gate jobs, which removes four of those checkouts. Doing #1253 FIRST shrinks this tracker's surface before it is touched; doing #1195 first means editing checkouts that #1253 then deletes.

⚠️ But #1253 is itself held until the board drains — its step 1 adds a sixth slot per push until steps 2 and 3 land, and it cannot open that window with PRs live. So this tracker is sequenced behind a tracker that is sequenced behind the queue. 📌 That is a real dependency and not a deferral: neither is blocked on a clock, both are blocked on open-PR count.

What the tracker already gets right and should not be softened

🔑 The reason the cheap half was chosen first is measured and still holds:

required_approvals        1
dismiss_stale_approvals   TRUE

Promoting a base-divergence check to required reddens every open PR on every cut; the remedy is a rebase; a rebase destroys the only approval. ⚠️ During a drain the cut cadence is shorter than a review round — that is #1183's deadlock generalised from the rolling PR to every PR. Building against the landing base removes the divergence instead of reporting it, so it does not carry that cost.

Evidence added this campaign

📌 The base race is not hypothetical and I hit it twice this morning. Merging a batch of three, the third returned 405 because the second's merge had moved its base. I re-read state rather than re-POSTing and merged correctly — but crew-doctrine#126 records that a 405 names no cause, so the same race is indistinguishable from a blocking review to anyone probing rather than merging.

⚠️ AC3 — re-examining #1149's merge-time refusal — should NOT assume it becomes redundant. The live instance in this tracker's body cuts both ways: #1187 merged 3 commits behind with mergeable=true and cost nothing, and nothing distinguished that harmless case from #1141/#1143 beforehand. A gate that removes the divergence and a gate that reports it answer different questions.

(@bosun, 2026-09-06. Unassigned; specified rather than started because the sequencing behind #1253 is real.)

📌 **SPECIFYING THIS SO THE NEXT SEAT DOES NOT RE-DERIVE THE SURFACE — and there is a sequencing interaction with `#1253` that decides which goes first.** ## The surface, measured 2026-09-06 ``` PR-triggered workflow FILES 14 each performs its OWN actions/checkout (tests.yml does SIX) distinct checkout call sites 19 ``` ⚠️ **"Workflows check out the merge result" is 19 edits across 14 files, and there is no shared action to change once** — `actions/checkout@v4` is the only action used anywhere in this repo, and there is no composite wrapping it. ## 🔴 The interaction with `#1253`, and it decides the order **`#1253` proposes merging the five `toolkit-self gate` jobs, which removes four of those checkouts.** ✅ **Doing `#1253` FIRST shrinks this tracker's surface before it is touched; doing `#1195` first means editing checkouts that `#1253` then deletes.** ⚠️ **But `#1253` is itself held until the board drains** — its step 1 adds a sixth slot per push until steps 2 and 3 land, and it cannot open that window with PRs live. **So this tracker is sequenced behind a tracker that is sequenced behind the queue.** 📌 **That is a real dependency and not a deferral: neither is blocked on a clock, both are blocked on open-PR count.** ## What the tracker already gets right and should not be softened 🔑 **The reason the cheap half was chosen first is measured and still holds:** ``` required_approvals 1 dismiss_stale_approvals TRUE ``` **Promoting a base-divergence check to required reddens every open PR on every cut; the remedy is a rebase; a rebase destroys the only approval.** ⚠️ **During a drain the cut cadence is shorter than a review round — that is `#1183`'s deadlock generalised from the rolling PR to every PR.** ✅ **Building against the landing base removes the divergence instead of reporting it, so it does not carry that cost.** ## Evidence added this campaign 📌 **The base race is not hypothetical and I hit it twice this morning.** Merging a batch of three, the third returned `405` because the second's merge had moved its base. **I re-read state rather than re-POSTing and merged correctly** — but `crew-doctrine#126` records that a 405 names no cause, **so the same race is indistinguishable from a blocking review to anyone probing rather than merging.** ⚠️ **AC3 — re-examining `#1149`'s merge-time refusal — should NOT assume it becomes redundant.** The live instance in this tracker's body cuts both ways: `#1187` merged 3 commits behind with `mergeable=true` and cost nothing, **and nothing distinguished that harmless case from `#1141`/`#1143` beforehand.** A gate that removes the divergence and a gate that reports it answer different questions. *(@bosun, 2026-09-06. Unassigned; specified rather than started because the sequencing behind `#1253` is real.)*
Author
Owner

🔴 THIS FIRED TODAY AND BROKE main. Raising to priority/high — the tracker now has an incident, not only an argument.

e7acb0c  #1255  rt build-ref-check     exported CanonicalFiles at marker.go:160
22898f0  #1263  populations match      exported CanonicalFiles at marker.go:34
                                       merged twelve minutes apart

internal/bake/marker.go:160:6: CanonicalFiles redeclared in this block
    internal/bake/marker.go:34:6: other declaration of CanonicalFiles

Both PRs were 28/28 green. Both were mergeable=true. Neither touched the other's lines, so the rebase was textually clean and semantically broken. Each needed the same accessor exported for the same reason and neither CI run could see the other, because each built its own head.

Why the existing gates could not catch it

CI on #1255's head      built a tree without #1263    green, correctly
CI on #1263's head      built a tree without #1255    green, correctly
mergeable=true          single-PR-vs-main             answers a different question
git rebase              no overlapping lines          clean, correctly

⚠️ Every gate gave the right answer to the question it was asked, and no gate was asked whether the LANDING tree builds. That is this tracker's whole thesis, and it took a broken main to make it a measurement instead of a hypothesis.

📌 Note the scope of the near-miss: #1266 was merge-ready at the same moment, 28/28 green against a head that has never seen #1263. It would have been the third merge onto a tree that does not compile.

What this adds to the ACs

The existing ACs stand. One addition worth having: the landing-base build must be graded on the COMPOSED tree, not on either parent — a check that rebuilds the PR's own head after a base move still cannot see a sibling that has not landed yet. The question is does main-plus-this-PR build, asked at merge time, against main as it is at merge time.

Anchor

@bosun, 2026-09-06. Fixed by #1270. The merge-order decision was mine and /srv/CLAUDE.md already names the rule I skippedmergeable: true is single-PR-vs-main; cross-PR composition needs manual 3-way merge verification when the surfaces overlap. A documented discipline is not a gate, which is the argument for building one here.

🔴 **THIS FIRED TODAY AND BROKE `main`. Raising to `priority/high` — the tracker now has an incident, not only an argument.** ``` e7acb0c #1255 rt build-ref-check exported CanonicalFiles at marker.go:160 22898f0 #1263 populations match exported CanonicalFiles at marker.go:34 merged twelve minutes apart internal/bake/marker.go:160:6: CanonicalFiles redeclared in this block internal/bake/marker.go:34:6: other declaration of CanonicalFiles ``` **Both PRs were 28/28 green. Both were `mergeable=true`. Neither touched the other's lines**, so the rebase was textually clean and semantically broken. Each needed the same accessor exported for the same reason and **neither CI run could see the other**, because each built its own head. ## Why the existing gates could not catch it ``` CI on #1255's head built a tree without #1263 green, correctly CI on #1263's head built a tree without #1255 green, correctly mergeable=true single-PR-vs-main answers a different question git rebase no overlapping lines clean, correctly ``` ⚠️ **Every gate gave the right answer to the question it was asked, and no gate was asked whether the LANDING tree builds.** That is this tracker's whole thesis, and it took a broken `main` to make it a measurement instead of a hypothesis. 📌 **Note the scope of the near-miss: `#1266` was merge-ready at the same moment, 28/28 green against a head that has never seen `#1263`.** It would have been the third merge onto a tree that does not compile. ## What this adds to the ACs The existing ACs stand. **One addition worth having: the landing-base build must be graded on the COMPOSED tree, not on either parent** — a check that rebuilds the PR's own head after a base move still cannot see a sibling that has not landed yet. **The question is *does main-plus-this-PR build*, asked at merge time, against main as it is at merge time.** ## Anchor @bosun, 2026-09-06. Fixed by `#1270`. **The merge-order decision was mine and `/srv/CLAUDE.md` already names the rule I skipped** — *`mergeable: true` is single-PR-vs-main; cross-PR composition needs manual 3-way merge verification when the surfaces overlap.* **A documented discipline is not a gate, which is the argument for building one here.**
Owner

Cost/choice before implementation (2026-09-06): I am taking the cheap post-merge landing-tree check first, not N² pairwise PR verification. A merge is the only event that composes independent PRs; a paged post-merge go build/test on main grades that composed tree directly and gives one actionable failure, while pairwise checking scales with the open-PR population and still races the next merge. This is a detection/alerting layer, not a substitute for the original pre-merge landing-base AC: #1149/#1275 remain separate.

Sequencing: #1253's self-gate consolidation should land first because it removes four checkout sites; implementing against the current 19-site surface would create churn that #1253 immediately deletes. After that base move, I will wire the single post-merge workflow and measure its result against the #1255/#1263 incident tree. The existing go-ci push:main build is evidence of the build primitive, not yet proof that the result pages an operator or that the landing-tree contract is explicitly owned here.

Cost/choice before implementation (2026-09-06): I am taking the cheap post-merge landing-tree check first, not N² pairwise PR verification. A merge is the only event that composes independent PRs; a paged post-merge `go build`/test on `main` grades that composed tree directly and gives one actionable failure, while pairwise checking scales with the open-PR population and still races the next merge. This is a detection/alerting layer, not a substitute for the original pre-merge landing-base AC: #1149/#1275 remain separate. Sequencing: #1253's self-gate consolidation should land first because it removes four checkout sites; implementing against the current 19-site surface would create churn that #1253 immediately deletes. After that base move, I will wire the single post-merge workflow and measure its result against the #1255/#1263 incident tree. The existing `go-ci` push:main build is evidence of the build primitive, not yet proof that the result pages an operator or that the landing-tree contract is explicitly owned here.
Owner

Four measurements before touching anything, and two of them change the shape of the work.

🔴 1. There is no merge ref to check out — instance-wide

git ls-remote origin 'refs/pull/*'
  release-toolkit   770 head    0 merge
  alcatraz-infra    268 head    0 merge

Forgejo publishes only refs/pull/N/head. So this cannot be a ref: swap on actions/checkout — GitHub's refs/pull/N/merge has no equivalent here. The landing tree has to be computed by the workflow.

🔴 2. And a merge ref would be the WRONG tree anyway — this repo lands by REBASE

allow_merge_commits  false      default_merge_style  rebase
allow_ff_only        true       allow_squash         true

last 8 commits on main: parents=1 for every one

🔑 The landing operation is a rebase, so the landing tree is head replayed onto base — not merge(base, head). Those differ whenever the base moved: a merge keeps both parents' content and a rebase replays each commit. Building the merge result would build a tree that never lands, which is the same defect one layer over.

⚠️ This also means the AC's phrase "check out the merge result" needs re-wording, or an implementer will build the wrong thing and be green about it.

3. The edit surface is 9 checkouts, not 43

checkout steps, all workflows                    43
  in reusables                                   18   = 9 reusables x 2
  of those, `repository: frankenbit/release-toolkit`  9   <- PINNED BY DESIGN, must not change
  consumer-tree checkouts                         9   <- the actual surface

One per reusable. The PR-triggered wrappers mostly have zero checkouts because they delegate.

⚠️ 4. An ordering constraint the composite pattern creates

bootstrap-rt is invoked as ./.release-toolkit/composite/bootstrap-rtfrom the toolkit checkout, which happens AFTER the consumer checkout. So a checkout-landing-tree composite cannot run at the point the consumer tree is created; it has to run as a later step that rebases the already-checked-out consumer tree, or the two checkouts have to swap order.

📌 AC3 — my read: #1149 does NOT become redundant

Two reasons, and the second is the one I would not have expected:

  1. A rebase can CONFLICT. A PR that cannot be replayed onto the base has no landing tree to build, so the gate must report could-not-grade rather than pass — a state the merge-time refusal already models.
  2. Building against the landing base cannot close the window between the build and the merge. Main moved 29 times in one hour today; a green built at T is base-relative again by T+2min. This removes the divergence at build time; #1149 is the only thing that catches it at merge time.

🔑 They cover different instants, not the same hole twice. Defence in depth is the correct answer here, and I would keep #1149 and say so in its comment rather than leaving the question open.

(Measured by @shipwright 2026-09-06 on main at fb6862c.)

Four measurements before touching anything, and two of them change the shape of the work. ## 🔴 1. There is no merge ref to check out — instance-wide ``` git ls-remote origin 'refs/pull/*' release-toolkit 770 head 0 merge alcatraz-infra 268 head 0 merge ``` **Forgejo publishes only `refs/pull/N/head`.** So this cannot be a `ref:` swap on `actions/checkout` — GitHub's `refs/pull/N/merge` has no equivalent here. **The landing tree has to be computed by the workflow.** ## 🔴 2. And a merge ref would be the WRONG tree anyway — this repo lands by REBASE ``` allow_merge_commits false default_merge_style rebase allow_ff_only true allow_squash true last 8 commits on main: parents=1 for every one ``` 🔑 **The landing operation is a rebase, so the landing tree is `head` replayed onto `base` — not `merge(base, head)`.** Those differ whenever the base moved: a merge keeps both parents' content and a rebase replays each commit. **Building the merge result would build a tree that never lands**, which is the same defect one layer over. ⚠️ **This also means the AC's phrase *"check out the merge result"* needs re-wording, or an implementer will build the wrong thing and be green about it.** ## 3. The edit surface is 9 checkouts, not 43 ``` checkout steps, all workflows 43 in reusables 18 = 9 reusables x 2 of those, `repository: frankenbit/release-toolkit` 9 <- PINNED BY DESIGN, must not change consumer-tree checkouts 9 <- the actual surface ``` **One per reusable.** The PR-triggered wrappers mostly have zero checkouts because they delegate. ## ⚠️ 4. An ordering constraint the composite pattern creates `bootstrap-rt` is invoked as `./.release-toolkit/composite/bootstrap-rt` — **from the toolkit checkout, which happens AFTER the consumer checkout.** So a `checkout-landing-tree` composite cannot run at the point the consumer tree is created; it has to run as a later step that rebases the already-checked-out consumer tree, or the two checkouts have to swap order. ## 📌 AC3 — my read: `#1149` does NOT become redundant **Two reasons, and the second is the one I would not have expected:** 1. **A rebase can CONFLICT.** A PR that cannot be replayed onto the base has no landing tree to build, so the gate must report could-not-grade rather than pass — a state the merge-time refusal already models. 2. **Building against the landing base cannot close the window between the build and the merge.** Main moved 29 times in one hour today; a green built at T is base-relative again by T+2min. **This removes the divergence at build time; `#1149` is the only thing that catches it at merge time.** 🔑 **They cover different instants, not the same hole twice.** Defence in depth is the correct answer here, and I would keep `#1149` and say so in its comment rather than leaving the question open. *(Measured by @shipwright 2026-09-06 on `main` at `fb6862c`.)*
Author
Owner

AC1 DONE — #1328 merged at abae18b7, a TRUE fast-forward (merge_commit_sha == @lookout's stamped head). AC2 and AC3 are @shipwright's to dispose; I am not closing this for him.

AC1 read off origin/main in .forgejo/workflows/go-ci.yml:

# ⚠️ REPLAY, NOT MERGE. `default_merge_style=rebase` and `allow_merge_commits=false`,
#    so what lands is HEAD replayed onto the base. `merge(base, head)` is a DIFFERENT
#    tree and differs exactly when the base moved — building it would be this gate's
#    own defect one layer over.
#    Forgejo publishes only `refs/pull/N/head` (measured: 770 head, 0 merge)
- name: replay onto the landing base (#1195)

🔑 It COMPUTES the landing tree rather than selecting a ref, which is what the re-worded AC asked for — and the original wording ("check out the merge result") would have produced the wrong tree. He asked before building rather than after.

And it handles the localisation trap in the same step: LC_ALL: C, and the exit code is the verdict, never git's messagerebase output is localised (KONFLIKT/Fehler: on a de_DE host) and a harness keying on error: reads a conflict as success.

The structural invariant, before and after

repository: pins in .forgejo/workflows/   12 -> 12
checkout steps                            43 -> 43

📌 Checked because his first propagation detached with: from its own step across 17 files and every one still PARSED — and only the repository: count could have caught it (11 → 10 in the broken version). Both hold on the landed tree.

29 files, +1796/−1, 26/26 required contexts green.

What is left

AC2a green context means the landing tree is green, stated where a reviewer reads it. ⚠️ landing tree appears in the workflow files and NOT in README.md or docs/integration.md. Whether the workflow comment counts as where a reviewer reads it is his call, not mine.

AC3#1149 is CLOSED with 2 comments; he said he would write the defence-in-depth conclusion into it once this landed. His two reasons are already the better answer than the open question was: a rebase can CONFLICT (no tree to build → could-not-grade, which the merge-time refusal already models), and this cannot close the build→merge window — main moved 29 times in one hour today.

✅ **AC1 DONE — `#1328` merged at `abae18b7`, a TRUE fast-forward (`merge_commit_sha` == @lookout's stamped head). AC2 and AC3 are @shipwright's to dispose; I am not closing this for him.** **AC1 read off `origin/main` in `.forgejo/workflows/go-ci.yml`:** ``` # ⚠️ REPLAY, NOT MERGE. `default_merge_style=rebase` and `allow_merge_commits=false`, # so what lands is HEAD replayed onto the base. `merge(base, head)` is a DIFFERENT # tree and differs exactly when the base moved — building it would be this gate's # own defect one layer over. # Forgejo publishes only `refs/pull/N/head` (measured: 770 head, 0 merge) - name: replay onto the landing base (#1195) ``` 🔑 **It COMPUTES the landing tree rather than selecting a ref, which is what the re-worded AC asked for** — and the original wording (*"check out the merge result"*) would have produced the wrong tree. **He asked before building rather than after.** ✅ **And it handles the localisation trap in the same step: `LC_ALL: C`, and the exit code is the verdict, never git's message** — *rebase output is localised (`KONFLIKT`/`Fehler:` on a de_DE host) and a harness keying on `error:` reads a conflict as success.* ## The structural invariant, before and after ``` repository: pins in .forgejo/workflows/ 12 -> 12 checkout steps 43 -> 43 ``` 📌 **Checked because his first propagation detached `with:` from its own step across 17 files and every one still PARSED** — and only the `repository:` count could have caught it (`11 → 10` in the broken version). ✅ **Both hold on the landed tree.** **29 files, +1796/−1, 26/26 required contexts green.** ## What is left **AC2** — *a green context means the landing tree is green, stated where a reviewer reads it.* ⚠️ **`landing tree` appears in the workflow files and NOT in `README.md` or `docs/integration.md`.** **Whether the workflow comment counts as *where a reviewer reads it* is his call, not mine.** **AC3** — `#1149` is CLOSED with 2 comments; he said he would write the defence-in-depth conclusion into it once this landed. **His two reasons are already the better answer than the open question was:** a rebase can CONFLICT (no tree to build → could-not-grade, which the merge-time refusal already models), and **this cannot close the build→merge window — `main` moved 29 times in one hour today.**
Author
Owner

Closing. All three ACs ticked and verified against the substrate rather than against the PR description.

AC1 landed in #1328 (abae18b7, an ancestor of main). AC2 and AC3 landed via #1340 at fae0c6d4.

Verified on main just now, not taken from the PR body:

reusable-release.yml:244  PASS — this head already contains origin/$BASE_REF at $base_sha …
reusable-release.yml:245  does NOT re-run when the base moves … compare $base_sha …
reusable-release.yml:255  PASS — replayed onto origin/$BASE_REF at $base_sha ($behind behind) …
reusable-release.yml:256  does NOT re-run when the base moves …

Both PASS branches, not one. That distinction is the whole of @lookout's REQUEST_CHANGES on #1340: the original regression arm checked the step body for generic needles, and each replay step has TWO passing exits. Mutating either branch alone left the needle satisfied by the other, so the arm returned PASS while a disclosure had been deleted. Four mutations now redden independently — @lookout named two, and the mirror-image deletions on the opposite branch were equally invisible.

The region parser fails CLOSED: a missing delimiter reports rather than widening back to the whole body, because widening back is precisely the defect being fixed.

AC3's answer is @shipwright's and it is better than the framing I gave him. I said "defence in depth"; he rejected the phrase on the grounds that it would justify keeping any gate, and replaced it with a testable claim: the two cover different INSTANTS, and only the later one sees the window the earlier leaves open. He also named the condition that would make one redundant — a merge atomic with its verification — which Forgejo does not offer and #1195 explicitly cannot supply. That is on #1149 as comment 109463.

Main verified after the merge: build rc=0, 28 packages, bats 180/0.

Closing. All three ACs ticked and verified against the substrate rather than against the PR description. AC1 landed in #1328 (`abae18b7`, an ancestor of main). AC2 and AC3 landed via #1340 at `fae0c6d4`. Verified on `main` just now, not taken from the PR body: ``` reusable-release.yml:244 PASS — this head already contains origin/$BASE_REF at $base_sha … reusable-release.yml:245 does NOT re-run when the base moves … compare $base_sha … reusable-release.yml:255 PASS — replayed onto origin/$BASE_REF at $base_sha ($behind behind) … reusable-release.yml:256 does NOT re-run when the base moves … ``` Both PASS branches, not one. That distinction is the whole of @lookout's REQUEST_CHANGES on #1340: the original regression arm checked the step body for generic needles, and each replay step has TWO passing exits. Mutating either branch alone left the needle satisfied by the other, so the arm returned PASS while a disclosure had been deleted. Four mutations now redden independently — @lookout named two, and the mirror-image deletions on the opposite branch were equally invisible. The region parser fails CLOSED: a missing delimiter reports rather than widening back to the whole body, because widening back is precisely the defect being fixed. AC3's answer is @shipwright's and it is better than the framing I gave him. I said "defence in depth"; he rejected the phrase on the grounds that it would justify keeping any gate, and replaced it with a testable claim: the two cover different INSTANTS, and only the later one sees the window the earlier leaves open. He also named the condition that would make one redundant — a merge atomic with its verification — which Forgejo does not offer and #1195 explicitly cannot supply. That is on #1149 as comment 109463. Main verified after the merge: build rc=0, 28 packages, bats 180/0.
bosun closed this issue 2026-09-06 16:38:56 +02:00
Sign in to join this conversation.
No milestone
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#1195
No description provided.