c2e7f55651
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>
242 lines
10 KiB
Markdown
242 lines
10 KiB
Markdown
# 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 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 <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.
|