okstra 0.183.2 → 0.184.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +1 -1
  2. package/docs/architecture.md +2 -2
  3. package/docs/cli.md +7 -4
  4. package/docs/project-structure-overview.md +1 -1
  5. package/docs/task-process/README.md +1 -1
  6. package/docs/task-process/common-flow.md +2 -3
  7. package/docs/task-process/error-analysis.md +3 -4
  8. package/docs/task-process/final-verification.md +2 -3
  9. package/docs/task-process/implementation-planning.md +2 -3
  10. package/docs/task-process/implementation.md +2 -3
  11. package/docs/task-process/release-handoff.md +3 -4
  12. package/docs/task-process/requirements-discovery.md +3 -4
  13. package/package.json +1 -1
  14. package/runtime/BUILD.json +2 -2
  15. package/runtime/prompts/launch.template.md +8 -7
  16. package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
  17. package/runtime/prompts/lead/plan-body-verification.md +27 -19
  18. package/runtime/prompts/lead/report-writer.md +3 -3
  19. package/runtime/prompts/profiles/_implementation-executor.md +1 -0
  20. package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
  21. package/runtime/prompts/profiles/implementation-planning.md +9 -11
  22. package/runtime/prompts/wizard/prompts.ko.json +7 -10
  23. package/runtime/python/okstra_ctl/conformance.py +37 -1
  24. package/runtime/python/okstra_ctl/incremental_scope.py +84 -39
  25. package/runtime/python/okstra_ctl/plan_items.py +410 -1
  26. package/runtime/python/okstra_ctl/plan_items_cli.py +346 -31
  27. package/runtime/python/okstra_ctl/render.py +4 -0
  28. package/runtime/python/okstra_ctl/wizard.py +39 -73
  29. package/runtime/schemas/final-report-v2.0.schema.json +12 -0
  30. package/runtime/schemas/final-report-v3.0.schema.json +12 -0
  31. package/runtime/skills/okstra-run/SKILL.md +2 -2
  32. package/runtime/validators/validate-run.py +164 -66
  33. package/runtime/validators/validate_session_conformance.py +24 -5
package/README.md CHANGED
@@ -240,7 +240,7 @@ Major workflow changes added to `main` after 0.8.0:
240
240
  - **Implementation direction selection before detailed planning** — `implementation-option-selection` is a read-only lifecycle phase between error analysis and planning. Comparison mode evaluates the merged raw candidates and displays at most three ranked directions. Every displayed direction has `coveragePercent == 100`, `scopePrecisionPercent == 100`, no unmapped commitment, and no contradicted requirement. The user confirms one direction in a separate `DIRECTION SELECTION` response before a new planning run starts with `--selected-direction`. `implementation-planning` then expands that one direction into files, stages, validation, and rollback, and the resulting plan still requires its own approval. A preselected direction is validated without generating alternatives. Existing approved plans without `planningContract: selected-direction` keep the legacy `--implementation-option` execution path.
241
241
  - **Multi-stage `implementation-planning` / `implementation`** — `implementation-planning` always produces a Stage Map and N stage sections. Each stage has no more than six steps, and stages with `depends-on (none)` can be implemented concurrently in separate `implementation` runs. Each `implementation` invocation runs a single stage, selected with `--stage <auto|N>`, and creates an evidence sidecar at `carry/stage-<N>.json` for automatic carry-in to the next stage. The `implementation-planning` run directory accumulates `consumers.jsonl` reverse links that record which run consumed each stage.
