Phase naming — name the end state, not the activity¶
A phase label should state the condition that is true once the phase is complete. Activity labels ("Implement X", "Remove Y", "Migration work") describe effort spent and invite the question "is it done?" to be argued. Outcome labels are testable: you can ask "is this true in production yet?" and get yes or no.
This pairs directly with the production-gated status rule — a step is done only when
deployed and accepted, not when development finishes.
Rules¶
- Write the label as an assertion of a future state. Present tense, declarative.
- Make it falsifiable. A reader should be able to check it against a running system.
- Number phases in delivery order so the phase number is also its sequence position. Do not use letters — they imply alphabetical order that rarely matches dependency order.
- Do not name a phase after the work it contains. If the label starts with a verb like Implement, Build, Remove, Update, Migrate, or Refactor, rewrite it.
- Keep it to roughly 6–10 words so it fits the phase status table.
- Watch for labels that overreach. A label must be true at that phase's exit, not at some later cutover.
Rewrite pattern¶
| Instead of (activity) | Write (end state) |
|---|---|
| Direct-read implementation | Reads are served directly from the new source |
| Seven-object data contract | All seven inputs are available as published contracts |
| Remove the legacy warehouse from the API | Every input has a read path on the new platform |
| Production identity provider cutover | Access runs on a production service principal |
| Parity acceptance and cutover | Production runs on the new source with accepted parity evidence |
| Discovery-script follow-up | No legacy-only tooling remains |
Worked example¶
From a warehouse cutover on Plumbing (docs/wip/warehouse-cutover-plan.md), six phases,
twenty steps:
"groups": [
{ "id": "phase-1", "label": "Phase 1 - Pricing reads are served directly from the new warehouse", "effort": 13, "status": "in_progress", "effort_remaining": 3 },
{ "id": "phase-2", "label": "Phase 2 - All seven inputs are available as published contracts", "effort": 13, "status": "in_progress", "effort_remaining": 8 },
{ "id": "phase-3", "label": "Phase 3 - Every Plumbing input has a read path on the new platform", "effort": 13, "status": "not_started" },
{ "id": "phase-4", "label": "Phase 4 - Warehouse access runs on a production service principal", "effort": 8, "status": "blocked" },
{ "id": "phase-5", "label": "Phase 5 - Production runs on the new platform with accepted parity evidence", "effort": 8, "status": "not_started" },
{ "id": "phase-6", "label": "Phase 6 - No legacy-only tooling remains", "effort": 2, "status": "not_started" }
]
Two things this example fixed that are easy to miss:
- Phase 3 was originally "Remove the legacy warehouse from the Plumbing API". That overreached — the legacy warehouse is not gone until the Phase 5 cutover. The honest exit condition is that a read path exists for every input, with the switch not yet thrown.
- Phases were originally lettered A–F in a non-delivery order (A → E → F → C → B → D was the real dependency chain). Renumbering to 1–6 in delivery order removed the need for a separate "read these out of order" caveat.
Sequence and dependency¶
When phases are numbered in delivery order, add a short Delivery sequence block after the phase status table containing:
- a Mermaid dependency graph whose node labels repeat the end-state wording, and
- a table with columns
Phase | True when complete | Blocked until | Owner.
The True when complete column lets that table stand alone without scrolling back to the
phase status table.
Steps versus phases¶
Step labels may stay task-shaped (Add the warehouse SQL connector) — they are units of work
assigned to a person. Phase labels are commitments to a state and are what stakeholders read,
so only phases require the end-state form.
See also: task description rules.