okstra 0.178.0 → 0.179.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 (110) hide show
  1. package/README.md +2 -2
  2. package/dist/commands/execute/plan-verify.mjs +1 -1
  3. package/dist/commands/execute/worktree-status.mjs +8 -2
  4. package/dist/commands/execute/worktree-status.mjs.map +1 -1
  5. package/dist/commands/lifecycle/install.mjs +1 -1
  6. package/dist/commands/lifecycle/install.mjs.map +1 -1
  7. package/dist/commands/report/render-final-report.mjs +3 -3
  8. package/docs/architecture/storage-model.md +3 -3
  9. package/docs/architecture.md +10 -9
  10. package/docs/cli.md +11 -13
  11. package/docs/for-ai/skills/okstra-inspect.md +3 -3
  12. package/docs/for-ai/skills/okstra-schedule-gen.md +2 -2
  13. package/docs/for-ai/skills/okstra-user-response.md +2 -2
  14. package/docs/project-structure-overview.md +10 -11
  15. package/docs/task-process/implementation-planning.md +1 -1
  16. package/docs/task-process/implementation.md +1 -1
  17. package/package.json +1 -1
  18. package/runtime/BUILD.json +2 -2
  19. package/runtime/agents/workers/report-writer-worker.md +11 -12
  20. package/runtime/bin/lib/okstra/globals.sh +2 -2
  21. package/runtime/bin/lib/okstra/interactive.sh +1 -1
  22. package/runtime/bin/lib/okstra/usage.sh +11 -9
  23. package/runtime/bin/lib/okstra-ctl/cmd-rerun.sh +1 -1
  24. package/runtime/bin/okstra-central.sh +2 -2
  25. package/runtime/bin/okstra-render-final-report.py +1 -1
  26. package/runtime/bin/okstra-token-usage.py +1 -1
  27. package/runtime/prompts/launch.template.md +1 -1
  28. package/runtime/prompts/lead/adapters/cmux.md +6 -1
  29. package/runtime/prompts/lead/context-loader.md +3 -2
  30. package/runtime/prompts/lead/convergence.md +1 -1
  31. package/runtime/prompts/lead/okstra-lead-contract.md +4 -4
  32. package/runtime/prompts/lead/plan-body-verification.md +3 -3
  33. package/runtime/prompts/lead/report-writer.md +21 -20
  34. package/runtime/prompts/lead/team-contract.md +1 -1
  35. package/runtime/prompts/profiles/_common-contract.md +5 -4
  36. package/runtime/prompts/profiles/_implementation-deliverable.md +1 -0
  37. package/runtime/prompts/profiles/_implementation-executor.md +2 -1
  38. package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
  39. package/runtime/prompts/profiles/implementation-planning.md +9 -5
  40. package/runtime/prompts/profiles/implementation.md +4 -4
  41. package/runtime/prompts/profiles/improvement-discovery.md +2 -2
  42. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +5 -4
  43. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +5 -4
  44. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +2 -2
  45. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +71 -9
  46. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +5 -4
  47. package/runtime/python/okstra_ctl/agent_prompt_cli.py +55 -3
  48. package/runtime/python/okstra_ctl/analysis_inputs.py +5 -3
  49. package/runtime/python/okstra_ctl/analysis_packet.py +21 -0
  50. package/runtime/python/okstra_ctl/backfill.py +12 -5
  51. package/runtime/python/okstra_ctl/consumers.py +70 -3
  52. package/runtime/python/okstra_ctl/convergence_engine.py +43 -17
  53. package/runtime/python/okstra_ctl/dispatch_core.py +47 -11
  54. package/runtime/python/okstra_ctl/dispatch_state.py +20 -19
  55. package/runtime/python/okstra_ctl/domain/worker_exec.py +13 -34
  56. package/runtime/python/okstra_ctl/domain/worker_presentation.py +128 -0
  57. package/runtime/python/okstra_ctl/execution_mutation_audit.py +5 -0
  58. package/runtime/python/okstra_ctl/final_report_paths.py +77 -1
  59. package/runtime/python/okstra_ctl/handoff.py +1 -2
  60. package/runtime/python/okstra_ctl/implementation_outcome.py +1 -1
  61. package/runtime/python/okstra_ctl/index.py +4 -4
  62. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +26 -12
  63. package/runtime/python/okstra_ctl/listing.py +4 -2
  64. package/runtime/python/okstra_ctl/manager_launch.py +1 -1
  65. package/runtime/python/okstra_ctl/manager_sync.py +1 -1
  66. package/runtime/python/okstra_ctl/path_hints.py +2 -2
  67. package/runtime/python/okstra_ctl/paths.py +13 -10
  68. package/runtime/python/okstra_ctl/plan_run_root.py +9 -5
  69. package/runtime/python/okstra_ctl/recap.py +3 -2
  70. package/runtime/python/okstra_ctl/reconcile.py +3 -1
  71. package/runtime/python/okstra_ctl/render.py +22 -22
  72. package/runtime/python/okstra_ctl/report_finalize.py +4 -4
  73. package/runtime/python/okstra_ctl/rollup.py +1 -1
  74. package/runtime/python/okstra_ctl/run.py +139 -284
  75. package/runtime/python/okstra_ctl/run_audit.py +5 -5
  76. package/runtime/python/okstra_ctl/run_index_row.py +2 -2
  77. package/runtime/python/okstra_ctl/session_transcript.py +89 -0
  78. package/runtime/python/okstra_ctl/stage_ledger.py +72 -0
  79. package/runtime/python/okstra_ctl/stage_map.py +28 -29
  80. package/runtime/python/okstra_ctl/stage_targets.py +61 -0
  81. package/runtime/python/okstra_ctl/user_response.py +97 -12
  82. package/runtime/python/okstra_ctl/wizard.py +43 -65
  83. package/runtime/python/okstra_ctl/worker_prompt_body.py +6 -7
  84. package/runtime/python/okstra_ctl/worker_runner.py +76 -213
  85. package/runtime/python/okstra_ctl/workflow.py +1 -1
  86. package/runtime/python/okstra_ctl/wrapper_status.py +23 -0
  87. package/runtime/python/okstra_ctl/write_policy.py +45 -6
  88. package/runtime/python/okstra_project/state.py +2 -2
  89. package/runtime/python/okstra_token_usage/__init__.py +1 -1
  90. package/runtime/python/okstra_token_usage/cli.py +3 -3
  91. package/runtime/python/okstra_token_usage/report.py +7 -24
  92. package/runtime/schemas/convergence-groups-v1.0.schema.json +1 -1
  93. package/runtime/schemas/convergence-groups-v2.0.schema.json +1 -1
  94. package/runtime/schemas/final-report-v2.0.schema.json +11 -1
  95. package/runtime/skills/okstra-inspect/facets/history.md +3 -3
  96. package/runtime/skills/okstra-inspect/facets/recap.md +1 -1
  97. package/runtime/skills/okstra-inspect/facets/report.md +5 -5
  98. package/runtime/skills/okstra-inspect/facets/status.md +2 -2
  99. package/runtime/skills/okstra-pr-gen/SKILL.md +1 -1
  100. package/runtime/skills/okstra-run/SKILL.md +1 -1
  101. package/runtime/skills/okstra-schedule-gen/SKILL.md +1 -1
  102. package/runtime/skills/okstra-user-response/SKILL.md +3 -3
  103. package/runtime/templates/project-docs/task-index.template.md +1 -1
  104. package/runtime/templates/report-writer-prompt-preamble.md +1 -1
  105. package/runtime/validators/forbidden_actions.py +76 -5
  106. package/runtime/validators/lib/fixtures.sh +14 -10
  107. package/runtime/validators/lib/runners.sh +1 -1
  108. package/runtime/validators/validate-implementation-plan-stages.py +3 -0
  109. package/runtime/validators/validate-report-views.py +1 -1
  110. package/runtime/validators/validate-run.py +95 -37
