ADR-013: Plan Lifecycle and Status Vocabulary
On this page
Context
docs/modules/ROOT/pages/plans/*.adoc holds 96 plan files. Nothing enforces Status-table upkeep, archival of completed plans, or a single vocabulary for the Status column. Three concrete drift patterns surfaced on 2026-04-22 during a priority-2 planning audit:
-
Status-table drift. Three plans (MR !104, !106, !107) were merged with code but their Status tables still read "Not started" for every row. The audit tool read the tables, not the code, and reported 0/N complete. Cleaned up in MR !111, but the underlying cause — no enforcement — remained.
-
Potential Improvements as idea graveyard. Every plan has a "Potential Improvements" section. Across 95 plans, an estimated 28 of 31 deferred items listed in those sections have no GitLab issue, no owner, and no scheduled follow-up. The section normalizes dumping ideas into a document nobody re-reads after merge.
-
Errata as first-resort for deviation. The precommit protocol’s Q4 ("deviated from plan? document why in errata") incentivizes adding an errata line rather than fixing the plan in place. 42 of 95 plans now have errata or "known-gaps" sections — typically describing scope or design changes that should have updated the plan’s Design section instead.
External convention review (2026-04-23) explicitly flagged all three patterns as drift-generators. Tier 1 documents already mandate an uppercase COMPLETE / DEFERRED vocabulary + archival of completed plans, but neither is enforced and practice has diverged (610 "Complete", 191 "✓", 85 "Done", 45 "complete", 0 "COMPLETE").
The Tier 1 template may tighten this upstream. Until then, Canopy needs its own enforceable rule using the vocabulary the repo actually uses — otherwise every convention edit produces a migration-day forcing function.
Decision
1. Canonical Status vocabulary
Every Status-table row’s Status column MUST begin with one of the following tokens (case-insensitive first-token match, whitespace-preserved):
-
Not started— default state for rows added to a new plan. -
In progress— step is being actively worked in an open MR. -
Done (YYYY-MM-DD) — …— step shipped; date + freeform detail required. Optional: reference the delivering MR (MR !N). -
Deferred (…)— step explicitly descoped; reason required in parentheses. -
Blocked (…)— step cannot proceed; blocker (external partner, spec pending, dependent plan) required. -
N/A— row added to the table for structural reasons but doesn’t apply.
Anything else (bare "Complete", "✓", "done", uppercase variants) is a lint violation. The canonical tokens match what 85%+ of existing plans already use; residual variants (the 191 "✓" and 45 lowercase "complete") will migrate as plans are touched.
2. Plan archival
Plans whose Status tables contain only Done / N/A rows MUST move to docs/modules/ROOT/pages/plans/archive/. Moving is a single operation — the file is relocated, the content is unchanged, the Antora xrefs break. Cross-references to archived plans update to the form xref:plans/archive/<plan-name>.adoc[…].
Archive is for audit trail, not discovery. A plan in archive/ is immutable in spirit: editing it should trigger asking "do we actually need a new plan?" rather than amending historical record.
3. Precommit protocol Q4 + Q6 amendments
Two precommit questions currently incentivize the drift:
Q4 (was): "Have you deviated from the plan? If so, document why in the plan’s errata section."
Q4 (now):
Have you deviated from the plan? If so:
Update the plan’s Design / Scope section to reflect what was built — the plan is a living spec, not an immutable record.
If the deviation produced follow-up work, file a GitLab issue and link it.
Errata sections are for genuinely post-hoc corrections (typos, citation errors) — not a dumping ground for "I built it differently."
Q6 (was): "Can this feature be improved? If so, document it in the plan’s 'potential improvements' section."
Q6 (now):
Can this feature be improved? If so, file a GitLab issue and link it. Plans are specifications, not backlogs — "Potential Improvements" sections in plans are no longer the correct home for deferred ideas.
Existing plans retain their "Potential Improvements" sections — no retroactive migration. New plans omit the section; implementers who find improvements during execution file issues instead.
4. Enforcement tooling
cargo xtask docs plan-lint validates every file under docs/modules/ROOT/pages/plans/ (excluding archive/):
-
Every
| <Step> | <Description> | <Status>row’s Status cell starts with one of the canonical tokens. -
File-level report lists violations with plan filename + row description (so the lint output is actionable without opening each file).
-
Exit non-zero on any violation. Wire into
cargo xtask validatewithallow_failure: trueat first (grandfathers the existing drift without blocking every MR). Promote to blocking in a follow-up once the existing violations are cleaned up.
cargo xtask docs plan-archive is a developer-invoked one-shot: scans plans for all-Done Status tables and git mv`s them into `archive/. Not run in CI.
5. Scope guidance for when to write a plan
Added per the convention review:
-
Plans cover features, services, or cross-cutting initiatives. If the work fits in a single MR and touches a single service, extend the parent domain plan (or skip the plan entirely — a clear MR description is often enough).
-
ADRs for tiny sweeps belong as errata on the originating ADR, not as new plans.
-
This is guidance, not a lint. Reviewers flag over-documented MRs; no CI enforcement.
Consequences
Positive
-
Drift visible. Status-table drift fails CI (eventually blocking; currently advisory). The "completed-plan-stale-status" pattern is caught at the commit level rather than discovered months later during audits.
-
Ideas get owners. Q6 edit routes improvement ideas to issues, where they have titles, labels, and assignees rather than rotting in plan prose.
-
Plans stay honest specifications. Q4 edit says "update the plan" before "add errata" — plans reflect reality.
-
Archive improves discovery. The
plans/index shrinks to in-flight work. Archived plans remain searchable but out of the way. -
No mass migration required. Canonical vocabulary matches the repo’s 85%+ existing convention; the lint lands with grandfathered
allow_failureso existing violations don’t block work.
Negative
-
Dual-home for improvements. During transition, existing plans still contain "Potential Improvements" sections. New work files issues. Reviewers must tolerate this asymmetry until retroactive cleanup (deferred, not in this ADR’s scope).
-
Precommit-hook drift risk. The hook’s Q4/Q6 text now diverges from the
coding-conventions.mdTier 1 template if the upstream template doesn’t track this edit. Handled by keeping both in sync manually until a template update propagates. -
plan-lintfalse positives on unusual Status text. Plans that use novel Status vocabulary (e.g., specific blockers phrased creatively) need to adopt one of the 6 canonical tokens. Small friction, catches drift.
Constraints
-
Vocabulary is closed-set. Adding a new token (e.g.,
Approved,Cancelled) requires updating this ADR + the lint’s allow-list. Intentional friction. -
Archive directory is flat. No nesting by year / program / tier. Simplicity over organization —
archive/is an audit trail, not a browsing surface. -
Tier 1 sync path. If a future upstream template mandates uppercase
COMPLETE, Canopy’s local vocabulary will diverge from Tier 1. When that happens, Canopy runscargo xtask check-docs --fix, migrates plans in bulk, and this ADR’s vocabulary section updates. Planned, not prevented.
Not addressed by this ADR
-
Retroactive cleanup of existing "Potential Improvements" sections. 95 plans have them. Sweeping them into GitLab issues is a separate effort, tracked if / when someone decides to do it.
-
Retroactive cleanup of existing Errata sections. 42 plans have them. Same story.
-
Plan scope lint. The "single-MR single-service plans shouldn’t exist" guidance is reviewer judgment, not tooling. A future ADR could formalize it if over-documentation becomes a measurable problem.
-
Automatic archival.
plan-archiveis developer-invoked. A future CI step could move plans automatically when all rows becomeDone, but not in this ADR.