# Implementation Planning Profile

```yaml
roles:
  - role: planner
    min: 2
    recommended: 2
    max: 5
    duty: planning-worker
  - role: critic
    min: 1
    recommended: 1
    max: 1
    duty: acceptance-critic
  - role: report-writer
    min: 1
    recommended: 1
    max: 1
    duty: report-writer
  - role: verifier
    min: 0
    recommended: 0
    max: 0
    duty: reverification-worker
    dynamic: true
```

- Purpose: turn an upstream-selected direction into an executable plan; legacy reruns may retain candidate comparison
- Required workers:
  - claude
  - codex
  - report-writer
- Optional workers (opt-in via `--workers`):
  - antigravity — when added to the roster it joins the analyser set; omitted by default
  - grok — optional adversarial analyser/critic through the Grok CLI wrapper
  - kimi — optional long-context analyser/critic through the Kimi CLI wrapper
{{INCLUDE:_common-contract.md}}
{{INCLUDE:_stage-discipline.md}}
- Brief consumption (phase-specific addendum — shared rules live in `_common-contract.md` under "Brief handoff contract"):
  - Apply the shared reporter-confirmation precondition exactly as written. In this phase, unresolved `intent-check:` / `conversion-block:` rows carry `Blocks=approval`, so the approval frontmatter stays `approved: false` until they are resolved.
  - never plan around an unconfirmed `intent-inference` augmentation as if it were a settled requirement. Treat the inference as settled ONLY when a `[CONFIRMED …]` marker sits on the matching `intent-check:` row after the precondition runs; absent the marker it stays a `Blocks=approval` clarification item per the precondition's `skipped` branch.
  - `conversion-block:` rows are handled by the precondition; planning around an untranslated reporter phrase is forbidden until it is resolved.
- Worker planning procedure:
  - **Ticket Tagging.** Tag every section 1–5 item with its related ticket. Use `Issue / Ticket`, fall back to Task ID, then `unknown`; comma-separate multiple tickets.
  - route by the planning input contract before analysis. A run carrying `selected-direction.json` uses the selected-direction procedure. Only a legacy rerun without that snapshot uses candidate comparison.
  - **Selected-direction planning procedure** — perform these steps in order and no others:
    1. Read `selected-direction.json` and the original requirements ledger end-to-end.
    2. Concretize the selected direction into files, interfaces, stages, validation, and rollback without changing its mechanism or boundaries.
    3. Link every planned file and stage bidirectionally to the original requirements.
    4. If current evidence requires changing the selected direction, emit `direction-invalidated` and stop planning.
  - identify requirement gaps and affected interfaces with file:line evidence, resolving codebase-answerable ambiguity before returning findings
  - surface migration, deployment, cross-project, and approval risks without drafting final-report headings or schema rows
- Legacy candidate-comparison-only responsibilities:
  - **Legacy candidate-comparison procedure** — only a legacy rerun without `selected-direction.json` compares feasible Option Candidates, preserves their trade-offs and Recommended Option, then produces stages, validation, rollback, and requirement coverage. This is the only branch that generates candidates, assigns candidate scores, recommends a direction, or awaits a user candidate choice.
  - **Spec-settled short-circuit** — when the brief already carries a decision-complete design the reporter has confirmed, do not re-litigate it. Preserve that design as the Recommended Option and its already-weighed alternatives as the remaining Option Candidates.
  - **Variation-point candidate scoring** — when `variationPointAnalysis` records two or more current implementations, include an extraction Option Candidate and score it against the non-extracted Option Candidate in the trade-off matrix. Penalize candidates that branch on resource identity inside a service or widen a unit's responsibility. This comparison preserves the established legacy `implementation-option` selection contract.
- Pre-planning context exploration (mandatory before direction realization or legacy option drafting):
  - read the task brief, related-task briefs, and any cited spec / design doc end-to-end
  - inspect the current state of every file the task names (or the closest matching files if names are stale) — record current responsibilities, public interfaces, and known coupling points
  - skim recent commits touching those files (`git log -- <path>`) to surface in-flight work or contested areas
  - **sibling exploration (variation-point evidence)**: read the `Related Task Graph` sibling / `related-to` tasks' done artifacts *and the code they actually landed* — a done report is a claim, the diff is the fact. When a sibling already implements the same behavior for another resource, register the existing implementation in `variationPointAnalysis.evidence` and verify that the chosen realization's `extractionDecision` addresses the present variation point without adding a second parallel implementation alongside it. Absent an explicit graph edge, still surface a same-behavior implementation you saw in the files or `git log` output already inspected above — an unrecorded edge does not make the duplication less real.
  - **codebase-first ambiguity resolution**: any ambiguity that can be answered by `Read` / `Grep` MUST be resolved that way and recorded with file:line evidence. Only ambiguities that genuinely require a human decision are escalated as `Clarification Items` rows. Writing a clarification row for something the code already answers is a defect of this phase.
  - **directive-first ambiguity resolution** (the same rule, pointed at the user instead of the code): any ambiguity the run's directive, the brief, the carried-in `user-responses/` sidecars, or the user's in-session instruction already answers MUST be resolved that way and recorded with the quoted instruction. Writing a clarification row for something the user already decided is the same defect as writing one for something the code already answers — and it costs more, because the row withholds approval until a whole separate answer cycle closes it. When an instruction points at a document, treat every item in that document as decided, including the ones the document itself flagged as needing a decision (shared rule: `_common-contract.md` "User instruction outranks the material it points at").
  - flag any requirement that is ambiguous, contradictory, or missing success criteria — register each one as a row in the report's `## 1. Clarification Items` table with `Blocks=approval` instead of guessing
  - read `<PROJECT_ROOT>/.okstra/glossary.md` and `<PROJECT_ROOT>/.okstra/decisions/` titles if present. Absent okstra memory files are the normal state — do not error. Treat the brief's `terminology:*` resolutions from `requirements-discovery` (if any) as authoritative; if missing, resolve any remaining fuzzy term as a `Blocks=approval` clarification row.
  - **Stage Ledger (read before drafting the Stage Map):** when this task already has a plan on disk, the analysis packet carries a `## Stage Ledger` JSON block listing every stage with its `status` (`done` / `active` / `ready` / `blocked`), `dependsOn`, and done commit. It states what exists, not what to plan. Two rules follow from it:
    - A stage whose `status` is `done` is already implemented and will not be executed again. Carry its plan body forward as written; do not rewrite its steps, and do not fold its work into a new stage.
    - Every stage number in the ledger is taken. A new stage takes the next number after the highest one listed; numbers are never reused or reordered. **Not yet machine-enforced** — the validator for this rule lands with the plan-amendment feature.
    - The ledger answers two questions from two sources, and the block names both. `sourcePlan` is the plan the completed stages were actually built against; `latestPlan` is the plan the `stages` list came from and is therefore the numbering authority. When they differ, the completed work followed the former and the highest taken number comes from the latter.
    - A `planDivergence` entry means the two plans disagree about a stage that is already `done` — the same number naming different work, or a completed stage the latest plan no longer declares. Do not pick one of the two yourself; register it as a `Blocks=approval` clarification row and assign no new stage number until it is resolved.
    - The block is absent ONLY on a task's first planning run. Its absence then means there is no prior plan, not that no stage is done. When the ledger could not be read, the packet says so under the same heading with a reason instead of going silent — in that state, assign no new stage number and report the reason as a blocker.
- Primary focus areas:
  - requirement gaps
  - affected components and boundaries
  - config files and deployment manifests that must change to reach the expected target state
  - direction realization consequences and trade-offs already fixed by the selected snapshot or legacy choice
  - hidden dependency or migration risk
  - validation and rollout approach
