Commit graph

65 commits

Author SHA1 Message Date
Andriujose
e7750c0c8f feat: diagnose malformed attention targets 2026-08-03 19:48:15 +00: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
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
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
e133924887 fix: preserve claims linked by open Refs PRs 2026-08-03 17:07:40 +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
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
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
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
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
8e6423a07c docs: define post-merge queue state 2026-07-25 00:09:14 +00:00
codex-bot-andresmgsl
eae000bd62 docs: add actions read to private caller guidance 2026-07-24 22:57:22 +00:00
codex-bot-andresmgsl
fdf544b390 feat: enforce changelog shape in guard and assembler 2026-07-24 16:14:35 +00: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
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
ce24a1a3ba polish: unreadable base version reads 'unreadable' in the release-shape warning
Part of #130.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 12:07:57 +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
53efbfba2a fix: scope job derives additively — labeler v5's PUT clobbered mid-job labels
actions/labeler@v5 writes the whole label set (setLabels PUT) even under
sync-labels: false, so a label applied between its read and its write is
silently removed — ceremony#128 lost `release` that way. v6/v7 write the
same way. Replace the step with actions/labels-scope: same labeler.yml
mapping (changed-files/any-glob-to-any-file subset, refused loudly
otherwise), changed paths via the API, and an additive POST as the only
write.

Part of #130.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 11:51:48 +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
claude-bot-andresmgsl
499783e241 feat: changelog-assembled — the release section must be exactly the fragments it consumed 2026-07-24 09:29:07 +00:00
codex-bot-andresmgsl
edbf30867e feat: warn when core taxonomy labels are missing 2026-07-24 09:22:41 +00:00
codex-bot-andresmgsl
439b5cf8a5 docs: record fragment arming guard 2026-07-24 09:21:24 +00:00
codex-bot-andresmgsl
21492ffeb9 feat: arm changelogs in fragment mode 2026-07-24 09:20:27 +00:00
claude-bot-andresmgsl
fbfe7dd1c1 feat: report why the degraded read degraded
The reconciler's mergeability/checks read kept its correct degrade but
threw the reason away: 2>/dev/null dropped gh's stderr, leaving a
permanent denial and a network hiccup byte-identical in the log (#95 had
to infer a cause from a control case, and the inference did not survive
incubator#48/#49). Capture stderr into a variable via a temp file (D2),
emit it as its own '#N: read failed: …' line beside the byte-identical
counted line (D1), collapsed and bounded by a pure helper (D3/D4), and
lead blind_sweep_warning with the sampled observed reason, demoting the
permissions hint from stated cause to named candidate (D5).

Part of #101 groundwork; tests and changelog follow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 07:34:33 +00:00
codex-bot-andresmgsl
0e489438de feat: enforce publishable changelog entries 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
2c57216a56 fix: surface wholly blind label sweeps 2026-07-23 23:12:30 +00:00
claude-bot-andresmgsl
27aa03464c feat: retire the six GitHub default labels at bootstrap
LABELS.md publishes them as deleted at bootstrap; nothing deleted them —
incubator's first dispatch left `good first issue` standing. One registry
(retired_label_names) beside core_label_rows, dispatch-only, through run,
tolerant of absence and refusal (#93).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 21:09:54 +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
f15cb8c0c9 feat: add attention core label row 2026-07-23 17:27:24 +00:00
codex-bot-andresmgsl
bc3c1bd5cf feat: nudge resolved offsite claims 2026-07-23 13:17:39 +00:00
codex-bot-andresmgsl
6a9b501e05 feat: exempt offsite claims from reclaim clock 2026-07-23 12:48:04 +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
fccdd409ff feat(guards): actions/runner-isolated — the scan, the two-condition rule, the exit codes (#58) 2026-07-23 12:03: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
codex-bot-andresmgsl
d0f1a43064 test: pin injected staleness boundary 2026-07-23 10:31:15 +00:00
codex-bot-andresmgsl
1030634b09 test: inject issue-flow staleness clock 2026-07-23 10:31:15 +00:00