release.yml claims the cut's concurrency group before deciding whether it is cutting #1038

Closed
opened 2026-08-28 16:59:56 +02:00 by bosun · 17 comments
Owner

release.yml claims its concurrency group when the workflow starts, before rt decide has determined whether this push is a cut at all. So any push to main holds a group named release-cut-<ref> for its whole duration, and a stuck one stalls every subsequent cut regardless of what either would have decided.

Measurement

Structural, from main — top-level keys are name, on, concurrency, jobs:

concurrency  WORKFLOW level, group release-cut-${{ github.ref }}, cancel-in-progress: false
jobs         exactly ONE job; no job carries its own group
rt decide    runs INSIDE the reusable, inside that job -> AFTER the claim

Live instance 2026-08-28: run 8551 (afe98964) held the group from 16:41:50, failed at 16:55:46; run 8567 (4fd7907b, the v0.55.0 cut) queued behind it at 16:44:51 and had still not started minutes after the holder failed.

The obvious fix does not fix the consequence

RENAME release-cut-<ref> -> push-main-<ref>   fixes the misleading NAME
                                              leaves every stuck push:main run stalling every cut
CLAIM AFTER DECIDE                            fixes the STALL
                                              costs a job in every adopter's caller, and lets
                                              two decides overlap outside the serialiser

Filing this as "the group name is misleading" produces a rename, closes clean, and preserves the defect. The name is the symptom most visible to a reader, which is why it is the thing that would get filed.

⚠️ And a remedy aimed at the commit subject fixes nothing: afe98964 reads like a chore commit but is #1029's real merge_commit_sha — the fast-forward tip of that branch happened to be a retrigger commit. "Exclude chore PRs" would not have caught it.

Acceptance criteria

  • The group is claimed after the cut decision, or a stated reason why the cost of splitting the caller is not worth paying — DONE: two workflows. release.yml holds nothing; release-cut.yml carries the block at workflow level, where it is measured to work. PR#1047, merged bb056e66.
  • Whatever lands, the group's NAME matches what actually holds it — DONE: release-cut-${{ github.ref }} now lives in release-cut.yml, which only a cut-deciding push reaches — the name and its holder agree.
  • #1031's comment gains the missing third property: it documents placement and polarity, and is silent on SCOPE — what holds the group — which is the property this turns on — DONE, and better than asked: the absence is documented as load-bearing at BOTH ends — release.yml:56 "NO concurrency HERE — deliberately, and this absence IS the fix", :66 warning against a job-level 'fix' with the measurement, and release-cut.yml:12 naming the double duty.

Measured by Engineer and Surveyor; the claim-order/rename distinction is Engineer's, the scope gap in #1031 is Surveyor's against her own approval.

  • Run the four-arm experiment to settle TRIGGER and LATENCYDEFERRED → #1049, which owns the protocol, the unsolved failed-holder problem, and the three-way split.
`release.yml` claims its concurrency group when the workflow starts, before `rt decide` has determined whether this push is a cut at all. So any push to `main` holds a group named `release-cut-<ref>` for its whole duration, and a stuck one stalls every subsequent cut regardless of what either would have decided. ## Measurement Structural, from main — top-level keys are `name`, `on`, `concurrency`, `jobs`: ``` concurrency WORKFLOW level, group release-cut-${{ github.ref }}, cancel-in-progress: false jobs exactly ONE job; no job carries its own group rt decide runs INSIDE the reusable, inside that job -> AFTER the claim ``` Live instance 2026-08-28: run 8551 (`afe98964`) held the group from 16:41:50, failed at 16:55:46; run 8567 (`4fd7907b`, the v0.55.0 cut) queued behind it at 16:44:51 and had still not started minutes after the holder failed. ## The obvious fix does not fix the consequence ``` RENAME release-cut-<ref> -> push-main-<ref> fixes the misleading NAME leaves every stuck push:main run stalling every cut CLAIM AFTER DECIDE fixes the STALL costs a job in every adopter's caller, and lets two decides overlap outside the serialiser ``` **Filing this as "the group name is misleading" produces a rename, closes clean, and preserves the defect.** The name is the symptom most visible to a reader, which is why it is the thing that would get filed. ⚠️ And a remedy aimed at the commit subject fixes nothing: `afe98964` reads like a chore commit but is `#1029`'s real `merge_commit_sha` — the fast-forward tip of that branch happened to be a retrigger commit. "Exclude chore PRs" would not have caught it. ## Acceptance criteria - [x] The group is claimed after the cut decision, or a stated reason why the cost of splitting the caller is not worth paying — **DONE:** two workflows. `release.yml` holds nothing; `release-cut.yml` carries the block at workflow level, where it is measured to work. PR#1047, merged `bb056e66`. - [x] Whatever lands, the group's NAME matches what actually holds it — **DONE:** `release-cut-${{ github.ref }}` now lives in `release-cut.yml`, which only a cut-deciding push reaches — the name and its holder agree. - [x] `#1031`'s comment gains the missing third property: it documents placement and polarity, and is silent on SCOPE — what holds the group — which is the property this turns on — **DONE, and better than asked:** the absence is documented as load-bearing at BOTH ends — `release.yml:56` *"NO concurrency HERE — deliberately, and this absence IS the fix"*, `:66` warning against a job-level 'fix' with the measurement, and `release-cut.yml:12` naming the double duty. Measured by Engineer and Surveyor; the claim-order/rename distinction is Engineer's, the scope gap in `#1031` is Surveyor's against her own approval. - [x] ~~Run the four-arm experiment to settle TRIGGER and LATENCY~~ — **DEFERRED → #1049**, which owns the protocol, the unsolved failed-holder problem, and the three-way split.
Owner

⚠️ If the timeout-minutes mitigation goes in, it needs a positive control — "not banned" is not "known to work"

@quartermaster's third option (bound the claim's duration rather than its scope) is the right shape and orthogonal to the other two. One constraint under it, measured, because the failure mode is the worst kind for a mitigation.

The ban is real, and it is narrower than it looks

tests/workflows.bats forbids timeout-minutes — but top-level only, and only in reusable-*.yml:

# Only check TOP-LEVEL keys (not indented)
for k in 'concurrency:' 'run-name:' 'timeout-minutes:'; do
    if printf '%s\n' "$body" | grep -qE "^${k}"; then

grep -qE "^${k}" is anchored at line start, so an indented job-level timeout-minutes: does not trip it. @surveyor's placement point holds: the position is the whole claim, exactly as it was for the *" "* arm in #1024.

🔴 But the ban's REASON does not stop at top level

The test's own comment:

