okstra 0.179.2 → 0.180.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 (78) hide show
  1. package/README.md +1 -1
  2. package/dist/cli-registry.mjs +14 -0
  3. package/dist/cli-registry.mjs.map +1 -1
  4. package/dist/commands/execute/incremental-carry.mjs +9 -8
  5. package/dist/commands/execute/incremental-carry.mjs.map +1 -1
  6. package/dist/commands/execute/plan-verify.mjs +3 -1
  7. package/dist/commands/execute/plan-verify.mjs.map +1 -1
  8. package/dist/commands/report/approval-decision.d.mts +1 -0
  9. package/dist/commands/report/approval-decision.mjs +21 -0
  10. package/dist/commands/report/approval-decision.mjs.map +1 -0
  11. package/dist/commands/report/design-snapshot.d.mts +1 -0
  12. package/dist/commands/report/design-snapshot.mjs +19 -0
  13. package/dist/commands/report/design-snapshot.mjs.map +1 -0
  14. package/docs/architecture/storage-model.md +1 -1
  15. package/docs/architecture.md +10 -10
  16. package/docs/cli.md +11 -8
  17. package/docs/project-structure-overview.md +15 -6
  18. package/docs/task-process/implementation-planning.md +2 -2
  19. package/package.json +1 -1
  20. package/runtime/BUILD.json +2 -2
  21. package/runtime/agents/workers/report-writer-worker.md +15 -164
  22. package/runtime/prompts/launch.template.md +6 -5
  23. package/runtime/prompts/lead/adapters/cmux.md +1 -1
  24. package/runtime/prompts/lead/convergence.md +2 -2
  25. package/runtime/prompts/lead/okstra-lead-contract.md +19 -18
  26. package/runtime/prompts/lead/plan-body-verification.md +39 -18
  27. package/runtime/prompts/lead/report-writer.md +64 -423
  28. package/runtime/prompts/lead/team-contract.md +1 -1
  29. package/runtime/prompts/profiles/_clarification-recommendation.md +5 -4
  30. package/runtime/prompts/profiles/_common-contract.md +3 -3
  31. package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
  32. package/runtime/prompts/profiles/change-impact-analysis.md +1 -1
  33. package/runtime/prompts/profiles/error-analysis.md +1 -1
  34. package/runtime/prompts/profiles/feature-analysis.md +1 -1
  35. package/runtime/prompts/profiles/implementation-planning.md +13 -11
  36. package/runtime/prompts/profiles/improvement-discovery.md +1 -1
  37. package/runtime/prompts/profiles/project-analysis.md +1 -1
  38. package/runtime/prompts/profiles/requirements-discovery.md +1 -1
  39. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +2 -1
  40. package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +1 -1
  41. package/runtime/python/okstra_ctl/agent_activity.py +23 -3
  42. package/runtime/python/okstra_ctl/agent_prompt_cli.py +6 -6
  43. package/runtime/python/okstra_ctl/analysis_packet.py +43 -2
  44. package/runtime/python/okstra_ctl/approval_decisions.py +327 -0
  45. package/runtime/python/okstra_ctl/design_snapshot.py +134 -0
  46. package/runtime/python/okstra_ctl/dispatch_core.py +62 -4
  47. package/runtime/python/okstra_ctl/dispatch_state.py +29 -4
  48. package/runtime/python/okstra_ctl/execution_mutation_audit.py +6 -2
  49. package/runtime/python/okstra_ctl/final_report_schema.py +24 -15
  50. package/runtime/python/okstra_ctl/incremental_carry.py +128 -16
  51. package/runtime/python/okstra_ctl/incremental_scope.py +4 -1
  52. package/runtime/python/okstra_ctl/path_hints.py +12 -0
  53. package/runtime/python/okstra_ctl/paths.py +12 -0
  54. package/runtime/python/okstra_ctl/plan_items_cli.py +113 -16
  55. package/runtime/python/okstra_ctl/ports/worker_dispatch.py +2 -1
  56. package/runtime/python/okstra_ctl/render.py +48 -1
  57. package/runtime/python/okstra_ctl/render_final_report.py +7 -6
  58. package/runtime/python/okstra_ctl/report_assembly.py +354 -0
  59. package/runtime/python/okstra_ctl/report_contract.py +2 -1
  60. package/runtime/python/okstra_ctl/report_finalize.py +60 -22
  61. package/runtime/python/okstra_ctl/report_inputs.py +72 -0
  62. package/runtime/python/okstra_ctl/report_markdown.py +69 -8
  63. package/runtime/python/okstra_ctl/report_narrative.py +319 -0
  64. package/runtime/python/okstra_ctl/report_projections.py +265 -0
  65. package/runtime/python/okstra_ctl/run.py +25 -9
  66. package/runtime/python/okstra_ctl/schema_excerpt.py +11 -6
  67. package/runtime/python/okstra_ctl/stage_fix_carry.py +4 -4
  68. package/runtime/python/okstra_ctl/stage_ledger.py +132 -18
  69. package/runtime/python/okstra_ctl/stage_map.py +70 -22
  70. package/runtime/python/okstra_ctl/team.py +1 -1
  71. package/runtime/python/okstra_ctl/worker_dispatch.py +5 -2
  72. package/runtime/python/okstra_ctl/worker_prompt_body.py +35 -0
  73. package/runtime/python/okstra_ctl/worker_prompt_policy.py +31 -3
  74. package/runtime/schemas/final-report-v3.0.schema.json +10210 -0
  75. package/runtime/schemas/report-narrative-v3.0.schema.json +30 -0
  76. package/runtime/templates/report-writer-prompt-preamble.md +15 -21
  77. package/runtime/templates/reports/html/macros/forms.html +6 -4
  78. package/runtime/validators/validate-run.py +258 -10
@@ -1,180 +1,31 @@
1
1
  ---
2
2
  name: report-writer-worker
3
- description: |
4
- Use this agent when okstra is in Phase 6 and the run roster includes `Report writer worker`. This agent authors the final-report file from collected analysis-worker results and convergence output. It is NOT an analysis worker — it does not produce independent findings.
5
-
6
- <example>
7
- Context: okstra has completed Phase 5.5 convergence and is entering Phase 6 with `Report writer worker` in the roster.
8
- user: "okstra this task bundle"
9
- assistant: "Phase 6 — dispatching report-writer-worker to author the final report."
10
- <commentary>The okstra skill dispatches this agent in Phase 6 to write the final-report file.</commentary>
11
- </example>
12
- color: purple
13
- model: inherit
14
- tools: ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "TodoWrite", "WebFetch", "WebSearch"]
3
+ description: >
4
+ Use this agent in Phase 6 to synthesize the report narrative Markdown from
5
+ settled worker results. It is not an analysis worker and does not publish
6
+ the final report record.
15
7
  ---