@@ -194,7 +194,7 @@ Runtime/install asset changes follow this checklist:
194
194
  | `agent-activity` | `src/commands/report/agent-activity.mts` | Thin Node shim for `okstra_ctl.agent_activity`; `append` records one run-bound activity and `project` writes the validated event projection into final-report data |
195
195
  | `report-finalize` | `src/commands/report/finalize.mts` | Run the whole Phase 7 post-report sequence in contractual order (Python: `okstra_ctl.report_finalize`) — the single reference point shared with the Codex lead adapter |
196
196
  | `render-views` | `src/commands/report/render-views.mts` | Render schema v2 data with its task-specific human template, or use the quick-report compatibility view |
197
- | `render-final-report`, `inject-report-index` | `src/commands/report/*.mts` | Render version-selected AI handoff Markdown from data.json; v1 index injection remains compatibility-only |
197
+ | `render-final-report`, `inject-report-index` | `src/commands/report/*.mts` | Render the full reading copy Markdown from data.json on demand; v1 index injection remains compatibility-only |
198
198
  | `wizard` | `src/commands/execute/wizard.mts` | Drive the `okstra-run` interactive state machine, including the final outcome envelope |
199
199
  | `token-usage` | `src/commands/execute/token-usage.mts` | Wrap installed Python token usage CLI |
200
200
  | `spawn-followups`, `error-log` | `src/commands/execute/*.mts` | Follow-up task bundle creation and run error-log append helpers |
@@ -272,8 +272,8 @@ Important modules:
272
272
  | `qa_commands.py` | QA command deny-list validation for plans |
273
273
  | `conformance.py` | validates task-level Tier 3 manifests, parses `QA-RESULT`, detects diff capability surfaces, and reduces results to PASS/ADVISORY/BLOCKING; DB/HTTP/external non-PASS is user-owned advisory while local IO and contract defects remain blocking, enforced by `scripts/okstra_ctl/conformance.py::decide_conformance_gate` and `validators/validate-run.py::_validate_conformance`. Also the single definition of the plan's `Conformance tests:` declaration format (`parse_conformance_tests`, `malformed_conformance_stages`), read both at the approval boundary (`run.py::_validate_approved_plan`) and at the end of an implementation run (`validators/validate-run.py`) so the two cannot disagree |
274
274
  | `pr_template.py` | PR body template resolution for release-handoff |
275
- | `report_views.py`, `render_final_report.py`, `final_report_schema.py` | Final-report contract: schema v2 data independently produces AI handoff Markdown and human HTML |
276
- | `report_markdown.py` | Schema-ordered Markdown serialisation of a data.json subtree for the AI handoff report — headings, tables for uniform row sets, prose for narrative fields; field order read from the schema, not from the mapping |
275
+ | `report_views.py`, `render_final_report.py`, `final_report_schema.py` | Final-report contract: schema v2 data independently produces the full reading copy Markdown and human HTML |
276
+ | `report_markdown.py` | Schema-ordered Markdown serialisation of a data.json subtree for the full reading copy — headings, tables for uniform row sets, prose for narrative fields; field order read from the schema, not from the mapping |
277
277
  | `final_report_paths.py`, `report_view_artifacts.py` | Path-helper SSOT for the final-report markdown/data.json pair and the generated view artifacts (HTML view, user-responses directory) |
278
278
  | `wizard.py` | `okstra-run` prompt state machine; user-facing Korean strings live in `prompts/wizard/prompts.ko.json` |
279
279
  | `wizard_stage_intent.py` | stage-related intent projection of the `okstra-run` wizard output — normalizes whole-task (`__whole_task__`) vs single/multi stage selection into render-args (`resolve_wizard_stage_intent`) |
@@ -381,9 +381,9 @@ Token/cost accounting:
381
381
 
382
382
  | Path | Role |
383
383
  |---|---|
384
- | `templates/reports/final-report-v2.template.md` | AI handoff Markdown spine |
385
- | `templates/reports/final-report-v2.template.md` | Schema v2 AI handoff Markdown spine |
386
- | `templates/reports/md/tasks/*.template.md`, `md/macros/sections.md` | Eleven dedicated task bodies for the AI handoff Markdown, sibling of `html/tasks/`; shared section macro |
384
+ | `templates/reports/final-report-v2.template.md` | Full reading copy Markdown spine |
385
+ | `templates/reports/final-report-v2.template.md` | Schema v2 full reading copy Markdown spine |
386
+ | `templates/reports/md/tasks/*.template.md`, `md/macros/sections.md` | Eleven dedicated task bodies for the full reading copy Markdown, sibling of `html/tasks/`; shared section macro |
387
387
  | `templates/reports/html/base.template.html`, `html/tasks/*.template.html` | Shared HTML shell plus eleven dedicated task templates for human reports; task bodies are not shared |
388
388
  | `templates/reports/report.css`, `report.js` | Inline assets for self-contained HTML report views |
389
389
  | `templates/reports/*.template.md` | Inputs, schedule, user-response, settings templates |
@@ -397,7 +397,7 @@ Token/cost accounting:
397
397
 
398
398
  ### 4.8 `schemas/`
399
399
 
400
- `schemas/final-report-v2.0.schema.json` is the current final-report data.json contract. The report-writer worker writes `final-report-<task-type>-<seq>.data.json`; independent renderers produce AI handoff Markdown and task-specific human HTML.
400
+ `schemas/final-report-v2.0.schema.json` is the current final-report data.json contract. The report-writer worker writes `final-report-<task-type>-<seq>.data.json`; independent renderers produce the full reading copy Markdown and task-specific human HTML.
401
401
 
402
402
  The deterministic convergence inputs are `schemas/convergence-groups-v1.0.schema.json`, `schemas/convergence-round-results-v1.0.schema.json`, and `schemas/convergence-critic-results-v1.0.schema.json`. `tools/build.mjs` syncs the entire source `schemas/` directory to `runtime/schemas/`; these JSON Schema files are runtime contracts, not Markdown publication-inventory entries.
403
403
 
@@ -410,7 +410,7 @@ Optional (v1.0 backward-compatible) top-level keys:
410
410
 
411
411
  | File | Role |
412
412
  |---|---|
413
- | `validate-run.py` | Run/final-report validation: schema v2 AI handoff order + structured data rules |
413
+ | `validate-run.py` | Run/final-report validation: schema v2 record + structured data rules |
414
414
  | `validate-brief.py`, `validate-brief.sh` | Brief frontmatter/body contract validation |
415
415
  | `validate-report-views.py` | HTML view validation (form-control placement / no external URLs / stale source digest / Response ID parity) |
416
416
  | `validate_analysis_report.py` | Cross-field validation for the three read-only analysis reports: frozen target/evidence snapshots, current-code evidence, review-source identity, and exact affected-ID resolution coverage on revision reruns |
@@ -537,9 +537,8 @@ Current report pipeline:
537
537
  2. The lead writes semantic groups; the convergence engine persists working state, per-round plans/results, an optional critic transition, and then a validated `state/convergence-<task-type>-<seq>.json` terminal state: schema v1.3 when newly finalized, or an unchanged historical final schema v1.0, v1.1, or v1.2 returned by `reuse-final`.
538
538
  3. Report-writer worker writes `reports/final-report-<task-type>-<seq>.data.json` against the current schema v2 contract, including `humanSummary` and one task-type deliverable.
539
539
  4. For implementation-planning, `okstra plan-items extract` creates the complete `P-*` queue, `validate` proves it still matches data.json, and the analyser instances run the separate plan-body verification round.
