Binding to the first marker occurrence dropped every later sentence of a
repeated declaration and let earlier prose hijack the parse — the false
ready promotion on rig#154. Each occurrence now contributes its own
clause, terminated at its own first ./; (unterminated -> end of input),
and the union feeds the unchanged classification and decision table.
Closes#184
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Command substitution strips every trailing newline, so 'grouped\n\n'
reached the case as a clean word and passed — the round's shared blocker
(codex, grok). A line count taken from the file itself now refuses any
physically multi-line sentinel before the word check runs, with the
existing diagnosis naming the file. Red rows: grouped/flat with a
trailing blank line in the unit suite, grouped in the armed suite.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Must-pass and must-fail rows from #182's test plan: the flip shape green
under the sentinel, drift and malformed sentinels red with file named, the
D3 fragment-list assertion, and D5 sentinel survival through consumption.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
During the ceremony#111 freeze, rig#126/#128 carried blocked beside
state:needs-human — the round had finished, but the hold said the merge
must not happen, and rig#126 was merged seven minutes after the
reconciler wrote the green label. decide_state() only joined the two
axes through blockers(), which emits branch facts; the hand-set blocked
label was never consulted.
blocked becomes the second exclusion on state:needs-human, exactly
parallel to needs-ruling: round says needs-human + has_label blocked ->
state:addressing. Deliberately not a blockers() emission — BLOCKERS is
machine-owned and the converge loop would strip the live hold on the
next tick, the same trap #51 names for needs-ruling.
Ruling record: discussion 122, armed default A fired 2026-07-25T09:00Z.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One definition in the fragment predicate; changelog-armed reds the PR
that writes the fragment and the assembler refuses at release, both by
inheritance. Doctrine names the number in BUILDER.md and CHANGELOG.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The stub published [opened, labeled, unlabeled, assigned, unassigned,
closed] while ceremony's own caller listens on eight types — PR #32's
70db91f widened the caller by edited and reopened and the stub never
followed. Both are load-bearing: an edited body rewrites the Blocked-by
declaration the reconcile sweep parses, and a reopened issue re-enters
the queue wearing labels derived at close (#144).
The stub's list is now byte-identical to the caller's, a parity row in
test/labels.test.sh keeps it that way (red on a dropped type, a drift,
or a reorder in one file only), and one adoption note names the tag the
widened list rides in on. The caller does not narrow.
Closes#144
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reconciler's rule was right and blind: the caller never listened on
review_requested/review_request_removed, so the one event that falsifies
(or restores) blocker:unrequested could not clear it, and a quiet repo
wore the red flag until the advisory cron (#137's timeline: 93 seconds,
cleared only by an unrelated PR's push).
- self-labels.yml + the CONSUMERS.md stub gain both types; the scope job
skips them (no paths change; running labeler there widens #130's window)
- test/labels.test.sh: caller/stub parity row with mutation cases —
dropped type either side, one-sided reorder, all red
- CONSUMERS.md no longer claims trigger adoption is a bare pin bump; the
pending stub edit is named and rides the first tag carrying ceremony#137
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
checks_state discards a CANCELLED entry only when its context group holds
at least one non-cancelled sibling — before the sort, so the duplicate the
repo-global reconcile queue evicted (after it had already attached a check
to the head) cannot outvote the success that did its work (#136a17e497,
#1334002924). An all-cancelled context never reported at all and still
classifies FAILURE; {FAILURE older, CANCELLED newest} keeps its red.
The fixture that pinned the opposite rule imagined a cancelled run
replacing a success; it never saw one that replaced nothing. Rewritten
with its reason, plus the recorded a17e497 shape, the all-cancelled
groups, and the cancelled-over-FAILURE case.
Closes#139
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The mapping header still described actions/labeler@v5 + sync-labels —
the exact mechanism this PR removed; it now describes labels-scope's
base-ref read and additive POST, keeping the #128 incident. The
scope:labels row gains actions/labels-scope/** and its test, and two
fixtures derive against the real mapping so the coverage is tested, not
just present. The reconcile job comment names labels-scope instead of
labeler (grok nit 3).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
facts.test.sh: greenfield fixtures for all-zeros and empty event.before,
the bare root establishing labeled=no, and the D2 pin (an unresolvable
MERGE_SHA exits 128 and never reports base_ver=(none) — the test that
|| true would fail). release-chain.test.sh: chain() gains optional
repo/stub args; a -dev root commit is a green NOTICE ceremony=no, a bare
unlabeled root still refuses. Plus changelog.d/134.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bare X.Y.Z at the head where the base says otherwise, no release label,
not a draft: the sweep emits one :⚠️: per pass naming both
versions. A warning only — release is declared intent and the reconciler
never guesses intent (LABELS.md). Version read via the API, both
backends, jq not node; unreadable reads nag nobody. Plus the yq test
contract in CI and fixture tests for the guard matrix.
Part of #130.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stamp 2 is one assembler-produced edit; changelog-armed's rule is stated
mode-first (fragment, then legacy, #112 D7/D8/D9); changelog-assembled
gets its operator section (#116); monotonic records D10; the two rewritten
error strings and the retired re-arm recovery follow (#117).
The regression row grepped only the four current ceremony names, so a
future enumeration under new names stayed green. Widen the pattern to
scope:[a-z0-9] — any concrete label name, in any shape, re-reds the row,
while doctrine's bare `scope:` and wildcard scope:* stay allowed (#104).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The mirror is byte-identical in every governed repo, so the four-row
enumeration was true at home and false in rig, box, cast and incubator —
14 of 16 vendored rows lied. The section keeps its doctrine and points at
the two places true wherever the reader stands; ceremony's own set is now
a pointer sentence in CONTRIBUTING, and a labels.test.sh row (red on main,
4 hits) keeps enumeration from returning.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
test/changelog.test.sh drives changelog_fragments (order, marker, absent
dir), changelog_fragment_problem (every rule, file named each time), and
changelog_assemble (both shapes, canonical order, mixed-shape refusals).
test/changelog-assemble.test.sh drives the CLI against constructed trees:
exact-byte writes, provably read-only --check, every refusal from the
spec, the publisher/assembler round trip, and idempotence.
Closes#114.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The unit block now feeds blind_sweep_warning a sampled reason and asserts
the new lead plus two must-fail guards: the disproven 'grant checks: read
and statuses: read' diagnosis stated as fact goes red, and so does any
drift in the counted line's whole-line shape (exactly the blind PRs match,
no more, no less — a reason line that matched would double-count, a folded
reason would undercount). read_failure_reason is covered pure: D4 wording
for empty stderr, multi-line collapse to one line, 400 chars truncated to
300 plus ellipsis within the 304-byte bound, 300 passing through whole.
blind_main_probe's gh pr view stub now fails with a denial on stderr, the
way real gh fails.
Part of #101.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI's shellcheck gate treats info findings as red. The gh() stubs paired
with bootstrap_labels are reached only through run's "$@", which
shellcheck cannot trace (the older probes stub reconcile_pr, which calls
gh directly), so they carry reasoned SC2317 directives; the LABELS.md
doctrine parse carries SC2016 for its literal backticks. Probes now live
in named functions, matching the house *_probe() pattern.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Registry-vs-LABELS.md identity, happy path, missing label, one-name 403,
DRY_RUN narration, and the executed-subprocess dispatch (#91's lesson: a
sourced probe cannot see set -e). Cron and pull_request_target delete
nothing. Verified red without the guard: the unguarded dispatch dies on
the FIRST absent label, one delete of six attempted.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
reconcile_opened_issue's two early exits were bare returns, which carry
the failed guard's status into the executed script's set -e — every
triage-authored mint killed the labels run before one issue was
reconciled (#91, 4/4 observed). The stand-downs now say return 0; a
genuine failure on the arrival path still aborts loudly.
The suite sources the script and takes the set -u-only branch, so it
was blind to this by construction. The new arrival section executes the
script as a subprocess behind a fixture-serving gh stub (the house
pattern from test/release-chain.test.sh) and covers all three arrival
outcomes plus the preserved loud-failure path; it fails against
bb37c15 with the production signature — exit 1, empty output.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>