16
8
 
17
- This is the Claude host execution adapter for a materialized Okstra invocation.
18
- The final prompt's `report-writer` duty contract owns the role boundary,
19
- required responsibility, and prohibited actions. This file owns only how that
20
- contract is executed with Claude host tools. Refuse a dispatch whose prompt has
21
- no adjacent verified invocation metadata. Consume the stored `executionLabel`.
22
- The final-report `executionRoles[]` set must equal the execution-manifest
23
- `roleExecutions[]` set.
24
-
25
- - The `**Report Language:**` header in your dispatch prompt is already
26
- resolved to `en` or `ko` by the lead. Copy it verbatim into
27
- `data.json.meta.reportLanguage`. Never write `auto` here.
28
- - **That header is not the language you write in — you always write English.**
29
- It names the language the human HTML renders in, and Phase 7 reads it to
30
- decide whether to dispatch a translator. The data.json is the SSOT every
31
- later phase, validator and agent reads, so authoring it in anything but
32
- English splits the record. **Enforced:** `okstra report-finalize` runs
33
- `report-translate check-source` as its first Phase 7 step and fails the run
34
- when the data.json's authored prose is not English. You can run that check
35
- yourself before returning.
36
-
37
- ## Host file procedure
38
-
39
- Use the assigned `runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json`
40
- path. Do not invoke the full reading copy renderer.
41
-
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
-
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
-
46
- ## Worker Result File (MANDATORY)
47
-
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
-
50
- 1. The canonical data.json path you wrote (project-relative).
51
- 2. The convergence-state input path from the prompt (project-relative).
52
-
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.
54
-
55
- ## Heartbeat (BLOCKING)
56
-
57
- Write the audit sidecar at `**Audit sidecar path:**` before required reading, then append `- PROGRESS: <stage> <ISO-8601-UTC>` at a cadence no longer than five minutes. Use only: `started`, `required-reading-complete`, `synthesis-start`, `data-json-write-start`, `render-start`, and `write-result-start`. When a stage runs longer than that, append `- PROGRESS: in-stage:<stage> <ISO-8601-UTC>` with the same stage names. The Phase 7 validator enforces the first stage, timestamps, cadence, and this allowlist.
58
-
59
- `data-json-write-start`, `render-start` and `write-result-start` open a stretch whose work is one uninterruptible tool call, so no `in-stage:` line can be appended while it runs. Those three carry a 20-minute budget instead of five. `required-reading-complete` and `synthesis-start` open the tool-free reading and synthesis stretches — the worker cannot append an `in-stage:` line mid-synthesis either — and carry a 15-minute budget. Every other stage keeps the five-minute cadence.
60
-
61
- ## Execution Rules
62
-
63
- 1. Extract the absolute `Project Root` from the lead prompt (look for a line starting with `**Project Root:**` or `Project Root:`). If missing, return:
64
- `REPORT_WRITER_PROJECT_ROOT_MISSING: absolute Project Root was not provided in the lead prompt`
65
-
66
- 2. Extract the assigned worker prompt history path (`Assigned worker prompt history path:`). If missing, return:
67
- `REPORT_WRITER_PROMPT_PATH_MISSING: assigned worker prompt history path was not provided`
68
- - Resolve relative paths against `Project Root` to get an absolute path.
69
-
70
- 3. Extract the assigned `Result Path` (the final-report file path). If missing, return:
71
- `REPORT_WRITER_RESULT_PATH_MISSING: assigned final-report Result Path was not provided`
72
- - Resolve relative paths against `Project Root` to get an absolute path.
73
-
74
- 4. Persist the exact worker prompt to the absolute prompt history path before producing any output. Prefer a Bash heredoc (`cat > <path> <<'EOF'`) — on a redispatch the prompt history file already exists, and the `Write` tool then refuses with "File has not been read yet"; the heredoc has no read-before-write constraint. Never use `/tmp/*prompt*.txt` as canonical storage. Create parent directories if missing.
75
-
76
- 5. Anchor all file operations to the absolute `Project Root`. Use absolute paths everywhere — do not rely on inherited cwd, do not `cd`.
9
+ # Report Writer Worker
77
10
 
78
- 6. **MCP usage**: The analysis packet's `Available MCP Servers` section is canonical. If the section is absent or says none, treat MCP as unavailable for this run; never infer tools from host configuration. You may invoke packet-listed tools by name (e.g. `mcp__<server>__<tool>`) to verify evidence cited by analysis workers. Do not invent MCP tools that are not listed.
11
+ The final prompt's `report-writer` duty contract and the selected report-writer preamble are authoritative.
79
12
 
80
- ## Required Reading Before Authoring
13
+ Write only the report narrative Markdown at `**Result Path:**`, the pointer at `**Worker Result Path:**`, and the audit sidecar. You must not write or patch `final-report-*.data.json`, the approval decision ledger, activity ledger, team state, convergence state, or design-preparation input.
81
14
 
82
- Before writing the data.json, you MUST:
15
+ Read every dispatched input end-to-end. Full context is available to write the plan and explanation; reading an artifact does not grant authority to reproduce or repair its machine metadata.
83
16
 
84
- 1. Extract `**Worker Preamble Path:**` and `**Worker Error Contract Path:**`; Read the selected report-writer preamble and shared error contract end-to-end. Do not substitute the analysis or implementation preamble.
85
- 2. Read every input file the lead enumerated under `## Inputs` (or equivalent heading) end-to-end (single `Read` call with no `offset`/`limit`; page explicitly only when required).
17
+ The analysis packet is the only MCP authority. If its MCP section is absent or says none, do not infer an MCP server from the task brief or another input.
86
18
 
