okstra 0.130.1 → 0.130.3

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 (43) hide show
  1. package/docs/architecture.md +7 -1
  2. package/docs/cli.md +10 -2
  3. package/docs/project-structure-overview.md +16 -9
  4. package/docs/task-process/implementation-planning.md +11 -5
  5. package/package.json +1 -1
  6. package/runtime/BUILD.json +2 -2
  7. package/runtime/agents/workers/antigravity-worker.md +3 -1
  8. package/runtime/agents/workers/claude-worker.md +1 -1
  9. package/runtime/agents/workers/codex-worker.md +3 -1
  10. package/runtime/agents/workers/report-writer-worker.md +8 -4
  11. package/runtime/prompts/lead/adapters/claude-code.md +2 -2
  12. package/runtime/prompts/lead/convergence.md +44 -21
  13. package/runtime/prompts/lead/okstra-lead-contract.md +9 -2
  14. package/runtime/prompts/lead/plan-body-verification.md +55 -12
  15. package/runtime/prompts/lead/report-writer.md +16 -15
  16. package/runtime/prompts/lead/team-contract.md +8 -6
  17. package/runtime/prompts/profiles/implementation-planning.md +10 -2
  18. package/runtime/python/okstra_ctl/codex_dispatch.py +5 -1
  19. package/runtime/python/okstra_ctl/convergence.py +121 -1
  20. package/runtime/python/okstra_ctl/convergence_engine.py +903 -13
  21. package/runtime/python/okstra_ctl/convergence_migration.py +9 -1
  22. package/runtime/python/okstra_ctl/dispatch_core.py +13 -0
  23. package/runtime/python/okstra_ctl/plan_items.py +203 -0
  24. package/runtime/python/okstra_ctl/plan_items_cli.py +81 -0
  25. package/runtime/python/okstra_ctl/report_finalize.py +6 -4
  26. package/runtime/python/okstra_ctl/worker_artifact_paths.py +23 -0
  27. package/runtime/python/okstra_ctl/worker_liveness.py +4 -4
  28. package/runtime/python/okstra_ctl/worker_prompt_body.py +1 -1
  29. package/runtime/python/okstra_ctl/worker_prompt_contract.py +2 -1
  30. package/runtime/python/okstra_ctl/worker_prompt_headers.py +14 -0
  31. package/runtime/schemas/convergence-critic-results-v1.0.schema.json +57 -0
  32. package/runtime/schemas/convergence-groups-v1.0.schema.json +109 -0
  33. package/runtime/schemas/convergence-round-results-v1.0.schema.json +55 -0
  34. package/runtime/schemas/final-report-v1.0.schema.json +21 -2
  35. package/runtime/templates/report-writer-prompt-preamble.md +5 -3
  36. package/runtime/templates/reports/final-report.template.md +1 -1
  37. package/runtime/templates/worker-prompt-preamble.md +8 -7
  38. package/runtime/validators/validate-run.py +154 -118
  39. package/runtime/validators/validate_session_conformance.py +66 -23
  40. package/src/cli-registry.mjs +7 -0
  41. package/src/commands/execute/convergence.mjs +6 -1
  42. package/src/commands/execute/plan-items.mjs +9 -0
  43. package/src/commands/inspect/worker-liveness.mjs +2 -2
