# Report Writer and Report Assembly Contract

## Phase 6 ownership

Phase 6 gives the report writer all settled analysis, convergence, task, and planning context required for synthesis. Read access supplies context; it does not grant write authority over another owner's file.

The report writer writes only the report narrative Markdown, its pointer record, and its audit sidecar. It must not write or patch `final-report-*.data.json`, the approval decision ledger, activity ledger, team state, convergence state, or design-preparation input.

The narrative owns judgments, plan body, summaries, user explanation, recommended steps, and follow-up descriptions. It never owns `designPreparation`, `designSurfaceCoverage`, `executionStatus`, `executionRoles`, `tokenUsage`, `crossVerification`, `approvalContext`, `clarificationItems`, `agentActivity`, or `planBodyVerification`. It must not pre-fill a future round, future gate result, activity identifier, usage value, or resolution.

Report assembly reads the role-owned inputs, validates them, derives links and statuses, and publishes the final record once. An error identifies `owner`, `artifactPath`, `fieldPath`, and `reason`; return it only to the named owner.

## Role-owned inputs

| Input | Owner | Write boundary |
|---|---|---|
| report narrative Markdown | report-writer | `reportNarrativePath` |
| approval decision ledger | lead | `approvalDecisionsPath` through `okstra approval-decision` |
| activity ledger | activity recorder | `leadEventsPath` through `okstra agent-activity append` |
| team state and usage | runtime/accounting adapter | `teamStatePath` |
| convergence state and plan-body result | convergence engine | `convergenceStatePath` |
| design-preparation snapshot | design-surface detector | `designPreparationPath` |
| final report record | report assembly | `expectedReportRecordPath` |

An active clarification exists only in `activeClarifications[]`. A decision carried from a previous run exists only in `carriedDecisions[]`; do not recreate it as an active question.

Each decision option has `role`, `answer`, `rationale`, `disposition`, `reach`, optional `scopeEffects`, `addedWork`, and `directionChange`. `reach` is exactly one of `in-repo` or `cross-repo`. `scopeEffects` may contain `new-schema` and `deferrable`. A `correctness-critical` option cannot use `select` or `accept-risk`; a `noncritical-dissent` option cannot use `select`.

Resolution `checkRefs` name existing `A-NNN` activity rows. Those activity rows carry `clarificationRefs[]`; their `planItemIds[]` let report assembly derive the reverse plan-item links. Do not store copied plan or activity identifiers in `approvalContext`.

## Report-writer dispatch

For report contract 3.0, prompt materialization first freezes one report synthesis
packet. The packet contains the task brief, analysis packet, report template,
report schema, convergence state, every successful settled worker result recorded
by the run manifest, accumulated `user-responses/` sidecars, and the current
session/token/cost accounting snapshot. Each file-backed source carries its owner,
project-relative path, SHA-256 digest, and value. The report writer receives the
packet's Markdown reading projection as the only task input instead of an
independently assembled list of raw paths.

The packet's authoring contract names the result path, narrative format, writing
instructions, runtime-owned content, and validation rules. Missing configured
paths and missing files are collected across the full source set before dispatch
and reported together with their owners. Report assembly compares every frozen
source digest again and reports every changed or missing source in one result.
This is enforced by
`initial_prompt_materialization._materialize_report_writer_packet()`,
`report_synthesis_packet.build_report_synthesis_packet()`, and
`report_assembly.assemble_report()`.

Materialize the duty prompt with `okstra agent-prompt materialize --audience report-writer`. The prompt starts with these anchors in order:

1. `**Project Root:**`
2. `**Prompt History Path:**`
3. `**Result Path:**`
4. `**Worker Result Path:**`
5. `**Worker Preamble Path:**`
6. `**Worker Error Contract Path:**`
7. `**Errors log path:**`
8. `**Errors sidecar path:**`

The errors sidecar anchor reserves the runtime-owned write-artifact path used by dispatch validation. No model-authored error JSON file is part of report-writer dispatch; failures use the typed error-log command from the worker error contract.

Register the dispatch with `okstra agent-prompt record-dispatch`. When `terminalBackend` is `cmux-pane`, run `okstra team dispatch`; for `runner: cli-wrapper`, run `okstra worker-dispatch --audience report-writer`. Attach the result through `okstra agent-prompt link-result`. The result path is the report narrative Markdown, not the final record.

