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>
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:
-
Conflation of
cycleandmilestone.README.md("AILang calls a cycle a milestone") andtemplates/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. -
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_idandmilestone_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:
- Complete — every cycle filed under the milestone is
auditdrift-clean (or its drift explicitly ratified), and the milestone container has no open iterations / issues remaining. - 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 openbugfindings;friction/spec_gapfindings 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)toaudit --(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
auditand no/bossdone-state closes a milestone.
audit/SKILL.md
- Step 2 table: "Audit can close" → drift-gate wording.
auditclears 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_idandmilestone_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 indocs/pipeline.mdrather 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.mdgate instead of restating the formula. Clarify that a/bossdone-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 ≡ milestoneconflation (the "AILang calls a cycle a milestone" lines). Cycle = a pipeline round; milestone = a tracker container spanning many cycles, closed only when complete ∧ functional. fieldtestrow: 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
vocabularytable (cycle= "top-level work unit", withmilestoneas an example value) and the minimal-profile example (cycle: milestone). Correct both — documentcycleas the pipeline round and add themilestonevocabulary field as the tracker container. - Document the milestone-fieldtest phase in the
pipelinesection alongside the existingfieldtestphase.
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
cyclewithmilestone; the only authoritative gate definition lives indocs/pipeline.md, andaudit/fieldtest/bossreference it without restating it. audit/SKILL.mdcarries the new Red Flag and drift-gate wording; its close-commit example is annotated as drift-clean.fieldtest/SKILL.mddocuments both carrier scopes and the milestone-level skip rule.templates/project-profile.ymlparses (YAML) with the newmilestone:vocabulary field and the milestone-fieldtest phase, anddocs/profile-schema.mdstays consistent with it.