540
- 5. `scripts/okstra-render-final-report.py` renders compact AI handoff Markdown with `templates/reports/final-report-v2.template.md`.
541
- 6. Token usage substitution fills usage/cost cells.
542
- 7. `scripts/okstra-render-report-views.py` independently selects one of eleven dedicated task templates and emits human-facing HTML directly from the same data.json; run validation checks both derived artifacts. A quick Markdown input retains its legacy conditional path.
540
+ 5. Token usage substitution fills usage/cost cells in the report record. The full reading copy is rendered on demand with `okstra render-final-report` from `templates/reports/final-report-v2.template.md`.
541
+ 6. `scripts/okstra-render-report-views.py` independently selects one of eleven dedicated task templates and emits human-facing HTML directly from the same data.json; run validation checks the record and the human HTML. A quick Markdown input retains its legacy conditional path.
543
542
 
544
543
  For the three analysis sidetracks, the HTML view also exports an immutable-source `## ANALYSIS REVIEW` sidecar. A revision rerun carries that sidecar, reanalyzes the whole confirmed scope, and records one `analysisReviewResolution` row for every affected ID before `validate_analysis_report.py` accepts the result.
545
544
 
@@ -152,7 +152,7 @@ The detailed selected-direction plan retains these deliverable surfaces:
152
152
  - `Requirement Coverage`
153
153
  - `Implementation Design Preparation`
154
154
 
155
- Approval is recorded with `approved: true` in YAML frontmatter. A selected-direction plan has no `implementation-option:` field and rejects `--implementation-option` before any approval-file mutation. If a `Blocks=approval` clarification row is unresolved, implementation prepare rejects the plan even when frontmatter is approved. Existing candidate plans keep their legacy option field and execution behavior.
155
+ Approval is recorded as `frontmatter.approved: true` on the report record (`--approve` or the in-session wizard). A selected-direction plan has no `implementationOption` field and rejects `--implementation-option` before any approval-file mutation. If a `Blocks=approval` clarification row is unresolved, implementation prepare rejects the plan even when the record is approved. Existing candidate plans keep their legacy option field and execution behavior.
156
156
 
157
157
  `plan-ready` requires 100% requirement coverage, 100% scope precision, and no unmapped stage or file change. If the selected mechanism or boundary cannot meet those conditions, planning emits `direction-invalidated` without an executable Stage Map and routes back to `implementation-option-selection`. It does not choose another direction automatically.
158
158
 
@@ -98,7 +98,7 @@ sequenceDiagram
98
98
  P-->>W: prepared implementation prompt or PrepareError
99
99
  ```
100
100
 
101
- `--approve` exists in the Python runtime, but the okstra-run wizard does not emit it as args. On the shell path, `--approve` flips the unchecked approval line, appends an audit line, and then follows the same validation path.
101
+ `--approve` exists in the Python runtime, but the okstra-run wizard does not emit it as args. On the shell path, `--approve` sets the report record `frontmatter.approved` to `true` and then follows the same validation path.
102
102
 
103
103
  ### 3.1 design-preparation preflight
104
104
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.178.0",
3
+ "version": "0.179.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.178.0",
3
- "builtAt": "2026-08-18T18:51:00.094Z",
2
+ "package": "0.179.0",
3
+ "builtAt": "2026-08-20T05:34:55.598Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -37,21 +37,20 @@ The final-report `executionRoles[]` set must equal the execution-manifest
37
37
  ## Host file procedure
38
38
 
39
39
  Use the assigned `runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json`
40
- path and the renderer commands supplied by the final prompt.
40
+ path. Do not invoke the full reading copy renderer.
41
41
 
42
- The data.json is the **single source of truth** for two audiences. The renderer (`scripts/okstra-render-final-report.py`) produces the AI handoff Markdown (`final-report-<task-type>-<seq>.md`) deterministically from it. Phase 7 produces the human HTML (`final-report-<task-type>-<seq>.html`) through the task-specific HTML renderer. HTML is rendered directly from the data.json; it is not a presentation of the Markdown. You do NOT hand-write either derived artifact. Both are regenerated whenever the data.json changes.
42
+ The data.json is the **single source of truth** for two audiences. Phase 7 produces the human HTML (`final-report-<task-type>-<seq>.html`) through the task-specific HTML renderer. The full reading copy is rendered on demand with `okstra render-final-report <data.json>`. HTML is rendered directly from the data.json; it is not a presentation of the Markdown. You do NOT hand-write either derived artifact.
43
43
 
44
- If you find yourself thinking "I'll just write the markdown directly" — stop. Write the data.json with your `Write` tool and let the renderer produce the markdown.
44
+ If you find yourself thinking "I'll just write the markdown directly" — stop. Write the data.json with your `Write` tool. The full reading copy is rendered on demand.
45
45
 
46
46
  ## Worker Result File (MANDATORY)
47
47
 
48
48
  Write the required worker-result record at the lead-registered `**Worker Result Path:**`. Both dispatch adapters include it in `WorkerJob.completion_paths` and refuse `completed` while it is absent. Schema: short YAML frontmatter (`workerId: "report-writer"`, plus the canonical fields copied verbatim from `analysis-material.md` per `team-contract`) followed by:
49
49
 
50
50
  1. The canonical data.json path you wrote (project-relative).
51
- 2. The rendered markdown path produced by the renderer (project-relative).
52
- 3. The convergence-state input path from the prompt (project-relative).
51
+ 2. The convergence-state input path from the prompt (project-relative).
53
52
 
54
- Keep the data.json contents and analysis-worker result list out of this file: the data.json is the canonical artifact and the analysis results remain prompt inputs. This file is the dispatch-required three-path pointer record.
53
+ Keep the data.json contents and analysis-worker result list out of this file: the data.json is the canonical artifact and the analysis results remain prompt inputs. This file is the dispatch-required two-path pointer record.
55
54
 
56
55
  ## Heartbeat (BLOCKING)
57
56
 
@@ -88,13 +87,13 @@ Before writing the data.json, you MUST:
88
87
  For the report writer specifically, the `## Inputs` list always includes:
89
88
 