- Design principles applied to realization review:
  - **Isolation & single responsibility**: each unit touched should have one clear purpose, well-defined interface, and be independently testable. Reject a realization that widens a unit's responsibility without requirement evidence.
  - **Files that change together live together**: split by responsibility, not by technical layer. Reject a realization that scatters one logical change across unrelated layers.
  - **Follow established patterns**: in existing codebases, conform to current conventions. Targeted cleanup of a file you are already modifying is acceptable; unrelated refactors are not.
  - **Variation-point extraction (OCP)**: when the same behavior is served by two or more resources / implementations — stated in the brief, or foreseeable from a sibling task or the code you inspected — the plan MUST record it in `variationPointAnalysis` and make an evidence-backed `extractionDecision` that preserves the chosen direction's architecture boundaries. Reject a realization that branches on resource identity inside a service (one `if` / `switch` arm per implementation): adding the next implementation then means editing that same call site again, which is the closed-for-extension shape this principle exists to catch. This does not contradict YAGNI below: YAGNI drops *speculative* variation (a second implementation nobody named), while a behavior with two implementations already on the table is a present fact, not a forecast. **Enforced:** the `variationPointAnalysis` bullet under `Required deliverable shape` names the schema / validator / `P-Var-*` enforcement points.
  - **YAGNI ruthlessly**: drop features, abstractions, and configuration knobs that do not serve the stated requirement. The test is a *present* caller, not a plausible one — an abstraction whose only justification is a requirement nobody has stated is this rule's target, while a behavior with two implementations already on the table belongs to `Variation-point extraction` above. **Enforced:** the §5.5.9 plan-body verification round raises a hidden direction expansion on selected-direction `P-Dir-1`, and retains legacy `P-Opt-*` `DISAGREE(e)` semantics. One phase later the `implementation` verifier's Static design gate fails the stage on a caller-less identifier.
  - **Project review-rule preflight**: a pack applies when either source names it — the task brief's `Source Material` / `Reporter Confirmations` cites its exact `SKILL.md` path, or `<PROJECT_ROOT>/.okstra/project.json` lists it under `reviewRulePacks` (the project's standing standard, applying whether or not the brief mentions it). The two sources are a union. Read only those files and the `references/*.md` files they directly name. Do not search parent directories or host skill catalogs. Do not run the PR-review workflow here; extract only the rules. For Fonts Ninja-style TS/NestJS review packs, this means planning away known review findings before code exists: shared transforms instead of duplicate helper stacks, behavioral tests instead of collaborator-tautology assertions, domain rules in domain modules rather than repositories/adapters, domain objects under `domain/`, plain-English functions, truthful/specific names, and no dead APIs introduced by the plan.
- Expected output emphasis:
  - feasible realization of the selected direction or legacy choice
  - dependency and risk visibility
  - recommended execution order
  - one `endStateCoverage` row per brief end-state id, whose `coveredBy` names the `R-NNN` row that carries it. The two tables are a chain, not duplicates: `endStateCoverage` proves no reporter requirement was dropped, `requirementCoverage` proves each one reaches a stage.
- Implementation Design Preparation (`implementation-design-prep-v1`, BLOCKING):
  - **Detector SSOT:** the planner MUST run the V1 detector defined by `scripts/okstra_ctl/design_surfaces.py` (`detect_design_surfaces()` over the detector's `RULES`) and MUST NOT invent or copy a second keyword list into the plan or prompt. **Enforced:** `validators/validate-run.py` `_validate_detector_coverage` reruns that detector and compares every `(stage, kind)` plus its trigger evidence.
  - **Exactly-once coverage:** for every detector-produced `(stage, kind)`, the planner MUST write exactly one `designSurfaceCoverage` row on that stage. **Enforced:** `validators/validate-run.py` `_validate_detector_coverage` rejects missing, duplicate, extra-detector-kind, or evidence-mismatched rows; `schemas/final-report-v2.0.schema.json` `$defs.DesignSurfaceCoverage` enforces the row shape.
  - **Disposition:** the design-surface detector snapshot uses `inline-contract` only when the stage already states the kind-specific minimum implementation contract; otherwise it uses `prep-item`. `not-applicable` is legal only with a concrete rationale consistent with the stage action. **Enforced:** `scripts/okstra_ctl/report_projections.py::project_design`, `schemas/final-report-v3.0.schema.json`, and `P-Prep-S<stage>-<kind>` verification.
  - **AI-prepared proposal:** every referenced PREP item MUST record `kind`, `stageRefs`, `need`, evidence-cited `knownFacts`, `openQuestions`, a concrete evidence-backed `aiProposal` (`summary`, `details`, `assumptions`, `evidence`, `confidence`), `humanConfirmation`, explicit `status`, and the safest reversible default available. **Enforced:** `schemas/final-report-v2.0.schema.json` `$defs.DesignPrepItem` / `$defs.DesignPrepProposal` enforce required fields, `validators/validate-run.py` `_validate_prep_references` enforces the bidirectional stage/kind link, and `prompts/lead/plan-body-verification.md` rejects empty or non-implementable proposals.
  - **Status choice:** prefer `provisional` with a `workingAssumption`, concrete `guardrails`, `reviewAt`, `ifStillOpen`, and canonical `requestPath`; use `blocked` only for business policy, external authority, a destructive migration decision, or the absence of any safe reversible assumption. **Enforced:** `schemas/final-report-v2.0.schema.json` `$defs.DesignPrepItem` enforces state-specific fields, `validators/validate-run.py` `_validate_design_prep_states` enforces confirmation/request invariants, and `prompts/lead/plan-body-verification.md` judges whether the disposition is justified. A declared `blocked` status does not by itself fail plan-body verification.
  - **External-reality anchoring (`external-interface` / `transformation-mapping` surfaces):** these two detector kinds are correct only against data whose shape lives *outside this repository* (a third-party response body / external payload format). For such a surface the referenced PREP item's `aiProposal` MUST derive the assumed shape — selectors, field paths, response structure — from a **captured real sample** and cite it in `knownFacts` / `evidence` (source + capture time); a shape invented from internal reasoning and marked `confidence: high` is the disallowed move, because a plan built on an assumed shape yields an implementation whose parser and fixture only ever agree with each other. When the brief supplies no sample and none is capturable at plan time, the item MUST stay `provisional` with a `workingAssumption` that the external shape is unverified against reality, a `guardrails` line forbidding the implementation from presenting a synthetic-fixture green run as reality-verified, and an `ifStillOpen` that routes to user confirmation against real data — it MUST NOT be dispositioned `inline-contract` / settled. **Enforced (semantic):** the surface's presence is machine-checked by `_validate_detector_coverage`; whether its proposal's evidence is genuinely external is judged by the §5.5.9 `P-Prep-S<stage>-<kind>` round (this phase runs it adversarially).
  - **Planner-only test surface:** add `manual-user-test` only when a test prerequisite changes the implementation interface or acceptance contract; the V1 detector never emits it. **Enforced:** `validators/validate-run.py` `_validate_detector_coverage` rejects detector-produced `manual-user-test`, `schemas/final-report-v2.0.schema.json` permits its planner-authored shape, and `prompts/lead/plan-body-verification.md` verifies the stage-action rationale.
  - **Trivial task:** when the detector returns no surfaces, its input uses `designPreparation.mode: no-design-inputs`, an empty `items` array, and a concrete reason tied to the plan. **Enforced:** `schemas/final-report-v3.0.schema.json` and `scripts/okstra_ctl/report_assembly.py`.
- Approval gate (phase-specific addendum to shared authority rule):
  - The report record `frontmatter.approved` field is the only authorised approval gate. report-writer always emits `false`. The user clears it by invoking the next phase with `--approve`, or by confirming approval in the in-session wizard. Editing the full reading copy does not approve the plan. `okstra_ctl.run._validate_approved_plan` reads this field and refuses entry until it is `true`.
- Cross-verification mode:
  - Phase 5.5 finding convergence runs in **adversarial mode** for this phase (`convergence.adversarial=true`). Verifiers actively try to refute each worker finding (requirement gap / risk / plan item) by re-inspecting its cited evidence; the burden of proof sits on the claim. See `prompts/lead/convergence.md` §"Adversarial Verification Mode".
  - §5.5.9 plan-body verification runs with an **adversarial posture** (`prompts/lead/plan-body-verification.md` §"Adversarial plan-body posture"): verifiers open and confirm every cited path / command and put the burden of proof on the plan. The gate threshold is majority-based for kinds `b`/`c`/`e`, but a single `DISAGREE` blocks on its own for the concrete, safety-critical kind `a` (path/symbol mismatch) — and `f` on `P-Req-*` items. `P-Var-*` items are excepted from the kind-`a` exception: a variation-point defect takes a majority. Rollback ordering (`d`) is advisory and never blocks the gate — a rollback is executed by a human, not by okstra's workers or verifiers. A majority also needs ≥2 participating votes, so a lone dissent whose peer returned a non-result does not block on a majority-gated kind (see that contract's §"Adversarial plan-body posture").
  - **Incremental re-verification scope (clarification re-runs):** when the lead's `okstra incremental-scope` decision is `mode == "incremental"` (procedure in `prompts/launch.template.md` §"Clarification Response Carried In"), workers re-analyze ONLY the stages listed in `reverify_stages` (the downstream closure of the impacted stages). Workers MUST NOT re-open, re-score, or re-judge any stage in `carry_stages` — those stages' prior plan-item verdicts are carried forward verbatim, and a worker never overwrites a carried verdict with its own judgement. When the decision is `mode == "full"` (the default), every stage is re-analyzed as usual.
  - **Single incremental-scope decision:** the lead calls `okstra incremental-scope` once the inputs are complete, passing the answered `C-NNN` ids through `--answered-clarifications`, changed design-preparation IDs through `--prep-items`, and any lead-resolved stage numbers through `--impacted`; the CLI unions all three before applying the existing dependency closure and cutoff. The clarification ids are resolved to stages by the CLI from the prior report's own `planItems[].clarificationId` and `blocked C-NNN` coverage links — the lead does not map answers to stage numbers. An answer that changes the selected planning payload, Stage Map, or execution approach is not a local impact: pass `--full-reason`, which is the only structural path that still forces `mode == "full"`. A clarification id that traces to no stage returns `mode == "unresolved"` — ask the user for stage numbers and call again with `--impacted`; do not treat it as full and do not silently drop the id. Unknown PREP IDs or invalid `stageRefs` still return an explicit full decision instead of being guessed. When the wizard pin is `auto` and the CLI returns `mode == "incremental"`, keep it — do not upgrade to full. When the user pinned a scope at the wizard (`REVERIFY_SCOPE_MODE` / `REVERIFY_SCOPE_STAGES` in `prompts/launch.template.md` §"Clarification Response Carried In" step 0), that pin is an input to this same call — `full` supplies the `--full-reason`, and pinned stage numbers join `--impacted` — never a bypass of the CLI's closure and cutoff.
  - **Stage-aware carry:** for an incremental decision, the report writer copies each `carry_stages` stage row unchanged into its narrative. After plan-item seeding, pass the decision's `carry_stages` and `reverify_stages` CSVs unchanged to `okstra incremental-carry --cur-narrative ... --state ... --out-state ...`. The helper rejects a changed or missing carried stage and copies prior `P-Step-*` / `P-Prep-*` verdicts for `carry_stages`, plus unchanged `P-Val-*` / `P-Req-*` / `P-Rb-*` rows whose extract `contentHash` still matches. It rewrites `dispatchQueue` and the sibling `plan-items-*.json` so the next prompt does not re-score a carried checklist row. Overlap, omissions, and canonical conflicts return `CarryError`. On that error, discard the partial state and run full re-verification.
{{INCLUDE:_coverage-critic.md}}
- Non-goals:
  - code-level micro-optimization unless it changes the implementation approach
  - **source code edits of any kind** — this run produces a plan document only; Edit/Write on project source files is forbidden until the plan is approved and a separate `implementation` run starts
  - executing builds, migrations, deployments, or any command that mutates project state outside the run's own artifact directories (`reports/`, `prompts/`, `state/`, `manifests/`, `worker-results/`, `status/`, `sessions/`)
  - this run stays in `implementation-planning` regardless of user phrasing — the shared anti-escalation rule applies
  - dispatching parallel sub-agents beyond the required worker roster — okstra owns worker fan-out
  - writing artifacts anywhere except `<PROJECT_ROOT>/.okstra/` — the run's `reports/` directory is the canonical location for this phase