87
- For the report writer specifically, the `## Inputs` list always includes:
19
+ The narrative must not contain `designPreparation`, `designSurfaceCoverage`, `executionStatus`, `executionRoles`, `tokenUsage`, `crossVerification`, `approvalContext`, `clarificationItems`, `agentActivity`, or `planBodyVerification`. Do not invent a future round, gate result, activity identifier, resolution, or usage value.
88
20
 
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.
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.
91
- - `templates/reports/i18n/en.json` and `templates/reports/i18n/ko.json`.
92
- - Every analysis worker's result file under `worker-results/`.
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.
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.
21
+ **Implementation-planning direction branch.** A selected-direction narrative carries `selectedDirectionRef` and `directionRealization`; `P-Dir-1` checks that realization against the selected core mechanism, architecture boundaries, planning invariants, and any hidden direction change. A legacy candidate-comparison narrative retains `P-Opt-*` option comparison semantics.
95
22
 
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.
97
-
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.
99
-
100
- ## Authoring Contract
101
-
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.
103
-
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.
105
-
106
- ### Implementation-planning frontmatter contract
107
-
108
- #### Selected-direction
109
-
110
- Emit `frontmatter.approved` as `false` and copy `implementationPlanning.selectedDirectionRef.snapshotPath` into `frontmatter.selectedDirectionRef`. You MUST omit `frontmatter.implementationOption`; the direction was selected upstream and cannot be selected again in planning. `schemas/final-report-v2.0.schema.json` enforces the required selected-direction reference and rejects an `implementationOption` property for this branch.
111
-
112
- #### Legacy candidate-comparison
113
-
114
- Emit `frontmatter.approved` as `false` and `frontmatter.implementationOption` as the empty string `""`. The user later flips `approved` to `true` and fills `implementationOption` with the chosen Option Candidate name to authorise and scope the next `implementation` run. Every other report type follows the same empty `implementationOption` default; the schema's non-selected-direction branch requires that field and rejects a selected-direction reference.
115
-
116
- ### General authoring rules
117
-
118
- Rules (the schema enforces most of these — they are listed here so you know *what* to populate, not *how* to validate):
119
-
120
- - Read the exact permitted header values from the task bundle schema excerpt. In the current v2 contract, `header.reportOwner` is `"Okstra lead"` and `header.reportAuthor` is `"Report writer worker"`. Set author to `"Okstra lead"` only for `release-handoff` runs (single-lead by design) or a recorded report-writer dispatch failure fallback. A legacy v1 excerpt may retain its historical compatibility values; follow that excerpt rather than inferring ownership from the provider.
121
- - **Source items (worker:item) preservation.** Every `consensus[].sourceItems`, `differences[].workersPosition[].itemId`, and `evidence.primary[].sourceItems` entry MUST carry the worker:item-id pair (e.g. `claude:F-001`, `codex:1.1`, `antigravity:F-3`, or `lead:mcp-1` for lead-only evidence). The schema enforces this via the `SourceItem` regex; bare worker-name lists no longer parse.
122
- - **Verdict Card consistency.** The Card has no `verdictToken` field: the verdict token is authored once, in `finalVerdict.verdictToken`. `verdictCard.direction` MUST byte-match `finalVerdict.direction`; `validators/validate-run.py` diffs it and fails the run on divergence. `verdictCard.nextStep` names the same action as `finalVerdict.nextStep` and `recommendedNextSteps[0].text` but is written as the actionable command the reader runs (e.g. `/okstra-run task-key=… task-type=release-handoff`) where the other two are prose — it is deliberately not a byte copy.
123
- - **Error-analysis diagnosis and routing.** When `header.taskType` is `error-analysis`, populate the required `errorAnalysis` object. Copy `errorAnalysis.symptomVerbatim` byte-for-byte from the symptom stated in the brief's `Source Material`; do not paraphrase it. Every `causeCandidates[]` row includes the full `supportingEvidence`, `falsifyingEvidenceChecked`, `confidence`, and `disproveWith` fields. When a candidate is a step in a propagation chain rather than a competing explanation — the analysis calls it a downstream step, a second stage, or a consequence of another candidate — set its `downstreamOf` to the ids of the candidates immediately upstream of it; leave the field absent for a candidate that stands on its own. Every id listed MUST be another candidate in the same report, no row may name itself, and the links MUST NOT form a cycle; `validators/validate-run.py::_validate_cause_chain` rejects all three. This is the only place the chain is machine-readable — prose calling a candidate "the second step of the chain" while `downstreamOf` is absent leaves the report's figure claiming the candidates are alternatives. Route `errorAnalysis.routing.nextTaskType=implementation-option-selection` with `direction=begin-option-selection`, or route `errorAnalysis.routing.nextTaskType=error-analysis` with `direction=continue-investigation`; no other pairing is valid. `verdictCard.nextStep`, `finalVerdict.nextStep`, the first `recommendedNextSteps` action and command, and the unique `followUpTasks` row whose `origin` is `phase-continuation` MUST all point to the same `errorAnalysis.routing.nextTaskType` target. The schema enforces only the presence of a `phase-continuation` row; `validators/validate-run.py::_validate_error_analysis_consistency` enforces exact target agreement and uniqueness.
124
- - **Implementation-option-selection comparison.** When `header.taskType` is `implementation-option-selection`, populate `implementationOptionSelection` from the converged direction-selection findings. Preserve every merged or rejected raw candidate in `candidateAudit`, and put at most three selectable candidates in `rankedOptions`. Each displayed candidate carries its requirement coverage, scope commitments, criterion scores, feasibility votes, safety blockers, unresolved feasibility facts, planning invariants, and exact coverage summary. In each displayed candidate, `expectedChangeAreas` names direction-level change surfaces, never exact file paths or an exact file list. `expectedVerification` names direction-level verification signals, never a stage list or executable test commands. `schemas/final-report-v2.0.schema.json` enforces the displayed-summary constants and the three-option cap; semantic recalculation belongs to `validators/validate-run.py`.
125
- - **Implementation-planning direction branch.** For `planningContract: selected-direction`, read `selectedDirectionRef` and its snapshot, then preserve their core mechanism, architecture boundaries, and planning invariants in `directionRealization`. Materialize files, interfaces, stages, validation, rollback, and bidirectional original-requirement links without candidate generation, scoring, recommendation, or user candidate selection. Author exactly one `P-Dir-1` whose payload is the complete `directionRealization`; its verifier checks those preserved properties and any hidden direction change. If the direction must change, author `direction-invalidated` and no execution queue. Legacy candidate-comparison reruns retain Option Candidates, trade-offs, Recommended Option, and `P-Opt-*` semantics.
23
+ **Implementation-option-selection comparison.** Candidate details remain direction-level and must not claim planning precision:
126
24
 
