forked from heavy-duty/ceremony
`.github/labels.conf` named five identities and every one of them 404s on this instance — a GitHub-shaped team that was never minted here. Both consumers of that roster were inoperable in consequence: `panel=` becomes the required-verdict set, so a review round could never converge, and `triage-actors=` is the arrival author gate, so every issue was a stray mint owing `needs-triage` that nobody the conf recognized could clear. Measured on #191: stamped `needs-triage` four seconds after mint by the arrival path working correctly, then unclosable for the rest of the day. The mapping is @andres's ruling, recorded on #191. `cluade` holds triage and reviews but does not build; `codex` builds; `kimi` reviews; `grok` comes off the bench; the human row is `andres`. CONTRIBUTING's roster table moves with the conf, including the approval count: panel-minus-author resolves to two on this roster, not three, because the only builder is itself on the panel. The rule is unchanged and still stated as panel-minus-author — only the number it currently comes to is named honestly. test/labels.test.sh now holds the conf and the table to the same set in both directions. It cannot reach the half that actually broke — two files agreeing with each other and neither with the forge — but it does catch a roster edit that touches one file and not the other, which is how a deliberate swap becomes a silent one. Not touched, deliberately: drills/*.md, which record runs that really happened under the old names; REVIEWER.md, whose old-name hits are citations and a past-event anecdote rather than roster definition. Refs #195 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
140 lines
7.4 KiB
Markdown
140 lines
7.4 KiB
Markdown
# Contributing
|
|
|
|
This repo defines how the heavy-duty repos work — the release ceremony, the
|
|
label state machine, and the agent team flow — and it runs entirely on its own
|
|
rules. If something here contradicts how this repo actually operates, one of
|
|
the two is a bug.
|
|
|
|
## The line
|
|
|
|
Work moves through one pipeline, and every stage has an owner:
|
|
|
|
```
|
|
discussion ──▶ triage ──▶ issue ──▶ build ──▶ review ──▶ human merge ──▶ release
|
|
(anyone) (agent) (queue) (agent) (agents) (human) (ceremony)
|
|
```
|
|
|
|
- **Discussions are where intent lives.** Anyone — human or agent — who has an
|
|
idea, a bug, a question, or a "we should…" opens a **discussion**, not an
|
|
issue. Discussions are allowed to be vague; that is what they are for.
|
|
- **Issues are minted only by triage.** Nobody else writes issues — not
|
|
humans, not builders, not reviewers. An issue is a work order with a quality
|
|
bar (the issue contract in [TRIAGE.md](TRIAGE.md)), and the bar holds
|
|
because exactly one role is accountable for it. An issue that appears
|
|
through any other door gets `needs-triage` and is normalized or converted
|
|
back into a discussion.
|
|
- **Builders turn one issue into one PR.** [BUILDER.md](BUILDER.md).
|
|
- **Reviewers converge on a verdict.** [REVIEWER.md](REVIEWER.md).
|
|
- **Humans decide twice**: in the discussion (what is worth doing, and any
|
|
call triage escalates back) and at the merge (whether it ships). Everything
|
|
between those two points is agent work by default.
|
|
- **Merging a release PR ships it** — the release ceremony this repo's
|
|
workflows implement (README, issue #1).
|
|
|
|
Who may set which label is [LABELS.md](LABELS.md)'s contract.
|
|
|
|
## The PR flow
|
|
|
|
PRs move through review rounds that builders answer whole, and only a human
|
|
merges. [BUILDER.md](BUILDER.md) is the shared flow contract; this file names
|
|
only ceremony-specific facts such as the roster and code conventions.
|
|
|
|
### Roster
|
|
|
|
Three identities share the work (org team `agents`), each living in its own
|
|
[box](https://github.com/heavy-duty/box) — one box per credential, because
|
|
the box is the blast-radius boundary; roles are what a session is told, and
|
|
[AGENTS.md](AGENTS.md) routes from there:
|
|
|
|
| identity | box (rig tenant) | standing work |
|
|
|---|---|---|
|
|
| `cluade-reviewer-andresmgsl` | `triage` (claude-box) | **triage** — the only door issues come through; this identity mints issues and nothing else writes them (#18's `triage-actors`) — and review. It does not build. |
|
|
| `codex-reviewer-andresmgsl` | codex-box | build + review |
|
|
| `kimi-reviewer-andresmgsl` | kimi-box | review |
|
|
|
|
**The review panel for any PR is every bench identity except its author** —
|
|
recusal by construction, enforced by the reconciler (#10): the required
|
|
verdicts are the panel minus the PR's author. On this roster that resolves
|
|
to **two** cross-vendor approvals of the current head, because the only
|
|
builder is itself on the panel and recuses from its own PRs; the rule is
|
|
panel-minus-author, and two is what it currently comes to, not a second
|
|
rule. Builders and triage default to different models so the issue contract
|
|
is honestly exercised — a spec gap should surface as a question on the
|
|
issue, not be silently filled by shared priors. Humans (`andres`) decide in
|
|
discussions and merge; the roster is config, not doctrine — swapping a
|
|
vendor is an edit to this table (and to `panel=` in
|
|
`.github/labels.conf` once #10 lands), nothing more.
|
|
|
|
The identities named here must be the identities `.github/labels.conf`
|
|
names, and both must exist on the forge the repo lives on. A roster that
|
|
agrees with itself and disagrees with the instance is the failure #195
|
|
records: `panel=` naming absent users cannot converge and
|
|
`triage-actors=` naming an absent user makes every issue a stray mint that
|
|
nobody can normalize. `test/labels.test.sh` holds this table and the conf
|
|
to the same set, in both directions.
|
|
|
|
Each governed repo names its own roster in its CONTRIBUTING; this one is
|
|
ceremony's. Its `scope:*` set is the same kind of repo-specific fact:
|
|
ceremony's scopes are defined in [`.github/labels.conf`](.github/labels.conf)
|
|
— one `name|color|description` row each, with PR path mapping in
|
|
[`.github/labeler.yml`](.github/labeler.yml). The conf is the set; no prose
|
|
table repeats it (#104).
|
|
|
|
## Code conventions
|
|
|
|
- Bash: `set -euo pipefail` in executables, `set -u` in test files (the test
|
|
harness asserts on failing commands, so no `-e` there).
|
|
- **mawk-compatible awk** — CI runners ship mawk, not gawk; no `\x` escapes.
|
|
- **Every piece of logic is a file of its own so a test can drive it.**
|
|
Workflows and actions gather facts; scripts decide. If a decision lives
|
|
inline in YAML, it is in the wrong place.
|
|
- Comments carry the *why* — the incident that bought the rule, with its
|
|
issue number (`box#108`, `rig#66`, …). When porting from a sibling repo,
|
|
the war stories come along; they are the documentation.
|
|
- Whole-version matching everywhere: `0.7.0` never matches `0.7.0-rc1`.
|
|
- Shellcheck- and actionlint-clean is a CI gate, not a suggestion.
|
|
|
|
## How the other repos use this
|
|
|
|
Two consumption modes, split by what has a runtime:
|
|
|
|
- **Machinery is consumed by reference.** Workflows and actions are fetched
|
|
by GitHub at run time from the ref the caller pins — no copy exists in the
|
|
consumer.
|
|
- **Doctrine is consumed as a machine-verified mirror.** A document's only
|
|
"runtime" is an agent reading the working tree of the repo it stands in —
|
|
a doc that requires a cross-repo fetch before it governs is a doc that
|
|
sometimes goes unread. So the agent-facing set — **AGENTS.md, TRIAGE.md,
|
|
BUILDER.md, REVIEWER.md, LABELS.md** — is vendored into each governed
|
|
repo at **`.ceremony/`**, byte-identical to this repo at the pinned ref,
|
|
by the sync tool (issue #19). A CI guard diffs the mirror against the pin
|
|
on every PR: hand-editing a vendored file, or bumping the pin without
|
|
re-syncing, goes red. It is a copy that cannot drift — which is the only
|
|
kind of copy this org allows.
|
|
|
|
A governed repo (box, rig, cast, incubator, …) therefore carries:
|
|
|
|
- `.ceremony/` — the vendored doctrine (machine-written; never edited by
|
|
hand; agents read it from the checkout, no network, no other repo);
|
|
- a thin root **`AGENTS.md` stub** — a few lines: "governed by
|
|
heavy-duty/ceremony; read `.ceremony/AGENTS.md` first; repo specifics in
|
|
CONTRIBUTING". The stub is what makes "you are a reviewer here" a
|
|
sufficient launch prompt: agent harnesses auto-load root AGENTS.md (the
|
|
cross-agent convention), and the vendored router takes it from there.
|
|
Tool-specific files (`CLAUDE.md`, …) reduce to one pointer line at it;
|
|
- the thin workflow callers (release, labels) pinned to a ceremony tag, plus
|
|
the `docs-sync --check` guard step in CI;
|
|
- a short header in its own CONTRIBUTING pointing agents at `.ceremony/`,
|
|
followed by only what is genuinely per-repo:
|
|
- the **review panel roster**,
|
|
- the **`scope:*` label set** (`.github/labels.conf` + `.github/labeler.yml`),
|
|
- the **drill meaning** (`drills/README.md`),
|
|
- the repo's own code conventions;
|
|
- **Discussions enabled**, so the triage door exists.
|
|
|
|
One pin governs both the machinery and the doctrine: the ref a repo's
|
|
workflows call is the ref its `.ceremony/` mirror is verified against.
|
|
Bumping the pin is one PR — the pin line plus the re-synced mirror, checked
|
|
by the same guard — and is how a process change rolls out: deliberately, per
|
|
repo, reviewed. The full adoption checklist lives in
|
|
[docs/CONSUMERS.md](docs/CONSUMERS.md) (issue #12).
|