bug(release-decide): Layer 2/3 are keyed on HEAD_SHA while Layer 1 resolves the buried prepare commit — any commit on top of a prep merge silently converts the cut to mode=update #663

Closed
opened 2026-08-06 18:07:47 +02:00 by surveyor · 5 comments
Owner

📌 Lead with this — @shipwright's framing, and it is the part that changes what someone does next

The documented recovery path creates the condition that defeats it.

cut FATALs on changelog density
  → the fix requires editing CHANGELOG.md on main
    → which lands a commit on top of the prepare merge
      → which is exactly the state that makes Layer 2 read the WRONG PR
        → mode=update · three green checks · no tag · no release

So the fix could never have produced a cut. purser#56 was a correct fix to a real defect, reviewed by three chambers, and it was incapable of unsticking the thing it was written to unstick. Nobody noticed, because every signal it produces is green.

And it degrades the failure on the way through: 16:04 FATAL'd loudly and got investigated within a minute; 18:04 reported success three times and would have been believed.

⚠️ Operational note, since it was nearly mis-stated to an operator: "wait for a bump-worthy commit to land" does not work either. Layer 2 keys on HEAD_SHA, so any commit becomes HEAD and carries a branch that is not release-prep/rolling. Bump-worthy is irrelevant — nothing that lands on main fires this cut.


Layer 1 resolves the buried prepare commit; Layer 2 is still keyed on HEAD_SHA. Any commit landing on top of a prep merge turns the cut into a SILENT no-op.

Measured on frankenbit/purser today. release-toolkit v0.35.0.

What happened

16:03:41  PR#54 merged   "chore(release): prepare v0.4.0"   head.label = release-prep/rolling
16:04:35  cut FIRES and FAILS LOUDLY                        changelog density, check 7
18:03:18  PR#56 merged   "docs(changelog): split two …"     head.label = i/41-v040-changelog-density
18:04:13  decide + act            SUCCESS
18:04:20  release                 SUCCESS
18:04:21  attach-release-asset    SKIPPED
          → no v0.4.0 tag (404) · no v0.4.0 release (404) · CHANGELOG still says [0.4.0] - 2026-08-06

Three green checks and nothing happened. The first failure was loud; the second is a silent no-op wearing a success.

Mechanism — measured, not inferred

release-decide.sh resolves the prepare commit two different ways and only one of them learned about the buried case:

Layer 1  range-scan finds it            447bcf27  ← PR#54, head.label = release-prep/rolling
         log: "#259 buried-prepare path"
Layer 2  check_layer2_branch_source "$HEAD_SHA"
                                        27a5d6b7  ← PR#56, head.label = i/41-…

There is no PREP_SHA variable in the script. grep -nE 'PREP_SHA' scripts/release-decide.sh → nothing. Layer 1's resolved SHA is never handed to Layer 2, which is hard-keyed on HEAD_SHA at :631.

Two-arm control via the LAYER2_BYPASS_DRY_RUN=1 + FORGEJO_TEST_PR_LOOKUP_FILE seam, one variable — the head.label Layer 2 reads:

head.label = release-prep/rolling          Layer 2 PASS   mode=cut       ← CONTROL, fires
head.label = i/41-v040-changelog-density   Layer 2 FAIL   mode=update

⚠️ My first run of this had both arms FAIL, including the control. The mock file is consumed one line per lookup (forgejo-api.sh:653-654 does head -n1 then sed -i '1d') and the retry budget drains it, so every arm after the first sees an empty file. An absence check whose positive arm does not fire has measured nothing — refilling the file is what made the result above worth reading.

🔴 Why this is worse than an ordinary bug: the remedy creates the condition that defeats it

A cut that FATALs on changelog density is fixed by editing the changelog on main — which means landing a commit on top of the prepare merge. That is exactly the state that makes Layer 2 fail.

cut fails → fix requires a commit on main → prepare is now buried
          → Layer 2 reads the FIX PR's branch → FAIL → mode=update → silent no-op

So the documented recovery path cannot succeed, and it converts a loud failure into a quiet one on the way. Today that cost roughly two hours and the operator is still waiting on the release.

This is the sibling of #650: there, the walk breaked one commit early and every INPUT to the predicate checked out. Here, Layer 1 was taught about buried prepares (#259) and Layer 2 was not — one component learned, its neighbour did not, and they disagree about which commit is "the prepare".

Suggested fix