- Clarification request policy (phase-specific addenda — shared policy is in `_common-contract.md`):
{{INCLUDE:_clarification-recommendation.md}}
  - **Evidence note required inside `Statement`**: every clarification row includes `Evidence checked: <path:line>` or `Evidence checked: none — <human-only reason>` in the `Statement` cell. `none` is allowed ONLY when the row's nature is "only a human can answer this" (reporter intent, business priority, organisational decision). A row with `none` that *could* have been answered by the codebase is a defect of this phase, restated from the pre-planning rule above. **Enforced:** `validators/validate-run.py` `_validate_clarification_evidence_note` requires the note to exist and to be either a concrete `<path>:<line>` or the `none — <reason>` form. The check proves the note exists, not that the lookup happened — a false `none` is caught by review, but it is now an attributable claim rather than a silent omission.
  - **`Blocks=approval` admissibility (scope boundary).** Requirement Coverage has an admissibility test and Clarification rows did not, so anything at all could acquire approval-blocking force — a prose inconsistency inside a discarded plan detail blocked a plan with the same weight as a business decision. A row earns `Blocks=approval` only when, **absent an answer, the `implementation` run would produce wrong or unsafe code**. Apply the test to the row's consequence, not its topic:
    - admissible — the answer selects between behaviours the code must implement, fixes a requirement the plan would otherwise satisfy incorrectly, or resolves a safety/data-integrity question.
    - NOT admissible → use `Blocks=none` — QA-harness or tooling scope, report notation and wording, numbering or citation-range cleanup, anything outside the chosen realization, and anything the codebase answers (which the codebase-first rule already forbids raising at all). These belong in `## 5. Missing Information and Risks` or a Working Assumption; they are recorded, not gating.
    - A row you would answer with "the plan would still produce the same code either way" is by construction `Blocks=none`.
- Deliverable completeness contract (BLOCKING — the schema checks data keys, not heading strings):
  - For a selected-direction plan, the plan-ready schema branch requires `planningContract`, `outcome`, `selectedDirectionRef`, `directionRealization`, `stageMap`, `stages`, `designPreparation`, `dependencyMigrationRisk`, `validationChecklist`, `rollbackStrategy`, `requirementCoverage`, `coverageSummary`, `variationPointAnalysis`, and `planBodyVerification`. Its `direction-invalidated` branch contains no execution fields.
  - Each `stages[]` entry requires `stage`, `title`, `sliceValue`, `acceptance`, `carryIn`, `stepwiseExecution` (1–6 rows), `exitContract`, and `stageValidation`. Each `stageMap[]` row requires `stage`, `title`, `dependsOn`, `stepCount`, `exitContractSummary`.
  - Beyond the schema, `validators/validate-run.py` reads the same data.json for `_validate_planning_conformance_declared`, `_validate_end_state_coverage`, `_validate_requirement_provenance`, `_validate_stage_has_requirement`, and `_validate_plan_body_state_file`. These run for every planning report regardless of schema version.
  - **Do not chase English heading substrings.** `PLANNING_REQUIRED_SECTIONS` and the Markdown scan in `collect_validation_errors` live inside `validate_phase_boundary`, which returns immediately when `schemaVersion == "2.0"` — they gate historical v1 Markdown only. The v2 full reading copy template renders nine headings and serialises the plan as JSON beneath them, so those substrings cannot appear, and a report is not defective for lacking them.
- Per-stage vertical slice and TDD contract (BLOCKING — enforced on the data, not on heading tokens):
  - Every stage declares `sliceValue`, `acceptance`, and the three cases `testCaseSuccess` / `testCaseBoundary` / `testCaseFailure` — happy path, edge/boundary input, failure input. **Enforced:** the v2 schema's `if not tddExemption then require` conditional on `ImplementationPlanStage`.
  - The first `stepwiseExecution` row's `action` starts with `RED:` and its `expected` reads FAIL; some later row's `action` starts with `GREEN:` and its `expected` reads PASS. **Enforced (S10c):** `collect_data_validation_errors` in `validators/validate-implementation-plan-stages.py`, run from `validate-run.py` `_append_stage_data_failures`.
  - `tddExemption` waives both rules above, and only for `doc-only`, `config-only`, or `pure-rename` work. An empty or arbitrary reason waives nothing. **Enforced (S10e):** same function — the schema alone cannot reject it, because it types the field as a plain string and keys its conditional on the property merely being present.
  - `stageMap[].dependsOn` must form a DAG (no self-dependency, no unknown stage, no cycle), each row's `stepCount` must equal its stage's actual `stepwiseExecution` row count, and two `(none)`-dependency stages must not name the same file in their `exitContract` — they run as concurrent implementation runs in separate worktrees. **Enforced (S8/S4/S9):** same function.
