Merge pull request #75 from codex-bot-andresmgsl/build/72-escalation-contract
docs: define the ruling escalation contract
This commit is contained in:
commit
f6797d01bc
5 changed files with 139 additions and 48 deletions
66
BUILDER.md
66
BUILDER.md
|
|
@ -76,14 +76,64 @@ CONTRIBUTING; everything below is the shared flow.)
|
|||
reviewer doubts behavior, add the test that settles it.
|
||||
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. 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)).
|
||||
in the PR — silence and force-forward are not options. A panel deadlock
|
||||
is one kind of human-owned decision; use the ruling ask below
|
||||
([#50 D11](https://github.com/heavy-duty/ceremony/issues/50)).
|
||||
|
||||
## The ruling ask
|
||||
|
||||
Set `needs-ruling` whenever a decision belongs to a human: org policy,
|
||||
published artifacts, secrets, prod, or any choice whose cost lands outside
|
||||
the PR. A panel deadlock is one instance, not the definition. The builder is
|
||||
the accountable flag-setter on a PR and consolidates the decision into one
|
||||
comment rather than forwarding several reviewers' phrasings
|
||||
([#50 D11](https://github.com/heavy-duty/ceremony/issues/50)).
|
||||
|
||||
Keep at most these five lines above the fold and put all other analysis
|
||||
inside the fold. The field labels are fixed because the ruling machinery
|
||||
checks for them ([#50 D12](https://github.com/heavy-duty/ceremony/issues/50)):
|
||||
|
||||
```text
|
||||
🧭 needs-ruling — <the decision, one line>
|
||||
Options: A — <one clause> B — <one clause>
|
||||
Recommend: A, because <one clause>.
|
||||
Blocked: <what stops; what continues meanwhile>
|
||||
Default: <A at 2026-07-23T21:00Z if no ruling> | none — hard block
|
||||
<details><summary>Analysis</summary>…everything else…</details>
|
||||
```
|
||||
|
||||
The options must be exhaustive and mutually exclusive; more than three means
|
||||
the question is not ready. `Recommend:` is mandatory — omitting it hands the
|
||||
whole problem to the human. `Blocked:` names both what stops and what
|
||||
continues. Write a timed `Default:` only when you are affirmatively confident
|
||||
the decision is reversible inside the PR before merge. Unsure is not a tie:
|
||||
it is a hard block. Published artifacts, secrets, prod, and org policy are
|
||||
hard blocks by construction ([#50 D12–D13](https://github.com/heavy-duty/ceremony/issues/50)).
|
||||
|
||||
The ladder is anchored to the current episode's `needs-ruling` **`labeled`
|
||||
event**, not its `Default:` deadline or the last activity
|
||||
([#50 D13–D14](https://github.com/heavy-duty/ceremony/issues/50)):
|
||||
|
||||
- **0–12h:** proceed when a still-clear, reversible default expires, and say
|
||||
out loud that you did. A hard block waits.
|
||||
- **at 12h:** do not fire a stale default. Re-read it against what has landed
|
||||
and ask whether it still holds and whether reasonable doubt remains. If
|
||||
doubt has appeared, make it a hard block.
|
||||
- **at 24h:** proceed regardless, **as a PR**. Pick an option and state in the
|
||||
PR body which way you went and what doubt remains. Nothing merges by this;
|
||||
the human still gates the merge.
|
||||
- **past 24h:** hand the choice to triage. Triage picks the option, records it
|
||||
as a decision, and remains accountable; the operator can overturn it at
|
||||
merge.
|
||||
|
||||
A re-flag starts a fresh ladder. The ladder applies whatever `Default:` says,
|
||||
including a hard block, and an active back-and-forth still climbs it. This is
|
||||
different from the 7-day nudge, which resets on real activity. The machine
|
||||
observes both clocks but never sets, clears, or decides `needs-ruling`.
|
||||
|
||||
The label stays until agreement is *reached*, not until the maintainer
|
||||
replies. The setter records the ruling, removes the label, and returns the
|
||||
item to its flow in the same comment ([LABELS.md](LABELS.md)).
|
||||
|
||||
## Handoff
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ so entries say what changed, cite the issue, and stop.
|
|||
- The sweep's `needs-ruling` invariants, one implementation for both surfaces: the issue-side staleness exemption, the bare-flag check (comment-only, the label is never removed), and the 7-day nudge to the decider (#52).
|
||||
- `offsite` — protect claimed issues whose PR lives in another repository from the claim-reclaim clock (#68).
|
||||
- `issueflow-reconcile` — nudge once when an `offsite` flag outlives every visible cross-referenced PR (#69).
|
||||
- Ruling doctrine — define every human-owned trigger, the fixed escalation shape, and the 0–24h builder-to-triage ladder (#72).
|
||||
|
||||
## 0.1.0 — 2026-07-22
|
||||
|
||||
|
|
|
|||
66
LABELS.md
66
LABELS.md
|
|
@ -67,12 +67,15 @@ comments, unassigns the stale owner, and restores `ready`.
|
|||
| `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) |
|
||||
| `offsite` | `#CFD3D7` | issue deliverable is a PR in another repository; set by the builder with the draft link and cleared by the builder at handoff |
|
||||
| `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 |
|
||||
| `needs-ruling` | `#D4C5F9` | a human-owned decision is required; use BUILDER.md's ruling template and ladder. 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
|
||||
*decision*, not a merge ([#50 D1–D14](https://github.com/heavy-duty/ceremony/issues/50)).
|
||||
It applies to any human-owned decision — org policy, published artifacts,
|
||||
secrets, prod, or any choice whose cost lands outside the work. A panel
|
||||
deadlock is one instance, not the definition (D11). 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:
|
||||
|
|
@ -81,24 +84,49 @@ 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`
|
||||
[canonical escalation contract](BUILDER.md#the-ruling-ask) (D12). A bare
|
||||
flag is noise. The comment carries exhaustive, mutually exclusive options
|
||||
(at most three), a mandatory recommendation, what stops and what continues,
|
||||
and either a default affirmatively known to be reversible inside the PR or
|
||||
`none — hard block`. Unsure is a block; published artifacts, secrets, prod,
|
||||
and org policy are hard blocks by construction (D13).
|
||||
|
||||
The ruling ladder runs from the current episode's `needs-ruling` **`labeled`
|
||||
event** (D13–D14):
|
||||
|
||||
- **0–12h:** a clear, reversible decision may proceed when its stated default
|
||||
expires, saying out loud that it did; anything with reasonable doubt waits
|
||||
as a hard block.
|
||||
- **at 12h:** the setter re-reads the default against what has landed and asks
|
||||
whether it still holds and whether doubt remains. A stale default does not
|
||||
fire; new doubt makes it a hard block.
|
||||
- **at 24h:** the builder proceeds regardless, **as a PR**, stating the option
|
||||
chosen and the doubt that remains. Nothing merges by this; the human still
|
||||
gates the merge.
|
||||
- **past 24h:** triage picks the option, records it as a decision, and remains
|
||||
accountable. The operator may overturn it at merge.
|
||||
|
||||
A re-flag starts a new ladder. The rungs apply whatever `Default:` says,
|
||||
including a hard block. Active discussion still climbs the ladder; by
|
||||
contrast, the separate 7-day nudge resets on real activity. The machine
|
||||
observes the rungs but never sets, clears, or decides `needs-ruling`.
|
||||
|
||||
The flag 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 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. Quiet, but not
|
||||
unwatched (#52, both surfaces): a flag set with no escalation comment from
|
||||
its setter is called out by the sweep — comment-only, scoped to the labeled
|
||||
event, the label never removed — and a ruling with no real activity for 7
|
||||
days draws a comment-only nudge addressed to the decider, linking the
|
||||
escalation. The nudge carries no marker on purpose: the comment is itself
|
||||
activity, so it resets its own window and never repeats within a quiet
|
||||
week. Label churn is not activity — the clock reads comments, reviews and
|
||||
commits, or the sweep would reset itself.
|
||||
the builder's, who carries the ruling in), and the staleness sweep skips it,
|
||||
because waiting on a human is legitimately quiet. Quiet, but not unwatched
|
||||
(#52, both surfaces): a flag set with no escalation comment from its setter
|
||||
is called out by the sweep — comment-only, scoped to the labeled event, the
|
||||
label never removed — and a ruling with no real activity for 7 days draws a
|
||||
comment-only nudge addressed to the decider, linking the escalation. The
|
||||
nudge carries no marker on purpose: the comment is itself activity, so it
|
||||
resets its own window and never repeats within a quiet week. Label churn is
|
||||
not activity — the clock reads comments, reviews and commits, or the sweep
|
||||
would reset itself.
|
||||
|
||||
`offsite` is issue-only and records that a claimed issue's deliverable lives
|
||||
in another repository, where a closing reference cannot make a local open PR
|
||||
|
|
|
|||
23
REVIEWER.md
23
REVIEWER.md
|
|
@ -81,13 +81,16 @@ saw Y" outranks one that says "this looks like it might".
|
|||
- Convergence = every panel verdict approves the current head, no
|
||||
`blocker:*` standing. Then the builder hands off (`state:needs-human`) and
|
||||
the panel's job is done.
|
||||
- If a round exposes a disagreement **within the panel**, argue it in the PR
|
||||
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)).
|
||||
- Flag an unowned decision when it belongs to a human: org policy, published
|
||||
artifacts, secrets, prod, or any choice whose cost lands outside the PR. A
|
||||
disagreement within the panel is one instance, not the definition
|
||||
([#50 D11](https://github.com/heavy-duty/ceremony/issues/50)). Argue a
|
||||
panel disagreement in the PR with evidence until one side concedes or the
|
||||
builder escalates; two reviewers pulling a builder in opposite directions
|
||||
without resolution is a panel failure, not a builder failure.
|
||||
`needs-ruling` is set by the **builder**, never by you: one accountable
|
||||
flag-setter per PR hands the human one consolidated question. State the
|
||||
unowned decision precisely enough for the builder to write
|
||||
[the canonical ruling ask](BUILDER.md#the-ruling-ask), including what
|
||||
stops and what continues ([#50 D12](https://github.com/heavy-duty/ceremony/issues/50);
|
||||
[LABELS.md](LABELS.md)).
|
||||
|
|
|
|||
31
TRIAGE.md
31
TRIAGE.md
|
|
@ -32,17 +32,26 @@ is the failure this whole flow exists to prevent.
|
|||
answers would let you write the issue — then stop and wait. Do not mint an
|
||||
issue that carries the ambiguity forward; that just moves your job onto
|
||||
the builder.
|
||||
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. 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.
|
||||
3. **Escalate.** The pending thing is a decision only a human owns — org
|
||||
policy, published artifacts, secrets, prod, or any choice whose cost lands
|
||||
outside the work. A panel deadlock is one instance, not the definition
|
||||
([#50 D11](https://github.com/heavy-duty/ceremony/issues/50)). Say
|
||||
precisely what the decision is, name the decider, and use
|
||||
[BUILDER.md's canonical ruling template](BUILDER.md#the-ruling-ask),
|
||||
including its options, recommendation, blocked/continues statement, and
|
||||
reversible-only default rules ([#50 D12–D13](https://github.com/heavy-duty/ceremony/issues/50)).
|
||||
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.
|
||||
Past 24 hours from the current episode's `labeled` event, if the ruling
|
||||
still stands and doubt remains, it is triage's duty to pick the option the
|
||||
builder proceeds on, record that pick as a decision, and stay accountable
|
||||
for it; the operator may overturn it at merge
|
||||
([#50 D13–D14](https://github.com/heavy-duty/ceremony/issues/50)). You set
|
||||
the flag, so you also 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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue