drills/README.md — doors-unchanged condition 2 keeps its own copy of the release path, and the copy has been one file short since #198 #251

Closed
opened 2026-08-24 15:42:19 +00:00 by claude-bot-andresmgsl · 10 comments

Context

drills/README.md's Doors unchanged condition 2 names the release path
twice: it says the path is exactly the output of
.github/scripts/release-path.sh, and then prints its own copy of that
output. The copy is wrong on this tree, and has been since the doctrine
arrived here.

  • The README's copy — drills/README.md#L86-L91 — lists six paths: .github/workflows/release.yml, bin/, lib/version.sh, lib/decide.sh, lib/facts.sh, lib/changelog.sh.
  • The script — .github/scripts/release-path.sh — prints seven. lib/forge.sh is the seventh, and the script's own header says why: #191 put the release doors on the forge shim, so lib/forge.sh is part of the doors' executable path on this forge and stands for the two backends that are sourced through $FORGE_LIB_DIR at run time (#198).

Both landed in the same commit: the #198 merge e035130 created
.github/scripts/release-path.sh on this tree with the forge-adapted seventh
path, and took upstream's drills/README.md — whose condition 2 was written
at 75d85df (#237) against the six-path upstream script — unchanged beside
it. Neither side was wrong when written; the merge left them disagreeing, and
nothing has read them against each other since. The stale copy has stood
through both 0.6.1 and 0.6.2.

Why this is worth a fix and not a shrug. Condition 2 is one of three
conditions that let a release skip a live drill. A release author who
copy-pastes the README's list measures six of the seven paths and can assert
doors-unchanged while lib/forge.sh — a file both doors execute here —
changed underneath. That is the exact failure the doors-unchanged shape was
built to refuse: #217/#237 added the executable list because the 0.5.0
record had to explain a lib/ delta it had predicted away.

And the README tells the reader the copy is guarded when it is not.
Condition 2 ends "its contract test keeps this inline list and the workflow's
direct and transitive dependencies in agreement." test/release-path.test.sh
holds the script's list against the workflow's direct and transitive
dependencies; it never opens drills/README.md. Standing in the README,
"this inline list" reads as the list on the page — so the one sentence that
would make a reader check the script instead is the sentence that tells them
they do not have to.

Both live release records already did the right thing without the README:
drills/0.6.1.md and drills/0.6.2.md each quote release-path.sh's output,
seven paths included, and every reviewer on !250 re-measured against the script.
0.6.2 has since been cut, tagged and published — 5a8fce8, 2026-08-24 — so
drills/0.6.2.md is a shipped record rather than a candidate's, and it is
correct. The doctrine page is the stale copy, not the records: this issue
corrects no shipped measurement.
Two live releases have now been recorded past
this defect without tripping over it, which is precisely why it survived — the
copy only bites an author who trusts it instead of the script.

Surfaced as a non-blocking review note on !250 (release 0.6.2, #231) and
routed here rather than into that PR: drills/README.md was not a carrier !250
could touch.

Spec

Decision: delete the copy rather than repair it. Repairing it — adding
lib/forge.sh to the README — leaves two lists that must be kept equal by
hand, permanently, with the forge one diverging from upstream's prose in a
file upstream owns. Deleting it leaves one list, in the file that is already
executable, already tested against the workflow, and already what every
record quotes.

  1. Condition 2 keeps its claim and loses its copy. It states that the
    release path is exactly what .github/scripts/release-path.sh prints, and
    directs the record author to run the script and paste its output —
    git diff <last-rehearsed-tag>..HEAD -- $(sh .github/scripts/release-path.sh)
    is the form the records already use. No path names in the sentence.
  2. The guarantee sentence stops overreaching. Say what is true: the
    script's contract test (test/release-path.test.sh) keeps the script's
    list and the workflow's direct and transitive dependencies in agreement.
    The reader must be left with no impression that any list in
    drills/README.md is machine-checked, because after this change there
    will not be one.
  3. The other path mentions in the file stay. Condition 1's
    .github/workflows/release.yml names the one file whose pin line is the
    allowed diff — that is the condition's content, not an enumeration of the
    path. The lib/ruling.sh mentions in the worked-example paragraph are a
    measured fact about 0.5.0 (#233, #217, #237) and are equally not the
    list. Do not touch either.
  4. A guard, so the copy cannot come back. Add a case to
    test/release-path.test.sh asserting that drills/README.md names none
    of the five path tokens that only ever appear as members of an
    enumeration: bin/, lib/version.sh, lib/decide.sh, lib/facts.sh,
    lib/changelog.sh. Each occurs exactly once in the file today, all five
    inside condition 2's list; none has a standalone reason to be named in
    drill doctrine. The case must fail on the file as it stands on main and
    pass after the edit — write it in that order and record both runs. The
    guard belongs beside the script's own contract tests, because it is the
    same contract read from the other side: one list, and the script owns it.
  5. No other file changes. In particular .github/scripts/release-path.sh
    is correct and stays byte-identical: this issue changes prose and a test,
    never the release path itself. A PR that touches the script has
    misunderstood the deliverable.
  6. Fragment. One changelog.d/251.md in the grouped shape the
    shape sentinel declares, under ### Fixed.

Tasks

  • Claim the issue and cut a branch from origin/main.
  • Add the failing case to test/release-path.test.sh; run it against the
    unedited drills/README.md and record the failure.
  • Rewrite condition 2: keep the claim, drop the six-path copy, direct the
    author to run the script, and correct the guarantee sentence to name
    what the contract test actually holds.
  • Re-run test/release-path.test.sh green, then bash test/run.sh whole.
  • Write the changelog fragment.
  • Run the sanctioned shellcheck sweep and git diff --check.
  • Confirm git diff origin/main..HEAD --stat names exactly
    drills/README.md, test/release-path.test.sh, and the fragment.

Acceptance criteria

  • drills/README.md contains none of bin/, lib/version.sh,
    lib/decide.sh, lib/facts.sh, lib/changelog.sh, and still contains
    condition 1's .github/workflows/release.yml and the worked example's
    lib/ruling.sh.
  • Condition 2 still states that the release path is exactly the output of
    .github/scripts/release-path.sh and now tells the record author to run
    it; the sentence about the contract test claims coverage only of the
    script's list.
  • test/release-path.test.sh carries a case that fails on the pre-change
    drills/README.md and passes after, and the PR records both runs.
  • bash test/run.sh is green whole at the PR head; git diff --check is
    clean.
  • git diff origin/main..HEAD touches exactly three files:
    drills/README.md, test/release-path.test.sh, and the new
    changelog.d/251.md. .github/scripts/release-path.sh is
    byte-identical to main.

Test plan

  • Must fail before the change: the new case, run against main's
    drills/README.md — five tokens present, five reported.
  • Must fail after a wrong fix: re-adding any single path name to
    drills/README.md (say a helpful lib/forge.sh) turns the new case red.
    This is the class the guard exists for; state it in the PR.
  • Must stay green: the existing release-path cases, which compare the
    script's output against the workflow's direct and transitive dependencies
    and are untouched by this work.
  • Whole suite: bash test/run.sh. Note in the PR any test file that
    cannot run in the builder's box and why, with the same file measured at the
    base commit as the control.

Dependencies

Nothing open blocks this issue; the parse over this body is the empty set.
The one edge it ever carried was against the 0.6.2 release PR, and its
condition was that PR's merge, not #231's close — as this section said when
it was written. !250 merged 2026-08-24T15:55:13Z as 5a8fce8, so the hazard
is spent and triage flipped this issue to ready by hand and rewrote this
paragraph in the same tick, rather than leave blocked prose standing over a
ready label. The marker phrase is rewritten away with it: the parser unions
that phrase even under a sentence saying the clause no longer applies
(RELEASES.md, flip mechanics).

What the edge was, kept as history. While !250 was open, its section was
proved byte-for-byte by actions/changelog-assembled against the fragments
reachable from its merge base 7bdae45. A new changelog.d/ fragment landing
on main moves that base and turns the release PR's own guard red. This issue
adds changelog.d/251.md, so it waited.

The hazard was real, and it is worth recording that it fired on a different
issue in the window this one sat out.
changelog.d/238.md landed on main at
15:54 with !249, sixty-nine seconds before !250 merged — after the base, and so
never consumed. CI / self-guards is red at the tagged commit 5a8fce8
(changelog-armed refuses the unconsumed fragment), and 0.6.2 ships #238's code
without crediting it. That is the exact outcome this issue's wait avoided; it is
open on #231 as an escalation to the operator, and it does not reach this
issue
: 0.6.2 is cut and main is re-armed to 0.6.3-dev at ca7ce6e, so
changelog.d/251.md lands in an open 0.6.3 window with no release PR standing
over it.

No collision edge is owed. This issue's deliverable set is
drills/README.md, test/release-path.test.sh and changelog.d/251.md, and
no other open issue on this board writes any of them. That is stated as the
standing fact rather than as a dated roster of who holds what this hour, because
a roster expires on the next claim, merge or mint, and this claim does not.
drills/ is untouched by the rest of the open board, and no open issue writes
test/release-path.test.sh; the test/ files the rest of the board does hold
are different files in the same directory, which is not a collision. Distinct
fragment filenames never conflict with each other (#112 D1). This issue is
concurrently claimable with every ready issue on the board.

(The roster of who else is open, and of which neighbouring test/ files they
hold, is removed here rather than re-dated a third time — triage,
2026-08-24T22:18Z. It expired twice in under four hours without its answer ever
changing: #234 closed at 18:15:11Z when !252 merged, and #240 closed at
19:58:11Z when !254 merged, each falsifying a paragraph written to record that
rosters expire. What survives is the rule, which does not: check this issue's
three paths against the deliverable set of every open ready, claimed or
blocked issue, taking each queue label from label events rather than off
.labels, and re-run it against the live board rather than reading a list
written here. Empty again at 2026-08-24T22:18Z.)

Its premises were re-measured against current main at the flip, not carried
forward on trust.
!250 changed thirteen files and drills/README.md is not
among them, so nothing this issue is minted against moved. On ca7ce6e:
.github/scripts/release-path.sh prints seven paths, drills/README.md
still prints six, each of the five enumeration-only tokens (bin/,
lib/version.sh, lib/decide.sh, lib/facts.sh, lib/changelog.sh) occurs
exactly once, .github/workflows/release.yml occurs twice and
lib/ruling.sh twice — exactly the counts decision 4 and the first acceptance
criterion assume. Every task and every criterion below is executable as written.

No release-window edge is owed. Under #343 membership lives in a ## Members
record with no fallback to the gate, and neither open issue carrying release
has one: #231 (post-merge) and #228, the sync epic, which has carried the
label since its 2026-08-17T22:26:57Z mint. So no window stands and this issue is
not a non-member of anything. (Corrected 2026-08-24T17:47Z: this read "#231 has
no such record and no other open issue carries the release label", which #228
falsifies. The conclusion is unchanged — the window test is the membership
record, never the label.)

This is not a child of #228: that epic adopts upstream 0.6.10.6.3, and the
drift is a forge-side consequence of #198's own merge resolution, not upstream
content.

## Context `drills/README.md`'s **Doors unchanged** condition 2 names the release path twice: it says the path is exactly the output of `.github/scripts/release-path.sh`, and then prints its own copy of that output. The copy is wrong on this tree, and has been since the doctrine arrived here. - The README's copy — [`drills/README.md#L86-L91`](https://forgejo.heavyduty.builders/heavy-duty/ceremony/src/commit/7bdae45c9847b77912fd0f8d9abe43977fc41045/drills/README.md#L86-L91) — lists six paths: `.github/workflows/release.yml`, `bin/`, `lib/version.sh`, `lib/decide.sh`, `lib/facts.sh`, `lib/changelog.sh`. - The script — [`.github/scripts/release-path.sh`](https://forgejo.heavyduty.builders/heavy-duty/ceremony/src/commit/7bdae45c9847b77912fd0f8d9abe43977fc41045/.github/scripts/release-path.sh) — prints **seven**. `lib/forge.sh` is the seventh, and the script's own header says why: #191 put the release doors on the forge shim, so `lib/forge.sh` is part of the doors' executable path *on this forge* and stands for the two backends that are sourced through `$FORGE_LIB_DIR` at run time (#198). Both landed in the same commit: the #198 merge `e035130` created `.github/scripts/release-path.sh` on this tree with the forge-adapted seventh path, and took upstream's `drills/README.md` — whose condition 2 was written at `75d85df` (#237) against the six-path upstream script — unchanged beside it. Neither side was wrong when written; the merge left them disagreeing, and nothing has read them against each other since. The stale copy has stood through both `0.6.1` and `0.6.2`. **Why this is worth a fix and not a shrug.** Condition 2 is one of three conditions that let a release skip a live drill. A release author who copy-pastes the README's list measures six of the seven paths and can assert doors-unchanged while `lib/forge.sh` — a file both doors execute here — changed underneath. That is the exact failure the doors-unchanged shape was built to refuse: #217/#237 added the executable list because the `0.5.0` record had to explain a `lib/` delta it had predicted away. **And the README tells the reader the copy is guarded when it is not.** Condition 2 ends "its contract test keeps this inline list and the workflow's direct and transitive dependencies in agreement." `test/release-path.test.sh` holds the *script's* list against the workflow's direct and transitive dependencies; it never opens `drills/README.md`. Standing in the README, "this inline list" reads as the list on the page — so the one sentence that would make a reader check the script instead is the sentence that tells them they do not have to. Both live release records already did the right thing without the README: `drills/0.6.1.md` and `drills/0.6.2.md` each quote `release-path.sh`'s output, seven paths included, and every reviewer on !250 re-measured against the script. `0.6.2` has since been cut, tagged and published — `5a8fce8`, 2026-08-24 — so `drills/0.6.2.md` is a shipped record rather than a candidate's, and it is correct. The doctrine page is the stale copy, not the records: **this issue corrects no shipped measurement.** Two live releases have now been recorded past this defect without tripping over it, which is precisely why it survived — the copy only bites an author who trusts it instead of the script. Surfaced as a non-blocking review note on !250 (release `0.6.2`, #231) and routed here rather than into that PR: `drills/README.md` was not a carrier !250 could touch. ## Spec **Decision: delete the copy rather than repair it.** Repairing it — adding `lib/forge.sh` to the README — leaves two lists that must be kept equal by hand, permanently, with the forge one diverging from upstream's prose in a file upstream owns. Deleting it leaves one list, in the file that is already executable, already tested against the workflow, and already what every record quotes. 1. **Condition 2 keeps its claim and loses its copy.** It states that the release path is exactly what `.github/scripts/release-path.sh` prints, and directs the record author to run the script and paste its output — `git diff <last-rehearsed-tag>..HEAD -- $(sh .github/scripts/release-path.sh)` is the form the records already use. No path names in the sentence. 2. **The guarantee sentence stops overreaching.** Say what is true: the script's contract test (`test/release-path.test.sh`) keeps the script's list and the workflow's direct and transitive dependencies in agreement. The reader must be left with no impression that any list in `drills/README.md` is machine-checked, because after this change there will not be one. 3. **The other path mentions in the file stay.** Condition 1's `.github/workflows/release.yml` names the one file whose pin line is the allowed diff — that is the condition's content, not an enumeration of the path. The `lib/ruling.sh` mentions in the worked-example paragraph are a measured fact about `0.5.0` (#233, #217, #237) and are equally not the list. Do not touch either. 4. **A guard, so the copy cannot come back.** Add a case to `test/release-path.test.sh` asserting that `drills/README.md` names none of the five path tokens that only ever appear as members of an enumeration: `bin/`, `lib/version.sh`, `lib/decide.sh`, `lib/facts.sh`, `lib/changelog.sh`. Each occurs exactly once in the file today, all five inside condition 2's list; none has a standalone reason to be named in drill doctrine. The case must fail on the file as it stands on `main` and pass after the edit — write it in that order and record both runs. The guard belongs beside the script's own contract tests, because it is the same contract read from the other side: one list, and the script owns it. 5. **No other file changes.** In particular `.github/scripts/release-path.sh` is correct and stays byte-identical: this issue changes prose and a test, never the release path itself. A PR that touches the script has misunderstood the deliverable. 6. **Fragment.** One `changelog.d/251.md` in the grouped shape the `shape` sentinel declares, under `### Fixed`. ## Tasks - [x] Claim the issue and cut a branch from `origin/main`. - [x] Add the failing case to `test/release-path.test.sh`; run it against the unedited `drills/README.md` and record the failure. - [x] Rewrite condition 2: keep the claim, drop the six-path copy, direct the author to run the script, and correct the guarantee sentence to name what the contract test actually holds. - [x] Re-run `test/release-path.test.sh` green, then `bash test/run.sh` whole. - [x] Write the changelog fragment. - [x] Run the sanctioned shellcheck sweep and `git diff --check`. - [x] Confirm `git diff origin/main..HEAD --stat` names exactly `drills/README.md`, `test/release-path.test.sh`, and the fragment. ## Acceptance criteria - [x] `drills/README.md` contains none of `bin/`, `lib/version.sh`, `lib/decide.sh`, `lib/facts.sh`, `lib/changelog.sh`, and still contains condition 1's `.github/workflows/release.yml` and the worked example's `lib/ruling.sh`. - [x] Condition 2 still states that the release path is exactly the output of `.github/scripts/release-path.sh` and now tells the record author to run it; the sentence about the contract test claims coverage only of the script's list. - [x] `test/release-path.test.sh` carries a case that fails on the pre-change `drills/README.md` and passes after, and the PR records both runs. - [x] `bash test/run.sh` is green whole at the PR head; `git diff --check` is clean. - [x] `git diff origin/main..HEAD` touches exactly three files: `drills/README.md`, `test/release-path.test.sh`, and the new `changelog.d/251.md`. `.github/scripts/release-path.sh` is byte-identical to `main`. ## Test plan - **Must fail before the change**: the new case, run against `main`'s `drills/README.md` — five tokens present, five reported. - **Must fail after a wrong fix**: re-adding any single path name to `drills/README.md` (say a helpful `lib/forge.sh`) turns the new case red. This is the class the guard exists for; state it in the PR. - **Must stay green**: the existing `release-path` cases, which compare the script's output against the workflow's direct and transitive dependencies and are untouched by this work. - **Whole suite**: `bash test/run.sh`. Note in the PR any test file that cannot run in the builder's box and why, with the same file measured at the base commit as the control. ## Dependencies **Nothing open blocks this issue; the parse over this body is the empty set.** The one edge it ever carried was against the `0.6.2` release PR, and its condition was that PR's **merge**, not #231's close — as this section said when it was written. **!250 merged 2026-08-24T15:55:13Z as `5a8fce8`**, so the hazard is spent and triage flipped this issue to `ready` by hand and rewrote this paragraph in the same tick, rather than leave `blocked` prose standing over a `ready` label. The marker phrase is rewritten away with it: the parser unions that phrase even under a sentence saying the clause no longer applies ([RELEASES.md](RELEASES.md), flip mechanics). **What the edge was, kept as history.** While !250 was open, its section was proved byte-for-byte by `actions/changelog-assembled` against the fragments reachable from its merge base `7bdae45`. A new `changelog.d/` fragment landing on `main` moves that base and turns the release PR's own guard red. This issue adds `changelog.d/251.md`, so it waited. **The hazard was real, and it is worth recording that it fired on a different issue in the window this one sat out.** `changelog.d/238.md` landed on `main` at 15:54 with !249, sixty-nine seconds before !250 merged — after the base, and so never consumed. `CI / self-guards` is red at the tagged commit `5a8fce8` (`changelog-armed` refuses the unconsumed fragment), and 0.6.2 ships #238's code without crediting it. That is the exact outcome this issue's wait avoided; it is open on #231 as an escalation to the operator, and **it does not reach this issue**: `0.6.2` is cut and `main` is re-armed to `0.6.3-dev` at `ca7ce6e`, so `changelog.d/251.md` lands in an open `0.6.3` window with no release PR standing over it. **No collision edge is owed.** This issue's deliverable set is `drills/README.md`, `test/release-path.test.sh` and `changelog.d/251.md`, and **no other open issue on this board writes any of them.** That is stated as the standing fact rather than as a dated roster of who holds what this hour, because a roster expires on the next claim, merge or mint, and this claim does not. `drills/` is untouched by the rest of the open board, and no open issue writes `test/release-path.test.sh`; the `test/` files the rest of the board does hold are different files in the same directory, which is not a collision. Distinct fragment filenames never conflict with each other (#112 D1). This issue is concurrently claimable with every `ready` issue on the board. *(The roster of who else is open, and of which neighbouring `test/` files they hold, is removed here rather than re-dated a third time — triage, 2026-08-24T22:18Z. It expired twice in under four hours without its answer ever changing: #234 closed at 18:15:11Z when !252 merged, and #240 closed at 19:58:11Z when !254 merged, each falsifying a paragraph written to record that rosters expire. What survives is the rule, which does not: check this issue's three paths against the deliverable set of every open `ready`, `claimed` or `blocked` issue, taking each queue label from label events rather than off `.labels`, and re-run it against the live board rather than reading a list written here. Empty again at 2026-08-24T22:18Z.)* **Its premises were re-measured against current `main` at the flip, not carried forward on trust.** !250 changed thirteen files and `drills/README.md` is not among them, so nothing this issue is minted against moved. On `ca7ce6e`: `.github/scripts/release-path.sh` prints **seven** paths, `drills/README.md` still prints **six**, each of the five enumeration-only tokens (`bin/`, `lib/version.sh`, `lib/decide.sh`, `lib/facts.sh`, `lib/changelog.sh`) occurs **exactly once**, `.github/workflows/release.yml` occurs twice and `lib/ruling.sh` twice — exactly the counts decision 4 and the first acceptance criterion assume. Every task and every criterion below is executable as written. No release-window edge is owed. Under #343 membership lives in a `## Members` record with no fallback to the gate, and neither open issue carrying `release` has one: #231 (`post-merge`) and #228, the sync epic, which has carried the label since its 2026-08-17T22:26:57Z mint. So no window stands and this issue is not a non-member of anything. *(Corrected 2026-08-24T17:47Z: this read "#231 has no such record and no other open issue carries the `release` label", which #228 falsifies. The conclusion is unchanged — the window test is the membership record, never the label.)* This is not a child of #228: that epic adopts upstream `0.6.1`–`0.6.3`, and the drift is a forge-side consequence of #198's own merge resolution, not upstream content.
claude-bot-andresmgsl added the
documentation
blocked
scope:release-flow
scope:docs
labels 2026-08-24 15:42:47 +00:00

This issue's Blocked by declarations parse to: {#231}

That is the exact set this sweep gates on — what the machine read, never a
judgment about whether it is what you meant. The parse unions every clause it
finds, so a sentence like no longer blocked by #9 contributes #9 like
any other; over-retaining is the deliberate direction of error, because a stale
blocked is a triage comment away and a false ready sends a builder into
work that cannot merge. If this set names something you did not declare, or
omits something you did, edit the declaration — the next sweep echoes the
correction.

Comment only: nothing on this path writes a label. The marker carries the set
itself, so a parse unchanged since the last echo never re-posts.

<!-- issueflow:blockers-parsed-231-28f04a1862af --> This issue's `Blocked by` declarations parse to: {#231} That is the exact set this sweep gates on — what the machine read, never a judgment about whether it is what you meant. The parse unions every clause it finds, so a sentence like `no longer blocked by #9` contributes `#9` like any other; over-retaining is the deliberate direction of error, because a stale `blocked` is a triage comment away and a false `ready` sends a builder into work that cannot merge. If this set names something you did not declare, or omits something you did, edit the declaration — the next sweep echoes the correction. *Comment only: nothing on this path writes a label. The marker carries the set itself, so a parse unchanged since the last echo never re-posts.*
claude-bot-andresmgsl added
ready
and removed
blocked
labels 2026-08-24 16:25:50 +00:00

This issue's Blocked by declarations parse to: {}

That is the exact set this sweep gates on — what the machine read, never a
judgment about whether it is what you meant. The parse unions every clause it
finds, so a sentence like no longer blocked by #9 contributes #9 like
any other; over-retaining is the deliberate direction of error, because a stale
blocked is a triage comment away and a false ready sends a builder into
work that cannot merge. If this set names something you did not declare, or
omits something you did, edit the declaration — the next sweep echoes the
correction.

Comment only: nothing on this path writes a label. The marker carries the set
itself, so a parse unchanged since the last echo never re-posts.

<!-- issueflow:blockers-parsed-none-44136fa355b3 --> This issue's `Blocked by` declarations parse to: {} That is the exact set this sweep gates on — what the machine read, never a judgment about whether it is what you meant. The parse unions every clause it finds, so a sentence like `no longer blocked by #9` contributes `#9` like any other; over-retaining is the deliberate direction of error, because a stale `blocked` is a triage comment away and a false `ready` sends a builder into work that cannot merge. If this set names something you did not declare, or omits something you did, edit the declaration — the next sweep echoes the correction. *Comment only: nothing on this path writes a label. The marker carries the set itself, so a parse unchanged since the last echo never re-posts.*

This issue is blocked, but its body has no parseable Blocked by #N declaration. The sweep will not guess the dependency.

<!-- issueflow:blocked-unparseable --> This issue is `blocked`, but its body has no parseable `Blocked by #N` declaration. The sweep will not guess the dependency.
Author
Member

🟢 Gate cleared by hand — this issue is ready, and the body no longer says otherwise. ready on at 2026-08-24T16:25:49Z, blocked off at 16:25:50Z, and ## Dependencies was rewritten in the same tick rather than left standing over the new label.

Label events paged by hand immediately before the write, not read off the thread: documentation + blocked + scope:release-flow + scope:docs at the 2026-08-24T15:42:45–47Z mint (triage), then the reconciler's blockers-parsed-231 comment at 15:53:41Z, then nothing until this flip. Unassigned throughout; no attention and none owed.

The condition that fired was the merge, not the close

This issue's declaration named its own wake precisely, and it is worth repeating because it is unusual: the hazard cleared at !250's merge, not at #231's close. !250 merged 2026-08-24T15:55:13Z as 5a8fce8. #231 is still open — post-merge since 15:58:08Z, on a criterion of its own — and that is irrelevant here, because what this issue waited on was a live release PR's merge base, and there is no longer a release PR.

So the sweep could not have done this: it flips on a closed blocker. A hand flip was written into the declaration at mint for exactly this reason, and this is it.

The hazard was not theoretical — it fired next door in the same window

Recorded because it is the strongest possible argument that this issue's wait was correct, and because it belongs in the record of what 0.6.2 cost:

changelog.d/238.md landed on main at 15:54 with !249sixty-nine seconds before !250 merged, and after !250's merge base 7bdae45. The assembler never saw it. Consequences, measured rather than argued:

  • CI / self-guards is red at the tagged commit 5a8fce8: changelog-armed refuses a released tree carrying an unconsumed fragment. Reproduced locally against both commits; green again at ca7ce6e.
  • 0.6.2 ships #238's code and does not credit it. The tag contains !249's merge; the published section and release body do not mention #238.

That is the exact outcome this issue's wait avoided by one issue. It is escalated to the operator on #231 and nothing about it reaches this issue: main is re-armed to 0.6.3-dev, no release PR stands, and changelog.d/251.md lands in an open window.

Premises re-measured against main before the flip, not carried forward

!250 changed thirteen files and drills/README.md is not among them — so a blocker's merge invalidated nothing here. Checked anyway, at ca7ce6e, because a flip is where a stale criterion surfaces:

what the issue assumes measured on main
release-path.sh prints seven paths seven ✓
drills/README.md condition 2 copies six six ✓, lib/forge.sh still absent
each of the five enumeration-only tokens occurs exactly once bin/ 1, lib/version.sh 1, lib/decide.sh 1, lib/facts.sh 1, lib/changelog.sh 1 ✓
condition 1's .github/workflows/release.yml and the worked example's lib/ruling.sh survive the guard 2 and 2 ✓

Those are precisely the counts decision 4 and the first acceptance criterion depend on. Every task and criterion is executable as written; the failing-case-first order the spec demands still produces a red on the tree as it stands.

Two lines of Context corrected in the same tick

The body called drills/0.6.2.md "the 0.6.2 candidate's" and said the stale copy had stood "through the 0.6.2 candidate". 0.6.2 is cut, tagged and published, so both are now false in the same small way. Corrected, not negated in place: drills/0.6.2.md is a shipped record, it quotes all seven paths, and it is correct. Two live releases have now been recorded past this defect without tripping over it — which is exactly why it survived this long. The copy only bites an author who trusts it instead of the script.

No collision edge is owed and none is written. Deliverables are drills/README.md, test/release-path.test.sh and changelog.d/251.md; no open issue on this board carries either of the first two. This issue is concurrently claimable with #234, #240 and #241.

🟢 **Gate cleared by hand — this issue is `ready`, and the body no longer says otherwise.** `ready` on at 2026-08-24T16:25:49Z, `blocked` off at 16:25:50Z, and `## Dependencies` was rewritten in the same tick rather than left standing over the new label. **Label events paged by hand immediately before the write, not read off the thread**: `documentation` + `blocked` + `scope:release-flow` + `scope:docs` at the 2026-08-24T15:42:45–47Z mint (triage), then the reconciler's `blockers-parsed-231` comment at 15:53:41Z, then nothing until this flip. Unassigned throughout; no `attention` and none owed. ## The condition that fired was the merge, not the close This issue's declaration named its own wake precisely, and it is worth repeating because it is unusual: **the hazard cleared at !250's merge, not at #231's close.** !250 merged **2026-08-24T15:55:13Z as `5a8fce8`**. #231 is still open — `post-merge` since 15:58:08Z, on a criterion of its own — and that is irrelevant here, because what this issue waited on was a *live release PR's merge base*, and there is no longer a release PR. So the sweep could not have done this: it flips on a closed blocker. A hand flip was written into the declaration at mint for exactly this reason, and this is it. ## The hazard was not theoretical — it fired next door in the same window Recorded because it is the strongest possible argument that this issue's wait was correct, and because it belongs in the record of what 0.6.2 cost: `changelog.d/238.md` landed on `main` at **15:54** with !249 — **sixty-nine seconds** before !250 merged, and after !250's merge base `7bdae45`. The assembler never saw it. Consequences, measured rather than argued: - `CI / self-guards` is **red at the tagged commit `5a8fce8`**: `changelog-armed` refuses a released tree carrying an unconsumed fragment. Reproduced locally against both commits; green again at `ca7ce6e`. - **0.6.2 ships #238's code and does not credit it.** The tag contains !249's merge; the published section and release body do not mention #238. That is the exact outcome this issue's wait avoided by one issue. It is escalated to the operator on #231 and **nothing about it reaches this issue**: `main` is re-armed to `0.6.3-dev`, no release PR stands, and `changelog.d/251.md` lands in an open window. ## Premises re-measured against `main` before the flip, not carried forward !250 changed thirteen files and `drills/README.md` is not among them — so a blocker's merge invalidated nothing here. Checked anyway, at `ca7ce6e`, because a flip is where a stale criterion surfaces: | what the issue assumes | measured on `main` | |---|---| | `release-path.sh` prints seven paths | seven ✓ | | `drills/README.md` condition 2 copies six | six ✓, `lib/forge.sh` still absent | | each of the five enumeration-only tokens occurs exactly once | `bin/` 1, `lib/version.sh` 1, `lib/decide.sh` 1, `lib/facts.sh` 1, `lib/changelog.sh` 1 ✓ | | condition 1's `.github/workflows/release.yml` and the worked example's `lib/ruling.sh` survive the guard | 2 and 2 ✓ | Those are precisely the counts decision 4 and the first acceptance criterion depend on. **Every task and criterion is executable as written**; the failing-case-first order the spec demands still produces a red on the tree as it stands. ## Two lines of Context corrected in the same tick The body called `drills/0.6.2.md` "the `0.6.2` candidate's" and said the stale copy had stood "through the `0.6.2` candidate". 0.6.2 is cut, tagged and published, so both are now false in the same small way. Corrected, not negated in place: `drills/0.6.2.md` is a **shipped** record, it quotes all seven paths, and it is correct. Two live releases have now been recorded past this defect without tripping over it — which is exactly why it survived this long. The copy only bites an author who trusts it instead of the script. **No collision edge is owed and none is written.** Deliverables are `drills/README.md`, `test/release-path.test.sh` and `changelog.d/251.md`; no open issue on this board carries either of the first two. This issue is concurrently claimable with #234, #240 and #241.
Author
Member

🔧 Conflict resolved (triage, 2026-08-24T16:35Z) — the blocked-unparseable comment above is a two-second artefact of this tick's own flip, not a malformed queue state. No label moves; the state it complained about no longer exists.

What happened, to the second. The flip is two writes and they cannot be atomic: the body write that rewrites the Blocked by #231 marker away, then the label write. Body landed ~16:25:48Z; ready on 16:25:49Z; blocked off 16:25:50Z. The sweep read the issue at 16:25:51Z — after the marker was gone and, on its read, with blocked still on. It did exactly what it should: refused to guess, and said so. Both its comments are accurate about the instant they describe.

The state now, label events paged by hand immediately before this write: documentation, ready, scope:docs, scope:release-flow, unassigned. ready on 16:25:49Z, blocked off 16:25:50Z, both by triage, and nothing since. The blockers-parsed-none echo is correct and stays correct — this issue declares nothing, because nothing open blocks it. The parse over its body is the empty set by design.

Nothing is owed to the sweep here. Its marker carries the parsed set, so an unchanged parse never re-posts, and the blocked-unparseable path writes no label. There is no stale blocked to clear and no false ready to correct: this issue is genuinely claimable, its declaration was genuinely retired at !250's merge, and the reason the marker had to be deleted rather than annotated is the same reason this warning fired — the parser unions its marker phrase even under a sentence saying the clause no longer applies.

Recorded rather than left, because TRIAGE.md puts the resolution of automation's conflict comments on triage, and an unanswered warning on a ready issue reads to the next scanner as a board that has not been looked at.

For the next flip, the ordering that avoids this: remove the queue label before writing the body, so the sweep can never observe blocked beside an empty parse. #241's flip in this same tick did not trip it — its two label writes fell a second apart in a window no sweep ran in — which is luck rather than method.

🔧 **Conflict resolved (triage, 2026-08-24T16:35Z) — the `blocked-unparseable` comment above is a two-second artefact of this tick's own flip, not a malformed queue state. No label moves; the state it complained about no longer exists.** **What happened, to the second.** The flip is two writes and they cannot be atomic: the body write that rewrites the `Blocked by #231` marker away, then the label write. Body landed ~16:25:48Z; `ready` on 16:25:49Z; `blocked` off 16:25:50Z. The sweep read the issue at **16:25:51Z** — after the marker was gone and, on its read, with `blocked` still on. It did exactly what it should: refused to guess, and said so. Both its comments are accurate about the instant they describe. **The state now**, label events paged by hand immediately before this write: `documentation`, `ready`, `scope:docs`, `scope:release-flow`, unassigned. `ready` on 16:25:49Z, `blocked` off 16:25:50Z, both by triage, and nothing since. The `blockers-parsed-none` echo is **correct and stays correct** — this issue declares nothing, because nothing open blocks it. The parse over its body is the empty set by design. **Nothing is owed to the sweep here.** Its marker carries the parsed set, so an unchanged parse never re-posts, and the `blocked-unparseable` path writes no label. There is no stale `blocked` to clear and no false `ready` to correct: this issue is genuinely claimable, its declaration was genuinely retired at !250's merge, and the reason the marker had to be *deleted* rather than annotated is the same reason this warning fired — the parser unions its marker phrase even under a sentence saying the clause no longer applies. Recorded rather than left, because [TRIAGE.md](TRIAGE.md) puts the resolution of automation's conflict comments on triage, and an unanswered warning on a `ready` issue reads to the next scanner as a board that has not been looked at. **For the next flip, the ordering that avoids this**: remove the queue label *before* writing the body, so the sweep can never observe `blocked` beside an empty parse. #241's flip in this same tick did not trip it — its two label writes fell a second apart in a window no sweep ran in — which is luck rather than method.
Author
Member

🧹 Body corrected (triage, 2026-08-24T17:47Z) — one false clause in ## Dependencies. ready is still the true label, no label moves, and nothing about this issue's contract changes.

Label events paged by hand immediately before this write. documentation/blocked/scope:release-flow/scope:docs at the 2026-08-24T15:42 mint, then ready on and blocked off at 16:25:49–50Z (triage, by hand, when !250 merged). Nothing since. Current state: ready, unassigned — unchanged by this comment.

The clause

## Dependencies said "#231 has no such record and no other open issue carries the release label". #228 falsifies it: the upstream-sync epic is open and has carried release continuously since its mint at 2026-08-17T22:26:57Z — the label was never removed from it, unlike #231, where the lead took it off at 2026-08-17T23:33:02Z and triage returned it at 2026-08-23T17:16:46Z.

The conclusion it supported is unchanged, and that is why this is a correction rather than a flip. Under #343 a standing release window is read from a release issue's ## Members record, with no fallback to the gate label — and neither #231 nor #228 has such a heading (both bodies grepped just now). So no window stands, no membership call is owed, and this issue is a non-member of nothing. The clause was reasoning from the label instead of from the record; it reached the right answer by a route that does not hold.

It is corrected in place with the old wording quoted, rather than swapped silently.

No attention is set: this issue is unassigned.

🧹 **Body corrected (triage, 2026-08-24T17:47Z) — one false clause in `## Dependencies`. `ready` is still the true label, no label moves, and nothing about this issue's contract changes.** **Label events paged by hand immediately before this write.** `documentation`/`blocked`/`scope:release-flow`/`scope:docs` at the 2026-08-24T15:42 mint, then `ready` on and `blocked` off at 16:25:49–50Z (triage, by hand, when !250 merged). Nothing since. Current state: `ready`, unassigned — unchanged by this comment. ## The clause `## Dependencies` said *"#231 has no such record and no other open issue carries the `release` label"*. **#228 falsifies it**: the upstream-sync epic is open and has carried `release` continuously since its mint at **2026-08-17T22:26:57Z** — the label was never removed from it, unlike #231, where the lead took it off at 2026-08-17T23:33:02Z and triage returned it at 2026-08-23T17:16:46Z. **The conclusion it supported is unchanged, and that is why this is a correction rather than a flip.** Under #343 a standing release window is read from a release issue's `## Members` record, with **no fallback to the gate label** — and neither #231 nor #228 has such a heading (both bodies grepped just now). So no window stands, no membership call is owed, and this issue is a non-member of nothing. The clause was reasoning from the label instead of from the record; it reached the right answer by a route that does not hold. It is corrected in place with the old wording quoted, rather than swapped silently. No `attention` is set: this issue is unassigned.
Author
Member

🧹 Body corrected (triage, 2026-08-24T19:02Z) — one paragraph in ## Dependencies, replaced by the invariant it was serving rather than re-dated. No label moves: this issue stays ready and unassigned, and the Blocked by parse over the body is the empty set before and after the write.

The paragraph was a 16:24Z roster of the whole open board, and it had expired three separate ways without its answer changing by a word:

  • it named #234 as claimed#234 closed at 18:15:11Z when !252 merged;
  • it named #240 as ready@codex-bot-andresmgsl claimed it at 17:47:17Z;
  • it predated the mint of #253 at 16:38Z and so never listed it at all.

The third is the one worth naming. A stale state is visibly stale — a reader sees claimed on a closed issue and knows to re-check. A missing row is invisible: an incomplete roster reads exactly like a clean board, and the whole purpose of the paragraph is to prove a negative over every open issue. A snapshot cannot prove that negative for issues minted after it was taken.

So the paragraph now states the fact the answer rests on: no other open issue writes drills/README.md, test/release-path.test.sh or this issue's fragment. drills/ is untouched by the rest of the board, and the nearest neighbours in test/ are three different files — #240's test/forge-backends.test.sh, #243's test/labels-reconcile.test.sh and #253's test/changelog-assembled.test.sh. The derivation is still dated so it can be re-run, but it no longer expires when somebody claims or mints something.

Nothing else moved. The history of the spent !250 edge, the re-measured token counts on ca7ce6e, the release-window paragraph and every task and criterion are untouched. ready, unassigned, no attention, claimable now.

🧹 **Body corrected (triage, 2026-08-24T19:02Z) — one paragraph in `## Dependencies`, replaced by the invariant it was serving rather than re-dated. No label moves: this issue stays `ready` and unassigned, and the `Blocked by` parse over the body is the empty set before and after the write.** The paragraph was a 16:24Z roster of the whole open board, and it had expired three separate ways without its answer changing by a word: - it named **#234 as `claimed`** — #234 closed at 18:15:11Z when !252 merged; - it named **#240 as `ready`** — @codex-bot-andresmgsl claimed it at 17:47:17Z; - it **predated the mint of #253** at 16:38Z and so never listed it at all. The third is the one worth naming. A stale state is visibly stale — a reader sees `claimed` on a closed issue and knows to re-check. A missing row is invisible: an incomplete roster reads exactly like a clean board, and the whole purpose of the paragraph is to prove a negative over every open issue. A snapshot cannot prove that negative for issues minted after it was taken. So the paragraph now states the fact the answer rests on: **no other open issue writes `drills/README.md`, `test/release-path.test.sh` or this issue's fragment.** `drills/` is untouched by the rest of the board, and the nearest neighbours in `test/` are three different files — #240's `test/forge-backends.test.sh`, #243's `test/labels-reconcile.test.sh` and #253's `test/changelog-assembled.test.sh`. The derivation is still dated so it can be re-run, but it no longer expires when somebody claims or mints something. **Nothing else moved.** The history of the spent !250 edge, the re-measured token counts on `ca7ce6e`, the release-window paragraph and every task and criterion are untouched. `ready`, unassigned, no `attention`, claimable now.
Author
Member

🧹 Body corrected (triage, 2026-08-24T22:18Z) — one paragraph in ## Dependencies. No label moves: this issue stays ready and unassigned, the Blocked by parse over the body is the empty set before and after the write, and no task, criterion or decision is touched.

Label events paged by hand immediately before this write, not read off .labels: documentation + blocked + scope:release-flow + scope:docs at the 2026-08-24T15:42:45–47Z mint, then ready on 16:25:49Z and blocked off 16:25:50Z. Nothing since. Current state: documentation, ready, scope:docs, scope:release-flow, unassigned — unchanged by this comment.

What was false

The paragraph named "the nearest neighbours in test/ are #240 (test/forge-backends.test.sh), #243 … and #253 …", under a parenthetical deriving it "over the whole open board — #228, #231, #240, #241, #243, #247, #253". #240 closed at 2026-08-24T19:58:11Z when !254 merged. The conclusion never moved — drills/README.md, test/release-path.test.sh and changelog.d/251.md are in no other open issue's deliverable set — but the roster carrying it did.

Why it is deleted rather than re-dated

That paragraph was written at 19:02Z to replace an expired 16:24Z roster, and its own note said an incomplete roster reads as a clean board. It expired again 56 minutes later. So the enumeration is removed instead of corrected a third time, and what stands is the rule and the file-level facts it produces: drills/ is untouched by the rest of the open board, no open issue writes test/release-path.test.sh, and the test/ files the rest of the board does hold are different files in the same directory — which is not a collision. Distinct fragment filenames never conflict (#112 D1).

The check — this issue's three paths against the deliverable set of every open ready, claimed or blocked issue, each queue label read from label events — was empty again at 22:18Z. This issue is concurrently claimable with every ready issue on the board.

🧹 **Body corrected (triage, 2026-08-24T22:18Z) — one paragraph in `## Dependencies`. No label moves: this issue stays `ready` and unassigned, the `Blocked by` parse over the body is the empty set before and after the write, and no task, criterion or decision is touched.** **Label events paged by hand immediately before this write**, not read off `.labels`: `documentation` + `blocked` + `scope:release-flow` + `scope:docs` at the 2026-08-24T15:42:45–47Z mint, then `ready` on 16:25:49Z and `blocked` off 16:25:50Z. Nothing since. Current state: `documentation`, `ready`, `scope:docs`, `scope:release-flow`, unassigned — unchanged by this comment. ## What was false The paragraph named *"the nearest neighbours in `test/` are #240 (`test/forge-backends.test.sh`), #243 … and #253 …"*, under a parenthetical deriving it *"over the whole open board — #228, #231, #240, #241, #243, #247, #253"*. **#240 closed at 2026-08-24T19:58:11Z when !254 merged.** The conclusion never moved — `drills/README.md`, `test/release-path.test.sh` and `changelog.d/251.md` are in no other open issue's deliverable set — but the roster carrying it did. ## Why it is deleted rather than re-dated That paragraph was written at 19:02Z to replace an expired 16:24Z roster, and its own note said an incomplete roster reads as a clean board. It expired again 56 minutes later. So the enumeration is removed instead of corrected a third time, and what stands is the rule and the file-level facts it produces: `drills/` is untouched by the rest of the open board, no open issue writes `test/release-path.test.sh`, and the `test/` files the rest of the board does hold are different files in the same directory — which is not a collision. Distinct fragment filenames never conflict (#112 D1). The check — this issue's three paths against the deliverable set of every open `ready`, `claimed` or `blocked` issue, each queue label read from label events — was **empty** again at 22:18Z. This issue is concurrently claimable with every `ready` issue on the board.
codex-bot-andresmgsl added
claimed
and removed
ready
labels 2026-08-25 07:30:20 +00:00
codex-bot-andresmgsl self-assigned this 2026-08-25 07:30:20 +00:00

Starting build as @codex-bot-andresmgsl.

Plan of record:

  • Add the drills/README.md enumeration guard to test/release-path.test.sh first and record its expected failure on the unchanged README.
  • Rewrite only condition 2 so the executable script is the single source of truth; preserve condition 1 and the lib/ruling.sh worked example.
  • Add grouped changelog.d/251.md, then run the focused test, whole suite, sanctioned shellcheck, git diff --check, and exact-scope/byte-identity checks.

The wrong-fix control is explicit: reintroducing any enumeration-only token, including a tempting lib/forge.sh, must make the new guard fail.

Starting build as @codex-bot-andresmgsl. Plan of record: - Add the `drills/README.md` enumeration guard to `test/release-path.test.sh` first and record its expected failure on the unchanged README. - Rewrite only condition 2 so the executable script is the single source of truth; preserve condition 1 and the `lib/ruling.sh` worked example. - Add grouped `changelog.d/251.md`, then run the focused test, whole suite, sanctioned shellcheck, `git diff --check`, and exact-scope/byte-identity checks. The wrong-fix control is explicit: reintroducing any enumeration-only token, including a tempting `lib/forge.sh`, must make the new guard fail.
Author
Member

Closed by merge, verified and ticked by hand (triage, 2026-08-25T09:04Z). Every acceptance criterion re-measured against merged main; both checklists ticked in the body in this same tick. No corrective work is owed and no successor is released.

!260 merged 2026-08-25T08:57:40Z as f6f2ec7 carrying Closes #251, so the forge closed this issue directly. That path skips post-merge and writes no transition comment, and it leaves both the Tasks and the Acceptance-criteria lists unticked — twelve empty boxes on a closed issue read as work abandoned rather than work finished. This comment is that missing tick, and it is written from a re-measurement rather than from the PR's own worklog.

Closes was the correct marker here: not one of the five criteria is post-merge, so none of them needed the Refs #N mechanism the contract reserves for that case.

Re-measured at f6f2ec7, not taken from the PR

Each criterion run against the merged tree, and the RED-first premise re-run against the pre-merge head 6dc8bf6:

criterion measured
drills/README.md names none of bin/, lib/version.sh, lib/decide.sh, lib/facts.sh, lib/changelog.sh 0, 0, 0, 0, 0 ✓ — each was 1 at 6dc8bf6
condition 1's .github/workflows/release.yml and the worked example's lib/ruling.sh survive 1 and 2 ✓ (release.yml was 2 before: the second occurrence was the copy)
condition 2 still names the script, now tells the author to run it, and the guarantee sentence claims only the script's list ✓ — it reads "The script's contract test keeps its list and the workflow's direct and transitive dependencies in agreement", and hands over git diff <last-rehearsed-tag>..HEAD -- $(sh .github/scripts/release-path.sh)
test/release-path.test.sh carries a case red before and green after ✓ — readme_has_no_path_enumeration; 7 passed, 0 failed at f6f2ec7, and all five tokens are present at 6dc8bf6, which is the failure the PR recorded as 6 passed, 1 failed
bash test/run.sh green whole; git diff --check clean ✓ — 31 test files passed, 0 failed, re-run here; git diff --check 6dc8bf6 f6f2ec7 clean
diff touches exactly three files; .github/scripts/release-path.sh byte-identical ✓ — changelog.d/251.md, drills/README.md, test/release-path.test.sh; the script is byte-identical to 6dc8bf6

One thing the builder did beyond the letter of decision 4, and it is right. The guard forbids six tokens, not five: lib/forge.sh is in the list alongside the five the spec enumerated. That is not scope creep — the Test plan named exactly this as the wrong-fix control ("re-adding any single path name … say a helpful lib/forge.sh … turns the new case red"), and a guard that permitted the seventh path would have been open at the one place this defect is most likely to be re-introduced, by someone correcting the copy instead of deleting it. The PR records the mutation run: restoring lib/forge.sh gives 6 passed, 1 failed with the token named.

The doctrine now has one list, and it is executable

The failure this issue was minted against is closed at its root: a release author reading condition 2 is sent to .github/scripts/release-path.sh and cannot copy a list out of the page, because there is no list on the page. drills/0.6.1.md and drills/0.6.2.md were already correct — they quoted the script — so, as the body said at mint, this change corrects no shipped measurement. It removes the trap that both records happened to step over.

Label residue, stated so the next scanner does not clean it up

Label events paged by hand immediately before this write: documentation + blocked + scope:release-flow + scope:docs at the 2026-08-24T15:42:45–47Z mint, ready on 16:25:49Z / blocked off 16:25:50Z (triage, when !250 merged), ready off 2026-08-25T07:30:19Z and claimed on 07:30:20Z with @codex-bot-andresmgsl assigned, then the close at 08:57:40Z by @andres.

So this issue is closed while still wearing claimed and its assignee. That is not a board lie and it is deliberately left alone: the Closes #N path releases no claim, and the issue-flow reconciler is open-scoped, so nothing will ever trip over it. The board's claimability invariants range over open issues; a closed one holds no slot.

Nothing is released and nothing is owed

  • No successor. The Blocked by parse over every open issue's body — driven through this repository's own blocked_references / blocked_cross_references, not by eye — returns the empty set on all four (#228, #231, #243, #247). Nothing was waiting on this close.
  • No attention. The work is delivered; there is no next move to hand the assignee, and the issue is closed.
  • No follow-up issue. The body's ## Dependencies history — the spent !250 edge, the changelog.d/238.md hazard that fired next door — stays as the record of why this issue waited. The 0.6.2 credit gap it describes is live on #231 and is not this issue's tail.
✅ **Closed by merge, verified and ticked by hand (triage, 2026-08-25T09:04Z). Every acceptance criterion re-measured against merged `main`; both checklists ticked in the body in this same tick. No corrective work is owed and no successor is released.** !260 merged **2026-08-25T08:57:40Z** as `f6f2ec7` carrying `Closes #251`, so the forge closed this issue directly. That path skips `post-merge` and writes no transition comment, and it leaves **both** the Tasks and the Acceptance-criteria lists unticked — twelve empty boxes on a closed issue read as work abandoned rather than work finished. This comment is that missing tick, and it is written from a re-measurement rather than from the PR's own worklog. `Closes` was the correct marker here: not one of the five criteria is post-merge, so none of them needed the `Refs #N` mechanism the contract reserves for that case. ## Re-measured at `f6f2ec7`, not taken from the PR Each criterion run against the merged tree, and the RED-first premise re-run against the pre-merge head `6dc8bf6`: | criterion | measured | |---|---| | `drills/README.md` names none of `bin/`, `lib/version.sh`, `lib/decide.sh`, `lib/facts.sh`, `lib/changelog.sh` | 0, 0, 0, 0, 0 ✓ — each was **1** at `6dc8bf6` | | condition 1's `.github/workflows/release.yml` and the worked example's `lib/ruling.sh` survive | 1 and 2 ✓ (`release.yml` was 2 before: the second occurrence *was* the copy) | | condition 2 still names the script, now tells the author to run it, and the guarantee sentence claims only the script's list | ✓ — it reads "The script's contract test keeps **its** list and the workflow's direct and transitive dependencies in agreement", and hands over `git diff <last-rehearsed-tag>..HEAD -- $(sh .github/scripts/release-path.sh)` | | `test/release-path.test.sh` carries a case red before and green after | ✓ — `readme_has_no_path_enumeration`; **7 passed, 0 failed** at `f6f2ec7`, and all five tokens are present at `6dc8bf6`, which is the failure the PR recorded as `6 passed, 1 failed` | | `bash test/run.sh` green whole; `git diff --check` clean | ✓ — **31 test files passed, 0 failed**, re-run here; `git diff --check 6dc8bf6 f6f2ec7` clean | | diff touches exactly three files; `.github/scripts/release-path.sh` byte-identical | ✓ — `changelog.d/251.md`, `drills/README.md`, `test/release-path.test.sh`; the script is byte-identical to `6dc8bf6` | **One thing the builder did beyond the letter of decision 4, and it is right.** The guard forbids **six** tokens, not five: `lib/forge.sh` is in the list alongside the five the spec enumerated. That is not scope creep — the Test plan named exactly this as the wrong-fix control ("re-adding any single path name … say a helpful `lib/forge.sh` … turns the new case red"), and a guard that permitted the seventh path would have been open at the one place this defect is most likely to be re-introduced, by someone correcting the copy instead of deleting it. The PR records the mutation run: restoring `lib/forge.sh` gives `6 passed, 1 failed` with the token named. ## The doctrine now has one list, and it is executable The failure this issue was minted against is closed at its root: a release author reading condition 2 is sent to `.github/scripts/release-path.sh` and cannot copy a list out of the page, because there is no list on the page. `drills/0.6.1.md` and `drills/0.6.2.md` were already correct — they quoted the script — so, as the body said at mint, **this change corrects no shipped measurement.** It removes the trap that both records happened to step over. ## Label residue, stated so the next scanner does not clean it up Label events paged by hand immediately before this write: `documentation` + `blocked` + `scope:release-flow` + `scope:docs` at the 2026-08-24T15:42:45–47Z mint, `ready` on 16:25:49Z / `blocked` off 16:25:50Z (triage, when !250 merged), `ready` off 2026-08-25T07:30:19Z and `claimed` on 07:30:20Z with @codex-bot-andresmgsl assigned, then the close at 08:57:40Z by @andres. So this issue is closed while still wearing `claimed` and its assignee. **That is not a board lie and it is deliberately left alone**: the `Closes #N` path releases no claim, and the issue-flow reconciler is open-scoped, so nothing will ever trip over it. The board's claimability invariants range over open issues; a closed one holds no slot. ## Nothing is released and nothing is owed - **No successor.** The `Blocked by` parse over every open issue's body — driven through this repository's own `blocked_references` / `blocked_cross_references`, not by eye — returns the empty set on all four (#228, #231, #243, #247). Nothing was waiting on this close. - **No `attention`.** The work is delivered; there is no next move to hand the assignee, and the issue is closed. - **No follow-up issue.** The body's `## Dependencies` history — the spent !250 edge, the `changelog.d/238.md` hazard that fired next door — stays as the record of why this issue waited. The 0.6.2 credit gap it describes is live on #231 and is not this issue's tail.
Sign in to join this conversation.
No milestone
No project
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: heavy-duty/ceremony#251
No description provided.