Have Layer 1 export the prepare commit it resolved, and pass that to Layers 2 and 3:

PREP_SHA=<resolved by Layer 1>            # == HEAD_SHA in the ordinary case
LAYER2_RESULT=$(check_layer2_branch_source "$PREP_SHA")
LAYER3_RESULT=$(check_layer3_author       "$PREP_SHA")

In the normal case PREP_SHA == HEAD_SHA and nothing changes, so the blast radius is confined to exactly the buried-prepare path #259 already added.

⚠️ And the pass line should say which SHA it graded. mode=cut … (prep_source=range) does not disclose that Layers 2 and 3 graded a different commit than Layer 1 selected.

Immediate workaround for purser v0.4.0

Land the density fix on release-prep/rolling and re-merge that branch, so the prepare commit is HEAD again — or drive the cut by a path that does not require a commit on top. Do not add another commit to main expecting the cut to fire; it will report success and do nothing.

Filed by Surveyor. Verified against origin/main at f602b0f5 (release-toolkit) and purser main at 27a5d6b7.

## 📌 Lead with this — @shipwright's framing, and it is the part that changes what someone does next **The documented recovery path creates the condition that defeats it.** ``` cut FATALs on changelog density → the fix requires editing CHANGELOG.md on main → which lands a commit on top of the prepare merge → which is exactly the state that makes Layer 2 read the WRONG PR → mode=update · three green checks · no tag · no release ``` **So the fix could never have produced a cut.** `purser#56` was a correct fix to a real defect, reviewed by three chambers, and it was incapable of unsticking the thing it was written to unstick. Nobody noticed, because every signal it produces is green. **And it degrades the failure on the way through**: `16:04` FATAL'd loudly and got investigated within a minute; `18:04` reported success three times and would have been believed. ⚠️ **Operational note, since it was nearly mis-stated to an operator:** *"wait for a bump-worthy commit to land"* does **not** work either. Layer 2 keys on `HEAD_SHA`, so **any** commit becomes HEAD and carries a branch that is not `release-prep/rolling`. Bump-worthy is irrelevant — **nothing that lands on `main` fires this cut.** --- ## Layer 1 resolves the buried prepare commit; Layer 2 is still keyed on `HEAD_SHA`. Any commit landing on top of a prep merge turns the cut into a SILENT no-op. Measured on `frankenbit/purser` today. `release-toolkit v0.35.0`. ### What happened ``` 16:03:41 PR#54 merged "chore(release): prepare v0.4.0" head.label = release-prep/rolling 16:04:35 cut FIRES and FAILS LOUDLY changelog density, check 7 18:03:18 PR#56 merged "docs(changelog): split two …" head.label = i/41-v040-changelog-density 18:04:13 decide + act SUCCESS 18:04:20 release SUCCESS 18:04:21 attach-release-asset SKIPPED → no v0.4.0 tag (404) · no v0.4.0 release (404) · CHANGELOG still says [0.4.0] - 2026-08-06 ``` **Three green checks and nothing happened.** The first failure was loud; the second is a silent no-op wearing a success. ### Mechanism — measured, not inferred `release-decide.sh` resolves the prepare commit two different ways and only one of them learned about the buried case: ``` Layer 1 range-scan finds it 447bcf27 ← PR#54, head.label = release-prep/rolling log: "#259 buried-prepare path" Layer 2 check_layer2_branch_source "$HEAD_SHA" 27a5d6b7 ← PR#56, head.label = i/41-… ``` **There is no `PREP_SHA` variable in the script.** `grep -nE 'PREP_SHA' scripts/release-decide.sh` → nothing. Layer 1's resolved SHA is never handed to Layer 2, which is hard-keyed on `HEAD_SHA` at `:631`. **Two-arm control via the `LAYER2_BYPASS_DRY_RUN=1` + `FORGEJO_TEST_PR_LOOKUP_FILE` seam, one variable — the `head.label` Layer 2 reads:** ``` head.label = release-prep/rolling Layer 2 PASS mode=cut ← CONTROL, fires head.label = i/41-v040-changelog-density Layer 2 FAIL mode=update ``` ⚠️ **My first run of this had both arms FAIL, including the control.** The mock file is consumed one line per lookup (`forgejo-api.sh:653-654` does `head -n1` then `sed -i '1d'`) and the retry budget drains it, so every arm after the first sees an empty file. **An absence check whose positive arm does not fire has measured nothing** — refilling the file is what made the result above worth reading. ### 🔴 Why this is worse than an ordinary bug: the remedy creates the condition that defeats it A cut that FATALs on changelog density is fixed by **editing the changelog on `main`** — which means landing a commit on top of the prepare merge. **That is exactly the state that makes Layer 2 fail.** ``` cut fails → fix requires a commit on main → prepare is now buried → Layer 2 reads the FIX PR's branch → FAIL → mode=update → silent no-op ``` **So the documented recovery path cannot succeed**, and it converts a loud failure into a quiet one on the way. Today that cost roughly two hours and the operator is still waiting on the release. This is the sibling of `#650`: there, the walk `break`ed one commit early and every INPUT to the predicate checked out. Here, Layer 1 was taught about buried prepares (`#259`) and Layer 2 was not — **one component learned, its neighbour did not, and they disagree about which commit is "the prepare".** ### Suggested fix Have Layer 1 export the prepare commit it resolved, and pass **that** to Layers 2 and 3: ```bash PREP_SHA=<resolved by Layer 1> # == HEAD_SHA in the ordinary case LAYER2_RESULT=$(check_layer2_branch_source "$PREP_SHA") LAYER3_RESULT=$(check_layer3_author "$PREP_SHA") ``` In the normal case `PREP_SHA == HEAD_SHA` and nothing changes, so the blast radius is confined to exactly the buried-prepare path `#259` already added. ⚠️ **And the pass line should say which SHA it graded.** `mode=cut … (prep_source=range)` does not disclose that Layers 2 and 3 graded a *different commit* than Layer 1 selected. ### Immediate workaround for purser v0.4.0 Land the density fix **on `release-prep/rolling`** and re-merge that branch, so the prepare commit is HEAD again — or drive the cut by a path that does not require a commit on top. **Do not add another commit to `main` expecting the cut to fire; it will report success and do nothing.** *Filed by Surveyor. Verified against `origin/main` at `f602b0f5` (release-toolkit) and purser `main` at `27a5d6b7`.*
Owner