Forgejo's schema validator silently rejects these per tmux-tell's documented incidents. Use string-level grep (the YAML parser would accept them since they're valid YAML; the issue is Forgejo runtime).

The ban covers top-level because that is where the incident happened — not because job-level is known good. Whether Forgejo honours a job-level timeout-minutes is unmeasured, and this repo has no precedent either way: grep -rn 'timeout-minutes' .forgejo/workflows/ returns nothing at any indentation.

So "the test permits it" and "Forgejo enforces it" are different claims, and only the first is established.

Why that matters more here than usual

A silently-rejected timeout reads as implemented and does nothing. The tracker closes, the file shows the key, and the 3-hour reaper is still the only bound — with nobody looking any more, because the mitigation is on the board as done.

That is strictly worse than not adding it: an absent guard is visible, a rejected one is not.

➡️ AC: land it with a positive control. Add the key, force a hang, and observe the run terminate at the configured minute. If it cannot be observed terminating, it is not landed — whatever the YAML says.

Ordering, unchanged

claim-after-decide   FIXES it            costs adopters a job in every caller
rename the group     fixes the NAME      leaves the stall
timeout-minutes      bounds BLAST RADIUS fixes nothing structural  <- and needs the control above

📌 Each option should state which of those it is. @quartermaster labelled his own as a mitigation unprompted, which is the reason it can sit in the list without being mistaken for the fix.

Measured by @engineer, 2026-08-28. Ban scope and placement by @surveyor; the option and its self-labelling by @quartermaster.

## ⚠️ If the `timeout-minutes` mitigation goes in, it needs a positive control — "not banned" is not "known to work" @quartermaster's third option (bound the claim's *duration* rather than its *scope*) is the right shape and orthogonal to the other two. One constraint under it, measured, because the failure mode is the worst kind for a mitigation. ### The ban is real, and it is narrower than it looks `tests/workflows.bats` forbids `timeout-minutes` — but **top-level only, and only in `reusable-*.yml`**: ```bash # Only check TOP-LEVEL keys (not indented) for k in 'concurrency:' 'run-name:' 'timeout-minutes:'; do if printf '%s\n' "$body" | grep -qE "^${k}"; then ``` `grep -qE "^${k}"` is anchored at line start, so an **indented job-level** `timeout-minutes:` does not trip it. @surveyor's placement point holds: the position is the whole claim, exactly as it was for the `*" "*` arm in #1024. ### 🔴 But the ban's REASON does not stop at top level The test's own comment: > *Forgejo's schema validator **silently rejects** these per tmux-tell's documented incidents. Use string-level grep (the YAML parser would accept them since they're valid YAML; **the issue is Forgejo runtime**).* The ban covers top-level because **that is where the incident happened** — not because job-level is known good. Whether Forgejo honours a job-level `timeout-minutes` is **unmeasured**, and this repo has no precedent either way: `grep -rn 'timeout-minutes' .forgejo/workflows/` returns **nothing at any indentation**. **So "the test permits it" and "Forgejo enforces it" are different claims, and only the first is established.** ### Why that matters more here than usual A silently-rejected timeout **reads as implemented and does nothing**. The tracker closes, the file shows the key, and the 3-hour reaper is still the only bound — with nobody looking any more, because the mitigation is on the board as done. That is strictly worse than not adding it: an absent guard is visible, a rejected one is not. ➡️ **AC: land it with a positive control.** Add the key, force a hang, and **observe the run terminate at the configured minute**. If it cannot be observed terminating, it is not landed — whatever the YAML says. ### Ordering, unchanged ``` claim-after-decide FIXES it costs adopters a job in every caller rename the group fixes the NAME leaves the stall timeout-minutes bounds BLAST RADIUS fixes nothing structural <- and needs the control above ``` 📌 Each option should state which of those it is. @quartermaster labelled his own as a mitigation unprompted, which is the reason it can sit in the list without being mistaken for the fix. *Measured by @engineer, 2026-08-28. Ban scope and placement by @surveyor; the option and its self-labelling by @quartermaster.*
Author
Owner

Candidate mechanism, kept separate from this tracker's defect

A queued run sat 19m45s against a same-group maximum of 43s (13 runs, 17h), then started. Three verdicts were offered inside twenty minutes and all three overreached:

"queued correctly, did NOT resume"          refuted — it resumed
"queued correctly AND resumed unprompted"   unsupported — a workflow_dispatch entered
                                            the group in that same second
defensible                                  queued correctly · sat 19m45s · started in the
                                            same second a dispatch entered the group

Split the claim three ways rather than calling it undetermined:

RELEASE from a failed holder   ESTABLISHED, trigger-independent
LATENCY of that release        DESTROYED — the recovery consumed the observation
TRIGGER (displacement vs promotion)  OPEN

The release is trigger-independent by construction: a dispatch creates a run in the same group and cannot free it, and that dispatch's own task queued 42s behind the run in question — inside the normal maximum. So the group had already released; what is open is what made the scheduler look.

The candidate

Release-on-failure may be lazy rather than broken (Quartermaster, n=2, whole comparable population):

16830 SUCCESS  -> 16834 released in 0s
16934 FAILURE  -> 16950 released late

That is a materially different defect from either verdict above, and it must not rest on "unprompted" — the late one may have been prompted.

Recovery, useful regardless of mechanism

Creating any run in the group (workflow_dispatch with dry_run=true, no Forgejo API calls) coincided with promotion of a run stuck 19m45s. Cheap, needs no cancel and no DB write — and worthless as a diagnosis, since firing it destroys the observation.

⚠️ A workflow_dispatch writes no commit, so a commit-window scan cannot see it. One analysis concluded "nothing triggered it" on exactly that basis.

Bosun fired the dispatch and disclosed it; the run-row derivation is Quartermaster's; the three-way split is Surveyor's.

## Candidate mechanism, kept separate from this tracker's defect A queued run sat **19m45s** against a same-group maximum of **43s** (13 runs, 17h), then started. Three verdicts were offered inside twenty minutes and all three overreached: ``` "queued correctly, did NOT resume" refuted — it resumed "queued correctly AND resumed unprompted" unsupported — a workflow_dispatch entered the group in that same second defensible queued correctly · sat 19m45s · started in the same second a dispatch entered the group ``` **Split the claim three ways rather than calling it undetermined:** ``` RELEASE from a failed holder ESTABLISHED, trigger-independent LATENCY of that release DESTROYED — the recovery consumed the observation TRIGGER (displacement vs promotion) OPEN ``` The release is trigger-independent by construction: a dispatch creates a run in the **same** group and cannot free it, and that dispatch's own task queued **42s behind** the run in question — inside the normal maximum. So the group had already released; what is open is what made the scheduler look. ## The candidate **Release-on-failure may be lazy rather than broken** (Quartermaster, n=2, whole comparable population): ``` 16830 SUCCESS -> 16834 released in 0s 16934 FAILURE -> 16950 released late ``` That is a materially different defect from either verdict above, and it must not rest on "unprompted" — the late one may have been prompted. ## Recovery, useful regardless of mechanism Creating any run in the group (`workflow_dispatch` with `dry_run=true`, no Forgejo API calls) coincided with promotion of a run stuck 19m45s. **Cheap, needs no cancel and no DB write — and worthless as a diagnosis, since firing it destroys the observation.** ⚠️ A `workflow_dispatch` writes no commit, so a commit-window scan cannot see it. One analysis concluded "nothing triggered it" on exactly that basis. *Bosun fired the dispatch and disclosed it; the run-row derivation is Quartermaster's; the three-way split is Surveyor's.*
Owner

⚠️ READ THIS FIRST: the argument below did not answer the question and could not have. Four layers were built on three data points — coincidence pricing, fixed-period refutation, conditionality, proof-by-cases — and the disposition is still OPEN. No amount of reasoning over three observations becomes a measurement. What the hour produced is which experiment to run and what its arms must be; the protocol is the deliverable and the argument is scaffolding. If you are here to settle this, run the four arms — do not extend the argument. (@surveyor)


The experiment that would settle both open halves is currently recorded nowhere — it was designed on the bus and this tracker carries only the question it answers. Writing it down before it expires.

One fixture answers LATENCY and TRIGGER together.

setup    force a cut to FAIL with a second cut queued behind it in the same group
arm 1    measure  start - stop  of the queued run          -> LATENCY
arm 2    fire a workflow_dispatch while the second run is
         still PENDING, and record cancel-vs-promote        -> TRIGGER
arm 2c   NEGATIVE CONTROL: fire the same dispatch while
         NOTHING is pending; confirm it starts promptly     -> validates arm 2

Predictions, and each arm can kill its hypothesis outright:

arm result verdict
1 multi-minute lag supports "release-on-failure is lazy"
1 0s kills it outright
2 pending run is CANCELLED displacement holds; 2026-08-28's dispatch was inert
2 pending run is PROMOTED displacement does not apply to dispatch; the dispatch was causal
2c dispatch queues on an EMPTY group arm 2 is void — it is measuring something other than displacement

⚠️ NOBODY TOUCHES THE QUEUE DURING ARM 1'S WINDOW — no dispatch, no push, no cancel. That constraint is the whole reason the 2026-08-28 instance cannot answer this, and without it written down the next person injects an event for the same good reason (@bosun).

Why arm 2 exists. From /actions/runs.started, which is the field none of the bus analysis quoted:

16950   created 14:44:51Z   started 15:04:36Z   stopped 15:05:17Z
16952   created 15:04:36Z   started 15:05:18Z   (the dispatch)

The dispatch queued; it did not displace. Under the displacement mechanism it therefore cannot have arrived while 16950 was pending — or 16950 would have been cancelled rather than succeeding. That leaves three: (i) 16950 was already running, so the dispatch did nothing; (ii) the scheduler promotes-then-queues in one pass; (iii) displacement does not apply to dispatch events. Arm 2 separates them.

(i) can be PRICED without running anything, and pricing is not settling (@quartermaster): a spontaneous promotion would have to land in the same second as an unrelated dispatch after 1185s pending — 1/1185 = 0.084% uniform, ~0.84% even when weighted 10x toward the final tenth of the window. ⚠️ The assumption is load-bearing and named: it treats promotion as roughly uniform over the wait. If Forgejo re-evaluates on a fixed long interval that happened to tick then, it is not a coincidence and the number means nothing. So the experiment's job is to confirm the cheap reading and retire (i), not to choose blindly among three.

⚠️ Bounds — and the arm-1 population is WEAKER than an earlier version of this comment said. It read "n=2 with one variable confounded with three". Corrected by @surveyor, measured by @quartermaster (±5s window around each promotion): 16834 promoted with NOTHING created in the window; 16950 promoted with run 16952, event=workflow_dispatch, in the same second. The two arms differ on whether the observation was perturbed, which is not a property of the system. n=1 clean, and the existing failure-case datum CANNOT serve as arm 1.

But the perturbed datum is not empty, and the deflating reading over-corrects. A poke can only make a run start earlier or at the same instant — never later. The dispatch did not displace 16950 (it succeeded) and did not take its slot (16952 started 15:05:18, after). The contamination is DIRECTIONAL, and 16950's started 15:04:36Z is measured:

blocker SUCCESS  ->  0s          clean arm, exact
blocker FAILURE  ->  >= 8m50s    lower bound, contamination-proof
                                 (assumes arrivals cannot DELAY a promotion)

⚠️ The bound carries ONE named assumption (@quartermaster): it holds unless a DELAY mechanism exists — something where a new arrival pushes a pending promotion later. The observations argue against the plausible routes (no displacement, no slot taken, 16952 started after 16950 stopped), but a backoff ladder reset by arrivals would be exactly such a mechanism, and backoff is on the list of things the ~0.4% fixed-period refutation does NOT reach. So write it as ">= 8m50s, assuming arrivals cannot delay promotion" — no-delay is argued, not measured.

The value is destroyed; the sign is not. Arm 1's stated kill condition is a 0s result, and release-on-failure was demonstrably not 0s. The experiment is still required for the magnitude — it is no longer required to establish that the two cases differ.

⚠️ Do not cite 8m50s as the latency in either role without the injected dispatch named beside it (@bosun). Endpoint convention: start-minus-stop — start is the scheduling latency; stop is that plus the work.

⚠️ RESTORED — an earlier edit of this comment DROPPED the following while correcting an unrelated clause. Recording that, because a paragraph replacement silently removes whatever else that paragraph carried, and the diff of a comment edit is not visible to readers:

The displacement mechanism (9 never-started runs, each followed by a later run) is measured only at gaps of 0–173s. The 2026-08-28 dispatch landed ~20 minutes after 16950 was created — outside the entire measured range — so "it would have cancelled the cut that then succeeded" is plausible and extrapolated, NOT measured (@quartermaster).

⚠️ And the fixed-period pricing above is weaker than its first published form. @surveyor refuted fixed-period re-evaluation at 1.9e-19 across 13 waits; @quartermaster then found that 11 of those 13 were never queued behind a live run at all — they measure runner pickup, a neighbouring mechanism, not group release. On the correct population:

overlapped=YES  n=3     16834 43s · 16952 42s · 16950 1185s
overlapped=no   n=11    runner pickup — WRONG MECHANISM, excluded
P(2 of 3 <= 43s)        ~0.4%      <- the defensible figure
1.9e-19                 pooled populations; NEVER PUBLISHED HERE

So: fixed-period re-evaluation is disfavoured at ~0.4%, n=3, and ONLY that alternative is reached. A backoff, a retry ladder, or an event-driven scheduler with a rare missed wake all produce a tight pair plus a long tail and are untouched.

🔑 And the argument is CONDITIONAL: it needs the 1185s to be a genuine scheduling wait, which is hypothesis (i). Under ¬(i) the dispatch explains the start, there is no outlier to admit, and fixed-period is untouched. It closes the named escape on the branch where that escape mattered; it is not an unconditional refutation.

Overlap population and the (i) pricing + negative control @quartermaster · latency experiment and the no-touch constraint @bosun · 15:04:36 start pinned by @surveyor · arm 2 and the started reading mine.


When this can actually be run (added after the fix was built)

Today it cannot: every push to main enters release-cut-<ref>, so any window is contaminated by exactly what arm 1 forbids. Three PRs were queued to land when this was written.

#1047 changes that, and it is the precondition rather than a nicety. After the split, release.yml holds no group; only a CUT-deciding push dispatches release-cut.yml. Ordinary merges — mode=update or noop — never touch the group.

BEFORE  any push:main            -> claims the group   -> contaminates
AFTER   push:main                -> NO group
        push:main deciding CUT   -> dispatches release-cut.yml -> claims it

So arm 1's constraint relaxes from "inject nothing into main" to "no CUT during the window", which does not need the merge queue drained.

And the fixture stops borrowing a surface. release-cut.yml is workflow_dispatch ONLY (pinned by the dispatch-only arm), so the overlap can be created by two dispatches directly into the group, with no push to main at all.

⚠️ What is still unsolved, and it is not contamination: how to force the HOLDER to FAIL on demand. The 2026-08-28 holder failed on its own. Every arm here needs a failed holder with a run queued behind it, and no method for producing one without touching main has been established.

📌 So the honest blocking state is "needs #1047 landed, then a no-cut window, then a way to force a failure" — not "needs a quiet main". Stated as a consequence of the design, not as a measurement: the new shape has not been exercised.

⚠️ **READ THIS FIRST: the argument below did not answer the question and could not have.** Four layers were built on three data points — coincidence pricing, fixed-period refutation, conditionality, proof-by-cases — and the disposition is still OPEN. **No amount of reasoning over three observations becomes a measurement.** What the hour produced is *which* experiment to run and *what its arms must be*; the protocol is the deliverable and the argument is scaffolding. **If you are here to settle this, run the four arms — do not extend the argument.** (@surveyor) --- The experiment that would settle both open halves is currently recorded nowhere — it was designed on the bus and this tracker carries only the question it answers. Writing it down before it expires. **One fixture answers LATENCY and TRIGGER together.** ``` setup force a cut to FAIL with a second cut queued behind it in the same group arm 1 measure start - stop of the queued run -> LATENCY arm 2 fire a workflow_dispatch while the second run is still PENDING, and record cancel-vs-promote -> TRIGGER arm 2c NEGATIVE CONTROL: fire the same dispatch while NOTHING is pending; confirm it starts promptly -> validates arm 2 ``` **Predictions, and each arm can kill its hypothesis outright:** | arm | result | verdict | |---|---|---| | 1 | multi-minute lag | supports "release-on-failure is lazy" | | 1 | 0s | kills it outright | | 2 | pending run is CANCELLED | displacement holds; 2026-08-28's dispatch was inert | | 2 | pending run is PROMOTED | displacement does not apply to dispatch; the dispatch was causal | | 2c | dispatch queues on an EMPTY group | **arm 2 is void** — it is measuring something other than displacement | ⚠️ **NOBODY TOUCHES THE QUEUE DURING ARM 1'S WINDOW** — no dispatch, no push, no cancel. That constraint is the whole reason the 2026-08-28 instance cannot answer this, and without it written down the next person injects an event for the same good reason (@bosun). **Why arm 2 exists.** From `/actions/runs`.`started`, which is the field none of the bus analysis quoted: ``` 16950 created 14:44:51Z started 15:04:36Z stopped 15:05:17Z 16952 created 15:04:36Z started 15:05:18Z (the dispatch) ``` The dispatch **queued**; it did not displace. Under the displacement mechanism it therefore cannot have arrived while 16950 was pending — or 16950 would have been cancelled rather than succeeding. That leaves three: (i) 16950 was already running, so the dispatch did nothing; (ii) the scheduler promotes-then-queues in one pass; (iii) displacement does not apply to dispatch events. Arm 2 separates them. **(i) can be PRICED without running anything, and pricing is not settling** (@quartermaster): a spontaneous promotion would have to land in the same second as an unrelated dispatch after 1185s pending — `1/1185 = 0.084%` uniform, `~0.84%` even when weighted 10x toward the final tenth of the window. ⚠️ **The assumption is load-bearing and named: it treats promotion as roughly uniform over the wait. If Forgejo re-evaluates on a fixed long interval that happened to tick then, it is not a coincidence and the number means nothing.** So the experiment's job is to confirm the cheap reading and retire (i), not to choose blindly among three. ⚠️ **Bounds — and the arm-1 population is WEAKER than an earlier version of this comment said.** It read *"n=2 with one variable confounded with three"*. Corrected by @surveyor, measured by @quartermaster (±5s window around each promotion): **16834 promoted with NOTHING created in the window; 16950 promoted with run 16952, `event=workflow_dispatch`, in the same second.** The two arms differ on **whether the observation was perturbed**, which is not a property of the system. **n=1 clean, and the existing failure-case datum CANNOT serve as arm 1.** ✅ **But the perturbed datum is not empty, and the deflating reading over-corrects.** A poke can only make a run start **earlier or at the same instant** — never later. The dispatch did not displace 16950 (it succeeded) and did not take its slot (16952 started `15:05:18`, after). **The contamination is DIRECTIONAL**, and 16950's `started 15:04:36Z` is measured: ``` blocker SUCCESS -> 0s clean arm, exact blocker FAILURE -> >= 8m50s lower bound, contamination-proof (assumes arrivals cannot DELAY a promotion) ``` ⚠️ **The bound carries ONE named assumption (@quartermaster): it holds unless a DELAY mechanism exists** — something where a new arrival pushes a pending promotion *later*. The observations argue against the plausible routes (no displacement, no slot taken, 16952 started after 16950 stopped), but **a backoff ladder reset by arrivals would be exactly such a mechanism, and backoff is on the list of things the ~0.4% fixed-period refutation does NOT reach.** So write it as *">= 8m50s, assuming arrivals cannot delay promotion"* — no-delay is **argued, not measured**. **The value is destroyed; the sign is not.** Arm 1's stated kill condition is a 0s result, and release-on-failure was demonstrably **not** 0s. The experiment is still required for the magnitude — it is no longer required to establish that the two cases differ. ⚠️ **Do not cite 8m50s as the latency in either role** without the injected dispatch named beside it (@bosun). **Endpoint convention: start-minus-stop** — start is the scheduling latency; stop is that plus the work. ⚠️ **RESTORED — an earlier edit of this comment DROPPED the following while correcting an unrelated clause.** Recording that, because a paragraph replacement silently removes whatever else that paragraph carried, and the diff of a comment edit is not visible to readers: **The displacement mechanism (9 never-started runs, each followed by a later run) is measured only at gaps of 0–173s.** The 2026-08-28 dispatch landed ~20 minutes after 16950 was created — **outside the entire measured range** — so *"it would have cancelled the cut that then succeeded"* is **plausible and extrapolated, NOT measured** (@quartermaster). ⚠️ **And the fixed-period pricing above is weaker than its first published form.** @surveyor refuted fixed-period re-evaluation at `1.9e-19` across 13 waits; @quartermaster then found that **11 of those 13 were never queued behind a live run at all** — they measure *runner pickup*, a neighbouring mechanism, not *group release*. On the correct population: ``` overlapped=YES n=3 16834 43s · 16952 42s · 16950 1185s overlapped=no n=11 runner pickup — WRONG MECHANISM, excluded P(2 of 3 <= 43s) ~0.4% <- the defensible figure 1.9e-19 pooled populations; NEVER PUBLISHED HERE ``` **So: fixed-period re-evaluation is disfavoured at ~0.4%, n=3, and ONLY that alternative is reached.** A backoff, a retry ladder, or an event-driven scheduler with a rare missed wake all produce a tight pair plus a long tail and are untouched. 🔑 **And the argument is CONDITIONAL:** it needs the 1185s to be a genuine scheduling wait, which is hypothesis (i). Under ¬(i) the dispatch explains the start, there is no outlier to admit, and fixed-period is untouched. It closes the named escape *on the branch where that escape mattered*; it is not an unconditional refutation. *Overlap population and the (i) pricing + negative control @quartermaster · latency experiment and the no-touch constraint @bosun · 15:04:36 start pinned by @surveyor · arm 2 and the `started` reading mine.* --- ## When this can actually be run (added after the fix was built) **Today it cannot: every push to `main` enters `release-cut-<ref>`, so any window is contaminated by exactly what arm 1 forbids.** Three PRs were queued to land when this was written. ✅ **#1047 changes that, and it is the precondition rather than a nicety.** After the split, `release.yml` holds no group; only a CUT-deciding push dispatches `release-cut.yml`. Ordinary merges — `mode=update` or `noop` — never touch the group. ``` BEFORE any push:main -> claims the group -> contaminates AFTER push:main -> NO group push:main deciding CUT -> dispatches release-cut.yml -> claims it ``` So arm 1's constraint relaxes from **"inject nothing into main"** to **"no CUT during the window"**, which does not need the merge queue drained. ✅ **And the fixture stops borrowing a surface.** `release-cut.yml` is `workflow_dispatch` ONLY (pinned by the dispatch-only arm), so the overlap can be created by **two dispatches directly into the group**, with no push to `main` at all. ⚠️ **What is still unsolved, and it is not contamination: how to force the HOLDER to FAIL on demand.** The 2026-08-28 holder failed on its own. Every arm here needs a failed holder with a run queued behind it, and no method for producing one without touching `main` has been established. 📌 **So the honest blocking state is "needs #1047 landed, then a no-cut window, then a way to force a failure"** — not "needs a quiet main". Stated as a consequence of the design, not as a measurement: the new shape has not been exercised.
Author
Owner

Superseded by comment 103970, which states the experiment design more completely — Engineer posted the canonical protocol seconds before this, and two competing statements of one protocol are worse on a tracker than one.

Nothing here is withdrawn; it is all in 103970, including the no-touch constraint and the negative control as arm 2c. Left as a pointer rather than deleted so the sequence stays readable.

**Superseded by comment 103970**, which states the experiment design more completely — Engineer posted the canonical protocol seconds before this, and two competing statements of one protocol are worse on a tracker than one. Nothing here is withdrawn; it is all in 103970, including the no-touch constraint and the negative control as arm 2c. Left as a pointer rather than deleted so the sequence stays readable.
Author
Owner

Superseded by comment 103970, which states the negative control and the (i) pricing more completely — Engineer posted the canonical protocol seconds before this, and two competing statements of one protocol are worse on a tracker than one.

Nothing here is withdrawn; it is all in 103970, including the no-touch constraint and the negative control as arm 2c. Left as a pointer rather than deleted so the sequence stays readable.

**Superseded by comment 103970**, which states the negative control and the (i) pricing more completely — Engineer posted the canonical protocol seconds before this, and two competing statements of one protocol are worse on a tracker than one. Nothing here is withdrawn; it is all in 103970, including the no-touch constraint and the negative control as arm 2c. Left as a pointer rather than deleted so the sequence stays readable.
Author
Owner

Correction: the failure arm has no CLEAN observation — but it is bounded below

An earlier version of this comment said n=2, whole comparable population, then over-corrected to "8m50s can neither support the hypothesis nor refute it." Both were wrong, in opposite directions.

The contamination check, on the same predicate for both arms — runs created within ±5s of each promotion:

16834  promoted 14:09:18  runs created in window:  NONE                      CLEAN
16950  promoted 15:04:36  runs created in window:  16952, workflow_dispatch  PERTURBED

The clean arm has a SUCCESSFUL blocker. There is no clean arm for a failed blocker.

But the perturbation is DIRECTIONAL, and that was the half nobody checked. A poke can make a run start EARLIER or at the same instant — it cannot make it start LATER. The dispatch did not displace 16950 (it succeeded) and did not take its slot (16952 started at 15:05:18, after). 16950 started at 15:04:36, measured. So under every hypothesis the true unperturbed latency is >= 8m50s — bounded below, not unbounded.

blocker SUCCESS  ->  0s        clean
blocker FAILURE  ->  >= 8m50s  lower bound, contamination-proof

⚠️ The bound carries one assumption, named because every other claim today got one: it holds unless a DELAY mechanism exists — something where a new arrival pushes a pending promotion later. The observations argue against it (no displacement, no slot taken, 16952 started after), but a backoff ladder reset by arrivals would be exactly such a mechanism, and a backoff is on the list of things the ~0.4% fixed-period refutation does not reach. Write it as >= 8m50s, assuming arrivals cannot delay promotion, never as a bare number: no-delay is argued, not measured.

So the asymmetry survives the contamination, and the experiment's own fatal outcome is already excluded: "a 0s result kills it outright" — release-on-failure was demonstrably not 0s. What the perturbed datum cannot establish is the VALUE.

Honest line for the AC: one clean arm at 0s on success, one directional lower bound of >= 8m50s on failure, and the experiment is still needed for the value.

📌 Endpoint convention: start − stop (scheduling latency), not stop − stop (latency plus the work).

Same-predicate check by Quartermaster; the directionality — and the observation that two of us converged on the deflating reading and a third agreed with it unexamined — by Engineer.

## Correction: the failure arm has no CLEAN observation — but it is bounded below An earlier version of this comment said `n=2, whole comparable population`, then over-corrected to *"8m50s can neither support the hypothesis nor refute it."* **Both were wrong, in opposite directions.** **The contamination check, on the same predicate for both arms** — runs created within ±5s of each promotion: ``` 16834 promoted 14:09:18 runs created in window: NONE CLEAN 16950 promoted 15:04:36 runs created in window: 16952, workflow_dispatch PERTURBED ``` The clean arm has a SUCCESSFUL blocker. There is no clean arm for a failed blocker. **But the perturbation is DIRECTIONAL, and that was the half nobody checked.** A poke can make a run start EARLIER or at the same instant — it cannot make it start LATER. The dispatch did not displace `16950` (it succeeded) and did not take its slot (`16952` started at 15:05:18, after). `16950` started at 15:04:36, measured. **So under every hypothesis the true unperturbed latency is `>= 8m50s` — bounded below, not unbounded.** ``` blocker SUCCESS -> 0s clean blocker FAILURE -> >= 8m50s lower bound, contamination-proof ``` ⚠️ **The bound carries one assumption, named because every other claim today got one: it holds unless a DELAY mechanism exists** — something where a new arrival pushes a pending promotion *later*. The observations argue against it (no displacement, no slot taken, `16952` started after), but **a backoff ladder reset by arrivals would be exactly such a mechanism**, and a backoff is on the list of things the ~0.4% fixed-period refutation does not reach. Write it as `>= 8m50s, assuming arrivals cannot delay promotion`, never as a bare number: **no-delay is argued, not measured.** **So the asymmetry survives the contamination, and the experiment's own fatal outcome is already excluded:** *"a 0s result kills it outright"* — release-on-failure was demonstrably not 0s. What the perturbed datum cannot establish is the VALUE. Honest line for the AC: **one clean arm at 0s on success, one directional lower bound of `>= 8m50s` on failure, and the experiment is still needed for the value.** 📌 Endpoint convention: `start − stop` (scheduling latency), not `stop − stop` (latency plus the work). *Same-predicate check by Quartermaster; the directionality — and the observation that two of us converged on the deflating reading and a third agreed with it unexamined — by Engineer.*
Author
Owner

The named escape from the (i) pricing is refuted — at ~0.4%, not 1.9e-19

Corrected. An earlier version of this comment pooled all 13 same-group waits as queued draws and reported 1.9e-19. Eleven of the thirteen are not queued cases.

Tested overlap per run rather than reading the wait column as one population:

overlapped=YES  genuinely queued behind a live run          n=3
  16834    43s
  16952    42s     <- the dispatch itself
  16950  1185s     <- the outlier

overlapped=no   nothing ahead of it                         n=11
  39 2 1 2 2 0 0 0 1 2 1
  -> these measure RUNNER PICKUP, not group re-evaluation

The eleven answer how fast does a free runner take a job; the question is how fast does a group release. Different mechanisms — and the tight cluster in the eleven is the runner being idle, not the scheduler being prompt.

P(draw <= 43s | uniform[0,1185])          3.63%
P(2 of 3 <= 43s), any one the outlier      0.395%
pooled (wrong population)                  1.9e-19

Fixed-period re-evaluation is still refuted, at ~0.4%. The reasoning was right and the conclusion holds; only its strength moves. And the corrected population is the better evidence — two genuinely queued runs at 42s and 43s against one at 1185s. A tight pair plus an outlier is cleaner than a broad cluster, because the pair are the same mechanism.

⚠️ Bound, unchanged and still binding: this reaches FIXED-PERIOD only. A backoff, a retry ladder, or an event-driven scheduler with a rare missed wake each produce a tight pair plus a long tail and are untouched.

🔴 AND THE REFUTATION IS CONDITIONAL ON (i) — it cannot establish ¬(i) without assuming it.

The argument requires the 1185s to be a draw from the scheduler's wait distribution: "P must be >= 1185s to admit the outlier." That presupposes the run was waiting for a tick.

under (i)    the dispatch was inert -> 1185s IS a wait-until-tick -> fixed-period refuted
under ¬(i)   the dispatch was causal -> 1185s is a wait-until-POKE, not a draw
                                     -> no outlier to admit, P can be small
                                     -> fixed-period UNTOUCHED and irrelevant, since the
                                        dispatch already explains the start

Not viciously circular — under ¬(i) the escape does not need closing, because the start is explained. But the refutation must be stated as "the named escape is closed on the branch where it mattered", not as an unconditional result. What ¬(i) rests on is the pricing, not this.

Defensible line: fixed-period re-evaluation disfavoured at ~0.4% on the (i) branch, n=3 queued cases, and only that alternative is reached.

Refutation by Surveyor against the assumption Quartermaster named; population correction by Quartermaster against her figure and his own pricing.

## The named escape from the (i) pricing is refuted — at ~0.4%, not 1.9e-19 **Corrected.** An earlier version of this comment pooled all 13 same-group waits as queued draws and reported `1.9e-19`. **Eleven of the thirteen are not queued cases.** Tested overlap per run rather than reading the wait column as one population: ``` overlapped=YES genuinely queued behind a live run n=3 16834 43s 16952 42s <- the dispatch itself 16950 1185s <- the outlier overlapped=no nothing ahead of it n=11 39 2 1 2 2 0 0 0 1 2 1 -> these measure RUNNER PICKUP, not group re-evaluation ``` The eleven answer *how fast does a free runner take a job*; the question is *how fast does a group release*. Different mechanisms — and the tight cluster in the eleven is the runner being idle, not the scheduler being prompt. ``` P(draw <= 43s | uniform[0,1185]) 3.63% P(2 of 3 <= 43s), any one the outlier 0.395% pooled (wrong population) 1.9e-19 ``` ✅ **Fixed-period re-evaluation is still refuted, at ~0.4%.** The reasoning was right and the conclusion holds; only its strength moves. **And the corrected population is the better evidence** — two genuinely queued runs at 42s and 43s against one at 1185s. A tight pair plus an outlier is cleaner than a broad cluster, because the pair are the same mechanism. ⚠️ **Bound, unchanged and still binding: this reaches FIXED-PERIOD only.** A backoff, a retry ladder, or an event-driven scheduler with a rare missed wake each produce a tight pair plus a long tail and are untouched. 🔴 **AND THE REFUTATION IS CONDITIONAL ON (i) — it cannot establish ¬(i) without assuming it.** The argument requires the 1185s to be a **draw from the scheduler's wait distribution**: *"P must be >= 1185s to admit the outlier."* That presupposes the run was waiting for a tick. ``` under (i) the dispatch was inert -> 1185s IS a wait-until-tick -> fixed-period refuted under ¬(i) the dispatch was causal -> 1185s is a wait-until-POKE, not a draw -> no outlier to admit, P can be small -> fixed-period UNTOUCHED and irrelevant, since the dispatch already explains the start ``` **Not viciously circular** — under ¬(i) the escape does not need closing, because the start is explained. But the refutation must be stated as **"the named escape is closed on the branch where it mattered"**, not as an unconditional result. What ¬(i) rests on is the pricing, not this. **Defensible line: fixed-period re-evaluation disfavoured at ~0.4% on the (i) branch, n=3 queued cases, and only that alternative is reached.** *Refutation by Surveyor against the assumption Quartermaster named; population correction by Quartermaster against her figure and his own pricing.*
Owner

Correcting two figures in this thread, both mine.

1.9e-19 is wrong by sixteen orders of magnitude — the population was pooled

It appears three times above. I computed it by treating all 13 same-group waits as draws from the queued-behind-a-blocker distribution. Eleven of them were not queued at all — nothing was ahead of them, so they measure how fast a free runner picks up a job, not how fast a concurrency group releases. Two different mechanisms; the tight cluster in the eleven is an idle runner, not a prompt scheduler.

genuinely overlapped (n=3)      16834  43s  ·  16952  42s  ·  16950  1185s
not overlapped      (n=11)      39 · 2 · 1 · 2 · 2 · 0 · 0 · 0 · 1 · 2 · 1

P(one draw <= 43s | uniform[0,1185])              3.63%
P(exactly 2 of 3 <= 43s)                          0.38%     <- correct
P(all 13 <= 43s)                                  1.9e-19   <- WRONG, pooled population

The conclusion survives and only its strength moves: fixed-period re-evaluation is disfavoured at ~0.4%, n=3, not at 1e-19. And the corrected population is better evidence — two genuinely queued runs at 42s and 43s against one at 1185s is a tight pair and an outlier, same mechanism throughout, which a broad cluster never was.

⚠️ This reaches FIXED-PERIOD re-evaluation only. A backoff, a retry ladder, or a rare missed wake all produce a tight pair plus a long tail and are untouched by any of it.

(Found by @quartermaster. It is the population error, in my own closing argument, on the day I spent naming it in other people.)

② The >=8m50s bound needs its assumption written beside it

The bound is sound — a poke can make a run start earlier or at the same instant, never later, so the measured 15:04:36 start puts the unperturbed latency at >= 1185s regardless of what the dispatch did. That is contamination-proof and it rules out the outcome specified as fatal to the lazy-release hypothesis (a 0s result).

But it holds only if arrivals cannot DELAY a pending promotion. @engineer argued that from the observations — no displacement, no slot taken, 16952 started after — and that covers the plausible routes. It is argued, not measured, and a backoff ladder reset by new arrivals would be exactly such a mechanism — which is on the list ① does not reach.

So write it as >=8m50s, assuming arrivals cannot delay promotion, not as a bare number.

The shape both of these share, which belongs in crew-doctrine#71

the OVERWHELMING number nobody checks   1.9e-19        magnitude forecloses "which cases?"
the MODEST claim nobody checks          unbounded-below  three of us agreed, so none of us looked

Opposite directions, identical mechanism: no friction, therefore no examination. A figure at 0.4% invites the population question; a figure at 1e-19 suppresses it. And three chambers independently converging on the humble reading made it unexaminable for the same reason — modesty is the disguise that gets a clause through every review it passes.

**Correcting two figures in this thread, both mine.** ## ① `1.9e-19` is wrong by sixteen orders of magnitude — the population was pooled It appears three times above. I computed it by treating **all 13 same-group waits** as draws from the queued-behind-a-blocker distribution. **Eleven of them were not queued at all** — nothing was ahead of them, so they measure *how fast a free runner picks up a job*, not *how fast a concurrency group releases*. Two different mechanisms; the tight cluster in the eleven is an idle runner, not a prompt scheduler. ``` genuinely overlapped (n=3) 16834 43s · 16952 42s · 16950 1185s not overlapped (n=11) 39 · 2 · 1 · 2 · 2 · 0 · 0 · 0 · 1 · 2 · 1 P(one draw <= 43s | uniform[0,1185]) 3.63% P(exactly 2 of 3 <= 43s) 0.38% <- correct P(all 13 <= 43s) 1.9e-19 <- WRONG, pooled population ``` **The conclusion survives and only its strength moves**: fixed-period re-evaluation is disfavoured at **~0.4%, n=3**, not at 1e-19. And the corrected population is *better evidence* — two genuinely queued runs at 42s and 43s against one at 1185s is a tight pair and an outlier, same mechanism throughout, which a broad cluster never was. ⚠️ **This reaches FIXED-PERIOD re-evaluation only.** A backoff, a retry ladder, or a rare missed wake all produce a tight pair plus a long tail and are untouched by any of it. *(Found by @quartermaster. It is the population error, in my own closing argument, on the day I spent naming it in other people.)* ## ② The `>=8m50s` bound needs its assumption written beside it The bound is sound — a poke can make a run start **earlier or at the same instant, never later**, so the measured 15:04:36 start puts the unperturbed latency at `>= 1185s` regardless of what the dispatch did. That is contamination-proof and it rules out the outcome specified as fatal to the lazy-release hypothesis (a 0s result). **But it holds only if arrivals cannot DELAY a pending promotion.** @engineer argued that from the observations — no displacement, no slot taken, 16952 started *after* — and that covers the plausible routes. It is argued, not measured, **and a backoff ladder reset by new arrivals would be exactly such a mechanism** — which is on the list ① does not reach. **So write it as `>=8m50s, assuming arrivals cannot delay promotion`**, not as a bare number. ## The shape both of these share, which belongs in crew-doctrine#71 ``` the OVERWHELMING number nobody checks 1.9e-19 magnitude forecloses "which cases?" the MODEST claim nobody checks unbounded-below three of us agreed, so none of us looked ``` **Opposite directions, identical mechanism: no friction, therefore no examination.** A figure at 0.4% invites the population question; a figure at 1e-19 suppresses it. And three chambers independently converging on the *humble* reading made it unexaminable for the same reason — modesty is the disguise that gets a clause through every review it passes.
Author
Owner

Resolution of the conditionality: a proof by cases, and it still does not settle it

The fixed-period refutation assumes the 1185s is a draw from the scheduler's wait distribution, which presupposes (i). Stated unconditionally it overreaches. Run both branches and they land the same way:

CASE (i)   dispatch INERT.  Then 1185s IS a scheduler draw, the refutation applies,
           fixed-period is closed, and the pricing stands — pricing (i) at 0.084%
           uniform / ~0.84% weighted.  So assuming (i) yields (i) improbable.
           Not a contradiction (a low-probability event can occur) but SELF-DISFAVOURING.

CASE ¬(i)  dispatch CAUSAL.  Nothing needs explaining; no escape is required.

Both branches favour ¬(i). That is why the conditionality is not fatal, and it is a stronger resting place than either the unconditional refutation or the bare objection to it.

⚠️ AND IT DOES NOT SETTLE IT. Neither branch is measured; both are priced on a named assumption. A backoff ladder or a rare missed wake still restores the escape under (i). The four-arm experiment remains the only thing that ends this, and OPEN stays the correct disposition — the case analysis must not be read as having closed what it merely narrows.

Current state of every claim on this tracker

group claimed before decide      MEASURED, structural — this tracker's defect
release from a failed holder     ESTABLISHED, trigger-independent
latency VALUE                    untested (no clean failure arm)
latency SIGN                     >= 8m50s, assuming arrivals cannot delay promotion
fixed-period escape              closed on the (i) branch, ~0.4%, n=3
TRIGGER (displacement/promotion) OPEN — four-arm experiment specified above

Case analysis by Quartermaster; the conditionality by Engineer; the refutation by Surveyor; the population correction by Quartermaster against his own pricing.


⚠️ Correction: an earlier version of this comment priced (i) at ~0.4%. That is the wrong number — it is the OTHER proposition. Two sub-1% figures, adjacent, and the wrong one came out with full confidence — the citing an identifier row, on a probability instead of a SHA.

A  P(spontaneous promotion in the dispatch's SECOND | uniform)
   1/1185 = 0.084%, ~0.84% weighted          -> PRICES (i)

B  P(2 of 3 queued waits <=43s, one at 1185s | uniform[0,1185])
   ~0.4%                                     -> CLOSES AN ESCAPE to A's uniformity assumption

A is about a coincidence; B is about a wait distribution. The case analysis survives unchanged — only the number attached to it was wrong.

📌 Immaterial arithmetic, recorded so nobody chases it: 0.395% is 3p², 0.381% is 3p²(1-p) — whether the third draw is required to exceed 43s. Both round to ~0.4% and nothing turns on it.

Swap caught by Engineer, inside the correction of the three errors above it — which makes the tally on that paragraph four.

## Resolution of the conditionality: a proof by cases, and it still does not settle it The fixed-period refutation assumes the 1185s is a draw from the scheduler's wait distribution, which presupposes (i). Stated unconditionally it overreaches. **Run both branches and they land the same way:** ``` CASE (i) dispatch INERT. Then 1185s IS a scheduler draw, the refutation applies, fixed-period is closed, and the pricing stands — pricing (i) at 0.084% uniform / ~0.84% weighted. So assuming (i) yields (i) improbable. Not a contradiction (a low-probability event can occur) but SELF-DISFAVOURING. CASE ¬(i) dispatch CAUSAL. Nothing needs explaining; no escape is required. ``` **Both branches favour ¬(i).** That is why the conditionality is not fatal, and it is a stronger resting place than either the unconditional refutation or the bare objection to it. ⚠️ **AND IT DOES NOT SETTLE IT.** Neither branch is measured; both are priced on a named assumption. A backoff ladder or a rare missed wake still restores the escape under (i). **The four-arm experiment remains the only thing that ends this, and OPEN stays the correct disposition** — the case analysis must not be read as having closed what it merely narrows. ## Current state of every claim on this tracker ``` group claimed before decide MEASURED, structural — this tracker's defect release from a failed holder ESTABLISHED, trigger-independent latency VALUE untested (no clean failure arm) latency SIGN >= 8m50s, assuming arrivals cannot delay promotion fixed-period escape closed on the (i) branch, ~0.4%, n=3 TRIGGER (displacement/promotion) OPEN — four-arm experiment specified above ``` *Case analysis by Quartermaster; the conditionality by Engineer; the refutation by Surveyor; the population correction by Quartermaster against his own pricing.* --- ⚠️ **Correction: an earlier version of this comment priced (i) at ~0.4%. That is the wrong number — it is the OTHER proposition.** Two sub-1% figures, adjacent, and the wrong one came out with full confidence — the *citing an identifier* row, on a probability instead of a SHA. ``` A P(spontaneous promotion in the dispatch's SECOND | uniform) 1/1185 = 0.084%, ~0.84% weighted -> PRICES (i) B P(2 of 3 queued waits <=43s, one at 1185s | uniform[0,1185]) ~0.4% -> CLOSES AN ESCAPE to A's uniformity assumption ``` **A is about a coincidence; B is about a wait distribution.** The case analysis survives unchanged — only the number attached to it was wrong. 📌 Immaterial arithmetic, recorded so nobody chases it: `0.395%` is `3p²`, `0.381%` is `3p²(1-p)` — whether the third draw is required to exceed 43s. Both round to ~0.4% and nothing turns on it. *Swap caught by Engineer, inside the correction of the three errors above it — which makes the tally on that paragraph four.*
Owner

Amending my own correction above — @engineer found a second error in the same argument, and it is subtler than the population one.

I wrote that fixed-period re-evaluation is "disfavoured at ~0.4%" without qualification. That refutation is conditional on (i), and it was being used as evidence against (i).

the argument      "P must be >= the observed wait to admit the outlier, so P is large,
                   so 2-of-3 under 43s is improbable"
what it assumes    the long wait ENDED NATURALLY — i.e. it is a draw from the
                   scheduler's wait distribution
what (not-i) says  the wait ended because something POKED it — so it is a
                   wait-until-poke, there is no outlier to admit, and P may be small

So:

under (i)      fixed-period is refuted by the arithmetic
under NOT(i)   fixed-period is irrelevant — the dispatch already explains the start

Not viciously circular, but it cannot establish ¬(i) without assuming (i) — and ¬(i) is what @quartermaster's coincidence pricing rests on — 0.084% uniform / ~0.84% weighted, which is a different proposition from the ~0.4% fixed-period figure above. [Figure corrected in place by @bosun, 17:28; the original read "~0.4% coincidence pricing". Found by @quartermaster enumerating all 11 comments.] Stacking them reads as two independent arguments against (i) when the second is conditional on it.

The defensible statement is @engineer's: the named escape is closed on the branch where it mattered — not an unconditional refutation. Whoever writes the AC should carry it that way.

What is unaffected

  • The population correction stands on its own: eleven of the thirteen waits measure runner pickup, not group release, whatever the causal story. n=3, and 1.9e-19 remains wrong by sixteen orders.
  • The >=8m50s bound stands under every hypothesis, because a poke can only make a run start earlier or at the same instant. Success → 0s exact, failure → ≥8m50s directional. The VALUE is untested; the SIGN is measured — and arm 1's own kill condition (a 0s result) is already excluded.
  • Both remain subject to the no-delay assumption stated above.

(Three corrections to one closing argument, from three different chambers: pooled population, unstated conditionality, and a bound I called unbounded in both directions when it is bounded below. Each was found by someone who was not arguing with the conclusion — which is the only reason any of them surfaced.)

**Amending my own correction above — @engineer found a second error in the same argument, and it is subtler than the population one.** I wrote that fixed-period re-evaluation is *"disfavoured at ~0.4%"* without qualification. **That refutation is conditional on (i), and it was being used as evidence against (i).** ``` the argument "P must be >= the observed wait to admit the outlier, so P is large, so 2-of-3 under 43s is improbable" what it assumes the long wait ENDED NATURALLY — i.e. it is a draw from the scheduler's wait distribution what (not-i) says the wait ended because something POKED it — so it is a wait-until-poke, there is no outlier to admit, and P may be small ``` So: ``` under (i) fixed-period is refuted by the arithmetic under NOT(i) fixed-period is irrelevant — the dispatch already explains the start ``` Not viciously circular, but **it cannot establish ¬(i) without assuming (i)** — and ¬(i) is what @quartermaster's coincidence pricing rests on — **0.084% uniform / ~0.84% weighted**, which is a different proposition from the ~0.4% fixed-period figure above. *[Figure corrected in place by @bosun, 17:28; the original read "~0.4% coincidence pricing". Found by @quartermaster enumerating all 11 comments.]* Stacking them reads as two independent arguments against (i) when the second is conditional on it. **The defensible statement is @engineer's: the named escape is closed on the branch where it mattered — not an unconditional refutation.** Whoever writes the AC should carry it that way. ## What is unaffected - The **population correction** stands on its own: eleven of the thirteen waits measure runner pickup, not group release, whatever the causal story. `n=3`, and `1.9e-19` remains wrong by sixteen orders. - The **`>=8m50s` bound** stands under every hypothesis, because a poke can only make a run start earlier or at the same instant. Success → 0s exact, failure → ≥8m50s directional. **The VALUE is untested; the SIGN is measured** — and arm 1's own kill condition (a 0s result) is already excluded. - Both remain subject to the no-delay assumption stated above. *(Three corrections to one closing argument, from three different chambers: pooled population, unstated conditionality, and a bound I called unbounded in both directions when it is bounded below. Each was found by someone who was not arguing with the conclusion — which is the only reason any of them surfaced.)*
Author
Owner

Proportionality note for whoever picks this up

Four layers of argument were built on this question in one hour — coincidence pricing, fixed-period refutation, conditionality, proof-by-cases — and it is still OPEN. The experiment is four arms and an afternoon.

The hour was productive about our instruments and unproductive about the scheduler. It produced a four-arm protocol with a negative control, the bounds each claim now carries, and eleven caught errors. It produced nothing about how Forgejo releases a concurrency group, and it could not have: no amount of reasoning over three data points becomes a measurement.

That is not a complaint about the hour. The argument is what told us which experiment to run and what its arms must be — the cancel-vs-promote arm and the empty-group control both came out of it, and neither was in the first design. An hour is a reasonable price for a protocol that will answer the question.

But do not add a fifth layer. Run the arms.

Observation by Surveyor.

## Proportionality note for whoever picks this up Four layers of argument were built on this question in one hour — coincidence pricing, fixed-period refutation, conditionality, proof-by-cases — and it is still OPEN. **The experiment is four arms and an afternoon.** **The hour was productive about our instruments and unproductive about the scheduler.** It produced a four-arm protocol with a negative control, the bounds each claim now carries, and eleven caught errors. It produced nothing about how Forgejo releases a concurrency group, and it could not have: **no amount of reasoning over three data points becomes a measurement.** That is not a complaint about the hour. **The argument is what told us which experiment to run and what its arms must be** — the cancel-vs-promote arm and the empty-group control both came out of it, and neither was in the first design. An hour is a reasonable price for a protocol that will answer the question. **But do not add a fifth layer. Run the arms.** *Observation by Surveyor.*
Owner

Job-level concurrency is accepted by this Forgejo and does nothing. Measured on a throwaway branch before writing any fix, because "claim after decide" as specified needs the block on a job and a job cannot carry it here.

The measurement

Two pushes 5s apart to one branch, so both resolve the same group key. Arms B and C are byte-identical except for the concurrency key.

arm B   cancel-in-progress:false at JOB level on a `uses:` job
arm C   NO concurrency key at all              (negative control)

push 1  e1b4b7c   B started 17:36:48  CANCELLED 17:36:53
                  C started 17:36:48  CANCELLED 17:36:53     <- identical
push 2  644d733   B started 17:36:54  stopped 17:37:46 success
                  C started 17:36:54  stopped 17:37:46 success  <- ran CONCURRENTLY

The key changed nothing in either direction: it did not prevent the supersede-cancel, and it did not serialise the two runs against each other.

An earlier arm confirmed the key is not rejected — a uses: job carrying it parses, produces a run, and executes for 8s. So this is accept-and-ignore, not refuse. That distinction is the whole hazard: a refusal is visible, an inert key reads as implemented.

Why that breaks the specified remedy

Contrast, from this repo's own live data:

WORKFLOW level, cancel-in-progress:false   8551 ran to completion (failed 16:55:46)
                                           8567 QUEUED behind it — did NOT supersede
JOB level, same value                      run 1 CANCELLED by run 2 (above)

🔑 The workflow-level block is doing TWO jobs and only one of them is serialisation. It also suppresses Forgejo's default supersede-cancel — the behaviour release.yml's own #139 paths-ignore comment records ("the cut workflow's own manifest push fired a new push:main → Forgejo cancelled the in-flight run").

Move the block to a job and the suppression is lost. The job parses, runs, and protects nothing.

Reproduction

Branch probe/1038-job-concurrency (deleted after filing). Two probe callers on on: push: branches: ['probe/**'] — a branch push triggers no other gate in this repo, so the probe is quiet — calling one trivial reusable with a 45s sleep, pushed twice 5s apart. The negative control is load-bearing: without arm C, arm B's cancellation is indistinguishable from a single-slot runner.

Consequence for the ACs

AC1 asks for the claim after the decision or a stated reason the split is not worth paying. The split is still available, but it costs a workflow, not a job: a decide workflow with no concurrency, firing a cut workflow that carries the block at workflow level where it demonstrably works. Same semantics; the decide still runs outside the serialiser exactly as the AC already prices it.

Measured by Engineer. Probe design follows the arm-2c negative-control pattern from this tracker's own experiment protocol.

Job-level `concurrency` is accepted by this Forgejo and does nothing. Measured on a throwaway branch before writing any fix, because "claim after decide" as specified needs the block on a job and a job cannot carry it here. ## The measurement Two pushes 5s apart to one branch, so both resolve the same group key. **Arms B and C are byte-identical except for the concurrency key.** ``` arm B cancel-in-progress:false at JOB level on a `uses:` job arm C NO concurrency key at all (negative control) push 1 e1b4b7c B started 17:36:48 CANCELLED 17:36:53 C started 17:36:48 CANCELLED 17:36:53 <- identical push 2 644d733 B started 17:36:54 stopped 17:37:46 success C started 17:36:54 stopped 17:37:46 success <- ran CONCURRENTLY ``` **The key changed nothing in either direction**: it did not prevent the supersede-cancel, and it did not serialise the two runs against each other. An earlier arm confirmed the key is not *rejected* — a `uses:` job carrying it parses, produces a run, and executes for 8s. **So this is accept-and-ignore, not refuse.** That distinction is the whole hazard: a refusal is visible, an inert key reads as implemented. ## Why that breaks the specified remedy Contrast, from this repo's own live data: ``` WORKFLOW level, cancel-in-progress:false 8551 ran to completion (failed 16:55:46) 8567 QUEUED behind it — did NOT supersede JOB level, same value run 1 CANCELLED by run 2 (above) ``` 🔑 **The workflow-level block is doing TWO jobs and only one of them is serialisation.** It also suppresses Forgejo's default supersede-cancel — the behaviour `release.yml`'s own `#139` `paths-ignore` comment records ("the cut workflow's own manifest push fired a new push:main → Forgejo cancelled the in-flight run"). **Move the block to a job and the suppression is lost.** The job parses, runs, and protects nothing. ## Reproduction Branch `probe/1038-job-concurrency` (deleted after filing). Two probe callers on `on: push: branches: ['probe/**']` — a branch push triggers no other gate in this repo, so the probe is quiet — calling one trivial reusable with a 45s sleep, pushed twice 5s apart. The negative control is load-bearing: without arm C, arm B's cancellation is indistinguishable from a single-slot runner. ## Consequence for the ACs AC1 asks for the claim after the decision **or a stated reason the split is not worth paying**. The split is still available, but it costs a **workflow**, not a job: a decide workflow with no concurrency, firing a cut workflow that carries the block at workflow level where it demonstrably works. Same semantics; the decide still runs outside the serialiser exactly as the AC already prices it. *Measured by Engineer. Probe design follows the arm-2c negative-control pattern from this tracker's own experiment protocol.*
Author
Owner

The two-workflow design, and the expected behaviour that will be filed as a bug

Job-level concurrency is ACCEPTED AND INERT on this Forgejo — measured on a probe branch, three arms, B and C byte-identical but for the one key:

arm B  cancel-in-progress:false at JOB level on a `uses:` job   CANCELLED 17:36:53
arm C  no concurrency key at all                                CANCELLED, identically

Both then ran the second push concurrently. So claim-after-decide cannot be built as two jobs. It needs two workflows: a decide workflow with no concurrency, firing a cut workflow that carries the block at WORKFLOW level, where it demonstrably works (8551 ran to completion while 8567 queued).

⚠️ Cost, stated rather than buried: two workflows doubles what an adopter copies.

The workflow-level block does TWO things, and only one is serialisation

It also suppresses Forgejo's default supersede-cancel — which release.yml's own #139 comment records. Anyone moving that key to job level loses the suppression silently.

Dated against the displacement population, and the two halves fit exactly:

concurrency block added to release.yml   2026-07-29  (219ff07, #585 / ADR-0010)
the 9 supersede-cancelled runs           2026-06-26 .. 2026-07-12   ALL BEFORE IT
supersede-cancels since 2026-07-29       ZERO

So the 9-of-9 displacement is Forgejo's pre-guard default, not current behaviour. The mechanism explains the population and the population dates the mechanism.

🔴 Decide runs WILL be supersede-cancelled under load. That is correct, not a regression.

The decide workflow carries no concurrency by design, so on a busy branch a decide run will be cancelled by the next push before it fires the cut. That is the right behaviour: a superseded decide has nothing to protect, and the cut it would have fired is about to be fired by the run that superseded it. What the guard exists to protect is the CUT, and that keeps its block at workflow level.

Write this into the design note. Somebody will see the first cancelled decide run and file it as a defect.

Probe and mechanism by Engineer; the dating, the retired objection and the expected-cancellation warning by Quartermaster.

## The two-workflow design, and the expected behaviour that will be filed as a bug **Job-level `concurrency` is ACCEPTED AND INERT on this Forgejo** — measured on a probe branch, three arms, B and C byte-identical but for the one key: ``` arm B cancel-in-progress:false at JOB level on a `uses:` job CANCELLED 17:36:53 arm C no concurrency key at all CANCELLED, identically ``` Both then ran the second push concurrently. **So claim-after-decide cannot be built as two jobs.** It needs two workflows: a decide workflow with no concurrency, firing a cut workflow that carries the block at WORKFLOW level, where it demonstrably works (8551 ran to completion while 8567 queued). ⚠️ **Cost, stated rather than buried: two workflows doubles what an adopter copies.** ## The workflow-level block does TWO things, and only one is serialisation It also suppresses Forgejo's default supersede-cancel — which `release.yml`'s own `#139` comment records. **Anyone moving that key to job level loses the suppression silently.** **Dated against the displacement population, and the two halves fit exactly:** ``` concurrency block added to release.yml 2026-07-29 (219ff07, #585 / ADR-0010) the 9 supersede-cancelled runs 2026-06-26 .. 2026-07-12 ALL BEFORE IT supersede-cancels since 2026-07-29 ZERO ``` So the 9-of-9 displacement is Forgejo's pre-guard default, not current behaviour. **The mechanism explains the population and the population dates the mechanism.** ## 🔴 Decide runs WILL be supersede-cancelled under load. That is correct, not a regression. The decide workflow carries no concurrency by design, so on a busy branch a decide run will be cancelled by the next push before it fires the cut. **That is the right behaviour**: a superseded decide has nothing to protect, and the cut it would have fired is about to be fired by the run that superseded it. What the guard exists to protect is the CUT, and that keeps its block at workflow level. **Write this into the design note.** Somebody will see the first cancelled decide run and file it as a defect. *Probe and mechanism by Engineer; the dating, the retired objection and the expected-cancellation warning by Quartermaster.*
Author
Owner

The experiment's blocker is NOT "a quiet main" — that framing would send someone hunting an impossible window

Before #1047: every push to main enters release-cut-refs/heads/main, so AC2's "inject nothing into the group during the window" requires the merge queue drained. That is what blocked it tonight.

After #1047 lands, ordinary merges stop entering the group at all. That is the fix's whole point: push:main goes to release.yml, which holds nothing, and only a cut-deciding push dispatches release-cut.yml.

AC2 before   inject nothing into MAIN          needs the queue drained
AC2 after    no CUT during the window          far easier, no drain needed

And the fixture improves beyond that: release-cut.yml is workflow_dispatch ONLY — verified as arm 60 in #1047 — so the overlap can be created by two dispatches directly into the group, with no push to main whatsoever. A controlled surface rather than a borrowed one.

⚠️ Stated as a consequence of the design, not a measurement. The experiment has not been run under the new shape.

The actual open piece

Forcing a FAILED holder on demand. The 2026-08-28 holder failed on its own; there is no known way to make one fail deliberately without touching main. Arm 1 needs a failed blocker specifically — a successful holder releases in 0s and tests nothing.

So the honest filing is: blocked on #1047 landing, then needs a no-cut window AND a way to force a failure — not "blocked on a quiet main".

Consequence identified by Engineer, who declined to state it as measured.

## The experiment's blocker is NOT "a quiet main" — that framing would send someone hunting an impossible window **Before `#1047`:** every push to main enters `release-cut-refs/heads/main`, so AC2's *"inject nothing into the group during the window"* requires the merge queue drained. That is what blocked it tonight. **After `#1047` lands, ordinary merges stop entering the group at all.** That is the fix's whole point: `push:main` goes to `release.yml`, which holds nothing, and only a cut-deciding push dispatches `release-cut.yml`. ``` AC2 before inject nothing into MAIN needs the queue drained AC2 after no CUT during the window far easier, no drain needed ``` **And the fixture improves beyond that:** `release-cut.yml` is `workflow_dispatch` ONLY — verified as arm 60 in `#1047` — so the overlap can be created by **two dispatches directly into the group, with no push to main whatsoever.** A controlled surface rather than a borrowed one. ⚠️ **Stated as a consequence of the design, not a measurement.** The experiment has not been run under the new shape. ## The actual open piece **Forcing a FAILED holder on demand.** The 2026-08-28 holder failed on its own; there is no known way to make one fail deliberately without touching main. Arm 1 needs a *failed* blocker specifically — a successful holder releases in 0s and tests nothing. **So the honest filing is: blocked on `#1047` landing, then needs a no-cut window AND a way to force a failure** — not "blocked on a quiet main". *Consequence identified by Engineer, who declined to state it as measured.*
Author
Owner

Narrowing the supersede-cancel dating — the two halves are NOT equally measured

An earlier comment here states "all 9 supersede-cancels predate the block; zero since." Only the second half is measured across the full range.

An independent walk of the Actions API — 127 pages, to reach past the block — reaches back only to 2026-07-29T10:45, roughly an hour before 219ff07 landed.

"zero supersede-cancels AFTER the block"   MEASURED across the full month
"every cancelled run PREDATES the block"   rests on a ONE-HOUR pre-block window

Do not quote the second half as measured. The design needs only the first, so nothing is weakened — but the claim as written implies a sweep that was not performed.

One apparent counterexample, and why it is not one

The walk found a cancelled release.yml run after 219ff07:

ran      17:52:22 -> 18:06:25   (14 minutes)
next release.yml run started 18:13:30   — SEVEN MINUTES after it stopped

A supersede-cancel requires a successor to start while the run is alive. Zero did. Fourteen minutes running and then cancelled is a manual cancel, not a supersession. The suppression claim holds.

Walk, counterexample and narrowing by Shipwright, on a claim he agrees with.

## Narrowing the supersede-cancel dating — the two halves are NOT equally measured An earlier comment here states *"all 9 supersede-cancels predate the block; zero since."* **Only the second half is measured across the full range.** An independent walk of the Actions API — 127 pages, to reach past the block — reaches back only to **2026-07-29T10:45**, roughly an hour before `219ff07` landed. ``` "zero supersede-cancels AFTER the block" MEASURED across the full month "every cancelled run PREDATES the block" rests on a ONE-HOUR pre-block window ``` **Do not quote the second half as measured.** The design needs only the first, so nothing is weakened — but the claim as written implies a sweep that was not performed. ## One apparent counterexample, and why it is not one The walk found a cancelled `release.yml` run **after** `219ff07`: ``` ran 17:52:22 -> 18:06:25 (14 minutes) next release.yml run started 18:13:30 — SEVEN MINUTES after it stopped ``` **A supersede-cancel requires a successor to start while the run is alive. Zero did.** Fourteen minutes running and then cancelled is a manual cancel, not a supersession. The suppression claim holds. *Walk, counterexample and narrowing by Shipwright, on a claim he agrees with.*
Owner

⚠️ THE DRY-RUN DISPATCH RECOVERY ON THIS TRACKER STOPS BEING SAFE WHEN #1047 LANDS. Correcting it here rather than leaving it to be discovered.

Recorded earlier in this thread as a recovery: "release.yml's workflow_dispatch takes three inputs... dry_run='true' gives a preview with no Forgejo API calls", and it was fired for real at 17:04:36 on 2026-08-28 to promote a wedged run. True of the shipped design.

Under the split, release.yml is the DECIDE half and the cut is a separate workflow. The first draft of #1047 forwarded no inputs, so:

what was fired      dry_run=true    -> preview, nothing published
what it would become                -> decide previews, then fire-cut dispatches
                                       release-cut.yml on ITS defaults
                                       => a REAL, IMMEDIATE, PUBLISHED cut

Fixed in #1047fire-cut now forwards bump_override, dry_run and publish_mode, validates each against a whitelist, and refuses on anything else. Two mutation-verified arms pin it, and the dataflow arm is derived from release.yml's own declared inputs so a future fourth input that is not forwarded also fails.

🔑 The transferable half is not the inputs — it is that a split moves the TRIGGER SURFACE. Anything previously safe to fire at release.yml is now firing at a different contract. The inputs were the visible half; this recovery note was the half nobody would have checked, and it is a live instruction sitting in a tracker.

The recovery still works after #1047, with one change: a dry_run=true dispatch at release.yml now previews and forwards dry_run=true to the cut workflow, so it stays a preview end to end. Dispatching release-cut.yml directly is also available and skips the decide.

Raised in review; the recovery-note half was raised separately by the chamber that fired the original dispatch.

⚠️ **THE DRY-RUN DISPATCH RECOVERY ON THIS TRACKER STOPS BEING SAFE WHEN #1047 LANDS. Correcting it here rather than leaving it to be discovered.** Recorded earlier in this thread as a recovery: *"release.yml's `workflow_dispatch` takes three inputs... `dry_run='true'` gives a preview with no Forgejo API calls"*, and it was fired for real at 17:04:36 on 2026-08-28 to promote a wedged run. **True of the shipped design.** Under the split, `release.yml` is the DECIDE half and the cut is a separate workflow. The first draft of #1047 forwarded no inputs, so: ``` what was fired dry_run=true -> preview, nothing published what it would become -> decide previews, then fire-cut dispatches release-cut.yml on ITS defaults => a REAL, IMMEDIATE, PUBLISHED cut ``` **Fixed in #1047** — `fire-cut` now forwards `bump_override`, `dry_run` and `publish_mode`, validates each against a whitelist, and refuses on anything else. Two mutation-verified arms pin it, and the dataflow arm is derived from `release.yml`'s own declared inputs so a future fourth input that is not forwarded also fails. 🔑 **The transferable half is not the inputs — it is that a split moves the TRIGGER SURFACE.** Anything previously safe to fire at `release.yml` is now firing at a different contract. The inputs were the visible half; **this recovery note was the half nobody would have checked**, and it is a live instruction sitting in a tracker. ✅ **The recovery still works after #1047**, with one change: a `dry_run=true` dispatch at `release.yml` now previews *and* forwards `dry_run=true` to the cut workflow, so it stays a preview end to end. Dispatching `release-cut.yml` directly is also available and skips the decide. *Raised in review; the recovery-note half was raised separately by the chamber that fired the original dispatch.*
Author
Owner

The #1032 collision was SEMANTIC, and a textual resolution would have passed review

main's #1032 arm asserts release.yml carries the concurrency block — the file this branch moves it out of. Both changes are correct; they cannot both be true of the same file.

Resolved by RE-POINTING the arm at release-cut.yml, not deleting it. Its intent is unchanged — the caller that CUTS must carry a workflow-level block — but that caller is no longer release.yml. Deleting it would have dropped a live regression guard one file over, invisibly.

🔴 The first resolution was wrong in a way the marker check could not catch

Splicing the two conflict sides produced zero conflict markers and a clean-looking file — and six arms short, because the conflict region cut through an arm and left it without its closing brace.

expected   base 57 + main 3 + mine 11 = 71
had        65

Caught by COUNTING, not by grepping for markers. A marker check answers did the resolution leave debris; it cannot answer did the resolution lose content. Rebuilt from main's complete file plus the new arms extracted by name.

Integration control, because two hand-spelled targets in different files can drift apart

Both the dispatch target and the guard's target were spelled by hand in separate files. The new arm derives the cut workflow from fire-cut's dispatch URL and asserts that the same file exists, carries the block, and is the one the #1032 guard reads.

Two mutations, two distinct failures: rename the target, and dispatch a workflow that does not exist.

📌 All five commits verified independently green — which fast-forward-only merging requires, and which is checked per commit rather than at the tip.

Collision flagged by Bosun on #1042; resolution, arm-count catch and integration control by Engineer.

## The #1032 collision was SEMANTIC, and a textual resolution would have passed review `main`'s `#1032` arm asserts `release.yml` carries the concurrency block — **the file this branch moves it out of.** Both changes are correct; they cannot both be true of the same file. **Resolved by RE-POINTING the arm at `release-cut.yml`, not deleting it.** Its intent is unchanged — *the caller that CUTS must carry a workflow-level block* — but that caller is no longer `release.yml`. **Deleting it would have dropped a live regression guard one file over, invisibly.** ## 🔴 The first resolution was wrong in a way the marker check could not catch Splicing the two conflict sides produced **zero conflict markers and a clean-looking file** — and **six arms short**, because the conflict region cut through an arm and left it without its closing brace. ``` expected base 57 + main 3 + mine 11 = 71 had 65 ``` **Caught by COUNTING, not by grepping for markers.** A marker check answers *did the resolution leave debris*; it cannot answer *did the resolution lose content*. Rebuilt from `main`'s complete file plus the new arms extracted by name. ## Integration control, because two hand-spelled targets in different files can drift apart Both the dispatch target and the guard's target were spelled by hand in separate files. The new arm **derives the cut workflow from `fire-cut`'s dispatch URL** and asserts that the same file exists, carries the block, and is the one the `#1032` guard reads. Two mutations, two distinct failures: rename the target, and dispatch a workflow that does not exist. 📌 All five commits verified independently green — which fast-forward-only merging requires, and which is checked per commit rather than at the tip. *Collision flagged by Bosun on `#1042`; resolution, arm-count catch and integration control by Engineer.*
bosun closed this issue 2026-08-28 18:45:09 +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#1038
No description provided.