drill/README.md is the repeatable procedure #107's second checkbox asks for: prerequisites (the throwaway machine, the tagged key, the fork's drill workflow, the pins), the invocation, what each leg asserts, where the record lands. drills/README.md's harness disclaimer flips to point at the instrument, its legs list and example record match what drill.sh actually runs and emits, and CONTRIBUTING's drill sentence names the script. Changelog entry under Unreleased. (ceremony flow: issue #105) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
112 lines
6.6 KiB
Markdown
112 lines
6.6 KiB
Markdown
# Contributing
|
|
|
|
This repo is governed by
|
|
[heavy-duty/ceremony](https://github.com/heavy-duty/ceremony). **Agents:
|
|
read [`.ceremony/AGENTS.md`](.ceremony/AGENTS.md) first** — it routes you to
|
|
your role file (builder, reviewer, triage), vendored beside it,
|
|
byte-identical to ceremony at the pin named in
|
|
[`.github/workflows/release.yml`](.github/workflows/release.yml) and
|
|
guarded by the `docs-sync` step in CI. The review-round doctrine — drafts,
|
|
whole-round replies, verdicts, the handoff — lives there and in
|
|
[`.ceremony/LABELS.md`](.ceremony/LABELS.md); this file keeps only what is
|
|
genuinely rig's.
|
|
|
|
## The PR loop, rig specifics
|
|
|
|
1. **Fork and branch.** Contributors work from forks; upstream branches are
|
|
for maintainers. Title the PR conventionally (`feat:`, `fix:`, `docs:`).
|
|
2. **The review panel** (`.github/labels.conf`'s `panel=` line):
|
|
`claude-bot-andresmgsl`, `codex-bot-andresmgsl`, `grok-bot-andresmgsl` —
|
|
the required verdicts for a PR are the panel minus its author. The
|
|
maintainer (`danmt`) takes the last word and merges.
|
|
3. **Checks must be green**: `shellcheck`, `bash test/cli.sh` and
|
|
`bash test/release.sh` locally mirror what CI runs; the db dump/restore
|
|
round-trip (`test/db-integration.sh`) executes in CI where Docker is
|
|
present. The release guards (`changelog-armed`, `changelog-monotonic`,
|
|
`drill-recorded`, `docs-sync`) run as ceremony's pinned actions.
|
|
4. **Feature PRs land their changelog entry as part of the PR**: add it
|
|
under `CHANGELOG.md`'s `## Unreleased` heading — that section becomes
|
|
the release notes verbatim when a release is cut.
|
|
|
|
## Changelog entries
|
|
|
|
Every PR that changes behaviour adds one line to `## Unreleased`. One line is
|
|
the whole rule — if it wraps more than twice in your editor, cut it down.
|
|
|
|
- **Say what changed, and stop.** Why it was wrong, how it was found, what it
|
|
cost, what it implies — that belongs in the PR body and the commit message,
|
|
which is where anyone chasing the reasoning already goes. This file answers
|
|
one question: what is different in this version.
|
|
- **Any word that can be removed, is removed.**
|
|
- **Lead with the surface, not the mechanism.** "`state:needs-human` is set at
|
|
handoff" beats "the labels workflow now also wakes on `labeled`".
|
|
- **Cite the issue or PR** — `(#96)` — and let the reader follow it for the
|
|
rest.
|
|
- **Mark a breaking change** with a leading `BREAKING:`.
|
|
- Group under `### Added` / `### Changed` / `### Fixed` / `### Removed`.
|
|
- No bold run-in headings, no sub-paragraphs, no code blocks, no prose essays.
|
|
|
|
Good:
|
|
|
|
- `state:needs-human` is set at handoff, not by the cron (#96)
|
|
- An unreadable check rollup no longer reads as "nothing is failing" (#90)
|
|
- BREAKING: `--class human|server` is now `--root-door closed|open` (#77)
|
|
|
|
Not an entry — that is a PR body:
|
|
|
|
- **`state:needs-human` no longer waits on the cron to become true** (#96) —
|
|
the labels workflow now also wakes on `pull_request_target: labeled` and
|
|
`unlabeled`, and the author sets it themselves when handing a PR over. A
|
|
review landing was never a trigger. There is no `pull_request_review_target`,
|
|
and on fork PRs — which is all of them here — ...
|
|
|
|
## Releasing
|
|
|
|
A release is a PR, and merging it is the release. The ceremony — the two
|
|
doors, the decide table, the stamps, the post-release re-arm — is
|
|
heavy-duty/ceremony's machinery, consumed by reference:
|
|
[its README](https://github.com/heavy-duty/ceremony/blob/main/README.md)
|
|
is the doctrine, `.github/workflows/release.yml` here is the ≤20-line
|
|
caller pinning it, and the guards run in `ci.yml` from the same pin.
|
|
Bare `X.Y.Z` tags, no `v`; the tag's source tarball is the package
|
|
`install.sh` downloads — rig ships no other artifact.
|
|
|
|
What stays rig's is the **drill** — the real-hardware gate before the
|
|
handoff of a release PR, run by `drill/drill.sh` (#105): `rig bootstrap`
|
|
converging the machine to its role twice with the second run diffed empty,
|
|
`test/db-integration.sh`, the runner lifecycle against a fork, a coolify
|
|
install. Rig's drill asserts **convergence** (a machine reaches its role,
|
|
idempotently), it runs `--host yes` with `BOX_REF=release/<box-version>` so
|
|
it exercises the box that will actually ship, and drills that share a
|
|
substrate share **one run ID** so the per-repo records can be joined after
|
|
the fact. The full meaning — the fixed candidate-ref pinning that dissolves
|
|
the box↔rig recursion, the per-version record files, the waiver rule — is
|
|
[`drills/README.md`](drills/README.md); the `drill-recorded` guard enforces
|
|
the record on every release tree.
|
|
|
|
## Labels — who sets what
|
|
|
|
The taxonomy and state machine are
|
|
[`.ceremony/LABELS.md`](.ceremony/LABELS.md); rig's `scope:*` rows live in
|
|
`.github/labels.conf` (reconciled by the labels caller) and their path map
|
|
in `.github/labeler.yml`. What matters day to day is who sets each kind —
|
|
most of it is machinery, and hand-moving a machine-owned label just gets
|
|
corrected on the next pass:
|
|
|
|
| Labels | Set by |
|
|
|---|---|
|
|
| `state:*` | the labels workflow ([.github/workflows/labels.yml](.github/workflows/labels.yml)) — recomputed from GitHub's own facts on PR events (label changes included) and every 15 minutes. Machine-owned, with one exception: the author sets `state:needs-human` at handoff and the workflow reconciles it. Otherwise never by hand. Exactly one per PR: *whose ball is it.* |
|
|
| `blocker:*` | the same workflow, from the same facts — *what is in the way.* Any number per PR, or none. Never by hand: applying one does not stop a merge, and removing one does not unblock anything. Fix the thing and the next sweep drops the label. |
|
|
| `stale` | the same workflow — 48h without commits, comments, or reviews. `blocked` PRs are exempt: they are quiet legitimately. |
|
|
| `scope:*` on PRs | actions/labeler, from the changed paths ([.github/labeler.yml](.github/labeler.yml)). Additive — you may add more, the machine won't remove them. |
|
|
| `scope:*` on issues | you, when opening or triaging — issues have no paths to derive from. |
|
|
| `blocked`, `release` | you — automation never guesses intent. |
|
|
| `merge-next` | you or the agent owning the queue. Which PR lands first is a judgement about how they conflict, so the workflow never sets it — it only **clears** it, the moment the PR stops being something a human could merge. |
|
|
| `bug` / `enhancement` / `documentation` | you, on issues only — a PR's type already lives in its title. |
|
|
|
|
## Issues
|
|
|
|
Give issues the same care as PR titles: say the surface in the title, apply a
|
|
`scope:` label and a type label (`bug` / `enhancement` / `documentation`) when
|
|
you open one, and `blocked` when it waits on something — that is what keeps
|
|
the board navigable as the issue count grows.
|