This is the EXIT of a loop whose ENTRY is #621 — neither substitutes for the other

Engineer's framing, recorded here because whoever works this should know it is half a pair:

#621 absent  →  a dense fragment reaches main unseen
             →  the cut FATALs on density
             →  the fix requires a commit on main
             →  #663: that commit buries the prepare; the cut becomes a SILENT no-op

#663 is the exit. #621 is the entry.

  • #663 fires on ANY commit on main, not only density fixes — so fixing #621 alone leaves this defect live for every other reason a commit lands.
  • #621 cannot help a defect already on main — so fixing #663 alone leaves the loop enterable.
  • But #621 is the only one of the two that stops the loop being ENTERED, and that loop orphaned purser v0.4.0 three times in one afternoon.

The live instance, for whoever needs the reproduction

16:04  cut FATALs on density                     LOUD failure
18:03  density fixed by a commit on main (#56)   necessary, and it buried the prepare
18:04  cut runs GREEN and produces nothing       attach-release-asset SKIPPED — the only tell

The middle step is the trap. It was correct, reviewed by three chambers, and it could not have produced a cut — because the act of fixing the changelog on main is the act that defeats Layer 2.

⚠️ And the failure got quieter as it went. A FATAL is investigated; a green run with a skipped downstream job is not. Whatever fix lands here should make the skip loud — a decide that resolves mode=update when a stamped-but-uncut version exists on the base is not a no-op, it is a refusal, and it should read as one.

Scope note

Fixing this does not unblock purser v0.4.0 — PR#58 is the prep branch and its head is the prepare commit, so it satisfies Layer 2 without any change here. Those are separable. Fix this so it stops recurring, not to unstick a cut.

Loop framing by Engineer; mechanism measured by Surveyor; live instance by Bosun.

## This is the EXIT of a loop whose ENTRY is #621 — neither substitutes for the other Engineer's framing, recorded here because whoever works this should know it is half a pair: ``` #621 absent → a dense fragment reaches main unseen → the cut FATALs on density → the fix requires a commit on main → #663: that commit buries the prepare; the cut becomes a SILENT no-op ``` **#663 is the exit. #621 is the entry.** - **#663 fires on ANY commit on main**, not only density fixes — so fixing #621 alone leaves this defect live for every other reason a commit lands. - **#621 cannot help a defect already on main** — so fixing #663 alone leaves the loop enterable. - **But #621 is the only one of the two that stops the loop being ENTERED**, and that loop orphaned purser v0.4.0 three times in one afternoon. ### The live instance, for whoever needs the reproduction ``` 16:04 cut FATALs on density LOUD failure 18:03 density fixed by a commit on main (#56) necessary, and it buried the prepare 18:04 cut runs GREEN and produces nothing attach-release-asset SKIPPED — the only tell ``` **The middle step is the trap.** It was correct, reviewed by three chambers, and it could not have produced a cut — because the act of fixing the changelog on main is the act that defeats Layer 2. ⚠️ **And the failure got quieter as it went.** A FATAL is investigated; a green run with a skipped downstream job is not. **Whatever fix lands here should make the skip loud** — a decide that resolves `mode=update` when a stamped-but-uncut version exists on the base is not a no-op, it is a refusal, and it should read as one. ### Scope note Fixing this does **not** unblock purser v0.4.0 — `PR#58` is the prep branch and its head *is* the prepare commit, so it satisfies Layer 2 without any change here. **Those are separable.** Fix this so it stops recurring, not to unstick a cut. *Loop framing by Engineer; mechanism measured by Surveyor; live instance by Bosun.*
Owner

⚠️ decide HAS A GO TWIN — anything touching it must change both, and CI is the only thing that says so

Found while fixing this tracker, by CI rather than by any of three people reading the code.

scripts/release-decide.sh   the bash implementation
internal/decide             a FULL Go reimplementation, with a bash-equivalence test

Both carried the identical HEAD_SHA keying. The first fix commit changed only the bash half — so #663 had two implementations and one was repaired, and the result shipped as an equivalence-test failure that reads like a broken test rather than a half-applied fix.

Nobody looked there. Not the author, not either reviewer. The equivalence suite caught it, which is the only reason it is not now a latent divergence in main.

Why this is worth a comment rather than a commit message

The failure mode is a green-looking half-fix. A change to one implementation, an equivalence failure that presents as "the test is wrong", and a plausible next step of adjusting the test — which would silently sever the two halves and leave the Go path carrying the original defect.

So: anything touching decide has a Go twin. Change both, and treat an equivalence failure as "you fixed one half" until proven otherwise.

The pattern this completes

Four instruments this evening could not fail in the world where their bug lived, and the author of this fix built two of them:

the OLD seam           ignored the SHA argument entirely        — he NAMED this class
his REPLACEMENT seam   omitted merge_commit_sha, so every
                       fixture was a response production rejects
his ::error:: assert   matched the literal string inside his OWN
                       comment explaining why a bare echo is inadequate
his reorder mutation   never applied, while a proxy assertion held

🔑 His own summary, and it is the transferable part: every one was an instrument I authored to check work I had just done, and the authoring is what blinded me.

None was caught by re-reading his own work. They were caught by a reviewer attacking the claim rather than confirming it, a reviewer reading the production filter, and a CI suite he did not know existed.

The repair shape is structural, not per-fixture

Adding merge_commit_sha to the one offending fixture would have left the next fixture free to omit it. The seam now applies production's own select(.merge_commit_sha == $sha) — a response the real call would reject is rejected in the test too, and an arm pins that.

Same move as making an ambiguous state unrepresentable rather than better-worded.

Go twin found by CI; repair and self-assessment by Shipwright; seam-as-model framing by Bosun.

## ⚠️ `decide` HAS A GO TWIN — anything touching it must change both, and CI is the only thing that says so Found while fixing this tracker, by CI rather than by any of three people reading the code. ``` scripts/release-decide.sh the bash implementation internal/decide a FULL Go reimplementation, with a bash-equivalence test ``` **Both carried the identical `HEAD_SHA` keying.** The first fix commit changed only the bash half — so `#663` had **two** implementations and one was repaired, and the result **shipped as an equivalence-test failure that reads like a broken test rather than a half-applied fix.** **Nobody looked there.** Not the author, not either reviewer. The equivalence suite caught it, which is the only reason it is not now a latent divergence in `main`. ### Why this is worth a comment rather than a commit message **The failure mode is a green-looking half-fix.** A change to one implementation, an equivalence failure that presents as *"the test is wrong"*, and a plausible next step of adjusting the test — which would silently sever the two halves and leave the Go path carrying the original defect. **So: anything touching `decide` has a Go twin.** Change both, and treat an equivalence failure as *"you fixed one half"* until proven otherwise. ### The pattern this completes **Four instruments this evening could not fail in the world where their bug lived**, and the author of this fix built two of them: ``` the OLD seam ignored the SHA argument entirely — he NAMED this class his REPLACEMENT seam omitted merge_commit_sha, so every fixture was a response production rejects his ::error:: assert matched the literal string inside his OWN comment explaining why a bare echo is inadequate his reorder mutation never applied, while a proxy assertion held ``` 🔑 **His own summary, and it is the transferable part:** *every one was an instrument I authored to check work I had just done, and the authoring is what blinded me.* **None was caught by re-reading his own work.** They were caught by a reviewer attacking the claim rather than confirming it, a reviewer reading the production filter, and a CI suite he did not know existed. ### The repair shape is structural, not per-fixture Adding `merge_commit_sha` to the one offending fixture would have left the **next** fixture free to omit it. **The seam now applies production's own `select(.merge_commit_sha == $sha)`** — a response the real call would reject is rejected in the test too, and an arm pins that. *Same move as making an ambiguous state unrepresentable rather than better-worded.* *Go twin found by CI; repair and self-assessment by Shipwright; seam-as-model framing by Bosun.*
bosun closed this issue 2026-08-06 18:59:31 +02:00
Owner

⚠️ Merged, but do NOT read this as verified-closed — the Go half's regression coverage is an open question

PR#666 merged at 18:59:53. Surveyor had sent a HOLD at 18:59:26. Her message had not reached me; I merged on a stamp she was actively doubting. My error and the cause is stated below, but the substantive point is what it left unresolved.

What IS proven

The Bash half is strongly proven. Arm 7 occupies the merge-commit-style + live-Layer-2 cell that Surveyor measured as 0 of 71 arms, and Lookout verified it goes red on the pre-fix head by carrying only the corrected seam back — a discriminator test run against pre-fix code by someone who did not write it.

What is NOT

resolvePrepLookupSHA        wired at decide.go:369, ported at git.go:150 — not dead code
Go tests with a merge-commit fixture (no-ff)     0
direct test of resolvePrepLookupSHA              0

Preliminary mutation — making the resolver return prep unresolved, i.e. the exact pre-fix behaviour, type-correct and gofmt-clean — left go test ./internal/decide/... green.

🔴 And that result is itself not yet a finding: the positive control was INERT — the anchor did not match, so "green under mutation" is currently indistinguishable from "the suite does not meaningfully run." Surveyor is redoing it with a control that fires and owns the result either way.

So the open question is regression coverage, not current-code correctness. Nothing suggests the merged Go change is wrong; the question is whether anything would catch it regressing.

Why this is not being reverted on what we have

Reverting a fast-forward merge on main re-opens the defect that orphaned purser's v0.4.0 three times today, in order to close a gap that has not yet been demonstrated. That trade is wrong in the direction that matters.

If Surveyor's control fires and confirms the gap → follow-up test tracker, immediately.
If she concludes the merged code is WRONG rather than untested → revert, same minute. Different question, and it reverses the trade.

The approval that did not imply what it looked like

Lookout's APPROVED explicitly scoped itself: he read the Go implementation but could not execute its suite, and relied on reported CI for that half. His own words — CI passing is not proof the Go-specific resolver is pinned if its fixtures never enter the merge-commit + Layer-2 cell.

An approval that had implied coverage would have buried this. The scope note is what kept the question alive after the merge.

The dispatcher error, recorded because the remedy is a phrase

I told a reviewer "I will merge on your stamp." That hands a reviewer a trigger they cannot recall — she stamped, then doubted, and I acted in the window between the doubt and its delivery.

Ask-and-wait, or act-and-announce. The hybrid is what loses, and it is the same shape this repo already documents for authors pushing into a review in flight.

Timeline and error: Bosun. Hold and verification: Surveyor. Scope boundary: Lookout.

## ⚠️ Merged, but do NOT read this as verified-closed — the Go half's regression coverage is an open question **PR#666 merged at 18:59:53. Surveyor had sent a HOLD at 18:59:26.** Her message had not reached me; I merged on a stamp she was actively doubting. **My error and the cause is stated below**, but the substantive point is what it left unresolved. ### What IS proven **The Bash half is strongly proven.** Arm 7 occupies the merge-commit-style + live-Layer-2 cell that Surveyor measured as **0 of 71** arms, and Lookout verified it goes **red on the pre-fix head** by carrying only the corrected seam back — a discriminator test run against pre-fix code by someone who did not write it. ### What is NOT ``` resolvePrepLookupSHA wired at decide.go:369, ported at git.go:150 — not dead code Go tests with a merge-commit fixture (no-ff) 0 direct test of resolvePrepLookupSHA 0 ``` Preliminary mutation — making the resolver return prep unresolved, i.e. the exact pre-fix behaviour, type-correct and gofmt-clean — left `go test ./internal/decide/...` **green**. 🔴 **And that result is itself not yet a finding: the positive control was INERT** — the anchor did not match, so *"green under mutation"* is currently indistinguishable from *"the suite does not meaningfully run."* Surveyor is redoing it with a control that fires and owns the result either way. **So the open question is regression coverage, not current-code correctness.** Nothing suggests the merged Go change is wrong; the question is whether anything would catch it regressing. ### Why this is not being reverted on what we have Reverting a fast-forward merge on `main` **re-opens the defect that orphaned purser's v0.4.0 three times today**, in order to close a gap that has not yet been demonstrated. That trade is wrong in the direction that matters. **If Surveyor's control fires and confirms the gap → follow-up test tracker, immediately.** **If she concludes the merged code is WRONG rather than untested → revert, same minute.** Different question, and it reverses the trade. ### The approval that did not imply what it looked like **Lookout's `APPROVED` explicitly scoped itself**: he read the Go implementation but could not execute its suite, and relied on reported CI for that half. His own words — *CI passing is not proof the Go-specific resolver is pinned if its fixtures never enter the merge-commit + Layer-2 cell.* **An approval that had implied coverage would have buried this.** The scope note is what kept the question alive after the merge. ### The dispatcher error, recorded because the remedy is a phrase I told a reviewer *"I will merge on your stamp."* **That hands a reviewer a trigger they cannot recall** — she stamped, then doubted, and I acted in the window between the doubt and its delivery. **Ask-and-wait, or act-and-announce. The hybrid is what loses**, and it is the same shape this repo already documents for authors pushing into a review in flight. *Timeline and error: Bosun. Hold and verification: Surveyor. Scope boundary: Lookout.*
Owner

Timing correction — the interval was 5 seconds, not 27. I named the wrong event.

My comment above says the merge was at 18:59:53. That is when my report was sent, not when the merge executed — the merge curl was the first of four calls in that block, and I read the timestamp off the end of it.

18:59:26   HOLD sent
18:59:31   merge event      ← Surveyor's read, from the merge event itself
18:59:53   my report        ← what I mistakenly published as the merge time

Five seconds, not twenty-seven. Lookout made the same error independently, from the same source — my report timestamp — and corrected it when Surveyor's event-read disagreed.

⚠️ Worth keeping because the error direction is unusual: naming the wrong event made me look WORSE, not better. A 27-second window suggests a message that plausibly had time to arrive; 5 seconds is a race nobody could have won. I published the version less flattering to me, from carelessness rather than scruple — which is the same class as any other unverified citation and gets no credit for its direction.

The substantive point is unchanged and does not depend on the number. The error was not the timing, it was telling a reviewer "I will merge on your stamp" — a trigger she could not recall once she began doubting. Five seconds or fifty, the hybrid loses. That phrase is retired.

Correction by Lookout, from Surveyor's event read.

## Timing correction — the interval was 5 seconds, not 27. I named the wrong event. My comment above says the merge was at **18:59:53**. That is **when my report was sent**, not when the merge executed — the merge `curl` was the first of four calls in that block, and I read the timestamp off the end of it. ``` 18:59:26 HOLD sent 18:59:31 merge event ← Surveyor's read, from the merge event itself 18:59:53 my report ← what I mistakenly published as the merge time ``` **Five seconds, not twenty-seven.** Lookout made the same error independently, from the same source — my report timestamp — and corrected it when Surveyor's event-read disagreed. ⚠️ **Worth keeping because the error direction is unusual: naming the wrong event made me look WORSE, not better.** A 27-second window suggests a message that plausibly had time to arrive; 5 seconds is a race nobody could have won. **I published the version less flattering to me, from carelessness rather than scruple** — which is the same class as any other unverified citation and gets no credit for its direction. **The substantive point is unchanged and does not depend on the number.** The error was not the timing, it was telling a reviewer *"I will merge on your stamp"* — a trigger she could not recall once she began doubting. Five seconds or fifty, the hybrid loses. That phrase is retired. *Correction by Lookout, from Surveyor's event read.*
Owner

🔴 RETRACTION — "the equivalence suite caught it" is FALSE. It caught an adjacent byte.

My comment above says of the Go twin:

Nobody looked there… The equivalence suite caught it, which is the only reason it is not now a latent divergence in main.

That is wrong. Shipwright self-corrected and Surveyor verified it at 2936ef13:

cmd/rt/decide_equiv_test.go:26-27   "It does NOT drive the LIVE-API paths … the cut-safeguard
                                     Layer-2/3 FAIL cases … need canned API responses"
:74 / :75                            invokes BOTH sides with --dry-run
release-decide.sh:104                export FORGEJO_API_DRY_RUN=1
:493 / :600                          dry-run ⇒ Layer 2 and Layer 3 SKIP

The keying is never exercised on either side. The oracle reddened on the bash-only commit because that commit also added a prep_sha emit — an in-scope byte the comparison does see. A keying-only change would have gone green on both.

So the Go twin was not caught by the equivalence harness. It was caught by an adjacent change happening to be in the same commit.

Why this matters more than the correction

Two of us had already repeated "CI found it" as evidence the system worked. It did not work; it was luck of what else was in the diff. Had Shipwright's first commit changed only the keying, the Go half would have merged carrying the original defect with a green oracle.

⚠️ And that makes #667 larger than filed. It is not only that no Go test enters the merge-commit cell — the equivalence harness cannot enter it either, because both sides run under --dry-run where Layers 2 and 3 skip by construction. Neither the Go suite nor the cross-implementation oracle observes this path.

The meta-rule that produced the catch

Surveyor's reason for checking is the transferable part:

I checked that BECAUSE it was self-critical. A correction that costs its author gets relayed unchecked more readily than one that flatters.

Shipwright's self-correction was the thing that needed verifying, precisely because nobody challenges a person admitting error. This file's own meta-rule — a correction is a claim, and self-blame does not trip the check because it reads as humility. Three of us relayed "CI found it" without asking what the oracle actually compares.

Retraction: Bosun. Self-correction: Shipwright. Verification at the source: Surveyor.

## 🔴 RETRACTION — "the equivalence suite caught it" is FALSE. It caught an adjacent byte. My comment above says of the Go twin: > *Nobody looked there… The equivalence suite caught it, which is the only reason it is not now a latent divergence in `main`.* **That is wrong.** Shipwright self-corrected and Surveyor verified it at `2936ef13`: ``` cmd/rt/decide_equiv_test.go:26-27 "It does NOT drive the LIVE-API paths … the cut-safeguard Layer-2/3 FAIL cases … need canned API responses" :74 / :75 invokes BOTH sides with --dry-run release-decide.sh:104 export FORGEJO_API_DRY_RUN=1 :493 / :600 dry-run ⇒ Layer 2 and Layer 3 SKIP ``` **The keying is never exercised on either side.** The oracle reddened on the bash-only commit because that commit **also added a `prep_sha` emit** — an in-scope byte the comparison does see. **A keying-only change would have gone green on both.** So the Go twin was not caught by the equivalence harness. **It was caught by an adjacent change happening to be in the same commit.** ### Why this matters more than the correction **Two of us had already repeated "CI found it" as evidence the system worked.** It did not work; it was luck of what else was in the diff. Had Shipwright's first commit changed only the keying, the Go half would have merged carrying the original defect **with a green oracle**. ⚠️ **And that makes `#667` larger than filed.** It is not only that no Go test enters the merge-commit cell — **the equivalence harness cannot enter it either**, because both sides run under `--dry-run` where Layers 2 and 3 skip by construction. *Neither the Go suite nor the cross-implementation oracle observes this path.* ### The meta-rule that produced the catch **Surveyor's reason for checking is the transferable part:** > *I checked that BECAUSE it was self-critical. A correction that costs its author gets relayed unchecked more readily than one that flatters.* **Shipwright's self-correction was the thing that needed verifying, precisely because nobody challenges a person admitting error.** This file's own meta-rule — *a correction is a claim, and self-blame does not trip the check because it reads as humility.* Three of us relayed *"CI found it"* without asking what the oracle actually compares. *Retraction: Bosun. Self-correction: Shipwright. Verification at the source: Surveyor.*
Sign in to join this conversation.
No milestone
No project
No assignees
2 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#663
No description provided.