forked from heavy-duty/ceremony
docs: needs-ruling doctrine — LABELS row + D5–D9 rationale, escalation mechanics in TRIAGE/BUILDER/REVIEWER, changelog (#51)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
6db45587ed
commit
ded7f9ac04
5 changed files with 46 additions and 3 deletions
|
|
@ -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.
|
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
|
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
|
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
|
## Handoff
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ so entries say what changed, cite the issue, and stop.
|
||||||
|
|
||||||
## Unreleased
|
## 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
|
## 0.1.0 — 2026-07-22
|
||||||
|
|
||||||
- `lib/version.sh` — one version abstraction, `file` and `package-json` backends (#3).
|
- `lib/version.sh` — one version abstraction, `file` and `package-json` backends (#3).
|
||||||
|
|
|
||||||
24
LABELS.md
24
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:building` | `#FBCA04` | the builder — PR is a draft |
|
||||||
| `state:bots-reviewing` | `#1D76DB` | the reviewer panel to finish the round (a request is live) |
|
| `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 |
|
| `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
|
`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 |
|
| `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) |
|
| `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 |
|
| `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 |
|
| `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)
|
## Scope — which surface? (PRs and issues, any number)
|
||||||
|
|
||||||
All scopes share one calm color, `#C5DEF5` — scopes locate, states alert. The
|
All scopes share one calm color, `#C5DEF5` — scopes locate, states alert. The
|
||||||
|
|
|
||||||
|
|
@ -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
|
with evidence until one side concedes or the builder escalates to the
|
||||||
maintainer for a ruling. Two reviewers pulling a builder in opposite
|
maintainer for a ruling. Two reviewers pulling a builder in opposite
|
||||||
directions without resolution is a panel failure, not a builder failure.
|
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)).
|
||||||
|
|
|
||||||
|
|
@ -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,
|
3. **Escalate.** The blocker is a *decision* only a human owns — scope,
|
||||||
money, product direction, breaking a public contract. Say precisely what
|
money, product direction, breaking a public contract. Say precisely what
|
||||||
the decision is, list the options with your recommendation, and name the
|
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
|
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
|
where it belongs if anywhere, close. A refusal with reasons is a good
|
||||||
outcome; a zombie discussion is not.
|
outcome; a zombie discussion is not.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue