From c2e7f5565169e8b64651f08aa9eee32836ad6321 Mon Sep 17 00:00:00 2001 From: Brummel Date: Sun, 31 May 2026 11:57:41 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20design=20spec=20for=20milestone-close?= =?UTF-8?q?=20gate=20(cycle=20=E2=89=A0=20milestone)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Separate `cycle` (a pipeline-graph round) from `milestone` (a Gitea tracker container that closes only when complete AND functional). `audit` proves drift-clean, never functional, so no audit signal — and no `/boss` done-state — may close a milestone. Introduces a milestone-close gate (complete ∧ functional) defined once in docs/pipeline.md, and a milestone-wide fieldtest as a carrier-scope variant of the existing fieldtest skill. The milestone fieldtest runs curated end-to-end scenarios derived top-down from the milestone's promise — proving the shipped implementation delivers what the milestone as a whole promised, not a mechanical union of per-cycle axes. The actual tracker close stays a manual act; this spec only defines when a milestone is closeable. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../2026-05-31-milestone-close-gate-design.md | 241 ++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 docs/specs/2026-05-31-milestone-close-gate-design.md diff --git a/docs/specs/2026-05-31-milestone-close-gate-design.md b/docs/specs/2026-05-31-milestone-close-gate-design.md new file mode 100644 index 0000000..a4f692d --- /dev/null +++ b/docs/specs/2026-05-31-milestone-close-gate-design.md @@ -0,0 +1,241 @@ +# Milestone-close gate: separating `cycle` from `milestone` + +**Date:** 2026-05-31 +**Status:** approved (design) +**Topic:** make `audit` unable to imply a milestone is finished; +add a milestone-wide fieldtest as the closing gate. + +## Problem + +`audit` runs at cycle close and emits a "clean" signal whose +commit body reads `cycle tidy (clean)`. `audit` only proves +*drift-clean* — the code matches the design ledger. It is blind +to whether the work is *functional* from a downstream +consumer's point of view; that is exactly what `fieldtest` +measures, and `fieldtest` runs *after* `audit`. + +Two defects follow: + +1. **Conflation of `cycle` and `milestone`.** `README.md` + ("AILang calls a cycle a *milestone*") and + `templates/project-profile.yml:41` (`cycle: # what a + top-level work unit is called`) treat a cycle as the + top-level work unit. It is not. A **milestone** is a tracker + (Gitea) container that closes only when the work is + *completely implemented and functional*. A **cycle** is one + round in the pipeline graph — a step in the loop. A milestone + spans potentially many cycles. + +2. **An audit-clean signal can leak into a milestone close.** + Because `audit`'s "clean" reads like "done", an orchestrator + can be led to close a Gitea milestone on the strength of an + audit, before any fieldtest has demonstrated the work is + functional. No skill closes a milestone today, so this is a + conceptual hazard rather than a code path — but the wording + actively invites the mistake. + +The fix is **not** to swap the `audit → fieldtest` order: +`fieldtest` needs the drift-clean state to test against, so +`audit` must stay first. The fix is to **decouple the gate** — +`audit`'s "clean" never closes anything, and a separate, +explicitly defined milestone-close gate requires both +completeness and functional validation. + +## Conceptual model + +### Two distinct axes + +- **Cycle** — one round in the pipeline graph + (`brainstorm → planner → implement → audit → [per-cycle + fieldtest]`). A cycle close is an internal loop step. Many + cycles per milestone. +- **Milestone** — a tracker (Gitea) container. Closes **only** + when *complete* (every cycle drift-clean / ratified, no open + iterations) **and** *functional* (milestone fieldtest green). + +### Three disentangled "clean" terms + +| Term | Owner | Means | Closes a milestone? | +|------|-------|-------|---------------------| +| `drift-clean` | `audit` | code matches the ledger | never | +| cycle functionally validated | per-cycle `fieldtest` | cycle-local surface usable | never | +| milestone closeable | milestone-close gate | complete ∧ functional | yes (manual act) | + +### Two fieldtest scopes + +The milestone fieldtest is a **scope variant** of the existing +`fieldtest` skill — same `fieldtester` agent, a different +carrier. No new skill; keeps the surface DRY. + +- **Per-cycle fieldtest** (existing, unchanged): carrier carries + `cycle_id` / `cycle_scope`; dispatched after a + surface-touching cycle's audit; catches cycle-local friction + early; optional / orchestrator-dispatched as today. +- **Milestone fieldtest** (new variant): carrier carries + `milestone_id` and `milestone_promise` — the milestone's + stated goal / acceptance in the tracker (and its spec, if one + exists). The fieldtester derives a small set of **curated + end-to-end scenarios** that, run as a downstream consumer, + prove the shipped implementation delivers **what the milestone + as a whole promised**. Scenarios are chosen *top-down from the + milestone's promise*, not assembled bottom-up as the + mechanical union of per-cycle axes — the per-cycle fieldtests + already cover axis-local surface; the milestone fieldtest's + job is to demonstrate the promise end to end. Its green status + roll-up is the closing gate. + +## The milestone-close gate (authoritative) + +A milestone may be closed in the tracker only when **both** legs +hold: + +1. **Complete** — every cycle filed under the milestone is + `audit` drift-clean (or its drift explicitly ratified), and + the milestone container has no open iterations / issues + remaining. +2. **Functional** — the milestone fieldtest has run its curated + end-to-end scenarios against the milestone's promise and its + status roll-up is `clean`: every scenario demonstrably + delivers what the milestone promised; no open `bug` findings; + `friction` / `spec_gap` findings either resolved or + explicitly ratified into the design ledger. + +No `audit` result, and no `/boss` done-state, closes a milestone +on its own. `/boss` done-state means "this `/boss` run has +nothing left to dispatch" — it is **not** a milestone close. + +The actual close (`tea milestone close`, or the tracker +equivalent) stays a **deliberate human / orchestrator act**, +performed only after the gate reads green. This design defines +*when* a milestone is closeable; it does **not** mechanise the +close. (`issue/SKILL.md` is out of scope here.) + +### Edge cases + +- **Non-surface milestone.** A milestone whose entire scope is + internal (refactor, infra, docs) touches no user-visible + surface. The milestone fieldtest is then *not applicable* + (same skip logic as the per-cycle fieldtest, lifted to + milestone level). The "functional" leg is satisfied vacuously; + the "complete" leg (audit-clean across cycles) suffices. +- **Single-cycle milestone.** Nothing special: the milestone + fieldtest still runs (if surface-touching), proving the + milestone's promise end to end even though that promise + happened to ship in a single cycle. The per-cycle and + milestone fieldtests may coincide in surface but remain + conceptually distinct gates — axis-local validation versus + promise-level proof. +- **Ratified drift.** A cycle whose drift was ratified + (`--update-baseline` + ratify paragraph) counts as satisfying + the "complete" leg for that cycle — ratification is the + documented form of "complete". + +## Per-file changes + +Single-source discipline (consistent with the recent +carrier/handoff single-sourcing): the gate is **defined once** +in `docs/pipeline.md`; every other file **references** it rather +than restating it, so the files cannot drift. + +### `docs/pipeline.md` (single source) + +- ASCII graph: rename `audit --(clean)` to `audit + --(drift-clean)`; annotate `[cycle close]` explicitly as a + loop step, not a milestone end. +- New subsection **"Cycle vs. milestone"** — defines the two + axes authoritatively (a milestone spans many cycles; a cycle + is one loop round). +- New subsection **"Milestone-close gate"** — the authoritative + gate definition above (complete ∧ functional), names the + milestone fieldtest as a mandatory precondition for + surface-touching milestones, and states plainly that no + `audit` and no `/boss` done-state closes a milestone. + +### `audit/SKILL.md` + +- Step 2 table: "Audit can close" → drift-gate wording. `audit` + clears the *cycle drift check*; it does not close the cycle or + the milestone. +- Close-commit wording (`cycle tidy (clean)`): kept, but + annotated explicitly as *drift-clean, not a milestone signal*. +- New **Red Flag**: "Declaring a milestone closeable on the + strength of an audit-clean, while the milestone fieldtest has + not run green." Cross-reference the gate in `docs/pipeline.md`. +- Overview: clarify `audit` ↔ cycle close (never milestone). + +### `fieldtest/SKILL.md` + +- Document the second trigger variant — the **milestone + fieldtest** (milestone-wide, end-to-end): carrier carries + `milestone_id` and `milestone_promise`; the fieldtester + derives curated end-to-end scenarios from the milestone's + promise (top-down, not a union of per-cycle axes) and proves + the shipped implementation delivers it; its green status + roll-up is the closing gate. Reference the gate in + `docs/pipeline.md` rather than restating it. +- The "audit first" precondition stays, for both variants. +- The milestone-fieldtest skip logic mirrors the per-cycle one, + lifted to milestone level (non-surface milestone → exempt). + +### `boss/SKILL.md` + +- Done-state: reference the `docs/pipeline.md` gate instead of + restating the formula. Clarify that a `/boss` done-state is + **not** an automatic milestone close — the milestone close + needs the milestone fieldtest green and is a manual act. + +### `README.md` + +- **Root fix**: correct the `cycle ≡ milestone` conflation + (the "AILang calls a cycle a *milestone*" lines). Cycle = a + pipeline round; milestone = a tracker container spanning many + cycles, closed only when complete ∧ functional. +- `fieldtest` row: per-cycle fieldtest stays optional; note the + milestone fieldtest as the mandatory gate before a + surface-touching milestone closes. + +### `templates/project-profile.yml` + +- Fix the `cycle:` comment ("top-level work unit" is wrong — a + cycle is the pipeline round). +- Add a `milestone:` vocabulary field for the tracker container. +- Add the milestone-fieldtest phase to the pipeline config (so a + project can name / gate it), consistent with the existing + `pipeline:` configuration model. + +### `docs/profile-schema.md` + +- Same root conflation lives here: the `vocabulary` table + (`cycle` = "top-level work unit", with `milestone` as an + example value) and the minimal-profile example (`cycle: + milestone`). Correct both — document `cycle` as the pipeline + round and add the `milestone` vocabulary field as the tracker + container. +- Document the milestone-fieldtest phase in the `pipeline` + section alongside the existing `fieldtest` phase. + +## Scope boundaries + +**In scope:** the gate definition and the wording/decoupling +across `docs/pipeline.md`, `audit`, `fieldtest`, `boss`, +`README.md`, `templates/project-profile.yml`; the milestone +fieldtest as a carrier-scope variant of the existing skill. + +**Out of scope:** mechanising the tracker close (no +`issue/SKILL.md` change, no automated `tea milestone close`); +reordering `audit → fieldtest`; any change to the per-cycle +fieldtest's existing behaviour beyond naming it "per-cycle". + +## Verification + +- Grep sweep: no remaining file equates `cycle` with + `milestone`; the only authoritative gate definition lives in + `docs/pipeline.md`, and `audit` / `fieldtest` / `boss` + reference it without restating it. +- `audit/SKILL.md` carries the new Red Flag and drift-gate + wording; its close-commit example is annotated as drift-clean. +- `fieldtest/SKILL.md` documents both carrier scopes and the + milestone-level skip rule. +- `templates/project-profile.yml` parses (YAML) with the new + `milestone:` vocabulary field and the milestone-fieldtest + phase, and `docs/profile-schema.md` stays consistent with it.