needs-ruling in the sweep — staleness skip, bare-flag detection, the 7-day nudge
#52
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#52
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Part of #50.
Blocked by #18 (the issue-flow sweep this extends does not exist on main until PR #32 merges)and by #51 (the label and its doctrine)— both dependencies have landed: #51 on 2026-07-23 in #54 (2f0d3c6), and #18 the same day in #32 (66f1c08). The sweep this extends is on main, the label row, the PR-side exclusion and the doctrine are on main, and this issue is unblocked — it movedblocked→claimedwhen @claude-bot-andresmgsl picked it up (draft PR #64). Superseded note, kept so the history reads straight: theneeds-rulinglabel did not exist on the live board while this issue was written. It does now — the bootstrap dispatch ran 2026-07-23 11:48Z and #56 is wearing the flag. That never blocked building or testing this issue, which exercises the sweep against fixtures (#65, #66), but it does mean the caveat "do not assumeneeds-rulingis applyable" no longer applies. Updated by triage 2026-07-23.Context
Accepted from discussion #30; the decisions are tabulated in #50 (D1–D10) — read the epic first, and do not reopen a D here.
#51 lands the label, the doctrine, and the PR reconciler's
state:needs-humanexclusion. This issue is the sweep half: the behaviors that need a scheduled pass over the board rather than a per-event state computation — never marking a pending rulingstale, nudging a ruling the human has forgotten, and flagging a flag that carries no escalation contract.It is split out on purpose. Triage told #18's builder to build to the current spec, and said so in the discussion: a ruling arrives as a separately minted issue, never as a moving target under an open PR.
Line references pinned at
4cfa331; the issue-flow sweep's own lines land with #18, so build against whatever #32 merged and cite it in the PR.Spec
Three behaviors, on both surfaces (issues and PRs) unless stated. The decisions are pure functions with the house discipline — facts in, verdict out, API calls at the edges — and one implementation serves both surfaces; two copies of a 7-day rule is how the family got here in the first place.
1. Staleness: never
stale, and the queue label is untouchedneeds-ruling, exactly as it skipsblocked— waiting on a human is legitimately quiet (D10). An already-appliedstalecomes off.needs-rulingis not a queue label (D8). #18's invariant 1 — every open issue isneeds-triage,epic, or exactly one ofready/claimed/blocked— must keep ignoring it entirely:claimed+needs-rulingis a healthy issue, andneeds-rulingalone with no queue label is still a violation of invariant 1 (and gets invariant 1's existing treatment, unchanged).needs-ruling(D9), on either surface. There is no fact GitHub exposes from which "the humans have agreed" can be derived, and guessing it would silently retire a live disagreement.2. The bare-flag check
D4: setting the flag requires the escalation contract — question, options, recommendation. The machine cannot judge prose and must not try (the family rule: automation never guesses). So the check is a mechanical proxy, decided here:
labeledevent on the item's timeline (repos/{repo}/issues/{n}/timeline,event == "labeled",label.name == "needs-ruling"— the most recent such event, so a re-flag after a removal is judged on its own escalation, not on the last one's).<!-- ceremony:needs-ruling-bare -->, and the sweep skips posting when a marked comment exists that is newer than the currentlabeledevent. Scoping to the current event is what lets a genuine re-flag be re-checked while a 15-minute cron never repeats itself.blockers()L246-L251).3. The 7-day nudge
D10: a ruling nobody has touched for 7 days gets a comment-only ping. Never a label, never
stale.STALE_AFTER(L46):RULING_NUDGE_AFTER=$((7 * 24 * 3600)). One constant, both surfaces.Tasks
RULING_NUDGE_AFTERconstant; one shared pure decision function per behavior, used by both surfaces.needs-ruling.[fixture #50]prefix, the same authorization and rules #18 wrote for its own run (fixtures are test apparatus, not work orders; label writes on your own fixtures only; all fixtures closed before handoff; each fixture → correction pair linked in the PR). Never construct a violation on a real queue issue, even transiently.needs-rulingparagraph's "the sweep skips it" sentence gains the nudge and the bare-flag check, now that they exist.## Unreleased, inserted above the heading below it.Acceptance criteria
needs-rulingand quiet past the 48h window is never markedstale, and an existingstaleis removed — pinned by a contract test.claimed+needs-rulingis not an invariant-1 violation;needs-rulingwith no queue label still is — both pinned by contract tests.labeledevent and for one posted after it; bare for a comment posted 16 minutes before it, for a comment by a different actor, and for no comment at all — one case each.needs-rulingis judged against the newestlabeledevent: an escalation accompanying the first flag does not satisfy the second — pinned by a case.labeledevent, proven by running the sweep twice over the same fixture and asserting one comment.needs-rulingitem quiet for 8 days is nudged; the same item quiet for 6 days is not; and a second sweep immediately after a nudge does not nudge again (the comment reset the window) — three cases.needs-ruling; asserted by a sweep-probe test over a fixture carrying it plus a grep-level check that the label appears in no mutation call.[fixture #50]scratch issue, links in the PR; all fixtures closed before handoff.test/run.sh, shellcheck and actionlint green.Test plan
Must pass — the acceptance cases above, as fixture tests in the sweep's test file, in the existing house style (source the script, set globals,
expect).Must fail, and shown failing in the PR body (a test that passes against the unpatched tree is testing nothing):
needs-ruling+ 8-days-quiet case must markstaleand go red.labeledevent instead of the newest → the re-flag case must go red.Dependencies
Part of #50. Blocked by #18. (#51 landed 2026-07-23 in #54 —
2f0d3c6.)Labels
enhancement,scope:labels,scope:docs,blocked.Every issue named by
Blocked byis closed. The sweep is moving this issue toready.🔨 Claiming — starting now. Branch
build/52-needs-ruling-sweep; draft PR follows with the worklog.