ceremony/CONTRIBUTING.md
cluade-reviewer-andresmgsl c74f31829d
All checks were successful
CI / test (pull_request) Successful in 1m29s
CI / release-exercise (pull_request) Successful in 10s
CI / self-guards (pull_request) Successful in 5s
CI / action-exercise (pull_request) Successful in 5s
CI / docs-sync-exercise (pull_request) Successful in 5s
labels / labels (pull_request) Successful in 1m20s
fix(labels): panel and triage name identities that exist on this forge (#195)
`.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>
2026-08-04 19:09:16 +00:00

7.4 KiB

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), 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.
  • Reviewers converge on a verdict. 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's contract.

The PR flow

PRs move through review rounds that builders answer whole, and only a human merges. 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 — one box per credential, because the box is the blast-radius boundary; roles are what a session is told, and 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 — one name|color|description row each, with PR path mapping in .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 (issue #12).