Files
Skills/docs/specs/2026-05-31-milestone-close-gate-design.md
T
Brummel c2e7f55651 docs: design spec for milestone-close gate (cycle ≠ milestone)
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) <noreply@anthropic.com>
2026-05-31 12:01:55 +02:00

10 KiB

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 <X> 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 gateaudit'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 <X> 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.