# Editing the workflow `harness/workflow.yaml` is the **single source of workflow truth** (spec F2, §5.3). `phase.py`, the gate (`nsp-gate.js`), and the skill all read from it. There are **no mirrored constants** in JS or Python — that was the ChironeWp3 drift bug (`PHASE_NAMES` truncated to 7 entries in JS, F8/datamart silently dropped). Editing this one file is the only place the workflow changes. ## Structure ```yaml schema_version: 1 phases: - id: F3 # stable id (referenced by the gate) name: riscrittura # human label (nsp phase show / gate UI) advance: kind:phase # how the phase advances (see below) prerequisites: # what must hold before advancing (gate checks these) - decision_exists: question_rewritten artifacts_out: [question.md] # files produced; used by teardown on rollback (D15) ``` `decision_min_phase: auto` and `max_phase: auto` mean these are derived from the phase list (don't hard-code them). ## `advance` kinds | kind | meaning | |----------------------------|----------------------------------------------------------------| | `kind:phase` | advances on any phase-approved decision for this phase | | `auto_if_empty` | auto-advances if no decisions were made (F2 memoria) | | `auto_if_empty_or_skipped` | auto-advances if empty OR a `phase_skipped` decision exists | | `reviewer_decide` | requires an explicit reviewer decision (F4, F8) | ## `prerequisites` predicates A phase is "ready to advance" when ALL its prerequisites hold. Predicates: - `decision_exists: ` — a decision of that type exists (effective view). - `decision_subject_exists: [, ]` — e.g. a `phase_skipped` for `phase:6`. - `file_validates: [, ]` — e.g. `schema_linking.json` parses as a `SchemaLinking`. - `all_ctes_approved: true` — all CTE blocks approved (F6). - `any: [...]` / `all: [...]` — combine predicates. A decision type's **min phase** is derived: the earliest phase whose prerequisites reference it. So adding a prerequisite like `decision_exists: my_new_decision` to F5 automatically makes `my_new_decision` valid from phase 5. ## Common edits ### Add a phase Append a phase to `phases:`. `max_phase` becomes the new count automatically. Give it an `id`, `name`, `advance`, and (optionally) `prerequisites` / `artifacts_out`. ### Reorder / merge / skip phases Reorder the list; `max_phase` and `decision_min_phase` recompute. To make a phase skippable, set `advance: auto_if_empty_or_skipped` and let the gate emit a `phase_skipped` decision (predicate `decision_subject_exists: [phase_skipped, "phase:N"]`). ### Add an artifact Add it to a phase's `artifacts_out`. On rollback (`teardown_to_phase`), artifacts produced by phases **after** the target are deleted; the target phase's artifacts are preserved. This is the D15 fix for the orphaned-CTE-blocks-finalize bug. ## After editing ```bash nsp phase meta --json # confirm the new shape (max_phase, phases, artifacts_out) pytest # L1 coherence smoke re-derives from the new workflow.yaml ``` No JS or Python constants to update — that's the point.