# v6 manifest schema

Source of truth for v6 manifest validation rules. The v6 reader and verifier in `src/work-manifest.ts` implement against this document.

v6 layers a `phase_plan:` block on top of v5. Everything else (slice graph, gate state, escalation links) is inherited from v5 unchanged — see `templates/manifests/v5/SCHEMA.md` for the unchanged parts. This document describes the v6 delta and the parsing contract.

---

## 1. Why phase_plan

v5 modeled a single canonical workflow: every gate was either `pending`, `complete`, or `skipped` based on what the skill recorded. There was no place to declare *up front* "this whole feature collapses to commit-only delivery, no PR" or "test-plan runs in light mode for this prototype-iteration feature."

v6 separates planning from execution:

- **`phase_plan:`** — the **plan** for which phases run and at what rigor. Set during preflight (Step 1 of `/feature`, `/bugfix`, etc). Stable through execution.
- **`phases:`** — the **gate state** as execution proceeds. Mutated by skills; gate-passed booleans recorded after the matching skill runs.

The two blocks are complementary, not redundant. A phase with `phase_plan.X: active-light` still has `phases.X.gate-passed: true` once its (lighter) skill invocation completes.

---

## 2. Top-level changes from v5

| Field | v5 | v6 |
|---|---|---|
| `schema_version` | `"5"` | `"6"` |
| `phase_plan` | (absent) | **required** at top level |
| `phases` | required | required (unchanged — still tracks gate state per phase/sub-phase) |
| `slice_graph` | required by parser | optional (the v5 SCHEMA already documented this as optional; v6 aligns the parser) |
| Everything else | — | unchanged |

The `slice_graph` change is a parser fix: v5's SCHEMA §1 listed `slice_graph` as optional but the verifier rejected its absence. v6 manifests can ship without a slice graph — it is populated at codify, not at preflight. v4 and v5 manifests continue to require it (v4 synthesizes one; v5 always shipped one in practice).

v6 is a purely additive change. Existing v5 fields keep their shape and semantics. `phase_plan` is the only new top-level field, and it is required.

The two blocks coexist by design — `phase_plan` captures intent at preflight, `phases` records what skills actually accomplished. A manifest with `phase_plan.X: skipped` keeps the corresponding `phases.X.{gate}.gate-passed: false`; status fields document the discrepancy.

---

## 3. phase_plan structure

`phase_plan:` is a map. Keys are workflow milestone identifiers (phase names, gate names, or any string the workflow recognizes). Values are either:

- **Scalar form** — one of the allowed plan-status values
- **Object form** — `{ status: <value>, reason: <string> }` when a reason needs to be attached

```yaml
phase_plan:
  concept: skipped                                # scalar form
  wireframe: skipped
  prototype: skipped
  codify: skipped
  production-build: active
  test-plan:                                      # object form
    status: active-light
    reason: "typecheck + browser verify only — no test runner installed"
  uiux-review: active
  code-review-final: active
  deliver: active-commit-only
  onboarding: skipped
  gotchas: as-discovered
```

### 3.1 Allowed plan-status values

| Value | Meaning | Gate-passed expected? |
|---|---|---|
| `active` | Runs at full rigor | yes (skill invocation required) |
| `active-light` | Runs with reduced rigor (e.g. typecheck-only test phase, no test-runner suite) | yes (skill invocation required) |
| `active-commit-only` | Runs but truncated to a partial deliverable (e.g. commit on existing branch instead of opening a PR) | yes (skill invocation required) |
| `skipped` | Does not run; downstream consumers MUST handle absence | no — stays `false` |
| `as-discovered` | Runs opportunistically when triggered; not scheduled | when triggered |
| `complete-inline` | Was done inline (often before the manifest existed) without skill invocation | no — stays `false`; status field documents the work |

The verifier rejects any other value with `E_BAD_PHASE_PLAN_STATUS`.

### 3.2 Key naming — strict enum per work type