90
89
  - `<instruction-set>/final-report-schema.json` — the **per-task-type excerpt** of schema v2 (other task-types' deliverable blocks and unreachable `$defs` are stripped). This is the shape you must author. Read this, NOT the full `schemas/final-report-v2.0.schema.json` source outside the task bundle. Validation still runs against the installed full schema, so the excerpt never relaxes the contract. The excerpt is frozen at prep time and carries the okstra version it was cut from (`x-okstraCutFromVersion`); if the renderer rejects a field the excerpt told you to write, the installed schema wins.
91
- - `<instruction-set>/final-report-template.md` — the AI handoff Markdown template. Read it to understand which IDs, routing fields, evidence, task deliverable, and audit blocks appear in the AI artifact; do NOT edit it, and do NOT use it as the human presentation contract.
90
+ - `<instruction-set>/final-report-template.md` — the full reading copy template. Read it to understand which IDs, routing fields, evidence, task deliverable, and audit blocks appear in the AI artifact; do NOT edit it, and do NOT use it as the human presentation contract.
92
91
  - `templates/reports/i18n/en.json` and `templates/reports/i18n/ko.json`.
93
92
  - Every analysis worker's result file under `worker-results/`.
94
93
  - `state/convergence-<task-type>-<seq>.json` (if present). When present, reproduce its `roundHistory[]`, `round2SkippedReason`, and `finalClassificationCounts` verbatim into the final report's Section 6 Round History sub-table — do not recompute from worker results.
95
94
  - `<instruction-set>/task-brief.md` — the brief this run was prepared from. The lead already lists it under `## Inputs`; what it is FOR is the `## Expected Behavior` / `## Preserved Behavior` / `## Expected Outcome` items, whose `EB-NNN` / `PB-NNN` / `EO-NNN` ids are exactly what `endStateCoverage` maps. Read the instruction-set copy, NOT the project's `.okstra/briefs/...` path from the task manifest — that path is not resolvable here, same as the full schema source. A report that omits an id the brief pinned is rejected by the run validator.
96
95
 
97
- For a carry-in `clarification-response.md`, reconcile every prior `clarificationItems[]` row, including an open row with blank user input. Record the current status and user decision in data.json; the AI handoff renderer places it under `## Clarification and User Decisions`, while HTML renders any still-open response controls. When no carry-in path was provided, omit `clarificationCarryIn` entirely.
96
+ For a carry-in `clarification-response.md`, reconcile every prior `clarificationItems[]` row, including an open row with blank user input. Record the current status and user decision in data.json; the full reading copy renderer places it under `## Clarification and User Decisions`, while HTML renders any still-open response controls. When no carry-in path was provided, omit `clarificationCarryIn` entirely.
98
97
 
99
98
  Write a Reading Confirmation block to `**Audit sidecar path:**`, per the selected report-writer preamble's `Required reading` section (the main final-report and worker-results files carry no Section 0 heading). If you cannot truthfully confirm a file end-to-end, record a `tool-failure` in the errors sidecar instead of fabricating the report.
100
99
 
@@ -102,7 +101,7 @@ Write a Reading Confirmation block to `**Audit sidecar path:**`, per the selecte
102
101
 
103
102
  You author the final-report data.json (the JSON SSOT). You author it against the `<instruction-set>/final-report-schema.json` excerpt — its `$defs` enumerate every row shape, enum value, and cross-field constraint that applies to this run's task-type. The validator and renderers both consume the **full** `schemas/final-report-v2.0.schema.json` (the excerpt is a faithful task-type-scoped subset of it), so a data.json that satisfies the excerpt can independently produce both audience artifacts.
104
103
 
105
- The AI handoff Markdown is an agent-facing ledger: verdict, routing, clarification decisions, evidence, one structured task deliverable, and execution audits. The human HTML is the reader-facing explanation: `humanSummary` plus the selected task block's `userNarrative` and structured facts. Populate both human fields in data.json even though the Markdown intentionally omits their full prose. Worker discussion, convergence mechanics, and token usage belong to audit data and must not be copied into the HTML human main body.
104
+ The full reading copy is an agent-facing ledger: verdict, routing, clarification decisions, evidence, one structured task deliverable, and execution audits. The human HTML is the reader-facing explanation: `humanSummary` plus the selected task block's `userNarrative` and structured facts. Populate both human fields in data.json even though the Markdown intentionally omits their full prose. Worker discussion, convergence mechanics, and token usage belong to audit data and must not be copied into the HTML human main body.
106
105
 
107
106
  ### Implementation-planning frontmatter contract
108
107
 
@@ -159,13 +158,13 @@ Rules (the schema enforces most of these — they are listed here so you know *w
159
158
  - For legacy candidate-comparison `implementation-planning`, each `requirementCoverage` row's `source` is a graded cell, not prose — free text like `"carry-in from requirements-discovery C-001"` is rejected. Write exactly one of: `brief:EB-001` / `brief:PB-001` / `brief:EO-001`, an end-state id the brief declares — when the brief pins ids, citing a heading instead is rejected, because every brief carries the same generic headings and a heading cannot say WHICH reporter line the requirement came from (only a brief authored before the end-state sections existed still takes the older `brief:<heading>` form, and there the heading must literally exist in it); `derived:R-NNN — <one-line reason>`, whose chain must terminate at a `brief:` or `contract:` row of the same table without cycling; or `contract:<rule>`, for artifacts okstra's own phase contract mandates, whose allowlist is exactly the two tokens `decision-record-step` (the §5.4 Decision Drafts materialization step) and `glossary-step` (the glossary proposal step) — any other rule name is rejected, so never invent one. (Maintainer SSOT for that allowlist: `scripts/okstra_ctl/scope_provenance.py` in the okstra repo.) A requirement you cannot source this way does not belong in the table: put it in `clarificationItems[]` with `Blocks=approval`. **Enforced:** `validators/validate-run.py` `_validate_requirement_provenance`. In the same table, anchor every stage number in `coveredBy` to a `Stage` / `Stages` word (`Stage 2`, `Stages 1-3`) — `_validate_stage_has_requirement` reads that cell as prose and fails the plan when a Stage Map stage is cited by no row.
160
159
  - For legacy candidate-comparison `implementation-planning`, also populate `implementationPlanning.decisionDrafts` (one row per decision meeting all three decision-record criteria; `[]` otherwise) and `implementationPlanning.skippedAdrCandidates` (evaluated-but-dropped adr-candidates; `[]` otherwise). The schema excerpt enumerates the row shape; the renderer emits §5.4 `### Decision Drafts`. When `decisionDrafts` is non-empty, the plan's stages MUST carry a stepwise step that creates `.okstra/decisions/<NNNN>-<slug>.md` (validate-run gates this).
161
160
  - For `implementation-planning`, populate `implementationPlanning.variationPointAnalysis` — a `hasMultipleImplementations` judgement synthesized from the analysis workers' output, not a field filled in last. When it is `true`, write one `points[]` row per varying behavior carrying `behavior`, the two or more `implementations` that serve it, `evidence` (a `path:line`, or the sibling task / stage that already implements that behavior), and an `extractionDecision` of `extract` / `interfaceKind` / `coveredBy` (the Stage Map stage that builds the interface) / `rationale`; when it is `false`, write a non-empty `noVariationRationale` and leave `points` empty (the two branches are mutually exclusive). Do NOT pass a boilerplate rationale. Populate test seams under `implementationPlanning.directionRealization.testSeams` for selected-direction plans and under `implementationPlanning.recommendedOption.testSeams` for legacy candidate-comparison plans. An empty list is a conscious "no seam needed" claim, never a default for a field nobody filled. Every point becomes a `P-Var-*` plan item judged in §5.5.9.
162
- - When the `Task Type` is `improvement-discovery`, populate `improvementDiscovery.candidates[]`, `improvementDiscovery.lensCoverage[]`, `improvementDiscovery.selectionLimit`, and `improvementDiscovery.userNarrative`. Each candidate carries the 11 logical fields enforced by `validators/validate_improvement_report.py`; each lens-coverage row records candidate IDs or an evidence-backed no-candidate rationale. Source IDs, lens names, and worker prefixes from `scripts/okstra_ctl/improvement_lenses.py`. The standard renderer derives the AI handoff Markdown; never author a free-form improvement report.
161
+ - When the `Task Type` is `improvement-discovery`, populate `improvementDiscovery.candidates[]`, `improvementDiscovery.lensCoverage[]`, `improvementDiscovery.selectionLimit`, and `improvementDiscovery.userNarrative`. Each candidate carries the 11 logical fields enforced by `validators/validate_improvement_report.py`; each lens-coverage row records candidate IDs or an evidence-backed no-candidate rationale. Source IDs, lens names, and worker prefixes from `scripts/okstra_ctl/improvement_lenses.py`. The standard renderer derives the full reading copy; never author a free-form improvement report.
163
162
 
164
- Write the three completion artifacts and the separate audit sidecar with your `Write` tool — that is the canonical authoring path, and okstra ships no hook that blocks `.md` writes (its seeded settings carry no `PreToolUse` entry at all — only the two session lifecycle hooks `SessionStart` compact-reminder and `SessionEnd` team-reconcile, neither of which can intercept a tool call). A Bash heredoc is acceptable ONLY when a specific `Write` call is genuinely rejected by the host environment, and it MUST produce byte-identical content — do not reach for it pre-emptively. After writing data.json, invoke the renderer (`Bash`): `okstra render-final-report <data.json path>`, then write the Worker Result Path pointer. Confirm data.json, rendered Markdown, the pointer, and the audit sidecar exist before responding with a short status line prefixed by your model identity, per the preamble §"Return message to the lead". **Enforced:** dispatch `completionPaths` requires the first three files and `validators/validate_session_conformance.py` validates the audit sidecar.
163
+ Write the report record, the Worker Result Path pointer, and the separate audit sidecar with your `Write` tool — that is the canonical authoring path. A Bash heredoc is acceptable ONLY when a specific `Write` call is genuinely rejected by the host environment, and it MUST produce byte-identical content — do not reach for it pre-emptively. Do not invoke `okstra render-final-report`; the full reading copy is on-demand. Confirm data.json, the pointer, and the audit sidecar exist before responding with a short status line prefixed by your model identity, per the preamble §"Return message to the lead". **Enforced:** dispatch `completionPaths` requires the record and the pointer; `validators/validate_session_conformance.py` validates the audit sidecar.
165
164
 
166
165
  ```
167
166
  **Model:** Report writer worker, <modelExecutionValue>
168
- data.json written to <abs path>; markdown rendered to <abs path>. Sections populated: <count>.
167
+ data.json written to <abs path>. Sections populated: <count>.
169
168
  ```
170
169
 
171
170
  ## Error reporting
@@ -97,7 +97,7 @@ REPORT_WRITER_WORKER_PROMPT_FILE=""
97
97
  REPORT_WRITER_WORKER_PROMPT_RELATIVE_PATH=""
98
98
  FINAL_REPORT_PATH=""
99
99
  FINAL_STATUS_PATH=""
100
- FINAL_REPORT_RELATIVE_PATH=""
100
+ FINAL_REPORT_RECORD_RELATIVE_PATH=""
101
101
  FINAL_STATUS_RELATIVE_PATH=""
102
102
  FINAL_REPORT_TEMPLATE_PATH=""
103
103
  FINAL_REPORT_TEMPLATE_RELATIVE_PATH=""
@@ -147,7 +147,7 @@ RUN_SESSIONS_RELATIVE_PATH=""
147
147
  LATEST_RUN_PATH=""
148
148
  LATEST_RUN_RELATIVE_PATH=""
149
149
  LATEST_REPORT_PATH=""
150
- LATEST_REPORT_RELATIVE_PATH=""
150
+ LATEST_REPORT_RECORD_RELATIVE_PATH=""
151
151
  CURRENT_TASK_STATUS=""
152
152
  CURRENT_RUN_STATUS=""
153
153
  VALIDATION_STATUS="not-run"
@@ -319,7 +319,7 @@ PY
319
319
  )" || return 1
320
320
 
321
321
  if [[ -z "$found" ]]; then
322
- printf 'resume-clarification: no final-report-*.md found for task %s:%s under %s/runs\n' \
322
+ printf 'resume-clarification: no final-report found for task %s:%s under %s/runs\n' \
323
323
  "$task_group" "$task_id" "$task_root" >&2
324
324
  return 1
325
325
  fi
@@ -47,19 +47,21 @@ optional arguments:
47
47
  Required for a new implementation-planning run. Existing planning
48
48
  reruns continue to use --clarification-response with their prior report.
49
49
  --approved-plan Path to the approved final-report.md from a prior implementation-planning run.
50
- Required when --task-type=implementation; the file MUST contain a recorded user approval marker.
50
+ Required when --task-type=implementation; that report's record
51
+ (final-report-*.data.json) MUST carry \`frontmatter.approved: true\`.
51
52
  --approve Treat the user's CLI invocation itself as the plan-approval signal. Only meaningful
52
- together with --approved-plan and --task-type=implementation. The runtime updates the
53
- top "User Approval Request" block of the approved-plan file: flips
54
- \`- [ ] Approved\` to \`- [x] Approved\` and appends an approval audit line
55
- (timestamp + "CLI --approve"). Use this for scripted/CI flows or when you want a
56
- single command to both approve and launch the next phase.
53
+ together with --approved-plan and --task-type=implementation. Sets
54
+ \`frontmatter.approved\` to true on the report record and re-renders the full reading
55
+ copy from it; editing that reading copy does not approve the plan. Use this for
56
+ scripted/CI flows or when you want a single command to both approve and launch the
57
+ next phase.
57
58
  --implementation-option <name>
58
59
  Name of the Option Candidate the user chose from the implementation-planning
59
60
  final-report. Only meaningful together with --approved-plan and
60
- --task-type=implementation. The runtime fills the approved-plan frontmatter
61
- \`implementation-option:\` line with <name>. When omitted, the implementation run
62
- falls back to the plan's \`Recommended Option\`.
61
+ --task-type=implementation. The runtime records <name> as
62
+ \`frontmatter.implementationOption\` on the report record and re-renders the full
63
+ reading copy from it. When omitted, the implementation run falls back to the plan's
64
+ \`Recommended Option\`.
63
65
  --qa-waiver <stageKey>:<reason>
64
66
  User-recorded waiver for a blocking local io conformance gate.
65
67
  Only meaningful with --task-type=implementation; prepare records
@@ -254,7 +254,7 @@ for original in targets:
254
254
  task_group=row["taskGroup"], task_id=row["taskId"],
255
255
  task_type=row["taskType"], run_seq=next_seq,
256
256
  when=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
257
- run_dir_rel=base_run, final_report_rel=final_rel,
257
+ run_dir_rel=base_run, final_report_record_rel=final_rel,
258
258
  final_status_rel=final_status_rel,
259
259
  )
260
260
  # 원본 invocation 시점에 캡처된 envOverrides 를 base 에 layer 한다.
@@ -108,7 +108,7 @@ print(json.dumps({k: v for k, v in zip(it, it)}, ensure_ascii=False))
108
108
  RECOMMENDED_ANALYSERS "${RECOMMENDED_ANALYSERS-}" \
109
109
  LEAD_MODEL "${LEAD_MODEL-}" \
110
110
  RUN_DIR_RELATIVE_PATH "${RUN_DIR_RELATIVE_PATH-}" \
111
- FINAL_REPORT_RELATIVE_PATH "${FINAL_REPORT_RELATIVE_PATH-}" \
111
+ FINAL_REPORT_RECORD_RELATIVE_PATH "${FINAL_REPORT_RECORD_RELATIVE_PATH-}" \
112
112
  FINAL_STATUS_RELATIVE_PATH "${FINAL_STATUS_RELATIVE_PATH-}" \
113
113
  OKSTRA_INVOCATION_ARGV_JSON "$_argv_json" \
114
114
  OKSTRA_INVOCATION_CWD "${OKSTRA_INVOCATION_CWD-}" \
@@ -140,7 +140,7 @@ with lockfile.open("r+") as lock:
140
140
  workers=[w for w in payload.get("RECOMMENDED_ANALYSERS", "").split(",") if w],
141
141
  lead_model=payload.get("LEAD_MODEL", ""),
142
142
  run_dir_rel=payload.get("RUN_DIR_RELATIVE_PATH", ""),
143
- final_report_rel=payload.get("FINAL_REPORT_RELATIVE_PATH", ""),
143
+ final_report_record_rel=payload.get("FINAL_REPORT_RECORD_RELATIVE_PATH", ""),
144
144
  final_status_rel=payload.get("FINAL_STATUS_RELATIVE_PATH", ""),
145
145
  argv=json.loads(payload.get("OKSTRA_INVOCATION_ARGV_JSON", "[]")),
146
146
  cwd=payload.get("OKSTRA_INVOCATION_CWD", ""),
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """CLI entry for the schema-selected AI handoff Markdown renderer.
2
+ """CLI entry for the schema-selected full reading copy Markdown renderer.
3
3
 
4
4
  Usage:
5
5
  python3 scripts/okstra-render-final-report.py \\
@@ -35,7 +35,7 @@ from okstra_token_usage import ( # noqa: E402,F401
35
35
  antigravity_cost_usd,
36
36
  iter_jsonl,
37
37
  na_block,
38
- populate_and_render,
38
+ populate_token_cells,
39
39
  populate_data_token_cells,
40
40
  SubstituteRefusedError,
41
41
  usage_block,
@@ -49,7 +49,7 @@ For a new `implementation-planning` run, the plan-body sequence is initial verif
49
49
  ## Run Paths
50
50
 
51
51
  - Team state: `{{TEAM_STATE_RELATIVE_PATH}}`
52
- - Final report: `{{FINAL_REPORT_RELATIVE_PATH}}`
52
+ - Final report: `{{FINAL_REPORT_RECORD_RELATIVE_PATH}}`
53
53
  - Final status: `{{FINAL_STATUS_RELATIVE_PATH}}`
54
54
  - Validator: `{{RUN_VALIDATOR_RELATIVE_PATH}}`
55
55
 
@@ -59,7 +59,12 @@ Use the screen to tell "still working" from "stuck", and to see at a glance whic
59
59
  - Worker completion is valid only from `workerDispatches[]`, terminal status sidecars, and required Result Paths. Pane creation alone is not completion.
60
60
  - Reverify uses a fresh jobs file at `runs/<task-type>/state/reverify-jobs-r<N>-<task-type>-<seq>.json`, sets `dispatchKind: "reverify-r<N>"`, and dispatches with `okstra team dispatch --project-root <root> --run-manifest <path> --dispatch-kind reverify-r<N> --jobs-file <jobs-file>`.
61
61
  - Report-writer uses a fresh one-job jobs file with `dispatchKind: "report-writer"` and the same schema, then dispatches through `okstra team dispatch --project-root <root> --run-manifest <path> --jobs-file <jobs-file>`.
62
- - Every reverify or report-writer jobs file carries `workerId`, `provider`, `role`, `modelExecutionValue`, `promptPath`, `resultPath`, `workerResultPath`, and `completionPaths`. For reverify, set `role` to `worker-reverify-r<N>` — the role selects the dispatch's idle budget and is recorded in the run's status sidecar, so it must name the actual assignment. The report-writer completion paths include both data.json and the worker-results audit file.
62
+ - A jobs file is `{"dispatchKind": "<kind>", "workers": [ … ]}` — the array key is `workers`, not `jobs`. Each entry follows the run manifest's identity version, and `scripts/okstra_ctl/dispatch_state.py` `worker_execution_identity` is the contract:
63
+ - **v2 entry** (`schemaVersion: "2.0"`, `executionIdentityVersion: 2`): carries `participantRef`, `roleExecutionRef`, `assignmentRef`, `executionLabel`, `dutyId`, `invocationRef`, a positive integer `attempt`, `provider`, `promptPath`, `workerResultPath`, and a `digests` object holding `catalogDigest`, `assignmentDigest`, `dutyDigest`, `instructionDigest`, and `promptDigest`. **Do not include `workerId`** — a v2 entry that carries it is refused as mixing v1 and v2 identity; the worker key is projected from `assignmentRef`.
64
+ - **v1 entry**: carries `workerId`, `provider`, `promptPath`, and `workerResultPath`, and must carry none of the v2 identity fields.
65
+ - `resultPath` and `completionPaths` are advisory in both: dispatch derives them from the same rules the roster path uses, so a jobs file cannot disagree with a roster dispatch about which artifact is the result.
66
+ - `workerResultPath` must carry the canonical `-worker-` token (`<role>-worker-<task-type>-<seq>.md`); the audit sidecar name is derived from it by inserting `-audit-` after that token, so a reverify name like `<role>-reverify-r1-<task-type>-<seq>.md` is refused with `worker result path has no canonical -worker- token`. The round belongs in `dispatchKind` and `invocationRef`, not in the artifact name.
67
+ - `role` names the role execution's own role — `verifier` for reverify, `report-writer` for the report writer. It is not a per-round label: `dispatch_state.py` requires the entry's `role` to equal both the role execution's `role` and the duty's role, so a value like `worker-reverify-r<N>` is refused as `jobs file v2 identity does not match role execution authority`. The round lives in `dispatchKind` and in `invocationRef`. The report-writer completion paths include both the report record and the worker-results audit file.
63
68
  - After either dispatch, run `okstra team await --project-root <root> --run-manifest <path>` before evaluating terminal status or completion paths.
64
69
 
65
70
  ## Completion, cleanup, and resume
@@ -54,7 +54,7 @@
54
54
  | `latestRunPath` | latest run path |
55
55
  | `latestRunStatus` | latest run status |
56
56
  | `latestRunPromptsPath` | latest run prompt directory path |
57
- | `latestReportPath` | latest report path |
57
+ | `latestReportRecordPath` | latest report path |
58
58
  | `latestResumeCommandPath` | resume helper path |
59
59
  | `historyTimelinePath` | timeline path |
60
60
  | `resultContract` | team contract and expected artifact metadata |
@@ -82,7 +82,8 @@ After identifying the task root in `task-manifest.json`, derive all paths accord
82
82
  │ ├── manifests/ (run-manifest-<task-type>-<seq>.json)
83
83
  │ ├── state/ (team-state-<task-type>-<seq>.json, convergence-<task-type>-<seq>.json)
84
84
  │ ├── prompts/ (run prompt path and worker prompt history paths recorded in the manifest)
85
- │ ├── reports/ (final-report-<task-type>-<seq>.md)
85
+ │ ├── reports/ (final-report-<task-type>-<seq>.data.json + .html;
86
+ │ the .md reading copy is rendered on demand)
86
87
  │ ├── status/ (final-<task-type>-<seq>.status)
87
88
  │ ├── sessions/ (runtime-specific resume artifacts when the selected adapter supports them)
88
89
  │ ├── logs/ (errors-<task-type>-<seq>.jsonl, optional)
@@ -84,7 +84,7 @@ Read the worker result files generated in Phase 4/5 and extract individual findi
84
84
  - Same semantics but disjoint ticket sets → separate groups (do NOT over-merge across tickets).
85
85
  - Only one worker confirms a finding → one single-source group.
86
86
  4. When grouping is ambiguous, prefer splitting over merging (avoid over-merging). Semantic matching, ticket-set equality, and evidence interpretation remain lead judgments; the engine does not perform fuzzy matching or decide whether evidence is credible.
87
- 5. Write `runs/<task-type>/state/convergence-groups-<task-type>-<seq>.json`. Each group carries its `ticketIds`, `originWorker`, `originEvidence`, `discoveredBy`, and every `<worker>:<item-id>` source in `sourceItems`. For analysis sidetracks where ticket tagging is not required, `ticketIds: []` is the canonical value; never synthesize `"unknown"` or another placeholder. `scripts/okstra_ctl/convergence_engine.py` and the version-selected convergence-groups schema enforce the required array field and reject non-string or blank entries while allowing the empty array. When a live command or external read produced reproducible evidence, also include `evidenceArtifacts[]` with its `.okstra/` path, SHA-256 digest, command, and environment. The field is optional because historical or inaccessible evidence may not have a captured artifact. The lead and verifier MUST NOT infer live or external evidence from wording or keyword matching; they use the finding's explicit claim, provenance, and supplied artifacts. Include the resolved worker roster in order with functional `audience` values; do not derive scope from provider or model identity. In a v2 run, set top-level `schemaVersion: "2.0"`, `executionIdentityVersion: 2`, and `runManifestPath` to the current run manifest's exact canonical project-relative path. Write the groups artifact under that same resolved run directory's `state/` directory. Every v2 worker row also carries the paired `participantRef` and `sourceRoleExecutionRef` from that run manifest's canonical role state. Set `sourceRoleExecutionRef` to the selected source `RoleExecution` row's `roleExecutionRef`, not that row's `sourceRoleExecutionRef` field; a static source row has null in the latter field. Copy `participantRef` from that same selected row. Never derive those references from the worker name, provider, model, or execution label. A legacy v1 document keeps `schemaVersion: "1.0"` and omits `executionIdentityVersion`, `runManifestPath`, and both worker reference fields. The `audience` enum is a convergence role, not a phase label: every finding-producing worker uses `analysis` and selects an `analyser`, `designer`, `planner`, or `verifier` source role. Only the report author uses `report-writer`, paired with a `report-writer` source role. There is no `implementation-verifier` audience here; map an implementation verifier to `analysis`.
87
+ 5. Write `runs/<task-type>/state/convergence-groups-<task-type>-<seq>.json`. Each group carries its `ticketIds`, `originWorker`, `originEvidence`, `discoveredBy`, and every `<worker>:<item-id>` source in `sourceItems`. For analysis sidetracks where ticket tagging is not required, `ticketIds: []` is the canonical value; never synthesize `"unknown"` or another placeholder. `scripts/okstra_ctl/convergence_engine.py` and the version-selected convergence-groups schema enforce the required array field and reject non-string or blank entries while allowing the empty array. When a live command or external read produced reproducible evidence, also include `evidenceArtifacts[]` with its `.okstra/` path, SHA-256 digest, command, and environment. The field is optional because historical or inaccessible evidence may not have a captured artifact. The lead and verifier MUST NOT infer live or external evidence from wording or keyword matching; they use the finding's explicit claim, provenance, and supplied artifacts. Include the resolved worker roster in order with functional `audience` values; do not derive scope from provider or model identity. In a v2 run, set top-level `schemaVersion: "2.0"`, `executionIdentityVersion: 2`, and `runManifestPath` to the current run manifest's exact canonical project-relative path. Write the groups artifact under that same resolved run directory's `state/` directory. Every v2 worker row also carries the paired `participantRef` and `sourceRoleExecutionRef` from that run manifest's canonical role state. Set `sourceRoleExecutionRef` to the selected source `RoleExecution` row's `roleExecutionRef`, not that row's `sourceRoleExecutionRef` field; a static source row has null in the latter field. Copy `participantRef` from that same selected row. Never derive those references from the worker name, provider, model, or execution label. A legacy v1 document keeps `schemaVersion: "1.0"` and omits `executionIdentityVersion`, `runManifestPath`, and both worker reference fields. The `audience` enum is a convergence role, not a phase label: every finding-producing worker uses `analysis` and selects an `analyser`, `designer`, `planner`, or `verifier` source role. Only the report author uses `report-writer`, paired with a `report-writer` source role. There is no `implementation-verifier` audience here; map an implementation verifier to `analysis`. **Your own review findings use `audience: "lead"`.** The phases that ask you to review the deliverable yourself produce findings that belong in this state — it is what the report author reads — and declaring yourself an analysis worker to get them in is forbidden. A `lead` row is a source, never a vote: `originWorker`, `discoveredBy` and `sourceItems` accept it, and the consensus count ignores it, so a finding only you saw stays queued for verification instead of resolving itself.
88
88
  6. Do not write a queue or classification in this grouped-input artifact. `okstra convergence seed` classifies Round 0 by mode:
89
89
  - Collaborative mode: multi-source groups become `full-consensus` immediately; only single-source groups enter the working queue.
90
90
  - Adversarial mode: every finding enters the working queue regardless of source count. Semantic grouping merges provenance only; it does not decide a finding is reliable.
@@ -350,7 +350,7 @@ If convergence is disabled, `seed`/`finalize` produce the auto-disabled final st
350
350
 
351
351
  ### Authoring ownership (BLOCKING)
352
352
 
353
- If `Report writer worker` is in the selected roster (`recommendedWorkers` / `resultContract.requiredWorkerRoles`), **Lead MUST dispatch it to author the final report data.json**. The worker writes the JSON SSOT at `runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json` and invokes `scripts/okstra-render-final-report.py` to produce the sibling AI handoff Markdown. Phase 7 renders human HTML directly from the same data.json. Lead writes none of these files; it prepares the prompt, dispatches, and reviews both audience artifacts. See [report-writer](./report-writer.md) "File-author ownership".
353
+ If `Report writer worker` is in the selected roster (`recommendedWorkers` / `resultContract.requiredWorkerRoles`), **Lead MUST dispatch it to author the final report data.json**. The worker writes the JSON SSOT at `runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json`. Phase 7 renders the human HTML from that record. The full reading copy is rendered on demand with `okstra render-final-report <data.json>`. Lead writes none of these files; it prepares the prompt, dispatches, and reviews the human HTML. See [report-writer](./report-writer.md) "File-author ownership".
354
354
 
355
355
  Before constructing the dispatch prompt, the lead MUST:
356
356
 
@@ -402,9 +402,9 @@ For a new `implementation-planning` run, the fixed order is initial verification
402
402
  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.
403
403
  3. Aggregate verdicts and resolve the gate result to one of `passed` / `passed-with-dissent` / `blocked-by-disagreement` / `aborted-non-result`.
404
404
  4. Write `runs/<task-type>/state/plan-body-verification.json` (schema in the plan-body-verification contract), appending round 1 and, if the one automatic rewrite ran, round 2 to `roundHistory[]`; data.json keeps only the final verdicts.
405
- 5. Populate `implementationPlanning.planBodyVerification` in data.json with round count, gate result, per-item verdicts, and dissent log. The AI handoff task-deliverable block carries this structure without a second prose rendering.
405
+ 5. Populate `implementationPlanning.planBodyVerification` in data.json with round count, gate result, per-item verdicts, and dissent log. The full reading copy task-deliverable block carries this structure without a second prose rendering.
406
406
  6. For every `majority-disagree` plan item, append one `clarificationItems[]` row with `blocks=approval` and the 1:1 ID match in the verdict classification (`majority-disagree → C-<N>`). Do not create a parallel open-questions structure.
407
- 7. Publish the YAML frontmatter `approved:` field as `false`. There is no in-body `- [ ] Approved` marker line — approval lives only in the frontmatter (see [plan-body-verification](./plan-body-verification.md) §"Round protocol" step 9). The user may flip it to `true` only when the gate is `passed` or `passed-with-dissent`. **Enforced:** `validators/validate-run.py` `validate_phase_boundary` fails a report shipping `approved: true` under `blocked-by-disagreement` / `aborted-non-result`, and run-prep (`scripts/okstra_ctl/run.py` `_validate_approved_plan`) fail-closes the same case. Manually flipping a blocked gate to passing is a contract violation.
407
+ 7. Publish the report record `frontmatter.approved` field as `false`. There is no in-body `- [ ] Approved` marker line — approval lives only in the record (see [plan-body-verification](./plan-body-verification.md) §"Round protocol" step 9). The user may set it to `true` (via `--approve` or the in-session wizard) only when the gate is `passed` or `passed-with-dissent`. **Enforced:** `validators/validate-run.py` `validate_phase_boundary` fails a report shipping `approved: true` under `blocked-by-disagreement` / `aborted-non-result`, and run-prep (`scripts/okstra_ctl/run.py` `_validate_approved_plan`) fail-closes the same case. Manually flipping a blocked gate to passing is a contract violation.
408
408
 
409
409
  If `convergence.planBodyVerification.enabled == false` (set by `--no-plan-verification` or by `okstra config set plan-verification off`), the entire sub-step is skipped and the top-of-report Approval marker is rendered unconditionally (legacy behaviour). This opt-out is intended for fast iteration only and is not recommended for handoff-ready plans.
410
410
 
@@ -433,7 +433,7 @@ jq -s 'group_by(.errorType) | map({type: .[0].errorType, count: length})' <runDi
433
433
 
434
434
  The errors log is informational. Its presence/absence does not affect the final verdict. Do not block report writing on it.
435
435
 
436
- After persistence, reply briefly in the resolved Report Language with: completion status, final report path, team-state path, validator result, resume command path, any remaining blocker. **Lead this reply with the run's task identity** — state `<task-group>/<task-id>` (or the full `taskKey`) first, so the reader knows which task the reply is about. **Every run-artifact path in this reply MUST be task-qualified** — report the final report as `.okstra/tasks/<task-group>/<task-id>/runs/<task-type>/reports/final-report-<task-type>-<seq>.md` rooted at the task bundle, NOT the bare `runs/<task-type>/reports/...` form (byte-for-byte identical across every task of the same task-type, so it cannot identify the task). The same task-qualified rule applies to the team-state path, resume command path, and any other run-artifact path this reply cites.
436
+ After persistence, reply briefly in the resolved Report Language with: completion status, the human report path, the report record path, team-state path, validator result, resume command path, any remaining blocker. **Lead this reply with the run's task identity** — state `<task-group>/<task-id>` (or the full `taskKey`) first, so the reader knows which task the reply is about. **Every run-artifact path in this reply MUST be task-qualified** — report the human report as `.okstra/tasks/<task-group>/<task-id>/runs/<task-type>/reports/final-report-<task-type>-<seq>.html` rooted at the task bundle, NOT the bare `runs/<task-type>/reports/...` form (byte-for-byte identical across every task of the same task-type, so it cannot identify the task). Under that, cite the report record (`.data.json`) and one line to render the full reading copy: `okstra render-final-report <task-qualified data.json>`. The same task-qualified rule applies to the team-state path, resume command path, and any other run-artifact path this reply cites.
437
437
 
438
438
  ## Run-scoped worker-resource lifecycle
439
439
 
@@ -245,7 +245,7 @@ round before any host or provider process starts.
245
245
  **Score the gate with `okstra plan-verify`, never by hand (BLOCKING).** Once this round's verdicts are in the data.json, lead runs
246
246
 
247
247
  ```
248
- okstra plan-verify --report <runs/<task-type>/reports/final-report-<task-type>-<seq>.md>
248
+ okstra plan-verify --report <runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json>
249
249
  ```
250
250
 
251
251
  and records what it returns: `gate.recomputed` is the round's gate value, `gate.blockedBy` its `gateBlockedBy` causes, `gate.blockingItems` the items that block. The same call runs every plan-body check a round can be judged on alone — provenance, fixability, subject substance, self-fix grouping, round recording, clarification matching, state-file rounds — and exits 2 with `failures[]` when the round is not contract-clean. **A round is not complete while that exit code is non-zero.**
@@ -301,7 +301,7 @@ round before any host or provider process starts.
301
301
  - `open → obsolete` only when a plan change removes the question
302
302
  `open` and `answered` continue to block approval; only `resolved` and `obsolete` are non-blocking. A user-directed correction does not consume the automatic self-fix limit, and a failed check does not restart the automatic loop.
303
303
  - A terminal row may preserve its original dissent classification only from audited history. Keep superseded votes in `state/plan-body-verification-implementation-planning-<seq>.json`; `validators/validate-run.py` `_historical_plan_item_evidence` recomputes criticality from the recorded `DISAGREE(a|f)` tokens and does not trust the row's classification alone. Every referenced `user-decision-required` / `user-decision-evaluated` activity must cite exactly that row's `C-NNN` and exactly the linked `approvalContext.planItemIds` set. The evaluated activity occurs after every required activity, has `outcome: resolved`, records at least one command whose every `exitCode` is `0`, points `resultPath` at the matching plan-body state artifact, and cites exactly one `plan-body-verification:round-N` evidence token. Round `N` is later than the recorded blocking round; its state votes are all `AGREE` or `SUPPLEMENT`, and they exactly match the final-report verdicts. Its immutable `completedAt` is later than every referenced required activity's canonical event timestamp and no later than every referenced evaluated activity's canonical event timestamp, so an older successful round cannot be relabelled as the response check. This user-response round is recorded in `roundHistory[]` but does not increment `selfFixRoundsApplied` or create another automatic `verification-round-completed` activity. Only the exact round token in `resolution.checkRefs` of a resolved `correctness-critical` row receives that exclusion; the referenced resolved `user-decision-evaluated` activity must match the row's exact `C-NNN` and plan-item set. **Enforced:** `validators/validate-run.py` `_validate_approval_activity_refs`, `_validate_correctness_resolution`, and `_validate_target_round_causality`, plus `validators/validate_session_conformance.py` `_resolved_correctness_reverification_rounds` and `_check_activity_round_counts`. When an independent coverage-only blocker is corrected, keep the `C-NNN` in the now non-blocking Requirement Coverage row's `decisionRefs` and in the matching state-sidecar plan item's `clarificationId`; that item must have no historical blocking dissent and must participate in a round whose `gateBlockedBy` contains `coverage-gap`. A run-wide `coverage-gap` without this item-level `C-NNN` link cannot classify another row. `obsolete` is valid only after current evidence shows that the question or blocker disappeared, or the linked item is historical and removed; a current linked item remains active even when audited history preserves an older classification.
304
- 9. Approval lives in the report's YAML frontmatter `approved:` field — there is no in-body marker line. The user may flip it to `true` only when the Gate result is `passed` or `passed-with-dissent`. **Enforced:** run-prep (`scripts/okstra_ctl/run.py` `_validate_approved_plan`) fail-closes an `approved: true` plan whose data.json carries a blocking `gateResult` or an open/answered `Blocks: approval` clarification row, and `validators/validate-run.py` `_validate_plan_body_gate_recompute` rejects a declared `gateResult` healthier than the recorded votes.
304
+ 9. Approval lives in the report record `frontmatter.approved` field — there is no in-body marker line. The user may set it to `true` (via `--approve` or the in-session wizard) only when the Gate result is `passed` or `passed-with-dissent`. **Enforced:** run-prep (`scripts/okstra_ctl/run.py` `_validate_approved_plan`) fail-closes an `approved: true` plan whose record carries a blocking `gateResult` or an open/answered `Blocks: approval` clarification row, and `validators/validate-run.py` `_validate_plan_body_gate_recompute` rejects a declared `gateResult` healthier than the recorded votes.
305
305
 
306
306
  ## `plan-body-verification-<task-type>-<seq>.json` schema
307
307
 
@@ -561,4 +561,4 @@ Mirrors finding convergence ([convergence](./convergence.md) §"Worker failure h
561
561
 
562
562
  - A dispatch that returns terminal non-result MUST NOT be aggregated as `DISAGREE`.
563
563
  - If at least one dispatch was issued AND **all** plan-body dispatches return non-result, the Gate result is `aborted-non-result`. Record one `contract-violation` event per non-result dispatch.
564
- - When the gate is `aborted-non-result`, report-writer MUST keep the frontmatter `approved: false` (publishing `approved: true` under this gate result is a validator failure). A single row is added to `## 1. Clarification Items` with `Statement="plan-body verification could not run — all workers returned non-result"`, `Kind=decision`, `Blocks=approval`, allowing the user to either retry the phase or override by manually flipping the frontmatter to `approved: true` (or running `--approve` on the resume command). The row MUST name which dispatches returned no result and what re-running them requires. **Enforced:** `validators/validate-run.py` `_validate_aborted_gate_has_clarification` — `_validate_plan_body_clarification_matching` cannot cover this case because it walks `majority-disagree` items and an aborted round produces none, which is exactly how an aborted run used to reach the user with no stated blocker and stall.
564
+ - When the gate is `aborted-non-result`, report-writer MUST keep the frontmatter `approved: false` (publishing `approved: true` under this gate result is a validator failure). A single row is added to `## 1. Clarification Items` with `Statement="plan-body verification could not run — all workers returned non-result"`, `Kind=decision`, `Blocks=approval`, allowing the user to either retry the phase or override by running `--approve` on the resume command (or confirming in the in-session wizard). The row MUST name which dispatches returned no result and what re-running them requires. **Enforced:** `validators/validate-run.py` `_validate_aborted_gate_has_clarification` — `_validate_plan_body_clarification_matching` cannot cover this case because it walks `majority-disagree` items and an aborted round produces none, which is exactly how an aborted run used to reach the user with no stated blocker and stall.