chore: bump ceremony pin to 0.2.0
This commit is contained in:
parent
20438f09e8
commit
e22e0dd4a8
12 changed files with 361 additions and 62 deletions
|
|
@ -11,14 +11,78 @@ triage bug, and the move is to say so on the issue, not to guess.
|
|||
- 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.
|
||||
- **One build at a time.** You hold at most one issue on which you are
|
||||
writing or revising a deliverable — finish or release that work before
|
||||
starting new work. The rule counts build work in flight, not claims: a
|
||||
claim does not consume the slot while it is **parked**, meaning the next
|
||||
move belongs to someone else. Exactly five shapes qualify:
|
||||
1. the issue carries `needs-ruling`, its escalation names a decider, and
|
||||
its `Blocked:` line stops the remaining work;
|
||||
2. the deliverable is in a review round where every outstanding verdict
|
||||
belongs to someone else — either the round is awaiting its first
|
||||
verdicts, or it was answered whole and the non-approvers re-requested
|
||||
(the review round, steps 1–2). This is the *live* round; shape 4 is
|
||||
the *passed* one — they are sequential and do not overlap;
|
||||
3. every remaining acceptance criterion is operator-owned, stated as such
|
||||
by triage on the issue;
|
||||
4. the deliverable is **handed off** — the round passed, no `blocker:*`
|
||||
stands, and you set `state:needs-human` per Handoff (below). The
|
||||
remaining move is the human's merge.
|
||||
5. the claim is **held by directive** — triage or the operator has told
|
||||
you to stop, the direction names what the hold waits on, and that thing
|
||||
is not yours to move. This is not "waiting for a good moment": somebody
|
||||
else has decided the work must not proceed, and only they end it.
|
||||
Not parked — these are what the rule defends against: waiting on
|
||||
yourself, waiting on CI, or waiting for a good moment. An issue you have
|
||||
simply stopped working on is not parked either — that is abandonment,
|
||||
and its move is unchanged: unassign and restore `ready` (Claiming,
|
||||
below).
|
||||
The 2026-07-23 board is why the rule counts work and not claims: one
|
||||
builder correctly held
|
||||
[#15](https://github.com/heavy-duty/ceremony/issues/15) (`offsite`,
|
||||
round answered whole, one verdict outstanding) and
|
||||
[#16](https://github.com/heavy-duty/ceremony/issues/16) (`needs-ruling`
|
||||
hard block, triage said hold) parked beside the one active build,
|
||||
[#73](https://github.com/heavy-duty/ceremony/issues/73).
|
||||
|
||||
## 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
|
||||
and no activity is what the staleness sweep reclaims.
|
||||
and no activity is what the staleness sweep reclaims unless `offsite`
|
||||
records that its PR lives in another repository.
|
||||
- **A park is declared, never inferred.** When your claim enters a parked
|
||||
shape (Picking, above), say so in a comment on that issue, naming what it
|
||||
waits on and who owns the next move. No new label: the comment is
|
||||
activity, so it feeds the same reclaim clock the `needs-ruling`
|
||||
([#52](https://github.com/heavy-duty/ceremony/issues/52)) and `offsite`
|
||||
([#68](https://github.com/heavy-duty/ceremony/issues/68)) exemptions
|
||||
already guard — a parked claim nobody can name is an abandoned one.
|
||||
Shape 4 alone is exempt from the separate comment: the handoff round
|
||||
summary plus the `state:needs-human` write *is* its declaration — both
|
||||
halves are already there, what the claim waits on (the merge) and who
|
||||
owns the next move (the human), and both are visible to any scan as a
|
||||
`labeled` event with the summary beside it. No second comment is owed
|
||||
on the issue. Every other shape still declares as above.
|
||||
- **Pick up `attention` before anything else.** On your claim, first post a
|
||||
short pickup comment and remove `attention`; the removal is the ack. A
|
||||
demand on a parked claim is usually its unpark, so take the slot back under
|
||||
the existing rule below rather than leaving the demand parked. A demand
|
||||
that *is* the park is different: the pickup comment is the declaration,
|
||||
so one comment does both jobs, and the demand does not take the slot back.
|
||||
- **A directed hold keeps its bookkeeping visible.** The PR carries `blocked`
|
||||
with a comment naming what it waits on; the issue stays `claimed` and
|
||||
carries `attention` until the builder acknowledges it. Nobody unassigns
|
||||
the issue, and the 48-hour reclaim does not fire because the claim has an
|
||||
open PR. Unparking follows the existing rule below.
|
||||
- **Unparking is a claim like any other.** When the wait ends, the parked
|
||||
issue is work again and takes the slot. If you are already active
|
||||
elsewhere, finish or release that work first, and say which you did on
|
||||
both issues — the slot is still one. Nothing counts claims per builder
|
||||
and no reconciler path enforces any of this: `claim_decision()` sees one
|
||||
issue at a time by construction, and no such machinery should be built
|
||||
expecting it to have been specified here. The discipline is the
|
||||
declaration, not a counter.
|
||||
- **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`.
|
||||
|
|
@ -26,15 +90,29 @@ triage bug, and the move is to say so on the issue, not to guess.
|
|||
## Building
|
||||
|
||||
- Branch per issue; open the PR **as a draft early**, `Closes #N` in the
|
||||
body. Drafts are invisible to the reviewer panel on purpose — the draft
|
||||
phase is yours.
|
||||
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
|
||||
in the same step set `offsite` and comment on that issue with the draft PR
|
||||
link as soon as the draft opens.
|
||||
Triage closes the authorizing issue by hand when its acceptance criteria
|
||||
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
|
||||
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.
|
||||
- **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).
|
||||
- Every behavior change writes one fragment, `changelog.d/<issue>.md`,
|
||||
named for the authorizing issue (`<repo>-<issue>.md` when the work is
|
||||
cross-repo) — the exact prose that will be published, nothing else: `- `
|
||||
bullets, and in a grouped repo the `### Added` / `### Changed` /
|
||||
`### Fixed` headings inside the fragment, creating a rarer kind only when
|
||||
a change genuinely is one. Never edit `CHANGELOG.md` for an entry — the
|
||||
release PR assembles the section from the fragments (#112); the monotonic
|
||||
guard still refuses anything that deletes a shipped heading.
|
||||
- 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.**
|
||||
|
|
@ -49,16 +127,81 @@ triage bug, and the move is to say so on the issue, not to guess.
|
|||
panel roster and any repo-specific flow notes live in that repo's own
|
||||
CONTRIBUTING; everything below is the shared flow.)
|
||||
|
||||
1. Mark ready-for-review; request **the whole panel** (the roster is in the
|
||||
repo's CONTRIBUTING).
|
||||
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.
|
||||
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
|
||||
in the PR — a maintainer can be asked for a ruling; silence and
|
||||
force-forward are not options.
|
||||
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
|
||||
|
||||
|
|
@ -72,5 +215,6 @@ this is a release PR) — hand it to the human, in order:
|
|||
|
||||
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.
|
||||
human's. The claim is now parked as shape 4 (Picking, above) — the handoff
|
||||
you just posted is its declaration, and your build slot is free. Address
|
||||
what comes back (`state:addressing`) and re-hand-off the same way.
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ the labels workflow's bootstrap dispatch (issue #10).
|
|||
|
||||
Two state machines share the taxonomy: the **PR machine** (proven in
|
||||
box/rig/cast, reconciled by machinery) and the **issue flow** (the
|
||||
triage → build queue, doctrine-enforced today, machinery to follow —
|
||||
issue #18). One rule joins everything: **states are machine-owned, intent
|
||||
triage → build queue, reconciled by the work-queue sweep). One rule joins
|
||||
everything: **states are machine-owned, intent
|
||||
labels are hand-set** — a hand-moved state label is a lie waiting to happen,
|
||||
and the reconciler recomputes it from GitHub's own facts.
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -54,11 +54,11 @@ strips it on sight).
|
|||
| `blocked` | `#6A737D` | waiting on another issue or PR (`Blocked by #N` in the body names it) | triage; anyone may correct it |
|
||||
| `epic` | `#5319E7` | organizes other issues via a dependency-ordered task list; **builders never pick an epic** | triage |
|
||||
|
||||
The invariant a board scan relies on: every open issue is either
|
||||
The work-queue sweep enforces the invariant a board scan relies on: every open issue is either
|
||||
`needs-triage`, `epic`, or carries exactly one of `ready` / `claimed` /
|
||||
`blocked`. A `claimed` issue with no open PR and no activity is what the
|
||||
staleness sweep will reclaim (issue #18); until that machinery exists,
|
||||
[TRIAGE.md](TRIAGE.md) owns the hygiene by hand.
|
||||
`blocked`. It flags conflicts rather than guessing intent. A `claimed` issue
|
||||
with no open PR and no activity for 48 hours is reclaimed by the sweep: it
|
||||
comments, unassigns the stale owner, and restores `ready`.
|
||||
|
||||
## Cross-cutting (PRs and issues)
|
||||
|
||||
|
|
@ -66,21 +66,117 @@ 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) |
|
||||
| `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-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 |
|
||||
| `attention` | `#D93F0B` | issue-only demand parked for the assignee; hand-set, and never written by the machine |
|
||||
| `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 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:
|
||||
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
|
||||
[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.
|
||||
|
||||
`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
|
||||
visible to the sweep (#68). The builder sets it in the same step that posts
|
||||
the cross-repo draft link, then clears it at handoff in the same comment that
|
||||
reports whether that PR merged or closed. The machine reads the flag and
|
||||
never writes it. It stops only the claim-reclaim clock: missing assignees are
|
||||
still flagged, queue-label conflicts and missing queue state are still
|
||||
repaired, and epic-completion and PR-side stale behavior are unchanged. The
|
||||
sweep tells the assignee once when every visible cross-referenced PR has
|
||||
closed; it only tells, and never clears the flag or changes the claim.
|
||||
|
||||
`attention` is issue-only and says a demand is parked on an issue for its
|
||||
assignee. Anyone who needs that assignee's hands — triage, the operator, or a
|
||||
sibling agent — sets it. The assignee alone clears it, as the first act of
|
||||
pickup together with a short comment; that removal is the acknowledgement
|
||||
and re-arms the flag for the next demand. If the session dies before the ack,
|
||||
the still-visible flag launches the next pickup instead. An unanswered flag
|
||||
is auditable evidence on the board.
|
||||
|
||||
The flag is additive: it composes with `ready`, `claimed`, or `blocked` and
|
||||
with `needs-ruling`, and never substitutes for queue state. It pauses no
|
||||
clock. Unlike `offsite` and `needs-ruling`, which make silence legitimate,
|
||||
unanswered `attention` is exactly the silence the 48-hour reclaim should
|
||||
take. It is hand-set doctrine only: nothing in `actions/` sets, clears,
|
||||
reads, or validates it, and no reconciler enforces the assignee requirement.
|
||||
An `attention` issue without an assignee is therefore a board bug, not a
|
||||
demand; anyone may assign it or remove the flag.
|
||||
|
||||
The three signals are mutually distinct: `attention` means an assignee owes
|
||||
a move; `needs-ruling` means a human owes a decision under
|
||||
[the escalation contract and ladder](BUILDER.md#the-ruling-ask); and a bare
|
||||
`@`-mention is an FYI that demands nothing and remains perfectly fine. A
|
||||
demand that is itself a human decision carries `needs-ruling`, never both.
|
||||
This distinction records the
|
||||
[#16 missed-ruling incident](https://github.com/heavy-duty/ceremony/issues/16#issuecomment-5061051198)
|
||||
and why the rejected mention poll is not returning: ordinary thread traffic
|
||||
re-arms mentions, but only the writer can declare that a move is owed (#83).
|
||||
|
||||
## Scope — which surface? (PRs and issues, any number)
|
||||
|
||||
All scopes share one calm color, `#C5DEF5` — scopes locate, states alert. The
|
||||
set is per-repo (`.github/labels.conf`); PRs get theirs from changed paths via
|
||||
actions/labeler, issues get theirs from triage. This repo's set:
|
||||
|
||||
| Label | Covers |
|
||||
|---|---|
|
||||
| `scope:release-flow` | the reusable release workflow, decide, the doors |
|
||||
| `scope:guards` | changelog-armed / changelog-monotonic / drill-recorded |
|
||||
| `scope:labels` | the labels workflow, reconciler, this taxonomy |
|
||||
| `scope:docs` | README doctrine, CONSUMERS.md, the role files |
|
||||
set is per-repo: PRs get theirs from changed paths via actions/labeler, issues
|
||||
get theirs from triage. This file never enumerates a set — it is mirrored
|
||||
byte-identically into every governed repo, and any list it carried would be
|
||||
true in one repo and false in the rest (#104). The set for the repo you are
|
||||
standing in lives in the two places that are true wherever you read them: its
|
||||
`.github/labels.conf` (the definitions, one `name|color|description` row per
|
||||
scope) and its own `CONTRIBUTING.md`, beside the other repo-specific facts.
|
||||
|
||||
## Issue types
|
||||
|
||||
|
|
@ -92,7 +188,8 @@ on a PR would say the same thing twice and drift.
|
|||
|
||||
The labels workflow (issue #10) recomputes PR state statelessly on PR events
|
||||
plus a 15-minute advisory cron, and bootstraps this taxonomy idempotently on
|
||||
manual dispatch. Issue-flow labels are doctrine-owned until #18 lands
|
||||
machinery for them. Default GitHub labels (`duplicate`, `invalid`,
|
||||
`question`, `wontfix`, `help wanted`, `good first issue`) are deleted at
|
||||
bootstrap — a `question` is a discussion, not an issue.
|
||||
manual dispatch. The sweep warns when the core taxonomy declares a label the
|
||||
repository lacks. The same workflow reconciles issue-flow labels on issue
|
||||
events and during the scheduled sweep. Default GitHub labels (`duplicate`,
|
||||
`invalid`, `question`, `wontfix`, `help wanted`, `good first issue`) are
|
||||
deleted at bootstrap — a `question` is a discussion, not an issue.
|
||||
|
|
|
|||
|
|
@ -23,9 +23,10 @@ The machine reads only your **verdict**; humans read your reasons.
|
|||
|
||||
In order of authority:
|
||||
|
||||
1. **The issue's acceptance criteria** — the PR's `Closes #N` names your
|
||||
spec. Check every criterion; a PR that ships less than the issue says is
|
||||
a request-changes even if the code is beautiful.
|
||||
1. **The issue's acceptance criteria** — the PR's `Closes #N`, or its
|
||||
cross-repo `Part of <owner>/<repo>#N`, names your spec. Check every
|
||||
criterion; a PR that ships less than the issue says is a request-changes
|
||||
even if the code is beautiful.
|
||||
2. **The repo's load-bearing constraints** — the rules bought with
|
||||
incidents (in ceremony itself: issue #1's constraint list; in a governed
|
||||
repo: its own CONTRIBUTING plus ceremony's README). A change that
|
||||
|
|
@ -40,6 +41,22 @@ In order of authority:
|
|||
test settles what a comment thread can't. A review that says "I ran X and
|
||||
saw Y" outranks one that says "this looks like it might".
|
||||
|
||||
## Where you review
|
||||
|
||||
- **A review request on you is your authorization** in any `heavy-duty` repo
|
||||
and on any fleet member's fork. You need no separate permission and do not
|
||||
wait for the repo to appear on a list: review is reversible
|
||||
read-plus-comment work, and the requester already decided it should happen.
|
||||
- **A request is authorization, not panel membership.** Convergence is
|
||||
measured against the target repo's `panel=` roster minus the author. If you
|
||||
are requested off-panel, post the verdict anyway and say in its body that
|
||||
it is advisory; neither your silence nor your request-changes is a gate the
|
||||
reconciler enforces. The nine-hour wait for kimi's off-panel verdict on
|
||||
rig#112 showed why authorization and membership must not be conflated.
|
||||
- **Being requested is a wake condition of its own.** It is how work in a
|
||||
repo you have never heard of reaches you; a repo list finds only work in
|
||||
repos somebody thought to list.
|
||||
|
||||
## What you do not do
|
||||
|
||||
- **Re-litigate the spec.** The issue's decisions were made in triage and,
|
||||
|
|
@ -64,7 +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.
|
||||
- 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)).
|
||||
|
|
|
|||
|
|
@ -32,10 +32,33 @@ 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.
|
||||
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.
|
||||
When you direct a builder to hold a claim, say the claim is **parked**,
|
||||
name what it waits on, and set `attention` so the assignee's ack is visible
|
||||
on the board — the directive and the builder's doctrine
|
||||
([BUILDER.md](BUILDER.md#claiming)) must use one word.
|
||||
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;
|
||||
when that ruling or any directive or answered builder question delivers
|
||||
the assignee's next move in prose, set `attention` in the same comment.
|
||||
This is not a substitute for minting work or for `needs-ruling`.
|
||||
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.
|
||||
|
|
@ -58,7 +81,9 @@ Every issue you mint carries, in this order:
|
|||
the builder's definition of done and the reviewer's review spec, verbatim.
|
||||
- **Test plan**: what proves it, including the cases that must fail.
|
||||
- **Dependencies**: `Blocked by #N` / `Blocks #N`, and `Part of #E` when an
|
||||
epic organizes it.
|
||||
epic organizes it. Name a cross-repo dependency the same way with its
|
||||
repository qualified (`Blocked by repo#N` or `owner/repo#N`); the sweep
|
||||
cannot resolve it, so triage verifies it and flips the issue by hand.
|
||||
- **Labels**: type (`bug`/`enhancement`/`documentation`), `scope:*`, and
|
||||
exactly one of `ready` / `blocked` (see [LABELS.md](LABELS.md)).
|
||||
|
||||
|
|
@ -75,13 +100,16 @@ dependency-ordered task list of child issues. Children reference the epic;
|
|||
the epic's checklist is the progress view. Builders never pick the epic
|
||||
itself. Keep the checklist current — a stale epic misleads every scan.
|
||||
|
||||
## Backlog hygiene (yours until #18 automates it)
|
||||
## Backlog hygiene
|
||||
|
||||
- **Dedup before minting** — search issues *and* closed issues; extend or
|
||||
reopen before duplicating.
|
||||
- **Flip `blocked` → `ready`** when the named dependency lands.
|
||||
- **Reclaim abandoned claims**: `claimed` + no open PR + no activity →
|
||||
comment, unassign, restore `ready`.
|
||||
- The issue-flow sweep flips `blocked` → `ready` when every named dependency
|
||||
lands, and flags a blocked issue whose dependency declaration is unreadable.
|
||||
- The sweep reclaims abandoned claims after 48 hours: `claimed` + no open PR
|
||||
+ no activity → comment, unassign, restore `ready`.
|
||||
- Automation never guesses intent. Resolve the conflict comments it leaves on
|
||||
malformed queue states, and close or extend completed epics when nudged.
|
||||
- **Close obsolete issues** with the reason and a link to what obsoleted
|
||||
them. Every label on every open issue stays true; the board is only worth
|
||||
scanning if it does not lie.
|
||||
|
|
|
|||
2
.github/DISCUSSION_TEMPLATE/ideas.yml
vendored
2
.github/DISCUSSION_TEMPLATE/ideas.yml
vendored
|
|
@ -1,4 +1,4 @@
|
|||
# Copied from heavy-duty/ceremony@0.1.0 .github/DISCUSSION_TEMPLATE/ideas.yml (heavy-duty/ceremony#24). Hand-maintained: no guard checks this against ceremony.
|
||||
# Copied from heavy-duty/ceremony@0.2.0 .github/DISCUSSION_TEMPLATE/ideas.yml (heavy-duty/ceremony#24). Hand-maintained: no guard checks this against ceremony.
|
||||
# Light on purpose: discussions are where ambiguity is ALLOWED — a form
|
||||
# that demands rigor at the door defeats the room's purpose (heavy-duty/ceremony#24,
|
||||
# decision 4). If these prompts fight the flow in practice, delete them
|
||||
|
|
|
|||
2
.github/DISCUSSION_TEMPLATE/q-a.yml
vendored
2
.github/DISCUSSION_TEMPLATE/q-a.yml
vendored
|
|
@ -1,4 +1,4 @@
|
|||
# Copied from heavy-duty/ceremony@0.1.0 .github/DISCUSSION_TEMPLATE/q-a.yml (heavy-duty/ceremony#24). Hand-maintained: no guard checks this against ceremony.
|
||||
# Copied from heavy-duty/ceremony@0.2.0 .github/DISCUSSION_TEMPLATE/q-a.yml (heavy-duty/ceremony#24). Hand-maintained: no guard checks this against ceremony.
|
||||
# Light on purpose — same reasoning as ideas.yml (heavy-duty/ceremony#24, decision 4):
|
||||
# optional prompts only, delete before hardening.
|
||||
body:
|
||||
|
|
|
|||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -1,4 +1,4 @@
|
|||
# Copied from heavy-duty/ceremony@0.1.0 .github/ISSUE_TEMPLATE/config.yml (heavy-duty/ceremony#24). Hand-maintained: no guard checks this against ceremony.
|
||||
# Copied from heavy-duty/ceremony@0.2.0 .github/ISSUE_TEMPLATE/config.yml (heavy-duty/ceremony#24). Hand-maintained: no guard checks this against ceremony.
|
||||
# The new-issue interception (heavy-duty/ceremony#24, decision 1): interception over
|
||||
# instruction — prose alone has already proven insufficient everywhere else
|
||||
# in this org. With blank issues disabled and exactly one form, a "New
|
||||
|
|
|
|||
2
.github/ISSUE_TEMPLATE/work-order.yml
vendored
2
.github/ISSUE_TEMPLATE/work-order.yml
vendored
|
|
@ -1,4 +1,4 @@
|
|||
# Copied from heavy-duty/ceremony@0.1.0 .github/ISSUE_TEMPLATE/work-order.yml (heavy-duty/ceremony#24). Hand-maintained: no guard checks this against ceremony.
|
||||
# Copied from heavy-duty/ceremony@0.2.0 .github/ISSUE_TEMPLATE/work-order.yml (heavy-duty/ceremony#24). Hand-maintained: no guard checks this against ceremony.
|
||||
# The issue contract (.ceremony/TRIAGE.md "The issue contract") as a form, so every
|
||||
# minted issue lands with the same bones and a later parser (heavy-duty/ceremony#18) can key on
|
||||
# stable section headings. Consistency, not a gate: the form applies no
|
||||
|
|
|
|||
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
|
|
@ -1,4 +1,4 @@
|
|||
<!-- Copied from heavy-duty/ceremony@0.1.0 .github/pull_request_template.md (heavy-duty/ceremony#24). Hand-maintained: no guard checks this against ceremony.
|
||||
<!-- Copied from heavy-duty/ceremony@0.2.0 .github/pull_request_template.md (heavy-duty/ceremony#24). Hand-maintained: no guard checks this against ceremony.
|
||||
Short on purpose: a template longer than a small PR gets deleted by
|
||||
reflex, not read (heavy-duty/ceremony#24). One issue, one PR — draft while building. -->
|
||||
|
||||
|
|
|
|||
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
|
|
@ -9,7 +9,8 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# changelog-monotonic compares HEAD against the merge base; a
|
||||
# changelog-monotonic and changelog-assembled compare HEAD against
|
||||
# the merge base; a
|
||||
# checkout that cannot resolve it is a hard failure in CI, not
|
||||
# a skip (a guard that can quietly stop guarding is the failure
|
||||
# shape these checks exist to refuse).
|
||||
|
|
@ -51,18 +52,21 @@ jobs:
|
|||
#
|
||||
# changelog-armed: the version-keyed arming rule (rig#66; the
|
||||
# unconditional form rig#44 reverted — this is its correct return).
|
||||
- uses: heavy-duty/ceremony/actions/changelog-armed@0.1.0
|
||||
- uses: heavy-duty/ceremony/actions/changelog-armed@0.2.0
|
||||
# changelog-monotonic: no shipped heading deleted or duplicated
|
||||
# (#98, box#122). Strict by default: an unresolvable base ref is red,
|
||||
# never a quiet skip — hence the fetch-depth: 0 above.
|
||||
- uses: heavy-duty/ceremony/actions/changelog-monotonic@0.1.0
|
||||
- uses: heavy-duty/ceremony/actions/changelog-monotonic@0.2.0
|
||||
# changelog-assembled: a release's stamped section must exactly match
|
||||
# the fragments it consumed. Vacuous on non-release PRs.
|
||||
- uses: heavy-duty/ceremony/actions/changelog-assembled@0.2.0
|
||||
# drill-recorded: a release version carries drills/<version>.md
|
||||
# (rig's drill meaning: drills/README.md). Vacuous on -dev trees.
|
||||
- uses: heavy-duty/ceremony/actions/drill-recorded@0.1.0
|
||||
- uses: heavy-duty/ceremony/actions/drill-recorded@0.2.0
|
||||
# docs-sync: the .ceremony/ doctrine mirror is byte-identical to the
|
||||
# pin read from release.yml (ceremony#19) — a hand edit or a
|
||||
# half-done pin bump goes red here.
|
||||
- uses: heavy-duty/ceremony/actions/docs-sync@0.1.0
|
||||
- uses: heavy-duty/ceremony/actions/docs-sync@0.2.0
|
||||
|
||||
# The install LIFECYCLE against a tree install.sh itself produced — the four
|
||||
# beats box and cast already run in CI (#106): install from this checkout,
|
||||
|
|
|
|||
2
.github/workflows/labels.yml
vendored
2
.github/workflows/labels.yml
vendored
|
|
@ -15,4 +15,4 @@ permissions:
|
|||
pull-requests: write
|
||||
jobs:
|
||||
labels:
|
||||
uses: heavy-duty/ceremony/.github/workflows/labels.yml@0.1.0
|
||||
uses: heavy-duty/ceremony/.github/workflows/labels.yml@0.2.0
|
||||
|
|
|
|||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -17,6 +17,6 @@ permissions:
|
|||
issues: write # --label on that fallback PR rides the issues API
|
||||
jobs:
|
||||
release:
|
||||
uses: heavy-duty/ceremony/.github/workflows/release.yml@0.1.0
|
||||
uses: heavy-duty/ceremony/.github/workflows/release.yml@0.2.0
|
||||
with:
|
||||
version-source: file
|
||||
|
|
|
|||
Loading…
Reference in a new issue