127
25
  ```json
128
- {
129
- "candidateDetailBoundary": {
130
- "expectedChangeAreas": "direction-level-only",
131
- "expectedVerification": "direction-level-signals-only",
132
- "forbidden": ["exact-file-lists", "stage-lists", "test-commands"]
133
- }
134
- }
26
+ {"candidateDetailBoundary":{"expectedChangeAreas":"direction-level-only","expectedVerification":"direction-level-signals-only","forbidden":["exact-file-lists","stage-lists","test-commands"]}}
135
27
  ```
136
- - **Human narrative.** Populate required `humanSummary` and the selected task block's `userNarrative`. Human-visible analysis facts must not exist only in Markdown; HTML is derived independently and can use only data.json. Keep worker discussion and audit details in `crossVerification`, `executionStatus`, and `tokenUsage`, outside the human narrative fields.
137
- - **External QA advisory.** A Tier 3 entry requiring `db`, `http`, or
138
- `external` may be non-PASS without changing approval or final verdict. Render
139
- its command log row as `tier: 3`, `status: advisory`, keep the observed and
140
- expected result in `statusReason`, add a `Residual Risk` row owned by `user`,
141
- and add the exact rerun command to `recommendedNextSteps`. Never turn this
142
- advisory alone into a clarification, Acceptance Blocker, conditional
143
- acceptance condition, or blocked routing.
144
- - **§7 phase-continuation row (mandatory for non-terminal task-types).** When `header.taskType` is one of `requirements-discovery` / `implementation-option-selection` / `implementation-planning` / `error-analysis` / `implementation` / `final-verification`, `followUpTasks` MUST contain at least one row whose `origin` is `phase-continuation`, `newTaskId` reuses the current task-id, `autoSpawn` is `"no"`, and `priority` is `"P0"`. For `release-handoff` runs, omit the phase-continuation row. The schema `allOf` / `contains` clause enforces row presence, not exact route-target agreement or uniqueness; phase validation must enforce those error-analysis semantics as specified above.
145
- - **No deprecated sections.** The schema has no `4.5.8 User Approval Request` body field, no `4.5.9 Open Questions`, no `5.1 Additional Material Request`, no `5.2 User Confirmation Questions` — clarifications go under the unified `clarificationItems[]` array.
146
- - **Optional Section 0.** Include `clarificationCarryIn` ONLY when the lead's prompt provides a non-empty carry-in path. Omit the key entirely otherwise (do NOT set it to `null` or an empty object).
147
- - **Reading Confirmation** goes at `**Audit sidecar path:**` per the selected report-writer preamble's `Required reading` section — never in the data.json or the main worker-results file.
148
- - Include all four convergence categories. The schema's `crossVerification.consensus` / `.differences` arrays carry full / partial / contested / worker-unique items; do not omit any.
149
- - Convergence round history goes in `crossVerification.roundHistory.rounds[]` with `round2SkippedReason`. When convergence is disabled, set `crossVerification.roundHistory` to `{"disabled": true}`. Values come verbatim from `state/convergence-<task-type>-<seq>.json` — do not recompute.
150
- - `verification-error` votes are their own verdict (`planItems[].verdicts[].verdict` enum); they are NOT folded into AGREE / DISAGREE counts.
151
- - **Token Usage cells are `null` in Phase 6.** Leave `tokenUsage.lead.totalTokens` / `.billableTokens` / `.costUsd` (and the worker / grand rows, and per-row `executionStatus[].totalTokens` etc.) as JSON `null`. The renderer emits `--` for nulls. Phase 7's `okstra-token-usage.py --substitute-data` populates them and re-renders. **Never** write `0`, `"not-collected"`, `"--"`, or any sentinel value — those are how zeros sneak into the report.
152
- - If only one analysis worker produced usable output, perform a reduced-confidence write-up and say so explicitly (e.g. note in `executionStatus[].summary`).
153
- - If evidence is missing, write `"I don't know"` in the relevant statement field rather than fabricating confidence.
154
- - Cite file paths and line numbers in every `evidence.primary[].source` / `consensus[].evidence` cell.
155
- - Preserve every analysis worker's ticket tagging — every row's `ticketId` field carries the ticket key or the task-fallback. For single-ticket runs, set `ticketCoverage` to `{"singleTicket": "<ticket>"}`. For runs that do not require ticket tagging (`release-handoff`, `final-verification`), set `ticketCoverage` to `{"omit": true}`.
156
- - For `requirements-discovery`, `error-analysis`, and `implementation-planning`, populate the top-level `endStateCoverage` with exactly one row per end-state id the brief declares — no more, no fewer. `disposition` is one of `addressed` / `deferred` / `not-applicable` / `blocked`. `addressed` requires a `coveredBy` anchor in THIS phase's own deliverable (requirements-discovery: the routing decision, the fan-out unit id, or the `C-NNN` clarification; error-analysis: the root-cause candidate or the next diagnostic; implementation-planning: the `R-NNN` row); every other disposition requires a `rationale`. Do not author a goal of your own here and do not restate the brief — this table records only how this phase accounted for what the reporter already pinned. When the brief declares no end-state ids (a brief authored before those sections existed), omit the field entirely. **Enforced:** `validators/validate-run.py` `_validate_end_state_coverage`.
157
- - For selected-direction `implementation-planning`, preserve each original requirement ID and populate its `stageRefs`, `stepRefs`, `validationRefs`, and `fileRefs`; `validate_selected_direction_plan` enforces forward and reverse exact coverage. For legacy candidate-comparison `implementation-planning`, populate `implementationPlanning.requirementCoverage` with one row per concrete requirement from the brief / packet, using IDs `R-001`, `R-002`, ... in source order. A `covered` row's `coveredBy` MUST name the specific Option Candidate plus Stage/Step that satisfies the requirement. Use `status: "covered"` only when the report's plan actually covers it; use `documented-deviation` only when `coveredBy` states the concrete alternative and the row records non-empty unique `decisionRefs` plus `approvalDisposition`. Each `C-NNN` ref must name a clarification in this report; each `D-NNNN` ref must name a `decisionDrafts[].number`. `approvalDisposition: "accepted"` requires a referenced clarification with `status: answered|resolved` and non-empty `userInput`; `approvalDisposition: "blocked C-NNN"` requires that same-report clarification to be `status: open, blocks: approval`. Otherwise use `gap` or `blocked C-NNN` and ensure the corresponding `Clarification Items` row blocks approval. Do not collapse this into `ticketCoverage`; ticket coverage is not requirement coverage. **Enforced:** `schemas/final-report-v2.0.schema.json` `$defs.ImplementationRequirementCoverageRow` and `validators/validate-run.py` `_validate_requirement_deviations`.
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.
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).
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.
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.
162
-
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.
164
-
165
- ```
166
- **Model:** Report writer worker, <modelExecutionValue>
167
- data.json written to <abs path>. Sections populated: <count>.
168
- ```
169
-
170
- ## Error reporting
171
-
172
- Record tool failures through the file selected by `**Worker Error Contract Path:**`. If `**Errors sidecar path:**` is absent, return `REPORT_WRITER_ERRORS_PATH_MISSING` and stop; otherwise use the shared schema and append protocol exactly. This worker has no external CLI.
173
28
 