242
242
  - **AI-prepared design preparation (implementation-planning → implementation)** — `implementation-planning` detects which stages need design input (domain contract, DB/table schema, external interface, transaction/consistency, transformation mapping, lifecycle, rollout/observability, manual user test) and has the AI draft a concrete proposal first, instead of handing the user an empty design document. Each item is assessed as `ready`, `provisional`, `blocked`, or `not-applicable`; a simple task may declare `no-design-inputs`. Phase 7 materializes an Okstra-owned request under `design-prep-requests/`, and `okstra design-prep <list|show|write>` or the okstra-run wizard records the confirmed answer as an **append-only** revision under `design-prep-inputs/`—neither path ever edits the approved planning snapshot. Before creating its worktree, `implementation` resolves only the items its selected stage cites in `stageRefs`: safe `provisional` assumptions are injected into the executor prompt so work proceeds, while an unsafe open decision makes only that stage wait or replan. A markerless legacy plan continues with a `legacy-unassessed` warning. Storage authorities: [`docs/architecture/storage-model.md`](docs/architecture/storage-model.md). CLI: [`docs/cli.md#okstra-design-prep`](docs/cli.md#okstra-design-prep).
243
- - **Phase 6 plan-body verification (implementation-planning only)** — Immediately after the report-writer worker drafts the final report and before the user approval gate, the lead performs one post-verification round. A selected-direction plan starts with `P-Dir-1`; a legacy candidate plan retains `P-Opt-*`. Both branches add `P-Step-*`, `P-Dep-*`, `P-Val-*`, and `P-Rb-*` items and ask every analyzer worker for an `AGREE`, `DISAGREE(a-e)`, or `SUPPLEMENT` verdict. The aggregate result is `passed`, `passed-with-dissent`, `blocked-by-disagreement`, or `aborted-non-result`. The frontmatter `approved` field is always published as `false`; a blocking result keeps it false and becomes a row in `## 1. Clarification Items`. For fast iteration, opt out with `--no-plan-verification`. Contract details: the "Plan-body verification mode" section of [`prompts/lead/convergence.md`](prompts/lead/convergence.md) and [`docs/cli.md#--no-plan-verification`](docs/cli.md#--no-plan-verification).
243
+ - **Phase 6 plan-body verification (implementation-planning only)** — Immediately after the report-writer worker drafts the final report and before the user approval gate, the lead performs one post-verification round. A selected-direction plan starts with `P-Dir-1`; a legacy candidate plan retains `P-Opt-*`. Both branches add `P-Step-*`, `P-Dep-*`, `P-Val-*`, and `P-Rb-*` items and ask every analyzer worker for an `AGREE`, `DISAGREE(a-e)`, or `SUPPLEMENT` verdict. The aggregate result is `passed`, `passed-with-dissent`, `blocked-by-disagreement`, or `aborted-non-result`. The frontmatter `approved` field is always published as `false`; a blocking result keeps it false and becomes a row in `## 1. Clarification Items`. A one-stage `no-design-inputs` plan keeps extraction and one verification round but sets `gating=false`, so the self-fix loop and a sweep batch do not run. For fast iteration, skip the round entirely with `--no-plan-verification`. Contract details: the "Plan-body verification mode" section of [`prompts/lead/convergence.md`](prompts/lead/convergence.md) and [`docs/cli.md#--no-plan-verification`](docs/cli.md#--no-plan-verification).
244
244
  - **Brief as translation layer + Step 6.5 reporter batch confirmation** — `okstra-brief-gen` converts external input—an issue ticket, requirements document, or user message—verbatim and marks okstra-added content as labeled augmentation. Step 6.5 asks the user to confirm in one batch whether that conversion changed meaning and records the result in `Reporter Confirmations`. Every analysis profile requires this section before phase analysis begins; `validators/validate-brief.py` enforces the requirement.
245
245
  - **Artifact-home rule (`.okstra/`)** — `<project>/.okstra/` is the only project artifact root owned by okstra. Anything outside this root is not okstra memory and may be read only when explicitly cited in Source Material or Reporter Confirmations. Writing outside the root requires the same explicit requested path. Internal equivalents are `glossary.md` for terminology and `decisions/<NNNN>-<slug>.md` for decision records, evaluated during `implementation-planning`.
246
246
  - **Single-owner report inputs and separate human views** — New runs use report contract v3. The report writer authors narrative Markdown while approval, activity, execution, convergence, design-preparation, and plan-body state remain in separate single-owner artifacts. Phase 7 validates those inputs and atomically assembles `final-report-<task-type>-<seq>.data.json` against `schemas/final-report-v3.0.schema.json`, then derives a task-specific human HTML view. The full reading copy Markdown still uses `templates/reports/final-report-v2.template.md` and is rendered on demand with `okstra render-final-report`. Each of the eleven task types owns a dedicated template under `templates/reports/html/tasks/`. Historical `schemas/final-report-v2.0.schema.json` and schema v1 reports remain readable; new runs do not write them.
@@ -747,8 +747,8 @@ The latest final report opens in `$EDITOR`, and after saving, the same phase rer
747
747
  The default for an `implementation-planning` clarification rerun is **full re-verification**. However, if an answer has only local impact and the code is unchanged, the lead re-verifies only the downstream closure of affected stages and carries forward the other stages' plan-item verdicts from the previous run. The decision is split between deterministic CLI logic and lead judgment.
748
748
 
749
749
  - **C1 (CLI decision, deterministic)**: The `executorWorktree.baseRef` in the previous run's `state/active-run-context-implementation-planning-<prev-seq>.json` must match the current run's base-ref SHA. If they differ, the code changed, so the result is immediately `full`.
750
- - **C2 (lead judgment)**: The set of Stage Map stage numbers affected by the answer. Never include a number not in the Stage Map. If the mapping is uncertain or the answer reverses the selected Option/approach, pass an **empty set** and fall back to `full`.
751
- - **Closure cutoff**: `okstra incremental-scope` calculates the `downstream_stage_closure` of affected stages in the `implementationPlanning.stageMap` dependency graph. If its size is **more than half** of all stages, the result is `full`; otherwise it outputs `{mode, reverify_stages, carry_stages, reason}` JSON.
750
+ - **C2 (lead judgment)**: The set of Stage Map stage numbers affected by the answer. Never include a number not in the Stage Map. An answered `C-NNN` that traces to no stage is `mode: unresolved`, not full — ask the user for those stage numbers (`--impacted`) rather than widening the whole re-run. If the answer reverses the selected Option, Stage Map, or approach, pass `--full-reason`; that is the only structural path that still forces `full`.
751
+ - **Closure cutoff**: `okstra incremental-scope` calculates the `downstream_stage_closure` of affected stages in the `implementationPlanning.stageMap` dependency graph. If its size is **more than half** of all stages, the result is `full`; otherwise it outputs `{mode, reverify_stages, carry_stages, reason}` JSON. When the wizard pin is `auto` and the CLI returns `mode == "incremental"`, the lead must not upgrade to full.
752
752
 
753
753
  ```bash
754
754
  okstra incremental-scope --prev-data <prev data.json> --cur-base-sha <sha> --prev-base-sha <sha> --impacted 2,3
package/docs/cli.md CHANGED
@@ -292,7 +292,7 @@ Pins how much of an `implementation-planning` clarification re-run is verified a
292
292
  - `full` — re-verify every stage. The lead still records the decision, passing `--full-reason "user requested full re-verification"`.
293
293
  - a stage-number list such as `2,3` — the named stages enter the lead's `--impacted` set, unioned with whatever it resolves itself. The CLI's dependency closure and cutoff still apply on top, so a wide enough pin correctly resolves to full.
294
294
  - Requires `--clarification-response`, and only `--task-type implementation-planning` accepts a non-empty value. Without a carried report there is no prior Stage Map to narrow against.
295
- - The wizard asks for this **only when the re-run is narrowable** that is, when every answered `C-NNN` traces back to a stage in the prior report. When one does not, full is already fixed and the confirmation block's `reverify-scope` line names the unlinked id instead of offering a choice that cannot change the outcome. Direct stage input is validated against the prior report's Stage Map, so a number that plan never had is refused at the prompt.
295
+ - The wizard asks for this when the re-run is narrowable **or** an answered `C-NNN` traces to no stage. An unlinked id does not freeze the run at full: the picker then offers stage numbers (recommended) or full, and omits `auto`. Direct stage input is validated against the prior report's Stage Map, so a number that plan never had is refused at the prompt. Empty input is auto only when every answered id already traces to a stage.
296
296
  - The value reaches the lead as the `REVERIFY_SCOPE_MODE` / `REVERIFY_SCOPE_STAGES` tokens the "Clarification Response Carried In" section of the lead prompt reads. It narrows or widens the inputs to `okstra incremental-scope`; it does not bypass that decision.
297
297
 
298
298
  ### `--resume-clarification`
@@ -701,6 +701,7 @@ Disables the Phase 6 plan-body verification round for the `implementation-planni
701
701
 
702
702
  - **Enabled (default)**: Immediately after the report-writer worker drafts its narrative in Phase 6, the lead extracts the synthesized plan into `P-*` items and dispatches them for reverification to every analyzer worker: `claude`, `codex`, and opted-in `antigravity`. A selected-direction plan uses `P-Dir-1` plus its step, dependency, validation, rollback, requirement, preparation, and variation items. A legacy candidate plan retains `P-Opt-*`. Worker verdicts (`AGREE` / `DISAGREE(a-e)` / `SUPPLEMENT`) are aggregated into one of four gate results: `passed`, `passed-with-dissent`, `blocked-by-disagreement`, or `aborted-non-result`. The approval control is available only for `passed` or `passed-with-dissent`. Items with majority DISAGREE become rows with `Blocks=approval` in `## 1. Clarification Items`. There is no automatic revision; the user answers and resumes the same phase.
703
703
  - **Disabled (with `--no-plan-verification`)**: The entire Phase 6 substep is skipped and the Approval marker is always rendered at the top of the final report, matching legacy behavior. This is a fast-iteration opt-out and is not recommended for a handoff-ready plan.
704
+ - **Advisory auto-path (not this flag)**: when `designPreparation.mode` is `no-design-inputs` and the Stage Map has exactly one row, `okstra plan-items prepare` sets `convergence.planBodyVerification.gating=false`. Extraction and one verification round still run; the self-fix loop and a sweep batch do not. Two-or-more stages, a PREP item, or non-empty design-preparation items keep `gating=true`.
704
705
  - The flag records `false` in the manifest at `convergence.planBodyVerification.enabled`. The resume command must include the same flag to preserve behavior; `_canonical_argv` guarantees faithful emission on resume.
705
706
  - For the detailed round protocol, verdict semantics, and state-file schema, see the "Plan-body verification mode (implementation-planning only)" section of `prompts/lead/convergence.md`.
706
707
 
@@ -815,10 +816,12 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
815
816
  | `okstra plan-items extract (--narrative <report-narrative.md>\|--data <historical-data.json>) --output <items.json>` | Deterministically extract the complete implementation-planning `P-*` queue. Contract v3 reads the report-writer narrative before publication; `--data` is the historical v2 reader. |
816
817
  | `okstra plan-items validate (--narrative <report-narrative.md>\|--data <historical-data.json>) --items <items.json>` | Require the persisted `P-*` queue to match a fresh deterministic extraction exactly. |
817
818
  | `okstra plan-items derivations --data <data.json> --response <user-response sidecar> [--clarification C-NNN]` | List the plan statements an answered clarification may have falsified. Extracts the symbols, paths, and ids the answer names (backticked spans plus `R-001` / `DEV-10174`-style ids) and reports every string in the plan body that mentions one, as a JSON pointer plus excerpt. Advisory: it says where a decision's subject is mentioned, never which mentions are now wrong — the supersession rule (`prompts/profiles/_common-contract.md`) requires the author to enumerate before editing, and this supplies the enumeration |
818
- | `okstra plan-items <prepare\|prompt\|validate-prepared> --run-manifest <path> …` | Bind the implementation-planning verification queue to the run manifest. `prepare` extracts the exact queue from `--narrative`; `prompt` emits its fixed lossless view; `validate-prepared` proves the prepared queue still matches the narrative. Python resolves the convergence-owned state path, so model callers never choose it. |
819
+ | `okstra plan-items <prepare\|prompt\|validate-prepared> --run-manifest <path> …` | Bind the implementation-planning verification queue to the run manifest. `prepare` extracts the exact queue from `--narrative` and, when `designPreparation.mode` is `no-design-inputs` and the Stage Map has one row, flips `convergence.planBodyVerification.gating` to `false` (stdout `Gating`). `prompt` emits its fixed lossless view; `validate-prepared` proves the prepared queue still matches the narrative. Python resolves the convergence-owned state path, so model callers never choose it. |
819
820
  | `okstra plan-items seed --narrative <report-narrative.md> --state <plan-body-verification.json>` | Create the convergence-owned `planBodyVerification.planItems[]` rows every verdict lands in, from the same deterministic extraction `extract` uses. The historical v2 form is `--data <data.json>`. Idempotent by id: an existing row keeps its verdicts and carried fields. Reports `seeded` / `existing` counts. |
820
821
  | `okstra plan-items apply-verdicts --state <plan-body-verification.json> --result <worker-id>=<result.md>… --round <N>` | Read each worker's Markdown verdict directly, validate every current `P-*` id, reject duplicate worker submissions, and overwrite that round's stored verdicts. The historical `--verdicts <file>` form remains automation compatibility only. |
821
- | `okstra plan-items complete-round --state <plan-body-verification.json> --run-manifest <current-run-manifest.json> --round <N> [--self-fix-note <item-id>=<markdown-file>]… [--self-fix-group <cause-file>=<item-id>[,<item-id>...]]… [--self-fix-stop-reason <all-resolved\|no-progress\|max-rounds-reached>]` | After `plan-verify` succeeds, atomically derive and record the round's per-item votes, gate result, participant counts from the actual assigned roster, immutable completion time, convergence history, and optional self-fix notes/groups read from Markdown files. Models do not write the state JSON. |
822
+ | `okstra plan-items complete-round --state <plan-body-verification.json> --run-manifest <current-run-manifest.json> --round <N> [--self-fix-note <item-id>=<markdown-file>]… [--self-fix-group <cause-file>=<item-id>[,<item-id>...]]… [--self-fix-stop-reason <all-resolved\|no-progress\|max-rounds-reached>]` | After `plan-verify` succeeds, atomically derive and record the round's per-item votes, gate result, participant counts from the actual assigned roster, immutable completion time, convergence history, and optional self-fix notes/groups read from Markdown files. Models do not write the state JSON. Stdout also carries `nextDispatch`. |
823
+ | `okstra plan-items next-dispatch --state <plan-body-verification.json> [--run-manifest <path>]` | After `apply-verdicts`, decide whether this round opens a worker batch. `kind: none` — missing-dependency `UNVERIFIABLE` only, no new batch. `kind: worker-correction` — re-prompt only those workers; peers stay idle. `kind: queue-reverify` — unsettled ties, those item ids only. A missing path is never environment-unverifiable. |
824
+ | `okstra plan-items correction-prompt --state <plan-body-verification.json> --run-manifest <path> --worker <id>` | Emit the planning-time environment-gap paragraph, then the assigned queue. The environment exception is first. Exits 2 unless `next-dispatch` named that worker as a blanket-UNVERIFIABLE correction target. |
822
825
  | `okstra error-log append-observed --out <errors.jsonl> --task-key <key> --phase <phase> --agent <assigned-worker-id> --agent-role worker --model <model> --error-type tool-failure --command-file <markdown-file> --command-kind <kind> --message-file <markdown-file> [--cause <cause> --evidence-file <kind=file>]…` | Worker-facing typed error recording surface. Python validates and serializes the JSONL record; a worker supplies scalar identity fields plus Markdown files for free-form command, message, and probe content, never a JSON sidecar or JSON argument. `sandbox-denied` requires both `targetProbe` and `controlProbe` evidence files. |
823
826
  | `okstra config <get\|set\|unset\|show> [key] [value] [--scope project\|global\|all]` | Manage persistent settings such as `pr-template-path` with atomic JSON writes |
824
827
  | `okstra memory <add\|list\|search\|show\|archive>` | Manage global conversation memory in `~/.okstra/memory-book`, a user-home store separate from project `.okstra/` and the CLI basis of the `save this in okstra` natural-language skill |
@@ -839,7 +842,7 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
839
842
  | `okstra task-list [--project-root <path>]` | Combine `list_project_tasks` and `read_latest_task` into JSON containing the task catalog and latest task |
840
843
  | `okstra task-show <task-key> [--project-root <path>]` | Summarize workflow, phase, status, and artifacts from the Task Read-Side Snapshot |
841
844
  | `okstra stage-map <task-key> [--cwd <dir>\|--project <dir>]` | Dump the task's implementation-planning Stage Map as JSON: `{ ok, taskKey, taskRoot, state, sourcePlanPath, stages:[{stage_number,title,depends_on,step_count}], doneStages:[int] }`. `state` is `ready` for one resolved source and `missing` when no Stage Map exists; corrupt or conflicting sources return structured non-zero errors instead of silently selecting another report. `doneStages` is read from the implementation-planning stage consumer state (with carry recovery). This is the read-side source `/okstra-schedule-gen [task-group]` uses to derive selectable unfinished stages and their completed dependency closure |
842
- | `okstra incremental-scope <args…>` | Decide re-verify vs carry-forward scope for an `implementation-planning` clarification re-run. Thin shim into `scripts/okstra_ctl/incremental_scope.py` (deterministic pure function): it reads the dependency graph from the prior run `data.json`'s `implementationPlanning.stageMap` and returns `mode:"incremental"` only when the base-ref SHA is unchanged and the affected stages' `downstream_stage_closure` covers at most half of all stages; otherwise it signals a full re-run. Used to bound the cost of a clarification re-run. `--preview --prev-data <path> --answered-clarifications <csv>` runs the link half alone — no base SHA, no side effects — and prints `{wouldForceFull, unlinkedIds, reason}`, so the cost of the next re-run is visible before `render-bundle` fixes a base commit |
845
+ | `okstra incremental-scope <args…>` | Decide re-verify vs carry-forward scope for an `implementation-planning` clarification re-run. Thin shim into `scripts/okstra_ctl/incremental_scope.py` (deterministic pure function): it reads the dependency graph from the prior run `data.json`'s `implementationPlanning.stageMap` and returns `mode:"incremental"` only when the base-ref SHA is unchanged and the affected stages' `downstream_stage_closure` covers at most half of all stages; `--full-reason` (selected option / Stage Map / approach) still forces `mode:"full"`. An answered `C-NNN` that traces to no stage returns `mode:"unresolved"` rather than full — pass `--impacted` with the stage numbers or `--full-reason`. `--preview --prev-data <path> --answered-clarifications <csv>` runs the link half alone — no base SHA, no side effects — and prints `{wouldForceFull, unlinkedIds, reason}`; unlinked ids set `wouldForceFull: false` and fill `unlinkedIds` |
843
846
  | `okstra incremental-carry <args…>` | Merge carried-forward plan-item verdicts into an incremental re-run. Contract v3 takes `--prev-data`, `--cur-narrative`, and the convergence-owned `--state`; it verifies carried stage rows and writes only `--out-state`, tagging copied verdicts with `carriedForwardFromSeq`. The historical v2 `--cur-data --out` form remains readable. Ownership, scope, item, or schema drift raises `CarryError` and forces a full fallback. |
844
847
  | `okstra code-review target --task-key <k> --stage <N> [--project-root <dir>] [--cwd <dir>] [--json]` / `okstra code-review target --branch <name> [--base <ref>] [--date <YYYY-MM-DD>] [--project-root <dir>] [--cwd <dir>] [--json]` | Resolve what a code review reads and where its result file goes. Output is always JSON, so `--json` only makes that explicit. `--project-root` and `--cwd` are shared pre-dispatch arguments and apply to both modes; `--cwd` is only consulted when `--project-root` is absent. Both modes return `{ ok, projectRoot, mode, worktreePath, branch, baseCommit, headCommit, reviewPath, round }`; stage mode additionally returns `taskKey`, `taskRoot`, and `stage`. Stage mode takes the diff base from the `base_ref` recorded on that stage's worktree-registry row when it was provisioned — not from a rule re-applied at review time — and names the result `.okstra/tasks/<task-group>/<task-id>/code-reviews/stage-<NN>.md`, where a re-review of the same stage becomes `-r2`, `-r3`, … (the `round` field). Only a legacy row provisioned before `base_ref` was recorded falls back to re-deriving the base through `stage_targets`, and a failure there is reported as `stage_base_unresolved`. `worktreePath` comes back empty whenever the stage worktree is not usable as a live checkout — the registry row is no longer `active` (whole-task final-verification released it), the row never carried a path, or the recorded directory is gone — and the review then reads the `branch` ref instead. Branch mode uses `--base` when given, otherwise the merge-base with the default branch (`refs/remotes/origin/HEAD`, else `main`/`master`), and names the result `.project-docs/code-reviews/<branch>/<YYYY-MM-DD>-<NN>.md`, where `<NN>` (the `round` field) is the next sequence number for that date — the highest already on disk plus one. Read-only: it resolves paths and creates no directory and no file, so the review directory does not exist until the caller writes the report. Backend for the okstra-code-review skill |
845
848
  | `okstra set-work-status <token> <todo\|in-progress\|blocked\|done> [--note <text>] [--task-group <g>] [--project-root <dir>]` | Update user-managed `workStatus` in task-manifest.json, along with `workStatusUpdatedAt` and, when `--note` is supplied, `workStatusNote`. `<token>` is a full task key or bare task ID. It uses the manifest renderer's serialization rules and returns `stage:"ambiguous"` plus `matches[]` when ambiguous |
@@ -249,7 +249,7 @@ Important modules:
249
249
  | `stage_ledger.py` | assembles the Stage Ledger handed to plan authoring — "what is already built" from the carry sidecar's plan, "which stage numbers are used" from the latest plan (ADR-0015 append-only, judged on the latest plan's `max`); it only joins `stage_targets` (status/lifecycle) and `stage_map` (source-of-stage) and serialises, owning no verdict. Carries `sourcePlan`/`latestPlan` and surfaces `planDivergence`; when the ledger cannot be read it emits the reason in plain text under the same heading instead of omitting the block |
250
250
  | `design_surfaces.py` | deterministic detection of an `implementation-planning` stage's design surface — matches the stage's file-path tokens/suffixes/patterns and action wording via `SurfaceRule` to derive which design input the stage needs among domain contract, DB/table schema, external interface, transaction/consistency, transformation mapping, lifecycle, rollout/observability, and manual user test, plus its evidence (`TriggerEvidence`). An unmappable structure raises `DesignSurfaceError` |
251
251
  | `design_prep.py` | fingerprint / materialize / resolve backend for design-preparation requests (CLI: `okstra design-prep <list\|show\|write>`) — computes an assessment fingerprint from the approved planning snapshot's `ASSESSMENT_FIELDS`, idempotently writes an Okstra-owned request under `design-prep-requests/`, and resolves the highest-revision append-only user response under `design-prep-inputs/` whose fingerprint matches as the effective response. Keeps the three authorities (report snapshot / Okstra request / user input) separate and never modifies the report or existing revisions. Sidecar I/O is protected by a directory-fd anchor + flock |
252
- | `incremental_scope.py` | incremental re-verification decision for an `implementation-planning` clarification re-run (deterministic pure function) — reads the dependency graph from the previous run data.json's `implementationPlanning.stageMap` and returns `mode="incremental"` only when the base-ref SHA is unchanged and the affected stages' `downstream_stage_closure` is at most half of all stages. CLI: `okstra incremental-scope` |
252
+ | `incremental_scope.py` | incremental re-verification decision for an `implementation-planning` clarification re-run (deterministic pure function) — reads the dependency graph from the previous run data.json's `implementationPlanning.stageMap` and returns `mode="incremental"` only when the base-ref SHA is unchanged and the affected stages' `downstream_stage_closure` is at most half of all stages; an unlinked `C-NNN` is `mode="unresolved"` (needs `--impacted`), not full. CLI: `okstra incremental-scope` |
253
253
  | `incremental_carry.py` | carry merge for an incremental re-run — verifies unchanged carried stage rows and merges their previous plan-item verdicts into the convergence-owned v3 plan state with a `carriedForwardFromSeq` tag. The historical v2 data.json form remains readable. Ownership, scope, or schema drift exits non-zero with `CarryError`. CLI: `okstra incremental-carry` |
254
254
  | `build_tools.py` | allowlist SSOT for deciding whether a plan's command cell invokes the project build toolchain (`npm`/`pytest`/`cargo`/`gradle`/… behind transparent leaders like `sudo`/`env`). The planning worktree has no dependencies installed, so `validators/validate-run.py` uses this to warn (advisory) when a toolchain stage declares no install precondition. Intentionally an allowlist, not a denylist, so unknown tokens go undetected rather than firing on `grep`/`sed` in every plan |
255
255
  | `stage_citations.py` | shared grammar SSOT for reading the Stage Map stage numbers a prose cell cites (`Stages 1, 2, and 3`, ranges, etc.). One definition serves two readers that must not drift — the coverage check in `validators/validate-run.py` proving every stage traces to a requirement, and `incremental_scope.py`'s back-trace resolving which stages an answered clarification touches |
@@ -34,7 +34,7 @@ flowchart TD
34
34
 
35
35
  `okstra-run` does not call `scripts/okstra.sh`. Instead it goes through `okstra wizard` and `okstra render-bundle` and converges on the same single Python entrypoint, `prepare_task_bundle()`.
36
36
 
37
- Launch selection is role slots and model refs, not a provider roster. The wizard shows the leader session read-only, then role counts (`min..max`, default **recommended**), then `--role-model <role>=<provider>/<model>` per slot. Roles with `min = 0` stay closed unless the user adds them. There is no provider multi-pick and no `Use defaults / Customize` fork for worker selection. `--workers` is compatibility-only. `lead` is a compatibility alias for `leader`. `executor` is a compatibility alias for `implementer`. New records write `leader` and `implementer`.
37
+ Launch selection is role slots and model refs, not a provider roster. The wizard asks role counts (`min..max`, default **recommended**), then `--role-model <role>=<provider>/<model>` per slot. current-session lead is this session and is listed on the confirmation summary. Roles with `min = 0` stay closed unless the user adds them. There is no provider multi-pick and no `Use defaults / Customize` fork for worker selection. `--workers` is compatibility-only. `lead` is a compatibility alias for `leader`. `executor` is a compatibility alias for `implementer`. New records write `leader` and `implementer`.
38
38
 
39
39
  ## 3. task-type documents
40
40
 
@@ -151,9 +151,8 @@ flowchart TD
151
151
  T[task-type selected] --> W{active worktree in registry?}
152
152
  W -->|yes| Reuse[reuse existing worktree<br/>base-ref prompt skipped]
153
153
  W -->|no| Base[ask base-ref<br/>validate with git rev-parse]
154
- Base --> L[leader session read-only]
155
- Reuse --> L
156
- L --> C[role-count min..max<br/>omit uses recommended]
154
+ Base --> C[role-count min..max<br/>omit uses recommended]
155
+ Reuse --> C
157
156
  C --> M[role-model provider/model per slot]
158
157
  M --> O[directive / related / clarification]
159
158
  O --> Special{release-handoff?}
@@ -20,10 +20,9 @@ flowchart TD
20
20
  Start[/okstra-run/] --> Common[common task identity flow]
21
21
  Common --> Type[task-type = error-analysis]
22
22
  Type --> Worktree{active worktree exists?}
23
- Worktree -->|yes| Leader[leader session read-only]
23
+ Worktree -->|yes| RoleCount[role-count min..max<br/>omit uses recommended; skip if min==max]
24
24
  Worktree -->|no| BaseRef[base-ref pick/text<br/>main recommended]
25
- BaseRef --> Leader
26
- Leader --> RoleCount[role-count min..max<br/>omit uses recommended; skip if min==max]
25
+ BaseRef --> RoleCount
27
26
  RoleCount --> RoleModel[role-model provider/model per slot]
28
27
  RoleModel --> RoleAdd[min=0 roles via role-add only<br/>default skip]
29
28
  RoleAdd --> Extras[directive, related tasks, clarification]
@@ -31,7 +30,7 @@ flowchart TD
31
30
  Confirm --> Render[render-bundle --render-only]
32
31
  ```
33
32
 
34
- Launch selection uses role slots and model refs only: leader is the current session (read-only), then each static role's count in `min..max` (default **recommended**; the count step is skipped when `min == max`), then one `provider/model` per slot. Roles with `min = 0` stay closed unless the user opens them with role-add (default skip). Duplicate model refs in the same role are rejected. There is no provider roster multi-pick and no `Use defaults / Customize` fork. Dynamic verifiers are not chosen at launch. `--workers` is a CLI compatibility input only, not a launch picker.
33
+ Launch selection uses role slots and model refs only: current-session lead is this session (listed on the confirmation summary), then each static role's count in `min..max` (default **recommended**; the count step is skipped when `min == max`), then one `provider/model` per slot. Roles with `min = 0` stay closed unless the user opens them with role-add (default skip). Duplicate model refs in the same role are rejected. There is no provider roster multi-pick and no `Use defaults / Customize` fork. Dynamic verifiers are not chosen at launch. `--workers` is a CLI compatibility input only, not a launch picker.
35
34
 
36
35
  ## 3. prepare_task_bundle handling
37
36
 
@@ -24,8 +24,7 @@ flowchart TD
24
24
  Type --> PlanPick[approved plan pick]
25
25
  PlanPick --> Approved[approval marker confirm]
26
26
  Approved --> Stage[stage pick<br/>whole-task or stage number]
27
- Stage --> Leader[leader session read-only]
28
- Leader --> RoleCount[role-count min..max<br/>omit uses recommended; skip if min==max]
27
+ Stage --> RoleCount[role-count min..max<br/>omit uses recommended; skip if min==max]
29
28
  RoleCount --> RoleModel[role-model provider/model per slot]
30
29
  RoleModel --> RoleAdd[min=0 roles via role-add only<br/>default skip]
31
30
  RoleAdd --> Extras[directive, related tasks, clarification]
@@ -33,7 +32,7 @@ flowchart TD
33
32
  Confirm --> Render[render-bundle]
34
33
  ```
35
34
 
36
- Launch selection uses role slots and model refs only: leader is the current session (read-only), then each static role's count in `min..max` (default **recommended**; the count step is skipped when `min == max`), then one `provider/model` per slot. Roles with `min = 0` stay closed unless the user opens them with role-add (default skip). Duplicate model refs in the same role are rejected. There is no provider roster multi-pick and no `Use defaults / Customize` fork. Dynamic verifiers are not chosen at launch. `--workers` is a CLI compatibility input only, not a launch picker.
35
+ Launch selection uses role slots and model refs only: current-session lead is this session (listed on the confirmation summary), then each static role's count in `min..max` (default **recommended**; the count step is skipped when `min == max`), then one `provider/model` per slot. Roles with `min = 0` stay closed unless the user opens them with role-add (default skip). Duplicate model refs in the same role are rejected. There is no provider roster multi-pick and no `Use defaults / Customize` fork. Dynamic verifiers are not chosen at launch. `--workers` is a CLI compatibility input only, not a launch picker.
37
36
 
38
37
  This phase does not ask for `base-ref` directly. The wizard selects whole-task or a single stage from the approved plan's Stage Map, and prepare resolves `VERIFICATION_TARGET` from the registry / `consumers.jsonl` / git state.
39
38
 
@@ -27,10 +27,9 @@ flowchart TD
27
27
  Input -->|rerun| Prior[prior planning report via clarification-response]
28
28
  Direction --> Worktree{active task worktree?}
29
29
  Prior --> Worktree
30
- Worktree -->|yes| Leader[leader session read-only]
30
+ Worktree -->|yes| RoleCount[role-count min..max<br/>omit uses recommended]
31
31
  Worktree -->|no| BaseRef[base-ref pick/text]
32
- BaseRef --> Leader
33
- Leader --> RoleCount[role-count min..max<br/>omit uses recommended]
32
+ BaseRef --> RoleCount
34
33
  RoleCount --> RoleModel[role-model provider/model per slot]
35
34
  RoleModel --> Extras[directive, related tasks, clarification]
36
35
  Extras --> Confirm
@@ -31,8 +31,7 @@ flowchart TD
31
31
  Approved -->|no| Retry[re-prompt same step]
32
32
  Approved -->|yes| Stage[stage multi-pick<br/>ready/active markers]
33
33
  Stage --> Chain[render-args<br/>stage + chain-stages]
34
- Chain --> Leader[leader session read-only]
35
- Leader --> RoleCount[role-count min..max<br/>omit uses recommended; skip if min==max]
34
+ Chain --> RoleCount[role-count min..max<br/>omit uses recommended; skip if min==max]
36
35
  RoleCount --> RoleModel[role-model provider/model per slot]
37
36
  RoleModel --> RoleAdd[min=0 roles via role-add only<br/>default skip]
38
37
  RoleAdd --> Extras[directive, related tasks, clarification]
@@ -40,7 +39,7 @@ flowchart TD
40
39
  Confirm --> Render[render-bundle]
41
40
  ```
42
41
 
43
- Launch selection uses role slots and model refs only: leader is the current session (read-only), then each static role's count in `min..max` (default **recommended**; the count step is skipped when `min == max`), then one `provider/model` per slot. Roles with `min = 0` stay closed unless the user opens them with role-add (default skip). Duplicate model refs in the same role are rejected. There is no provider roster multi-pick and no defaults-vs-customize fork. `executor` is only a compatibility alias for `implementer` in model refs; implementer slots are chosen through role-count / role-model. Dynamic verifiers are not chosen at launch. `--workers` is a CLI compatibility input only, not a launch picker. `stage_pick` is a multi-pick that shows done/in-progress/ready/waiting status. The selected stage set goes through dependency closure and topological sort into a `chain-stages` CSV, and each actual run executes only one of those stages.
42
+ Launch selection uses role slots and model refs only: current-session lead is this session (listed on the confirmation summary), then each static role's count in `min..max` (default **recommended**; the count step is skipped when `min == max`), then one `provider/model` per slot. Roles with `min = 0` stay closed unless the user opens them with role-add (default skip). Duplicate model refs in the same role are rejected. There is no provider roster multi-pick and no defaults-vs-customize fork. `executor` is only a compatibility alias for `implementer` in model refs; implementer slots are chosen through role-count / role-model. Dynamic verifiers are not chosen at launch. `--workers` is a CLI compatibility input only, not a launch picker. `stage_pick` is a multi-pick that shows done/in-progress/ready/waiting status. The selected stage set goes through dependency closure and topological sort into a `chain-stages` CSV, and each actual run executes only one of those stages.
44
43
 
45
44
  The current okstra-run wizard path does not expose `--approve` that directly flips the approval checkbox. The plan file must already have a recognized approval marker.
46
45
 
@@ -27,10 +27,9 @@ flowchart TD
27
27
  Type --> Plan[approved plan auto/pick]
28
28
  Plan --> Scope[handoff stage pick<br/>whole-task or eligible stages]
29
29
  Scope --> Worktree{active task worktree?}
30
- Worktree -->|yes| Leader[leader session read-only]
30
+ Worktree -->|yes| RoleCount[role-count min..max<br/>omit uses recommended; skip if min==max]
31
31
  Worktree -->|no| BaseRef[base-ref pick/text]
32
- BaseRef --> Leader
33
- Leader --> RoleCount[role-count min..max<br/>omit uses recommended; skip if min==max]
32
+ BaseRef --> RoleCount
34
33
  RoleCount --> RoleModel[role-model provider/model per slot]
35
34
  RoleModel --> RoleAdd[min=0 roles via role-add only<br/>default skip]
36
35
  RoleAdd --> Extras[directive, related tasks, clarification]
@@ -40,7 +39,7 @@ flowchart TD
40
39
  Confirm --> Render[render-bundle]
41
40
  ```
42
41
 
43
- `release-handoff` has no analysis-worker dispatch. Launch selection still shows the leader session (read-only) and any applicable role-count / role-model steps; there is no provider roster multi-pick and no `Use defaults / Customize` fork. Dynamic verifiers are not chosen at launch. `--workers` is not a launch picker, and the runtime forces the worker list to empty. The wizard outcome's `renderArgs` includes `pr-template-path` only for release-handoff. Scope selection finishes before prepare, and the project/global save runs before `render-bundle` via the `config.set pr-template-path` action of `outcome.persistActions[]`. whole-task requires an accepted whole-task verification report, and for stage-group only the stages that were marked `verified` by an accepted single-stage verification in the Stage Lifecycle Snapshot but not yet covered by a `pr` become candidates.
42
+ `release-handoff` has no analysis-worker dispatch. Launch selection still shows any applicable role-count / role-model steps; current-session lead is this session and is listed on the confirmation summary. There is no provider roster multi-pick and no `Use defaults / Customize` fork. Dynamic verifiers are not chosen at launch. `--workers` is not a launch picker, and the runtime forces the worker list to empty. The wizard outcome's `renderArgs` includes `pr-template-path` only for release-handoff. Scope selection finishes before prepare, and the project/global save runs before `render-bundle` via the `config.set pr-template-path` action of `outcome.persistActions[]`. whole-task requires an accepted whole-task verification report, and for stage-group only the stages that were marked `verified` by an accepted single-stage verification in the Stage Lifecycle Snapshot but not yet covered by a `pr` become candidates.
44
43
 
45
44
  Note that this phase is also a target of task worktree provisioning. The normal flow reuses the implementation/final-verification result of the same task-key. Starting a new task may create a new branch, and it is likely to be blocked at the entry gate's "implementation commit exists" condition.
46
45
 
@@ -31,10 +31,9 @@ flowchart TD
31
31
  Keep -->|keep| Base
32
32
  Keep -->|change/no brief| Brief
33
33
  Type --> Base{active task worktree?}
34
- Base -->|yes| Leader[leader session read-only]
34
+ Base -->|yes| RoleCount[role-count min..max<br/>omit uses recommended; skip if min==max]
35
35
  Base -->|no| BaseRef[base-ref pick/text]
36
- BaseRef --> Leader
37
- Leader --> RoleCount[role-count min..max<br/>omit uses recommended; skip if min==max]
36
+ BaseRef --> RoleCount
38
37
  RoleCount --> RoleModel[role-model provider/model per slot]
39
38
  RoleModel --> RoleAdd[min=0 roles via role-add only<br/>default skip]
40
39
  RoleAdd --> Extras[directive, related tasks, clarification]
@@ -42,7 +41,7 @@ flowchart TD
42
41
  Confirm --> Render[render-bundle]
43
42
  ```
44
43
 
45
- Launch selection uses role slots and model refs only: leader is the current session (read-only), then each static role's count in `min..max` (default **recommended**; the count step is skipped when `min == max`), then one `provider/model` per slot. Roles with `min = 0` stay closed unless the user opens them with role-add (default skip). Duplicate model refs in the same role are rejected. There is no provider roster multi-pick and no `Use defaults / Customize` fork. Dynamic verifiers are not chosen at launch. `--workers` is a CLI compatibility input only, not a launch picker.
44
+ Launch selection uses role slots and model refs only: current-session lead is this session (listed on the confirmation summary), then each static role's count in `min..max` (default **recommended**; the count step is skipped when `min == max`), then one `provider/model` per slot. Roles with `min = 0` stay closed unless the user opens them with role-add (default skip). Duplicate model refs in the same role are rejected. There is no provider roster multi-pick and no `Use defaults / Customize` fork. Dynamic verifiers are not chosen at launch. `--workers` is a CLI compatibility input only, not a launch picker.
46
45
 
47
46
  ## 3. prepare_task_bundle handling
48
47
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.183.2",
3
+ "version": "0.184.0",
4
4
  "description": "Host-aware multi-provider cross-verification orchestrator runtime and agent skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.183.2",
3
- "builtAt": "2026-08-23T08:48:03.022Z",
2
+ "package": "0.184.0",
3
+ "builtAt": "2026-08-23T11:33:15.100Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -98,15 +98,15 @@ For a new `implementation-planning` run, the plan-body sequence is initial verif
98
98
 
99
99
  ### Incremental re-verification (implementation-planning clarification re-runs only)
100
100
 
101
- The **default is full re-verification**. Only narrow this re-run to the impacted stages when the deterministic `okstra incremental-scope` CLI returns `mode == "incremental"`; on any doubt, stay full. This procedure fires ONLY when this run's task-type is `implementation-planning` AND a prior final report exists for this task-key (its data.json at `runs/implementation-planning/reports/final-report-implementation-planning-<prev-seq>.data.json`, where `<prev-seq>` is the most recent prior implementation-planning run's seq). For every other task-type, ignore this block and re-verify normally. This branches on the CLI's `mode` output only — it does NOT re-implement the safety logic in the prompt.
101
+ The **default is full re-verification**. Narrow this re-run to the impacted stages only when the deterministic `okstra incremental-scope` CLI returns `mode == "incremental"`. When the wizard pin is `auto` and the CLI returns `mode == "incremental"`, do not upgrade to full — the CLI already applied the base-ref check, the dependency closure, and the cutoff. This procedure fires ONLY when this run's task-type is `implementation-planning` AND a prior final report exists for this task-key (its data.json at `runs/implementation-planning/reports/final-report-implementation-planning-<prev-seq>.data.json`, where `<prev-seq>` is the most recent prior implementation-planning run's seq). For every other task-type, ignore this block and re-verify normally. This branches on the CLI's `mode` output only — it does NOT re-implement the safety logic in the prompt.
102
102
 
103
- 0. **Honour the scope the user already pinned (not a judgement — an instruction).** The wizard asks for a re-verification scope whenever this re-run is narrowable, and the answer arrives as two tokens: mode `{{REVERIFY_SCOPE_MODE}}`, stages `{{REVERIFY_SCOPE_STAGES}}`. Apply it before you form your own view:
104
- - `auto` — the user left the decision to this procedure. Run steps 1–6 exactly as written; nothing is pinned.
103
+ 0. **Honour the scope the user already pinned (not a judgement — an instruction).** The wizard asks for a re-verification scope whenever this re-run is narrowable or an answered id traces to no stage, and the answer arrives as two tokens: mode `{{REVERIFY_SCOPE_MODE}}`, stages `{{REVERIFY_SCOPE_STAGES}}`. Apply it before you form your own view:
104
+ - `auto` — the user left the decision to this procedure. Run steps 1–7 exactly as written; nothing is pinned. If the CLI returns `mode == "incremental"`, keep it; do not upgrade to full.
105
105
  - `full` — the user asked for full re-verification. Still call the CLI once (the record is the audit trail), passing `--full-reason "user requested full re-verification"` and every other CSV empty. Do not argue the user out of it with a narrower `--impacted`: the answer already weighed the cost.
106
106
  - `stages` — the user named the stages that must be re-verified. Put **every** number in `{{REVERIFY_SCOPE_STAGES}}` into `--impacted`, unioned with any stage you resolve yourself. Never drop one because your own back-trace did not reach it — the user is telling you about a blast radius the prior report failed to record, which is exactly what the back-trace cannot see. The CLI still applies the dependency closure and the cutoff on top, so a wide enough pin correctly ends in `mode == "full"`.
107
107
  1. **List the answered clarifications (a fact, not a judgement).** Collect the `C-NNN` ids you reconciled as answered in §1, and the IDs of any PREP items changed by the carried response. Pass the ids through `--answered-clarifications`; the CLI resolves which Stage Map stages each one touches by reading the prior run's own links — the `P-*` plan item that carried that `clarificationId`, and the requirement-coverage row blocked on `C-NNN` in either its `status` or its `approvalDisposition` (a `documented-deviation` row records the block in the latter). Do NOT map answers to stage numbers yourself.
108
- - An id that traces to no stage in the prior report makes the CLI return `mode == "full"` for the whole re-run. That is the intended outcome: an answer whose blast radius the prior report never recorded must not narrow anything.
109
- - **Structural change is your call, and you declare it.** When an answer overturns the selected Option, restructures the stages, or changes the recommended approach, pass `--full-reason "<what changes and how>"`. It forces `mode == "full"` regardless of every other input — the back-trace resolves stages, it cannot judge whether the plan's shape survived. Do NOT express this by leaving the CSVs empty: that produces the same `no impacted stages resolved` reason as a re-run that simply failed to map anything, and the two become indistinguishable in the record. `--impacted` remains available for stage numbers you resolved yourself.
108
+ - An id that traces to no stage in the prior report does **not** force `mode == "full"` for the whole re-run. The CLI returns `mode == "unresolved"` and names the ids. Ask the user for the stage numbers those answers affect, then call the CLI again with those numbers in `--impacted`. Do not drop the unlinked id from `--answered-clarifications`. Do not treat `unresolved` as full, and do not silently narrow to the ids that did trace. An approval blocker in this shape should have failed the prior report's back-trace; naming stages is the remaining outlet.
109
+ - **Structural change is your call, and you declare it.** When an answer overturns the selected Option, restructures the stages, or changes the recommended approach, pass `--full-reason "<what changes and how>"`. It forces `mode == "full"` regardless of every other input — the back-trace resolves stages, it cannot judge whether the plan's shape survived. Do NOT express this by leaving the CSVs empty. `--impacted` remains available for stage numbers you resolved yourself. `--full-reason` is the only structural path that still forces full.
110
110
  - Pass ONLY stage numbers that appear in that Stage Map when you do use `--impacted`. Never invent or guess a stage number — a number absent from the graph must never enter `--impacted` (it would leak into `reverify_stages`).
111
111
  2. **Resolve the two base SHAs (safety condition C1 — code-unchanged, decided by the CLI, not by you).**
112
112
  - Current base SHA: `{{EXECUTOR_WORKTREE_BASE_REF}}` (this run's resolved worktree base commit).
@@ -124,8 +124,9 @@ The **default is full re-verification**. Only narrow this re-run to the impacted
124
124
  ```
125
125
  The CLI reads the plan's dependency graph from the prior `implementationPlanning.stageMap`, which is authoritative for the impacted stage numbers. The CLI prints JSON `{mode, reverify_stages, carry_stages, reason}`. Instruct the report writer to record this decision in its narrative as `implementationPlanning.incrementalDecision`, using camel-case array keys `reverifyStages` and `carryStages`. Final report assembly preserves that writer-owned decision.
126
126
  4. **`mode == "full"`** → run the existing full re-verification path unchanged; ignore `reverify_stages` / `carry_stages`.
127
- 5. **`mode == "incremental"`** → scope every worker dispatch prompt to `reverify_stages` only (the downstream closure of the impacted stages). Do NOT re-analyze `carry_stages` — their prior plan-item verdicts are carried forward verbatim (see `prompts/profiles/implementation-planning.md` "Cross-verification mode" and `prompts/lead/convergence.md` "Convergence scope").
128
- 6. **Merge carried-forward verdicts.** In `incremental` mode, the report writer includes every `carry_stages` stage row unchanged in its narrative. After `okstra plan-items seed --narrative ... --state ...`, the lead runs:
127
+ 5. **`mode == "incremental"`** → scope every worker dispatch prompt to `reverify_stages` only (the downstream closure of the impacted stages). Do NOT re-analyze `carry_stages` — their prior plan-item verdicts are carried forward verbatim (see `prompts/profiles/implementation-planning.md` "Cross-verification mode" and `prompts/lead/convergence.md` "Convergence scope"). When the pin in step 0 was `auto`, this is the decision — do not upgrade it to full.
128
+ 6. **`mode == "unresolved"`** ask the user for stage numbers; re-enter step 3 with those numbers in `--impacted`. Do not fall back to full. Do not record `unresolved` as `incrementalDecision`. Then continue from the new `mode`.
129
+ 7. **Merge carried-forward verdicts.** In `incremental` mode, the report writer includes every `carry_stages` stage row unchanged in its narrative. After `okstra plan-items seed --narrative ... --state ...`, the lead runs:
129
130
  ```
130
131
  okstra incremental-carry \
131
132
  --prev-data runs/implementation-planning/reports/final-report-implementation-planning-<prev-seq>.data.json \
@@ -382,7 +382,7 @@ Distinct from Phase 5.5 finding convergence:
382
382
 
383
383
  Lead's responsibilities in this sub-step (in order):
384
384
 
385
- For a new `implementation-planning` run, the fixed order is initial verification → one planner self-fix → targeted re-verification → user gate. The initial verification is round 1 and the targeted re-verification is round 2. A second automatic self-fix is a contract violation.
385
+ For a new `implementation-planning` run, the fixed order is initial verification → one planner self-fix → targeted re-verification → user gate. The initial verification is round 1 and the targeted re-verification is round 2. A second automatic self-fix is a contract violation. When `okstra plan-items prepare` reports `"gating": false` (one-stage `no-design-inputs` plan), skip the self-fix loop and the sweep batch: extraction and round 1 still run, then go to the user gate. Two-or-more stages, a PREP item, or non-empty `designPreparation.items` keep `gating: true` and the full order.
386
386
 
387
387
  1. Build the queue with `okstra plan-items prepare --narrative <report-writer-narrative.md> --run-manifest <run-manifest>`, place the output of `okstra plan-items prompt --run-manifest <run-manifest>` verbatim in every verifier prompt, then run `okstra plan-items validate-prepared --narrative <report-writer-narrative.md> --run-manifest <run-manifest>`. Python resolves the one convergence-owned state path from that run identity. The lead MUST NOT summarise, select, omit, reorder, or renumber the queue. Each prompt uses the compact subject plus the lossless payload, and asks every item:
388
388
 
@@ -45,9 +45,9 @@ Plan-body verification is configured under `convergence.planBodyVerification` in
45
45
  | `enabled` | `true` | If `false`, the round is skipped and the approval gate is not blocked by this round (legacy behaviour). |
46
46
  | `maxRounds` | `1` | Upper bound. Plan-body verification is consistency / completeness checking, not fact checking — additional rounds rarely help. Range 1–3. |
47
47
  | `selfFixMaxRounds` | `1` | One report-writer rewrite at most. The initial verification is round 1; targeted re-verification is round 2 after that rewrite. |
48
- | `gating` | `true` | If `true` (default), `majority-disagree` blocks approval. If `false`, the round is advisory-only and never blocks approval. |
48
+ | `gating` | `true` | If `true` (default), `majority-disagree` blocks approval. If `false`, the round is advisory-only and never blocks approval. Prepare emits `true` because the plan does not exist yet. After the report-writer draft, `okstra plan-items prepare` (and `seed`) flip it to `false` when `designPreparation.mode` is `no-design-inputs` and the Stage Map has exactly one row. That path keeps extraction and one verification round and does not run the self-fix loop or a sweep batch. Two-or-more stages, a PREP item, or non-empty `designPreparation.items` keep `gating=true`. `--no-plan-verification` is the separate manual opt-out (`enabled=false`). **Enforced:** `okstra_ctl.plan_items.advisory_plan_body_gating`, `validators/validate-run.py` `_validate_advisory_plan_body_gating`. |
49
49
 
50
- Default values are emitted into the manifest by `scripts/okstra_ctl/render.py` (`_build_convergence_block`). The ctx knob `OKSTRA_PLAN_VERIFICATION=false` flips `planBodyVerification.enabled` to false.
50
+ Default values are emitted into the manifest by `scripts/okstra_ctl/render.py` (`_build_convergence_block`). The ctx knob `OKSTRA_PLAN_VERIFICATION=false` flips `planBodyVerification.enabled` to false. `gating=false` is not that opt-out: extraction and one round still run.
51
51
 
52
52
  The shared Majority definition and the auto-disable rule (fewer than 2 analyser workers → advisory `gating=false` path) are owned by [convergence](./convergence.md) §"Convergence Algorithm" / §"Configuration" and apply here unchanged.
53
53
 
@@ -311,14 +311,14 @@ Exception for `P-Req-*`: verifiers still MUST NOT re-open the original task brie
311
311
 
312
312
  ## Adversarial plan-body posture
313
313
 
314
- When `config.adversarial == true` (the default for `implementation-planning`; see [convergence](./convergence.md) §"Configuration"), the plan-body round runs with an **adversarial posture**. The classification rules and gate arithmetic in §"Round protocol" are UNCHANGED — `majority-disagree` (a *majority* of analysers DISAGREE) remains the only classification that blocks approval, and `dissent-isolated` still passes the gate. Adversarial mode changes only *how each verifier evaluates an item*:
314
+ When `config.adversarial == true` (the default for `implementation-planning`; see [convergence](./convergence.md) §"Configuration"), the plan-body round runs with an **adversarial posture**. The classification rules and gate arithmetic in §"Round protocol" are UNCHANGED — `majority-disagree` blocks approval, and that class now includes a blocking-kind minority dissent so a 2-AGREE / 1-DISAGREE on `b` / `c` / `e` is not passed silently. Advisory `dissent-isolated` (`DISAGREE(d)`, `P-Rb-*`) still does not block. Adversarial mode changes only *how each verifier evaluates an item*:
315
315
 
316
316
  - The burden of proof sits on the plan: an item earns `AGREE` only if the verifier actively tried to break it and could not.
317
317
  - The verifier MUST open the file paths / symbols / commands the item cites and confirm they exist and are **defined** as written. This is the one allowed widening of the lightweight "judge from internal consistency and stated commands / paths" rule — confirming the existence of cited paths is not "re-analyzing the original requirements". The widening stops at *definition*: a build/test command's **execution success** is out of scope here, because the planning worktree has no dependencies installed (§"Planning-time environment gap"). Confirm the script is declared; do not treat its failure to run as evidence against the plan.
318
318
  - If a cited path / command / validation signal cannot be confirmed, the verifier responds `DISAGREE(<kind>)` with the applicable breakage kind (a–f); uncertainty resolves toward DISAGREE, not AGREE.
319
- - **Single-vote-blocking kinds.** A single `DISAGREE` is approval-blocking on its own — no majority needed — when the breakage kind is `a` (cited path/symbol mismatch) on any plan item other than a `P-Var-*` one, or `f` (requirement-coverage mismatch) on a `P-Req-*` item. On a `P-Var-*` item kind `a` never blocks on one vote a variation-point defect is a design judgement and takes a majority exactly like `b` / `e` (see §"Plan-body verdict semantics"). These defects are concrete, safety-critical, and adversarially verifiable (the verifier confirmed the cited path / requirement), so one correct dissent must not be outvoted. Each creates a `majority-disagree` classification and MUST become a `Blocks=approval` clarification row. Lead never applies this rule by hand `okstra plan-verify` scores it (see §"Round protocol" step 5). Kinds `b` / `c` / `e` still need a majority `b` especially is prone to planning-vs-implementation environment false positives. **Rollback ordering (`d`) never blocks the gate at all** — a rollback is executed by a human, not by okstra's workers or verifiers, so a `DISAGREE(d)` is recorded as dissent and dropped from every gate tally; it can only ever fold an item into `passed-with-dissent`. Because `a` is reserved for a concrete contradiction between two spelled-out references (see §"Plan-body verdict semantics"), an abbreviated / ellipsis / under-specified path is raised as `b` (majority-gated), never `a` — a lone "this path is abbreviated" dissent must not single-vote-block on notation alone, and it is especially not blocking on a *rejected* option that will never be implemented. **Enforced:** `validators/validate-run.py` `_classify_plan_item_gate` (`_SINGLE_VOTE_BLOCKING_KINDS = {a}`, `_ADVISORY_ONLY_KINDS = {d}` + the P-Req `f` rule).
319
+ - **Single-vote-blocking kinds.** A reproduced `DISAGREE(a)` (cited path/symbol mismatch) on any item other than a `P-Var-*` one, or a reproduced `DISAGREE(f)` on a `P-Req-*` item, blocks on that one vote even if the rest AGREE. **Enforced:** `validators/validate-run.py` `_single_vote_block_survives`. Kinds `b` / `c` / `e` do not auto-block on one unreproduced vote, but a blocking-kind minority with ≥2 participating votes is still `majority-disagree` and goes to the userthe majority does not silently pass it. **Rollback ordering (`d`) never blocks.** Because `a` is reserved for a concrete contradiction between two spelled-out references, an abbreviated path is raised as `b`, never `a`. **Enforced:** `validators/validate-run.py` `_classify_plan_item_gate`.
320
320
 
321
- Plan-body verification stays **lightweight** even under this posture — the `verificationMode = "full-reanalysis"` forcing in [convergence](./convergence.md) §"Adversarial Verification Mode" applies to finding convergence only (see §"Mode constraint"); the adversarial posture here only changes verifier behaviour, not the mode. This raises verification *quality* (active refutation, plan-side burden). The gate *threshold* stays majority-based for the majority-gated kinds (`b`/`c`/`e`), with the single-vote-blocking exception above for the concrete, safety-critical kinds (`a`, and `f` on P-Req); rollback ordering (`d`) is advisory and never blocks. A majority requires at least two participating (non-error) votes, so a lone surviving `DISAGREE` whose peer returned a non-result does NOT block on a majority-gated kind — a worker failure must not make the gate stricter than a healthy roster would.
321
+ Plan-body verification stays **lightweight** even under this posture — the `verificationMode = "full-reanalysis"` forcing in [convergence](./convergence.md) §"Adversarial Verification Mode" applies to finding convergence only (see §"Mode constraint"); the adversarial posture here only changes verifier behaviour, not the mode. This raises verification *quality* (active refutation, plan-side burden). A reproduced fact (`a`, or `f` on P-Req) still blocks on one confirmed vote. A blocking-kind minority (`b`/`c`/`e`) with ≥2 participating votes goes to the user rather than passing as `has-dissent`. Rollback ordering (`d`) is advisory and never blocks. A lone surviving `DISAGREE` whose peer returned a non-result does NOT block — a worker failure must not make the gate stricter than a healthy roster would.
322
322
 
323
323
  ## Round protocol (single round at default `maxRounds=1`)
324
324
 
@@ -346,23 +346,23 @@ CLI-wrapper calls follow the planned execution surface after
346
346
  consume only `modelExecutionValue`. A missing or invalid invocation contract blocks the
347
347
  round before any host or provider process starts.
348
348
 
349
- 1. Lead runs `okstra plan-items prepare --narrative <report-writer-narrative.md> --run-manifest <run-manifest>`, places the fixed output of `okstra plan-items prompt --run-manifest <run-manifest>` verbatim in every verifier prompt, then runs `okstra plan-items validate-prepared --narrative <report-writer-narrative.md> --run-manifest <run-manifest>`. Python resolves the one convergence-owned state path from that run identity. Dispatch only after that exact-match validation succeeds.
349
+ 1. Lead runs `okstra plan-items prepare --narrative <report-writer-narrative.md> --run-manifest <run-manifest>`, places the fixed output of `okstra plan-items prompt --run-manifest <run-manifest>` verbatim in every verifier prompt, then runs `okstra plan-items validate-prepared --narrative <report-writer-narrative.md> --run-manifest <run-manifest>`. After a self-fix rewrite, pass `--state <plan-body-verification.json>` on prepare and validate-prepared so the dispatch queue is the changed items plus their stage closure, not the full extract. Python resolves the one convergence-owned state path from that run identity. Dispatch only after that exact-match validation succeeds. The prompt is the dispatch queue: `observed` / `deferred` stages are omitted; plan-wide items (`P-Dir-1`, `P-Var-*`, `P-Dep-*`, items with no `stageScope`) stay. **Enforced:** `okstra_ctl.plan_items.dispatch_item_ids` / `reverify_item_ids`.
350
350
 
351
- **Then seed the landing table (BLOCKING):** `okstra plan-items seed --narrative <report-writer-narrative.md> --state <plan-body-verification.json> --run-manifest <run-manifest.json>`. `apply-verdicts` in step 8 refuses a verdict whose item has no `planBodyVerification.planItems[]` row. The report writer never owns that state, so the deterministic seed is the only creator of its rows. The seed is idempotent by id and never touches an existing row, so it is safe to re-run between rounds and after a self-fix re-extraction. Skipping it makes step 8 fail with `plan-body state has no row for [...]`.
351
+ **Then seed the landing table (BLOCKING):** `okstra plan-items seed --narrative <report-writer-narrative.md> --state <plan-body-verification.json> --run-manifest <run-manifest.json>`. `apply-verdicts` in step 8 refuses a verdict whose item has no `planBodyVerification.planItems[]` row. The report writer never owns that state, so the deterministic seed is the only creator of its rows. The seed is idempotent by id and never touches existing verdicts, so it is safe to re-run between rounds and after a self-fix re-extraction. It does refresh `contentHash` from the current extract. Skipping it makes step 8 fail with `plan-body state has no row for [...]`.
352
352
 
353
- **`--run-manifest` is what scopes the gate to the stage you are starting.** Seed uses it to record `planBodyVerification.stageLedger` a `{stage: status}` snapshot in the Stage Ledger's own `done` / `active` / `ready` / `blocked` vocabulary. Without it the block carries no ledger, and the gate falls back to judging every stage at once, which is the behaviour described in the paragraph below and the reason this flag exists.
353
+ **`--run-manifest` is what scopes the gate to the stage you are starting.** Seed uses it to overlay disk `done` / `active` onto `planBodyVerification.stageLedger`. The current plan's depends-on fills `ready` / `blocked` when no prior plan exists, so a first run does not treat every stage as in-scope. **Enforced:** `okstra_ctl.plan_items.planning_stage_ledger`.
354
354
  2. For each analyser worker in the roster (`claude`, `codex`, and `antigravity` if opted in), lead constructs a reverify prompt using the template in §"Plan-body reverify prompt" below.
355
355
  3. Dispatch uses the same wrapper infrastructure as finding convergence, so the `--role-slug` is the same canonical `<role>-worker` that convergence uses — not a round-specific slug. Result file path: `runs/<task-type>/worker-results/<role>-worker-plan-verify-r<N>-implementation-planning-<seq>.md` (e.g. `codex-worker-plan-verify-r1-implementation-planning-003.md`). **`<seq>` is the report's sequence** — the one in this run's `final-report-<task-type>-<seq>` filename, NOT the `workerResults` sequence the initial analysis results carry. The two are equal in most runs and diverge in some (`reports: 004` alongside `workerResults: 005` is a real case), and provenance globs on the report's. Picking the other one makes `_validate_plan_body_verdict_provenance` report that no result file exists while the file is sitting in the directory. The `-worker-` token is load-bearing twice over: §"Plan-body reverify prompt" requires the same anchor headers as convergence, whose `**Audit sidecar path:**` is derived by `okstra_ctl.worker_artifact_paths.audit_sidecar_rel()` inserting `-audit-` after that token — a slug without it makes the header underivable and the helper raises. Record each `planItems[].verdicts[].worker` as the same `<role>-worker` string, because provenance compares it to this filename's prefix. **Enforced:** `tests/contract/test_reverify_dispatch_anchors.py` derives the sidecar from the documented name and re-extracts the prefix the provenance resolver uses.
356
356
  **Verdict provenance (BLOCKING).** Every verdict recorded in `planItems[].verdicts[]` MUST trace back to a dispatch that actually returned a result file at the path above. The whole gate — classification, self-fix eligibility, promotion, `gateBlockedBy` — is computed from these votes, so an unbacked vote lets the round be skipped while the gate still reads `passed`. **Enforced:** `validators/validate-run.py` `_validate_plan_body_verdict_provenance` fails any `verdicts[].worker` with no matching `<worker>-plan-verify-r<N>-<task-type>-<seq>.md` result file. Recording a `verification-error` for a dispatch that produced no result is the correct way to represent a failed worker — inventing an `AGREE` is a contract violation.
357
357
 
358
358
  4. After all dispatches return, lead aggregates verdicts per `P-*` item across workers and classifies each:
359
359
 
360
- **Every item carries at least one verdict row (BLOCKING).** Aggregation covers the whole queue, not the subset that came back cleanly. An item left with an empty `verdicts[]` is not a weak signal the gate can discount — it classifies `all-non-result`, states as `needs-reverify`, and folds into `passed-with-dissent` next to items two verifiers actually agreed on, so a plan item nobody judged reads as a passing one. This is the shape a self-fix round produces when the planner adds an item and the targeted round-N queue never picks it up. A worker that returned nothing is a `verification-error` row (step 3), not a missing row; if an item was never dispatched, dispatch it before scoring the round. **Enforced:** `validators/validate-run.py` `_validate_round_recorded_verdicts` fails any run whose `roundCount` ≥ 1 leaves an extracted item with no verdict row.
360
+ **Every in-scope item carries at least one verdict row (BLOCKING).** Aggregation covers the dispatch queue, not deferred or observed stages. An in-scope item left with an empty `verdicts[]` is not a weak signal the gate can discount — it classifies `all-non-result`, states as `needs-reverify`, and folds into `passed-with-dissent` next to items two verifiers actually agreed on, so a plan item nobody judged reads as a passing one. This is the shape a self-fix round produces when the planner adds an in-scope item and the targeted round-N queue never picks it up. A worker that returned nothing is a `verification-error` row (step 3), not a missing row; if an in-scope item was never dispatched, dispatch it before scoring the round. **Enforced:** `validators/validate-run.py` `_validate_round_recorded_verdicts` fails any run whose `roundCount` ≥ 1 leaves an in-scope item with no verdict row.
361
361
 
362
362
  - `full-consensus` — all participating analysers `AGREE` (SUPPLEMENT counts as agree on the item itself).
363
- - `partial-consensus` — majority `AGREE`, dissenting `DISAGREE` recorded.
364
- - `dissent-isolated` — only one worker `DISAGREE`s, others `AGREE` treat as `partial-consensus` for gate purposes; record dissent. (Distinct from finding-convergence `worker-unique`, which means the *opposite*: only one worker AGREEs. Plan-body classifications use this dedicated label to avoid the collision.)
365
- - `majority-disagree` — a *majority* of analysers `DISAGREE` (majority needs ≥2 participating non-error votes; rollback-ordering `DISAGREE(d)` votes are advisory and excluded from the tally), OR any single-vote-blocking kind fires: one `DISAGREE(a)` on any item other than a `P-Var-*` one where kind `a` never blocks on one vote and takes a majority like `b` / `e` — or one `DISAGREE(f)` on a `P-Req-*` item (see §"Single-vote-blocking kinds"). This classification **blocks approval**.
363
+ - `partial-consensus` — majority `AGREE` with two or more blocking `DISAGREE`s. On kinds `b` / `c` / `e` this is scored `majority-disagree` and **blocks approval** so the user decides; it is not folded into a passing gate.
364
+ - `dissent-isolated` — only one worker `DISAGREE`s, others `AGREE`. On a blocking kind (`b` / `c` / `e`, and kind `a` on `P-Var-*`) this is scored `majority-disagree` and **blocks approval**. Advisory-only `DISAGREE(d)` and `P-Rb-*` stay recorded dissent and do not block. (Distinct from finding-convergence `worker-unique`, which means the *opposite*: only one worker AGREEs.)
365
+ - `majority-disagree` — a *majority* of analysers `DISAGREE` (majority needs ≥2 participating non-error votes; rollback-ordering `DISAGREE(d)` votes are advisory and excluded from the tally), OR any blocking-kind dissent with ≥2 participating votes (a minority `DISAGREE` is not outvoted), OR any single-vote-blocking kind fires: one reproduced `DISAGREE(a)` on any item other than a `P-Var-*` one, or one reproduced `DISAGREE(f)` on a `P-Req-*` item (see §"Single-vote-blocking kinds"). This classification **blocks approval**.
366
366
  - `needs-reverify` — one of two shapes the round could not settle.
367
367
  - **An even split on a blocking kind.** The majority test is strict, so a panel splitting evenly (1-AGREE / 1-DISAGREE, 2-2, …) reaches neither `full-consensus` nor `majority-disagree`. Until this shape existed it folded into `has-dissent` and the gate passed: two verifiers read the same plan, disagreed on a defect that is not advisory, and the split was recorded and never acted on. An even panel is not only the two-analyser roster — one `UNVERIFIABLE` or one lost dispatch makes any roster even for that item. Re-dispatch those items and record the votes with `--round 2`; a split that survives that round becomes `majority-disagree` and goes to the user, because nothing further is going to settle it. **The round is not optional**: `needs-reverify` folds into `passed-with-dissent`, so without the re-verification this classification would be a label and nothing else. **Enforced:** `validators/validate-run.py` `_validate_unresolved_tie_was_reverified` fails a gate declared over a tie that was never re-verified, and `_classify_plan_item_gate` promotes a tie carrying a round-2 verdict to `majority-disagree`.
368
368
  - **A lone dissent nobody cross-verified** — a single-vote-blocking kind fired but the item has **fewer than 2 participating non-error votes**, i.e. the lone dissent was never cross-verified because its peer returned `verification-error`. A single-vote-blocking kind means "one *confirmed* DISAGREE is enough"; an unconfirmed one is not, and on a `P-Var-*` item none fires at all — its kind `a` never blocks on one vote and takes a majority like `b` / `e`. This does **not** block approval — blocking on it would make a worker failure produce a stricter gate than a healthy roster, the same paradox the ≥2-vote majority rule already rules out. The item is re-dispatched in the next round (step 7); if it survives the round budget it is promoted per step 8 with a Statement that says verification never completed. **Enforced:** `validators/validate-run.py` `_classify_plan_item_gate` returns `needs-reverify` for this shape and `_recompute_plan_body_gate` folds it into `passed-with-dissent`.
@@ -370,7 +370,7 @@ round before any host or provider process starts.
370
370
  5. Gate result resolution:
371
371
  - any `majority-disagree` item present AND `gating=true` → `blocked-by-disagreement`
372
372
  - all dispatches non-result → `aborted-non-result`
373
- - any `partial-consensus` / `dissent-isolated` / `needs-reverify` present, no `majority-disagree` → `passed-with-dissent`
373
+ - any advisory `dissent-isolated` / `needs-reverify` present, no `majority-disagree` → `passed-with-dissent`
374
374
  - all items `full-consensus` → `passed`
375
375
 
376
376
  **Score the gate with `okstra plan-verify`, never by hand (BLOCKING).** Once this round's verdicts are in the data.json, lead runs
@@ -388,9 +388,17 @@ round before any host or provider process starts.
388
388
  **A coverage row citing this run's own `C-NNN` is not an independent blocker.** When a coverage row's `blocked C-NNN` points at a clarification that step 8 below promoted from a `majority-disagree` item in *this same run*, that blocker is already counted once as the plan item. Counting it again as a coverage gap makes the run block on a clarification it just authored, and the row carries into the next run as a fresh blocker — the Requirement Coverage ↔ Clarification cycle. Such rows are excluded from `coverage-gap`. **Enforced:** `validators/validate-run.py` `_independent_coverage_blockers`.
389
389
  6. `okstra plan-items complete-round --run-manifest <current-run-manifest.json>` derives `planBodyVerification.participatingAnalysers` from the current assigned roster and persisted votes, then atomically records the completed round. The gate arithmetic is unchanged, but a shrunken roster changes what the round can settle: with two participating analysers a 1-AGREE / 1-DISAGREE split is a tie, so it reaches neither consensus nor `majority-disagree` and the item has to go back for a round (see `needs-reverify` above). **Enforced:** `validators/validate-run.py` `_validate_participating_analysers` recomputes `voting` from the recorded verdicts and fails a declared figure the table denies. `validators/validate-run.py` `_detect_uniform_verifier` remains advisory; do not copy its JSON output into state.
390
390
 
391
- **Check each verifier's verdict distribution before the next round.** Read the `okstra plan-verify` warnings alongside the gate value. Two shapes mean the roster was narrower than it looks: a verifier whose every vote was one token, and a verifier that returned no vote for items it was assigned. Both are contract violations of the adversarial posture, not stylistic preferences the verifier is told to open the cited evidence and judge it.
391
+ **Check each verifier's verdict distribution before the next round.** After `apply-verdicts` and before opening another worker batch, run `okstra plan-items next-dispatch --state <plan-body-verification.json> --run-manifest <current-run-manifest.json>`. Python owns that decision. Do not invent a full-roster round from a `needs-reverify` label, from every-item `UNVERIFIABLE`, or from `okstra plan-verify` warnings. `_detect_uniform_verifier` remains advisory; do not copy its JSON output into state.
392
392
 
393
- When either fires, re-dispatch that verifier with a correction paragraph stating the exact nature of the violation and what IS checkable in this worktree. A byte-identical re-dispatch reproduces the same failure; a corrected one recovered 37 substantive verdicts from a worker whose first attempt answered `UNVERIFIABLE` to all 80 items. The environment exception in §"Planning-time environment gap" covers **running build and test commands only** — whether a referenced path exists, whether a command is declared in `package.json`, and whether the plan is internally consistent are all checkable without it, and a blanket "capability constraints prevent workspace resolution" is not a valid answer to any of them.
393
+ | `kind` | What the lead does |
394
+ |---|---|
395
+ | `none` | Do not add a worker batch. A round whose only failures are missing-dependency command runs — `UNVERIFIABLE` on a declared `npm` / `pytest` / equivalent, §"Planning-time environment gap" — is this shape. |
396
+ | `worker-correction` | Re-dispatch **only** those workers. Peers are not re-run. The queue does not become a new round. Place the output of `okstra plan-items correction-prompt --worker <id> --run-manifest … --state …` first in that worker's prompt — the environment-exception paragraph is first. A byte-identical re-dispatch reproduces the same failure; a corrected one recovered 37 substantive verdicts from a worker whose first attempt answered `UNVERIFIABLE` to all 80 items. |
397
+ | `queue-reverify` | An unsettled tie on a blocking kind. Re-dispatch those `itemIds` only. |
398
+
399
+ A referenced **path** that does not exist is still `DISAGREE(b)` / a fact probe, never environment-unverifiable. **Enforced:** `okstra_ctl.plan_items.next_dispatch` / `correction_prompt_text`.
400
+
401
+ The environment exception in §"Planning-time environment gap" covers **running build and test commands only** — whether a referenced path exists, whether a command is declared in `package.json`, and whether the plan is internally consistent are all checkable without it, and a blanket "capability constraints prevent workspace resolution" is not a valid answer to any of them.
394
402
 
395
403
  **How the corrective round is recorded.** The first prompt was dispatched, so it is immutable — `--replace-undispatched` refuses it, correctly. Materialize the correction under a NEW `--invocation-id` and a new prompt path. Before linking its result, retire the first attempt's link: `okstra agent-prompt reject-result --run-manifest <path> --dispatch-id <first dispatch id> --superseded-by <corrective dispatch id> --reason "<what was wrong with the returned result>"`. Without that step the corrective `link-result` fails with `agent result is already linked to another dispatch`, which is how a worker that ran for twenty minutes and wrote a good result ends up unrecordable. Nothing is deleted: the rejected link stays in `agentResultLinks` carrying `supersededBy` and `rejectionReason`, so the ledger shows both attempts and why the second exists.
396
404
 
@@ -401,7 +409,7 @@ round before any host or provider process starts.
401
409
  - missing or weak `P-Prep-*` contracts are repaired by adding kind-specific inline detail or an AI-prepared PREP item with a concrete proposal. Facts that require user or external authority remain `blocked` and keep their request material; never invent those facts during self-fix.
402
410
  - **Drop plan items whose element the round deleted.** A self-fix rewrite may remove a plan element (a validation check, a rollback row). `P-*` ids are positional, so a deletion shifts every later row and silently re-points surviving verdicts at their neighbours — and a verdict recorded against a removed element keeps blocking a gate while being unfindable in the plan, so reading the plan never reveals the cause. After each round, re-extract plan items with `okstra plan-items extract` and re-verify any item whose `subject` no longer matches; never carry the old vote forward across a shift. **Enforced:** `validators/validate-run.py` `_validate_verdicts_match_current_subjects` (re-pointing) and `_validate_plan_item_extraction_completeness` (dangling ids).
403
411
  - **Classify each cause group before instructing it (BLOCKING).** A group is either an *authoring* defect — the plan says something wrong, incomplete, or self-contradictory, which self-fix owns — or a *citation* defect, where the plan points at an analysis artifact incorrectly. Only the first is self-fix work. For the second the finding already exists and already went through convergence, so the fix is to re-cite the converged artifact; instructing report-writer to re-derive the fact means the author reads the source material and produces a **finding that never went through convergence**, which the plan then carries as if it had. That is the role boundary the lead contract draws ("keep analysis, execution, verification, and report authoring responsibilities distinct; return defects to the role that owns them"), and report-writer is authoring-only by its own contract. `P-Req-*` items with breakage kind `f` are where this goes wrong most often: the question is usually whether a coverage row points correctly at something already measured, not whether the measurement is right. State the classification in the group's instruction so the author knows which of the two it is being asked to do.
404
- - **A verdict older than the last self-fix is not a verdict (BLOCKING).** A verdict cast in round 1 judged the text before the only automatic rewrite. Once that rewrite runs, the judgement is about a plan that no longer exists. `--round <N>` on `apply-verdicts` stamps each row, and `validators/validate-run.py` `_validate_verdict_rounds_outlive_self_fix` fails any non-carried item whose verdict round is at or before `selfFixRoundsApplied`. Before declaring the gate, every item still holding a pre-self-fix verdict MUST be re-verified in round 2.
412
+ - **A verdict older than the last self-fix is not a verdict unless the item's content is unchanged (BLOCKING).** A verdict cast in round 1 judged the text before the only automatic rewrite. Once that rewrite runs, a changed item's judgement is about a plan that no longer exists. `--round <N>` on `apply-verdicts` stamps each row and copies `contentHash` onto `verifiedContentHash`. `validators/validate-run.py` `_validate_verdict_rounds_outlive_self_fix` fails an in-scope item whose verdict round is at or before `selfFixRoundsApplied` **and** whose `contentHash` does not match `verifiedContentHash`. Matching hashes keep the prior verdict — that is what avoids a sweep round over unchanged stages. Deferred and observed items are out of the gate and do not need a post-self-fix verdict. **Enforced:** `_validate_verdict_rounds_outlive_self_fix`.
405
413
  - Lead re-runs plan-body verification, then records each worker Markdown result through `okstra plan-items apply-verdicts --state <plan-body-verification.json> --result <worker>=<result.md> --round <N>`. Score the result with `okstra plan-verify --narrative <report-writer-narrative.md> --state <plan-body-verification.json>`, then call `okstra plan-items complete-round --state <plan-body-verification.json> --run-manifest <current-run-manifest.json> --round <N>`. These commands fail on an assigned item the worker left unanswered, on a verdict for an item outside the queue, and on a duplicate worker result.
406
414
  - For a self-fix, record the correction through the typed convergence command rather than writing `selfFixNote` or `selfFixGroups` JSON. A resolved item does not create a clarification.
407
415
  - **Each round is a worker batch.** Before dispatching round N ≥ 2, reclaim the previous round's completed verifiers exactly as at any other batch boundary ([okstra-lead-contract](./okstra-lead-contract.md) "Run-scoped worker-resource lifecycle") and emit `PROGRESS: phase-batch-cleanup panes=<n>`, then announce the round with `PROGRESS: phase-5.5.9-plan-verify round=<N> items=<count>`. Saying a round will "reuse" the previous verifiers and then dispatching under fresh names leaves every prior round holding its panes — five rounds of that is what exhausts the pane budget and blocks the next dispatch. **Enforced:** `validators/validate_session_conformance.py` `_check_plan_verify_cleanup_checkpoints` requires both lines once the state file records two or more rounds.
@@ -547,9 +555,9 @@ The per-round structures mirror the finding-convergence state artifact ([converg
547
555
  | `gate.items[].classification` | `planItems[].rounds[].classification` | Condition |
548
556
  |---|---|---|
549
557
  | `full-consensus` | `full-consensus` | no `DISAGREE` |
550
- | `has-dissent` | `dissent-isolated` | exactly one `DISAGREE` |
551
- | `has-dissent` | `partial-consensus` | two or more `DISAGREE`, a tie included |
552
- | `majority-disagree` | `majority-disagree` | |
558
+ | `has-dissent` | `dissent-isolated` | exactly one advisory `DISAGREE` (`d` or `P-Rb-*`) |
559
+ | `has-dissent` | `partial-consensus` | two or more advisory `DISAGREE`s |
560
+ | `majority-disagree` | `majority-disagree` | majority, blocking-kind minority, or reproduced single-vote |
553
561
  | `needs-reverify` | `needs-reverify` | — |
554
562
  | `all-non-result` | `needs-reverify` | no non-error vote at all |
555
563
 
@@ -69,10 +69,10 @@ The pointer record contains the narrative and audit paths. Completion never depe
69
69
  ## Implementation-planning sequence
70
70
 
71
71
  1. Dispatch the report writer and wait for the narrative and pointer.
72
- 2. Parse the narrative and extract the deterministic plan-item queue without publishing `data.json`.
72
+ 2. Parse the narrative and extract the deterministic plan-item queue without publishing `data.json`. `okstra plan-items prepare` flips `convergence.planBodyVerification.gating` to `false` when the detector reports `no-design-inputs` and the Stage Map has one row.
73
73
  3. Run initial plan-body verification as round 1.
74
- 4. Apply at most one automatic planner self-fix to the narrative.
75
- 5. Run targeted re-verification as round 2 when needed.
74
+ 4. Apply at most one automatic planner self-fix to the narrative. Skip this step when `gating` is `false`.
75
+ 5. Run targeted re-verification as round 2 when needed. Skip this step when `gating` is `false`.
76
76
  6. Persist the completed `planBodyVerification` value in convergence state.
77
77
  7. Complete the design-surface detector snapshot.
78
78
  8. Run Phase 7 report assembly.
@@ -42,6 +42,7 @@ persisted prompt lacks the heading `Coding-conventions preflight`
42
42
  - **DB / IO / SQL changes require real execution — mock-only is NOT validation evidence:** when this run's diff touches DB/IO/SQL (ORM / query-builder code — sequelize / typeorm / prisma / knex / raw SQL — `*.repository.*`, model/entity files, `migrations/**`, `*.sql`, or any changed query string), a mocked unit test cannot observe the SQL the query builder actually emits (observed failure class: `_implementation-verifier.md` §"DB / IO / SQL change — real-execution gate"). The executor MUST run the change against a real (or faithful-replica) datastore — the `db-test` validation step (plan `validation` db step, else `project.json.qaCommands.db-test`), targeting a **local / replica** DB — and cite its exact command + exit code in the final report's `Validation evidence`. If no real DB / `db-test` command is reachable, do NOT claim the change verified: label the DB portion `static-analysis only …, unverified (not executed)` in the report, surface it in the routing recommendation, and never downplay the real run as "too heavy". `git push` stays forbidden (universal list); the unverified DB state is carried forward so `final-verification` cannot accept it and `release-handoff` cannot push.
43
43
  - **External-source adapters — structure AND fixture both derive from a captured real sample; a self-authored fixture is NOT reality evidence:** when this run's diff builds or changes an `external-interface` or `transformation-mapping` surface (an HTTP / network client, or a parser / mapper of a third-party payload — HTML / JSON / XML / CSV originating outside this repo), the adapter's structural assumptions (selectors, field paths, expected response shape) AND the static fixture / golden that tests them MUST BOTH derive from a **captured real sample** of that payload — the capture cited in the stage's `external-interface` / `transformation-mapping` design-prep item, or one captured this run and recorded with its `source` + capture time. The captured sample is a static fixture (no live socket), so a parser test against it stays in source like any unit test — the Real-IO isolation rule below governs *live* calls, not the captured bytes. Do NOT hand-invent the shape and then hand-write a fixture that agrees with it: the passing test then only proves the code matches your assumption, never that the assumption matches reality (self-confirming oracle — the observed failure was a parser whose selectors existed in its synthetic fixture and in zero real pages: hundreds of green units over a fiction, and the whole structure built on the wrong shape). When no real sample is reachable (no network this run, or the brief supplied none), do NOT synthesize a stand-in and present its green tests as correctness: mark the adapter's shape `reality-unverified (no captured sample)` in `Validation evidence`, keep any placeholder fixture explicitly labelled an assumption (never validation evidence), and surface an explicit **user-owned** item in the routing recommendation to confirm against real data. Unlike the DB gate above this does NOT itself block acceptance — live external verification stays a user-owned item per `final-verification`'s External QA advisory policy — but a synthetic external fixture presented as reality-verified is exactly the mock-only external evidence the `final-verification` test-correctness pass is meant to reject.
44
44
  - **Real-IO test isolation (BLOCKING).** A test that exercises a **real** datastore, HTTP endpoint, external service, message queue, or filesystem — a live DB connection / DSN, a real `fetch` / `axios` / `http` request, an actual S3 / queue client, anything the project's normal CI test suite cannot run because that backend is absent — MUST be written under the task's qa scripts directory `<task_root>/qa/scripts/` (`<TASK_QA_PATH>/scripts`; the `qa/` root itself holds only data sidecars — the Tier 3 conformance manifest and `result-*.json`). It MUST NOT be written into the project source test tree — `src/**`, `test/**`, `tests/**`, `**/__test__/**`, `**/__tests__/**`, `*.spec.*`, `*.test.*`, or anywhere the project's lint/test globs collect. Two reasons: (a) the project's CI / normal suite has no real DB or network, so a real-IO test placed in source silently breaks the pipeline; (b) it is an okstra verification artifact, and the artifact-home rule confines okstra outputs to `.okstra/`. **The dividing line is the IO, not the intent:** a unit test that stubs/spies only *injected collaborators* (mock — no real socket, no real DB handle) is a TDD red-green artifact and stays in source; the moment a test opens a real connection or makes a real network call it belongs in qa. A stage's real-IO requirement check is a Tier 3 conformance script under `<task_root>/qa/scripts/` (declared via the implementation-planning conformance entry) — never smuggle real IO into a `*.spec.*` in source to make it run "as a unit test". The `db-test` real-execution gate above is satisfied by the conformance/db-test path against the replica, NOT by adding a live-DB `*.spec.*` to the project suite. **Author qa specs with the project's own test framework — never hand-roll `describe`/`it`/`expect`.** When the project ships a test runner as a devDependency (jest / vitest / pytest …), the qa spec uses it, invoked with the project config plus a discovery override pointing at the qa scripts dir (jest: `npx jest --config <project jest config> --roots <task_root>/qa/scripts --runInBand <spec-name>`) — the project config keeps module aliases resolving while the default sweep never collects the file; never widen the project's own test config to include qa paths. For TypeScript qa specs also write `<task_root>/qa/scripts/tsconfig.json` (`extends` the project tsconfig, adds the runner's `types` entry, `"include": ["**/*.ts"]`) so editors resolve path aliases and test globals — it is a qa artifact like the rest (untracked). **These qa artifacts stay untracked — never commit them.** `.okstra/**` is gitignored (the artifact-home rule); conformance scripts and their results are *executed* and recorded in the carry sidecar / verifier result, never written into git history. A committed `.okstra/qa` file is a stage-branch defect that leaks okstra internals into the eventual PR (see the `git add` rules below).
45
+ - **Stage conformance script (BLOCKING when the approved plan declared `Conformance tests:`).** Planning only declared the path and `requires`. This run MUST write the script to that path under `<task_root>/qa/scripts/` and add the matching `<task_root>/qa/conformance-manifest.json` entry: `stageKey` (= `<task-id>-stage-<N>`), `script`, `runCommand`, `requirementIds`, `requires` (the set the plan declared), `passContract`, `exemption: null`, `waiver: null`. Do not skip this when the plan declared tests. If the plan declared `Conformance exemption:`, do not invent a script. The script's standard interface: a `main` that exits `0`=PASS / non-zero=FAIL, and whose stdout ends with `QA-RESULT: PASS|FAIL` followed by one `REQ <id>: PASS|FAIL: <reason>` line per requirement. The verifier runs `runCommand` from the **worktree cwd**, and that cwd is the tree under test. `runCommand` MUST NOT repoint it: a leading `cd <checkout> &&` sends the script at a tree without this stage's changes. Absolute paths are fine and usually necessary — the script and its `tsconfig` live under `<task_root>/qa/scripts/`, i.e. under `.okstra/`, and a worktree does not carry `.okstra/`. Point at those by absolute path; leave the cwd alone. **Enforced:** `scripts/okstra_ctl/conformance.py` `_check_entry` rejects a `runCommand` whose first word in any `&&` / `;` segment changes directory; `validators/validate-run.py` `_validate_conformance` fails the run if the inherited declaration has no script file.
45
46
  - read the approved plan at this prompt's `**Approved plan:**` anchor end-to-end and parse the `## 5.5 Stage Map`. Read this prompt's `**Stage for this implementation run:**` anchor: the single stage number this run owns. The runtime already selected and reserved this stage (one run = one stage) — do NOT recompute the start stage from `consumers.jsonl`. Both anchors are generated headers; when either is missing, stop and report `contract-violated` rather than inferring the value.
46
47
  - load every `runs/<plan-key>/carry/stage-<i>.json` for `i ∈ depends-on(this stage)` and inject them into the executor's working context as "runtime carry-in". For a `depends-on (none)` stage, no sidecar load — task-brief only.
47
48
  - this stage's `depends-on` are all already `status:done`. Its file list, step order, Stage Validation commands, Stage Exit Contract, and rollback path are the authoritative scope.