diff --git a/BUILDER.md b/BUILDER.md index a1680f1..6667fb2 100644 --- a/BUILDER.md +++ b/BUILDER.md @@ -58,7 +58,13 @@ CONTRIBUTING; everything below is the shared flow.) 3. Never dismiss a review, never merge, never mark your own work as passed. A blocking point you disagree with is answered with evidence or escalated in the PR — a maintainer can be asked for a ruling; silence and - force-forward are not options. + force-forward are not options. The ruling ask has mechanics: set + `needs-ruling` on the PR, with one comment carrying the question, the + options, and your recommendation — a panel deadlock is consolidated into + that one question, never forwarded as three phrasings of it. The label + stays until agreement is *reached*, not until the maintainer replies; + you record the ruling, remove the label, and return the PR to its flow + ([LABELS.md](LABELS.md)). ## Handoff diff --git a/CHANGELOG.md b/CHANGELOG.md index 02c415e..34eed9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ so entries say what changed, cite the issue, and stop. ## Unreleased +- `needs-ruling` — the cross-cutting flag for a pending human decision, excluded from `state:needs-human` and from the staleness sweep (#51). + ## 0.1.0 — 2026-07-22 - `lib/version.sh` — one version abstraction, `file` and `package-json` backends (#3). diff --git a/LABELS.md b/LABELS.md index 4be1fb3..a995657 100644 --- a/LABELS.md +++ b/LABELS.md @@ -18,7 +18,7 @@ and the reconciler recomputes it from GitHub's own facts. |---|---|---| | `state:building` | `#FBCA04` | the builder — PR is a draft | | `state:bots-reviewing` | `#1D76DB` | the reviewer panel to finish the round (a request is live) | -| `state:addressing` | `#D93F0B` | the builder — round complete without full approval, or nobody was asked, or a blocker is up | +| `state:addressing` | `#D93F0B` | the builder — round complete without full approval, or nobody was asked, or a blocker is up, or a ruling is pending | | `state:needs-human` | `#8250DF` | the human — **this PR could be merged right now**: zero blockers, whole panel approved the current head | `bots-reviewing` vs `addressing` is deliberate: staleness in the first means @@ -66,9 +66,31 @@ staleness sweep will reclaim (issue #18); until that machinery exists, |---|---|---| | `stale` | `#B60205` | no activity for 48h — sweep-managed, never hand-applied | | `blocked` | `#6A737D` | (see above — same label serves PRs waiting on another PR/issue; legitimately quiet, the staleness sweep skips it) | +| `needs-ruling` | `#D4C5F9` | a human decision is required; the question, options and a recommendation are in the flagging comment. Set by triage or the builder; a state, not a signal — it clears on agreement, not on a reply | | `release` | `#0E8A16` | release flow, versioning, packaging work — and the ceremony PR itself | | `merge-next` | `#0E8A16` | head of the merge queue — merge this one next. Queue order is *intent*: never set by the reconciler, only cleared by it | +`needs-ruling` marks where the human's turn is when the pending thing is a +*decision*, not a merge (#50 settled it, D1–D10). It is not +`state:needs-human`: that label means exactly "this PR could be merged right +now", and the retired `state:needs-rebase` is the family's proof that a +label meaning two things lies about both. It is not a `blocker:*` either: +every blocker names work the *builder* owes, a ruling is owed by the human — +and the flag must live on issues too, where blockers do not exist. On issues +it coexists with the queue labels (the one-of-three invariant above ignores +it); its color is the light shade of `state:needs-human`'s, so the human +axis reads as one family. It is a state, not a signal: set only with the +escalation contract (the question, the options, a recommendation — a bare +flag is noise), it stays up until agreement is *reached* — a human reply +alone does not clear it — and its setter closes it out: records the ruling +as a decision in one comment, removes the label, and returns the item to +its flow in that same comment, never as a side effect. If the human +disagrees that agreement was reached, the label goes back on. The machine +reads it and never writes it: the reconciler refuses `state:needs-human` +while it stands (the PR falls to `state:addressing` — the ball on the PR is +the builder's, who carries the ruling in), and the staleness sweep skips +it, because waiting on a human is legitimately quiet. + ## Scope — which surface? (PRs and issues, any number) All scopes share one calm color, `#C5DEF5` — scopes locate, states alert. The diff --git a/REVIEWER.md b/REVIEWER.md index 17ac893..3a28e0b 100644 --- a/REVIEWER.md +++ b/REVIEWER.md @@ -68,3 +68,9 @@ saw Y" outranks one that says "this looks like it might". with evidence until one side concedes or the builder escalates to the maintainer for a ruling. Two reviewers pulling a builder in opposite directions without resolution is a panel failure, not a builder failure. + The escalation is flagged `needs-ruling` — by the **builder**, never by + you: one accountable flag-setter per PR is what keeps the escalation + contract enforceable and hands the human one consolidated question + instead of three phrasings of it. Your job is to state the disagreement + precisely enough that the builder can write that escalation + ([LABELS.md](LABELS.md)). diff --git a/TRIAGE.md b/TRIAGE.md index 3dcf655..a0a7df0 100644 --- a/TRIAGE.md +++ b/TRIAGE.md @@ -35,7 +35,14 @@ is the failure this whole flow exists to prevent. 3. **Escalate.** The blocker is a *decision* only a human owns — scope, money, product direction, breaking a public contract. Say precisely what the decision is, list the options with your recommendation, and name the - decider. The discussion is where humans decide; wait there. + decider. The discussion is where humans decide; wait there. When the + decision blocks something already on the board — an existing issue, or + minted work a discussion's ruling gates — set `needs-ruling` on it too, + so the board shows where the human's turn is; the issue keeps its queue + label. You set the flag, so you close it out ([LABELS.md](LABELS.md)): + judge when agreement is reached, record the ruling as a decision in one + comment, remove the label, and return the issue to its flow in that same + comment. 4. **Decline.** Real idea, wrong repo or wrong time. Say why plainly, link where it belongs if anywhere, close. A refusal with reasons is a good outcome; a zombie discussion is not.