174
- ## Notes
29
+ Use `# OKSTRA Report Narrative`, nested `- **Field**` rows, `- Item N` array entries, and `> value` scalar lines. Do not write JSON, YAML, JSON Pointer, or fenced JSON.
175
30
 
176
- - You do NOT participate in convergence re-verification voting.
177
- - You do NOT produce independent analysis findings — your input is the analysis workers' results plus convergence output.
178
- - Do NOT modify analysis worker result files. They are read-only inputs to you.
179
- - If the analysis workers disagree and convergence ended with `Contested` items, surface them in the final report verbatim — do not silently pick a side.
180
- - `Contested` is a final-only classification. If you see findings labeled `Contested` in the convergence state, the lead has already exhausted re-verification — do not invent a synthesizing answer; surface each worker's position verbatim.
31
+ Report assembly validates every owner input and publishes the final record once. An assembly error naming another owner must be returned to that owner, not repaired in the narrative.
@@ -126,19 +126,20 @@ The **default is full re-verification**. Only narrow this re-run to the impacted
126
126
  --answered-clarifications <csv of answered C-NNN ids, empty when none> \
127
127
  --full-reason "<empty, or what structural change forces full>"
128
128
  ```
129
- The CLI reads the plan's dependency graph from the top-level `## 5.5 Stage Map` (`implementationPlanning.stageMap`), which is authoritative for the impacted stage numbers — there is no per-option stage graph. The CLI prints JSON `{mode, reverify_stages, carry_stages, reason}`. Instruct the report-writer to record this JSON verbatim into this run's data.json as `implementationPlanning.incrementalDecision` (keys `mode`, `reverifyStages`, `carryStages`, `reason`) — the renderer turns it into the `### 0.1 Incremental Re-Verification Scope` audit block, and the validator fails an `incremental`-mode run whose Section 0 omits that block.
129
+ The CLI reads the plan's dependency graph from the prior `implementationPlanning.stageMap`, which is authoritative for the impacted stage numbers. The CLI prints JSON `{mode, reverify_stages, carry_stages, reason}`. Instruct the report writer to record this decision in its narrative as `implementationPlanning.incrementalDecision`, using camel-case array keys `reverifyStages` and `carryStages`. Final report assembly preserves that writer-owned decision.
130
130
  4. **`mode == "full"`** → run the existing full re-verification path unchanged; ignore `reverify_stages` / `carry_stages`.
131
131
  5. **`mode == "incremental"`** → scope every worker dispatch prompt to `reverify_stages` only (the downstream closure of the impacted stages). Do NOT re-analyze `carry_stages` — their prior plan-item verdicts are carried forward verbatim (see `prompts/profiles/implementation-planning.md` "Cross-verification mode" and `prompts/lead/convergence.md` "Convergence scope").
132
- 6. **Merge carried-forward verdicts.** In `incremental` mode, after this run's report-writer authors its data.json, instruct it to merge the prior plan-item verdicts for `carry_stages` into it:
132
+ 6. **Merge carried-forward verdicts.** In `incremental` mode, the report writer includes every `carry_stages` stage row unchanged in its narrative. After `okstra plan-items seed --narrative ... --state ...`, the lead runs:
133
133
  ```
134
134
  okstra incremental-carry \
135
135
  --prev-data runs/implementation-planning/reports/final-report-implementation-planning-<prev-seq>.data.json \
136
- --cur-data <this run's data.json> \
136
+ --cur-narrative <this run's report-writer narrative> \
137
+ --state <this run's plan-body-verification state> \
137
138
  --prev-seq <prev-seq> \
138
139
  --carry-stages <csv from incrementalDecision.carry_stages> \
139
140
  --reverify-stages <csv from incrementalDecision.reverify_stages> \
140
- --out <this run's data.json>
141
+ --out-state <this run's plan-body-verification state>
141
142
  ```
142
- A non-zero exit (`CarryError` — schema drift between the two runs) means the carry is unsafe: fall back to **full** — discard the incremental result and re-verify every stage. Note: `verdictCard` / `finalVerdict` are NEVER carried — this run re-computes them from the re-verified plus carried plan items.
143
+ A non-zero exit means the writer changed or omitted a carried stage, the item set drifted, or the stage scopes conflict. Fall back to **full** and re-verify every stage. The command writes only the convergence-owned plan state. It never patches the writer narrative or final `data.json`. `verdictCard` / `finalVerdict` are never carried.
143
144
 
144
145
  **Carry completeness (BLOCKING).** In incremental mode, this run's `planItems` MUST contain every plan-item id from the re-verified stages, each carried forward with its updated verdict. If re-verification concludes a plan item should be REMOVED, that is a signal the answer's blast radius is NOT local — abandon incremental and re-route to a FULL re-verification. The carry merge only ever ADDS prior items whose id is absent from this run; it cannot distinguish a legitimate deletion from an untouched carry, so it would resurrect a stale verdict.
@@ -64,7 +64,7 @@ Use the screen to tell "still working" from "stuck", and to see at a glance whic
64
64
  - **v1 entry**: carries `workerId`, `provider`, `promptPath`, and `workerResultPath`, and must carry none of the v2 identity fields.
65
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
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.
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 its narrative Markdown, worker-result pointer, and audit sidecar; they do not include the Phase 7 report record.
68
68
  - After either dispatch, run `okstra team await --project-root <root> --run-manifest <path>` before evaluating terminal status or completion paths.
69
69
 
70
70
  ## Completion, cleanup, and resume
@@ -30,7 +30,7 @@ When this contract says "queue" without qualifier, it means the *verification qu
30
30
 
31
31
  An initial pane role `verifier` is still a Phase 4/5 analysis worker; it does not mean Phase 5.5 reverify. Only a queue-scoped dispatch whose prompt/result path carries `-reverify-r<N>-` performs the reverify step described by this contract.