Keys are validated against a per-work-type allow-list. An unknown key produces `E_UNKNOWN_PHASE_PLAN_KEY` with a "did you mean?" suggestion when a near-match exists.

This was a convention through early v6 — the verifier accepted any string and a `W_UNKNOWN_PHASE_PLAN_KEY` warning surfaced typos. Dogfood signal (a misspelled `prototpe` parsed fine but broke `rules/common/skill-selection.md`'s mode-detection read of `phase_plan.prototype`) drove the 2026-05-17 promotion to a hard error.

#### Allowed keys per work type

Commands MUST use these keys (kebab-case, match the templates verbatim). The list in `src/work-manifest.ts` (`ALLOWED_PHASE_PLAN_KEYS`) is the source of truth — this table must stay in sync.

| Work type | Allowed `phase_plan` keys |
|---|---|
| `feature` | `discover-codebase`, `concept`, `wireframe`, `plan-design-system`, `prototype`, `iterate`, `codify`, `worktree`, `production-build`, `test-plan`, `uiux-review`, `code-review-final`, `deliver`, `onboarding`, `gotchas` |
| `greenfield` | `discover-requirements`, `concept`, `wireframe`, `plan-design-system`, `prototype`, `iterate`, `codify`, `scaffold`, `production-build`, `test-plan`, `uiux-review`, `code-review-final`, `deliver`, `onboarding`, `gotchas` |
| `bugfix` | `debug-root-cause`, `production-build`, `code-review`, `deliver`, `gotchas` |
| `hotfix` | `debug-root-cause`, `production-build`, `smoke-tests`, `code-review-critical`, `deliver`, `gotchas`, `followup-ticket` |
| `refactor` | `discover-codebase`, `brainstorm`, `task-decompose`, `production-build`, `test-execution`, `assessment`, `deliver`, `gotchas` |

`plan-design-system` is a workflow-specific gate that runs at Step 4.5 of `/feature` and `/greenfield` (post-wireframe-lock, pre-prototype). It is not in `references/common/phases.md` because it is a step within Phases 2-3 rather than its own canonical phase. Backend-only / CLI work skips it (`status: skipped` with a reason); frontend work uses `status: active`.

The seven canonical phase names from `references/common/phases.md` (`concept`, `wireframe`, `prototype`, `iterate`, `codify`, `production-build`, `deliver`) MUST match the canonical spelling when used. Other keys are workflow-specific gates or sub-phases.

#### Adding a new key

To extend a workflow with a new phase_plan key:
1. Add it to the appropriate `ManifestType` entry in `ALLOWED_PHASE_PLAN_KEYS` (`src/work-manifest.ts`).
2. Add it to the table above.
3. Update the relevant command's preflight planner so it emits the new key.

### 3.3 gate-passed discipline

A manifest with `phase_plan.X: active` MUST still earn `phases.X.gate-passed: true` through skill invocation — the `gate-enforcer.sh` hook enforces this. Plan-status `active` does NOT pre-authorize gate passage.

Conversely, `phase_plan.X: skipped` / `complete-inline` SHOULD keep `phases.X.gate-passed: false`. The plan-status documents intent; the gate field documents whether the rigor was applied. Setting `gate-passed: true` on a skipped phase trains the manifest to lie about gate state.

This convention is enforced by skills (they read phase_plan and choose not to set gate-passed on skipped phases). The verifier does NOT cross-validate plan-status against gate-passed — that's a hot path for false positives and the convention is well-understood.

---

## 4. Migration from v5

A v5 manifest read by the v6 parser is accepted as-is. The parser returns `schema: 'v5'` and the synthesized in-memory manifest has `phase_plan: undefined`. Tools that need a phase_plan can synthesize a default: every phase in `phases.{block}.{gate}` becomes `phase_plan.{gate}: active`.

`/forge-evolve` (when it lands) will offer to rewrite v5 manifests on-disk into v6 shape, prompting for plan-status per phase. v5 manifests parse forever; no forced migration.

---

## 5. Verifier interface delta

Added `ErrorCode`:

```ts
type ErrorCode = 
  | ...v5 codes...
  | 'E_BAD_PHASE_PLAN_STATUS'    // phase_plan value not in §3.1 enum
  | 'E_BAD_PHASE_PLAN_SHAPE';    // phase_plan not a map; or object-form value missing status
```

Added `Manifest` field:

```ts
export type PhasePlanStatus = 
  | "active" | "active-light" | "active-commit-only" 
  | "skipped" | "as-discovered" | "complete-inline";

export interface PhasePlanEntry {
  status: PhasePlanStatus;
  reason?: string;
}

export interface Manifest {
  // ...v5 fields...
  phase_plan?: Record<string, PhasePlanEntry>;   // normalized at parse time
}
```

At parse time, scalar-form values are normalized to object-form `{ status: <value> }` so downstream code reads a uniform shape.

`schema` in the return value gains a third value: `'v4' | 'v5' | 'v6'`.

---

## 6. Artifacts convention

The Manifest interface includes an optional top-level `artifacts:` map. v6 standardizes a convention for tracking phase outputs (paths + lock timestamps):

```yaml
artifacts:
  concept:
    deck_path: decks/{name}/slides.md
    locked_at: "2026-05-12T10:00:00Z"
  wireframe:
    html_path: pocs/{name}-wireframe/index.html
    locked_at: "2026-05-12T11:00:00Z"
  prototype:
    stack: vite-react                            # set at preflight; one of: vite-react | nextjs | existing-repo | python-fastapi | cli | go-service | none
    path: pocs/{name}-prototype/
    scaffold_status: complete                    # written by build-prototype
    locked_at: "2026-05-12T12:00:00Z"            # written by iterate-prototype (sole writer)
  codify:
    locked_at: "2026-05-12T13:00:00Z"   # no path — slice_graph is the artifact
  production-build:
    locked_at: "2026-05-12T16:00:00Z"   # no path — slice terminal state is the artifact
```

**Lock signal.** Presence of `artifacts.{phase}.locked_at` is the canonical "phase locked" signal. Skills that gate on a prior phase being locked (e.g. `harden` requires prototype locked, `build-prototype` requires wireframe locked) check this field rather than a separate status enum.

**Path fields are phase-specific:**

| Phase | Field | Notes |
|---|---|---|
| concept | `deck_path` | Path to the slide deck |
| wireframe | `html_path` | Path to the single-HTML wireframe |
| prototype | `stack` + `path` + `scaffold_status` | `stack` selects the prototype technology — one of `vite-react` (default for React projects), `nextjs`, `existing-repo`, `python-fastapi`, `cli`, `go-service`, `none`. Set at `/feature` or `/greenfield` preflight from `project.stack:` + work-item description; user confirms or overrides. `path` written by `build-prototype` (Phase 3) after scaffold click-through; `scaffold_status: complete` flags initial-scaffold readiness. `locked_at` is written by `iterate-prototype` (Phase 4) on convergence — **single-writer** to avoid race conditions. If `stack: none`, `phase_plan.prototype: skipped` must match. |
| codify | (no path) | The slice_graph + aiwiki/architecture/ files ARE the artifacts |
| production-build | (no path) | Slice terminal state + the production code ARE the artifacts |
| deliver | `pr_urls` | Array of PR URLs created |

The parser does not validate the artifacts shape — this is a documentation convention. Skills follow it; the verifier ignores it.

---

## 7. Reserved evolutions

Out of scope for v6.0; slots reserved so we don't break the schema again:

- **Plan-status `deferred`** (run later as a follow-up work item) — the current `escalated_from` / `successor_path` fields cover the multi-manifest case; `deferred` is a single-manifest variant we may need.
- **Phase ordering hints** — currently implicit (insertion order in YAML map). Add an explicit `order:` field if real usage shows fragility.
- **Strict artifact validation** — per-phase required fields could be enforced if real usage shows skills writing inconsistent shapes.
