diff --git a/BUILDER.md b/BUILDER.md index 0ab03f1..9066716 100644 --- a/BUILDER.md +++ b/BUILDER.md @@ -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 diff --git a/RELEASES.md b/RELEASES.md new file mode 100644 index 0000000..304ca8a --- /dev/null +++ b/RELEASES.md @@ -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 `. +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 ` 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. diff --git a/TRIAGE.md b/TRIAGE.md index 840c09a..75e1b6d 100644 --- a/TRIAGE.md +++ b/TRIAGE.md @@ -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 diff --git a/changelog.d/248.md b/changelog.d/248.md new file mode 100644 index 0000000..e33c4f6 --- /dev/null +++ b/changelog.d/248.md @@ -0,0 +1,3 @@ +### Added + +- Document the optional, operator-ruled release-epic flow for governed repositories. diff --git a/docs/CONSUMERS.md b/docs/CONSUMERS.md index 99e1ce2..437d64b 100644 --- a/docs/CONSUMERS.md +++ b/docs/CONSUMERS.md @@ -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