- Legacy candidate-comparison-only deliverable:
  - The report writer records the plan body in report narrative Markdown under `implementationPlanning`. Report assembly adds machine-owned `designPreparation`, `designSurfaceCoverage`, and `planBodyVerification`, then validates the completed `data.json` against `schemas/final-report-v3.0.schema.json`.
  - **Narrative field names under `Implementation Planning` (the only ones accepted):** `Coverage Summary`, `Cross Project Dependencies`, `Decision Drafts`, `Dependency Migration Risk`, `Direction Invalidation`, `Direction Realization`, `Incremental Decision`, `Option Candidates`, `Outcome`, `Planning Contract`, `Recommended Option`, `Requirement Coverage`, `Rollback Strategy`, `Routing`, `Selected Direction Ref`, `Skipped ADR Candidates`, `Stage Map`, `Stages`, `Stepwise Execution`, `Supersession Ledger`, `Tradeoff Matrix`, `User Narrative`, `Validation Checklist`, `Variation Point Analysis`. The `## 5.5.<i>` headings, the `step | action | ...` tables, and the `Slice value:` / `Test case (...):` lines described below are the shape of the **rendered report a reader sees**, not narrative syntax — the narrative carries the same content as `- **Field**` / `- Item <N>` / `> value` rows under these names. `Design Preparation` and `Plan Body Verification` belong to report assembly and are rejected here.
  - Legacy candidate-comparison requires at least two implementation options. **Each option must include**:
    - **File Structure**: an explicit list of files to create / modify / delete with each file's responsibility (one-line each). Use the form `Create: path — responsibility` / `Modify: path:line-range — change summary` / `Delete: path — reason`. Write every `path` in full and `<PROJECT_ROOT>`-relative — never ellipsis-abbreviated (`…` / `...` / a trailing `/…`); an abbreviated path does not resolve and is rejected by plan-body verification as a kind-b path mismatch.
      - **Two-tier change description.** Each `fileStructure` row carries `summary` **and** optional `details`, and they are not interchangeable:
        - `summary` — one plain-language sentence a reviewer who has never opened this file can follow: what changes and why it is needed. Name behaviour and domain nouns, not identifiers. No function/type/variable names, no call chains, no line numbers, no conditional logic spelled out. **Enforced:** `schemas/final-report-v2.0.schema.json` `$defs.OptionCandidate.fileStructure.items.summary` caps it at 120 characters, so a dense identifier dump fails schema validation.
        - `details` — the technical specifics that used to be crammed into `summary`: exact symbol names, signatures, comparison semantics, line ranges, enum members. Omit the field when there is nothing beyond the summary.
        - Bad `summary` (identifier dump, no readable claim): `Widen the return of retrieveFontFamilyGroupWithFontVersionId to also yield the matched FontFamily (own key and enabled flag) and the matched Font.status.`
        - Good — `summary`: `Carry enough information out of the font lookup to tell whether the matched family is switched off.` / `details`: `Add the matched FontFamily (own key + enabled) and Font.status to the return of retrieveFontFamilyGroupWithFontVersionId. Both are already in scope in the :102-104 loop and dropped at return.`
    - affected interfaces / public contracts and downstream consumers
    - estimated blast radius (units, configs, deployment manifests, data migrations)
  - trade-off matrix across options (rows = options, columns at minimum: complexity, risk, reversibility, test coverage cost, rollout cost)
  - recommended option with rationale tied to the design principles above
  - `recommendedOption.testSeams` is a mandatory array with one row per boundary a test injects at and replaces: `boundary`, `injectedAs`, and `replacedInTest`. An empty array is legal only with a defensible reason. `schemas/final-report-v2.0.schema.json` requires the array and all three row fields; the §5.5.9 `P-Var-*` round rejects a seam whose construction or wiring point is not actually replaceable.
  - Record non-blocking working assumptions inside the Recommended Option. A required answer belongs in a `Blocks=approval` clarification instead. Omit the assumptions only when there are none.
  - The YAML frontmatter carries `implementation-option:` directly under `approved:` so the user can select an Option Candidate after planning.
