docs: add release-management doctrine

This commit is contained in:
Andriujose 2026-08-03 17:29:15 +00:00
parent d225c68fa4
commit ab60709f49
5 changed files with 112 additions and 2 deletions

View file

@ -11,6 +11,8 @@ 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.
- In a repository that adopts version epics, read [RELEASES.md](RELEASES.md)
before choosing among release-window members.
- **Your own red head outranks a new claim.** A failing check at the head
of a PR you authored is picked up **before claiming another issue**
repairing your own red PR comes ahead of new work, which is why the

100
RELEASES.md Normal file
View file

@ -0,0 +1,100 @@
# Release management
This file describes the release-management pattern available to governed
repositories. Adoption is per repository and operator-ruled: a repository
without version epics is not out of compliance. A repo-local roadmap is the
map; each epic remains the source of truth for its own release. Where an older
repo-local description differs from this file, this file governs.
## The ladder
Represent each planned release with one version epic. The epic is the working
surface for that release: it states the goal, names the members, and records
the ordered waves as checklists. Keep the machine-readable progress checklist
under the exact heading `## Task list`; the issue-flow sweep reads that heading
when it decides whether to nudge triage about a completed epic.
Keep a short repo-local roadmap beside the epics. The roadmap shows the whole
ladder and points to each working surface; it does not duplicate the live
member lists or ordering. crew's roadmap discussion [heavy-duty/crew#338](https://github.com/heavy-duty/crew/discussions/338)
maps the ladder whose `0.1.2` working surface moved from the crufty ledger
[heavy-duty/crew#162](https://github.com/heavy-duty/crew/issues/162) to
[heavy-duty/crew#346](https://github.com/heavy-duty/crew/issues/346).
## Gates
Each version epic declares the predecessor that opens it. Special ordering —
a double gate or an out-of-chain gate — is written explicitly on that epic;
there is no hidden global schedule. Shipping closes the current epic, and its
close is the signal for triage to open the next release-init cycle by hand.
Version epics carry `epic`, not a queue label: `ready` offers work to builders,
and builders never pick epics.
The gate orders windows, not their contents. Members enter a release only by
decision during release-init. The double gate on heavy-duty/crew#163 and the
out-of-chain track on [heavy-duty/crew#348](https://github.com/heavy-duty/crew/issues/348)
are worked examples of exceptions declared where they apply.
## Release-init
The preceding epic's close is the trigger. Triage then runs five steps:
1. Mint the epic's “to mint when this arc opens” list together with findings,
deferred work, and discussion outcomes accumulated since the epic was
written. Each member initially declares `Blocked by <the epic>`.
2. Graph hard `Blocked by` edges and same-file clusters on the epic.
3. Write the waves into the epic body as checklists in claim order, with a
separate verification lane and the progress view under `## Task list`.
4. Ask the operator to bless the order, then have triage open the first wave
by applying the flip mechanics below. The operator's blessing is the one
step this chain never automates.
5. Ship through the repository's cut process, close the epic, and treat that
close as the trigger for the next window.
heavy-duty/crew#346 is the worked wave plan; its graph made both hard edges
and shared-file contention visible before builders entered the queue. If init
finds no work worth minting, the operator either folds the empty window into a
later release or skips the version, recording that ruling on the epic before
closing it unshipped.
## One primary window, declared parallel tracks
Run one primary release window by default. A cut takes whatever has landed, so
interleaving unrelated windows blurs both the release story and the evidence
behind it. Gates open windows; they do not silently admit members, so builders
still see one deliberately ordered queue.
The operator may declare a parallel track at init when its footprint is
disjoint from the primary window: another repository, another artifact, or
provably non-overlapping clusters. The declaration names the boundary and any
bridge work that must rejoin the primary. [heavy-duty/crew#348](https://github.com/heavy-duty/crew/issues/348)
is the worked example: its app and artifact form a parallel track while its
small crew-side bridge remains in the primary window.
## Flip mechanics
To admit a member, strike its live `Blocked by <the epic>` declaration and
swap `blocked` to `ready` in the same edit. Never preserve history by negating
the marker phrase — the blocker parser unions declarations even when prose
says they no longer apply. Preserve the old text only after striking or
rewriting the parseable clause, then verify the parser's resulting set.
Release membership is a decision, never a sweep default. Triage performs each
flip only after the operator blesses the wave; the issue-flow sweep may resolve
ordinary issue dependencies, but it does not choose a release's contents.
heavy-duty/crew#346 records the member-by-member flip that opened its first
wave.
## The ledger pattern
When a release epic has become too crufty to remain a clear working surface,
create a replacement and treat the old epic as a ledger. Do not close the old
epic until every live member declaration points at the replacement and the
blocker parser verifies the new set. Closing early can release every member
that still names the old issue.
The [heavy-duty/crew#162](https://github.com/heavy-duty/crew/issues/162) to
[heavy-duty/crew#346](https://github.com/heavy-duty/crew/issues/346)
transition is the worked example: all member declarations were re-pointed and
parse-verified before #162 closed; #162 remains the historical record while
#346 is the release's working surface.

View file

@ -122,6 +122,7 @@ label): the approach, the decisions, the constraint list, and a
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.
Repositories that adopt version epics follow [RELEASES.md](RELEASES.md).
## Backlog hygiene

3
changelog.d/248.md Normal file
View file

@ -0,0 +1,3 @@
### Added
- Document the optional, operator-ruled release-epic flow for governed repositories.

View file

@ -550,14 +550,18 @@ when the pinned taxonomy declares a core label the repository lacks.
Machinery is consumed by reference — GitHub fetches the workflows and
actions above from the pin at run time — but documents have no runtime: an
agent reads the working tree it stands in. So the agent-facing doc set
(ceremony's `docs/VENDORED.txt`: AGENTS.md, TRIAGE.md, BUILDER.md,
REVIEWER.md, LABELS.md) is vendored into each consumer at **`.ceremony/`**,
declared by ceremony's `docs/VENDORED.txt` is vendored into each consumer at **`.ceremony/`**,
byte-identical to ceremony at the pin, plus a generated `.ceremony/README.md`
marking the directory machine-managed. `actions/docs-sync` owns the copy:
`--fix` writes it (and deletes what the manifest dropped — mirror means
mirror), `--check` re-diffs it in CI on every PR, so a hand edit or a stale
pin goes red instead of quietly governing.
`RELEASES.md` joins that mirror with the first tag carrying ceremony#248.
It is **unreleased** until that tag exists: consumers add
`.ceremony/RELEASES.md` only with the ordinary pin bump and re-sync, never by
copying it ahead of their pinned doctrine set.
The consumer's ci.yml gains the guard alongside the others:
```yaml