Commit graph

177 commits

Author SHA1 Message Date
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
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
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
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
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
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
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
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
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
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
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
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
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
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
claude-bot-andresmgsl
0a812c4b19 labels: keep edited/reopened on issues; correct 0.3.0 adoption prose (#199 round)
Round fixes on #200.

codex-1 (blocking): the issues narrowing dropped `edited`/`reopened`, but both
carry a queue-state change an event uniquely carries — `edited` a body rewrite
of the `Blocked by #N` declaration the sweep parses
(issueflow-reconcile.sh:179), `reopened` a closed issue re-entering the queue.
Dropping them tripped #199's must-fail. Narrow to
`[opened, closed, edited, reopened]`, dropping only the churn/validation
actions labeled/unlabeled/assigned/unassigned. Trigger tests now pin
edited/reopened present and the four dropped; labels.test.sh exact-list updated.

kimi (blocking): the "supersedes unreleased #144" prose was false — #144's
edited/reopened shipped in 0.3.0. Dissolved: we now keep them. CONSUMERS prose
rewritten to the real version history (0.2.0 #32 / 0.3.0 #144 / #199 narrows),
and the #137 review-request line corrected from "unreleased" to shipped-in-0.3.0.

kimi (non-blocking): reusable labels.yml comment no longer cites */15.

codex-2 (AC1 after-measurement / closing) escalated to triage on #199 — held,
not guessed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 19:04:27 +00:00
claude-bot-andresmgsl
f8ad0b34c7 test: guard the #199 trigger surface; update #144 parity to [opened, closed]
Add test/labels-triggers.test.sh: reconcile stays cancel-in-progress:
false (the must-fail — true kills a sweep mid-board), the cron is hourly
not */15, each churn action is gone from the issues surface, and the PR
labeled handoff wake survives the issues narrowing. Update the #137/#144
parity block in labels.test.sh to the narrowed [opened, closed] contract
and replace its fragile inline pull_request_target scan (the #199 prose
comments name the trigger keys) with the anchored event_types reader.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 18:21:48 +00:00
claude-bot-andresmgsl
0eea112d50 fix: blocked_reference_records unions every Blocked by clause
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>
2026-07-25 13:58:39 +00:00
claude-bot-andresmgsl
0b158a6917 fix: the sentinel's one-line contract is checked on the file, not the $(cat) word
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>
2026-07-25 13:29:02 +00:00
claude-bot-andresmgsl
2fd9cb7ded test: sentinel rows across the shape predicate, the armed guard, and the assembler
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>
2026-07-25 13:12:30 +00:00
Daniel Marin
4debf53872
Merge pull request #181 from claude-bot-andresmgsl/build/180-blocked-excludes-nh
fix: `blocked` excludes `state:needs-human` in decide_state()
2026-07-25 11:31:23 +01:00
claude-bot-andresmgsl
1c9a82aaf0 fix: blocked excludes state:needs-human in decide_state (#180)
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>
2026-07-25 09:08:54 +00:00
codex-bot-andresmgsl
5a3d72f09c fix: make post-merge transitions episode-aware 2026-07-25 04:29:37 +00:00
codex-bot-andresmgsl
24dd818b35 test: cover post-merge queue boundaries 2026-07-25 00:14:23 +00:00
codex-bot-andresmgsl
bb7dd51ba7 feat: transition merged refs work to post-merge 2026-07-25 00:11:42 +00:00
codex-bot-andresmgsl
eae000bd62 docs: add actions read to private caller guidance 2026-07-24 22:57:22 +00:00
claude-bot-andresmgsl
6c746364af feat: changelog_fragment_problem bounds entries at 300 characters (#167)
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>
2026-07-24 18:25:46 +00:00
codex-bot-andresmgsl
eb25b38c14 test: cover grouped anchored release replay 2026-07-24 16:17:02 +00:00
codex-bot-andresmgsl
fdf544b390 feat: enforce changelog shape in guard and assembler 2026-07-24 16:14:35 +00:00
codex-bot-andresmgsl
ef658f52a4 feat: centralize changelog shape validation 2026-07-24 16:12:52 +00:00
claude-bot-andresmgsl
0b77d4b860 fix: the CONSUMERS.md stub's issues: types match the caller's, parity-tested
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>
2026-07-24 13:43:35 +00:00
Daniel Marin
6a16a30bab
Merge pull request #143 from claude-bot-andresmgsl/build/137-review-request-wake
fix: review_requested wakes the labels sweep — blocker:unrequested clears when the ask lands
2026-07-24 14:33:11 +01:00
Daniel Marin
089f2dba29
Merge pull request #140 from claude-bot-andresmgsl/build/139-cancelled-not-verdict
fix: a queue-cancelled duplicate check is not a verdict — checks_state discards it when a real one stands
2026-07-24 14:14:36 +01:00
Daniel Marin
ad5c175d0c
Merge pull request #133 from claude-bot-andresmgsl/build/130-labels-scope-clobber
fix: labels/scope writes additively — a label applied mid-job survives
2026-07-24 14:14:03 +01:00
claude-bot-andresmgsl
c0e796b6c9 fix: review_requested wakes the sweep — blocker:unrequested clears when the ask lands
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>
2026-07-24 12:50:09 +00:00
claude-bot-andresmgsl
d8f54aab04 fix: a queue-cancelled duplicate check is not a verdict
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 (#136 a17e497,
#133 4002924). 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>
2026-07-24 12:34:51 +00:00
claude-bot-andresmgsl
40029242cb fix(round): labeler.yml header tells the truth; labels-scope maps to scope:labels
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>
2026-07-24 12:22:09 +00:00
claude-bot-andresmgsl
a17e49737f test: root-commit fixtures — (none) base, D2 loud-death pin, e2e chain
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>
2026-07-24 12:16:04 +00:00
claude-bot-andresmgsl
d0b857eb10 docs: the additive-scope contract in CONSUMERS/LABELS; changelog fragment
Part of #130.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 11:57:10 +00:00
claude-bot-andresmgsl
16dfdb9a4f feat: reconciler warns on a release-shaped PR missing its release label
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>
2026-07-24 11:54:49 +00:00
claude-bot-andresmgsl
c6e9afee87 docs: README describes the assembled stamp and the four guards
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).
2026-07-24 11:11:23 +00:00
codex-bot-andresmgsl
e37c2dfeef test: correct changelog guard trio interaction 2026-07-24 10:36:30 +00:00
Daniel Marin
fc070ea896
Merge branch 'main' into build/115-changelog-armed-fragment-mode 2026-07-24 11:11:03 +01:00
Daniel Marin
531b8af54c
Merge pull request #124 from claude-bot-andresmgsl/build/116-changelog-assembled
actions/changelog-assembled — the release PR's section must be exactly the fragments it consumed
2026-07-24 11:07:43 +01:00
Daniel Marin
d1b1079feb
Merge pull request #107 from codex-bot-andresmgsl/build/105-missing-core-label-warning
feat: warn when core taxonomy labels are missing
2026-07-24 11:07:14 +01:00
Daniel Marin
be67b48ee9
Merge pull request #108 from claude-bot-andresmgsl/build/104-scope-table-delete
LABELS.md: delete the scope table — the per-repo set lives in labels.conf and the repo's CONTRIBUTING
2026-07-24 10:48:32 +01:00
claude-bot-andresmgsl
c33567686b test: drive changelog-assembled against constructed histories 2026-07-24 09:30:58 +00:00
codex-bot-andresmgsl
22f1a0246f docs: explain core taxonomy bootstrap maintenance 2026-07-24 09:24:40 +00:00
codex-bot-andresmgsl
fffc95633e test: pin missing core label warning contract 2026-07-24 09:22:41 +00:00
codex-bot-andresmgsl
21492ffeb9 feat: arm changelogs in fragment mode 2026-07-24 09:20:27 +00:00
codex-bot-andresmgsl
386ce67bd1 test: specify changelog-armed fragment mode 2026-07-24 09:19:18 +00:00
Daniel Marin
6ec9aa24fa
Merge pull request #120 from claude-bot-andresmgsl/build/114-changelog-assemble
lib/changelog.sh + bin/changelog-assemble — read the fragments, assemble one section, consume them
2026-07-24 10:10:28 +01:00
claude-bot-andresmgsl
9d1eb81037 test: guard LABELS.md against any concrete scope name
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>
2026-07-24 08:32:53 +00:00
claude-bot-andresmgsl
ee0aab1c69 docs: delete LABELS.md's scope table — the per-repo set lives in labels.conf and CONTRIBUTING
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>
2026-07-24 08:32:53 +00:00
claude-bot-andresmgsl
4c0ecf10d8 test: lib fragment trio and the changelog-assemble CLI; changelog entry
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>
2026-07-24 08:28:15 +00:00
claude-bot-andresmgsl
09d2ea764f test: pin the two-line degrade, the bounded reason, and the demoted diagnosis
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>
2026-07-24 07:34:46 +00:00
codex-bot-andresmgsl
ce27861821 test: cover absent Unreleased section 2026-07-24 00:26:48 +00:00
codex-bot-andresmgsl
69410723bd fix: reject entry-less release notes 2026-07-23 23:44:51 +00:00
codex-bot-andresmgsl
0e489438de feat: enforce publishable changelog entries 2026-07-23 23:44:51 +00:00
codex-bot-andresmgsl
73bee80faa feat: define publishable changelog sections 2026-07-23 23:44:51 +00:00
codex-bot-andresmgsl
1d3e98497d Merge remote-tracking branch 'origin/main' into build/95-labels-blind-sweep-warning
# Conflicts:
#	CHANGELOG.md
#	test/labels-reconcile.test.sh
2026-07-23 23:15:08 +00:00
codex-bot-andresmgsl
970d58b2c6 test: prove blind sweeps leave PRs untouched 2026-07-23 23:13:24 +00:00
codex-bot-andresmgsl
2c57216a56 fix: surface wholly blind label sweeps 2026-07-23 23:12:30 +00:00
claude-bot-andresmgsl
d76d3b6136 test: keep the bootstrap probes shellcheck-clean
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>
2026-07-23 21:18:46 +00:00
claude-bot-andresmgsl
019ed5b68e test: pin bootstrap retirement against absence, refusal and set -e
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>
2026-07-23 21:13:07 +00:00
claude-bot-andresmgsl
c39b78959d fix: stand down with exit 0 on non-mint issue arrivals
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>
2026-07-23 20:16:20 +00:00
codex-bot-andresmgsl
bcee821153 test: annotate nested attention assertion 2026-07-23 17:29:23 +00:00
codex-bot-andresmgsl
cb5ae0a068 test: pin attention issueflow inertness 2026-07-23 17:28:02 +00:00
codex-bot-andresmgsl
f15cb8c0c9 feat: add attention core label row 2026-07-23 17:27:24 +00:00
claude-bot-andresmgsl
a4918a5a46 test: cover the shape check and the ladder's rungs
Pure decisions (shape presence, rung boundaries, Default: parse for wording
only), sweep probes for every AC path (malformed-once, conforming silence,
rungs despite activity, cron progression, missed-moment skip, re-flag
episode, unreadable comment list, malformed+rung same pass), and the
existing nudge fixtures updated to conforming escalations with pre-seeded
rung markers so each probe observes one behavior alone.

Part of #73.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 15:49:31 +00:00
codex-bot-andresmgsl
5216369e7e test: cover offsite nudge integration 2026-07-23 13:19:11 +00:00
codex-bot-andresmgsl
bc3c1bd5cf feat: nudge resolved offsite claims 2026-07-23 13:17:39 +00:00
codex-bot-andresmgsl
4b7747a2f5 test: keep offsite fixture shellcheck-clean 2026-07-23 12:48:59 +00:00
codex-bot-andresmgsl
6a9b501e05 feat: exempt offsite claims from reclaim clock 2026-07-23 12:48:04 +00:00
claude-bot-andresmgsl
8203f081ea test: missing-fixture gh stub applies the caller's --jq; pin LC_ALL=C
Real 'gh api --jq .[].created_at' on an empty collection emits no lines;
the stub printed a literal '[]', which under byte-wise collation sorts
after ISO-8601 timestamps and poisoned the PR-surface probe's
last_activity. Route the synthesized empty array through the same jq
projection as a present fixture, and pin the test's collation so the
verdict cannot flip with the runner's ambient locale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 12:25:24 +00:00
claude-bot-andresmgsl
c4079ea37c test(labels): surface-level ruling contracts on both reconcilers
Issue side: invariant-1 composition, the reclaim clock stopping under a
pending ruling (with a flag-free reclaim control), the stale heal, label
churn invisible to the activity clock, the surface-level nudge reset, and
no edit anywhere naming the flag. PR side: the wired nudge riding the
stale sweep's activity computation, one nudge across two sweeps, #51's
stale skip intact.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 12:25:05 +00:00
claude-bot-andresmgsl
a40e42544e feat(labels): wire the ruling pass into both reconcilers + lib test suite
Issue side: the claim-reclaim clock stops under a pending ruling (the
decision still sees an unassigned claim), an already-applied stale heals
off, and reconcile_ruling runs for any flagged issue on any queue state.
PR side: reconcile_ruling rides the (#51) stale section's real-activity
computation. test/ruling.test.sh pins the window boundaries, newest-event
anchoring, per-event marker scoping, the markerless nudge reset, the
unreadable-timeline rule, and that no scenario writes a label.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 12:25:05 +00:00
Daniel Marin
cf69d8ce9e
Merge pull request #60 from claude-bot-andresmgsl/build/58-runner-isolated
feat(guards): actions/runner-isolated — no pull_request-triggered job on a self-hosted runner
2026-07-23 13:24:41 +01:00
claude-bot-andresmgsl
179cd0808b test(guards): runner-isolated fixture matrix — 12 spec rows plus the block-sequence cases (#58) 2026-07-23 12:03:55 +00:00
codex-bot-andresmgsl
3935bf82d9 test: pin cross-repo reconciliation contract 2026-07-23 11:40:55 +00:00
codex-bot-andresmgsl
bc099eb4e2 fix: guard cross-repo issue references 2026-07-23 11:38:14 +00:00
codex-bot-andresmgsl
43092576ef fix: harden issueflow reconciliation edges 2026-07-23 10:49:36 +00:00