- Required deliverable shape (final report, in addition to the standard sections):
  - In the selected-direction branch, `directionRealization` is the sole design payload. Its `fileStructure`, interfaces, blast radius, test seams, assumptions, and invariants concretize the snapshot without introducing another option or recommendation.
  - **Variation-point analysis (`variationPointAnalysis`, mandatory — every plan emits the block, rendered as §5.5.11):** declare `hasMultipleImplementations`, and when it is `true`, one `points[]` row per varying behavior carrying `behavior`, the two or more `implementations` that serve it, `evidence` (a `path:line`, or the sibling task / stage that already implements it), and an `extractionDecision` of `extract` / `interfaceKind` / `coveredBy` (the Stage Map stage that builds the interface) / `rationale`. **A `false` declaration is not an omission — it is a claim**, so it carries a written `noVariationRationale` and an empty `points` array; the two are mutually exclusive, because declared points would be silently dropped from verification under a `false` header. A project whose `.okstra/project.json` sets `architecture.style: hexagonal` extracts a point as a port (`interfaceKind: "port"`), never as a shared helper. **Enforced:** `schemas/final-report-v2.0.schema.json` `$defs.VariationPointAnalysis` / `$defs.VariationPoint` make the block required and pin the row shape; `validators/validate-run.py` `_validate_variation_point_analysis` rejects a `false` declaration with no rationale, a `false` declaration carrying points, a `true` declaration with no point, an `extract: true` decision naming no `interfaceKind` or no `coveredBy`, and a hexagonal project extracting as anything but a port; and every point becomes a `P-Var-<N>` plan item judged in §5.5.9 (`prompts/lead/plan-body-verification.md`) — a plan declaring no variation point is still verified, through the lone `P-Var-0`.
  - **Stage Map (mandatory — always emitted, even when N=1):** a table of all stages with `stage | title | depends-on | step-count | exit-contract-summary`. `depends-on` is `(none)` or a comma-separated stage number list. Stages with `depends-on (none)` can be implemented in parallel by two simultaneous `implementation` runs.
    - **Keep the table at exactly 5 columns** — do NOT add a column. `validators/validate-implementation-plan-stages.py` parses `stage | title | depends-on | step-count | exit-contract-summary` and silently skips any row that is not exactly 5 cells, so a 6th column would drop every stage and bypass S2–S11.
    - **Multi-project plans only** (the plan's work spans more than one project — see the Project-boundary partition rule below): prefix each stage's `title` cell with a `[<project>]` tag (e.g. `[okstra] Add X`) so the project each stage belongs to is readable at a glance, and add exactly one line directly under the Stage Map table — `Cross-project parallelism: <which per-project stages run in parallel, which are sequenced, and the cross-project dependency that forces each sequencing>`. Single-project plans omit both the tag and the line.
  - **Per-stage slice declaration (mandatory lines, directly under the `## 5.5.<i> Stage <i>:` heading, before `### Carry-In`):**
    - `Slice value: <the one user-observable increment this stage delivers, end-to-end>` — describe WHAT starts working from the consumer's view (e.g. "querying X returns Y"), NOT a layer name ("add a repository"). Validator S10a rejects a missing/empty value.
    - `Acceptance: <the observable pass condition or the exact command>` — the signal that proves the slice is done; normally the same test command that the `RED:` step below flips to PASS. Validator S10b rejects a missing/empty value.
    - **Test-case design (mandatory three lines unless the stage carries a `TDD exemption:`):** the plan, not the executor, decides which cases the stage's tests must cover, so a stage cannot ship with only one happy-path assertion. Declare all three:
      - `Test case (success): <input → expected, the command/test name>` — the happy path: a valid input that proves the slice works end-to-end.
      - `Test case (boundary): <edge input → expected, the command/test name>` — an edge/boundary input (empty, min/max, missing, first/last, off-by-one). When the slice genuinely has no boundary, write `N/A — <reason>` so the value is non-empty and the omission is a conscious, reviewable decision — never leave it blank.
      - `Test case (failure): <invalid input → error/rejection, the command/test name>` — a negative/failure path (invalid input, unauthorized, error propagation).
      Validator S10d rejects a missing/empty line in any of the three categories (skipped only when a `TDD exemption:` line is present). The `RED:` step below must encode these cases, not a single assertion.
      **Pick the cases by distinct outcome, not by line coverage.** When the stage writes or reconciles state, its meaningfully different outcomes are usually more than three — normal success, target already in the desired state (resume), existing data reused rather than created, a conflicting concurrent state, target absent, and mid-way failure with rollback. Enumerate the ones this stage actually implements and route them across the three lines (the `boundary` line is where resume / already-done / reuse belongs; `failure` carries conflict, absence, and rollback), naming each in the cell rather than collapsing them into "edge input". An implemented outcome with no declared case is a coverage gap the executor will not backfill.
  - **Per-stage subsections** (`## 5.5.<i> Stage <i>: <title>` for each `i`), each containing the four required subsections:
    - `### Carry-In` — for `depends-on (none)`: task-brief only. Otherwise: each depended-on stage's static exit contract + runtime sidecar path `runs/<impl-key>/carry/stage-<i>.json` placeholder.
    - `### Stepwise Execution Order` — bite-sized table with `step | action | files | command | outcome | expected`. `outcome` is one word — `PASS` or `FAIL` — and `expected` is the sentence saying what that looks like here; a verdict written inside the sentence is not read as one. The `files` cell lists each touched path in full and `<PROJECT_ROOT>`-relative — never ellipsis-abbreviated (`…` / `...`), which does not resolve and is rejected by plan-body verification as a kind-b path mismatch. **The narrative row additionally carries `plannedPaths`: the same paths as an array, one repository-relative path per entry, with no globs, exclusions, counts or commentary.** `files` is the sentence a reader sees; `plannedPaths` is the ledger report assembly preserves and the implementer write policy enforces. When a step legitimately covers a set too large to enumerate, split it or name the directory the set lives under. **Effective row count ≤ 8** (excluding header / divider / blank). Each step is one cohesive, self-contained change. **TDD ordering is MUST, not a preference:** the **first** effective step's `action` cell MUST start with the literal `RED:` and describe the failing test(s) that capture this stage's `Acceptance` **and the three declared `Test case (success|boundary|failure)` lines** (`outcome` = `FAIL`); at least one later `action` cell MUST start with the literal `GREEN:` and describe the minimal implementation that makes it pass (`outcome` = `PASS`); an optional refactor step starts with `REFACTOR:`. **Exemption:** doc-only / config-only / pure-rename stages with no observable runtime behaviour may omit RED/GREEN by declaring one line `TDD exemption: <reason>` in the stage section. Validator S10c enforces RED-first + GREEN; the `outcome` cell agreeing with its prefix is a schema conditional. S10e rejects an unsupported exemption reason (`validators/validate-implementation-plan-stages.py`).
    - **The `command` cell runs inside an okstra task worktree, not a bare checkout (BLOCKING).** okstra provisions `.okstra`, the configured sync entries (`.project-docs`, `.claude`, …), and — for `implementation` — a nested `stage-<N>/` worktree into the tree the step executes in. Two consequences bind every command you write:
      - **Clean-tree assertions use `okstra worktree-status --check-clean`.** A bare `git status --porcelain` is never empty there, so an assertion built on one fails on okstra's scaffolding rather than on the stage's work. The okstra command asks the same question over source paths only and exits 1 when dirty, so it stands alone as a step's assertion: `okstra worktree-status --check-clean`. Validator S13 rejects the bare form. Do not add a `git tag stage-<N>-exit` to the step — okstra writes that tag itself when it settles the stage, at the commit the carry evidence records, and a step that tags mid-stage puts it on an earlier commit.
      - **Never read an `.okstra/` artifact back out of a git object.** `.okstra/**` is gitignored and never committed — the executor aborts a commit that stages an ignored path and the verifier reports a committed `.okstra` path as a branch defect — so `git cat-file -e <tag>:.okstra/…`, `git show <tag>:.okstra/…`, and every variant of that read can never resolve, at any tag, in any stage. A later stage that needs a QA artifact reads it from the working tree or receives it through the carry sidecar / verifier result; do not design a stage contract around one being reachable from a tag. Validator S12 rejects the read.
    - **Per-stage conformance declaration (mandatory one line, in the stage section — same placement freedom as `TDD exemption:`):** the stage MUST carry exactly one of:
      - `Conformance tests: stage-<N> — <task_root>/qa/scripts/stage-<N>.<ext> (requires=[db|io|http|external,...])` — declare that a Tier3 verification script will prove this stage's upstream requirements (brief / requirements-discovery / error-analysis / improvement-discovery → this stage's `Acceptance`) hold against **real** DB rows, real endpoints, or the real external API — NOT mocks. This phase emits the line and the `requires` set only. Do NOT write `<task_root>/qa/scripts/stage-<N>.*` and do NOT add a `runCommand` or `conformance-manifest.json` entry here — the matching `implementation` stage run creates the script file and the manifest `runCommand`. A plan that declares tests with no script file on disk is valid at this gate.
      - `Conformance exemption: <reason>` — only for stages that touch no db/io/http/external surface, or where unit tests fully cover the increment. Exemption stays a planning declaration; do not move it to implementation. (If the eventual `implementation` diff actually touches one of those surfaces, `validate-run.py`'s diff-surface cross-check is BLOCKING — an exemption cannot hide a real db/io/http/external change.)
      - **External QA outcome guideline:** after satisfying the S11 declaration above, a line whose `requires` contains
        `db`, `http`, or `external` should name those capabilities here so the later `runCommand` can be written against them.
        Okstra may start the environment and run it automatically, but `FAIL`, missing evidence, or an
        unavailable environment is a user-owned follow-up, never a plan approval or
        later run blocker. `requires=[]` and `requires=[io]` remain blocking.
        Remote IO should also declare `external`.
      Layout split (the implementer writes these, not this phase): executable scripts (conformance + any real-IO test) live under `<task_root>/qa/scripts/`; data sidecars (`conformance-manifest.json`, `result-*.json`) stay at the `qa/` root. This declaration is enforced at four layers: `validators/validate-implementation-plan-stages.py` check **S11** forces every stage to carry one of the two lines; at the planning boundary `validators/validate-run.py` `_validate_planning_conformance_declared` accepts a well-formed `Conformance tests:` line even when the script file and manifest entry are absent (malformed `requires` still fails); the matching `implementation` stage run that inherited `Conformance tests:` fails closed when the script file is missing (`_validate_conformance`); and the manifest JSON structure — including each entry's `script` living under `qa/scripts/` and a `runCommand` that does not change cwd — is enforced by `validate_conformance_manifest` when the implementer writes the entry.
    - `### Stage Exit Contract` — predicted added/modified files, newly exposed identifiers/types/endpoints, downstream-usable resources.
    - `### Stage Validation` — pre / mid / post exact commands or observable outcomes for this stage only.
      - **Dependency precondition (stages that run the project toolchain).** The planning worktree is created without installed dependencies, so a stage whose steps call `npm` / `yarn` / `pytest` / `cargo` / equivalent cannot have those commands succeed at plan time — they exit `127`, not RED/GREEN. Declare the install **once** as a `phase: pre` row in `### Validation Checklist` (e.g. `VC-008 — the implementation run's stage worktree has workspace dependencies installed`) and have every such stage's `Stage Validation` cite that `VC-NNN` in its `pre:` line. Do not repeat the install commands per stage, and do not silently assume the tooling is present: a plan that never states the precondition produces steps whose commands never resolve, which the §5.5.9 round then reports as unverifiable. **Enforced (advisory):** `validators/validate-run.py` `_detect_missing_dependency_precondition` warns when a toolchain-invoking stage cites no `VC-NNN`, or cites one that is not `phase: pre`. Whether the cited row genuinely covers dependencies is a §5.5.9 judgement, not a machine check. Detection uses the token allowlist in `scripts/okstra_ctl/build_tools.py`; a project overrides it with `buildToolTokens` in `.okstra/project.json`.
  - **Vertical-slice-first partition rule (1st-class):** the grouping anchor is a **thin end-to-end vertical slice** — one stage delivers a single user-observable increment, crossing whatever layers are needed (data → service → API → UI) to make that one increment work. File/module proximity is demoted to the **intra-slice grouping rule**: within a slice, keep steps touching the same file/directory/module together so the diff, PR, and rollback unit stay cohesive. **Horizontal layer-splitting is forbidden** — never carve "the DB layer" into one stage and "the service layer" into the next; that produces stages that ship no standalone user value. A stage is split ONLY when (a) a real `depends-on` data/contract dependency exists, (b) effective steps would exceed 8, or (c) it is a distinct vertical slice (a different user-value increment). Maximising the number of parallel stages is NOT a reason to split — parallelism is an emergent property of independent stages, never a partitioning goal.
  - **Project-boundary partition rule (hard boundary):** a *project* boundary is either (a) a different repository / `PROJECT_ROOT`, or (b) a different top-level independently-deployable module within one repo. A stage maps to a single worktree on one repo/branch, so **no stage may contain edits belonging to more than one project** — this is a hard split that overrides the ≤8-step merging allowance; never co-locate two projects' changes in one stage to save a stage. Two cases:
    - **Same repo, different modules** — put each project/module's increment in its own stage. Their file sets are disjoint, so they default to `depends-on (none)` and run in parallel; add a `depends-on` link ONLY when a real cross-module contract / shared-schema / deploy-order dependency exists.
    - **Different repos** — a single okstra task **cannot** span repos: every stage worktree is a `git worktree add` in one repo's main checkout (one `<project-id>`), the run-index / manifests / registry are keyed to that one project, and the edit allowlist only covers that project's tree plus `~/.okstra/worktrees/**`. This is a structural limit, not a style preference. Therefore cross-repo work MUST be split into **a separate okstra run per repo** — never modelled as stages of one task. State, in the Stage Map `Cross-project parallelism:` line, whether those per-repo runs can proceed in parallel. To avoid re-deriving shared analysis, the second repo's run should consume the first run's relevant plan/decision artifacts as brief Source Material (see the cross-project dependency rows below).
    - **Parallel-feasibility check (mandatory for every multi-project plan):** disjoint files (S9 below) is necessary but NOT sufficient for parallelism — a cross-project API/contract/schema/deploy-order dependency forces sequencing even when no file overlaps. For each pair of projects, explicitly determine and record (in the `Cross-project parallelism:` line) whether they are independent (run in parallel) or sequenced (and the exact dependency that forces the order).
  - **Parallel-safety invariant (BLOCKING):** any two stages that are both `depends-on (none)` MUST predict disjoint file sets in their `Stage Exit Contract`. Two parallel `implementation` runs would otherwise edit the same file concurrently. Work touching a shared file must either go in one stage or be ordered with `depends-on`. Enforced by `validators/validate-implementation-plan-stages.py` check S9.
  - **Cross-project dependency rows (`crossProjectDependencies` array — replaces the old `## Cross-Repo Carry` appendix):** a dependency on another project (a different repo / a different top-level independently-deployable module / a published package) is recorded not as the freeform `## Cross-Repo Carry` appendix but as an **`XP-NNN` row of the structured field `crossProjectDependencies`**. Put one XP row per project dependency and fill in whichever `direction` applies (rendered as `### Cross-Project Dependencies` §5.4; a single-project plan uses an empty array). The row fields replace the old three subsections with — `requiredWork` (the concrete work the other side must build) / `verificationSignal` (the signal this run will observe) / `linkedWork` (the stage/step of this plan that is blocked until the signal is met) / `howToStart` (the exact handoff in the other repo).
    - `direction: upstream-precondition` — this run **waits on the other side's prerequisite work (precondition required)**: `requiredWork` = the concrete work the other side must implement first, `verificationSignal` = the signal this run must observe before proceeding (PR merged / endpoint live / version published), `linkedWork` = the stage/step of this plan that is blocked until the signal is met, `howToStart` = in the other repo, `okstra-brief-gen` (**cite this report's absolute path as Source Material** — the only permitted cross-`<PROJECT_ROOT>` read) → `okstra-run`.
    - `direction: downstream-carry` — this run **produces what the other side will consume (the existing carry)**: `requiredWork` = the self-contained B-portion the other side will implement next (from the other side's perspective, a new `R-NNN`, proposed stages, and affected files), `verificationSignal` = the (this-run-delivered) signal the other side confirms before proceeding, `linkedWork` = the stage/step of this plan that delivers that signal, `howToStart` is the same as upstream. Do not hand over this run's already-`done` stages as if they were the other side's stages — include only the portion the other side still has to build.
    - **A cross-repo dependency cannot be expressed with `depends-on`:** that gate only resolves commits inside a single repo's git graph (`scripts/okstra_ctl/run.py` `_resolve_stage_base_commit`), so it cannot point at a commit in another repo. Cross-repo work is split into a separate okstra run + XP row rather than a stage (the Different-repos rule). The other run is structurally independent, so it does not automatically recognise this run's completed stages as `done` (nor should it — that is this repo's work); the XP row is only a narrative input that seeds the other side's planning. This run does not write to the other repo's tree or its `.okstra/` (it emits only this report's XP rows). The old "Recognition caveat" wording has been moved into a render note (i18n `crossProjectRecognitionNote`) and is emitted automatically in the `### Cross-Project Dependencies` section.
  - **Stage exit contract is the carry surface:** keep it as narrow as possible. Wider surface = more downstream coupling.
  - dependency / migration risk assessment (ordering constraints, data backfills, feature-flag prerequisites, repo-internal sequencing)
  - **Cross-Project Dependencies (conditionally required):** when the plan depends on work in another project / repo / published package, add (a) a `kind: cross-project` DM row to `dependencyMigrationRisk`, and (b) a matching `XP-NNN` row to `crossProjectDependencies`. An upstream-precondition row must have concrete `requiredWork` / `verificationSignal` / `howToStart` — `validators/validate-run.py` enforces that a DM `cross-project` ⇒ at least one `direction: upstream-precondition` XP row, and the schema enforces non-empty row fields. A cross-project dependency is recorded as this structured precondition, not as a soft Recommended Next Step. A single-project plan uses an empty array.
  - **recommendedNextSteps policy:** keep the substance of cross-project preconditions/carries in `crossProjectDependencies`, and put in `§3 Recommended Next Steps` only a pointer to that section (`§5.4 Cross-Project Dependencies`) — no double recording.
  - **Resuming from an approval blocker (BLOCKING).** When this report carries a progress-blocking `blocks: approval` clarification (`open`, or `request-revision` / `reject`), `recommendedNextSteps[0]` MUST be a command the reader can run now: `/okstra-user-response`, and the `--answered-clarifications` re-run in that step's `text` or `commands`. Do not write "The Okstra lead will …" as the first step. Point them at `okstra recap assemble`, which prints the answered ids, the exact flag value, the sidecar paths, and whether the re-verification would fall back to full. Do not restate those values here: they are unknown while you write, because the user has not answered yet. Do not tell the reader to start another planning run before the answers exist. An `accept-risk` / `select` / `answer` already recorded is not this case. **Enforced:** `validators/validate-run.py` `_validate_rerun_guidance`.
  - **Asking for approval (BLOCKING).** When `outcome` is `plan-ready` and no `blocks: approval` row still blocks progress, one `recommendedNextSteps` entry MUST tell the reader to approve (`--approve` or the in-session wizard). A remaining `blocked-by-disagreement` whose rows the user already accepted does not send them back to planning. Do not recommend another `implementation-planning` run. **Enforced:** `validators/validate-run.py` `_validate_approval_guidance`.
  - validation checklist (pre / mid / post) — each item is an exact command or observable outcome
  - rollback strategy — exact revert path (commits, flags, migrations) and the signal that triggers rollback
  - **Requirement admissibility (scope boundary):** a brief line becomes a Requirement Coverage row only when **a stage can satisfy it by changing files in this repository** — source, tests, config, or deployment *manifest files*. A line whose satisfaction needs a person's approval, a ticket status change, or an action against live infrastructure (applying a manifest, a cutover, creating a dashboard or alert, validating in staging/production) is NOT a requirement for this phase: it belongs to the brief's `## External Gates`, and this plan neither creates a stage for it nor cites it in coverage. Briefs generated by `okstra-brief-gen` pre-split these into the end-state sections `## Expected Behavior` / `## Preserved Behavior` / `## Expected Outcome` (admissible) and `## External Gates` (not); when reading an older brief that carries a raw Definition-of-Done checklist, apply the same test line by line. The boundary is the *action*, not the topic — "add the flag to `values-prod.yaml`" is admissible, "apply that manifest to prod" is not. Planning an operational stage this phase cannot execute (see the run-scope rule above forbidding deployments) produces steps whose commands never resolve, which the §5.5.9 gate then correctly blocks — the plan must not create that deadlock in the first place.
  - **Scope provenance (BLOCKING):** every Requirement Coverage row's `Source` cell must be exactly one of three forms, and every Stage Map stage must be cited by at least one coverage row's `Covered by`. The forms:
    - `brief:EB-001` / `brief:PB-001` / `brief:EO-001` — an end-state id the brief declares. Citing a heading instead is rejected when the brief pins ids: every brief carries the same generic headings, so a heading citation cannot say WHICH reporter line the requirement came from. Briefs authored before the end-state sections existed keep the older `brief:<heading>` form, and there a heading the brief does not contain is a fabricated requirement. **Enforced:** `validators/validate-run.py` `_validate_requirement_provenance` and `validators/validate_fanout.py` `_check_provenance`, both via `scope_provenance.brief_citation_problem`.
    - `derived:R-NNN — <one-line rationale>` — derived from another row in the same table. The chain must terminate at a `brief:` or `contract:` row and must not cycle. Use this for genuine technical consequences: a migration stage is `derived:R-003 — R-003 cannot be satisfied without a schema change`.
    - `contract:<rule>` — an artifact okstra's own phase contract mandates, so it has no brief line to cite. The allowlist is exactly `decision-record-step` (the §5.4 Decision Drafts materialization step) and `glossary-step` (the glossary proposal step); the SSOT is `scripts/okstra_ctl/scope_provenance.py`. Never widen this form to launder work the brief did not ask for.
    An item you can give none of these three sources to is **not a requirement and not a stage**. Its only admissible outlet is a `## 1. Clarification Items` row with `Blocks=approval`, carrying the recommendation format from `_clarification-recommendation.md`. Do not fold it into an option, a stage, or a step "while we are in here" — that is the scope expansion this rule exists to stop. This makes concrete the planning-input rule that any change beyond what `Requirement Summary` explicitly demands is out of scope by default. **Enforced:** `validators/validate-run.py` `_validate_requirement_provenance` (source resolution) and `_validate_stage_has_requirement` (no stage without a requirement).
    - **The reach of this gate — do not over-trust it.** What is mechanically enforced is the *form* of each source, that a cited `brief:` id is one the brief actually declares (or, on a pre-end-state brief, that the heading literally exists), that a `derived:` chain terminates without cycling, and that no stage is uncited. What is **not** enforced is whether the cited source genuinely demands the requirement. The id form closes the older loophole — a brief no longer offers generic headings any invented work could be hung on — but it leaves one open: attaching a requirement the cited `EB-NNN` does not actually ask for still parses clean, because no machine reads that id's sentence and compares it to your row. The gate's value is that it forces every item to name a specific reporter line and makes fabrication explicit and auditable — judging whether that line actually demands the item remains a reviewer / `DISAGREE(f)` responsibility, and passing this gate is never evidence that the scope is justified.
    - **Stage citation format — enumerate, never range (scale gate):** the reverse check reads each `Covered by` cell as prose, so a stage counts as cited only when its number is anchored to a `Stage` / `Stages` word on the same line. These read: `Stage 2`; `Stage 1, Stage 2, Stage 3`; `Stages 1, 2, 3`; `Stages 1, 2, and 3`; and `and` / `&` conjunctions. A bare number with no `stage` word anchoring it is NOT read as a citation, so `covered by the selected plan, step 4` cites nothing. **A range cites only its two endpoints:** `Stages 1-3` cites 1 and 3, and stage 2 stays uncited — write every stage out. Range syntax (`-`, `to`, `through`) is still parsed, so `Stages 7-8` is a valid two-stage citation; what it cannot do is stand in for an interior nobody named. **Enforced:** `validators/validate-run.py` `_validate_stage_has_requirement` via `okstra_ctl.stage_citations.enumerated_stage_numbers` fails the plan when any Stage Map stage is cited by no coverage row.
    - **Why enumeration is the scale gate.** The number of stages a plan carries is not bounded by any threshold — a genuinely large requirement may need many, and okstra does not guess a ratio. What IS bounded is how cheaply a plan can *claim* coverage of them: one `Stages 1-64` cell used to satisfy the reverse check for the whole map while the planner confirmed nothing, so scale grew for free. Enumeration prices it — every stage you claim costs you the act of naming it and asking whether this requirement is really satisfied there. A plan that cannot bring itself to type the numbers is telling you the stages are not all needed. The typing is the confirmation, so do not batch it mechanically: a row listing `Stages 1, 2, 3, ..., 12` you did not check one by one is the same rubber stamp with more characters.
    - Because that reader only sees prose, it still cannot tell a citation from a mention: `Stage 1 (superseded by Stage 2)` counts Stage 1 as cited. Cite the stages a requirement is actually satisfied by, not stages merely mentioned.
  - **Requirement Coverage (mandatory, §5.5.8):** selected-direction plans preserve the original requirement IDs and link each row to `stageRefs`, `stepRefs`, `validationRefs`, and `fileRefs`; exact forward and reverse coverage is enforced by `validate_selected_direction_plan`. Legacy candidate-comparison plans retain one `R-NNN` row per concrete requirement and the existing Option Candidate plus Stage/Step `coveredBy` semantics. The exact `P-Req-*` queue comes from `scripts/okstra_ctl/plan_items.py` in both branches.
  - **Every coverage and validation row states the stages it belongs to.** Put the stage numbers in `stageRefs` on each `### Requirement Coverage` row and each `### Validation Checklist` row — the same integers the row's `Covered by` prose or its check already names. `coveredBy` stays as it is; this is the machine-readable form of the same fact.

    Prose is not a substitute. Reading a stage out of `Covered by` needs a regular expression over a sentence, and a gate cannot be scored on that. Without the field, every requirement and validation row counts against whatever stage is being started, including rows that belong only to stages already finished or not yet begun — measured on one run, that is 9 of 13 blockers, 6 of them on frozen stages no amount of planning can close.

    Omit `stageRefs` only for a row that genuinely spans no stage. An omitted field is read as "applies to every stage", which is the safe reading and the current behaviour, so an older plan without it is unaffected.
    - **Legacy compatibility details:** assign `R-001`, `R-002`, ... in source order. `Source` uses the existing `brief:` / `derived:` / `contract:` grammar. A `covered` row names the specific Option Candidate and Stage/Step. A gap, blocked clarification, or unaccepted deviation keeps the gate non-passing. `validators/validate-run.py` retains `_validate_requirement_coverage_covered_by`, `_validate_requirement_deviations`, `_validate_gate_blocked_by`, and `_independent_coverage_blockers` enforcement for this branch.
  - **Review-rule compliance plan:** when a project-local review rule pack is found, the chosen realization MUST include the design implication of those rules in its File Structure / interfaces / blast-radius notes. For any helper or data transform used by more than one changed service, the plan must either place it in a shared module or explicitly justify why duplication is intentional. For any test step, the plan must state the observable behavior being asserted, not the internal collaborator call being pinned. For any exported/public method added or renamed, the step must carry the intended noun/side-effect semantics so implementation names can be reviewed before code is written.
  - the report record MUST include `frontmatter.approved: false` (report-writer always emits the unflipped value). The user authorises the next `implementation` run with `--approve` or the in-session wizard. Do NOT recreate any `User Approval Request` body block — the validator fails reports that contain one (see `validators/validate-run.py` deprecated patterns).
  - Selected-direction plans omit `implementation-option:` because `selectedDirectionRef` already fixes the direction; the legacy-only selector rule is owned by the legacy deliverable section above.
  - **the frontmatter `approved: false` line is rendered unconditionally; if the plan-body verification gate (§5.5.9) returns `blocked-by-disagreement` or `aborted-non-result`, the writer MUST keep `approved: false` and the validator refuses any report that ships with `approved: true` under such a gate result.**
  - every ambiguity flagged during pre-planning that the user must resolve before approval registered as a `Blocks=approval` row in the `## 1. Clarification Items` table (the unified table is the single home for these — the "no separate `Open Questions` block" rule is in the shared `_common-contract.md` clarification policy)
  - **Exact plan-item queue (BLOCKING).** Run `okstra plan-items prepare --narrative <report-narrative.md> --run-manifest <run-manifest>`, place `okstra plan-items prompt --run-manifest <run-manifest>` output verbatim in the verifier prompt, then run `okstra plan-items validate-prepared --narrative <report-narrative.md> --run-manifest <run-manifest>`. Python resolves the one convergence-owned state path from that run identity. Do not freely summarise, select, omit, reorder, or renumber the queue. Prompt headings use the compact subject and include the lossless payload. For every item, ask:

    ```text
    What concrete false-positive input, failure ordering, or omitted dependency
    would make this plan item incorrect even if its happy path succeeds?
    ```

    An `AGREE` note records the counterexample considered and its exclusion reason. If the judgement needs unavailable external material, record `verification-error`, not `DISAGREE`. **Enforced:** `validators/validate-run.py` `_validate_plan_item_extraction_completeness` compares the exact deterministic set, independently rejecting missing, unexpected, and duplicate plan-item IDs, including `P-Prep-*`.
  - **§5.5.9 Plan Body Verification (BLOCKING).** After report-writer finishes the draft, the lead runs a worker peer-review round on the persisted queue. Selected-direction plans begin with `P-Dir-1`; legacy candidate-comparison plans begin with `P-Opt-*`; both continue with the shared execution items. The fixed order remains initial verification → one planner self-fix → targeted re-verification → user gate. Gate recomputation, extraction completeness, approval-context reconciliation, and self-fix limits remain enforced by `validators/validate-run.py`; verdict details and dissent format are owned by `prompts/lead/plan-body-verification.md`.
  - **Approval decision state.** The lead records active and carried decisions through `okstra approval-decision`. Every option carries `disposition`, one `reach`, and optional `scopeEffects`. A resolved decision names existing `A-NNN` checks; report assembly derives `approvalContext`, status, resolution, and reverse links.
    - `open → answered` when the raw user response is recorded
    - `answered → resolved` after the selected disposition is applied, when that work completed
    - `open → obsolete` only when a plan change removes the question
    `open` blocks until the user judges. `answered` with `accept-risk` / `select` / `answer` does not block. `request-revision` / `reject` still block unless this report's `supersessionLedger` already incorporated that id. Do not move `answered` back to `open` because a check failed. **Enforced:** `scripts/okstra_ctl/clarification_items.py` `row_blocks_progress`, `validators/validate-run.py` `_validate_approval_context`, run-prep `scripts/okstra_ctl/run.py` `_validate_approved_plan`.
  - **Terminal approval evidence.** A resolved correctness-critical decision names a later successful evaluation through `resolutionInput.checkRefs`. Each referenced activity carries the same `C-NNN` in `clarificationRefs[]`, the affected `planItemIds[]`, zero-exit commands, and the plan-body state result. Report assembly rejects a missing activity or reverse link before publication. **Enforced:** `scripts/okstra_ctl/report_assembly.py::_clarification_row` and `_attach_plan_backlinks`.
  - **Decision-record evaluation (sole owner)**: this phase is the **single owner** of decision-record evaluation in the okstra lifecycle. The brief never evaluates or drafts decision records — it only forwards `adr-candidate:*` signals. Every `adr-candidate:*` entry inherited from the brief's `Open Questions` is a mandatory evaluation target. In addition, evaluate every decision the chosen realization introduces against the three criteria:
    1. **Hard to reverse** — would changing the decision later cost meaningfully more than deciding now?
    2. **Surprising without context** — would a future reader, seeing only the code, wonder "why was it built this way?"?
    3. **Real trade-off** — were there named alternatives, and was one picked for specific reasons?
    If **all three** hold, record a decision draft as a `decisionDrafts[]` row (rendered in §5.4 `### Decision Drafts`, one `#### <number>-<slug>` subsection each). Each row carries `number` (= `(max existing in <PROJECT_ROOT>/.okstra/decisions/ + 1)` zero-padded to ≥4 digits), `slug` (kebab-case), `status: Proposed`, and the `context` / `decision` / `consequences` / `alternativesConsidered` fields (`alternativesConsidered` names the rejected alternatives and why). If any of the three criteria is missing, do NOT raise a draft — instead record a `skippedAdrCandidates[]` row (`topic` + `reason: <criterion that failed>`, rendered as a `skipped adr-candidate: … — reason: …` line under the same section) so the next reader knows the candidate was evaluated and intentionally dropped.
    The decision files are NOT written by this phase. The approved plan's stepwise execution order MUST include the step `Create <PROJECT_ROOT>/.okstra/decisions/<NNNN>-<slug>.md from the §5.4 Decision Drafts subsection <number>-<slug>` (materializing the structured fields into the file's `## Status / ## Context / ## Decision / ## Consequences / ## Alternatives Considered` shape) so the `implementation` run commits the file inside okstra's subtree. `validators/validate-run.py` enforces this: a non-empty `decisionDrafts` whose stages carry no stepwise step referencing `.okstra/decisions/` is `contract-violated`.
  - **Glossary proposals**: if a term or definition should become okstra institutional memory, add the step `Update <PROJECT_ROOT>/.okstra/glossary.md: <term> = <definition>` to the stepwise execution order. Use no other project-memory path.