32
32
 
33
- The end-to-end artifact lifecycle is worker results → Round 0 grouping → reducer-owned queue → analyser-instance re-verification → optional `okstra convergence apply-critic-gaps` transition → validated terminal state (newly finalized v1.3, or unchanged historical v1.0–v1.2 from `reuse-final`) → report-writer data.json. Cross-verification is queue-scoped: it does not mean that one worker reviews another worker's complete result. The reducer asks independent analyser instances to vote only on non-consensus findings selected by the persisted plan; the report writer never votes.
33
+ The end-to-end artifact lifecycle is worker results → Round 0 grouping → reducer-owned queue → analyser-instance re-verification → optional `okstra convergence apply-critic-gaps` transition → validated terminal state (newly finalized v1.3, or unchanged historical v1.0–v1.2 from `reuse-final`) → report-writer narrative → deterministic report assembly. Cross-verification is queue-scoped: it does not mean that one worker reviews another worker's complete result. The reducer asks independent analyser instances to vote only on non-consensus findings selected by the persisted plan; the report writer never votes.
34
34
 
35
35
  Initial and reverify worker prompts carry `**Audit sidecar path:**`. Initial workers write their reading confirmation there; reverify workers use their own canonical sidecar for the reverify session without reopening the initial worker's full reading packet.
36
36
 
@@ -71,7 +71,7 @@ Read the worker result files generated in Phase 4/5 and extract individual findi
71
71
 
72
72
  **Convergence scope.** Convergence operates on sections 1–5 of the worker output (the common core, see the worker preamble §"Worker output sections"). Section 6 ("Specialization Lens") is additive worker-specific depth and MUST NOT be fed into the consensus grouping, the verification queue, or the round-N reverify prompts. Carry Section 6 forward into the final report verbatim through the report-writer worker — do not let it inflate `unique` counts or trigger spurious `verification-error` statuses.
73
73
 
74
- **Incremental re-verification scope (implementation-planning clarification re-runs).** When the lead's `okstra incremental-scope` decision is `mode == "incremental"` (procedure in `prompts/launch.template.md` §"Clarification Response Carried In"), only findings the lead attributes to a stage in `reverify_stages` enter the verification queue. Findings and plan-item verdicts carried forward for `carry_stages` are NOT re-queued — they skip the re-verification rounds entirely and are merged verbatim into this run's data.json via `okstra incremental-carry`. When the decision is `mode == "full"` (the default), every finding enters the queue as usual.
74
+ **Incremental re-verification scope (implementation-planning clarification re-runs).** When the lead's `okstra incremental-scope` decision is `mode == "incremental"` (procedure in `prompts/launch.template.md` §"Clarification Response Carried In"), only findings the lead attributes to a stage in `reverify_stages` enter the verification queue. Findings and plan-item verdicts carried forward for `carry_stages` are NOT re-queued. The report writer preserves those stage rows in its narrative, and `okstra incremental-carry` verifies that they are unchanged before copying their prior verdicts into the convergence-owned plan state. When the decision is `mode == "full"` (the default), every finding enters the queue as usual.
75
75
 
76
76
  1. In the "Findings" section of each worker's results, identify individual items by number (F-001, F-002, ...) and parse the ticket identifier attached to each item:
77
77
  - For table-form findings, read the `Ticket ID` column.
@@ -2,7 +2,9 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- The lead orchestrates the selected AI workers against a prepared task bundle, collects their independent outputs, supervises convergence, and ensures the final report is produced. When `Report writer worker` is in the selected roster, that worker authors the final-report artifacts; the lead reviews and approves them. The lead never substitutes its own reasoning for a worker result and never bypasses a rostered report writer.
5
+ The lead orchestrates the selected AI workers against a prepared task bundle, collects their independent outputs, supervises convergence, and ensures the final report is produced. When `Report writer worker` is in the selected roster, that worker authors the report narrative Markdown; report assembly publishes the final record from role-owned inputs. The lead never substitutes its own reasoning for a worker result and never bypasses a rostered report writer.
6
+
7
+ The lead owns the approval decision ledger and writes it only through `okstra approval-decision`. The activity recorder owns the activity ledger. Runtime adapters own team state and usage. The convergence engine owns convergence state and the completed plan-body result. The design-surface detector owns the design-preparation snapshot. Report assembly validates these inputs and publishes `final-report-*.data.json` once; a failure is returned to the owner named in its diagnostic.
6
8
 
7
9
  ## When to Use
8
10
 
@@ -44,10 +46,10 @@ Read-side inspection (`/okstra-inspect`) and scheduling (`/okstra-schedule-gen`)
44
46
 
45
47
  ## Core operating contract
46
48
 
47
- - The `leader` owns orchestration, convergence supervision, and final-report review/approval. It does not author the final-report file when `Report writer worker` is in the roster. `lead` is a compatibility alias for `leader` and must not be written on new artifacts.
49
+ - The `leader` owns orchestration, convergence supervision, and final-report review. It does not author the report narrative or assembled record when `Report writer worker` is in the roster. `lead` is a compatibility alias for `leader` and must not be written on new artifacts.
48
50
  - Dispatch consumes stored role executions, not provider-named worker IDs. Canonical roles are `leader`, `analyser`, `critic`, `designer`, `planner`, `implementer`, `verifier`, `report-writer`, and `translator`. `executor` is a compatibility alias for `implementer`.
49
51
  - Pane titles and operational rows use the stored `executionLabel`. Do not rebuild that label from a provider name or model string.
50
- - `report-writer`, when in the roster, is the **author** of the final-report file. Lead reviews the draft and may request a revision via a follow-up dispatch, but MUST NOT write the report itself as a "shortcut". The only legal lead-authored fallback needs two things together: a Report writer worker dispatch that was actually attempted and recorded a terminal status of `error`/`timeout`/`not-run` with an explicit reason in team-state, **and** the user's permission recorded as a `## REPORT AUTHORING` block in this run's `user-responses/` sidecar. The failure is what lets you ask; only the user can answer. Record both in `header.leadAuthoredFallback` so the report itself carries the fact — see [report-writer](./report-writer.md) "Lead-authored fallback".
52
+ - `report-writer`, when in the roster, is the sole author of the report narrative. Lead reviews the draft and may request a revision through a follow-up dispatch, but MUST NOT edit that narrative or the assembled `data.json`. Contract v3 has no lead-authored fallback; a failed writer dispatch is retried or leaves the run blocked. Historical v2 reports may still carry `header.leadAuthoredFallback`, but no new run writes it.
51
53
  - "Session resume", "team is no longer alive", and similar are NOT valid reasons to skip Report writer worker dispatch — see [report-writer](./report-writer.md) "Resume-safe dispatch".
