actions/* + lib/* + CHANGELOG — merge upstream 0.6.0 onto the forge tree, and port every gh call site it brought (#198) #204

Merged
andres merged 211 commits from build/198-upstream-0.6.0 into main 2026-08-05 13:10:55 +00:00

211 commits

Author SHA1 Message Date
adf907c963 fix(198): the action fails closed, the caller decides scheduling, the guard decides the forge (#198)
All checks were successful
CI / test (pull_request) Successful in 3m2s
CI / release-exercise (pull_request) Successful in 10s
CI / self-guards (pull_request) Successful in 6s
CI / action-exercise (pull_request) Successful in 6s
CI / docs-sync-exercise (pull_request) Successful in 6s
Refs guard / refs-not-closing (pull_request) Has been skipped
labels / labels (pull_request) Successful in 46s
@codex-reviewer-andresmgsl's second review, both points taken.

The refs action goes back to `forge_preflight || exit 1`. 97e63ac had it exit 0
with a notice so the PR check would not be red, and that conflated two
different questions: "this action cannot produce a verdict" is the ACTION's
contract and must stay a refusal, while "this check should not block the
board" is the CALLER's decision. The caller now carries it —
refs-guard.yml skips unless github.server_url is github.com, mirroring
forge_detect positively. A skipped check is a green head; an action that
reports success it did not earn is not. The leaked preflight_err temp file
goes with the revert.

The workflow guard asked the wrong question. `command -v gh` alone passes the
moment a Forgejo runner image happens to ship gh, and then dispatches against
a forge that cannot serve it — the client/forge mismatch forge_preflight
exists to prevent. It decides the FORGE first now, mirroring forge_detect
positively, and the binary second. The source guard splits to match: a
declaration guarded only by binary presence is reported, with a fixture that
fails on exactly that shape.

The warning text was also wrong on the facts, as noted: issue-event sweeps ARE
this caller's event-driven wakes, so they are precisely what is lost. It now
says the hourly scheduled sweep survives and every event-driven wake through
this caller does not, until #205.

Point 1 of that review — jq 1.6 accepting an empty payload — was already fixed
in 728102a, pushed before the review landed.

Verified under the runner's jq 1.6 as well as 1.7: 28 test files, 0 failed
both ways. shellcheck 0.10.0 (CI's pin), actionlint, self-ref, marker,
vendored, changelog-armed all clean with every file tracked.

Refs #198
2026-08-05 12:30:28 +00:00
728102a3ba fix(issueflow): issue_payload_valid refuses an empty payload on jq 1.6 too (#198)
All checks were successful
CI / test (pull_request) Successful in 3m3s
CI / release-exercise (pull_request) Successful in 11s
CI / self-guards (pull_request) Successful in 6s
CI / action-exercise (pull_request) Successful in 6s
CI / docs-sync-exercise (pull_request) Successful in 6s
Refs guard / refs-not-closing (pull_request) Successful in 5s
labels / labels (pull_request) Successful in 45s
`CI / test` was red at 97e63ac on a case that passes on this box: upstream's
own "an empty payload is refused". The cause is not the test.

`jq -e` disagrees with itself across versions on EMPTY input. jq 1.7 exits 4 —
no valid result was ever produced. jq 1.6 exits 0. Measured both ways today
against the same filter. This instance's runner image
(ghcr.io/catthehacker/ubuntu:act-22.04) carries jq 1.6.

So on this forge the guard #247 D3 added specifically to refuse an unreadable
read was ACCEPTING one: an empty body read as a valid issue payload, and the
sweep would have reconciled an issue from a payload it never received. The
test is upstream's, it is correct, and it passes on a GitHub runner — which is
why upstream never saw this.

The fix does not depend on jq's exit code for an input it never receives: the
payload is read, emptiness is decided in the shell, and jq judges only a
non-empty body.

Verified under BOTH jq versions, not just the one on this box: empty refused
and healthy accepted on 1.6 and 1.7, and the whole suite green under jq 1.6 —
28 test files, 0 failed — as well as under 1.7.

Refs #198
2026-08-05 12:22:20 +00:00
06f05aebec fix(198): the workflow declares and refuses instead of being exempted by name (#198)
Some checks failed
CI / test (pull_request) Failing after 3m3s
CI / release-exercise (pull_request) Successful in 11s
CI / self-guards (pull_request) Successful in 7s
CI / action-exercise (pull_request) Successful in 5s
CI / docs-sync-exercise (pull_request) Successful in 6s
Refs guard / refs-not-closing (pull_request) Successful in 5s
labels / labels (pull_request) Successful in 46s
@codex-reviewer-andresmgsl's blocker 1 is right and the filename exemption was
the wrong shape. It exempted the whole FILE — any later `gh` call anywhere in
labels.yml would have ridden in free — and it let the merge ship a step that
dies with `command not found` on every sweep on this forge, which #197's bar
does not permit.

The declaration mechanism already existed; a workflow simply could not reach
it. It can: `CEREMONY_FORGE_CLIENT: gh` in the step's env is the same
declaration actions/refs-not-closing carries, and the refusal that a script
gets from forge_preflight is inline here because a workflow has no shell to
call it from. The dispatch now warns by name, cites #205, and exits 0 rather
than reddening every sweep for a known gap.

So the guard needs no exemption list at all. It now requires the pair —
declared AND refusing — and reports a declaration that carries no refusal,
which is a permission slip for `command not found`.

That predicate was wrong on its first write, and its mutation test caught it:
`refuses_when_unavailable` matched the word `forge_preflight` inside
labels.yml's own comment explaining that it has NO forge_preflight to call. A
guard reading prose as evidence is the blind sweep again, in the guard written
to forbid it. Comments are stripped now, as gh_calls already stripped them.

Blocker 4: the nudge strips a trailing slash from the server URL. Reverting the
strip reds two cases.

Blockers 2 and 3 were already fixed in 97e63ac, before either review landed.

test/run.sh 28 files 0 failed under CI's env; shellcheck 0.10.0 (CI's pin),
actionlint, self-ref, marker, vendored and changelog-armed all clean, with
every file tracked this time.

Refs #198
2026-08-05 12:16:53 +00:00
97e63acef0 fix(refs-not-closing): report and skip on a forge it cannot speak, rather than reddening every PR (#198)
Some checks failed
CI / test (pull_request) Failing after 3m2s
CI / release-exercise (pull_request) Successful in 11s
CI / self-guards (pull_request) Successful in 7s
CI / action-exercise (pull_request) Successful in 6s
CI / docs-sync-exercise (pull_request) Successful in 6s
Refs guard / refs-not-closing (pull_request) Successful in 5s
labels / labels (pull_request) Successful in 45s
The first head's `Refs guard` failed on this PR, correctly: spec 4's
CEREMONY_FORGE_CLIENT=gh declaration made forge_preflight refuse by name on
this forge. But that workflow runs on every pull request here, so the
declaration as first written turns every future PR red until #199 lands —
blocking the board for a gap that already has its own issue.

Refusing and scheduling are different questions. This action must never
produce a verdict from a graph it did not read, and it does not: on a forge it
cannot speak it now says so by name, cites #199, states that no verdict was
produced, and reaches the forge zero times. A preflight failure for any other
reason stays fatal, and on a forge it CAN speak nothing changes.

Also: five SC2016 findings in test/no-runtime-gh.test.sh. They were invisible
locally because shellcheck-all.sh lints TRACKED files and the guard was still
untracked when I ran it — a new file is exactly the case that check cannot
see. Verified this time against CI's pinned shellcheck 0.10.0 with the file
committed.

test/run.sh: 28 test files, 0 failed, under CI's CEREMONY_REQUIRE_* env.
shellcheck, actionlint, self-ref, marker and vendored guards all clean.

Refs #198
2026-08-05 12:09:05 +00:00
2900529533 test(no-runtime-gh): the workflow exemption names the issue that removes it (#205)
Some checks failed
CI / test (pull_request) Failing after 32s
CI / release-exercise (pull_request) Successful in 11s
CI / self-guards (pull_request) Successful in 7s
CI / action-exercise (pull_request) Successful in 6s
CI / docs-sync-exercise (pull_request) Successful in 6s
Refs guard / refs-not-closing (pull_request) Failing after 5s
labels / labels (pull_request) Successful in 45s
An exemption without a work item is just a hole with a comment on it.

Refs #198
2026-08-05 11:58:28 +00:00
e035130f65 merge upstream 0.6.0 onto the forge tree, and port every gh call site it brought (#198)
Some checks failed
CI / test (pull_request) Failing after 33s
CI / release-exercise (pull_request) Successful in 12s
CI / self-guards (pull_request) Successful in 6s
CI / action-exercise (pull_request) Successful in 6s
CI / docs-sync-exercise (pull_request) Successful in 6s
Refs guard / refs-not-closing (pull_request) Failing after 5s
labels / labels (pull_request) Successful in 43s
`git merge` of upstream `8c3a4d1` onto `dad99dd`, common ancestor `84bb1a4`.
18 hunks in 10 files; `lib/forge.sh`, `lib/forge-github.sh` and
`lib/forge-forgejo.sh` conflict in none and come out byte-identical.

The resolutions the issue decided: VERSION and both CEREMONY_SELF_REF
carriers take upstream's numbers; `.github/labels.conf` and `drills/0.4.1.md`
keep this forge's; CHANGELOG keeps both sides and names the upstream commit
this tree carries.

The part the hunks did not contain. Upstream's 0.5.0/0.6.0 work added whole
functions to files this tree already owned, so `git merge` took its side
without raising a conflict — and with them, EIGHT runtime `gh` call sites
that #188 had removed. Seven are ported onto the shim: two reads and four
comment writes in issueflow-reconcile, and labels-reconcile's HEAD_COMMIT_AT
read. The eighth is `gh workflow run` in labels.yml, which a workflow cannot
declare a client for and whose Forgejo equivalent this instance answers with
500 rather than a 4xx — named with its reason rather than ported on a guess.

test/no-runtime-gh.test.sh makes the rule mechanical, because reviewing the
diff could not: four reviewers reading it each found a different subset, and
the contract suite stubs `gh`, so a reintroduced call site passes it.

Three seams the resolution decides are silent when resolved wrongly, and each
now has a case that fails on the wrong one: the merged record's `merged_at`
third column (without it every sort key ties and the highest PR number comes
back), the open gather's one-BODY-row-per-line feed (a whole decoded body as
one record loses every declaration including the first), and the whole-board
read whose COLLISION_FLAGS/WINDOW_FLAGS consumers auto-merged.

The open gather carries CLOSING rows as well as BODY rows. `Refs` alone would
drop every `Closes #N` link on the open side and reclaim a claim the PR was
holding — the existing base64 round-trip case is red without it.

actions/refs-not-closing declares CEREMONY_FORGE_CLIENT=gh: its only gather
is GraphQL, which Forgejo does not serve at all. #199 ports it.

test/run.sh: 28 test files, 0 failed. shellcheck and actionlint clean.

Refs #198
2026-08-05 11:56:23 +00:00
github-actions[bot]
8c3a4d1dee chore: bump main to 0.6.1-dev — a dev install must not impersonate 0.6.0 2026-08-05 09:35:51 +00:00
Daniel Marin
0ce6cb961a
Merge pull request #323 from cndgrr/build/249-release-0-6-0
release: cut 0.6.0
2026-08-05 10:35:37 +01:00
cndgrr
f832334abe drill 0.6.0: probes 5 and 6, the setup corrections, and the disposal state 2026-08-05 09:14:55 +00:00
cndgrr
6f30989e2a drill 0.6.0: probes 1, 3 and 4 recorded from their runs 2026-08-05 09:11:46 +00:00
cndgrr
24b69aea8e drill 0.6.0: the scratch repo, the candidate ref, and probe 2 2026-08-05 09:05:20 +00:00
cndgrr
fb8f8282a9 release: cut 0.6.0
Thirty-five fragments assembled into '## 0.6.0 — 2026-08-05'; VERSION to
bare 0.6.0; the three CEREMONY_SELF_REF carriers stamped "0.6.0" in this
one commit; the three docs/CONSUMERS.md availability markers cleared to
name 0.6.0 — refs-not-closing (#218), the RELEASES.md mirror entry (#248)
and the vendored-manifest completeness guarantee (#251).

drills/0.6.0.md opens with the measurement that decides its shape: the
doors-unchanged conditions do NOT all hold at this candidate, because
lib/changelog.sh moved on the release path since the last rehearsed tag
0.4.0. A full disposable-repo rehearsal is owed and is in progress; the
record is committed early and filled from the runs as they happen.

Refs #249.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 00:01:03 +00:00
Daniel Marin
d272964dac
Merge pull request #315 from cndgrr/build/311-readme-rewrite
README.md — rewritten whole from the current tree
2026-08-05 00:50:16 +01:00
cndgrr
263bb74652 README: three steps run past the tag, and the publish is one of them
The overview's fail-loudly paragraph said "Only two steps run past the tag"
and attributed the tag-standing/no-release state to the artifact hook alone.
Past `tag the merge commit` (release.yml:224) come three steps: the artifact
hook (:236), `publish the release` (:246) and the -dev re-arm (:267). The
publish is `gh release create --verify-tag`, so it can fail on the API call
or the assets with the tag already standing — the same state, from a second
cause.

State the count as three and sort them by what a failure leaves behind: two
fail before the release exists (hook, publish), both recovered by the tag
door; the third is the re-arm, still the one failure in the file that leaves
a real release behind. The nothing-exists recovery text names the publish
among the causes of a tag with no release too; its remedy is unchanged.
2026-08-04 23:29:12 +00:00
cndgrr
1410c01caf README: the forward pointer cites #317, never a version number
Triage's D6 (#311), added mid-round: the file's single forward-looking
sentence is sanctioned, and bounded. Two corrections to what round 4
landed, both of them the bound rather than the claim -- the claim itself
was re-measured by triage and holds.

- it named 'the 0.7.0 window'. Which release carries that work is a
  scheduling fact owned by the epic and RELEASES.md, where release-init
  may fold an empty window into a later release or skip the version
  outright, so the number can move with no diff under this file while
  every guard stays green. The issue number does not move: #317 is the
  stable name of the work.
- it was present indicative -- 'makes' -- one paragraph after banana
  rides row 6 today. It now reads as work that has not landed, on its
  own, without the reader chasing the link.

Still one sentence, still only in this section, and it weakens no
present-tense claim around it: the -dev half stays unreachable, the
malformed half stays live, and the manual bump stays the remedy today.

Refs #311

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 22:57:43 +00:00
cndgrr
a663c631bf README: two tag-door asserts, the artifact hook past the tag, doctrine is mirrored
codex's round-4 blockers, both reproduced against the tree:

- the opening introduced machinery and doctrine together as 'never
  copied', which the doctrine paragraph then contradicts by design — the
  .ceremony/ mirror is a copy, kept honest by a guard rather than by
  absence. The clause now says of each half what is true of it.
- the tag door has two refusing asserts, not one: tag/tree identity
  (release.yml L328-L339) and a publishable version section
  (L340-L352, changelog_section_problem), the second already quoted in
  this page's own troubleshooting catalog.

claude's N1, taken: the zero-artifact boundary is the tag, not the
publish — the consumer's artifact hook runs between them and its
non-zero exit aborts with a tag standing. The re-arm remains the single
failure that leaves a real release behind.

Per triage's steer, one line pointing the rc half of the re-arm refusal
at the 0.7.0 window (#317); the rc recovery prose is not widened.

Refs #311
2026-08-04 22:44:02 +00:00
cndgrr
ca44ee0f84 README: the overview's zero-artifact promise is the pre-publish one
The re-arm section corrected last round said the bump runs after publish and
its refusal leaves a release standing; the overview still promised an
unconditional -dev re-arm and zero artifacts on any failed assert. Scope the
guarantee to the asserts before the publish, name the rc exception where the
reader meets it first, and stop calling a malformed version's refusal
unreachable - only the -dev half is.
2026-08-04 22:12:29 +00:00
cndgrr
6df42797d2 README: reflow the npm entry and drop an overstated clause
The rc path is how this refusal is reached, not 'the one door an
operator actually walks through' -- most releases are bare and never
see it. Also rewrap the npm paragraph, left ragged by the previous
commit.
2026-08-04 21:42:23 +00:00
cndgrr
05e977814f README: point the two new anchors at the lines that carry the claim
version_is_dev's range started one line in, past the signature comment
that states the -dev-only rule; and the npm range stopped at L112,
before the npm pkg set / npm install --package-lock-only lines it was
cited for. L68-L76 and L114-L115.
2026-08-04 21:40:41 +00:00
cndgrr
052c8734b9 README: the re-arm refusal is the rc path, not an unreachable guard
The section called version_next_dev's refusal unreachable by reading
decide's sense of 'bare' (not -dev) into version_next_dev's regex
(^X.Y.Z$). An rc lives between the two: row 6 admits a labeled rc
transition as a shippable ceremony, the bump step gates only on
ceremony=yes, and its VER is the tree's version verbatim -- so an rc
release tags, notes, publishes, then refuses here. State that path and
its remedy (version.sh L78-L82: an rc's next version is a human
decision), keep the guard reading for the -dev/garbage half that really
is unreachable, and close the same conflation in row 6.

Also: version_write runs npm pkg set + a lockfile-only install, not
npm version (which would tag); and drop 'unarmed main' so the section
uses release.yml's one sense of armed.

Reported by claude-bot-andresmgsl on #315.
2026-08-04 21:38:27 +00:00
cndgrr
dac4946e43 README: the re-arm is the merge door's, so state its reach correctly
Self-caught while re-reading the section added in 87f300c. It said
version_next_dev's refusal was "the tag door's edge" — wrong twice. The tag
door does not bump at all (release.yml:303-307, cast's precedent: the
fallback does not rewrite main), and the merge door's bump runs only on
ceremony=yes, which decide rows 5-6 reach only on a transition TO bare. The
message is therefore unreachable through either door as they stand, and the
honest description is a guard against a future decide change, not an edge an
operator can hit today.

Writing a troubleshooting entry that sends an operator looking down the tag
door for a message the tag door cannot emit is the same defect this round is
fixing one paragraph up, so it does not get to ship in the fix.
2026-08-04 21:04:10 +00:00
cndgrr
87f300c453 README: re-measure the incubator drill and close the refusal catalog
The drill paragraph called incubator's drill "TBD". It has been defined in
heavy-duty/incubator since 2026-07-23 (f7851cb, refined 7c20a4e on 07-24):
the pre-release verify of the canonical candidate deployed to staging, its
smoke probe run inside the container on deployed credentials, the record
pinning commit SHA and image digest. Carried from main unmeasured, which is
the one thing D1 forbids. With incubator named the paragraph enumerates five
meanings, not three, so its own tally closes now too.

The refusal catalog claimed to be generated from the sources, but its
regeneration grep never read lib/version.sh — the four version_read messages
it quotes all live there. Adding version.sh to the documented command turned
up four more refusals the catalog was missing: unknown backend on the read
side, and version_next_dev / version_write's two on the post-release re-arm,
which release.yml:275,283 really can emit. The re-arm ones get their own
section because their remedy is unlike every other entry here — the tag and
the publish already happened, so the fix is a manual bump, not a re-run.

One refusal stays outside the grep by construction: "no version field" is a
console.error inside the node one-liner, with no >&2 and no "refuse ". The
section now says so rather than shipping a command that silently
under-produces the catalog it claims to generate.

Answers codex-bot-andresmgsl and kimi-bot-andresmgsl (blocking, both the
incubator claim) and claude-bot-andresmgsl nit 2, at head 57a7b15.
2026-08-04 20:59:30 +00:00
cndgrr
57a7b156ac README.md rewritten whole from the current tree 2026-08-04 20:22:13 +00:00
cndgrr
8080618a70 changelog.d: the README is rewritten whole from the current tree 2026-08-04 20:17:30 +00:00
Daniel Marin
d24a3142a6
Merge pull request #314 from cndgrr/build/307-issueflow-ruling-preread-pin
test/issueflow-reconcile.test.sh — pin the claimed-branch ruling pre-read (#307)
2026-08-04 21:11:57 +01:00
cndgrr
3af132edf7 changelog.d: the claimed-branch ruling pre-read is pinned
Grouped shape, terminal cite, 287 characters.

Refs #307
2026-08-04 19:44:42 +00:00
cndgrr
3bba47bacf test: pin the claimed-branch ruling pre-read against its own diagnostic
`claimed` + `needs-ruling` with no assignee posts `claimed-unassigned`
before the tail, so the top read is the only reason the ruling nudge
below still sees the real quiet. Probe 110 asserts both outputs of one
sweep and reds when that read drifts below the post.

Recovered from ed588a2 by sha — the branch ref was deleted and the push
crossed the merge — then re-measured at fd22bd2, behind #293. 486 -> 488
on this file.

Refs #307
2026-08-04 19:39:05 +00:00
Daniel Marin
fd22bd2fe7
Merge pull request #312 from cndgrr/build/293-issueflow-flags
issueflow-reconcile — the sweep flags what the window and collision rules forbid: unblocked twins, and an unblocked non-member during a standing window
2026-08-04 20:29:42 +01:00
cndgrr
20e35b15da the release note says unblocked, not ready
D3b corrected the window flag to fire on `claimed` too, PR in flight or
not; the fragment still described it as a `ready` non-member, which is
the reading the ruling struck.
2026-08-04 19:03:24 +00:00
cndgrr
13605cf4b6 pin family scoping in the direction that carries it, and D3b on the window side
The family-scoping fixture asserted that a foreign marker does not
SILENCE the flag, which a family-blind grep satisfies too. The property
that is load-bearing is the other one: a foreign family late on the
thread must not make the flag re-post. Made family-blind, the sweep now
reds.

D3b says a claimed non-member is flagged whether or not it has an open
PR, and the fixtures covered that for the collision flag only — the
window side, which is the flag the 18:11Z correction was about, had no
case at all.
2026-08-04 19:00:48 +00:00
cndgrr
4f852ee152 the normalization fixtures fail for the reasons they are named for
check() matches its expectation as a substring, so a bare
`issueflow-reconcile` row was satisfied by `issueflow-reconcile.test`
too: the multi-extension rule stayed green under a normalization that
strips only the last extension. keys_of brackets each key, and the
no-em-dash row asserts its emptiness through grep rather than through an
expectation check() cannot make.

Beside them, the cases the two fixes are named for: a self-folding + title
answers its key once and never chains an issue to its own number, and
`needs-triage` and a label-less issue are outside both flags.
2026-08-04 18:58:21 +00:00
cndgrr
919135db12 the two flags mean one thing by unblocked, and a key set is a set
B1: deliverable_keys answered a multiset, so a `+` title whose segments
normalize to one key made collision_flags find the issue adjacent to
itself and chain it to its own number — the comment asked an issue to
declare `Blocked by` itself, and two such carriers corrupted the chain
between them. The keys are deduped where the set property belongs.

B2: window_in_scope excluded only blocked/epic/post-merge, admitting
`needs-triage` and label-less issues, so the sweep could add
`needs-triage` to an issue and then tell it about a mint-time membership
call in the same pass. Both flags now call one unblocked_claimable
predicate — #293 D2 corrected gives one gloss on `unblocked` and D3b
says D3 uses it.

The window log line says "unblocked", not "ready": D3b corrected exactly
that wording, and the flag fires on `claimed` too.
2026-08-04 18:54:52 +00:00
cndgrr
5d13573c53 fixtures take the corpus correction and the three ruled cases
Triage ruled all three questions (18:11Z) and corrected the corpus: at the
10:28:54Z mint #257 was `ready`, not `claimed`, and #253 was `claimed`
with NO open PR — #285 was not created until 10:49:16Z. The morning board
takes that shape, which also makes the six `ready` non-members come out as
six, and puts the blocked twin on the board rather than only in a decision
probe.

D2's parenthetical is struck: unblocked = open and not blocked, both flags,
one definition. D3b is corrected in the same direction — a non-member
`claimed` WITH an open PR is flagged too, because a non-member holding a
builder and a review round is #292's competition realized, not a mild case
of it. Nothing in the implementation moves: neither flag ever consulted PR
liveness.

Three cases added, so the fixtures pin the rulings and not the prose:

- both carriers `claimed` with their own PRs open -> still flags. The
  ninety-three minutes from #285's creation to its merge are exactly when
  the struck parenthetical went silent on a live collision, which made the
  flag's firing a property of someone's workflow rather than of the board.
- a fifteen-member declaration with every member closed -> D3 dormant, and
  the release issue never flagged as its own non-member. A gate declaration
  never empties; the precondition is its OPEN members, read off the board.
- flagged -> resolved -> recreated unchanged -> silent, asserted as D4's
  stated boundary rather than left accidental.

Plus today's board — the `blocked` sink, a `claimed` gate member, two
`blocked` issues — which draws nothing. Verified live as well as in
fixtures: a DRY_RUN sweep of this branch against heavy-duty/ceremony's real
board draws zero flags of either kind.
2026-08-04 18:21:56 +00:00
cndgrr
bdcc211a0b the sweep flags what the window and collision rules forbid
Two advisory flags on the issue-flow sweep, the mechanical backstop for
#288's collision rule and #292's window rule. Both are prose today, and
both failed silently on the same morning: #284 was minted `ready` into a
file another issue held claimed with a PR in flight, and six `ready`
non-members raced an emptying gate. #262 measured the pattern — the same
class of rule, once in a guard, produced zero misses.

Comments only (D1): no label write, no state change, no new label. The
sweep never guesses intent; it states the board fact and triage resolves.

The collision key is the title's em-dash prefix NORMALIZED, because the
2026-08-04 miss spelled one deliverable two ways — `actions/issueflow-reconcile`
against bare `issueflow-reconcile` — so exact-prefix matching would have
missed the pair it was written for. One leading path segment comes off,
then every extension; a `+`-joined title matches on any segment.

The flag asks for a CHAIN, not a fan (#288 D3): within one key each issue
names the newest open carrier below it, so the declaration it asks for
releases exactly one successor per close.

A standing window is a release issue whose gate still holds an OPEN member.
The board read IS the open set, so membership decides openness with no
extra call, and an all-closed gate is the emptied gate the release's own
blocked -> ready promotion answers — which is why a `ready` release leaves
the flag dormant instead of flagging the whole board.

Dedup is the declaration echo's, extracted into state_marker /
state_echo_needed and scoped per family (D4): the marker is keyed to the
offending state's value and compared against that family's last word on
the thread, so a state that changes always speaks.

Fixtures replay the 2026-08-04 morning board whole and the post-ruling
board beside it: the first draws exactly four collision flags and six
window flags and writes not one label; the second draws none.

Closes #293.
2026-08-04 18:15:00 +00:00
cndgrr
90a35008a1 wip: the collision and window board flags, decisions and gather
Both checks ride the existing sweep walk and write nothing but comments
(#293 D1). The board read now answers the whole payload, because both
decisions are over the WHOLE board — every open issue's labels and title,
and every open release issue's body — and a second pagination for the same
rows would be a second board free to disagree with this one mid-sweep.

Fixtures still owed.
2026-08-04 18:06:03 +00:00
Daniel Marin
2807d34c74
Merge pull request #308 from andriujoseba/build/292-window-graph
docs: make standing release windows explicit graphs
2026-08-04 18:52:48 +01:00
Daniel Marin
b32d8253e7
Merge pull request #310 from dan-claude-bot/build/302-labeler-map-rows
fix: the labeler map learns lib/attention.sh — and the surfaces it never knew
2026-08-04 18:45:46 +01:00
Andriujose
d81b04148e docs: scope window insertion to immediate successors 2026-08-04 17:39:46 +00:00
dan-claude-bot
f5bac7c7ee fix: the labeler map learns lib/attention.sh and the surfaces it never knew (#302)
lib/attention.sh had #267 D4's premise exactly — both reconcilers source
it, nothing release-side does — and [scope:release-flow] alone was a
wrong answer of the class that decision exists to correct. The sweep
workflow pair joins beside its trigger pair (detached in #209), the
shared-lib tests take scope:labels alone (a test inherits no lib/**
glob), and D4's seven enumerated rows land one each. No catch-all, by
decision: both directories span all four scopes. Each of the 13 new map
rows is protected by its own assertion — deleted alone, each reds
exactly its case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 17:28:37 +00:00
Andriujose
ad5d988563 Merge remote-tracking branch 'origin/main' into build/292-window-graph 2026-08-04 17:28:26 +00:00
Daniel Marin
0c7d7e758c
Merge pull request #306 from cndgrr/build/304-reconcile-fixture-roster
fix(test): the labels-reconcile fixtures stop reading the live roster (#304)
2026-08-04 18:25:30 +01:00
Andriujose
630fd116c2 docs: distinguish window fan-out from collision chains 2026-08-04 17:23:03 +00:00
Andriujose
830a643a4b docs: make standing release windows explicit graphs
Add the mint-time membership call, the sink/source/subset invariants, and the release note while the contradictory successor-count criterion awaits triage clarification.\n\nCloses #292.
2026-08-04 17:12:48 +00:00
cndgrr
66a0eb5d65 test(labels-reconcile): fixture roster replaces the live panel by slot
The state-machine fixtures bound BOT1/BOT2/BOT3 to REQUIRED_BOTS by index off
the shipped .github/labels.conf, so three fixtures silently required a
four-member panel=. Shrinking it to three left the third slot unbound and
set -u aborted the file before assertion 1: 217 assertions became 0, on main
and on every branch cut from it.

The fixtures now write their own conf, in test/labels.test.sh's shape, at all
three load sites (top of file, the #205 re-drafted-round block, and the
mutant_blockers subshell). One live-file case survives as a property — the
shipped conf parses and recuses each member from its own panel — with no
index and no expected size, and a copy whose panel= names nobody proves it
still has teeth.

Refs #304
2026-08-04 17:04:09 +00:00
Daniel Marin
97cc00e268
Merge pull request #303 from dan-claude-bot/build/284-ruling-clock-comments-only
fix: the issue-side ruling clock reads comments only — an assignment is the claim's fact
2026-08-04 17:49:57 +01:00
Daniel Marin
404c4f6099
Update labels.conf 2026-08-04 17:39:45 +01:00
Daniel Marin
7e52e35596
Merge pull request #301 from andriujoseba/build/282-triage-slim
docs: slim TRIAGE.md incident narratives
2026-08-04 17:37:33 +01:00
Daniel Marin
b2a3e43ae4
Merge pull request #300 from cndgrr/build/267-labeler-scope
fix(labels): the scope map locates again — changelog.d/** is every PR
2026-08-04 17:37:20 +01:00
dan-claude-bot
48547d5eb1 fix: the issue-side ruling clock reads comments only (#284)
Claiming a needs-ruling issue dated it through the assigned timeline
event, silencing the 7-day escalation nudge at exactly the moment a
builder started working through it. The ruling block now reads
last_issue_comment_activity (D1); the reclaim clock keeps the assignment
(D2) because there the assignment IS the claim; post-merge hands its
evidence read to the ruling block instead of reading again (D6, D7); the
claimed branch reads both clocks at its top, before anything it posts.
LABELS.md and lib/ruling.sh now say what each surface's clock reads
(D4, D5). The #257-era order compositions move to the comments read —
the timeline is no longer an input the issue clocks take, and its
unreadability no longer holds unrelated writes hostage; that narrowing
is pinned rather than implied.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 16:16:19 +00:00
cndgrr
abe31a6d4a test(labels): assert each guard row alone, not bundled with its sibling
The D3 guard loop passed the action path and its test path to derives in
one call. derive_labels emits scope:guards when either matches, so any one
of the six rows could be deleted with the case still green — three
assertions standing in for six rows. Split into one assertion per path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 15:56:55 +00:00
Andriujose
f3125631b9 docs: keep answer outcome explicit 2026-08-04 15:51:16 +00:00
Andriujose
de3ab517d7 docs: slim triage doctrine 2026-08-04 15:48:27 +00:00
Daniel Marin
fe45180616
Merge pull request #299 from andriujoseba/build/257-board-read
fix: abort on unreadable issue board
2026-08-04 16:43:32 +01:00
cndgrr
5356702817 docs(changelog): fragment for #267 2026-08-04 15:34:17 +00:00
cndgrr
0fe015da69 fix(labels): the scope map locates again
changelog.d/** matched every PR that changes behavior, so scope:release-flow
was a constant, not a locator (#267).
2026-08-04 15:33:39 +00:00
Daniel Marin
5fd1c1b014
Merge pull request #295 from cndgrr/build/281-builder-slim
docs(builder): BUILDER.md slims to the rules — narratives become bare local cites
2026-08-04 16:27:06 +01:00
Andriujose
353fa54ae1 fix: abort on unreadable issue board 2026-08-04 15:03:25 +00:00
Daniel Marin
b7005ee9cd
Merge pull request #298 from andriujoseba/build/288-collision-edge
docs: declare collision-edge chains
2026-08-04 15:56:19 +01:00
cndgrr
b44f7ed6ee Merge branch 'main' into build/281-builder-slim 2026-08-04 14:47:38 +00:00
Andriujose
90961cad51 docs: declare collision-edge chains 2026-08-04 14:27:49 +00:00
Daniel Marin
1a9fdde8fc
Merge pull request #297 from andriujoseba/build/266-task-list-heading
docs: name the epic task-list heading
2026-08-04 15:20:56 +01:00
cndgrr
64e3af682f Merge branch 'main' into build/281-builder-slim 2026-08-04 14:13:38 +00:00
cndgrr
92c0e7c6f0 docs(builder): bind the red-head trigger to the round's ruled terms
The red-head rule in Picking carried its own trigger definition at the
pre-slim head; the squeeze took it, leaving 'failing check' and 'red
check' classified only forward in the review round. At an all-cancelled
head that let parked shape 2 read satisfied on its face, parking a claim
the rule says is never parked (#163, #276).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 14:13:34 +00:00
Andriujose
08b817b814 docs: name the epic task-list heading 2026-08-04 13:42:24 +00:00
cndgrr
49e1fe2e6e docs(builder): give step 1 its paragraph breaks back; fix two doubled connectives 2026-08-04 13:40:51 +00:00
cndgrr
900963d653 docs(builder): name the subject in the staleness-sweep clause 2026-08-04 13:40:08 +00:00
Daniel Marin
f899bb5690
Merge pull request #296 from andriujoseba/build/264-flagging-unassigned
docs(triage): scope no-assignee bug to flagging
2026-08-04 14:39:59 +01:00
cndgrr
43d845575a docs(builder): state #276's collapse rule in full, not by allusion 2026-08-04 13:39:06 +00:00
cndgrr
4800aef363 docs(builder): restore the re-request deferral and three clauses the audit found 2026-08-04 13:36:41 +00:00
cndgrr
b4779bbc18 docs(builder): keep code spans unbroken across the wrap 2026-08-04 13:32:36 +00:00
cndgrr
46aeb02078 docs(builder): restore five rules the squeeze had compressed away 2026-08-04 13:31:48 +00:00
cndgrr
4100121c7a docs(builder): final squeeze — 272 lines, every D2 rule intact 2026-08-04 13:30:12 +00:00
cndgrr
cef7ea206b docs(builder): WIP — proposal biography and list scaffolding out 2026-08-04 13:27:52 +00:00
cndgrr
e1c3e3e9af docs(builder): WIP — terse register throughout 2026-08-04 13:23:56 +00:00
cndgrr
1b6143f4aa docs(builder): WIP — drop optional whys, classes as a list 2026-08-04 13:21:26 +00:00
cndgrr
92ff82c1c6 docs(builder): WIP — minimal-statement register 2026-08-04 13:18:31 +00:00
Andriujose
5ea59780b6 docs(triage): scope no-assignee bug to flagging 2026-08-04 13:16:58 +00:00
cndgrr
d25cbb04b7 docs(builder): WIP — further compression, ruling ladder tightened 2026-08-04 13:16:37 +00:00
cndgrr
e3ec95bb37 docs(builder): WIP — tighter register across every section 2026-08-04 13:14:35 +00:00
Daniel Marin
4e0c5c2568
Merge pull request #294 from andriujoseba/build/291-terminal-fragment-cites
docs: add terminal citations to 0.6.0 fragments
2026-08-04 14:14:23 +01:00
cndgrr
6ebdd2efd9 docs(builder): WIP — slim BUILDER.md to the rules, bare local cites 2026-08-04 13:11:32 +00:00
Daniel Marin
acf4125fd6
Merge branch 'main' into build/291-terminal-fragment-cites 2026-08-04 14:11:20 +01:00
Daniel Marin
2e11855839
Merge pull request #289 from cndgrr/build/262-fragment-cite
feat(changelog): the terminal issue cite joins the fragment guard
2026-08-04 13:58:32 +01:00
Andriujose
7af13eae73 docs: add terminal citations to release fragments 2026-08-04 12:53:38 +00:00
cndgrr
9f1f88de72 fix(changelog): #285's fragment carries a terminal cite
changelog.d/253.md landed on main after this branch's point with the
cite trailing the period — the crew#309 shape, the fifth fragment to
arrive with it since #262 measured two. The criterion is that
changelog.d/ is clean at the head, and the head CI reviews is the merge
with main, which is where this one surfaced.

Refs #262.
2026-08-04 12:40:55 +00:00
cndgrr
d13f61b6c5 merge: origin/main — #285's fragment lands under the rule
The self-guards job checks out the merge of this branch with main, so
changelog.d/253.md from #285 reaches changelog_fragment_problem there and
nowhere else; the branch alone is green. Merged rather than rebased
because the round's reviewers track head SHAs.

Refs #262.
2026-08-04 12:39:55 +00:00
cndgrr
75a5b68c8a fix(changelog): one fragment, one diagnosis, wherever the long entry sits
awk runs END on the way out of an exit from a main rule, so the length
row printed mid-file was followed by the citation row it outranks — the
internal protocol line landing inside the human-facing excerpt. Found by
claude-bot and kimi-bot in #262's first round, independently and with the
same reproduction.

The guard is the reported flag the empty-heading walk in this same
predicate already uses. The fixtures are the axis 57.md could not reach:
its over-bound entry is last, so only END's flush can print. 58.md puts
one before another bullet, 59.md before a heading and after a misplaced
cite. With lib/changelog.sh alone reverted they red, which is what the
green suite was hiding.

Refs #262.
2026-08-04 12:38:13 +00:00
Daniel Marin
d30b091872
Merge pull request #285 from andriujoseba/build/253-release-init-announce
feat: announce release initialization
2026-08-04 13:22:21 +01:00
cndgrr
84c73fe22e test(changelog): the precedence case runs in the order that can fail
Refs #262
2026-08-04 12:16:49 +00:00
cndgrr
175e082c1b test(release-exercise): the replay's fragment fixture carries a cite
The step-replay job builds a consumer tree and runs the REAL assembler
over it, so its changelog.d/42.md is a fragment fixture like every one in
test/ — and the only one living outside it. #262's diff-surface criterion
says no workflow file; the criterion and a green head cannot both hold
here, and the fixture is the smaller thing to move.

Refs #262
2026-08-04 12:15:04 +00:00
cndgrr
8aeea67d8d docs(changelog): the citation is guard-enforced, not house style
Closes #262
2026-08-04 12:12:52 +00:00
cndgrr
f4cb970097 test(changelog): the cite rule's own cases, both callers asserted
Refs #262
2026-08-04 12:11:09 +00:00
cndgrr
ef818001d0 test(changelog): every fragment fixture carries a terminal cite
Scoped to the fixtures the new rule actually binds: a fragment whose
predicate complaint is already its name, a smuggled heading, a dangling
heading or the 300-character bound is left alone, so the diagnosis it
tests is still the one it draws. The section-predicate fixtures are
untouched (D4).

The computed-length fixtures keep their measured lengths: the cite is
seven characters, so an entry that must measure exactly 300 builds 293
of the run and lets the cite carry the rest. 33.md's cite lands on the
last continuation line, which is the wrapped-citation case.

Refs #262
2026-08-04 12:08:54 +00:00
cndgrr
c40d9412f6 wip(test): fixture sweep, first pass — over-reaches onto section fixtures
'- Fixed entry.' is shared between the dangling-heading fragment fixture
and the section-predicate fixture, so the global replace crossed D4's
line. Next commit filters to fragments that actually red on the cite rule.

Refs #262
2026-08-04 12:06:29 +00:00
cndgrr
46b80fb6b2 fix(changelog): the four drifted fragments carry a terminal cite
Refs #262
2026-08-04 12:01:45 +00:00
cndgrr
72fa3e0b4d feat(changelog): the terminal issue cite joins the fragment guard
Refs #262
2026-08-04 12:00:48 +00:00
Daniel Marin
101f7bd309
Merge pull request #286 from cndgrr/build/276-checks-collapse
docs(builder): a displaced predecessor is not the check's verdict
2026-08-04 12:53:51 +01:00
Andriujose
994aeb58aa fix: cite release doctrine in both layouts 2026-08-04 11:43:44 +00:00
cndgrr
4c8398a15d docs(builder): the collapse rule, without the incident
Round 1 on #286: codex, claude and kimi each blocked on the same thing —
the amended paragraph carried incident narrative that CONTRIBUTING.md's
doctrine conventions keep in the cited record.

Drop #275's head sha and both start stamps, the cancel-in-progress
episode sentence, and the four clauses of 2026-07-27 gloss provenance.
Carry checks_state's carve-out as one clause with a bare (#139, #276).

The rule is unchanged and still decides #275's head unaided: two entries
for one check, newest by start time wins, and the cancelled one is not
the check's word while a non-cancelled sibling stands.
2026-08-04 11:29:13 +00:00
Andriujose
0bbdb2c6e2 test: mark portable citation literal 2026-08-04 11:19:59 +00:00
Andriujose
e7df33b277 fix: make release init citation portable 2026-08-04 11:17:51 +00:00
cndgrr
b2313beae8 docs(builder): a displaced predecessor is not the check's verdict
Step 1 ruled what a rollup entry means and never said which entry to
read. A job in a cancel-in-progress group displaces itself, so a head
routinely carries a CANCELLED node beside the SUCCESS that replaced it,
and read by class alone that head is not green while checks_state calls
it SUCCESS.

State the collapse ahead of the classes it feeds: newest entry by start
time, and a CANCELLED entry is not the check's word while a non-cancelled
sibling stands at the same head. All-cancelled and pending are untouched.

Refs #276
2026-08-04 10:58:59 +00:00
Daniel Marin
017c571438
Merge pull request #277 from cndgrr/build/251-vendored-manifest
feat(guards): the tag declares its vendorable set
2026-08-04 11:52:15 +01:00
Andriujose
f300f60d6e test: keep release init probes shellcheck clean 2026-08-04 10:50:55 +00:00
Andriujose
e3b4b2cdfd feat: announce release initialization 2026-08-04 10:48:55 +00:00
Daniel Marin
80b9eaf6e7
Merge pull request #283 from andriujoseba/build/280-doctrine-conventions
docs: define vendored doctrine conventions
2026-08-04 11:44:29 +01:00
Daniel Marin
57ff445341
Update labels.conf 2026-08-04 11:38:54 +01:00
Daniel Marin
0b5185aa28
Update labels.conf 2026-08-04 11:33:16 +01:00
cndgrr
42b8fb6850 fix(consumers): the manifest is readable at 0.1.0, not 0.5.0
docs/VENDORED.txt and actions/docs-sync/docs-sync.sh entered the tree in
the same commit and are byte-identical at every tag — blobs 10c20a3c and
ba426479 at 0.1.0 through 0.5.0 — and 0.1.0's copy of the tool is already
manifest-driven (MANIFEST="docs/VENDORED.txt", L75). Citing 0.5.0 told the
0.1.0-0.4.1 tail, which is exactly the population this section is written
for, that the manifest was unavailable at its pin, so it would keep the
hardcoded list: #251's failure mode reproduced by the document that exists
to abolish it. The same file already said 0.1.0 at L131-L133.

Also make the guard's tracked-ness skip announce itself. It degrades to
"not asserted" wherever the tree is not a git work tree root, and doing
that in silence is the shape this script's own header argues against, so
the skip now prints on both output paths, green and red, with a test row
each way.

Round 1: claude blocking point, and claude nit 3.
2026-08-04 10:29:44 +00:00
cndgrr
b9eedd8973 docs(consumers): cite the manifest availability to the tag that shipped it
0.5.0 availability is actions/docs-sync own arrival (#19); #251 is the
guidance and the guarantee, not the file date.

Refs #251
2026-08-04 10:18:17 +00:00
cndgrr
7909383ca0 docs(consumers): read the pin manifest, never a copy of it
Re-vendor tooling and docs-sync equivalents read the pin docs/VENDORED.txt
(available at 0.5.0 and later) instead of naming the doc set themselves, so
a new doctrine file reaches every consumer at its next ordinary pin bump
with zero list edits. A hardcoded list propagates nothing and its
staleness is silent: docs-sync --check asserts byte-identity for the files
the list names and says nothing about one it omits.

What makes reading the manifest sufficient rather than merely better is
the self-guard this PR adds, tagged unreleased until the first tag carries
it, per the RELEASES.md paragraph above it.

Refs #251
2026-08-04 10:18:17 +00:00
Andriujose
6949f8cbdc docs: define doctrine conventions 2026-08-04 10:17:30 +00:00
cndgrr
5677710b2c test(guards): the manifest guard drives the whole test plan
One CI step beside the self-ref pin, and test/vendored.test.sh covering
both directions: the manifest -> tree scan (missing, symlink, directory,
empty, ../ escape, absolute, untracked) and the closed-world root rule
(neither list, vendored, exempted, prose is not an input, no recursion
below the root), plus the real tree unmodified and the RELEASES.md
regression both ways.

The one-off `grep -Fx RELEASES.md` row at test/docs-sync.test.sh is
deleted (#251 D4): two spellings of "the manifest is right" is the drift
the manifest exists to prevent. Its intent is now a guard case, which the
next doctrine file inherits for free.

Refs #251
2026-08-04 10:16:27 +00:00
cndgrr
0760d0c21f feat(guards): the vendored manifest guards ceremony own tree
docs/VENDORED.txt is already machine-authoritative on the consumer side —
actions/docs-sync reads it as the sole declaration and enforces
"manifest union .ceremony/, nothing else". Nothing enforced the other end:
a doctrine file landing at ceremony every root with nobody adding it to
the manifest is a SILENT miss, because docs-sync only asserts
byte-identity for the files the manifest names.

Two directions, two mechanisms (#251 D2): manifest -> tree is a scan
(regular, non-empty, tracked, no symlink, no directory, no .. escape);
tree -> manifest is a closed-world rule over root *.md with a short
in-script exemption list, because nothing in the tree answers "which
files are vendorable".

Refs #251
2026-08-04 10:16:16 +00:00
Daniel Marin
05738ff0dd
Merge pull request #273 from andriujoseba/build/238-marker-check
feat: guard unreleased documentation markers
2026-08-04 11:14:01 +01:00
Andriujose
e98020b432 test: document marker fixture incidents 2026-08-04 09:57:51 +00:00
Daniel Marin
4a9f113ef3
Merge pull request #274 from cndgrr/build/254-postmerge-nudge
feat(issueflow): the post-merge evidence nudge
2026-08-04 01:35:08 +01:00
Daniel Marin
58a8d308e4
Merge pull request #275 from dan-claude-bot/build/260-272-green-conclusion
docs(builder): green is read from conclusion, and the checkless head is the third ruled case
2026-08-04 01:34:52 +01:00
dan-claude-bot
806e99e1cb docs(builder): green is read from conclusion, and the checkless head is the third ruled case
The ruled-term paragraph now names its field — a check carrying a
terminal conclusion is green or not-green by that conclusion whatever
its status reports — and rules the head with no checks configured:
nothing to wait for, request straight away, no argued exception owed.
The draft-round restatement comes out so the file states the rule once.

Closes #260, closes #272 via the PR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 23:44:43 +00:00
cndgrr
3fffb8d849 fix(issueflow): the nudge says what it measured
"no activity for N days" was true of the old clock and is now imprecise:
an assignment no longer counts, so an item assigned yesterday would read
a nudge claiming nine days of nothing. It says "no comment" instead —
the fact the sweep actually read.

Refs #254
2026-08-03 23:42:19 +00:00
cndgrr
fd9da98abf docs(labels): the evidence clock names what the sweep actually reads
"a comment, a review or a commit" is the ruling nudge's house phrasing and
false on the issue surface twice over: there is no review or commit fact in
what the sweep reads, and an assignment is no longer counted here. Say
what is read, and say what does not buy another 7 days of silence.

Refs #254
2026-08-03 23:40:48 +00:00
cndgrr
d4a82707f2 fix(issueflow): the evidence clock is not the claim clock
The nudge rode `last_issue_activity`, which counts `assigned` timeline
events because assignment is the claim the 48-hour reclaim protects.
`post-merge` has no claim: an assignee there is the invalid composition
the flag beside it reports, so counting the assignment let a broken board
buy the item another 7 days of silence — this issue's failure direction
taken backwards.

One computation, two clocks over it: `issue_activity_at` is the body,
`last_issue_activity` keeps the reclaim and ruling clocks byte-identical,
and `last_issue_comment_activity` is the evidence clock. Both clocks are
read before this branch posts anything, the ruling one included — read
after, it would date the issue by the evidence nudge's own comment and
silence the ruling nudge, which is the self-silencing the branch already
guarded against in the other direction.

Refs #254
2026-08-03 23:38:53 +00:00
cndgrr
37d138fecf test(issueflow): prose is never judged, and the constant has one spelling
The two 'must fail loudly' cases from the plan: an unparseable body still
nudges and the nudge quotes none of it, and a grep-level pin that no second
7-day constant appears in the sweep.

Refs #254
2026-08-03 23:10:42 +00:00
cndgrr
b18168b4f3 docs(labels): quiet post-merge is visible, still never reclaimed
LABELS.md said the sweep never reclaims post-merge and stopped there, which
now reads as 'the machine says nothing' — after this change it says one
thing, once per 7 quiet days. Plus the fragment.

Refs #254
2026-08-03 23:08:11 +00:00
cndgrr
b3fd26b21b test(issueflow): the evidence nudge's fixtures — window, addressee, no marker
Covers the must-nudge pair (8 quiet days; post-merge + needs-ruling both
speak), the must-not set (6 days, fresh comment, every other queue state),
self-rate-limiting proven by sweeping again a day later rather than by
asserting a marker's absence, and zero writes across every probe.

Two existing probes move: #36's 'no comment' assertion described the
starvation this issue ends, and #67 gets recent activity so its precedence
count stays the assertion doing the work.

Refs #254
2026-08-03 23:06:47 +00:00
Andriujose
2a1f8a012b test: pin inline marker mention boundary 2026-08-03 23:05:05 +00:00
cndgrr
63351bd425 feat(issueflow): post-merge evidence nudge fires on 7 quiet days
WIP checkpoint: the nudge itself, tests still owed. Reuses
ruling_nudge_decision so the 7-day rule keeps one spelling, addresses the
triage actor (post-merge is triage's completion queue), and carries no
idempotency marker — the comment is itself activity, so it self-rate-limits.

Refs #254
2026-08-03 23:03:53 +00:00
Andriujose
d87d76d64b fix: align marker guard with release oracle 2026-08-03 22:56:02 +00:00
Daniel Marin
c7f40bb818
Merge pull request #270 from cndgrr/build/258-fix-round-draft
docs(builder): a fix round may ride a draft
2026-08-03 23:55:44 +01:00
Andriujose
7200c8da49 docs: define traceable availability markers 2026-08-03 22:51:17 +00:00
Andriujose
dca8e7220c test: exercise documentation marker guard 2026-08-03 22:49:22 +00:00
Andriujose
461c25b08e wip: add unreleased marker guard 2026-08-03 22:47:55 +00:00
Daniel Marin
1b84d27691
Merge pull request #269 from andriujoseba/build/237-doors-unchanged
docs: define doors-unchanged drill evidence
2026-08-03 23:40:32 +01:00
cndgrr
349fb4d964 docs(builder): crew#139 is an open proposal, and the CI cost is what it would end
Both occurrences said crew's engine already converts a PR back to draft at
round close; crew#139 is OPEN, blocked and unassigned, so the passage was
describing an unshipped mechanism as live (codex, kimi). The same sentence
also read as if the conversion caused the CI spend — it is the status quo
the conversion would end, so the counterfactual is now explicit. Rewrapped
the paragraph at 80 columns; 7948b99 had left one line at 133.
2026-08-03 22:27:42 +00:00
Andriujose
e728612ea1 test: resolve sibling release dependencies 2026-08-03 22:12:49 +00:00
cndgrr
3367cae4d4 docs(reviewer): one vocabulary across the two files — mid-round saves
Refs #258
2026-08-03 21:52:44 +00:00
cndgrr
7948b99acf docs(builder): unknot the opening sentence
Refs #258
2026-08-03 21:51:48 +00:00
cndgrr
dbbdbdb6dd docs(builder): cite what this file carries, not what crew's engine does
The first paragraph referred to a 15-minute cadence and a checkpoint
discipline, neither of which BUILDER.md states — the cadence is crew's
engine rule and the pointer sent a reader to a section that says nothing
about it. Attribute the measurement to crew#139 and point at what
Building actually says. The LABELS.md sentence stops restating the
state:building row's condition and points at it instead: one rule in two
voices, per #258's test plan.

Refs #258
2026-08-03 21:51:25 +00:00
cndgrr
6634a517bc docs(builder): a fix round may ride a draft
BUILDER.md's review round assumed ready-throughout, so a builder or
reviewer meeting a mid-round draft found behaviour the doctrine never
described. Three points, doctrine not mechanism: the draft phase stays
the builder's through a fix round, ready-for-review is the builder's own
act and no engine's, and where the draft suppressed CI green is proven
at the flip with the request following it — step 1's rule at a stated
moment, not a second rule.

REVIEWER.md gains the reading that keeps a reviewer from misfiling it:
a draft carrying state:addressing is a fix round in progress.

Refs #258
2026-08-03 21:49:11 +00:00
Daniel Marin
3c96406081
Merge pull request #261 from cndgrr/build/252-blocker-echo
feat(issueflow): echo the parsed blocker set when it changes
2026-08-03 22:43:29 +01:00
Andriujose
e5a87201f8 test: derive release path from executable lines 2026-08-03 21:43:23 +00:00
Andriujose
75d85df20c docs: define doors-unchanged drill records 2026-08-03 21:41:34 +00:00
Andriujose
ba55d1d552 feat: declare the release door path 2026-08-03 21:39:02 +00:00
Daniel Marin
8f4478f67e
Merge pull request #268 from andriujoseba/build/218-refs-not-closing
feat: guard Refs PRs from closing issues
2026-08-03 22:32:20 +01:00
cndgrr
2498dfdce0 test(issueflow): AC-1's other input is an edit, not a re-sweep
AC-1 names two inputs and says "both by fixture". The suite had the first and,
for the second, only a re-sweep of a BYTE-IDENTICAL body — which is the test
plan's other must-not-echo bullet, and cannot stand in for this one: an
identical body is quiet under both spellings of the decision, the one that
keys on the parse and the one that keys on the prose, so it cannot tell them
apart. Only an edit that changes the prose and preserves the parse can.

The new probe reorders the refs and adds sentences on either side, leaving the
set at {#90, #91}, and asserts the marker count, the thread's total echo count
and the issue-edit count all hold still. What it pins is that the marker is a
function of the PARSE and not of the prose around it — the property the whole
idempotency rests on.

Mutation-proven isolating: an echo that also re-fires when the declaration
prose moved since the last echo — quiet on identical re-sweeps, correct on
every set change — passes the pre-existing suite 304/0 and reds only here.
2026-08-03 21:15:37 +00:00
cndgrr
2bab52dc71 fix(issueflow): the echo's illustrative #9 is an illustration, not a reference
The echo body wrote `no longer blocked by #9` unbackticked, twice in one
sentence. GitHub linkifies both, so every echo posted a pair of "mentioned in"
events onto this repo's issue #9 — an issue with nothing to do with the one
being echoed. On a 15-minute cron that is one pair per `blocked` issue on the
board, and the first sweep after merge would have written the whole board's
worth at once.

The file's own convention two branches down already avoids this: the
`blocked-unparseable` comment code-spans its `Blocked by #N` for exactly this
reason. The prose is unchanged, the rendered set is unchanged, and the marker
digests the parsed set rather than the comment body, so no marker moves.
2026-08-03 21:15:37 +00:00
Andriujose
360b262c47 fix: satisfy refs guard shellcheck 2026-08-03 21:00:40 +00:00
Andriujose
022d1fcda6 fix: exercise refs guard action boundary 2026-08-03 20:58:56 +00:00
cndgrr
d604abd074 test(issueflow): spell a healthy blocked+attention issue as no diagnostic
#263 landed on main after this branch's head and asserts that a healthy
assigned attention under blocked posts nothing at all. The #252 echo makes
every blocked issue carry one comment — its parse — so the proxy is false
while the contract behind it is not: probe 66 draws the parse echo and no
attention diagnostic.

Re-spelled the way the same section's other cases already spell it, as the
absence of the attention-malformed marker, plus a companion assertion that
exactly one comment landed. That is strictly tighter than test -f was: this
case now fails if an attention comment appears beside the echo, which the
old form could not detect once any comment existed.

Refs #252
2026-08-03 20:44:09 +00:00
cndgrr
fb89c92434 Merge remote-tracking branch 'origin/main' into build/252-blocker-echo 2026-08-03 20:42:09 +00:00
cndgrr
5314a8f343 test(issueflow): say what the pairwise family actually is
claude-bot: `{acme.widgets#9}` is not a reachable declared set — the clause
parser stops at the `.` and blocked_reference_records never hands the token
through, though issue_references does answer CROSS for it. The comment claimed
all four were declarations the reconciler accepts. The member stays (the
marker's contract is over the tokens the classifier admits) and the comment
now says which is which.

Refs #252
2026-08-03 20:39:14 +00:00
cndgrr
04bdde7b1e fix(issueflow): the parse echo is idempotent against the last echo, not the history
ensure_comment's any-occurrence grep answers "have I ever said this", which
is right for a flag like blocked-unparseable and wrong for a value that
changes. A -> B -> A found A's own first echo and stayed silent, leaving the
thread's newest echo asserting B while the sweep gated on A: a stale parse
presented as the current one, and the third edit did change the parsed set,
so the criterion says it speaks.

blocked_parse_echo_needed compares this parse's marker against the LAST
blockers-parsed-* marker on the thread. The read stays inside guarded_read /
skip_issue, so an unreadable history still fails closed (#247 D1) rather than
answering "nothing echoed yet" and re-posting. ensure_comment is untouched
for every other caller.

Refs #252
2026-08-03 20:35:10 +00:00
Andriujose
869d05bf85 fix: satisfy CI shellcheck gate 2026-08-03 20:34:35 +00:00
Andriujose
66b136efc0 docs: wire refs guard into ceremony flow 2026-08-03 20:31:34 +00:00
Andriujose
dcf72a9af8 feat: add refs-not-closing guard core 2026-08-03 20:29:51 +00:00
Daniel Marin
db63b677bb
Merge pull request #263 from andriujoseba/build/232-attention-diagnostics
feat: diagnose malformed attention targets
2026-08-03 21:24:27 +01:00
cndgrr
195c49b8e1 test(issueflow): the marker's collision test is pairwise, not through one form
Anchoring every pair on the `/` spelling passed under a fix that only
taught the slug about `/` — and that fix still collapses `acme-widgets#9`,
`acme_widgets#9` and `acme.widgets#9` onto one marker. Found by mutating
the implementation to that cheap fix and watching the suite stay green on
the cases that matter. The contract is that no two distinct parses
collide, so the assertion is now every pair.

Refs #252
2026-08-03 20:07:23 +00:00
cndgrr
5910c36137 fix(issueflow): key the parse echo to the set, not to a slug of it
The marker claimed to be scoped to the parsed set's value and was scoped
to a lossy rendering of it: `tr -c '[:alnum:]' '-'` maps `acme/widgets#9`
and `acme-widgets#9` — both parses this reconciler accepts — onto one
marker, so a declaration edited between them found the old echo and said
nothing. Silence in exactly the case the echo exists to speak about.

The identity is now a digest of the exact rendered set. The readable slug
stays in front of it and decides nothing. Distinguishing `/` would have
closed the reported pair and left the class: `-`, `_` and `.` are all
legal in a qualifier and all collapse the same way, so all four are
pinned, and the sweep probe observes the second echo actually landing.

Refs #252
2026-08-03 20:05:04 +00:00
Andriujose
8aa7b12bf9 test: satisfy CI shellcheck annotations 2026-08-03 19:55:32 +00:00
Andriujose
a9b3f4d766 test: cover attention target diagnostics 2026-08-03 19:53:25 +00:00
Andriujose
e7750c0c8f feat: diagnose malformed attention targets 2026-08-03 19:48:15 +00:00
Daniel Marin
5626a4ef1e
Merge pull request #259 from andriujoseba/build/230-attention-target
docs: clarify the attention target at triage write time
2026-08-03 20:40:48 +01:00
cndgrr
5cfb69e104 test(issueflow): the parse echo, mutation-proven in both directions
The idempotency contract is the marker's scope, so both directions are
pinned: an unchanged set must reuse its marker (or a 15-minute cron
repeats itself forever) and a changed one must not (or a misparse hides
under a marker the thread already carries). crew#308's negated clause is
replayed through the sweep, and the empty parse is echoed beside the
untouched `blocked-unparseable` flag.

Refs #252
2026-08-03 19:28:31 +00:00
cndgrr
374005ef77 feat(issueflow): echo the parsed blocker set when it changes
The clause parse is exact and unforgiving, and its output was invisible:
every incident in this class was found by a human running the parser by
hand, hours or days late. The sweep now states what it read — one marker
comment per distinct parsed set, comment-only, no label writes.

Refs #252
2026-08-03 19:24:59 +00:00
Daniel Marin
f73facea6e
Merge pull request #256 from cndgrr/build/247-read-guards
fix(issueflow): a failed read must never reach a decision function
2026-08-03 20:19:35 +01:00
Andriujose
dbb0554288 docs: clarify attention target 2026-08-03 19:07:08 +00:00
Daniel Marin
2e5aa52e29
Merge pull request #255 from andriujoseba/build/248-release-doctrine
docs: add release-management doctrine
2026-08-03 20:00:30 +01:00
cndgrr
6217798e14 fix(issueflow): a per-issue pass commits its whole effect, or none of it
The per-read guards closed the reported class — a failed read never reaches
a decision function — and left one layer standing. A pass could mutate and
only THEN reach a guarded read, fail it, and report the issue as skipped:
`stale` removed, or `needs-triage` minted, under a log line saying the
sweep had touched nothing. That is the same false report #247 exists to
close, told from the other end, and the panel reproduced it on four
separate compositions.

Fixed as the ordering invariant rather than per site. Inside
reconcile_issue_pass's subshell, run() and log() stage their effects, and
commit_staged_effects replays them in order once the pass has completed.
skip_issue emits its own line directly and exits, so the buffer dies with
the subshell. A skip therefore implies zero `gh issue edit`, zero
`gh issue comment`, and no log line about a mutation that never landed —
for compositions nobody has written yet, because reconcile_issue has no way
to mutate directly. Reads stay where they are: they may happen anywhere,
since nothing lands until the end.

Stated per site it would hold until the next composition. Two consequences
worth naming: reconcile_ruling is covered without touching lib/ruling.sh,
because it posts through the sourcing script's run()/log() — the PR surface
keeps its own and is unaffected; and a genuine crash mid-pass now also
lands nothing, where before it left the earlier mutations applied. D4's
handler string, D6's tail and D7's exit 0 are all unchanged, and the
healthy path is byte-identical: every staged write commits under the same
`>/dev/null` its call site already applied.

Refs #247
2026-08-03 18:55:52 +00:00
Andriujose
8c29424519 docs: correct release gate mechanics 2026-08-03 18:26:58 +00:00
cndgrr
f965e8404c test(issueflow): an absent fixture answers nothing to a --jq read, as gh does
The sourced stub returned a literal `[]` to --jq callers when a fixture
was missing, where the real API answers an empty list and the filter
yields nothing. last_issue_activity then sorted `[]` beside an ISO-8601
timestamp — and `[]` outsorts a timestamp in the C locale but not in a
UTF-8 one, so the sweep dated an issue by a stub artifact on the runner
and by created_at here.

The old code swallowed the resulting `date` failure and graded the claim
on a literal 0 anyway; #247's guards turn a failed read into a skip,
which is what made the lie visible. Adopt the arrival stub's shape.
Suite green under LC_ALL=C, C.UTF-8 and en_US.UTF-8.

Refs #247
2026-08-03 18:14:19 +00:00
cndgrr
24f62cc8e9 test(issueflow): the failing --jq read yields no timestamps, as gh does
The .http-error mode applies a requested --jq filter to the error body,
so a failing comments read returns nothing rather than a JSON blob —
which is what let last_issue_activity fall back to created_at and
reclaim a live claim. With it, all three of the issue's must-fail-before
cases fail against the pre-change script, the destroyed claim included.

Pin offsite_timeline's own deliberate silence directly: the activity
read hits the same endpoint, so probe 32 now skips before the offsite
verification it used to reach (D8 leaves that read alone).

Refs #247
2026-08-03 18:08:19 +00:00
cndgrr
865d5bd1df test(issueflow): drive the real 5xx — a JSON error body on stdout
The PATH-stubbed gh gains a `.http-error` mode: the response body goes
to STDOUT, the reason to stderr, the status non-zero. The existing
`.error` sentinel produces empty stdout, which is the *safe* path — an
empty label set either way — and is why this class was never caught.

The three must-fail-before cases, plus the 200-`null` path a status
check alone leaves open, the suppressed-marker duplicate, the D6 tail's
count and numbers, and the crash handler proven distinct from a skip.

Refs #247
2026-08-03 18:06:13 +00:00
cndgrr
13e8f54d60 fix(issueflow): a failed read never reaches a decision function
`gh api` prints a 5xx response body to stdout AND exits non-zero, and
GitHub's 5xx body is a JSON object. Inside the per-issue subshell that
payload passed `has("pull_request") | not`, emptied `.labels[]`, and
`queue_decision` — correct on the input it was handed — wrote
`needs-triage` onto a healthy epic. The run then logged `reconciled.`
and exited 0 (crew#329, #247).

errexit could not have caught it: a command whose status is tested by
`||` runs with errexit suppressed, and the suppression extends through
the whole subshell body, so the `|| log` handler is what disables the
errexit that would have aborted at the failed read. Removing the handler
revives errexit and loses #91's resilience, and an inline `set -e` does
not re-arm it. Explicit per-read checks are the mechanism.

Every read inside that subshell is now checked — the issue read on its
status AND on its payload shape (an HTTP 200 whose body is `null` exits
0 and empties the label set just the same), both reads in
`last_issue_activity`, and the comments read in
`issue_comment_has_marker`. On failure the issue is left exactly as it
is, the reason rides its own `#$n:` line, and the subshell exits with a
distinguished status the sweep counts, so a deliberate skip is not
reported as a crash and a genuine crash is still named byte-identically.

`read_failure_reason` moves to lib/read.sh beside a new `guarded_read`,
sourced by both reconcilers: labels-reconcile's copy was the only one,
and the issue surface needs the identical rule.

Refs #247
2026-08-03 18:01:58 +00:00
Daniel Marin
d8a70657eb
Merge pull request #250 from cndgrr/build/236-unrequested-green-gate
fix(labels): blocker:unrequested waits for green, and for the round to settle
2026-08-03 18:48:22 +01:00
Andriujose
a74ebb9876 docs: align release triggers with shipped flow 2026-08-03 17:33:39 +00:00
Andriujose
ab60709f49 docs: add release-management doctrine 2026-08-03 17:29:15 +00:00
cndgrr
d4512a1e82 test(labels): drive the fetch that feeds the grace, at the sweep level
The predicate's fixtures cannot see the read that sets HEAD_COMMIT_AT, so a
sweep probe drives it both ways: read, and the blocker is written off a dated
head; denied, and the denial is named on its own line while the state still
converges — this read narrows one blocker, it does not skip the PR the way an
unreadable rollup does. Renaming the assignment reds the probe.

The read also moves after the mergeability/checks skip: a PR the sweep walks
away from must not pay for a call whose only consumer is a blocker that pass
will never decide.

Refs #236
2026-08-03 17:23:11 +00:00
Daniel Marin
d225c68fa4
Merge pull request #243 from andriujoseba/build/241-open-pr-refs
fix: preserve claims with open Refs PRs
2026-08-03 18:21:21 +01:00
cndgrr
8459a9255b test(labels): drive the green gate and the grace, and mutate both to prove them
The six cases the issue names, plus the boundary (the grace is inclusive), a
verdict inside the window against an old head, both unreadable timestamps, and
the configured-grace override.

Two proofs run rather than asserted in prose: a copy of the script with the
gate removed must flag the PENDING fixture, and a copy with the grace removed
must flag the inside-the-window one. The harness checks itself against the
unmutated copy first, or a flip would prove nothing.

The pre-#236 stall fixtures gain real timestamps. Their symbolic stamps are
not unreadable — GNU date reads `t1` as 01:00 in military timezone T, a time on
whatever day the suite runs — so a grace measured against a fixed NOW would
flip with the calendar. Every assertion is byte-identical.

Refs #236
2026-08-03 17:18:46 +00:00
cndgrr
b5ec236b42 fix(labels): blocker:unrequested waits for green, and for the round to settle
`blocker:unrequested` is the one blocker that names an act the author must
perform, and it never knew whether performing it was permitted. BUILDER.md's
review round requires a green check at the head before requesting, so a
builder waiting out a pending run is complying — and the blocker fired on
compliance (crew#318 ~12:44Z, ceremony#235 12:30Z, both 2026-08-03).

Gate the branch on CHECKS ∈ SUCCESS | NONE (D1): PENDING is CI's move, which
state:addressing already says, and FAILURE belongs to blocker:ci-red rather
than to a second label on the same stall. Then require the supporting facts —
the head's own date and the round's newest submitted review — to have stood
for RECONCILE_UNREQUESTED_GRACE (default 300s, D2), measured off those
timestamps because this sweep is stateless per pass. A timestamp that cannot
be read refuses the blocker.

Refs #236
2026-08-03 17:12:38 +00:00
Andriujose
071ac49cc2 test: retain executable transition control 2026-08-03 17:08:16 +00:00
Andriujose
702ec5fc5d test: model both open PR linkage paths 2026-08-03 17:08:16 +00:00
Andriujose
e133924887 fix: preserve claims linked by open Refs PRs 2026-08-03 17:07:40 +00:00
Andriujose
19ae4aedd1 test: reproduce open Refs claim loss 2026-08-03 17:07:40 +00:00
Daniel Marin
78a86198d2
Merge pull request #245 from cndgrr/build/242-post-merge-pr-order
fix(issueflow): the deliverable PR is the last merged, not the highest numbered
2026-08-03 18:03:10 +01:00
cndgrr
544d4a0603 style(test): separate the merge-order block from the offsite decisions
Refs #242
2026-08-03 16:29:21 +00:00
cndgrr
9c690f02b7 test(issueflow): drive the merge-order selection, and the spent-marker shape end to end
issue_probe's merged-PR argument becomes a spec list — `PR` or `PR@<iso>`
— so a probe can state merge order; the bare form keeps every existing call
site literal.

The direct-drive cases cover crew#176's shape (the lower number merged
later), agreeing orders, interleaved issues, the mergedAt tie broken by
highest PR number under both input orders, and the empty answer. The
end-to-end probe is crew#321's: a marker already standing for the
later-merged, lower-numbered PR must suppress the transition, which
selecting by number could never do.

Two static pins keep the request count honest — the sweep issues exactly two
GraphQL queries, with mergedAt selected on the merged-PR node it already
fetched.

Refs #242
2026-08-03 16:27:43 +00:00
cndgrr
f4afaa1346 fix(issueflow): the deliverable PR is the last merged, not the highest numbered
post_merge_pr_for_issue answered "which merged Refs PR is this issue's
deliverable?" with sort -n | tail -n1. Merge order is not number order:
crew#176's two Refs PRs merged #184 at 19:05:16Z and #182 at 19:05:18Z.

MERGED_REF_PR_RECORDS gains mergedAt as a third column — a field on the
merged-PR node set already fetched, so no additional GraphQL request — and
the selection sorts on it, breaking ties by highest PR number so the answer
never depends on input order.

Refs #242
2026-08-03 16:23:15 +00:00
Daniel Marin
46329f5993
Merge pull request #244 from cndgrr/build/231-labels-attention-absolute
docs(labels): the `attention` absolute stops denying the shipped reconciler
2026-08-03 17:17:27 +01:00
cndgrr
bded7d0b54 docs(labels): the attention absolute stops denying the shipped reconciler
LABELS.md asserted 'nothing in actions/ sets, clears, reads, or validates
it' and then documented two exceptions to itself four sentences later. The
sentence is false on two of the four verbs: issueflow-reconcile.sh clears a
carried attention on the derived claimed -> post-merge transition and reads
it to gate the post-merge-assigned diagnostic. Keep the hand-set intent,
drop the absolute (#231).
2026-08-03 15:48:05 +00:00
github-actions[bot]
985ee7e6e4 chore: bump main to 0.5.1-dev — a dev install must not impersonate 0.5.0 2026-08-03 12:40:50 +00:00
Daniel Marin
ee75c2aba3
Merge pull request #235 from dan-claude-bot/build/233-release-0-5-0
release: cut 0.5.0
2026-08-03 13:40:35 +01:00
dan-claude-bot
d48374ff00 drill: probe 5 states suite coverage, not live dogfood
The #226 delta sits behind reconcile_ruling's needs-ruling gate and this
board has no such item — the post-merge sweeps ran the file, never the
delta. The record now says what was observed and why the live claim is
unreachable (round 1, claude).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 12:28:40 +00:00
dan-claude-bot
e5e7555184 release: cut 0.5.0
Five fragments assembled into '## 0.5.0 — 2026-08-03' (#205 #216 #221
#224 #226); VERSION to bare 0.5.0; the three CEREMONY_SELF_REF carriers
stamped "0.5.0" in this one commit; the panel-rows unreleased marker in
docs/CONSUMERS.md cleared to name 0.5.0; drills/0.5.0.md records the
doors-unchanged ruling with the measurements as they are at 0ac3a6f.

Refs #233.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 12:01:05 +00:00
Daniel Marin
0ac3a6ff7e
Merge pull request #234 from dan-claude-bot/build/226-best-shaped-escalation
fix: select the best-shaped escalation, not the earliest
2026-08-03 12:57:42 +01:00
dan-claude-bot
0df40f8d08 fix: select the best-shaped escalation, not the earliest
Closes-adjacent contract in the PR body; authorizing issue #226.

ruling_escalation_row scored every setter in-window row 0-4 by the shared
field matcher; highest wins, equal scores break to the earliest epoch, an
undecodable body scores 0. ruling_shape_decision now grades through the
same matcher, so the selector and the check cannot drift (crew#293).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 10:55:45 +00:00
Daniel Marin
b18c0bc0b2
Merge pull request #227 from dan-claude-bot/build/224-cut-0-5-0-bundle
feat: per-author panels, draft vs the round, the marker sweep, the write-token rule — the 0.5.0 bundle
2026-08-02 16:13:46 +01:00
dan-claude-bot
44b1a3d23c fix: a draft never reads state:needs-human — round 1, claude
The reorder let a draft with a live human request plus a standing block
or comment fall through to round_state, whose human-request precedence
sits above BLOCK/FEEDBACK — 224 of claude's 1500 fixture cases read
needs-human on a PR GitHub cannot merge. decide_state now disqualifies
needs-human unconditionally under DRAFT=true, landing on
state:addressing like the blocker/needs-ruling/blocked clauses. The two
new rows assert the criterion where it can actually fail: human
requested x {CHANGES_REQUESTED, COMMENTED}. Also grok's nit: the
bootstrap row for state:building now matches LABELS.md (draft is
evidence, not the definition), and the CONSUMERS.md reflow nits are in.

Refs #205

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 14:13:25 +00:00
dan-claude-bot
069faf481a fix: refuse a bracket login that is not [A-Za-z0-9-] — round 1, codex
panel[z]]=b parsed at the round-1 head: the case pattern only proves
some ]= occurs, so the stray ] stayed inside the login and the real
author silently fell back to the base panel — the misroute D4 exists to
refuse. The login charset is now enforced with the bracket-specific
diagnostic; codex's probe and an invalid-character row are the new
must-fail fixtures.

Refs #224

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 14:13:25 +00:00
dan-claude-bot
6f245639ca docs: the write-capable token rule — repo-owned by default, established publishers only, SHA-pinned
The ruling from discussion #171 as ruled: canonical text in REVIEWER.md
§What you review against item 2 (beside the verify-at-pin sub-bullet it
is the sibling of), short form in BUILDER.md §Building pointing at it.
CONTRIBUTING.md and docs/CONSUMERS.md checked for contradiction or
duplication: none — their pin prose is the mirror/caller pinning rule —
so both are deliberately untouched.

Refs #216

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 13:32:31 +00:00
dan-claude-bot
ecb0371cad docs: clear five stale unreleased markers; the release PR owns clearing
Each marker now says available-at-tag in the guide's existing L420
phrasing, verified by tag containment in #221; every never-mix-refs
sentence survives verbatim. The convention paragraph gains its missing
half: the release PR that ships machinery clears, in that same PR,
every marker its assembled section makes false.

Refs #221

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 13:32:31 +00:00
dan-claude-bot
7c53267377 fix: a standing non-approving verdict outranks draft in decide_state
round_outranks_draft consults the round before draft short-circuits: a
re-drafted PR carrying CHANGES_REQUESTED, an owed round-reply, or
push-staled approvals reads state:addressing; a live panel request on a
draft surfaces as state:bots-reviewing rather than being absorbed
(the must-not-paper-over combination, decided as: visible). Approvals do
not outrank draft, so a draft never reads needs-human, and a virgin
draft is byte-identical to before. LABELS.md's state:building row makes
draft evidence, not the definition.

Refs #205

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 13:29:15 +00:00
dan-claude-bot
8db6c3ae29 feat: per-author review panels — labels.conf gains panel[<login>]= rows
One resolution point (panel_for_author) feeds set_required_bots; the
author's row when the conf defines one, the base panel= otherwise, minus
the author in either case. Bracket prefixes are matched quoted so the
case patterns cannot glob (D7, panela= tripwire). configured_label_rows
skips the rows so a dispatch bootstrap cannot mint a label named after
one. BUILDER.md/REVIEWER.md carry the one D9 wording; CONSUMERS.md
publishes the row as unreleased with the parse-failure warning.

Refs #224

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 13:29:15 +00:00
github-actions[bot]
80da0a8a1f chore: bump main to 0.4.2-dev — a dev install must not impersonate 0.4.1 2026-08-01 18:15:54 +00:00
Daniel Marin
2050b278f1
Merge pull request #214 from dan-claude-bot/build/212-release-0-4-1
release: cut 0.4.1
2026-08-01 19:15:42 +01:00
dan-claude-bot
74d59e9554 release: cut 0.4.1
Consume the two displacement-fix fragments into the 0.4.1 section, stamp
VERSION and every CEREMONY_SELF_REF carrier, and record the doors-unchanged
drill ruling with the candidate-head evidence table.

Refs #212

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 17:54:47 +00:00
Daniel Marin
c2987fd8d8
Merge pull request #211 from dan-claude-bot/build/209-detach-reconcile-sweep
labels: detach the reconcile sweep from PR-triggered runs (#209)
2026-08-01 18:50:26 +01:00
Daniel Marin
b6d7e26d82
Merge pull request #210 from dan-claude-bot/build/208-checks-state-self-filter
fix: checks_state never grades the label machine's own runs (#208)
2026-08-01 18:44:44 +01:00
dan-claude-bot
be660358f2 docs: spell out crew's four-edit migration; fold in crew#250 field facts (#209)
crew#250 verified two facts the design prose now carries: a queue-
displaced run is not independently rerunnable (gh run rerun / --failed /
--job all refuse), so a victim PR had no manual escape hatch; and the
displacing burst is deterministic — one review_requested event per
panelist per request — so displacement is the steady state of a working
fleet, scaling with panel size, not a traffic spike.

CONSUMERS.md now walks the adoption as one atomic four-edit PR with crew
as the worked example: the pin bump in every ceremony uses: reference,
the new labels-sweep.yml caller, the cron RELOCATED (bold warning: a
copied-not-moved schedule double-fires sweeps into the one shared group
and reads as the bug getting worse after the fix), and actions: write
replacing the labels caller's actions: read.

Refs #209

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 17:25:26 +00:00
dan-claude-bot
45aa806207 labels: detach the reconcile sweep from PR-triggered runs (#209)
The sweep rode the same workflow run as the PR event that woke it, so
every displacement in the shared labels-reconcile queue recorded a
CANCELLED reconcile check on some PR — fake red CI that held review
requests. The reconcile + issueflow jobs move, unchanged, to a new
reusable labels-sweep.yml behind their own caller; labels.yml gains a
trigger job that dispatches the consumer's sweep caller with the plain
GITHUB_TOKEN (workflow_dispatch is a documented no-retrigger exemption)
on every event that used to run reconcile. A displaced sweep now cancels
on the Actions tab, attached to no PR; PR checks show scope + trigger.

Because every trigger-driven wake arrives as workflow_dispatch, the event
name alone no longer separates the operator's manual bootstrap from an
event-woken sweep: the sweep caller's bootstrap dispatch input does — the
trigger passes no, a bare manual dispatch defaults to yes. The sweep
reusable also takes pr_workflow_name, exported as SELF_WORKFLOW for the
#208 reconciler (harmless to earlier ones; zero file overlap with #208).

The trigger is deliberately loud: a pin bumped without the sweep caller,
its bootstrap input, or actions: write on the labels caller goes red at
the trigger job instead of silently never sweeping again — documented in
docs/CONSUMERS.md with the split stubs and the atomic-adoption note.

Refs #209

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 16:20:00 +00:00
dan-claude-bot
8841d9711f fix: checks_state never grades the label machine's own runs (#208)
The shared reconcile concurrency group displaces queued sweeps as
CANCELLED, and the displaced run's successor attaches to a different PR —
so on the victim the newest self entry stayed CANCELLED, scored FAILURE,
and the sweep set blocker:ci-red off its own corpse every cadence
(crew#227). Drop rollup entries whose workflowName matches SELF_WORKFLOW
(defaulting to the ambient GITHUB_WORKFLOW — the caller's name, so no
workflow edit and no hardcoded consumer name) before the newest-per-context
collapse; an empty name filters nothing. A self-only rollup now honestly
scores NONE, and a genuine foreign failure still blocks beside a cancelled
self entry — the must-fail guard against re-opening #136.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 16:12:17 +00:00