- No-placeholder rule (plan failures — reject any realization detail or step that contains these):
  - "TBD", "TODO", "implement later", "fill in details", "add appropriate error handling", "handle edge cases", "write tests for the above" without actual test code
  - "similar to another plan/task" without repeating the concrete content (readers may consume sections out of order)
  - references to types, functions, flags, or files that no other step or realization detail defines
  - steps that describe *what* to do without showing *how* (commands, code, or exact diffs are required for any code-touching step)
- Self-review pass before finalising the report (the Okstra lead runs this; do not delegate it):
  1. **Spec coverage** — for every requirement in the task brief, point to the chosen realization and stages/steps that satisfy it in Requirement Coverage. Follow the selected-direction or legacy compatibility row shape declared above. List gaps explicitly as `gap` or `blocked C-NNN`. A deliberate alternative is `documented-deviation` only when its decision references resolve and its approval disposition is user-confirmed or names an open approval blocker; a publishable gate permits only `covered` or an accepted deviation.
  2. **Placeholder scan** — search the report for the patterns in the No-placeholder rule above and fix inline.
  3. **Internal consistency** — the chosen realization's file list, interfaces, stages, and validation must agree on paths, names, and signatures. A symbol called `clearLayers()` in one field and `clearFullLayers()` in the steps is a bug.
  4. **Ambiguity check** — any requirement that could be read two ways must be made explicit or moved to the `## 1. Clarification Items` table as a `Blocks=approval` row.
  5. **Scope check** — if the plan now spans multiple independent subsystems, split it into separate planning runs rather than shipping an oversized plan. Then walk the plan in the expansion direction: for every stage, name the Requirement Coverage row that demanded it, and for every requirement row, read its `Source` cell as a skeptic — does the cited brief heading actually exist, and does a `derived:` rationale state a real technical consequence rather than a preference? Move anything that fails to a `Blocks=approval` clarification row.
  6. **Review-rule preflight check** — when a project review rule pack applies (cited by the brief, or listed by `okstra model-io project-context --project-root <PROJECT_ROOT> --task-ref <task-ref>`), map each relevant rule to the chosen realization. Reject the draft if it knowingly creates a violation that the later PR reviewer would flag, unless the plan records a specific rationale and follow-up. In particular, scan for repeated helper stacks across planned files, tests that assert delegation to the same calculator/helper they exercise, public names that hide side effects, domain rules placed in repositories/adapters, and APIs made dead by this change.
  7. **Plan-body verification reconciliation (BLOCKING for implementation-planning).** For every §5.5.9 `planItems[]` entry whose *gate class after stage scope* is `majority-disagree` (in-scope execution only), set that item's `clarificationId` to a `C-<N>` row that MUST exist in `## 1. Clarification Items` with `Kind` chosen per the standard policy and `Blocks=approval`. Do **not** promote `observed` / `deferred` / `record` items — those belong in `setAside`. Raw vote `majority-disagree` is not enough; promoting it is how a frozen or unreached stage kept opening new `C-NNN` rows. **Enforced:** `validators/validate-run.py` `_validate_plan_body_clarification_matching` uses `_plan_item_gate_class` and fails when an in-scope execution majority-disagree item has no `clarificationId`, or its `clarificationId` is dangling / points at a non-`approval` row. For `partial-consensus` and `dissent-isolated` plan-items, the dissenting opinion lives in §5.5.9 `Dissent log` and is NOT promoted to §5.
  8. **Stage Map self-check** — for every stage, count the effective rows of its `Stepwise Execution Order` table by hand; reject the draft if any stage exceeds 8. Confirm each stage declares a non-empty `Slice value:` and `Acceptance:` line, the three `Test case (success|boundary|failure):` lines (or carries a `TDD exemption:` line), and that its first step `action` starts with `RED:` with a later `GREEN:` — this is what validator S10 enforces, including S10d on the test-case lines. Read each stage's three test-case lines as a reviewer: reject any that restates the happy path in all three slots, leaves `boundary` blank, or writes `N/A` where a real edge input exists. Walk the `depends-on` graph and confirm it is a DAG (no cycle, no self-reference). For each `depends-on` link, confirm it encodes a real data/contract dependency — do NOT add links to serialise unrelated work, and do NOT split a stage merely to create more parallel stages. **Parallel-safety:** for every pair of `depends-on (none)` stages, confirm their `Stage Exit Contract` predicted file sets are disjoint; if they share a file, merge them or add a `depends-on` link (validator S9 rejects overlap). **Project-boundary:** confirm no stage mixes edits from two projects (different repo/`PROJECT_ROOT` or different top-level deployable module); if any stage does, split it per project. For multi-project plans, confirm each stage's `title` carries its `[<project>]` tag and the `Cross-project parallelism:` line under the table records the parallel-vs-sequenced determination (with the forcing dependency) for every project pair; for cross-repo work, confirm it is split into separate per-repo runs (required — one run structurally cannot touch another repo) rather than crammed into one task's stages.
  9. **Cross-project dependency check** — confirm you have not missed a dependency on another repo / another top-level deployable module / a published package. If `dependencyMigrationRisk` has a `kind: cross-project` row, confirm a matching `direction: upstream-precondition` `XP-NNN` row exists in `crossProjectDependencies`, and re-read as a reviewer whether its `requiredWork` is the concrete work the other side must actually build rather than an abstract phrase ("other side's work done") — validator S only checks existence, so concreteness is the self-review's responsibility. Confirm cross-repo work is split into a separate run + XP row instead of being crammed into one task's stages, and that the cross-project substance is not duplicated in `§3 Recommended Next Steps` but lives only in `§5.4 Cross-Project Dependencies`.
  10. **Decision-draft materialization check** — when `decisionDrafts` is non-empty, confirm as a reviewer which stage's stepwise order contains the matching materialization step (creating `.okstra/decisions/<NNNN>-<slug>.md`) and that the number of drafts corresponds 1:1 with the materialization steps. The validator only checks the *existence* of the step, so the `<NNNN>-<slug>` correctness and count correspondence are the self-review's responsibility.
  11. **Variation-point & seam check** — read `variationPointAnalysis` as a skeptic. Is `hasMultipleImplementations` honest against the brief and the sibling code you inspected during pre-planning, or was `false` chosen because it is the cheaper field to fill? For every point with `extract: true`, confirm the `extractionDecision` names a real interface (a `port` for a hexagonal project, not a shared helper) and a `coveredBy` stage that exists in the Stage Map — an interface no stage builds is a decision nobody executes. Then read the chosen realization's `testSeams`: each `injectedAs` must name a construction or wiring point a test can actually substitute at, not a symbol the test would have to re-implement — a seam nothing can be injected into leaves the executor writing self-mocks. An empty `testSeams` array is only acceptable when you can defend it in one sentence; the validator accepts it either way, so this is the check that catches an unfilled field posing as a decision.
  12. **Approval blast-radius check (BLOCKING).** Every approval clarification must be reachable from `planItems[].clarificationRefs[]` or a requirement-coverage blocker. Report assembly derives plan-item links from activity `clarificationRefs[]` plus `planItemIds[]`; `okstra incremental-scope` reads the resulting reverse links.
      - **The link must resolve to a stage, not merely exist.** `incremental-scope` reads the stage number out of a `P-Step-<stage>.<step>` / `P-Prep-S<stage>-<kind>` plan-item id, out of `stageScope` / `stageRefs` on the linked plan item or coverage row, or out of a `Stage N` citation in the blocked coverage row's `coveredBy`. A `P-Req-*` / `P-Val-*` id is positional, so a blocker linked only that way MUST carry `stageRefs` or cite the stage in `coveredBy`. Writing the blocked row's `coveredBy` as prose with no `Stage N` in it — `No stage.`, `Partly covered — …` — satisfies nothing unless `stageRefs` is present: the row passes the link check and the next re-run cannot place the answer without asking for stage numbers.
      - What to write when no stage covers the requirement yet: name the stage the answer will change, not the stage that satisfies the requirement today. A `Blocks=approval` row is admissible only when, absent an answer, `implementation` would produce wrong or unsafe code (see the admissibility rule above) — so some stage's code is at stake by construction. If you genuinely cannot name one, the row fails the admissibility test and belongs in `## 5. Missing Information and Risks` with `Blocks=none`, not in the approval gate.
      **Enforced:** `validators/validate-run.py` `_validate_approval_clarification_backtrace` — one failure for a missing link, a separate one for a link that resolves to no stage.