The pointer record contains the narrative and audit paths. Completion never depends on the final record because assembly runs after writer completion.

## Implementation-planning sequence

1. Dispatch the report writer and wait for the narrative and pointer.
2. Parse the narrative and extract the deterministic plan-item queue without publishing `data.json`. `okstra plan-items prepare` flips `convergence.planBodyVerification.gating` to `false` when the detector reports `no-design-inputs` and the Stage Map has one row.
3. Run initial plan-body verification as round 1.
4. Apply at most one automatic planner self-fix to the narrative. Skip this step when `gating` is `false`.
5. Run targeted re-verification as round 2 when needed. Skip this step when `gating` is `false`.
6. Persist the completed `planBodyVerification` value in convergence state.
7. Complete the design-surface detector snapshot.
8. Run Phase 7 report assembly.

The writer never predicts steps 3–7 in its draft. The plan-body verifier reads the plan body, and report assembly adds its completed result afterward.

**Implementation-planning direction branch.** A selected-direction narrative carries `selectedDirectionRef` and `directionRealization`; the `P-Dir-1` verifier 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. These are plan-body judgments, not frontmatter fields owned by the writer.

**Implementation-option-selection comparison.** Candidate details remain direction-level and must not claim planning precision:

```json
{"candidateDetailBoundary":{"expectedChangeAreas":"direction-level-only","expectedVerification":"direction-level-signals-only","forbidden":["exact-file-lists","stage-lists","test-commands"]}}
```

For historical schema-v1 Markdown only, the following heading table remains a read-side compatibility reference. It is not an authoring instruction for report contract 3.0.

| # | Required substring | Recommended heading form |
|---:|---|---|

## Phase 6 → Phase 7 execution sequence (BLOCKING order)

Do not run the seven steps below manually. Invoke `okstra report-finalize`; contract 3.0 runs them in this order:

1. **`token-usage`** — collect usage into team state without touching the final record.
2. **`project-activity`** — report assembly validates every owner input and publishes the final record once.
3. **`check-source`** — verify the assembled English source.
4. **`render-views`** — render the Markdown reading copy and human HTML.
5. **`spawn-followups`** — materialize registered follow-up tasks.
6. **`validate-run`** — validate the record, views, run manifest, and team state.
7. **`teardown-stages`** — remove eligible stage worktrees after successful validation.

After `report-finalize` returns, the lead — not the report writer — closes the run with the launch prompt's User closeout: one command the user can run now.

### Before `report-finalize`: the translation sidecar

Never dispatch the translator before report assembly and `check-source`. For a non-English human report, first run `report-finalize --only token-usage --only project-activity --only check-source`; the extraction command refuses to build a work list from a non-English source. Then dispatch the translator worker with `okstra agent-prompt materialize --audience translator`, `okstra agent-prompt record-dispatch`, `okstra worker-dispatch --audience translator`, and `okstra agent-prompt link-result`. Resume with `report-finalize --only render-views --only spawn-followups --only validate-run --only teardown-stages`; do not assemble the record a second time.

## Routing pointer

`workflow.nextRecommendedPhase` has `phase`, `status`, and `rationale`. The status vocabulary is `ready`, `pending`, `blocked`, and `terminal`.

`phase` is non-empty only for an authored `ready` pointer. `prepare` may lower `ready` to `pending` while retaining `phase` in `scripts/okstra_ctl/render.py::_derive_next_recommended_phase`; readers must use `status` for launchability. When `finalVerification.routingRecommendation.target` is `release-handoff(stage-group)`, write `phase` as `release-handoff`. When `implementationPlanning.outcome` is `plan-ready` and a `Blocks=approval` row still blocks progress (`open`, or an unincorporated `request-revision` / `reject`), or the plan-body gate is `aborted-non-result`, write `status` `blocked` (empty `phase`). `blocked-by-disagreement` with every approval row user-proceeded (`accept-risk` / `select` / `answer`) or incorporated in `supersessionLedger` is approvable — write `ready` at `implementation`. The user still has to approve it separately.

## Compatibility

Contract 2.0 reports remain readable. They are never migrated or rewritten by report assembly. New writes use contract 3.0 only.