@@ -331,7 +331,7 @@ After the Report writer worker draft is reviewed (or after the lead-authored fal
331
331
 
332
332
  This is a Phase 6 sub-step — it does NOT introduce a new top-level lifecycle phase; the lead operating-phase model (Phase 1 Intake → Phase 7 Persist, labels in the "Quick Reference" table above as the single source of truth) is preserved. `okstra-inspect status` exposes it as a `planVerification` sub-field of the implementation-planning phase, not a separate lifecycle phase identifier.
333
333
 
334
- **REQUIRED RESOURCE:** Read [plan-body-verification](./plan-body-verification.md) for the round protocol, plan-item ID scheme (`P-Opt-*` / `P-Step-*` / `P-Dep-*` / `P-Val-*` / `P-Rb-*`), verdict semantics (`AGREE` / `DISAGREE(a-e)` / `SUPPLEMENT`), classification rules, gate-result resolution, and the state-file schema at `runs/<task-type>/state/plan-body-verification.json`.
334
+ **REQUIRED RESOURCE:** Read [plan-body-verification](./plan-body-verification.md) for the round protocol, plan-item ID scheme (`P-Opt-*` / `P-Step-*` / `P-Dep-*` / `P-Val-*` / `P-Rb-*` / `P-Req-*` / `P-Prep-*`), verdict semantics (`AGREE` / `DISAGREE(a-f)` / `SUPPLEMENT`), classification rules, gate-result resolution, and the state-file schema at `runs/<task-type>/state/plan-body-verification.json`.
335
335
 
336
336
  Distinct from Phase 5.5 finding convergence:
337
337
 
@@ -341,7 +341,14 @@ Distinct from Phase 5.5 finding convergence:
341
341
 
342
342
  Lead's responsibilities in this sub-step (in order):
343
343
 
344
- 1. Extract `P-*` plan items from the draft report's `## 5.4 Implementation Plan Deliverables` per the prefix source-section mapping in the plan-body-verification contract.
344
+ 1. Build the queue with `okstra plan-items extract --data <data.json> --output <state>/plan-items-....json`, place the persisted `items[]` verbatim in every verifier prompt, then run `okstra plan-items validate --data <data.json> --items <state>/plan-items-....json`. 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:
345
+
346
+ ```text
347
+ What concrete false-positive input, failure ordering, or omitted dependency
348
+ would make this plan item incorrect even if its happy path succeeds?
349
+ ```
350
+
351
+ An `AGREE` response records the considered counterexample and exclusion reason in its note; unverified external material is `verification-error`, not `DISAGREE`.
345
352
  2. Dispatch a single plan-body reverify round to every analyser worker in the roster (`claude`, `codex`, and `antigravity` when opted in). `Report writer worker` is NOT a participant in this round.
346
353
  3. Aggregate verdicts and resolve the gate result to one of `passed` / `passed-with-dissent` / `blocked-by-disagreement` / `aborted-non-result`.
347
354
  4. Write `runs/<task-type>/state/plan-body-verification.json` (schema in the plan-body-verification contract).
@@ -15,8 +15,10 @@ Plan-body verification runs **after** finding convergence and **after** the repo
15
15
  ```
16
16
  Phase 4 workers produce independent analyses (Findings F-001…)
17
17
  → Phase 5.5 FINDING convergence ([convergence](./convergence.md), sections "Convergence Algorithm" through "Convergence State Artifact")
18
- → Phase 6 report-writer authors final-report draft (consolidated Option Candidates / Stepwise Execution Order / Dependency / Validation Checklist / Rollback)
18
+ → Phase 6 report-writer authors final-report data.json (consolidated Option Candidates / Stepwise Execution Order / Dependency / Validation Checklist / Rollback)
19
+ → okstra plan-items extract + validate creates the deterministic P-* queue
19
20
  → PLAN-BODY VERIFICATION ROUND ← this contract
21
+ → final render/validation
20
22
  → User Approval gate (the frontmatter `approved:` flip is honoured by run-prep only when this round's Gate result is `passed` or `passed-with-dissent`)
21
23
  → implementation phase (separate run)
22
24
  ```
@@ -51,7 +53,21 @@ The shared Majority definition and the auto-disable rule (fewer than 2 analyser
51
53
 
52
54
  ## Plan-item extraction (Round 0 equivalent)
53
55
 
54
- From the report-writer's draft of `## 5.4 Implementation Plan Deliverables`, lead extracts plan items with the following prefixes (see also `templates/reports/final-report.template.md` §5.5.9):
56
+ From the report-writer's draft of `## 5.4 Implementation Plan Deliverables`, the lead creates the verification queue only through this sequence (see also `templates/reports/final-report.template.md` §5.5.9):
57
+
58
+ ```text
59
+ okstra plan-items extract --data <data.json> --output <state>/plan-items-....json
60
+ → place the persisted `items[]` verbatim in every verifier prompt
61
+ → okstra plan-items validate --data <data.json> --items <state>/plan-items-....json
62
+ ```
63
+
64
+ The persisted `items[]` are the sole queue. The lead MUST NOT freely summarise,
65
+ select, omit, reorder, or renumber items. A verifier prompt may use the compact `subject`
66
+ as its heading, but it MUST include the lossless `payload` for the item's evidence and
67
+ judgement. The final `validate` command confirms that the persisted queue exactly matches
68
+ the current draft before verdict aggregation.
69
+
70
+ The deterministic extractor assigns the following prefixes:
55
71
 
56
72
  | Prefix | Source sub-section | One row per |
57
73
  |--------|--------------------|-------------|
@@ -67,7 +83,7 @@ From the report-writer's draft of `## 5.4 Implementation Plan Deliverables`, lea
67
83
 
68
84
  Each plan item inherits the `[TICKETID: ...]` tag of its source section (per the standard ticket-tagging contract).
69
85
 
70
- For every detector-produced `(stage, kind)`, extract exactly one plan item named `P-Prep-S<stage>-<kind>`. The V1 detector in `scripts/okstra_ctl/design_surfaces.py` owns the set: extraction consumes its output and never reruns a free-form requirements or keyword analysis. The worker receives the stage trigger evidence and its single `designSurfaceCoverage` row, plus only the referenced `designPreparation` PREP items needed to judge that row. `validators/validate-run.py` `_validate_expected_prep_plan_items` enforces the exact ID set and `_validate_detector_coverage` enforces exactly-once coverage/evidence.
86
+ For every detector-produced `(stage, kind)`, extract exactly one plan item named `P-Prep-S<stage>-<kind>`. The V1 detector in `scripts/okstra_ctl/design_surfaces.py` owns the set: extraction consumes its output and never reruns a free-form requirements or keyword analysis. The worker receives the stage trigger evidence and its single `designSurfaceCoverage` row, plus only the referenced `designPreparation` PREP items needed to judge that row. `okstra_ctl.plan_items.expected_plan_item_ids()` and `validators/validate-run.py` enforce the exact ID set, while `_validate_detector_coverage` enforces exactly-once coverage/evidence.
71
87
 
72
88
  When extracting each item, lead also captures a **`subject`** — a plain one-line label (≤12 words) describing *what that item is* in the reader's terms, e.g. `P-Opt-1` → "Option A: split upload v2 into a new module", `P-Step-1.1` → "Stage 1 Step 2: regression-check with `npm run test:v2`". This is a label-capture, not new analysis. The `subject` is what §5.5.9 renders as the per-item heading so the reader knows *what* each AGREE/DISAGREE is about without cross-referencing §4.5; a bare `P-*` ID with no subject is a contract violation. **Enforced:** `validators/validate-run.py` `_validate_plan_item_subject_substance` fails a subject that is a placeholder — under 3 chars, equal to the item id, or shaped like a bare `P-*` id.
73
89
 
@@ -104,7 +120,7 @@ Worker non-result handling (`timeout`, `error`, no result file, wrapper `cli-fai
104
120
 
105
121
  Plan-body verification only supports **lightweight mode** (defined in [convergence](./convergence.md) §"Verification Mode"). `full-reanalysis` is not meaningful here because the "original source materials" for a plan item are the worker's own analysis plus the lead-mediated synthesis — there is no independent ground truth to re-read. The manifest's top-level `verificationMode` is ignored for this round; lightweight is always used.
106
122
 
107
- Exception for `P-Req-*`: verifiers still MUST NOT re-open the original task brief for this round, but they MUST compare the requirement text embedded in the `Requirement Coverage` row with the cited Option / Stage / Step in the draft plan. A row is not sound merely because it says `covered`; the cited plan item must actually satisfy the row's stated requirement.
123
+ Exception for `P-Req-*`: verifiers still MUST NOT re-open the original task brief for this round, but they MUST compare the requirement text embedded in the `Requirement Coverage` row with the cited Option / Stage / Step in the draft plan. A row is not sound merely because it says `covered`; the cited plan item must actually satisfy the row's stated requirement. A `documented-deviation` never earns automatic `AGREE`: verify all four parts independently — the original requirement, the concrete alternative in `coveredBy`, every `decisionRefs` target, and the `approvalDisposition`. `accepted` requires a referenced user-confirmed clarification; `blocked C-NNN` requires that same-report clarification to be open and block approval.
108
124
 
109
125
  ## Adversarial plan-body posture
110
126
 
@@ -119,7 +135,7 @@ Plan-body verification stays **lightweight** even under this posture — the `ve
119
135
 
120
136
  ## Round protocol (single round at default `maxRounds=1`)
121
137
 
122
- 1. Lead parses the report-writer draft and extracts the `P-*` plan items.
138
+ 1. Lead runs `okstra plan-items extract --data <data.json> --output <state>/plan-items-....json`, places the persisted `items[]` verbatim in every verifier prompt with the compact `subject` and lossless `payload`, then runs `okstra plan-items validate --data <data.json> --items <state>/plan-items-....json`. Dispatch only after that exact-match validation succeeds.
123
139
  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.
124
140
  3. Dispatch uses the same wrapper infrastructure as finding convergence. The `--role-slug` is `<role>-plan-verify-r<N>`. Result file path: `runs/<task-type>/worker-results/<role-slug>-plan-verify-r<N>-implementation-planning-<seq>.md`.
125
141
  4. After all dispatches return, lead aggregates verdicts per `P-*` item across workers and classifies each:
@@ -199,7 +215,7 @@ Plan-body verification stays **lightweight** even under this posture — the `ve
199
215
 
200
216
  `planItems[].classification` enum: `full-consensus | partial-consensus | dissent-isolated | majority-disagree | needs-reverify | contested`. `needs-reverify` is the peer-error shape from §"Round protocol" step 4 (a single-vote-blocking kind with fewer than 2 participating non-error votes) — it survives into the state file when the round budget runs out before the re-dispatch resolves it, and `_recompute_plan_body_gate` folds it into `passed-with-dissent`. `contested` only appears when `maxRounds > 1`; at default `maxRounds=1` any otherwise-unresolved item folds into `partial-consensus` per the round protocol above.
201
217
 
202
- `planItems[].votes.<worker>` is the verbatim verdict token emitted by the worker — `AGREE | DISAGREE(<a|b|c|d|e|f>) | SUPPLEMENT` or `verification-error` for terminal non-result dispatches. The `DISAGREE` token retains its `<kind>` suffix so the breakage class is recoverable from the state file alone.
218
+ `planItems[].votes.<worker>` stores `AGREE | DISAGREE(<a|b|c|d|e|f>) | SUPPLEMENT` as emitted. A terminal non-result dispatch produces a non-result `verification-error` for each assigned item; a completed dispatch produces a per-item `verification-error` when that item is `UNVERIFIABLE`. Either `verification-error` MUST NOT be converted to `DISAGREE` and is excluded from consensus. The `DISAGREE` token retains its `<kind>` suffix so the breakage class is recoverable from the state file alone.
203
219
 
204
220
  `planBodyVerification.selfFixRoundsApplied` (int, default 0): how many self-fix rounds actually ran. `planBodyVerification.selfFixStopReason` (enum, default `not-attempted`): why the loop exited — see §"Round protocol" step 7. `planItems[].verdicts[].fixability` is each `DISAGREE`'s `planner-fixable | needs-user-input` judgement, recorded in the final data.json (`§5.5.9`).
205
221
 
@@ -228,15 +244,36 @@ verdict:
228
244
  When you give a DISAGREE, also answer **Fixability** — `planner-fixable` if this defect can be fixed using only the code + this plan draft + the brief, `needs-user-input` if an open user clarification / external information is required.
229
245
  - **SUPPLEMENT**: The item is sound but a dependency / edge case / precondition
230
246
  is missing.
247
+ - **UNVERIFIABLE**: Capability, credential, network, or service state prevents
248
+ verification of this item. The input alias is persisted as `verification-error`;
249
+ Fixability is not applicable.
231
250
 
232
251
  Do NOT re-analyze the original requirements. Judge solely from plan internal
233
252
  consistency and stated commands / paths. Do NOT inspect the original task brief
234
253
  or worker analyses for this round.
235
254
 
255
+ For every `P-*` item, answer this fixed falsification question before choosing
256
+ the verdict:
257
+
258
+ ```markdown
259
+ What concrete false-positive input, failure ordering, or omitted dependency
260
+ would make this plan item incorrect even if its happy path succeeds?
261
+ ```
262
+
263
+ Even for `AGREE`, the worker MUST record the considered counterexample and why
264
+ the supplied `payload` excludes it in the verdict note. If capability,
265
+ credential, network, or service state prevents verification, record
266
+ `UNVERIFIABLE` for that item. It is persisted as `verification-error`, excluded from both numerator and denominator, and must not be converted to `DISAGREE`.
267
+ Every verdict requires a non-empty explanation.
268
+
236
269
  For `P-Req-*` items, compare only the requirement text embedded in the row
237
270
  against the cited plan item(s). Do not open the original brief, but do reject
238
271
  coverage rows that cite no concrete option/stage/step or cite a plan item that
239
- does not satisfy the row's own requirement.
272
+ does not satisfy the row's own requirement. For `documented-deviation`, do not
273
+ auto-AGREE based on the status token: separately verify the requirement, the
274
+ alternative stated in `coveredBy`, each `decisionRefs` target, and the
275
+ `approvalDisposition` (`accepted` must be user-confirmed; `blocked C-NNN` must
276
+ name an open `Blocks=approval` clarification in this report).
240
277
 
241
278
  For each `P-Prep-S<stage>-<kind>` item, use only the supplied detector trigger
242
279
  evidence, its single `designSurfaceCoverage` row, and referenced PREP items.
@@ -254,6 +291,8 @@ rerun a free-form requirements analysis. Every DISAGREE includes Fixability.
254
291
  - Are referenced file paths consistent with the option's File Structure list?
255
292
  - Is the named command executable as written?
256
293
  - Does the success criterion produce an observable signal?
294
+ **Payload**:
295
+ > <lossless persisted payload>
257
296
 
258
297
  ### P-Opt-2 [TICKETID: <id>]: <one-line summary>
259
298
  ...
@@ -263,24 +302,28 @@ rerun a free-form requirements analysis. Every DISAGREE includes Fixability.
263
302
  **Trigger evidence**: <detector-produced evidence rows>
264
303
  **Coverage row**: <the single designSurfaceCoverage row>
265
304
  **Referenced PREP items**: <verbatim referenced items, or none>
305
+ **Payload**:
306
+ > <lossless persisted payload>
266
307
 
267
308
  ## Response format
268
309
 
269
310
  ### P-Step-3
270
- **Verdict**: AGREE | DISAGREE(<a|b|c|d|e|f>) | SUPPLEMENT
271
- **Fixability** (only when DISAGREE): planner-fixable | needs-user-input — "planner-fixable if it can be fixed using only the code + this plan + the brief"
311
+ **Verdict**: AGREE | DISAGREE(<a|b|c|d|e|f>) | SUPPLEMENT | UNVERIFIABLE
312
+ **Fixability** (only when DISAGREE): planner-fixable | needs-user-input — "planner-fixable if it can be fixed using only the code + this plan + the brief". Fixability is not applicable for `UNVERIFIABLE`.
313
+ **Note**: <the falsification candidate considered and why it is excluded; required even for AGREE>
272
314
  **Explanation**: <2-3 sentences>
273
315
 
274
316
  ### P-Opt-2
275
317
  ...
276
318
 
277
319
  ### P-Prep-S2-external-interface
278
- **Verdict**: AGREE | DISAGREE(<a|b|c|d|e|f>) | SUPPLEMENT
279
- **Fixability** (DISAGREE only): planner-fixable | needs-user-input
320
+ **Verdict**: AGREE | DISAGREE(<a|b|c|d|e|f>) | SUPPLEMENT | UNVERIFIABLE
321
+ **Fixability** (DISAGREE only): planner-fixable | needs-user-input. Fixability is not applicable for `UNVERIFIABLE`.
322
+ **Note**: <the falsification candidate considered and why it is excluded; required even for AGREE>
280
323
  **Explanation**: <2-3 sentences applying the disposition-specific rule>
281
324
  ```
282
325
 
283
- When `config.adversarial == true`, the lead prepends the adversarial framing from §"Adversarial plan-body posture" to the `## Instructions` block: the burden of proof is on the plan, the verifier opens and confirms every cited path / command, and an item whose cited references cannot be confirmed is answered `DISAGREE(<kind>)` rather than `AGREE`. The verdict tokens, breakage kinds (a–f), classification, and the majority gate threshold are unchanged. This prepended framing supersedes the template's "Judge solely from plan internal consistency" instruction for the adversarial round.
326
+ When `config.adversarial == true`, the lead prepends the adversarial framing from §"Adversarial plan-body posture" to the `## Instructions` block: the burden of proof is on the plan, the verifier opens and confirms every accessible cited path / command, and evidence that was opened but is insufficient yields the applicable `DISAGREE(<kind>)` rather than `AGREE`. Inability to inspect because of capability, credential, network, or service state yields `UNVERIFIABLE`, not DISAGREE. The verdict tokens, breakage kinds (a–f), classification, and the majority gate threshold are unchanged. This prepended framing supersedes the template's "Judge solely from plan internal consistency" instruction for the adversarial round.
284
327
 
285
328
  The "Reverify prompt: required-reading suppression" rule in [convergence](./convergence.md) (lightweight mode does NOT inject a `[Required reading]` clause) applies here as well.
286
329
 
@@ -35,30 +35,31 @@ The prompt MUST include, in this order at the top:
35
35
  1. `**Project Root:** <absolute-path>`
36
36
  2. `**Prompt History Path:** <project-relative-path>` (under current run `prompts/`)
37
37
  3. `**Result Path:** runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json` — canonical JSON SSOT. The renderer produces the sibling `.md` automatically.
38
- 4. `**Worker Result Path:** runs/<task-type>/worker-results/report-writer-worker-<task-type>-<seq>.md`mandatory validator-checked worker-results audit file
39
- 5. `Assigned worker prompt history path: <absolute-path>`
40
- 6. The four BLOCKING dispatch anchor headers generated from the report-writer audience (the worker cannot synthesize any of these paths):
38
+ 4. `**Audit sidecar path:** <absolute-path>`the generated report-writer audit destination derived from the Markdown `**Worker Result Path:**`, never from Result Path.
39
+ 5. `**Worker Result Path:** runs/<task-type>/worker-results/report-writer-worker-<task-type>-<seq>.md` — canonical Markdown worker-result source for the audit-path derivation.
40
+ 6. `Assigned worker prompt history path: <absolute-path>`
41
+ 7. The four BLOCKING dispatch anchor headers generated from the report-writer audience (the worker cannot synthesize any of these paths):
41
42
  - `**Worker Preamble Path:** <absolute-path>` — selects `templates/report-writer-prompt-preamble.md`.
42
43
  - `**Worker Error Contract Path:** <absolute-path>` — selects `templates/worker-error-contract.md`.
43
44
  - `**Errors log path:** <absolute-path>` — run-level errors JSONL (`logs/errors-<task-type>-<seq>.jsonl`).
44
45
  - `**Errors sidecar path:** <absolute-path>` — this worker's per-run sidecar JSON (`worker-results/report-writer-worker-errors-<task-type>-<seq>.json`).
45
- 7. `**Model:** Report writer worker, <modelExecutionValue>` (resolved per Phase 5.5 anchor-header rules)
46
- 8. The full `[Required reading]` clause (see [team-contract](./team-contract.md)) — for Phase 6 it adds two **per-task-type, instruction-set-local** read-only files, both scoped to this run's task-type by `okstra-ctl` at prep time:
46
+ 8. `**Model:** Report writer worker, <modelExecutionValue>` (resolved per Phase 5.5 anchor-header rules)
47
+ 9. The full `[Required reading]` clause (see [team-contract](./team-contract.md)) — for Phase 6 it adds two **per-task-type, instruction-set-local** read-only files, both scoped to this run's task-type by `okstra-ctl` at prep time:
47
48
  - `<instruction-set>/final-report-schema.json` — a task-type excerpt of the data.json schema (the other task-types' deliverable blocks and their unreachable `$defs` are stripped; ~38% of the full schema is `$defs` alone). This is your authoring aid for the data.json shape — the installed schema, not the excerpt, is what the run is judged against. Do **NOT** pull the full `schemas/final-report-v1.0.schema.json` — it carries all task-types and its `schemas/...` path is not part of the task bundle. (Validation still runs against the full schema post-hoc via the renderer, so the excerpt never relaxes the contract.)
48
49
  - `<instruction-set>/final-report-template.md` — the **phase-stripped** template (every other task-type's §5.x deliverable block removed by `render.py`'s `_strip_phase_blocks`, leaving only your run's §5.x). Do **NOT** also pull the full `templates/reports/final-report.template.md` source (it re-adds ~330 lines of other phases' deliverables and is not in the task bundle).
49
- 9. A one-line MCP pointer instead of the verbatim block (redundant — the brief is already in the report-writer's Required reading, item 8): `**MCP servers:** follow the task brief's "## Available MCP Servers" section (already in your Required reading).`
50
- 10. The convergence classifications (Full/Partial/Contested/Worker-Unique), the round history data (`roundHistory[]`), the `round2SkippedReason` value, and pointers to all worker result files under `worker-results/`. The report-writer worker populates `crossVerification.roundHistory` in the data.json so Section 6 can show which rounds executed, queue sizes, and why Round 2 was (or was not) skipped. The renderer prints the full per-round table only when more than one round ran; single-round or zero-round histories are auto-collapsed to a one-line summary.
51
- 11. `**Report Language:** <en|ko>` — must be either `en` or `ko`; `auto`
50
+ 10. A one-line MCP pointer instead of the verbatim block (redundant — the brief is already in the report-writer's Required reading, item 9): `**MCP servers:** follow the task brief's "## Available MCP Servers" section (already in your Required reading).`
51
+ 11. The convergence classifications (Full/Partial/Contested/Worker-Unique), the round history data (`roundHistory[]`), the `round2SkippedReason` value, and pointers to all worker result files under `worker-results/`. The report-writer worker populates `crossVerification.roundHistory` in the data.json so Section 6 can show which rounds executed, queue sizes, and why Round 2 was (or was not) skipped. The renderer prints the full per-round table only when more than one round ran; single-round or zero-round histories are auto-collapsed to a one-line summary.
52
+ 12. `**Report Language:** <en|ko>` — must be either `en` or `ko`; `auto`
52
53
  has been resolved by the lead from project.json / global config
53
54
  before the dispatch is constructed. The worker copies this verbatim
54
55
  into `data.json.meta.reportLanguage`.
55
- 12. For implementation-planning runs: a literal block listing the 12 required English section headings — `Option Candidates`, `Trade-off`, `Recommended Option`, `Stage Map`, `Stepwise Execution Order`, `Dependency`, `Validation Checklist`, `Rollback`, `Requirement Coverage`, `Plan Body Verification`, `Cross-Project Dependencies`, `Decision Drafts`. This list is `PLANNING_REQUIRED_SECTIONS` in `validators/validate-run.py`; that tuple is the SSOT and this block must match it exactly. The writer uses these exact substrings as section headings (Korean translation in parentheses is allowed), and the `Plan Body Verification` section carries its required `Gate result:` line.
56
- 13. An explicit instruction: `You are the author of TWO files: (a) the final-report data.json at <Result Path>, (b) the worker-results audit file at <Worker Result Path>. After writing the data.json, invoke "okstra render-final-report <Result Path>" through the available execution interface so the markdown sibling is rendered before you return. Do not return the report inline. The validator fails the run when (a)'s schema validation fails, when the rendered markdown is absent, or when (b) is missing.`
57
- 14. The prose budget (dedup contract): `verdictCard.finalConclusion` is the conclusion SSOT — at most 3 sentences. `rationale.*` fields stay within 2 sentences each and reference the verdict card / row IDs instead of restating their prose; `readerSummary` fields are one line each; `summary` stays at 3-5 rows unless the run covers multiple tickets. The schema field descriptions carry the same budgets (`tests/contract/test_report_prose_budget.py` guards both surfaces). Generation time scales with output volume, so exceeding the budget is a cost bug, not extra diligence.
56
+ 13. For implementation-planning runs: a literal block listing the 12 required English section headings — `Option Candidates`, `Trade-off`, `Recommended Option`, `Stage Map`, `Stepwise Execution Order`, `Dependency`, `Validation Checklist`, `Rollback`, `Requirement Coverage`, `Plan Body Verification`, `Cross-Project Dependencies`, `Decision Drafts`. This list is `PLANNING_REQUIRED_SECTIONS` in `validators/validate-run.py`; that tuple is the SSOT and this block must match it exactly. The writer uses these exact substrings as section headings (Korean translation in parentheses is allowed), and the `Plan Body Verification` section carries its required `Gate result:` line.
57
+ 14. An explicit instruction: `You are the author of TWO files: (a) the final-report data.json at <Result Path>, (b) the worker-results audit file at <Audit sidecar path>. After writing the data.json, invoke "okstra render-final-report <Result Path>" through the available execution interface so the markdown sibling is rendered before you return. Do not return the report inline. The validator fails the run when (a)'s schema validation fails, when the rendered markdown is absent, or when (b) is missing.`
58
+ 15. The prose budget (dedup contract): `verdictCard.finalConclusion` is the conclusion SSOT — at most 3 sentences. `rationale.*` fields stay within 2 sentences each and reference the verdict card / row IDs instead of restating their prose; `readerSummary` fields are one line each; `summary` stays at 3-5 rows unless the run covers multiple tickets. The schema field descriptions carry the same budgets (`tests/contract/test_report_prose_budget.py` guards both surfaces). Generation time scales with output volume, so exceeding the budget is a cost bug, not extra diligence.
58
59
 
59
60
  **Fix-run incremental authoring (applies when the run's profile carries a "Fix-Run Carry" block).** Do not author the data.json from scratch. Start by copying the previous run's data.json (the `Previous report` path in the Fix-Run Carry block) to this run's Result Path, then update ONLY the blocks the fix run changed: `meta`/`header` (run seq, dates), `executionStatus`, `implementation.verifierResults`, `implementation.validationEvidence`, `implementation.commitList` / `diffSummary`, `crossVerification`, `verdictCard`, `finalVerdict`, and any `evidence` rows the fix touched. Deliverable prose for unchanged sections is carried forward verbatim — do not re-generate it. Then invoke the renderer exactly as in a full run. The schema validation and renderer contract are unchanged, so an incrementally-authored data.json passes the same post-hoc gates. The lead's dispatch prompt MUST include the previous data.json path when the carry block is present.
60
61
 
61
- **Completion detection after dispatch (BLOCKING).** A dispatch acknowledgement is NOT completion — detect completion via the SSOT protocol in [team-contract](./team-contract.md) "Worker-completion detection", with a one-entry pending set covering the data.json (Result Path) and the worker-results file (Worker Result Path). Do NOT end the turn with a prose "waiting for the report" statement.
62
+ **Completion detection after dispatch (BLOCKING).** A dispatch acknowledgement is NOT completion — detect completion via the SSOT protocol in [team-contract](./team-contract.md) "Worker-completion detection", with a one-entry pending set covering the data.json (Result Path) and the worker-results audit file (Audit sidecar path). Do NOT end the turn with a prose "waiting for the report" statement.
62
63
 
63
64
  ### Resume-safe dispatch
64
65
 
@@ -265,12 +266,12 @@ When the run's `task-type` is `release-handoff`, the final report MUST include S
265
266
 
266
267
  The final-report template `templates/reports/final-report.template.md` Section 5.6 already encodes this contract — copy that block verbatim and fill in. For non-`release-handoff` runs, omit Section 5.6 entirely.
267
268
 
268
- ### Mandatory worker-results file (BLOCKING)
269
+ ### Mandatory worker-results audit file (BLOCKING)
269
270
 
270
- You (the report-writer worker) MUST also write a worker-results audit file at the path the lead provides as `**Worker Result Path:**`, defaulting to:
271
+ You (the report-writer worker) MUST also write a worker-results audit file at the absolute path the lead provides as `**Audit sidecar path:**`, derived from `**Worker Result Path:**` and defaulting to:
271
272
 
272
273
  ```
273
- runs/<task-type>/worker-results/report-writer-worker-<task-type>-<seq>.md
274
+ runs/<task-type>/worker-results/report-writer-worker-audit-<task-type>-<seq>.md
274
275
  ```
275
276
 
276
277
  This file is checked by the validator whenever the role's terminal status is `completed`. Without it the run fails with `report-writer is completed but worker result file is missing`.
@@ -63,7 +63,7 @@ Only workers selected from `recommendedWorkers` in `task-manifest.json` and `res
63
63
 
64
64
  ## Worker Prompt Composition
65
65
 
66
- Every worker prompt MUST start with the anchor headers rendered by `okstra_ctl.worker_prompt_headers.worker_prompt_headers()` (the generating SSOT — never hand-author or reorder them). Their meaning and extraction rules for workers are documented in the worker preamble §"Anchor headers". Phase-specific extra headers (implementation worktree, final-verification target snapshot, improvement-discovery grilling log) are listed there too.
66
+ Every worker prompt MUST start with the anchor headers rendered by `okstra_ctl.worker_prompt_headers.worker_prompt_headers()` (the generating SSOT — never hand-author or reorder them). The generated absolute `**Audit sidecar path:**` is derived by `audit_sidecar_rel()` from the canonical worker result path; workers write to that header and never synthesize a `runs/<task-type>/...` destination. Their meaning and extraction rules for workers are documented in the worker preamble §"Anchor headers". Phase-specific extra headers (implementation worktree, final-verification target snapshot, improvement-discovery grilling log) are listed there too.
67
67
 
68
68
  The `**Coding preflight pack:**` anchor is emitted only for the `implementation-executor` and `implementation-verifier` audiences. An implementation report-writer never receives it. A pane display role named `verifier` during `final-verification` is still an initial analysis worker; it neither receives implementation conventions nor becomes a Phase 5.5 reverify dispatch. Reverify is identified by the `-reverify-r<N>-` prompt/result path contract in [convergence](./convergence.md).
69
69
 
@@ -71,7 +71,7 @@ For `improvement-discovery`, `worker_prompt_headers()` resolves and validates th
71
71
 
72
72
  The body must include: role name, task type, task key, assigned model, output contract, evidence handling rules, and `analysis-packet.md` as the single primary analysis input. For `final-verification`, the compact target anchors and that packet path are sufficient; do not copy the full diff stat, source/fallback path inventory, profile sections, report-output sections, or lead self-review into the initial prompt.
73
73
 
74
- A `final-verification` prompt may contain exactly one optional `## Run-specific directive` section for a true run delta. It is limited to 40 nonblank lines; the nonblank prompt body excluding common/target anchors is limited to 96 lines. Larger shared material belongs in the instruction set and analysis packet. Before initial dispatch, the selected adapter validates each prompt and requires byte-identical normalized analysis bodies after removing only worker/model/role and worker-specific artifact-path metadata.
74
+ A `final-verification` prompt may contain exactly one optional `## Run-specific directive` section for a true run delta. It is limited to 40 nonblank lines; the nonblank prompt body excluding common/target anchors is limited to 96 lines. Larger shared material belongs in the instruction set and analysis packet. Before initial dispatch, the selected adapter removes worker-specific header lines (including optional `**Pane role:**`) and requires the remaining normalized analysis bodies to be byte-identical; the semantic `Primary analysis packet` input remains in that comparison.
75
75
 
76
76
  The Phase 7 run validator enforces the same cross-task rule against the persisted prompt paths recorded by the run manifest, dispatch records, and team-state. Both dispatch adapters and `validators/validate-run.py` call `okstra_ctl.worker_prompt_contract.validate_initial_prompt_records()`. The validator resolves each record through `PromptPlan`, applies audience-specific anchors, compares every equality group with at least two prompts, validates report-writer common anchors without adding it to an analysis group, and ignores `-reverify-r<N>-` prompts because they belong to the lightweight convergence protocol. A dispatch-time bypass therefore remains a run-level contract violation.
77
77
 
@@ -123,14 +123,16 @@ Terminal statuses that can be recorded for a worker:
123
123
 
124
124
  Between wakes, `okstra worker-liveness` is the **only** sanctioned way to ask whether a pending worker is still alive. Do NOT hand-roll a polling script, an `ls` / `stat` loop, or any ad-hoc file-existence check: a lead that writes its own probe owns that probe's bugs, and those bugs surface as *worker* failures — a shell quoting slip silently turns the probe into a no-op that reports health it never measured.
125
125
 
126
- Each probe matches exactly one dispatch backend, and passing a worker to the wrong flag returns a confident wrong answer:
126
+ Each probe matches exactly one dispatch backend. The dispatch record's `livenessMode` is authoritative: `audit-heartbeat` registers the in-process audit sidecar, while `wrapper-status` registers the CLI wrapper status sidecar. Do not infer either transport from a worker filename or provider name.
127
127
 
128
128
  | Worker | Backend | Flag | What it reads |
129
129
  |---|---|---|---|
130
- | `claude-worker` | in-process subagent | `--audit` | its audit sidecar's newest `- PROGRESS:` heartbeat |
130
+ | any in-process worker (including `claude-worker` and `report-writer-worker`) | in-process dispatch | `--audit` | its registered audit sidecar's newest `- PROGRESS:` heartbeat |
131
131
  | `codex-worker` / `antigravity-worker` | CLI wrapper | `--prompt` | `<prompt>.log` / `<prompt>.status.json` |
132
132
 
133
- The mismatch is not intermittent, it is guaranteed: only the `okstra-*-exec.sh` wrappers ever write `<prompt>.log` / `<prompt>.status.json`, so an in-process worker produces neither by construction. Probing a `claude-worker` with `--prompt` therefore reports `did-not-launch` for a worker that is running normally, every time, as soon as the 60s launch grace passes. Probe in-process workers with `--audit`.
133
+ The mismatch is not intermittent, it is guaranteed: only the `okstra-*-exec.sh` wrappers ever write `<prompt>.log` / `<prompt>.status.json`, so an in-process worker produces neither by construction. Probing an in-process worker with `--prompt` therefore reports `did-not-launch` for a worker that is running normally, every time, as soon as the 60s launch grace passes. Probe in-process workers with `--audit`.
134
+
135
+ This is a transport-adapter liveness choice only. It does not change the reducer's worker identity or its verification responsibility: both remain bound to the registered worker instance and canonical artifacts.
134
136
 
135
137
  Branch on the exit code, not the JSON: `0` = every probe healthy, `1` = at least one `stalled` / `did-not-launch`. The probe reports; it never kills or re-dispatches. Acting on an unhealthy verdict means spending the existing one-retry budget below.
136
138
 
@@ -144,7 +146,7 @@ After each worker subagent returns (regardless of role), Lead MUST verify the ca
144
146
  - The result file is absent at the resolved absolute path even though the worker returned without a `*_RESULT_MISSING` sentinel — for example, claude-worker returned its final assistant message but never persisted the artifact, or the wrapper exited 0 and the codex/antigravity sub-agent forwarded raw stdout despite the contract.
145
147
  - The result file exists but cannot be parsed (frontmatter unreadable, sections 1–5 entirely missing). A truncated file in the middle of section 5 is NOT covered here — it goes to the validator's regular `error` path, not the retry path.
146
148
  - `okstra worker-liveness --prompt` reports a **CLI-wrapper** worker (`codex` / `antigravity`) `did-not-launch` — neither `<prompt-path>.log` nor `<prompt-path>.status.json` exists past the launch grace (default 60s). The wrapper writes its status sidecar before invoking the CLI and hard-fails loudly with a distinct exit code on every argument check before that, so the absence of BOTH artifacts means the dispatch itself never reached the script. Without this trigger the only evidence was a lead noticing two missing files by eye, and the run paid the full polling cap for a worker that never started.
147
- - `okstra worker-liveness --audit` reports an **in-process** worker (`claude-worker`) `stalled` — its audit sidecar's newest `- PROGRESS:` heartbeat is older than the cadence budget, or the sidecar carries no heartbeat at all. This is the in-process equivalent of the CLI wrappers' idle watchdog: the wrapper reaps a silent CLI itself, but nothing reaped a silent `claude-worker` until its deadline.
149
+ - `okstra worker-liveness --audit` reports an **in-process** worker `stalled` — its registered audit sidecar's newest `- PROGRESS:` heartbeat is older than the cadence budget, or the sidecar carries no heartbeat at all. This is the in-process equivalent of the CLI wrappers' idle watchdog: the wrapper reaps a silent CLI itself, but nothing reaped a silent in-process worker until its deadline.
148
150
  - The result file exists but its audit sidecar does not, at `runs/<task-type>/worker-results/<worker>-audit-<task-type>-<seq>.md`. Workers write both in the same step, so a result without a sidecar means the Reading Confirmation block — the only evidence the worker read its inputs — was never produced. `validate-run.py` fails the run on this at Phase 7 either way (`validate_worker_results_audit`); checking it here spends the existing one-retry budget while the role can still be re-dispatched, instead of surfacing hours later when the worker session is gone.
149
151
 
150
152
  **One-retry policy:**
@@ -128,12 +128,20 @@
128
128
  - **The reach of this gate — do not over-trust it.** What is mechanically enforced is the *form* of each source, that a `brief:` heading literally exists, that a `derived:` chain terminates without cycling, and that no stage is uncited. What is **not** enforced is whether the cited source genuinely demands the requirement. Every brief carries the same generic headings (`## Acceptance Criteria`, `## Context`, …), so attaching invented work to a real heading still parses clean. The gate's value is that it forces every item to name an origin and makes fabrication explicit and auditable — judging whether that origin actually demands the item remains a reviewer / `DISAGREE(f)` responsibility, and passing this gate is never evidence that the scope is justified.
129
129
  - **Stage citation format:** the reverse check reads each `Covered by` cell as prose, so a stage counts as cited only when its number is anchored to a `Stage` / `Stages` word on the same line. These all read: `Stage 2`; `Stage 1, Stage 2, Stage 3`; `Stages 1, 2, 3`; `Stages 1, 2, and 3`; ranges (`Stages 1-3`, `Stages 1 to 3`, `Stages 1 through 3`); and `and` / `&` conjunctions. A bare number with no `stage` word anchoring it is NOT read as a citation, so `covered by the recommended option, step 4` cites nothing. **Enforced:** `validators/validate-run.py` `_validate_stage_has_requirement` fails the plan when any Stage Map stage is cited by no coverage row.
130
130
  - Because that reader only sees prose, it cannot tell a citation from a mention: `Stage 1 (superseded by Stage 2)` still counts Stage 1 as cited, and one row citing `Stages 1-64` rubber-stamps every stage in the map. Cite the stages a requirement is actually satisfied by — not stages merely mentioned, and never a blanket range standing in for the work of checking.
131
- - **Requirement Coverage (mandatory, §5.5.8):** one row per concrete requirement from the task brief / packet. Assign stable IDs `R-001`, `R-002`, ... in source order. Columns: `ID | Source | Requirement | Covered by option / stage / step | Status`. `Source` follows the three-form grammar defined in the **Scope provenance** rule above — a free-form `file:line` is not one of the three forms and is rejected. When this run's brief is a fan-out packet, the task manifest's `taskBriefPath` points at that packet file, so `brief:` cites the packet's own headings (`## Scope`, `## Evidence`, `## Requirement Provenance`) — not the headings of the upstream user brief the packet came from. `Covered by` must name the specific Option Candidate and Stage/Step that satisfies it, not just "recommended option". **Enforced:** `validators/validate-run.py` `_validate_requirement_coverage_covered_by` fails a `covered` row whose `coveredBy` is bare "recommended option", names no Option/Stage/Step anchor, or cites a Stage number absent from the Stage Map (whether the cited step *actually satisfies* the requirement remains a worker `DISAGREE(f)` judgment). `Status` is one of `covered`, `gap`, or `blocked C-NNN`. If any row is `gap` or `blocked C-NNN`, the Plan Body Verification gate MUST NOT be `passed` / `passed-with-dissent`; add a matching `Blocks=approval` row for the blocker and keep `approved: false`.
131
+ - **Requirement Coverage (mandatory, §5.5.8):** one row per concrete requirement from the task brief / packet. Assign stable IDs `R-001`, `R-002`, ... in source order. Columns: `ID | Source | Requirement | Covered by option / stage / step | Status`. `Source` follows the three-form grammar defined in the **Scope provenance** rule above — a free-form `file:line` is not one of the three forms and is rejected. When this run's brief is a fan-out packet, the task manifest's `taskBriefPath` points at that packet file, so `brief:` cites the packet's own headings (`## Scope`, `## Evidence`, `## Requirement Provenance`) — not the headings of the upstream user brief the packet came from. For `covered`, `Covered by` must name the specific Option Candidate and Stage/Step that satisfies it, not just "recommended option". **Enforced:** `validators/validate-run.py` `_validate_requirement_coverage_covered_by` fails a `covered` row whose `coveredBy` is bare "recommended option", names no Option/Stage/Step anchor, or cites a Stage number absent from the Stage Map (whether the cited step *actually satisfies* the requirement remains a worker `DISAGREE(f)` judgment). `Status` is one of `covered`, `gap`, `blocked C-NNN`, or `documented-deviation`. A deviation records the concrete alternative in `coveredBy`, non-empty unique `decisionRefs` (`C-NNN` clarification IDs and/or `D-NNNN` decision-draft numbers), and `approvalDisposition: accepted|blocked C-NNN`. `accepted` is valid only when a referenced clarification is user-confirmed (`answered|resolved` with non-empty `userInput`); `blocked C-NNN` is valid only when that same-report clarification is `open` and `Blocks=approval`. **Enforced:** schema `$defs.ImplementationRequirementCoverageRow` plus `validators/validate-run.py` `_validate_requirement_deviations`; the exact `P-Req-*` queue still comes from `scripts/okstra_ctl/plan_items.py`. If any row is `gap`, plain `blocked C-NNN`, or a deviation whose approval disposition is blocked, the Plan Body Verification gate MUST NOT be `passed` / `passed-with-dissent`; add a matching `Blocks=approval` row for the blocker and keep `approved: false`.
132
132
  - **Review-rule compliance plan:** when a project-local review rule pack is found, each Option Candidate MUST include the design implication of those rules in its File Structure / interfaces / blast-radius notes. For any helper or data transform used by more than one changed service, the plan must either place it in a shared module or explicitly justify why duplication is intentional. For any test step, the plan must state the observable behavior being asserted, not the internal collaborator call being pinned. For any exported/public method added or renamed, the step must carry the intended noun/side-effect semantics so implementation names can be reviewed before code is written.
133
133
  - the YAML frontmatter MUST include the line `approved: false` (report-writer always emits the unflipped value). The user authorises the next `implementation` run by flipping it to `approved: true` (manual edit or `--approve` CLI). Do NOT recreate any `User Approval Request` body block — the validator fails reports that contain one (see `validators/validate-run.py` deprecated patterns).
134
134
  - the YAML frontmatter MUST include the line `implementation-option:` directly under `approved:` (report-writer always emits it with an **empty value**). The user selects which Option Candidate the next `implementation` run executes by filling this line with that option's name (manual edit or `--implementation-option <name>` CLI). When left empty, the `implementation` run falls back to the `Recommended Option`.
135
135
  - **the frontmatter `approved: false` line is rendered unconditionally; if the plan-body verification gate (§5.5.9) returns `blocked-by-disagreement` or `aborted-non-result`, the writer MUST keep `approved: false` and the validator refuses any report that ships with `approved: true` under such a gate result.**
136
136
  - every ambiguity flagged during pre-planning that the user must resolve before approval registered as a `Blocks=approval` row in the `## 1. Clarification Items` table (the unified table is the single home for these — the "no separate `Open Questions` block" rule is in the shared `_common-contract.md` clarification policy)
137
+ - **Exact plan-item queue (BLOCKING).** Run `okstra plan-items extract --data <data.json> --output <state>/plan-items-....json`, place the persisted `items[]` verbatim in the verifier prompt, then run `okstra plan-items validate --data <data.json> --items <state>/plan-items-....json`. Do not freely summarise, select, omit, reorder, or renumber the queue. Prompt headings use the compact `subject` and include the lossless `payload`. For every item, ask:
138
+
139
+ ```text
140
+ What concrete false-positive input, failure ordering, or omitted dependency
141
+ would make this plan item incorrect even if its happy path succeeds?
142
+ ```
143
+
144
+ An `AGREE` note records the counterexample considered and its exclusion reason. If the judgement needs unavailable external material, record `verification-error`, not `DISAGREE`. **Enforced:** `validators/validate-run.py` `_validate_plan_item_extraction_completeness` compares the exact deterministic set, independently rejecting missing, unexpected, and duplicate plan-item IDs, including `P-Prep-*`.
137
145
  - **§5.5.9 Plan Body Verification (BLOCKING).** After report-writer finishes the draft, the lead MUST run a worker peer-review round on the consolidated plan body (Option Candidates / Trade-off Matrix / Recommended Option / Stage Map and per-stage sections / Dependency / Validation Checklist / Rollback / Requirement Coverage) and populate `### 5.5.9 Plan Body Verification` in the final report. The round protocol, plan-item ID scheme (`P-Opt-*` / `P-Step-*` / `P-Dep-*` / `P-Val-*` / `P-Rb-*`), verdict semantics, gate-result classification, and dissent log format are defined in `prompts/lead/plan-body-verification.md`. The four gate-result values are `passed`, `passed-with-dissent`, `blocked-by-disagreement`, `aborted-non-result`. When the gate would have been `blocked-by-disagreement` or `aborted-non-result`, the lead MUST NOT silently flip it to one of the passing values to "unblock" the run — that is a contract violation. **Enforced:** `validators/validate-run.py` `_validate_plan_body_gate_recompute` re-derives the gate from `planItems[].verdicts` and fails when the declared `gateResult` claims a healthier outcome than the recorded votes support; `_validate_plan_item_extraction_completeness` fails when any plan-body deliverable category is under-extracted into `planItems`, so a dropped item can no longer dodge the gate. When `convergence.adversarial=true` (the default for this phase), this round uses the adversarial posture — verifiers confirm cited paths/commands and the burden of proof is on the plan — but the gate threshold stays `majority-disagree` (see that skill's §"Adversarial plan-body posture"). Among the majority-disagree items, those that are majority-`planner-fixable` go through one report-writer self-fix pass before being promoted to a user clarification (`prompts/lead/plan-body-verification.md` "Self-fix round"). Only items not resolved by self-fix, or that are majority-`needs-user-input`, become `Blocks=approval` clarifications. `validators/validate-run.py` `_validate_self_fix_before_clarification` fails a planner-fixable majority item promoted without a self-fix as `contract-violated`.
138
146
  - **Decision-record evaluation (sole owner)**: this phase is the **single owner** of decision-record evaluation in the okstra lifecycle. The brief never evaluates or drafts decision records — it only forwards `adr-candidate:*` signals. Every `adr-candidate:*` entry inherited from the brief's `Open Questions` is a mandatory evaluation target. In addition, evaluate every decision the recommended option introduces against the three criteria:
139
147
  1. **Hard to reverse** — would changing the decision later cost meaningfully more than deciding now?
@@ -148,7 +156,7 @@
148
156
  - references to types, functions, flags, or files that no other step or option defines
149
157
  - steps that describe *what* to do without showing *how* (commands, code, or exact diffs are required for any code-touching step)
150
158
  - Self-review pass before finalising the report (`Claude lead` runs this; do not delegate to a generic subagent):
151
- 1. **Spec coverage** — for every requirement in the task brief, point to the option(s) and step(s) that satisfy it in `### 5.5.8 Requirement Coverage`. Every row must name the Option Candidate and Stage/Step. List gaps explicitly as `gap` or `blocked C-NNN`; a publishable gate with a non-`covered` row is a validator failure.
159
+ 1. **Spec coverage** — for every requirement in the task brief, point to the option(s) and step(s) that satisfy it in `### 5.5.8 Requirement Coverage`. Every `covered` row must name the Option Candidate and Stage/Step; every deviation must name its concrete alternative. List gaps explicitly as `gap` or `blocked C-NNN`. A deliberate alternative is `documented-deviation` only when its decision references resolve and its approval disposition is user-confirmed or names an open approval blocker; a publishable gate permits only `covered` or an accepted deviation.
152
160
  2. **Placeholder scan** — search the report for the patterns in the No-placeholder rule above and fix inline.
153
161
  3. **Internal consistency** — option file lists, trade-off matrix, and recommended step list must agree on file paths, names, and signatures. A symbol called `clearLayers()` in the matrix and `clearFullLayers()` in the steps is a bug.
154
162
  4. **Ambiguity check** — any requirement that could be read two ways must be made explicit or moved to the `## 1. Clarification Items` table as a `Blocks=approval` row.
@@ -353,6 +353,7 @@ def _worker_dispatch_record(
353
353
  "promptPath": str(worker.prompt_path),
354
354
  "resultPath": str(worker.result_path),
355
355
  "workerResultPath": str(worker.worker_result_path),
356
+ "livenessMode": "wrapper-status",
356
357
  "statusSidecarPath": str(status_path_for_prompt(worker.prompt_path)),
357
358
  "degradedFrom": "",
358
359
  "reason": reason,
@@ -532,6 +533,7 @@ def _render_missing_report_writer_prompt(
532
533
  worker_id=REPORT_WRITER_WORKER_ID,
533
534
  prompt_rel=_worker_prompt_path(manifest, REPORT_WRITER_WORKER_ID),
534
535
  result_rel=result_rel,
536
+ audit_source_rel=worker_result_rel,
535
537
  )
536
538
  lines.extend([
537
539
  f"**Worker Result Path:** {worker_result_rel}",
@@ -664,7 +666,7 @@ def _report_writer_prompt_body(
664
666
  "## Output Contract",
665
667
  "You are the author of TWO files:",
666
668
  "- The final-report data.json at Result Path.",
667
- "- The worker-results audit file at Worker Result Path.",
669
+ "- The worker-results audit file at Audit sidecar path.",
668
670
  (
669
671
  'After writing the data.json, invoke "okstra render-final-report '
670
672
  '<Result Path>" so the markdown sibling is rendered before you return.'
@@ -682,12 +684,14 @@ def _base_prompt_headers(
682
684
  worker_id: str,
683
685
  prompt_rel: str,
684
686
  result_rel: str,
687
+ audit_source_rel: str | None = None,
685
688
  ) -> list[str]:
686
689
  try:
687
690
  return worker_prompt_headers(
688
691
  project_root=project_root,
689
692
  prompt_rel=prompt_rel,
690
693
  result_rel=result_rel,
694
+ audit_source_rel=audit_source_rel,
691
695
  worker_id=worker_id,
692
696
  dispatch_kind="initial",
693
697
  manifest=manifest,
@@ -9,6 +9,7 @@ from typing import Any
9
9
 
10
10
  from .convergence_engine import (
11
11
  ConvergenceContractError,
12
+ apply_critic_gap_results,
12
13
  apply_round_results,
13
14
  finalize_working_state,
14
15
  plan_next_round,
@@ -29,13 +30,107 @@ from .convergence_store import (
29
30
  )
30
31
 
31
32
 
33
+ _EVIDENCE_ARTIFACT = {
34
+ "path": ".okstra/tasks/example/group/task/runs/error-analysis/evidence/F-001/mysql-query.txt",
35
+ "sha256": "0123456789abcdef" * 4,
36
+ "command": "mysql --batch --raw < query.sql",
37
+ "environment": "staging read-only replica",
38
+ }
39
+
40
+ _EXAMPLES: dict[str, dict[str, Any]] = {
41
+ "groups": {
42
+ "schemaVersion": "1.0",
43
+ "taskKey": "example/group/task",
44
+ "config": {
45
+ "enabled": True,
46
+ "adversarial": False,
47
+ "maxRounds": 2,
48
+ "effectiveMaxRounds": 2,
49
+ "verificationMode": "lightweight",
50
+ },
51
+ "workers": [
52
+ {"workerId": "claude-worker", "audience": "analysis"},
53
+ {"workerId": "codex-worker", "audience": "analysis"},
54
+ {"workerId": "report-writer", "audience": "report-writer"},
55
+ ],
56
+ "groups": [
57
+ {
58
+ "findingId": "F-001",
59
+ "summary": "A live row contradicts the migration assumption",
60
+ "category": "risk",
61
+ "ticketIds": ["TASK-001"],
62
+ "originWorker": "claude-worker",
63
+ "originEvidence": "The live row contradicts the migration assumption",
64
+ "evidenceArtifacts": [_EVIDENCE_ARTIFACT],
65
+ "discoveredBy": {
66
+ "claude-worker": {
67
+ "itemId": "F-1",
68
+ "evidence": "staging query result",
69
+ }
70
+ },
71
+ "sourceItems": [{"worker": "claude-worker", "itemId": "F-1"}],
72
+ }
73
+ ],
74
+ },
75
+ "round-results": {
76
+ "schemaVersion": "1.0",
77
+ "round": 1,
78
+ "dispatches": [
79
+ {"worker": "codex-worker", "status": "completed", "durationMs": 1250}
80
+ ],
81
+ "votesByFinding": {
82
+ "F-001": {
83
+ "codex-worker": {
84
+ "verdict": "unverifiable",
85
+ "disagreeBasis": None,
86
+ "explanation": "staging credentials were unavailable",
87
+ }
88
+ }
89
+ },
90
+ },
91
+ "critic-results": {
92
+ "schemaVersion": "1.0",
93
+ "taskKey": "example/group/task",
94
+ "mode": "coverage",
95
+ "provider": "codex",
96
+ "terminalStatus": "completed",
97
+ "durationMs": 1750,
98
+ "candidates": [
99
+ {
100
+ "candidateId": "CG-001",
101
+ "summary": "The rollback path has no live-data verification",
102
+ "category": "missing",
103
+ "ticketIds": ["TASK-001"],
104
+ "originEvidence": "No rollback query result is present",
105
+ "evidenceArtifacts": [_EVIDENCE_ARTIFACT],
106
+ }
107
+ ],
108
+ },
109
+ }
110
+
111
+
32
112
  def _parser() -> argparse.ArgumentParser:
33
113
  parser = argparse.ArgumentParser(
34
114
  prog="okstra convergence",
35
115
  description="Advance and validate deterministic re-verification state.",
116
+ epilog=(
117
+ "Working lifecycle: groups v1.0 → work v1.0 → "
118
+ "round-plan/results v1.0 → final v1.3\n"
119
+ "historical final readers: v1.0, v1.1, v1.2"
120
+ ),
121
+ formatter_class=argparse.RawDescriptionHelpFormatter,
36
122
  )
37
123
  subparsers = parser.add_subparsers(dest="operation", required=True)
38
124
 
125
+ example = subparsers.add_parser(
126
+ "example", help="print a deterministic input artifact example"
127
+ )
128
+ example.add_argument(
129
+ "--kind",
130
+ choices=("groups", "round-results", "critic-results"),
131
+ required=True,
132
+ )
133
+
39
134
  seed = subparsers.add_parser("seed", help="create, resume, or recover working state")
40
135
  seed.add_argument("--groups", type=Path, required=True)
41
136
  seed.add_argument("--work-state", type=Path, required=True)
@@ -52,6 +147,13 @@ def _parser() -> argparse.ArgumentParser:
52
147
  apply.add_argument("--plan", type=Path, required=True)
53
148
  apply.add_argument("--results", type=Path, required=True)
54
149
 
150
+ apply_critic = subparsers.add_parser(
151
+ "apply-critic-gaps",
152
+ help="apply one coverage-critic verification batch",
153
+ )
154
+ apply_critic.add_argument("--work-state", type=Path, required=True)
155
+ apply_critic.add_argument("--results", type=Path, required=True)
156
+
55
157
  finalize = subparsers.add_parser("finalize", help="write the public final state")
56
158
  finalize.add_argument("--work-state", type=Path, required=True)
57
159
  finalize.add_argument("--output", type=Path, required=True)
@@ -173,6 +275,14 @@ def _apply_round(args: argparse.Namespace) -> tuple[str, Path]:
173
275
  return "applied", args.work_state
174
276
 
175
277
 
278
+ def _apply_critic_gaps(args: argparse.Namespace) -> tuple[str, Path]:
279
+ state = load_json_object(args.work_state)
280
+ results = load_json_object(args.results)
281
+ updated = apply_critic_gap_results(state, results)
282
+ write_json_atomic(args.work_state, updated)
283
+ return "applied", args.work_state
284
+
285
+
176
286
  def _finalize(args: argparse.Namespace) -> tuple[str, Path]:
177
287
  state = load_json_object(args.work_state)
178
288
  final = finalize_working_state(state)
@@ -198,6 +308,7 @@ def _execute(args: argparse.Namespace) -> tuple[str, Path]:
198
308
  "seed": _seed,
199
309
  "plan-round": _plan_round,
200
310
  "apply-round": _apply_round,
311
+ "apply-critic-gaps": _apply_critic_gaps,
201
312
  "finalize": _finalize,
202
313
  "validate": _validate,
203
314
  }
@@ -206,6 +317,16 @@ def _execute(args: argparse.Namespace) -> tuple[str, Path]:
206
317
 
207
318
  def main(argv: list[str] | None = None) -> int:
208
319
  args = _parser().parse_args(argv)
320
+ if args.operation == "example":
321
+ print(
322
+ json.dumps(
323
+ _EXAMPLES[args.kind],
324
+ ensure_ascii=False,
325
+ sort_keys=True,
326
+ indent=2,
327
+ )
328
+ )
329
+ return 0
209
330
  try:
210
331
  action, path = _execute(args)
211
332
  except (ConvergenceContractError, json.JSONDecodeError, ValueError) as exc:
@@ -220,4 +341,3 @@ def main(argv: list[str] | None = None) -> int:
220
341
 
221
342
  if __name__ == "__main__":
222
343
  raise SystemExit(main())
223
-