52
54
  - A shell command the lead runs must not be able to ask a question. The lead's shell is the user's own, where `cp`, `mv`, and `rm` are commonly aliased to their `-i` form; the confirmation that alias raises has nobody to answer it, so the call hangs until it is killed — observed as a `cp` over an existing state file stalling a whole self-fix round. Invoke these as `command cp` / `command mv` / `command rm`, which skips alias expansion and leaves the tool's own behaviour untouched. `-f` is not a substitute: it changes what the tool does on failure (`rm -f` reports success on a path that never existed).
53
55
  - If the brief is incomplete, continue with explicit uncertainty markers rather than fabricating confidence.
@@ -136,7 +138,7 @@ The sequence is fixed:
136
138
  3. On an answer — record the raw text in the row's `userInput`, set `status: answered` and `userConfirmation: asked-and-answered`, and apply the selected disposition in this run.
137
139
  4. Only when asking fails does the row stay open: `asked-awaiting` when the user has not answered, `deferred-no-interactive-session` when this run has no user to ask.
138
140
 
139
- For activity-contract-v1 `implementation-planning`, every approval row carries `approvalContext`. Classify a user-owned selection as `user-decision`, a surviving non-correctness majority disagreement as `noncritical-dissent`, and a cited path/symbol mismatch, `P-Req-*` coverage mismatch, or independent Requirement Coverage blocker as `correctness-critical`. `select` is limited to `user-decision`, `accept-risk` is limited to `noncritical-dissent`, and `request-revision` / `reject` are available to all three classifications. `correctness-critical` never offers or records `accept-risk`. **Enforced:** `validators/validate-run.py` `_validate_approval_context` recomputes the classification, disposition allowlist, activity references, and resolved-state requirements.
141
+ For report contract v3 `implementation-planning`, record active approval decisions only through `okstra approval-decision`; report assembly derives each report row's status, resolution, and backtraces from that lead-owned ledger plus the activity ledger. Classify a user-owned selection as `user-decision`, a surviving non-correctness majority disagreement as `noncritical-dissent`, and a cited path/symbol mismatch, `P-Req-*` coverage mismatch, or independent Requirement Coverage blocker as `correctness-critical`. `select` is limited to `user-decision`, `accept-risk` is limited to `noncritical-dissent`, and `request-revision` / `reject` are available to all three classifications. `correctness-critical` never offers or records `accept-risk`. Contract v2 remains read-only compatible; do not create a new v2 report. **Enforced:** `scripts/okstra_ctl/approval_decisions.py` rejects invalid option/disposition combinations, and `validators/validate-run.py` `_validate_v3_approval_context` recomputes report backtraces.
140
142
 
141
143
  The approval state transitions are fixed:
142
144
 
@@ -147,7 +149,7 @@ The approval state transitions are fixed:
147
149
 
148
150
  `open` and `answered` continue to block approval; only `resolved` and `obsolete` are non-blocking. `user-decision` resolves after the choice is applied and structure / extraction / Requirement Coverage checks pass. `noncritical-dissent` resolves only after an explicit `accept-risk` with non-empty user text and activity-backed checks. `correctness-critical` resolves only after the correction's targeted re-verification records `AGREE` or an acceptable `SUPPLEMENT` for every linked item and no independent coverage blocker remains. A user-directed correction does not consume the automatic self-fix limit, and a verification failure after that correction does not restart the automatic loop.
149
151
 
150
- When a terminal row preserves a pre-correction dissent classification, keep the superseded votes in `state/plan-body-verification-implementation-planning-<seq>.json`; the validator recomputes the historical class from those votes and never trusts `approvalContext.classification` alone. Each cited `user-decision-required` and `user-decision-evaluated` activity records the row's exact `C-NNN` in `evidenceRefs` and covers every `approvalContext.planItemIds` value; an evaluated activity also records check evidence beyond the `C-NNN` itself. A corrected coverage-only blocker keeps its `C-NNN` in the non-blocking Requirement Coverage row's `decisionRefs`, and the state-sidecar plan item without a historical blocking dissent that participated in the `coverage-gap` round keeps the same `C-NNN` in `clarificationId`; a run-wide `coverage-gap` without that item-level link is not evidence for the row. An `obsolete` row is invalid while its disagreement or coverage blocker remains active in the current plan. **Enforced:** `validators/validate-run.py` `_read_approval_history`, `_activity_matches_approval_context`, `_historical_coverage_clarification_ids`, and `_validate_approval_context`.
152
+ When a terminal row preserves a pre-correction dissent classification, keep superseded votes in `state/plan-body-verification-implementation-planning-<seq>.json`. Activities that implement or check the decision record the exact `C-NNN` in `clarificationRefs` and the affected `P-*` identifiers in `planItemIds`. Report assembly verifies that every resolution `checkRefs` value names an existing activity and derives each plan item's `clarificationRefs`; the lead never copies those references into `approvalContext`. A corrected coverage-only blocker keeps its `C-NNN` in the non-blocking Requirement Coverage row's `decisionRefs`. An `obsolete` row is invalid while its disagreement or coverage blocker remains active in the current plan. **Enforced:** `scripts/okstra_ctl/report_assembly.py` `_clarification_row` / `_attach_plan_backlinks` and `validators/validate-run.py` `_validate_v3_approval_context`.
151
153
 
152
154
  **Predicting the blocker is not the same as raising it.** A lead that says "this will likely become an approval blocker; I will ask at that point" has already reached the moment — ask then, in that message. One run announced exactly that, never asked, wrote the row anyway, and then spent its entire self-fix budget on a gate no round could clear, because the user had already answered the question before the run started.
153
155
 
@@ -346,11 +348,11 @@ If convergence is disabled, `seed`/`finalize` produce the auto-disabled final st
346
348
 
347
349
  ## Phase 6: Final report assembly
348
350
 
349
- **REQUIRED RESOURCE:** Read [report-writer](./report-writer.md) for report structure, dispatch template, resume-safe dispatch, shared-graph integrity check, and lead-authored fallback rules.
351
+ **REQUIRED RESOURCE:** Read [report-writer](./report-writer.md) for report ownership, dispatch, assembly, and Phase 7 rules.
350
352
 
