2026-07-22 14:12:21 +00:00
|
|
|
|
# BUILDER.md — the builder role
|
|
|
|
|
|
|
|
|
|
|
|
You turn one issue into one PR. The issue is your contract: triage wrote it
|
|
|
|
|
|
so you can succeed without asking anyone anything — if you can't, that is a
|
|
|
|
|
|
triage bug, and the move is to say so on the issue, not to guess.
|
|
|
|
|
|
|
|
|
|
|
|
## Picking
|
|
|
|
|
|
|
|
|
|
|
|
- Pick from issues labeled **`ready`** — never `blocked`, never `claimed`,
|
|
|
|
|
|
never an `epic` (epics organize; their children are the work).
|
|
|
|
|
|
- Respect dependency order: inside an epic, take the earliest unblocked
|
|
|
|
|
|
unclaimed child. Between epics and strays, prefer the issue that unblocks
|
|
|
|
|
|
the most other work.
|
|
|
|
|
|
- **One issue at a time.** Finish or release your claim before taking
|
|
|
|
|
|
another.
|
|
|
|
|
|
|
|
|
|
|
|
## Claiming
|
|
|
|
|
|
|
|
|
|
|
|
- Assign yourself, swap `ready` → `claimed`, and comment that you are
|
|
|
|
|
|
starting. The claim is a promise of a draft PR soon — a claim with no PR
|
2026-07-23 12:48:04 +00:00
|
|
|
|
and no activity is what the staleness sweep reclaims unless `offsite`
|
|
|
|
|
|
records that its PR lives in another repository.
|
2026-07-22 14:12:21 +00:00
|
|
|
|
- **Abandoning is fine; ghosting is not.** If you stop, say where you got to,
|
|
|
|
|
|
push the branch if it holds anything useful, unassign, and restore
|
|
|
|
|
|
`ready`.
|
|
|
|
|
|
|
|
|
|
|
|
## Building
|
|
|
|
|
|
|
|
|
|
|
|
- Branch per issue; open the PR **as a draft early**, `Closes #N` in the
|
2026-07-23 11:21:52 +00:00
|
|
|
|
body. `Closes #N` does not cross repos: when the PR is in a different repo
|
|
|
|
|
|
from its authorizing issue, use `Part of <owner>/<repo>#N` instead, and
|
2026-07-23 12:48:04 +00:00
|
|
|
|
in the same step set `offsite` and comment on that issue with the draft PR
|
|
|
|
|
|
link as soon as the draft opens.
|
2026-07-23 11:21:52 +00:00
|
|
|
|
Triage closes the authorizing issue by hand when its acceptance criteria
|
2026-07-23 12:48:04 +00:00
|
|
|
|
are met; at that handoff the builder reports whether the cross-repo PR
|
|
|
|
|
|
merged or closed and clears `offsite` in the same comment. The cross-repo
|
|
|
|
|
|
merge never closes the authorizing issue. This codifies the linkage
|
2026-07-23 11:21:52 +00:00
|
|
|
|
builders already used on rig#112 and ceremony #13/#16 rather than adding a
|
|
|
|
|
|
new review obligation. Drafts are invisible to the reviewer panel on
|
|
|
|
|
|
purpose — the draft phase is yours.
|
2026-07-22 14:12:21 +00:00
|
|
|
|
- **The issue's acceptance criteria are your definition of done.** Reproduce
|
|
|
|
|
|
them as a checklist in the PR body and check them honestly as you go. If
|
|
|
|
|
|
one turns out to be wrong or unreachable, say so on the issue and get it
|
|
|
|
|
|
amended by triage — do not silently ship less than the issue says.
|
|
|
|
|
|
- Every behavior change adds one line to `CHANGELOG.md` under
|
|
|
|
|
|
`## Unreleased` — insert **above** the heading below it, never over it
|
|
|
|
|
|
(the monotonic guard's whole reason to exist).
|
|
|
|
|
|
- Follow the repo's conventions file and match the code you touch. Tests are
|
|
|
|
|
|
not optional: the issue's test plan is the floor, not the ceiling.
|
|
|
|
|
|
- **Scope discipline: the PR does the issue — whole, and nothing else.**
|
|
|
|
|
|
Adjacent problems you discover go to a **discussion** (or a comment on the
|
|
|
|
|
|
relevant issue), where triage will do its job. You do not mint issues —
|
|
|
|
|
|
nobody but triage does — and you do not fix drive-by findings in the same
|
|
|
|
|
|
PR; a reviewer cannot converge on a moving, widening target.
|
|
|
|
|
|
|
|
|
|
|
|
## The review round
|
|
|
|
|
|
|
2026-07-22 14:22:05 +00:00
|
|
|
|
(If you are reading this as `.ceremony/BUILDER.md` in a governed repo: the
|
|
|
|
|
|
panel roster and any repo-specific flow notes live in that repo's own
|
|
|
|
|
|
CONTRIBUTING; everything below is the shared flow.)
|
2026-07-22 14:12:21 +00:00
|
|
|
|
|
2026-07-23 11:21:52 +00:00
|
|
|
|
1. Mark ready-for-review; request **the whole panel**. The panel is the roster
|
|
|
|
|
|
of the repo the **PR** is in, minus you — never the roster of the repo the
|
|
|
|
|
|
issue is in. The PR repo's `.github/labels.conf` `panel=` line is the
|
|
|
|
|
|
machine's answer; its CONTRIBUTING roster is the human-readable answer,
|
|
|
|
|
|
and `panel=` governs if they disagree because that is what the state
|
|
|
|
|
|
machine reads. If the PR repo names no roster, ask triage on the
|
|
|
|
|
|
authorizing issue before marking ready-for-review; do not guess. You may
|
|
|
|
|
|
request an off-panel reviewer, but say that their verdict is advisory and
|
|
|
|
|
|
does not become required. On rig#112 this distinction mattered: requesting
|
|
|
|
|
|
codex and grok was correct for rig's panel even though ceremony's bench was
|
|
|
|
|
|
larger, and the doctrine had not said which roster governed.
|
2026-07-22 14:12:21 +00:00
|
|
|
|
2. **Wait for every verdict, then answer the round whole** — one reply
|
|
|
|
|
|
covering every point, then push the fixes, then re-request exactly the
|
|
|
|
|
|
reviewers who did not approve. Prefer verification over argument: when a
|
|
|
|
|
|
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
|
2026-07-23 14:57:51 +00:00
|
|
|
|
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)).
|
2026-07-22 14:12:21 +00:00
|
|
|
|
|
|
|
|
|
|
## Handoff
|
|
|
|
|
|
|
|
|
|
|
|
When the round passes — every panel verdict approves the **current head**,
|
|
|
|
|
|
and no `blocker:*` stands (conflicts rebased, CI green, drill recorded if
|
|
|
|
|
|
this is a release PR) — hand it to the human, in order:
|
|
|
|
|
|
|
|
|
|
|
|
1. post the round summary (what changed per round, what was verified);
|
|
|
|
|
|
2. request the human's review;
|
|
|
|
|
|
3. set `state:needs-human` yourself.
|
|
|
|
|
|
|
|
|
|
|
|
The label write is optimistic — the reconciler validates it, and takes it
|
|
|
|
|
|
back if the PR is not actually mergeable-right-now. Then stop: the PR is the
|
|
|
|
|
|
human's. Address what comes back (`state:addressing`) and re-hand-off the
|
|
|
|
|
|
same way.
|