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.
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.
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.
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
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
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>
"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
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
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
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
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
`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
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
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
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).
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>
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>
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>
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>
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>