351
353
  ### Authoring ownership (BLOCKING)
352
354
 
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".
355
+ If `Report writer worker` is in the selected roster (`recommendedWorkers` / `resultContract.requiredWorkerRoles`), Lead dispatches it to author only `report-writer-narrative-<task-type>-<seq>.md`, its worker-result pointer, and its audit sidecar. The worker may read the complete run context but cannot write `final-report-*.data.json` or another role's ledger. After every required input exists, Phase 7 runs report assembly, which validates the role-owned inputs and atomically publishes the report record once. Phase 7 then renders the human HTML from that record. Contract v2 artifacts remain readable but no new run writes them. **Enforced:** report-writer dispatch completion paths in `scripts/okstra_ctl/dispatch_state.py`, granted artifacts in `scripts/okstra_ctl/dispatch_core.py`, and `scripts/okstra_ctl/report_assembly.py` `assemble_report`.
354
356
 
355
357
  Before constructing the dispatch prompt, the lead MUST:
356
358
 
@@ -360,7 +362,8 @@ Before constructing the dispatch prompt, the lead MUST:
360
362
  based on its main prose language (default `en` when the brief is
361
363
  mostly code/identifiers). Pass the final `en` or `ko` value as
362
364
  `**Report Language:**` in the report-writer dispatch prompt, and ensure
363
- the worker writes the same value into `data.json.meta.reportLanguage`.
365
+ report assembly writes the same value into `data.json.meta.reportLanguage`
366
+ from the immutable run manifest.
364
367
 
365
368
  The convergence output provides four finding categories:
366
369
 
@@ -375,7 +378,7 @@ If only one worker result is usable: reduced-confidence synthesis. If evidence i
375
378
 
376
379
  ### Phase 6 sub-step: Plan-body verification (implementation-planning only, BLOCKING)
377
380
 
378
- After the Report writer worker draft is reviewed (or after the lead-authored fallback completes), **if** `task_type == "implementation-planning"` **and** `task-manifest.json` `convergence.planBodyVerification.enabled == true` (default), the lead MUST run one additional verification round on the consolidated plan body before declaring Phase 6 complete and entering Phase 7.
381
+ After the Report writer worker narrative is reviewed, **if** `task_type == "implementation-planning"` **and** `task-manifest.json` `convergence.planBodyVerification.enabled == true` (default), the lead MUST run the plan-body verification sequence on the consolidated plan body before declaring Phase 6 complete and entering Phase 7.
379
382
 
380
383
  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. The round's outcome is read from the final report's `### 5.5.9 Plan Body Verification` section and `implementationPlanning.planBodyVerification` in its data.json — it is not a separate lifecycle phase identifier.
381
384
 
@@ -391,7 +394,7 @@ Lead's responsibilities in this sub-step (in order):
391
394
 
392
395
  For a new `implementation-planning` run, the fixed order is initial verification → one planner self-fix → targeted re-verification → user gate. The initial verification is round 1 and the targeted re-verification is round 2. A second automatic self-fix is a contract violation.
393
396
 
394
- 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:
397
+ 1. Build the queue with `okstra plan-items extract --narrative <report-writer-narrative.md> --output <state>/plan-items-....json`, place the persisted `items[]` verbatim in every verifier prompt, then run `okstra plan-items validate --narrative <report-writer-narrative.md> --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:
395
398
 
396
399
  ```text
397
400
  What concrete false-positive input, failure ordering, or omitted dependency
@@ -401,23 +404,21 @@ For a new `implementation-planning` run, the fixed order is initial verification
401
404
  An `AGREE` response records the considered counterexample and exclusion reason in its note; unverified external material is `verification-error`, not `DISAGREE`.
402
405
  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
406
  3. Aggregate verdicts and resolve the gate result to one of `passed` / `passed-with-dissent` / `blocked-by-disagreement` / `aborted-non-result`.
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 full reading copy task-deliverable block carries this structure without a second prose rendering.
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
+ 4. Write `runs/<task-type>/state/plan-body-verification-<task-type>-<seq>.json`, appending each round to `roundHistory[]` and updating its nested `planBodyVerification` final projection. This state belongs to convergence and is the only plan-verification input report assembly reads.
408
+ 5. Record every surviving `majority-disagree` decision through `okstra approval-decision`; record its plan and clarification links only on activities. Do not append `clarificationItems[]` directly.
409
+ 6. Run report assembly after the final plan-body state, approval ledger, design snapshot, activity ledger, and team state are complete. Assembly writes `implementationPlanning.planBodyVerification` and derived clarification rows while publishing `data.json` once.
407
410
  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
411
 
409
412
  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
413
 
411
414
  ## Phase 7: Artifact persistence and validator handoff
412
415
 
413
- The detailed persistence checklist and the BLOCKING token-usage collector invocation live in [report-writer](./report-writer.md). Persist the run yourself — do not assume okstra saves the final artifacts for you.
416
+ The detailed persistence sequence lives in [report-writer](./report-writer.md). Drive it through `okstra report-finalize`; do not patch any final-report field manually.
414
417
 
415
418
  Order of operations:
416
419
 
417
- 1. Run `okstra agent-activity project --project-root <root> --run-manifest <path> --data <data.json>`. This deterministically projects the canonical lead-events activity rows before any prose inspection.
418
- 2. Run `okstra report-translate check-source <data.json>` even when `meta.reportLanguage` is `en`.
419
- 3. When `meta.reportLanguage` is not `en`, dispatch the translator worker. The worker builds its work list with `okstra report-translate extract`, writes `final-report-<task-type>-<seq>.i18n.<lang>.json`, and gates it with `okstra report-translate check`.
420
- 4. Run `okstra report-finalize ...`. This command owns token substitution, view rendering, follow-up persistence, and validation in their contractual order.
420
+ 1. Run `okstra report-finalize ...`. Contract v3 collects usage, assembles the role-owned inputs into `data.json` once, checks the source, renders views, persists follow-ups, validates the run, and performs eligible teardown in order.
421
+ 2. When `meta.reportLanguage` is not `en`, first run only `token-usage`, `project-activity`, and `check-source`. Dispatch the translator against that assembled record, then resume with only `render-views`, `spawn-followups`, `validate-run`, and `teardown-stages` so assembly is not repeated.
421
422
 
422
423
  Keep the assigned worker prompt history paths stable in `team-state`, `run-manifest`, and `task-manifest`. Do not rewrite prompt artifacts to `/tmp` or omit prompt metadata for attempted workers.
423
424