feat(labels): needs-ruling — the label, the doctrine, and the reconciler's exclusion rule (#51)
#54
No reviewers
Labels
No labels
attention
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-ruling
needs-triage
offsite
post-merge
ready
release
scope:docs
scope:guards
scope:labels
scope:release-flow
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/ceremony#54
Loading…
Reference in a new issue
No description provided.
Delete branch "build/51-needs-ruling"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #51. Part of #50 — every decision referenced below (D1–D10) is settled in the epic; nothing here reopens one.
Adds the
needs-rulinglabel: the bootstrap row, thedecide_stateexclusion rule (a PR carrying it cannot bestate:needs-human; it lands onstate:addressing), the staleness skip, the doctrine in all four role files, and the contract tests. The label is hand-set intent — deliberately not inBLOCKERS, which the converge loop strips; a test pins that it survives a full sweep.Worklog
core_label_rows(): theneeds-ruling|D4C5F9row (cross-cutting group, afterblocked)LABELS=""global init +decide_stategains theneeds-rulingdisqualifier →state:addressingneeds-rulingjoinsblockedstate:addressingamendment, D5–D9 paragraph## Unreleasedtest/run.sh+ shellcheck + actionlint greenMust-fail evidence (test-plan cases 8–10, actually run)
decide_statedisqualifier deleted (has_label needs-ruling→false): 3 red —a pending ruling excludes needs-human — want state:addressing, got state:needs-human, plus both full-sweep probe assertions. Suite:80 passed, 3 failed.needs-rulingadded toBLOCKERS: 1 red, exactly on the removal assertion —...and the sweep never touches needs-ruling itself — want no, got yes(the converge loop emitsstate -> state:addressing (cleared needs-ruling)). Suite:82 passed, 1 failed. This is the trap §2 of the issue names.LABELS=""init deleted: the issue originally predicted the suite goes red underset -u; measured otherwise (bash 5.2.37): 83/83 stay green with 9LABELS: unbound variablestderr complaints — the unset expansion inside the herestring is a redirection error (rc 127, shell survives), sohas_labelfails open ("label absent"), which would silently wave a live escalation through to needs-human. Worse than red. Added a test that re-sources the script in a cleanbash -uand asserts the read is silent: with it, deleting the guard is 1 red (a fresh source reads LABELS cleanly — want , got …unbound variable). Issue amended by triage to carry the real mechanism; the shipped guard + test satisfy the rewritten case 10 as-is.All perturbations reverted; final suite:
labels-reconcile tests: 84 passed, 0 failed;test/run.sh: 12 files passed; shellcheck + actionlint clean.Acceptance criteria (from #51, as amended 2026-07-23)
needs-rulingexists incore_label_rows()with colorD4C5F9and a pipe-free description — the "a dispatch run has created it on heavy-duty/ceremony" half was struck by triage's amendment (post-merge maintainer step, tracked on #50).state:needs-humanyieldsstate:addressingwhile it carriesneeds-ruling— pinned by a contract test.needs-rulingfrom the same fixture returns it tostate:needs-human— pinned by a contract test, so the rule is an exclusion and not a one-way latch.needs-rulingappears in neitherBLOCKERSnorblockers()' output, and areconcile_prprobe over a PR carrying it emits no removal of it — pinned by a test that asserts the label survives a full sweep.needs-rulingand quiet for longer thanSTALE_AFTERis not markedstale, and an existingstaleis removed — pinned by a test.merge-nextis cleared on a PR that gainsneeds-ruling— pinned by a test.DRAFT=true+needs-rulingis stillstate:building— pinned by a test.state:addressing's row mentions the pending ruling.## Unreleasedline, added above the## 0.1.0heading.test/run.sh, shellcheck and actionlint are green.⟲ resuming from
ded7f9ac04Worklog update (no commit — tree is untouched, here is why): resumed at head
ded7f9a, re-ran the full suite in the worktree —test/run.sh: 12 files passed, 0 failed, includinglabels-reconcile tests: 84 passed, 0 failed. Every code, docs and test item in the worklog is done and pushed; the single unchecked item is the label-bootstrap dispatch, which is unreachable pre-merge by anyone (details in the worklog) and is awaiting triage's amendment ruling on #51 — requested there at 00:51Z, addressed to @dan-claude-bot. I am watching #51 and will act the moment the ruling lands: with recommendation (a) I check the AC's reachable half, note the dispatch as the post-merge step, mark ready-for-review and request the panel; any other ruling I implement as written.Out of draft at head
ded7f9a. The one open item — the pre-merge label bootstrap dispatch — was a spec gap; triage ruled and amended #51 (ruling): the dispatch AC is struck, the maintainerworkflow_dispatchis a post-merge step tracked on epic #50, and the PR body now carries the note thatneeds-rulingdoes not yet exist on this repo (inert-but-silent window — the reconciler only reads the label, D9).Everything else was already done and green on this head: 84/84 labels-reconcile tests (must-fail cases 8–10 run and recorded in the body), 12/12 test files, shellcheck + actionlint clean, CI green, mergeable. Panel requested: @codex-bot-andresmgsl @grok-bot-andresmgsl @kimi-bot-andresmgsl.
🔎 reviewing head
ded7f9ac04🔎 reviewing head
ded7f9ac04Verdict: approve
Reviewed head
ded7f9ac04f106c5a3216ea7475d9bc9d9ed4066against #51 (as amended 2026-07-23) and the load-bearing constraints it cites.Acceptance criteria
needs-rulingincore_label_rows()atD4C5F9, pipe-free descblocked, beforerelease; desc 87 chars, no|; bootstrap half correctly struck per triage rulingneeds-human+ flag →state:addressingdecide_statedisqualifier + contract testBLOCKERS/blockers(), survives full sweepBLOCKERSarray untouched; probe asserts no removal of the flagstalecomes offmerge-nextretractsdesired != needs-human; pinnedDRAFT+ flag stillstate:buildingstate:addressingrow names pending ruling; LABELS carries D5–D9## Unreleasedline above## 0.1.0test/run.sh12/12,labels-reconcile84/0, shellcheck clean; CI check rollup SUCCESSVerify over opine
./test/run.shat this head: 12 files passed, 0 failed.shellcheckonlabels-reconcile.sh: clean.state:addressing(blockers path still first).set -u, not a red suite).Spec / trap fidelity
BLOCKERSso the converge loop strips a live escalation — is both avoided in code and pinned by the full-sweep probe.LABELS=""init is load-bearing for the fail-open herestring behavior; the clean-shell guard test pins it.No blocking findings. Non-blocking: none worth the builder’s round.
Closes the review of #51’s implementation as written.
Triage confirming, for the panel mid-round: the acceptance criteria in this PR's body are the current ones. Two items moved on #51 after the issue was minted, both my gaps, both amended in place (ruling):
needs-rulingon this repo —labels.yml's reconcile job checks out the default branch by design (that checkout is whypull_request_targetis safe here), so any dispatch, on any ref, bootstraps main'score_label_rows(). Independently, neither builder nor triage identities hold dispatch or label-create rights. Reviewing this PR against "the label exists on heavy-duty/ceremony" would be reviewing against a struck criterion. The maintainer dispatch is a post-merge step, tracked on #50's task list — not in this thread, where post-merge steps go to die.has_labelread turns the suite red underset -u; it does not — the unset expansion is a herestring redirection error, so the shell survives andhas_labelfails open. TheLABELS=""init is therefore load-bearing, not hygiene, and the right pin is the clean-bash -usilence assertion this PR ships, not a suite colour that never changes.Nothing else in #51 moved. No triage-side blocker on #54; the remaining gate is the panel.
Approve — head
ded7f9a, reviewed against every acceptance criterion in #51.Verified in a detached worktree at the head, not just by reading:
bash test/labels-reconcile.test.sh: 84 passed, 0 failed (all seven must-pass cases, including the control, the exclusion, the not-a-latch case, and the guard pin).decide_statedisqualifier → 3 red, incl.a pending ruling excludes needs-human(gotstate:needs-human).needs-rulingtoBLOCKERS→...and the sweep never touches needs-ruling itselfgoes red. The trap §2 names is pinned.LABELS=""init → the guard test goes red withLABELS: unbound variableon stderr while the rest of the suite stays green — confirming the amended fail-open analysis (a suite run alone is not evidence; the direct pin is).test/run.sh: identical result onmainand on this head (8 files pass, 4 fail — every failure is apackage-jsoncase requiringnode, which this machine lacks; none touched by this diff).shellcheck0.10.0 clean on both touched shell files;actionlint1.7.7 clean.needs-ruling|D4C5F9|…sits afterblocked, beforerelease; description is pipe-free and 89 chars (< GitHub's 100 cap).decide_stategains the disqualifier with the comment carrying both whys (honeststate:addressinglanding; deliberately not inBLOCKERS).DRAFT=truestill outranks. Staleness skip joinsblockedwith the D10 comment.merge-nextretraction falls out of the existing rule and is pinned.state:addressingamendment + the D5–D9 paragraph; TRIAGE/BUILDER/REVIEWER each name the label, its clearing rule, and — in REVIEWER — that reviewers never set it, with the one-accountable-flag-setter rationale. CHANGELOG line is under## Unreleased, above## 0.1.0.Nothing blocking. The work matches the spec line for line, and the tests actually fail when the thing they guard is removed.
Verdict: I agree with everything and have no additional feedback.
Reviewed head
ded7f9ac04against #51 as amended. The hand-set needs-ruling flag is excluded from state:needs-human without entering BLOCKERS, survives reconciliation, retracts merge-next, and suppresses staleness; the doctrine and changelog match the contract.Fresh verification on this exact head: ./test/run.sh passed all 12 test files, including labels-reconcile 84 passed / 0 failed; shellcheck passed. Local actionlint is unavailable, and the current GitHub self-guards and test checks are successful.
Round summary — handing off
Round 1 (the only round): out of draft at head
ded7f9a; the one spec gap found while building — whether the pre-merge label bootstrap dispatch belongs in scope — was escalated on #51, ruled and amended by triage before review started, so the panel reviewed a settled spec.All three panel verdicts approve the current head
ded7f9a, each with independent verification, none with blocking or non-blocking asks:labels-reconcile84 passed / 0 failed, and re-derived the must-fail evidence independently rather than trusting the worklog.needs-rulingis excluded fromstate:needs-humanwithout enteringBLOCKERS, survives reconciliation, retracts merge-next, suppresses staleness; doctrine + changelog match the contract.CI is green on the head (12 test files, including the full
labels-reconcilematrix), the branch is mergeable, and noblocker:*stands. Requesting @danmt and settingstate:needs-human— the PR is yours.