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
`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
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>
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>
The sentinel pins the set's shape, outranking the newest-published-section
inference (#182 D2); malformed content is a red diagnosis naming the file.
bin/changelog-assemble skips 'shape' in the stray-file loop so the sentinel
survives the consumption (#182 D5).
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>
A repository's first push to main is a branch-create push whose head is a
root commit: event.before is all-zeros and MERGE_SHA^1 does not exist, so
the fallback died at exit 128 before establishing a fact. The parent count
is now read via rev-list --parents (a fact, not an inferred failure), the
no-base path skips the belt-and-braces fetch and the base git show, and an
unresolvable MERGE_SHA still fails loudly.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lib/changelog.sh gains changelog_fragments (publication order: trailing
issue number descending, filename tie-break), changelog_fragment_problem
(the release-time rules moved onto the PR that writes the fragment, #112
D9), and changelog_assemble (canonical group order per D5, one shape per
repo per D4). bin/changelog-assemble folds changelog.d/ into one release
section, deletes exactly what it consumed, and --check proves the body
without touching the tree.
Part of #112. Closes#114 groundwork; tests follow.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lib/ruling.sh gains the mechanical half of #50 D12-D15: the comment body
rides the fetch as base64 (rows stay line-oriented), ruling_shape_decision
checks the four line-anchored bold-tolerant field labels,
ruling_deadline_decision derives the rung from the labeled epoch,
ruling_default_decision parses the Default: line for wording only, and
reconcile_ruling wires them with bare-stops-here exclusion — every write a
comment, one per marker per episode, both surfaces via the shared lib.
Part of #73.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One implementation for both surfaces: the stale exemption, the bare-flag
mechanical proxy (15-minute back-window against the newest labeled event),
the marker-scoped idempotency, the 7-day nudge with no marker (the comment
is the activity that resets its own window), and the one impure
orchestrator both reconcilers will source.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closes#9. .github/workflows/release.yml (workflow_call) replaces box's,
rig's and cast's release.yml: the merge door (facts -> decide -> notes ->
tag+publish+bump, every decision in a tested lib script) and the tag door
(the manual fallback and backfill, no bump). Plus lib/facts.sh (the merge
door's impure half, contract-tested against fixture repos with a stubbed
gh), the self-ref pin guard (.github/scripts/self-ref-check.sh + CI step +
tests), the release-exercise scratch caller (dry wiring), the end-to-end
script-chain rehearsal, and the caller + artifact-hook contracts in
docs/CONSUMERS.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The six-row decision table from issue #8 as a pure script: the workflow
gathers facts (VER, BASE_VER, RELEASED, LABELED), this script decides.
Notices/refusals ported near-verbatim from box's decide step, de-repo-ified;
design lineage box#96 / rig#47 / cast#111.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The seam that lets box/rig/incubator (a VERSION file) and cast
(package.json + lockfile sync) share every other ceremony component
unchanged. Sourced, pure (no git), fail-loud on every unreadable state.
Carries cast's pkg_version discipline (node's parser, never regex) and
its lockfile-only bump incantation; refuses pre-release arithmetic
(-dev/-rc1) per box's prefix-confusion lore. The npm-backed write test
skips locally without npm but CI sets CEREMONY_REQUIRE_NPM so the skip
is a failure there — the case can never quietly stop running.
Closes#3
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>