okstra 0.201.3 → 0.204.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 (273) hide show
  1. package/README.md +3 -3
  2. package/dist/cli-registry.mjs +7 -7
  3. package/dist/cli-registry.mjs.map +1 -1
  4. package/dist/commands/lifecycle/install.mjs +50 -124
  5. package/dist/commands/lifecycle/install.mjs.map +1 -1
  6. package/dist/commands/lifecycle/setup.mjs +15 -0
  7. package/dist/commands/lifecycle/setup.mjs.map +1 -1
  8. package/dist/commands/memory/memory.mjs +41 -8
  9. package/dist/commands/memory/memory.mjs.map +1 -1
  10. package/dist/lib/citation-guidance.d.mts +21 -0
  11. package/dist/lib/citation-guidance.mjs +79 -0
  12. package/dist/lib/citation-guidance.mjs.map +1 -0
  13. package/dist/lib/install-assets.mjs +3 -0
  14. package/dist/lib/install-assets.mjs.map +1 -1
  15. package/dist/lib/runtime-manifest.mjs +2 -1
  16. package/dist/lib/runtime-manifest.mjs.map +1 -1
  17. package/dist/lib/types.d.mts +2 -1
  18. package/docs/architecture/storage-model.md +17 -10
  19. package/docs/architecture.md +26 -20
  20. package/docs/cli.md +16 -13
  21. package/docs/contributor-change-matrix.md +3 -2
  22. package/docs/performance-improvement-plan-v2.md +2 -3
  23. package/docs/project-structure-overview.md +38 -9
  24. package/docs/task-process/README.md +1 -1
  25. package/docs/task-process/common-flow.md +1 -1
  26. package/docs/task-process/final-verification.md +3 -1
  27. package/docs/task-process/implementation.md +1 -1
  28. package/docs/task-process/release-handoff.md +36 -39
  29. package/package.json +1 -2
  30. package/runtime/BUILD.json +2 -2
  31. package/runtime/agents/common.json +28 -0
  32. package/runtime/agents/operations/code-review.json +6 -0
  33. package/runtime/agents/operations/report-translation.json +6 -0
  34. package/runtime/agents/operations/schedule-verification.json +6 -0
  35. package/runtime/agents/roles/analyser.json +18 -0
  36. package/runtime/agents/roles/critic.json +18 -0
  37. package/runtime/agents/roles/designer.json +18 -0
  38. package/runtime/agents/roles/implementer.json +20 -0
  39. package/runtime/agents/roles/leader.json +20 -0
  40. package/runtime/agents/roles/planner.json +18 -0
  41. package/runtime/agents/roles/report-writer.json +19 -0
  42. package/runtime/agents/roles/translator.json +19 -0
  43. package/runtime/agents/roles/verifier.json +18 -0
  44. package/runtime/bin/lib/okstra/usage.sh +5 -5
  45. package/runtime/prompts/duties/acceptance-critic.json +32 -0
  46. package/runtime/prompts/duties/acceptance-verifier.json +32 -0
  47. package/runtime/prompts/duties/analysis-worker.json +32 -0
  48. package/runtime/prompts/duties/code-reviewer.json +32 -0
  49. package/runtime/prompts/duties/diagnosis-worker.json +32 -0
  50. package/runtime/prompts/duties/direction-selection-worker.json +32 -0
  51. package/runtime/prompts/duties/discovery-worker.json +32 -0
  52. package/runtime/prompts/duties/implementation-executor.json +32 -0
  53. package/runtime/prompts/duties/implementation-verifier.json +32 -0
  54. package/runtime/prompts/duties/lead.json +32 -0
  55. package/runtime/prompts/duties/planning-worker.json +36 -0
  56. package/runtime/prompts/duties/report-writer.json +32 -0
  57. package/runtime/prompts/duties/reverification-worker.json +32 -0
  58. package/runtime/prompts/duties/schedule-verifier.json +32 -0
  59. package/runtime/prompts/duties/scope-critic.json +32 -0
  60. package/runtime/prompts/duties/technical-verification-worker.json +32 -0
  61. package/runtime/prompts/duties/translator.json +32 -0
  62. package/runtime/prompts/launch.template.md +3 -2
  63. package/runtime/prompts/lead/adapters/cmux.md +1 -1
  64. package/runtime/prompts/lead/convergence.md +4 -4
  65. package/runtime/prompts/lead/okstra-lead-contract.md +115 -6
  66. package/runtime/prompts/lead/plan-body-verification.md +6 -6
  67. package/runtime/prompts/lead/report-writer.md +3 -3
  68. package/runtime/prompts/profiles/_common-contract.md +2 -2
  69. package/runtime/prompts/profiles/_implementation-executor.md +4 -1
  70. package/runtime/prompts/profiles/_implementation-verifier.md +3 -3
  71. package/runtime/prompts/profiles/change-impact-analysis.json +31 -0
  72. package/runtime/prompts/profiles/change-impact-analysis.md +0 -20
  73. package/runtime/prompts/profiles/error-analysis.json +39 -0
  74. package/runtime/prompts/profiles/error-analysis.md +0 -25
  75. package/runtime/prompts/profiles/feature-analysis.json +31 -0
  76. package/runtime/prompts/profiles/feature-analysis.md +0 -20
  77. package/runtime/prompts/profiles/final-verification.json +30 -0
  78. package/runtime/prompts/profiles/final-verification.md +3 -22
  79. package/runtime/prompts/profiles/forbidden-actions.json +4 -3
  80. package/runtime/prompts/profiles/implementation-option-selection.json +31 -0
  81. package/runtime/prompts/profiles/implementation-option-selection.md +0 -20
  82. package/runtime/prompts/profiles/implementation-planning.json +40 -0
  83. package/runtime/prompts/profiles/implementation-planning.md +6 -29
  84. package/runtime/prompts/profiles/implementation.json +30 -0
  85. package/runtime/prompts/profiles/implementation.md +1 -20
  86. package/runtime/prompts/profiles/improvement-discovery.json +31 -0
  87. package/runtime/prompts/profiles/improvement-discovery.md +0 -20
  88. package/runtime/prompts/profiles/project-analysis.json +31 -0
  89. package/runtime/prompts/profiles/project-analysis.md +0 -20
  90. package/runtime/prompts/profiles/release-handoff.json +5 -0
  91. package/runtime/prompts/profiles/release-handoff.md +71 -73
  92. package/runtime/prompts/profiles/requirements-discovery.json +39 -0
  93. package/runtime/prompts/profiles/requirements-discovery.md +0 -25
  94. package/runtime/prompts/profiles/technical-verification.json +39 -0
  95. package/runtime/prompts/profiles/technical-verification.md +0 -25
  96. package/runtime/prompts/wizard/prompts.ko.json +12 -17
  97. package/runtime/python/okstra_ctl/adapters/hosts/antigravity/relay.md +1 -0
  98. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +3 -0
  99. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/manifest.json +1 -1
  100. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -3
  101. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/worker-session.md +108 -0
  102. package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +1 -0
  103. package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +2 -0
  104. package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +2 -0
  105. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +8 -1
  106. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +8 -0
  107. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +23 -6
  108. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +6 -2
  109. package/runtime/python/okstra_ctl/agent/invocation.py +168 -113
  110. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +120 -0
  111. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +107 -2
  112. package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +0 -49
  113. package/runtime/python/okstra_ctl/analysis_packet.py +4 -1
  114. package/runtime/python/okstra_ctl/application/open_worker.py +6 -1
  115. package/runtime/python/okstra_ctl/assignment_resolver.py +16 -5
  116. package/runtime/python/okstra_ctl/cmux.py +69 -20
  117. package/runtime/python/okstra_ctl/code_review_target.py +16 -8
  118. package/runtime/python/okstra_ctl/conformance.py +43 -0
  119. package/runtime/python/okstra_ctl/consumers.py +6 -3
  120. package/runtime/python/okstra_ctl/container.py +31 -8
  121. package/runtime/python/okstra_ctl/context_cost.py +11 -15
  122. package/runtime/python/okstra_ctl/contract_refreeze.py +156 -0
  123. package/runtime/python/okstra_ctl/convergence_critic_prompt.py +4 -6
  124. package/runtime/python/okstra_ctl/convergence_provenance.py +81 -18
  125. package/runtime/python/okstra_ctl/design_prep.py +34 -1
  126. package/runtime/python/okstra_ctl/dispatch_core.py +53 -27
  127. package/runtime/python/okstra_ctl/domain/host.py +5 -0
  128. package/runtime/python/okstra_ctl/domain/worker_runtime.py +10 -0
  129. package/runtime/python/okstra_ctl/error_report.py +4 -3
  130. package/runtime/python/okstra_ctl/execution_manifest.py +71 -18
  131. package/runtime/python/okstra_ctl/execution_mutation_audit.py +21 -21
  132. package/runtime/python/okstra_ctl/handoff.py +167 -277
  133. package/runtime/python/okstra_ctl/implementation_stage.py +9 -0
  134. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +113 -0
  135. package/runtime/python/okstra_ctl/lead_progress.py +1 -1
  136. package/runtime/python/okstra_ctl/legacy_model_selection.py +2 -2
  137. package/runtime/python/okstra_ctl/manager_cli.py +175 -14
  138. package/runtime/python/okstra_ctl/manager_launch.py +41 -19
  139. package/runtime/python/okstra_ctl/manager_paths.py +22 -3
  140. package/runtime/python/okstra_ctl/manager_split.py +474 -0
  141. package/runtime/python/okstra_ctl/manager_store.py +331 -21
  142. package/runtime/python/okstra_ctl/manager_sync.py +37 -16
  143. package/runtime/python/okstra_ctl/manager_view.py +217 -0
  144. package/runtime/python/okstra_ctl/model_discovery.py +30 -0
  145. package/runtime/python/okstra_ctl/model_io/lines.py +14 -1
  146. package/runtime/python/okstra_ctl/model_io/renderers.py +4 -3
  147. package/runtime/python/okstra_ctl/models.py +1 -1
  148. package/runtime/python/okstra_ctl/next_phase.py +16 -6
  149. package/runtime/python/okstra_ctl/operation_invocation.py +86 -0
  150. package/runtime/python/okstra_ctl/option_comparison.py +168 -0
  151. package/runtime/python/okstra_ctl/path_hints.py +9 -0
  152. package/runtime/python/okstra_ctl/paths.py +3 -0
  153. package/runtime/python/okstra_ctl/plan_items_cli.py +6 -1
  154. package/runtime/python/okstra_ctl/profile_show.py +42 -1
  155. package/runtime/python/okstra_ctl/qa_commands.py +15 -0
  156. package/runtime/python/okstra_ctl/registry/host_discovery.py +20 -12
  157. package/runtime/python/okstra_ctl/registry/host_registry.py +11 -0
  158. package/runtime/python/okstra_ctl/render.py +50 -0
  159. package/runtime/python/okstra_ctl/report_contract.py +1 -1
  160. package/runtime/python/okstra_ctl/report_finalize.py +13 -6
  161. package/runtime/python/okstra_ctl/report_html/view_models/final_verification.py +2 -21
  162. package/runtime/python/okstra_ctl/report_html/view_models/release_handoff.py +21 -3
  163. package/runtime/python/okstra_ctl/report_html/visualizations.py +0 -5
  164. package/runtime/python/okstra_ctl/report_synthesis_packet.py +177 -17
  165. package/runtime/python/okstra_ctl/report_translation.py +2 -1
  166. package/runtime/python/okstra_ctl/report_translation_dispatch.py +69 -9
  167. package/runtime/python/okstra_ctl/role_requirements.py +142 -129
  168. package/runtime/python/okstra_ctl/rollup.py +3 -1
  169. package/runtime/python/okstra_ctl/run.py +76 -29
  170. package/runtime/python/okstra_ctl/schedule_semantics.py +17 -6
  171. package/runtime/python/okstra_ctl/stage_fix_carry.py +23 -4
  172. package/runtime/python/okstra_ctl/stage_integrate.py +178 -18
  173. package/runtime/python/okstra_ctl/stage_map.py +16 -2
  174. package/runtime/python/okstra_ctl/stage_targets.py +209 -43
  175. package/runtime/python/okstra_ctl/team.py +22 -13
  176. package/runtime/python/okstra_ctl/time_report.py +2 -1
  177. package/runtime/python/okstra_ctl/usage_report.py +3 -1
  178. package/runtime/python/okstra_ctl/verification_target.py +13 -2
  179. package/runtime/python/okstra_ctl/wizard/confirmation.py +3 -9
  180. package/runtime/python/okstra_ctl/wizard/ids.py +1 -1
  181. package/runtime/python/okstra_ctl/wizard/registry.py +1 -1
  182. package/runtime/python/okstra_ctl/wizard/state.py +3 -5
  183. package/runtime/python/okstra_ctl/wizard/steps_plan.py +3 -23
  184. package/runtime/python/okstra_ctl/worker_prompt_contract.py +5 -1
  185. package/runtime/python/okstra_ctl/worker_prompt_headers.py +35 -7
  186. package/runtime/python/okstra_ctl/worker_prompt_policy.py +66 -48
  187. package/runtime/python/okstra_ctl/workflow.py +1 -1
  188. package/runtime/python/okstra_ctl/worktree/__init__.py +3 -1
  189. package/runtime/python/okstra_ctl/worktree/naming.py +9 -0
  190. package/runtime/python/okstra_ctl/worktree_registry.py +38 -9
  191. package/runtime/python/okstra_token_usage/pricing.py +6 -4
  192. package/runtime/schemas/agent-common-v1.schema.json +34 -0
  193. package/runtime/schemas/agent-duty-v1.schema.json +38 -0
  194. package/runtime/schemas/agent-operation-v1.schema.json +11 -0
  195. package/runtime/schemas/agent-profile-v1.schema.json +46 -0
  196. package/runtime/schemas/agent-role-v1.schema.json +29 -0
  197. package/runtime/schemas/final-report-v2.0.schema.json +118 -97
  198. package/runtime/schemas/final-report-v3.0.schema.json +118 -97
  199. package/runtime/skills/okstra-brief-gen/SKILL.md +84 -4
  200. package/runtime/skills/okstra-chat/SKILL.md +2 -2
  201. package/runtime/skills/okstra-code-review/SKILL.md +23 -9
  202. package/runtime/skills/okstra-container-build/SKILL.md +10 -10
  203. package/runtime/skills/okstra-inspect/SKILL.md +1 -1
  204. package/runtime/skills/okstra-inspect/facets/cost.md +1 -1
  205. package/runtime/skills/okstra-inspect/facets/error-zip.md +9 -9
  206. package/runtime/skills/okstra-inspect/facets/errors.md +16 -16
  207. package/runtime/skills/okstra-inspect/facets/logs.md +7 -7
  208. package/runtime/skills/okstra-inspect/facets/recap.md +2 -2
  209. package/runtime/skills/okstra-inspect/facets/report.md +1 -1
  210. package/runtime/skills/okstra-inspect/facets/status.md +4 -3
  211. package/runtime/skills/okstra-inspect/facets/time.md +11 -10
  212. package/runtime/skills/okstra-manager/SKILL.md +70 -5
  213. package/runtime/skills/okstra-pr-gen/SKILL.md +6 -5
  214. package/runtime/skills/okstra-rollup/SKILL.md +5 -5
  215. package/runtime/skills/okstra-run/SKILL.md +32 -13
  216. package/runtime/skills/okstra-schedule-gen/SKILL.md +19 -14
  217. package/runtime/skills/okstra-setup/SKILL.md +21 -10
  218. package/runtime/skills/okstra-setup/references/project-config.md +7 -6
  219. package/runtime/skills/okstra-usage/SKILL.md +1 -1
  220. package/runtime/skills/okstra-user-response/SKILL.md +1 -1
  221. package/runtime/templates/manager/view.template.html +109 -0
  222. package/runtime/templates/report-writer-prompt-preamble.md +8 -0
  223. package/runtime/templates/reports/brief.template.md +14 -4
  224. package/runtime/templates/reports/html/i18n/en.json +7 -4
  225. package/runtime/templates/reports/html/i18n/ko.json +7 -4
  226. package/runtime/templates/reports/html/tasks/final-verification.template.html +2 -2
  227. package/runtime/templates/reports/html/tasks/release-handoff.template.html +8 -5
  228. package/runtime/templates/reports/i18n/en.json +1 -1
  229. package/runtime/templates/reports/md/tasks/release-handoff.template.md +1 -1
  230. package/runtime/templates/reports/release-handoff-input.template.md +6 -4
  231. package/runtime/templates/translator-prompt-preamble.md +36 -0
  232. package/runtime/validators/checks/validate-assets-01.py +7 -8
  233. package/runtime/validators/validate-brief.py +77 -2
  234. package/runtime/validators/validate-implementation-plan-stages.py +2 -1
  235. package/runtime/validators/validate-run.py +59 -9
  236. package/runtime/validators/validate-schedule.py +9 -0
  237. package/docs/for-ai/README.md +0 -68
  238. package/docs/for-ai/skills/okstra-brief-gen.md +0 -262
  239. package/docs/for-ai/skills/okstra-chat.md +0 -34
  240. package/docs/for-ai/skills/okstra-code-review.md +0 -57
  241. package/docs/for-ai/skills/okstra-container-build.md +0 -129
  242. package/docs/for-ai/skills/okstra-inspect.md +0 -262
  243. package/docs/for-ai/skills/okstra-manager.md +0 -69
  244. package/docs/for-ai/skills/okstra-memory.md +0 -126
  245. package/docs/for-ai/skills/okstra-pr-gen.md +0 -49
  246. package/docs/for-ai/skills/okstra-rollup.md +0 -114
  247. package/docs/for-ai/skills/okstra-run.md +0 -250
  248. package/docs/for-ai/skills/okstra-schedule-gen.md +0 -240
  249. package/docs/for-ai/skills/okstra-setup.md +0 -158
  250. package/docs/for-ai/skills/okstra-usage.md +0 -29
  251. package/docs/for-ai/skills/okstra-user-response.md +0 -72
  252. package/runtime/agents/workers/claude-worker.md +0 -128
  253. package/runtime/agents/workers/report-writer-worker.md +0 -37
  254. package/runtime/agents/workers/translator-worker.md +0 -63
  255. package/runtime/prompts/duties/acceptance-critic.md +0 -44
  256. package/runtime/prompts/duties/acceptance-verifier.md +0 -44
  257. package/runtime/prompts/duties/analysis-worker.md +0 -44
  258. package/runtime/prompts/duties/code-reviewer.md +0 -44
  259. package/runtime/prompts/duties/common.md +0 -39
  260. package/runtime/prompts/duties/diagnosis-worker.md +0 -44
  261. package/runtime/prompts/duties/direction-selection-worker.md +0 -44
  262. package/runtime/prompts/duties/discovery-worker.md +0 -44
  263. package/runtime/prompts/duties/implementation-executor.md +0 -44
  264. package/runtime/prompts/duties/implementation-verifier.md +0 -44
  265. package/runtime/prompts/duties/lead.md +0 -44
  266. package/runtime/prompts/duties/planning-worker.md +0 -52
  267. package/runtime/prompts/duties/report-writer.md +0 -44
  268. package/runtime/prompts/duties/reverification-worker.md +0 -44
  269. package/runtime/prompts/duties/schedule-verifier.md +0 -44
  270. package/runtime/prompts/duties/scope-critic.md +0 -44
  271. package/runtime/prompts/duties/technical-verification-worker.md +0 -44
  272. package/runtime/prompts/duties/translator.md +0 -44
  273. package/runtime/python/okstra_ctl/pane_title.py +0 -154
@@ -44,6 +44,111 @@ Read-side inspection (`/okstra-inspect`) and scheduling (`/okstra-schedule-gen`)
44
44
  | 6. Synthesis | Dispatch Report writer worker, review draft. **For `implementation-planning`: then run the Phase 6 plan-body verification sub-step (see Phase 6 section below). Selected-direction plans verify `P-Dir-1`; legacy plans retain `P-Opt-*`.** | `report-writer` + `plan-body-verification` (sub-step) |
45
45
  | 7. Persist | Call `collect_usage`, update manifests, run the cleanup approval gate, then call `shutdown_workers` only on approval | selected runtime adapter + `report-writer` + this contract |
46
46
 
47
+ ## Command map
48
+
49
+ Every `okstra` command the lead documents cite, grouped by phase, each spelled with every argument its parser requires. Replace each `<placeholder>` with the value the named artifact gives; do not guess a flag that is not listed — run `okstra <command> --help` instead. The table locates a command; the Procedure column names the section that owns when and how to run it, and that section wins where the two differ. **Enforced:** `tests/contract/test_lead_command_map.py` checks every row, and every `okstra` example in the lead documents, against the CLI's own argument parser.
50
+
51
+ ### Phase 1 — intake and re-run scope
52
+
53
+ | Command | Use when | Procedure |
54
+ |---|---|---|
55
+ | `okstra model-io project-context --project-root <dir>` | Resolve the task key, task manifest, and latest run manifest; add `--task-ref <id-or-key>` for an explicit task | `context-loader` "Step 1" |
56
+ | `okstra model-io run-input --run-manifest <path>` | Read run identity, worker roster, model assignments, and artifact paths instead of opening the run manifest | `context-loader` "Step 3" |
57
+ | `okstra model-io active-context-input --project-root <dir> --run-manifest <path>` | Read the prior executor base ref for an incremental re-run | launch prompt "Incremental re-verification" |
58
+ | `okstra incremental-scope --prev-data <path>` | Decide re-verify vs carry-forward stages on a clarification re-run | launch prompt "Incremental re-verification" |
59
+ | `okstra incremental-carry --prev-data <path> --prev-seq <seq> --cur-narrative <path>` | Merge carried-forward plan-item verdicts after `plan-items seed` | launch prompt "Incremental re-verification" |
60
+ | `okstra stage-map <task-key>` | Read the prior Stage Map and done stages before re-planning | host orchestration rules (implementation-planning) |
61
+ | `okstra stage-close <task-key> --stage <N> --from-commit <sha>` | Close a stage whose work already landed outside okstra | host orchestration rules (implementation-planning) |
62
+ | `okstra git-reconcile --plan-run-root <dir> --project-id <id> --task-group <group> --task-id <id> --work-category <category>` | Detect (`--check --text`) or record (`--apply --stage <N> --use-ref <ref>`) stage SHAs made stale by git history outside okstra | host orchestration rules (implementation) |
63
+
64
+ ### Any phase — progress, activity, errors, approval
65
+
66
+ | Command | Use when | Procedure |
67
+ |---|---|---|
68
+ | `okstra lead-progress append --project-root <dir> --run-manifest <path> --phase <phase-id>` | Every `PROGRESS` checkpoint; print the `progressLine` it returns. `--phase` accepts only the listed phase ids; use `--worker`, `--field NAME=VALUE`, and `--detail <text>` for the rest | this contract "Progress reporting" |
69
+ | `okstra agent-activity append --project-root <dir> --run-manifest <path> --kind <kind> --agent <agent> --outcome <outcome> --summary <text>` | Before each activity boundary when the run manifest declares `activityContractVersion: 1` | launch prompt "Progress reporting" |
70
+ | `okstra error-log append-observed --out <errors-path> --task-key <key> --phase <phase> --agent <provider-worker> --agent-role <role> --model <model> --error-type <type> --command-kind <kind> --command <command> --message <text>` | Record an observed failure. `--agent` takes the provider worker id (`codex-worker`); an execution label goes in `--execution-label` | this contract "Errors log path wiring"; `team-contract` "Worker error-log command" |
71
+ | `okstra approval-decision open --ledger <path> --task-key <key> --task-type <type> --run-seq <seq> --clarification-id <C-NNN> --ticket-id <id> --statement <text> --expected-form <form> --classification <class> --origin <origin> --user-confirmation <text> --unblock-condition <text> --recommended-disposition <disposition>` | Open an approval blocker after the user confirmed it | this contract "User confirmation before an approval blocker" |
72
+ | `okstra approval-decision carry --ledger <path> --clarification-id <C-NNN> --from-responses <path>` | Carry a decision answered in an earlier run | `report-writer` "Role-owned inputs" |
73
+ | `okstra approval-decision resolve --ledger <path> --clarification-id <C-NNN> --disposition <disposition> --user-text <text> --user-response-ref <ref>` | Record the user's disposition of an approval row; it touches no verdict | `plan-body-verification` "Round protocol" |
74
+ | `okstra user-response show --report <path>` | Not a lead step: the `okstra-user-response` skill reads approval rows through it, so an `origin` outside the schema enum breaks every later read | `plan-body-verification` "Round protocol" |
75
+
76
+ ### Phase 2–4 — prompts and dispatch
77
+
78
+ | Command | Use when | Procedure |
79
+ |---|---|---|
80
+ | `okstra agent-prompt materialize --project-root <dir> --invocation-id <id> --audience <audience> --instruction <path> --prompt <path>` | Materialize every worker prompt; the command owns the anchor headers and paths | `convergence` "Invocation materialization gate"; `report-writer` "Report-writer dispatch" |
81
+ | `okstra agent-prompt verify --project-root <dir> --metadata <path>` | Immediately before each dispatch | `convergence` "Invocation materialization gate" |
82
+ | `okstra agent-prompt record-dispatch --project-root <dir> --run-manifest <path> --metadata <path> --enforcement-mode <mode>` | Record a native-session dispatch | `convergence` "Invocation materialization gate" |
83
+ | `okstra agent-prompt jobs --project-root <dir> --run-manifest <path> --dispatch-kind <kind> --metadata <path> --out <jobs-file>` | Build a verified jobs file for `okstra team dispatch` | cmux adapter "cmux dispatch details" |
84
+ | `okstra team dispatch --project-root <dir> --run-manifest <path>` | Dispatch pane-backed workers (`terminalBackend` is `cmux-pane`) | cmux adapter "Semantic operation mapping" |
85
+ | `okstra worker-dispatch --project-root <dir> --run-manifest <path>` | Dispatch CLI-backed workers when `terminalBackend` is not `cmux-pane` | this contract "Model assignments" |
86
+ | `okstra codex-dispatch --project-root <dir> --run-manifest <path>` | CLI-backed dispatch for a prepared Codex run; never under the cmux adapter | cmux adapter "cmux dispatch details" |
87
+
88
+ ### Phase 5 — await and collect
89
+
90
+ | Command | Use when | Procedure |
91
+ |---|---|---|
92
+ | `okstra team await --project-root <dir> --run-manifest <path>` | Wait for pane-backed workers | cmux adapter "Semantic operation mapping" |
93
+ | `okstra worker-liveness --team-state <path> --dispatch-id <id>` | Probe a pending worker; add `--wait` to block until result, death, or timeout | `team-contract` "Mid-run liveness probes" |
94
+ | `okstra agent-prompt link-result --project-root <dir> --run-manifest <path> --dispatch-id <id> --result <path>` | Link a returned result to its dispatch | `convergence` "Invocation materialization gate" |
95
+ | `okstra agent-prompt reject-result --project-root <dir> --run-manifest <path> --dispatch-id <id> --superseded-by <id> --reason <text>` | Retire a linked result before a corrective dispatch links its own | `plan-body-verification` "Round protocol" |
96
+ | `okstra worker-audit-check --run-dir <path> --task-type <type> --seq <seq>` | Check a live worker's audit sidecars and citations before accepting its result; add `--worker <id>` | `team-contract` "Lead Redispatch Policy on Result-Missing" |
97
+ | `okstra team reclaim --project-root <dir> --run-manifest <path>` | Close finished dispatches' panes at a batch boundary | this contract "Progress reporting" |
98
+
99
+ ### Phase 5.5 / 5.6 — convergence and critic
100
+
101
+ | Command | Use when | Procedure |
102
+ |---|---|---|
103
+ | `okstra convergence example --kind <kind>` | Print a valid input example before writing a groups, round-results, or critic-results file | `convergence` "Round 1-N" |
104
+ | `okstra convergence prepare-groups --run-manifest <path> --input <path>` | Publish Round 0 grouped findings | `convergence` "Round 0" |
105
+ | `okstra convergence seed --groups <path> --work-state <path> --final-state <path> --migration-dir <dir>` | Create, resume, or recover the working state | `convergence` "Round 0" |
106
+ | `okstra convergence plan-round --work-state <path> --plan <path>` | Write the next round's dispatch plan | `convergence` "Round 1-N" |
107
+ | `okstra convergence reverify-prompt --run-manifest <path> --plan <path> --worker <worker>` | Print one worker's reverify instructions; never compose them by hand | `convergence` "Re-verification Dispatch" |
108
+ | `okstra convergence collect-results --plan <path> --mode <mode> --run-manifest <path> --output <path>` | Read one round's responses into the apply-round input | `convergence` "Round 1-N" |
109
+ | `okstra convergence apply-round --work-state <path> --plan <path> --results <path>` | Apply a round's results | `convergence` "Round 1-N" |
110
+ | `okstra convergence critic-prompt --run-manifest <path>` | Print the coverage-critic instructions; dispatch concurrently with the first reverify round | `convergence` "Coverage critic pass" |
111
+ | `okstra convergence critic-verify-prompt --run-manifest <path> --gaps <path> --worker <worker>` | Print one analyser's gap-verification instructions | `convergence` "Gap verification" |
112
+ | `okstra convergence apply-critic-gaps --work-state <path> --results <path>` | Apply the gap-verification batch once, before `finalize` | `convergence` "Coverage critic pass" |
113
+ | `okstra convergence apply-acceptance-critic --work-state <path> --results <path>` | Record the final-verification acceptance-critic accounting | `convergence` "Acceptance critic pass" |
114
+ | `okstra convergence finalize --work-state <path> --output <path>` | Write the public final state after every round and critic batch is applied | `convergence` "Round 1-N" |
115
+ | `okstra convergence validate --state <path> --kind <kind>` | Replay a persisted state's invariants | `convergence` "Convergence Test" |
116
+
117
+ ### Phase 6 — synthesis and plan-body verification
118
+
119
+ | Command | Use when | Procedure |
120
+ |---|---|---|
121
+ | `okstra agent-prompt check-corrections --project-root <dir> --run-manifest <path> --corrections <path>` | Check a report-writer corrections ledger before a corrective dispatch | `report-writer` "Corrective report-writer dispatch" |
122
+ | `okstra agent-prompt apply-corrections --project-root <dir> --run-manifest <path> --corrections <path>` | Apply a ledger that `check-corrections` reported `mechanical: true`, without a writer round | `report-writer` "Corrective report-writer dispatch" |
123
+ | `okstra design-snapshot --narrative <path> --output <path>` | Complete the design-surface detector snapshot; both paths come from the run manifest | `report-writer` "Implementation-planning sequence" |
124
+ | `okstra plan-items prepare --run-manifest <path> --narrative <path>` | Extract this round's plan items (add `--state <path>` after a self-fix, `--tie-vote` for a critic tie round) | `plan-body-verification` "Round protocol" |
125
+ | `okstra plan-items prompt --run-manifest <path>` | Print the plan-items block placed verbatim in every verifier prompt | `plan-body-verification` "Round protocol" |
126
+ | `okstra plan-items validate-prepared --run-manifest <path> --narrative <path>` | Validate the prepared envelope before dispatch | `plan-body-verification` "Round protocol" |
127
+ | `okstra plan-items seed --narrative <path>` | Create the `planItems[]` rows a round lands in | `plan-body-verification` "Round protocol" |
128
+ | `okstra plan-items extract --narrative <path> --output <path>` | Re-extract plan items after a round and re-verify any item whose subject shifted | `plan-body-verification` "Round protocol" |
129
+ | `okstra plan-items correction-prompt --run-manifest <path> --state <path> --worker <id>` | Put its output first in a `worker-correction` re-dispatch prompt | `plan-body-verification` "Round protocol" |
130
+ | `okstra plan-items collect-verdicts --items <path> --result <worker>=<path> --output <path>` | Read a round's responses into a verdicts envelope; never parse them by hand | `plan-body-verification` "Round protocol" |
131
+ | `okstra plan-items apply-verdicts --state <path> --round <N> --result <worker>=<path>` | Record the round's verdicts | `plan-body-verification` "Round protocol" |
132
+ | `okstra plan-items complete-round --state <path> --run-manifest <path> --round <N>` | Record the completed round | `plan-body-verification` "Round protocol" |
133
+ | `okstra plan-items next-dispatch --state <path>` | Decide whether the round opens another worker batch | `plan-body-verification` "Round protocol" |
134
+ | `okstra plan-items resolve-dissent --state <path> --item <P-id> --decision-file <path>` | Record an evidence-based lead decision after the single self-fix | `plan-body-verification` "Round protocol" |
135
+ | `okstra plan-verify --report <path>` | Score the plan-body gate; never tally votes in a script | `plan-body-verification` "Round protocol" |
136
+
137
+ ### Phase 7 — persist
138
+
139
+ | Command | Use when | Procedure |
140
+ |---|---|---|
141
+ | `okstra report-finalize --project-root <dir> --run-manifest <path> --report <path>` | Run the whole Phase 7 sequence; do not run its steps separately | `report-writer` "Phase 6 → Phase 7 execution sequence" |
142
+ | `okstra report-translate check-data --run-manifest <path>` | Check an existing translation after a narrative correction | `report-writer` "Phase 6 → Phase 7 execution sequence" |
143
+ | `okstra handoff record-verified --plan-run-root <dir> --stage <N> --report-path <path> --data-json <path>` | Record an accepted final-verification against its stage | this contract "Lifecycle Phase Boundaries" |
144
+ | `okstra team teardown --project-root <dir> --run-manifest <path>` | Close every recorded pane at the end of the run | cmux adapter "Semantic operation mapping" |
145
+
146
+ **Names that do not exist.** Lead sessions have called these; each fails with `unknown command`. Use the command on the right.
147
+
148
+ - `validate-run` — `okstra report-finalize` runs the run validation; `okstra plan-verify` scores the plan-body gate mid-round.
149
+ - `status` — `okstra model-io run-input` for run state; `okstra worker-liveness` for a worker.
150
+ - `progress` — `okstra lead-progress append`.
151
+
47
152
  ## Core operating contract
48
153
 
49
154
  - 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.
@@ -106,7 +211,7 @@ User-utterance interpretation rule:
106
211
 
107
212
  At each phase or implementation-stage boundary, at task completion, and before a controlled session pause or handoff, follow the launch prompt's "Progress, remaining work, and recommendation" guidance. Include the result and evidence, unfinished work and blockers, and the next action with its reason. During an authorized continuous run, deliver this update beside the checkpoint and continue; do not turn the update into an approval gate. The same guidance applies to the final reply after persistence below.
108
213
 
109
- Apply the launch prompt's file-link presentation guidance to these updates as well: short localized labels, one link per line, an intact verified destination, and a copyable file-opening command when the terminal expands links. Keep file links separate from the raw progress checkpoint line.
214
+ Apply the launch prompt's file-link presentation guidance to these updates as well: short localized labels, one link per line, an intact absolute destination, and a copyable file-opening command when the terminal expands links. Keep file links separate from the raw progress checkpoint line.
110
215
 
111
216
  Follow the launch prompt's operation-level progress guidance: name the actual prepared artifact or executed check, distinguish preparation from execution and results, and cite the observed error when blocked. Apply its existing-authorization guidance before asking about provider data transfer again; reuse approval for the same recipients and material while respecting separate host execution permission.
112
217
 
@@ -119,7 +224,7 @@ For an `implementation-planning` run whose run manifest declares `activityContra
119
224
  The live projection follows this shape:
120
225
 
121
226
  ```text
122
- PROGRESS: phase-4-dispatch worker=codex-worker model=gpt-5.6-sol
227
+ PROGRESS: phase-4-dispatch worker=codex-worker model=gpt-6-sol
123
228
  ACTIVITY: id=A-001 agent=codex-worker summary="Verify Stage Map paths and commands" items=P-Step-001,P-Step-002 result=runs/.../codex-worker-....md outcome=pending
124
229
  ```
125
230
 
@@ -140,7 +245,7 @@ Required checkpoints:
140
245
  - `PROGRESS: phase-5-stage-complete stage=<N> steps=<done>/<count>` — `implementation` only, immediately after the Executor result is verified and its `### Stage Carry Evidence` block is parsed, before the verifier dispatch. `<done>` counts the block's `stepResults[]` rows whose `status` is `done` — read from the emitted block, never recomputed from git. Omitted when the Executor ends without carry evidence (`FAIL` or a non-result); the user then learns the outcome from `phase-5-collect status=<terminal-status>`.
141
246
  - `PROGRESS: phase-5.5-convergence round=<N> queue=<count>` — at the start of each convergence round (Phase 5.5).
142
247
  - `PROGRESS: phase-5.6-critic provider=<provider> gaps=<n>` — after the critic result is collected (Phase 5.6, opt-in; the critic dispatch itself fires concurrently with the first 5.5 reverify round). Omitted when `convergence.critic.enabled == false`.
143
- - `PROGRESS: phase-batch-cleanup panes=<n>` — immediately after cleaning up the previous batch's panes, at each batch boundary (① just before the first `phase-5.5-convergence` round ② just before the `phase-6-synthesis` report-writer dispatch). `<n>` is the number of panes closed at that boundary — the panes of dispatches this run recorded and that have since finished — read from the `okstra team reclaim --dry-run` pass taken immediately before the closing pass, never estimated. A pane the harness opened for its own teammate carries no recorded id, so it is not counted and not closed. Expose only the counts and NEVER expose a raw `paneId` or worker handle. Just before the first batch (analysis-worker dispatch) there is nothing to clean up, so it is a no-op and the marker is omitted.
248
+ - `PROGRESS: phase-batch-cleanup panes=<n>` — immediately after cleaning up the previous batch's panes, at each batch boundary (① just before the first `phase-5.5-convergence` round ② just before the `phase-6-synthesis` report-writer dispatch). `<n>` is the number of panes closed at that boundary — the panes of dispatches this run recorded and that have since finished — read from the `okstra team reclaim --project-root <dir> --run-manifest <path> --dry-run` pass taken immediately before the closing pass, never estimated. A pane the harness opened for its own teammate carries no recorded id, so it is not counted and not closed. Expose only the counts and NEVER expose a raw `paneId` or worker handle. Just before the first batch (analysis-worker dispatch) there is nothing to clean up, so it is a no-op and the marker is omitted.
144
249
  - `PROGRESS: phase-6-synthesis dispatching report-writer-worker` — at the start of Phase 6.
145
250
  - `PROGRESS: phase-5.5.9-plan-verify round=<N> items=<count>` — immediately before dispatching each plan-body verification round (`implementation-planning` only; see [plan-body-verification](./plan-body-verification.md) §"Round protocol"). Each round is a worker batch like any other, so round 2 and later MUST be preceded by a `phase-batch-cleanup` line reclaiming the previous round's verifiers. The numbering keeps this line sorted where the work happens — after Phase 6, because the round verifies the drafted plan body.
146
251
  - `PROGRESS: user-confirm <C-NNN> <the question, one line>` — immediately before asking the user about anything that would otherwise become an open `Blocks=approval` row (see "User confirmation before an approval blocker" below). Not tied to a phase: it fires wherever the blocker surfaces. `<C-NNN>` is the id the row will carry, so the answer and the row can be matched afterwards.
@@ -164,9 +269,13 @@ Every question this run puts to the user obeys the same three rules, whatever th
164
269
 
165
270
  3. **Never hand over a blank you could have filled.** Anything you worked out while investigating belongs in the options. Stating a finding in chat and then offering nothing but free input is the failure this rule exists for. Measured: a re-verification-scope prompt whose preamble named the stage the answers touched (`Stage 1`) still offered `직접 입력 — 다시 볼 stage 번호를 지정` as its first and only actionable choice, and the user had to retype what the run had just told them.
166
271
 
272
+ 4. **The question carries its own background.** The user has not read the report and cannot resolve this run's identifiers. Before an identifier carries weight in the question — a requirement id, a record row id, a stage number, a finding count, a role such as "the verifiers" — say in one clause what it is and what it says. The first sentence names what this task is about, in words that survive outside the run. Citing the coordinate stays right (see [_common-contract.md](../profiles/_common-contract.md) "Record coordinates only"); an unexpanded coordinate is what fails. Measured: a question asked how the task should handle "the min price half of EB-002" and never said what EB-002 required, so the only thing the user could answer was that the question could not be understood — which costs the same turn as asking, and loses the run's momentum on top.
273
+
274
+ The test is mechanical: strike every identifier out of your question. If what remains no longer poses a choice, the background is missing.
275
+
167
276
  When you are relaying a wizard step, [okstra-run](../../skills/okstra-run/SKILL.md) still owns the option set: relay every `options[]` entry unchanged, in order. This rule adds to it rather than overriding it — put your investigation in the question body and name which of the wizard's own options you recommend and why. Do not drop, merge, reorder, or invent a wizard option to satisfy this section.
168
277
 
169
- **Enforced:** `scripts/okstra_ctl/wizard.py` `Prompt.__post_init__` refuses any wizard step whose free-input option is not last (only an abort option may follow it), so a question cannot open on "answer directly". The investigate-first half is a contract on the lead, checked by the same evidence source as the rest of this section — see the `PROGRESS: user-confirm` checkpoint below for the one question kind that leaves a record.
278
+ **Enforced:** `scripts/okstra_ctl/wizard.py` `Prompt.__post_init__` refuses any wizard step whose free-input option is not last (only an abort option may follow it), so a question cannot open on "answer directly". The investigate-first half is a contract on the lead, checked by the same evidence source as the rest of this section — see the `PROGRESS: user-confirm` checkpoint below for the one question kind that leaves a record. Rule 4 is a **guideline**: nothing reads the question text, and a scan for bare identifiers cannot tell an expanded citation from an unexpanded one, so it would fail the questions that are written correctly.
170
279
 
171
280
  ## User confirmation before an approval blocker (BLOCKING)
172
281
 
@@ -217,7 +326,7 @@ The table below documents those prep-time seed values **for reference only** —
217
326
  | Lead role | opus | -- | runtime-specific role label; orchestration + convergence supervision + final-report review/approval |
218
327
  | Report writer worker | sonnet | report-writer-worker | `agents/workers/report-writer-worker.md` |
219
328
  | Claude worker | opus | claude-worker | `agents/workers/claude-worker.md` |
220
- | Codex worker | gpt-5.6-sol | codex-worker | duty + task instructions composed per invocation; deterministic `worker-dispatch` execution |
329
+ | Codex worker | gpt-6-sol | codex-worker | duty + task instructions composed per invocation; deterministic `worker-dispatch` execution |
221
330
  | Antigravity worker | gemini-3.1-pro | antigravity-worker | duty + task instructions composed per invocation; deterministic `worker-dispatch` execution |
222
331
 
223
332
  Each analysis assignment follows its recorded `runner`. `runner=native-session` uses the host's native subagent primitive after `host-native-spec-link-gate`. `runner=cli-wrapper` follows the planned execution surface after `core-pre-dispatch` verification: `okstra team dispatch` when `terminalBackend` is `cmux-pane`, otherwise the deterministic `okstra worker-dispatch` process boundary. No LLM transport wrapper sits in front of a provider CLI.
@@ -529,7 +638,7 @@ When the host native picker is available and two of those rows could apply, ask
529
638
 
530
639
  **Cite run-artifact paths, do not assemble them.** The `report-finalize` result's `reportPaths` carries this run's `humanReport`, `reportRecord`, `teamState`, and `renderFullCopy` command, each already rooted at the project (`.okstra/tasks/<task-group>/<task-id>/runs/...`). Every other run-artifact path the reply cites — the resume command among them — comes from the launch prompt's `## Manifests` / `## Run Paths` lists, which are rooted the same way. A path you compose from a `runs/<task-type>/...` pattern instead is identical across every task of that task-type, so it names no task and does not resolve from the project root either.
531
640
 
532
- Write file references as Markdown links with short labels in the Report Language, following the launch prompt's file-link presentation guidance. `reportPaths.markdown` supplies verified destinations for the report, report record, and team state; preserve those destinations and shorten only their display labels. Put each link on its own line without repeating the path in prose or inserting a line break inside the destination. Apply the same format to worker results, error logs, and briefs, using the project-rooted paths from `## Manifests` / `## Run Paths`. If the terminal expands links into long paths, offer the host-appropriate copyable file-opening command described in the launch prompt. Markdown alone does not guarantee clickable links in every terminal.
641
+ Write file references as Markdown links with short labels in the Report Language, following the launch prompt's file-link presentation guidance. `reportPaths.markdown` supplies absolute destinations for the report, report record, and team state; preserve those destinations and shorten only their display labels. Put each link on its own line without repeating the path in prose or inserting a line break inside the destination. Apply the same format to worker results, error logs, and briefs: take the project-rooted path from `## Manifests` / `## Run Paths` and prefix the absolute project root, so every link destination is absolute. Relative paths stay in commands, not in link destinations. If the terminal expands links into long paths, offer the host-appropriate copyable file-opening command described in the launch prompt. Markdown alone does not guarantee clickable links in every terminal.
533
642
 
534
643
  **Enforced:** `scripts/okstra_ctl/report_finalize.py` `_closeout_report_paths` builds the task-qualified paths and `closeout_command` builds the close, so neither is re-derived; `validators/validate_session_conformance.py` `_check_progress_checkpoints` requires the `phase-7-persist` checkpoint this phase opens with.
535
644
 
@@ -405,7 +405,7 @@ For contract 3.0, `prepare` checks the selected-direction draft with the same se
405
405
  - `dissent-isolated` — only one worker `DISAGREE`s, others `AGREE`. On a blocking kind (`b` / `c` / `e`, and kind `a` on `P-Var-*`) this is scored `majority-disagree` and **blocks approval**. Advisory-only `DISAGREE(d)` and `P-Rb-*` stay recorded dissent and do not block. (Distinct from finding-convergence `worker-unique`, which means the *opposite*: only one worker AGREEs.)
406
406
  - `majority-disagree` — a *majority* of analysers `DISAGREE` (majority needs ≥2 participating non-error votes; rollback-ordering `DISAGREE(d)` votes are advisory and excluded from the tally), OR any blocking-kind dissent with ≥2 participating votes (a minority `DISAGREE` is not outvoted), OR an unresolved single-vote-blocking kind fires: one reproduced `DISAGREE(a)` on any item other than a `P-Var-*` one, or one reproduced `DISAGREE(f)` on a `P-Req-*` item (see §"Single-vote-blocking kinds"). This classification **blocks approval**. A valid critic correction is scored before these blocking rules, whether or not the analysers split evenly.
407
407
  - `needs-reverify` — one of two shapes the round could not settle.
408
- - **An even split on a blocking kind.** A panel splitting evenly (1-AGREE / 1-DISAGREE, 2-2, …) needs a critic decision. An unresolved single-vote-blocking kind remains `majority-disagree`; other unresolved splits are `needs-reverify`. Do **not** re-run the original two. Dispatch `critic-worker` immediately on those items only (`okstra plan-items prepare --tie-vote`, then `okstra plan-items prompt`). The prompt carries the analyser split and no other plan items. Read the answer with `okstra plan-items collect-verdicts --items <the `--tie-vote` plan-items artifact> --result critic-worker=<path> --output <envelope>` — `--items` takes that artifact, whose `dispatchQueue` is the tie items, so pointing the next step at the raw result is refused against this round's full queue. Record the critic vote as `verdicts[].worker = critic-worker` with `okstra plan-items apply-verdicts --append --items <the `--tie-vote` plan-items artifact> --result critic-worker=<path>` — `--items` persists the exact partial `dispatchQueue` used by verdict validation and `complete-round`. Earlier verdicts and completed-round history outside that queue remain unchanged. If a previous version saved the critic votes but left the full queue in state, the ordinary `okstra plan-items complete-round --state <state> --run-manifest <manifest> --round <N>` automatically reads this run's canonical prepared queue. It uses that queue when all votes recorded for this round are included, preserving a wider recorded batch when a later prepared queue excludes its votes. An explicit `--items <prepared artifact>` remains available for selecting an artifact. Enforced by `plan_items_cli._round_inputs` and `tests/run/test_plan_items.py` completion coverage. Do not fabricate new-round votes for already agreed items. Without it the result is checked against the whole persisted round queue and refused for every item the critic was never given (measured 2026-09-10: a 7-item tie round refused against 44 items), and the only way through was `--verdicts`, which the CLI's own help calls a historical envelope. Critic `AGREE` / `SUPPLEMENT` settles the split to `has-dissent`, including an earlier `DISAGREE(a)` or `DISAGREE(f)` on `P-Req-*`. This decision corrects the disputed judgement before single-vote blocking is evaluated; it does not delete the original dissent. Critic `DISAGREE` on a blocking kind is `majority-disagree`. **Enforced:** `validators/validate-run.py` `_validate_unresolved_tie_was_reverified` fails an in-scope item that carries an even split on a blocking kind and has neither a `critic-worker` vote nor a `blocks: approval` clarification row, `_classify_plan_item_gate` scores the tie shape and fails a settled classification the votes do not support, and `okstra_ctl.plan_items.next_dispatch` returns kind `critic-tie` for exactly these items, so the tie round is the queue the CLI hands you rather than one you assemble. **With no critic on the roster the split is a user decision, not another round.** `okstra plan-items next-dispatch --run-manifest <current-run-manifest.json>` answers `user-decision` (not `critic-tie`) when the run's `invocationAssignments` carries no `critic/*` entry, and its `itemIds` are the tie items. For each of them do what step 8 does for a surviving `majority-disagree`: `okstra approval-decision open` with `approvalContext.classification` set to `correctness-critical` when `_is_correctness_critical` is true, or `noncritical-dissent` otherwise, plus the matching `## 1. Clarification Items` row at `Blocks=approval`. Dispatch no further verification for those items. The `Blocks=approval` row is what withholds approval until the user disposes, exactly as for any other approval row; the gate retains unresolved single-vote blockers as `majority-disagree` and folds other `needs-reverify` items into `passed-with-dissent`, so the round closes on the gate it actually scored. A tie left with neither a critic vote nor a decision row surfaces as recorded dissent plus an `advisories[]` entry, not a round-blocking failure. **Enforced:** `okstra_ctl.plan_items.critic_is_rostered` reads the roster and `next_dispatch` returns the kind; `validators/validate-run.py` `_validate_unresolved_tie_was_reverified` reads a `blocks: approval` row linked to the item as the settlement and emits its advisory only when neither settlement is recorded.
408
+ - **An even split on a blocking kind.** A panel splitting evenly (1-AGREE / 1-DISAGREE, 2-2, …) needs a critic decision. An unresolved single-vote-blocking kind remains `majority-disagree`; other unresolved splits are `needs-reverify`. Do **not** re-run the original two. Dispatch `critic-worker` immediately on those items only (`okstra plan-items prepare --tie-vote ...`, then `okstra plan-items prompt ...`). The prompt carries the analyser split and no other plan items. Read the answer with `okstra plan-items collect-verdicts --items <the --tie-vote plan-items artifact> --result critic-worker=<path> --output <envelope>` — `--items` takes that artifact, whose `dispatchQueue` is the tie items, so pointing the next step at the raw result is refused against this round's full queue. Record the critic vote as `verdicts[].worker = critic-worker` with `okstra plan-items apply-verdicts --state <plan-body-verification.json> --round <N> --append --items <the --tie-vote plan-items artifact> --result critic-worker=<path>` — `--items` persists the exact partial `dispatchQueue` used by verdict validation and `complete-round`. Earlier verdicts and completed-round history outside that queue remain unchanged. If a previous version saved the critic votes but left the full queue in state, the ordinary `okstra plan-items complete-round --state <state> --run-manifest <manifest> --round <N>` automatically reads this run's canonical prepared queue. It uses that queue when all votes recorded for this round are included, preserving a wider recorded batch when a later prepared queue excludes its votes. An explicit `--items <prepared artifact>` remains available for selecting an artifact. Enforced by `plan_items_cli._round_inputs` and `tests/run/test_plan_items.py` completion coverage. Do not fabricate new-round votes for already agreed items. Without it the result is checked against the whole persisted round queue and refused for every item the critic was never given (measured 2026-09-10: a 7-item tie round refused against 44 items), and the only way through was `--verdicts`, which the CLI's own help calls a historical envelope. Critic `AGREE` / `SUPPLEMENT` settles the split to `has-dissent`, including an earlier `DISAGREE(a)` or `DISAGREE(f)` on `P-Req-*`. This decision corrects the disputed judgement before single-vote blocking is evaluated; it does not delete the original dissent. Critic `DISAGREE` on a blocking kind is `majority-disagree`. **Enforced:** `validators/validate-run.py` `_validate_unresolved_tie_was_reverified` fails an in-scope item that carries an even split on a blocking kind and has neither a `critic-worker` vote nor a `blocks: approval` clarification row, `_classify_plan_item_gate` scores the tie shape and fails a settled classification the votes do not support, and `okstra_ctl.plan_items.next_dispatch` returns kind `critic-tie` for exactly these items, so the tie round is the queue the CLI hands you rather than one you assemble. **With no critic on the roster the split is a user decision, not another round.** `okstra plan-items next-dispatch --state <plan-body-verification.json> --run-manifest <current-run-manifest.json>` answers `user-decision` (not `critic-tie`) when the run's `invocationAssignments` carries no `critic/*` entry, and its `itemIds` are the tie items. For each of them do what step 8 does for a surviving `majority-disagree`: `okstra approval-decision open` with `approvalContext.classification` set to `correctness-critical` when `_is_correctness_critical` is true, or `noncritical-dissent` otherwise, plus the matching `## 1. Clarification Items` row at `Blocks=approval`. Dispatch no further verification for those items. The `Blocks=approval` row is what withholds approval until the user disposes, exactly as for any other approval row; the gate retains unresolved single-vote blockers as `majority-disagree` and folds other `needs-reverify` items into `passed-with-dissent`, so the round closes on the gate it actually scored. A tie left with neither a critic vote nor a decision row surfaces as recorded dissent plus an `advisories[]` entry, not a round-blocking failure. **Enforced:** `okstra_ctl.plan_items.critic_is_rostered` reads the roster and `next_dispatch` returns the kind; `validators/validate-run.py` `_validate_unresolved_tie_was_reverified` reads a `blocks: approval` row linked to the item as the settlement and emits its advisory only when neither settlement is recorded.
409
409
  - **A lone dissent nobody cross-verified** — a single-vote-blocking kind fired but the item has **fewer than 2 participating non-error votes**, i.e. the lone dissent was never cross-verified because its peer returned `verification-error`. A single-vote-blocking kind means "one *confirmed* DISAGREE is enough"; an unconfirmed one is not, and on a `P-Var-*` item none fires at all — its kind `a` never blocks on one vote and takes a majority like `b` / `e`. This does **not** block approval — blocking on it would make a worker failure produce a stricter gate than a healthy roster, the same paradox the ≥2-vote majority rule already rules out. The item is re-dispatched in the next round (step 7); if it survives the round budget it is promoted per step 8 with a Statement that says verification never completed. **Enforced:** `validators/validate-run.py` `_classify_plan_item_gate` returns `needs-reverify` for this shape and `_recompute_plan_body_gate` folds it into `passed-with-dissent`.
410
410
  - `contested` only meaningful when `maxRounds > 1`; at default `maxRounds=1`, fold any unresolved item into `partial-consensus`.
411
411
  5. Gate result resolution:
@@ -429,7 +429,7 @@ For contract 3.0, `prepare` checks the selected-direction draft with the same se
429
429
  **Record the cause, not just the outcome.** The gate value names the outcome; `planBodyVerification.gateBlockedBy` (array) names every input that blocked it — `majority-disagree`, `coverage-gap`, `non-result`. Two independent inputs can block: a `majority-disagree` plan item, and a Requirement Coverage `gap` / `blocked C-NNN` row (`prompts/profiles/implementation-planning.md` §"Requirement Coverage"). A coverage-only block still renders as `blocked-by-disagreement` because that is the only blocking non-abort value, so **without `gateBlockedBy` the report asserts a worker disagreement that never happened** and the reader hunts for a dissent that does not exist. Leave the array empty for a passing gate. **Enforced:** `validators/validate-run.py` `_validate_gate_blocked_by` fails a passing gate that has a blocking coverage row — the coverage rule was prose-only before. The declared array itself is not compared against a recomputed one: `okstra plan-verify` returns `gate.blockedBy` from the same computation that produced the gate value, so recording what it returns is what makes the array right.
430
430
 
431
431
  **A coverage row citing this run's own `C-NNN` is not an independent blocker.** When a coverage row's `blocked C-NNN` points at a clarification that step 8 below promoted from a `majority-disagree` item in *this same run*, that blocker is already counted once as the plan item. Counting it again as a coverage gap makes the run block on a clarification it just authored, and the row carries into the next run as a fresh blocker — the Requirement Coverage ↔ Clarification cycle. Such rows are excluded from `coverage-gap`. **Enforced:** `validators/validate-run.py` `_independent_coverage_blockers`.
432
- 6. `okstra plan-items complete-round --run-manifest <current-run-manifest.json>` derives `planBodyVerification.participatingAnalysers` from the current assigned roster and persisted votes, then atomically records the completed round. The gate arithmetic is unchanged, but a shrunken roster changes what the round can settle: with two participating analysers a 1-AGREE / 1-DISAGREE split is a tie, so it reaches neither consensus nor `majority-disagree` and the item has to go back for a round (see `needs-reverify` above). **Enforced:** `validators/validate-run.py` `_validate_participating_analysers` recomputes `voting` from the recorded verdicts and fails a declared figure the table denies. `validators/validate-run.py` `_detect_uniform_verifier` remains advisory; do not copy its JSON output into state.
432
+ 6. `okstra plan-items complete-round --state <plan-body-verification.json> --run-manifest <current-run-manifest.json> --round <N>` derives `planBodyVerification.participatingAnalysers` from the current assigned roster and persisted votes, then atomically records the completed round. The gate arithmetic is unchanged, but a shrunken roster changes what the round can settle: with two participating analysers a 1-AGREE / 1-DISAGREE split is a tie, so it reaches neither consensus nor `majority-disagree` and the item has to go back for a round (see `needs-reverify` above). **Enforced:** `validators/validate-run.py` `_validate_participating_analysers` recomputes `voting` from the recorded verdicts and fails a declared figure the table denies. `validators/validate-run.py` `_detect_uniform_verifier` remains advisory; do not copy its JSON output into state.
433
433
 
434
434
  **Check each verifier's verdict distribution before the next round.** After `apply-verdicts` and before opening another worker batch, run `okstra plan-items next-dispatch --state <plan-body-verification.json> --run-manifest <current-run-manifest.json>`. Python owns that decision. Do not invent a full-roster round from a `needs-reverify` label, from every-item `UNVERIFIABLE`, or from `okstra plan-verify` warnings. `_detect_uniform_verifier` remains advisory; do not copy its JSON output into state.
435
435
 
@@ -446,7 +446,7 @@ For contract 3.0, `prepare` checks the selected-direction draft with the same se
446
446
 
447
447
  The environment exception in §"Planning-time environment gap" covers **running build and test commands only** — whether a referenced path exists, whether a command is declared in `package.json`, and whether the plan is internally consistent are all checkable without it, and a blanket "capability constraints prevent workspace resolution" is not a valid answer to any of them.
448
448
 
449
- **How the corrective round is recorded.** The first prompt was dispatched, so it is immutable — `--replace-undispatched` refuses it, correctly. Materialize the correction under a NEW `--invocation-id` and a new prompt path. Before linking its result, retire the first attempt's link: `okstra agent-prompt reject-result --run-manifest <path> --dispatch-id <first dispatch id> --superseded-by <corrective dispatch id> --reason "<what was wrong with the returned result>"`. Without that step the corrective `link-result` fails with `agent result is already linked to another dispatch`, which is how a worker that ran for twenty minutes and wrote a good result ends up unrecordable. Nothing is deleted: the rejected link stays in `agentResultLinks` carrying `supersededBy` and `rejectionReason`, so the ledger shows both attempts and why the second exists.
449
+ **How the corrective round is recorded.** The first prompt was dispatched, so it is immutable — `--replace-undispatched` refuses it, correctly. Materialize the correction under a NEW `--invocation-id` and a new prompt path. Before linking its result, retire the first attempt's link: `okstra agent-prompt reject-result --project-root <dir> --run-manifest <path> --dispatch-id <first dispatch id> --superseded-by <corrective dispatch id> --reason "<what was wrong with the returned result>"`. Without that step the corrective `link-result` fails with `agent result is already linked to another dispatch`, which is how a worker that ran for twenty minutes and wrote a good result ends up unrecordable. Nothing is deleted: the rejected link stays in `agentResultLinks` carrying `supersededBy` and `rejectionReason`, so the ledger shows both attempts and why the second exists.
450
450
 
451
451
  Then run `okstra plan-items complete-round --state <plan-body-verification.json> --run-manifest <current-run-manifest.json> --round <N>`. Python appends one immutable round history entry, records each verified item's votes, derives the current projection from the actual assigned roster, and stamps `completedAt` after the preceding verification command succeeds. The file accumulates across rounds; it is never truncated to the latest one. Report assembly later projects the completed nested `planBodyVerification` into the final record.
452
452
  7. **Self-fix loop (one rewrite, targeting planner-fixable defects).** After the initial verification, lead may run one report-writer rewrite when a `majority-disagree` item has a majority of `DISAGREE` verdicts at `fixability == planner-fixable`. Re-verify changed items once, preserving verdicts on unchanged content. Then stop automatic self-fix regardless of outcome and follow step 8. The fixed order is initial verification → one planner self-fix → targeted re-verification → lead decision or immediate user confirmation. A second automatic self-fix is rejected by `plan_items_cli._record_self_fixes`; `_validate_self_fix_grouping` and session activity validation detect multiple recorded rewrites. No rewrite is needed when no item qualifies.
@@ -464,7 +464,7 @@ For contract 3.0, `prepare` checks the selected-direction draft with the same se
464
464
  - `all-resolved` — no planner-fixable `majority-disagree` item remains. Exit.
465
465
  - `no-progress` — the round resolved **zero** planner-fixable items relative to the previous round. Exit even with budget left: the same rewrite would repeat. Newly *introduced* defects count against progress, so a rewrite that trades one defect for another stops the loop rather than churning. A round that re-targets only what the previous round left unresolved is this same conclusion reached one dispatch earlier — exit on it under this reason rather than paying for the round that proves it. **Enforced (advisory):** `validators/validate-run.py` `_detect_self_fix_recurrence` warns on that shape and names this stop reason.
466
466
  - `max-rounds-reached` — the single automatic rewrite has been used. Exit to step 8. Count distinct `selfFixGroups[].round` values; `selfFixRoundsApplied` is the last verification round number, not the rewrite count. Extra verification batches before the rewrite do not increase the self-fix budget.
467
- - `cause-group-recurrence` — legacy read-only value for reports produced before activity contract v1. A new activity-contract-v1 run cannot emit it because there is no second automatic self-fix round in which a cause group can recur. **Enforced at the only writer:** `okstra plan-items complete-round --self-fix-stop-reason` accepts `all-resolved` / `no-progress` / `max-rounds-reached` and nothing else (`scripts/okstra_ctl/plan_items_cli.py:261`), and it is what sets `selfFixStopReason`, so the value has no way into a new report. A legacy report that already carries it is still an *exhausted* loop, so step 8 may promote its surviving items: `_SELF_FIX_EXHAUSTED_REASONS` admits this value alongside `no-progress` / `max-rounds-reached`. Refusing it there left such a report with no exit at all — the loop may not run again, and the item may not be promoted either.
467
+ - `cause-group-recurrence` — legacy read-only value for reports produced before activity contract v1. A new activity-contract-v1 run cannot emit it because there is no second automatic self-fix round in which a cause group can recur. **Enforced at the only writer:** `okstra plan-items complete-round ... --self-fix-stop-reason` accepts `all-resolved` / `no-progress` / `max-rounds-reached` and nothing else (`scripts/okstra_ctl/plan_items_cli.py:261`), and it is what sets `selfFixStopReason`, so the value has no way into a new report. A legacy report that already carries it is still an *exhausted* loop, so step 8 may promote its surviving items: `_SELF_FIX_EXHAUSTED_REASONS` admits this value alongside `no-progress` / `max-rounds-reached`. Refusing it there left such a report with no exit at all — the loop may not run again, and the item may not be promoted either.
468
468
  - `not-attempted` — the loop never ran because no item qualified.
469
469
  The `no-progress` and `max-rounds-reached` exits are what make the loop terminate; `selfFixMaxRounds` alone is the backstop.
470
470
  - a `majority-disagree` item with a majority of its deciding `DISAGREE` votes at `needs-user-input` is NOT a self-fix target — after correctness-critical precedence, it goes straight to the next step as `user-decision` rather than generic `noncritical-dissent`. **Enforced (promotion path):** `validators/validate-run.py` `_validate_self_fix_before_clarification` demands an exhausted self-fix budget only of `planner-fixable` majorities, so a `needs-user-input` majority is promotable with no self-fix round, and `_validate_plan_body_clarification_matching` fails it when it reaches no `blocks: approval` row. The classification *value* is authoring guidance per step 8: `scripts/okstra_ctl/approval_decisions.py` checks it against the classification enum and its allowed dispositions only — no validator recomputes it from the votes' `fixability`.
@@ -673,7 +673,7 @@ posture in §"Adversarial plan-body posture" still applies, and this round does
673
673
  not revisit the requirements themselves.
674
674
 
675
675
  Omitting either line fails `okstra team dispatch --dispatch-kind
676
- plan-verify-r<N>` before any process starts, reported as `<task-type>
676
+ plan-verify-r<N> ...` before any process starts, reported as `<task-type>
677
677
  prompt contract: <worker>: exactly one Primary analysis packet path is required
678
678
  (found 0)`. Fix the instructions file and re-materialize with
679
679
  `--replace-undispatched` rather than editing the published prompt.
@@ -852,7 +852,7 @@ What the generated round 2+ prompt has that round 1 does not:
852
852
 
853
853
  An item with no recorded vote carrying a round number gets no block, and an envelope with nothing to carry keeps the round-1 shape — `priorRounds` is absent and no preamble is prepended, so a first round is unaffected by this section.
854
854
 
855
- **Enforced:** `okstra plan-items validate-prepared --state <same state>` re-derives the carry and exits 2 when the prepared envelope's `priorRounds` does not match, alongside the `items` / `dispatchQueue` comparison it already made. A prepared queue that dropped the dissent cannot pass the step-1 validation the dispatch is gated on.
855
+ **Enforced:** `okstra plan-items validate-prepared ... --state <same state>` re-derives the carry and exits 2 when the prepared envelope's `priorRounds` does not match, alongside the `items` / `dispatchQueue` comparison it already made. A prepared queue that dropped the dissent cannot pass the step-1 validation the dispatch is gated on.
856
856
 
857
857
  The two spellings are different anchors for different artifacts: `**Prior round dissent**` is the block `prompt` puts in the prompt, `**Prior dissent**` is the line the worker puts in its result. `scripts/okstra_ctl/verdict_blocks.py` parses the result line into the verdict block when it is present and leaves it empty when it is not, so an omitted answer line is still silent — the prompt is what is now guaranteed, not the response.
858
858
 
@@ -24,7 +24,7 @@ Report assembly reads the role-owned inputs, validates them, derives links and s
24
24
 
25
25
  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.
26
26
 
27
- Prepare seeds `carriedDecisions[]` when it creates the ledger: every clarification the run's carry-in record answered or resolved, plus every row that record's user-responses sidecars answered, arrives carried (`scripts/okstra_ctl/approval_decisions.py` `seed_carried_decisions`). The carry-in record is the `--clarification-response` file; for a new plan it is the option-selection record `--selected-direction` names, and for an implementation run the approved plan `--approved-plan` names (`render._carry_in_source`) — the same pointer assembly writes to `clarificationCarryIn.sourceFile`, so the page links those ids to the prior run's page. A carried plan row's `requirementCoverage[].decisionRefs` may still name a `C-NNN` the carry-in record does not answer — a decision from an older run. Carry it before assembly: `okstra approval-decision carry --ledger <approvalDecisionsPath> --from-responses <instruction-set/clarification-response.md> --clarification-id C-NNN` — repeat `--clarification-id` to take several in one call. That bundle is the source of truth for an earlier run's answer: it is task-level and cumulative, so no prior run seq has to be located, and each response section names the report that posed the question, which is where the row's `statement`, `expectedForm`, and options come from. A carried row lands as `answered`, not `resolved` — it was resolved in another run, and `resolution.checkRefs` names *this* run's activity rows. Carrying an answer also obliges a `supersessionLedger` entry for it.
27
+ Prepare seeds `carriedDecisions[]` when it creates the ledger: every clarification the run's carry-in record answered or resolved, plus every row that record's user-responses sidecars answered, arrives carried (`scripts/okstra_ctl/approval_decisions.py` `seed_carried_decisions`). The carry-in record is the `--clarification-response` file; for a new plan it is the option-selection record `--selected-direction` names, and for an implementation run the approved plan `--approved-plan` names (`render._carry_in_source`) — the same pointer assembly writes to `clarificationCarryIn.sourceFile`, so the page links those ids to the prior run's page. A carried plan row's `requirementCoverage[].decisionRefs` may still name a `C-NNN` the carry-in record does not answer — a decision from an older run. Carry it before assembly: `okstra approval-decision carry --ledger <approvalDecisionsPath> --from-responses <launch prompt "Clarification Response Carried In" → Source path> --clarification-id C-NNN` — repeat `--clarification-id` to take several in one call. That bundle is the source of truth for an earlier run's answer: it is task-level and cumulative, so no prior run seq has to be located, and each response section names the report that posed the question, which is where the row's `statement`, `expectedForm`, and options come from. A carried row lands as `answered`, not `resolved` — it was resolved in another run, and `resolution.checkRefs` names *this* run's activity rows. Carrying an answer also obliges a `supersessionLedger` entry for it.
28
28
 
29
29
  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`.
30
30
 
@@ -58,7 +58,7 @@ This is enforced by
58
58
  `report_synthesis_packet.build_report_synthesis_packet()`, and
59
59
  `report_assembly.assemble_report()`.
60
60
 
61
- Materialize the duty prompt with `okstra agent-prompt materialize --audience report-writer`. `--assignment-ref` is `initial/report-writer` — the run manifest declares the report writer under the `initial` phase, so `report-writer/report-writer` is refused. `--result` is the narrative path the run manifest's `reportNarrativePath` names (report contract 3.0; the `expectedReportRecordPath` data.json under 2.0) and `--audit-source` is the roster's worker result (`team-state` `workers[].resultPath` for `report-writer`). The materializer refuses any other value: report assembly reads the narrative from the manifest path and nowhere else, and `okstra team await` records the roster row completed only when the roster's file exists. A corrective dispatch (fix, self-fix, verdict, citations, supersession) reuses both paths and changes only the prompt path and the invocation ID. **Enforced:** `_validate_report_writer_paths` in `scripts/okstra_ctl/agent/prompt_cli/materialize.py`. The materializer also writes the report synthesis packet next to the narrative and puts `- Report synthesis packet: <path>` as the first line of the prompt's `## Inputs` section — opening the section when the instruction has none, or inserting under the instruction's own `## Inputs` heading when it has one. The instruction therefore does not enumerate raw source paths; a missing or unreadable packet source is refused before dispatch with its owner (`report synthesis packet contract defects`). **Enforced:** `_report_writer_input_lines` in `scripts/okstra_ctl/agent/prompt_cli/materialize.py`. The prompt starts with these anchors in order:
61
+ Materialize the duty prompt with `okstra agent-prompt materialize --audience report-writer ...`. `--assignment-ref` is `initial/report-writer` — the run manifest declares the report writer under the `initial` phase, so `report-writer/report-writer` is refused. `--result` is the narrative path the run manifest's `reportNarrativePath` names (report contract 3.0; the `expectedReportRecordPath` data.json under 2.0) and `--audit-source` is the roster's worker result (`team-state` `workers[].resultPath` for `report-writer`). The materializer refuses any other value: report assembly reads the narrative from the manifest path and nowhere else, and `okstra team await` records the roster row completed only when the roster's file exists. A corrective dispatch (fix, self-fix, verdict, citations, supersession) reuses both paths and changes only the prompt path and the invocation ID. **Enforced:** `_validate_report_writer_paths` in `scripts/okstra_ctl/agent/prompt_cli/materialize.py`. The materializer also writes the report synthesis packet next to the narrative and puts `- Report synthesis packet: <path>` as the first line of the prompt's `## Inputs` section — opening the section when the instruction has none, or inserting under the instruction's own `## Inputs` heading when it has one. The instruction therefore does not enumerate raw source paths; a missing or unreadable packet source is refused before dispatch with its owner (`report synthesis packet contract defects`). **Enforced:** `_report_writer_input_lines` in `scripts/okstra_ctl/agent/prompt_cli/materialize.py`. The prompt starts with these anchors in order:
62
62
 
63
63
  1. `**Project Root:**`
64
64
  2. `**Prompt History Path:**`
@@ -147,7 +147,7 @@ When the result carries `recovery.mode: same-run`, continue the authorized work
147
147
 
148
148
  ### The translation sidecar: the `translate` step
149
149
 
150
- `report-finalize` dispatches the translator itself. Its `translate` step runs directly before `render-views` (which overlays the sidecar), and it is a no-op when `reportLanguage` is `en` or the `*.i18n.<lang>.json` sidecar already exists. When the sidecar is missing, the step reuses this run's undispatched translator reservation if the lead already materialized one, otherwise it materializes the prompt itself — instruction file `state/translator-instructions-<task-type>-<seq>.md` (kept when the lead wrote one), prompt `prompts/translator-worker-prompt-<task-type>-<seq>.md`, `--result worker-results/translator-translations-<task-type>-<seq>.md`, `--audit-source worker-results/translator-worker-<task-type>-<seq>.md`, invocation id `<task-type>-<seq>-translator` (`-r2`, `-r3` after a failed attempt) — then runs the CLI-wrapper dispatch, which records the dispatch and links the result. Do not run `agent-prompt materialize --audience translator` or `worker-dispatch --workers translator` by hand before `report-finalize`; the sequence used to be a manual lead step and was skipped in practice (2026-09-09, fontsninja-v3-site dev-10628-3: a `ko` run finalized in one call, zero translator reservations, English HTML). **Enforced:** `okstra_ctl.report_finalize.V3_STEP_ORDER` places the step; `okstra_ctl.report_translation_dispatch.translate_report` owns it; `_translator_job_from_reservation` in `scripts/okstra_ctl/dispatch_core.py` still refuses two undispatched reservations for one run, which only a hand-made second reservation produces.
150
+ `report-finalize` dispatches the translator itself. Its `translate` step runs directly before `render-views` (which overlays the sidecar), and it is a no-op when `reportLanguage` is `en` or the `*.i18n.<lang>.json` sidecar already exists. When the sidecar is missing, the step reuses this run's undispatched translator reservation if the lead already materialized one, otherwise it materializes the prompt itself — instruction file `state/translator-instructions-<task-type>-<seq>.md` (kept when the lead wrote one), prompt `prompts/translator-worker-prompt-<task-type>-<seq>.md`, `--result worker-results/translator-translations-<task-type>-<seq>.md`, `--audit-source worker-results/translator-worker-<task-type>-<seq>.md`, invocation id `<task-type>-<seq>-translator` (`-r2`, `-r3` after a failed attempt) — then dispatches the worker and waits for it: on a `terminalBackend: cmux-pane` run into a cmux pane like every other worker (the same code as `okstra team dispatch` followed by `okstra team await`; run-end `okstra team teardown` closes that pane), otherwise through the CLI-wrapper dispatcher. Either path records the dispatch and links the result. Do not run `agent-prompt materialize --audience translator` or `worker-dispatch --workers translator` by hand before `report-finalize`; the sequence used to be a manual lead step and was skipped in practice (2026-09-09, fontsninja-v3-site dev-10628-3: a `ko` run finalized in one call, zero translator reservations, English HTML). **Enforced:** `okstra_ctl.report_finalize.V3_STEP_ORDER` places the step; `okstra_ctl.report_translation_dispatch.translate_report` owns it; `_translator_job_from_reservation` in `scripts/okstra_ctl/dispatch_core.py` still refuses two undispatched reservations for one run, which only a hand-made second reservation produces.
151
151
 
152
152
  The step succeeds only when the sidecar exists afterwards; a translator that exits 0 without publishing it fails the step. A failed `translate` does not stop the sequence: `render-views` still writes the view from the English body, `validate-run` records the missing sidecar as an advisory, and the result's resume hint starts at `--only translate --only render-views …` — run that once the cause (a host approval gate, an unavailable provider) is cleared rather than closing the run on an English view.
153
153
 
@@ -5,7 +5,7 @@ Edit here once; every profile picks the change up at next render. Do NOT
5
5
  add phase-specific rules to this file — phase rules stay in the per-
6
6
  profile document.
7
7
  -->
8
- - Team contract (shared): roster roles, model-assignment rules, dispatch invariants, and required-worker attempt rules are canonical in the team contract (`prompts/lead/team-contract.md`). Two consequences every phase honours: the host-native Okstra lead is synthesis-only (in `implementation`, distinct from the `Executor` and verifiers), and unnamed generic parallel workers never replace or extend the per-profile `Required workers:` roster. Prep-time model recommendations come from the catalog defaults in `okstra_ctl.models` (for example, `Codex worker` → `gpt-5.6-sol`); at dispatch time the task-manifest's materialized assignment is the only source — there is no dispatch-time fallback.
8
+ - Team contract (shared): roster roles, model-assignment rules, dispatch invariants, and required-worker attempt rules are canonical in the team contract (`prompts/lead/team-contract.md`). Two consequences every phase honours: the host-native Okstra lead is synthesis-only (in `implementation`, distinct from the `Executor` and verifiers), and unnamed generic parallel workers never replace or extend the per-profile `Required workers:` roster. Prep-time model recommendations come from the catalog defaults in `okstra_ctl.models` (for example, `Codex worker` → `gpt-6-sol`); at dispatch time the task-manifest's materialized assignment is the only source — there is no dispatch-time fallback.
9
9
  - Worker interaction model (shared — read before inferring behaviour from the roster):
10
10
  - the per-profile `Required workers:` block is a **roster**, not a behaviour contract. Each role's interaction mode changes across operating phases of the same run.
11
11
  - **Phase 4 / 5 (independent analysis)**: every analyser in the resolved provider assignment roster produces findings independently and has no access to another worker's output. `report-writer` does not analyse.
@@ -86,7 +86,7 @@ profile document.
86
86
  - **One decision per row.** A `decision` row asks one question. When a single option bundles two independent decisions, split the row. The tell is usually the option's `reach`: an option that is `cross-repo` only because one bundled clause crosses a repository boundary contains two decisions of different cost. The §5.5.9 adversarial round judges this semantic rule.
87
87
  - each row's `Blocks` column picks one of `{approval, next-phase, none}`. `approval` is reserved for items that gate an approval action, especially the `implementation-planning` `approved:` frontmatter flip; outside `implementation-planning`, unresolved brief reporter-confirmation rows use `next-phase` instead. `next-phase` blocks the next run from starting cleanly. `none` is informational/audit-only.
88
88
  - write every entry in full, descriptive sentences that a non-developer can act on without further context. Avoid abbreviations and internal jargon. The `Statement` cell must state *what* is needed, *why* the answer / attachment changes the next step, and (for `material`) *where* the user can find it and *where* to place it. The `Expected form` cell must state the answer shape (yes/no, one of the options, number/date, file path, short description, etc.); supply concrete option choices when applicable.
89
- - **Record coordinates only.** A clarification `statement`, `expectedForm`, or `options[]` answer/rationale may cite a report-record row id (`RB-002`, `C-014`) or a `path:line`. Do not cite a section number (`§4.7`, `§1`). That number exists only on one full reading copy. **Guideline — the cost is a worse question, not a failed run.** `scripts/okstra_ctl/user_response.py` `resolve_refs_from_record` resolves a row id against the report record and returns `null` for a `§` number, so the `okstra user-response` picker shows the user a bare `§4.7` with no context snippet next to the question it is supposed to explain. Nothing rejects the row.
89
+ - **Record coordinates only.** A clarification `statement`, `expectedForm`, or `options[]` answer/rationale may cite a report-record row id (`RB-002`, `C-014`) or a `path:line`. Do not cite a section number (`§4.7`, `§1`). That number exists only on one full reading copy. **Guideline — the cost is a worse question, not a failed run.** `scripts/okstra_ctl/user_response.py` `resolve_refs_from_record` resolves a row id against the report record and returns `null` for a `§` number, so the `okstra user-response` picker shows the user a bare `§4.7` with no context snippet next to the question it is supposed to explain. Nothing rejects the row. A row id the picker *can* resolve still reaches the user as an id: the `statement` says in one clause what the cited row requires before the question leans on it — see the lifecycle core contract "Asking the user" rule 4.
90
90
  - **Schema-v2 authors do not use the string grammar below.** A v2 `Kind=decision` row carries its choices in `options[]` (see the Clarification recommendation fragment for the field list); the renderer and the `okstra user-response` picker both build their selectable options from that array, so a choice that exists only in prose is a choice the user cannot pick. The rest of this bullet governs schema-v1 tables and analysis-worker result tables, which have only string cells.
91
91
  - if a schema-v1 table or an analysis-worker result table requires a recommended answer, alternatives, or an evidence-check note, encode it inside the existing 4-column schema: put evidence notes in `Statement` as `Evidence checked: <path:line>` or `Evidence checked: none — <human-only reason>`, and put recommendations/options in `Expected form` as `Recommended: (a) <answer> — <rationale>; Alternatives: (b) <option> (c) <option>`. The recommended answer is always the first option and MUST carry the `(a)` label; alternatives continue the same letter sequence from `(b)` (a lone alternative is `(b) <option>`, never restart at `(a)`), so the full option set reads `(a) (b) (c) …` in order and renders each as its own selectable option. Do **not** append a pick-one answer-space summary such as `(pick 1 of A / B)` or `(pick N of …)` to `<options>` — the rendered `<select>` already enforces single choice, and that annotation leaks verbatim into an option label. Do not add `Recommended`, `Evidence`, `Alternatives`, or `evidence-checked` columns, and do not break the merged record-meta cell back into separate columns.
92
92
  - For schema v2, data.json is canonical and the HTML exports answers to a user-response sidecar; the source report is never edited. `--resume-clarification` carries those answers into the next run. The lower-level `--clarification-response <path>` remains available for scripted runs.
@@ -33,6 +33,9 @@ reaches it. Enforcement: `_required_resource_path_candidates` names the three
33
33
  bodies for the `implementation-executor` audience, so composing the prompt is
34
34
  what puts them there — there is no separate dispatch-time heading check, and a
35
35
  missing heading means the composer did not run, not that a worker dropped it.
36
+ Both publishers compose it: the dispatch-time composer for a prompt that does not
37
+ exist yet, and `okstra agent-prompt materialize` for a prompt the lead publishes
38
+ first (`required_resource_block`), because dispatch reuses an existing prompt as is.
36
39
  The `<SENTINEL_PREFIX>_PREFLIGHT_MISSING` /
37
40
  `<SENTINEL_PREFIX>_POSTWRITE_GATE_MISSING` sentinels were the CLI-wrapper
38
41
  template's check; that template is gone.
@@ -46,7 +49,7 @@ template's check; that template is gone.
46
49
  - **DB / IO / SQL changes require real execution — mock-only is NOT validation evidence:** when this run's diff touches DB/IO/SQL (ORM / query-builder code — sequelize / typeorm / prisma / knex / raw SQL — `*.repository.*`, model/entity files, `migrations/**`, `*.sql`, or any changed query string), a mocked unit test cannot observe the SQL the query builder actually emits (observed failure class: `_implementation-verifier.md` §"DB / IO / SQL change — real-execution gate"). The executor MUST run the change against a real (or faithful-replica) datastore — the `db-test` validation step (plan `validation` db step, else `project.json.qaCommands.db-test`), targeting a **local / replica** DB — and cite its exact command + exit code in the final report's `Validation evidence`. If no real DB / `db-test` command is reachable, do NOT claim the change verified: label the DB portion `static-analysis only …, unverified (not executed)` in the report, surface it in the routing recommendation, and never downplay the real run as "too heavy". `git push` stays forbidden (universal list); the unverified DB state is carried forward so `final-verification` cannot accept it and `release-handoff` cannot push.
47
50
  - **External-source adapters — structure AND fixture both derive from a captured real sample; a self-authored fixture is NOT reality evidence:** when this run's diff builds or changes an `external-interface` or `transformation-mapping` surface (an HTTP / network client, or a parser / mapper of a third-party payload — HTML / JSON / XML / CSV originating outside this repo), the adapter's structural assumptions (selectors, field paths, expected response shape) AND the static fixture / golden that tests them MUST BOTH derive from a **captured real sample** of that payload — the capture cited in the stage's `external-interface` / `transformation-mapping` design-prep item, or one captured this run and recorded with its `source` + capture time. The captured sample is a static fixture (no live socket), so a parser test against it stays in source like any unit test — the Real-IO isolation rule below governs *live* calls, not the captured bytes. Do NOT hand-invent the shape and then hand-write a fixture that agrees with it: the passing test then only proves the code matches your assumption, never that the assumption matches reality (self-confirming oracle — the observed failure was a parser whose selectors existed in its synthetic fixture and in zero real pages: hundreds of green units over a fiction, and the whole structure built on the wrong shape). When no real sample is reachable (no network this run, or the brief supplied none), do NOT synthesize a stand-in and present its green tests as correctness: mark the adapter's shape `reality-unverified (no captured sample)` in `Validation evidence`, keep any placeholder fixture explicitly labelled an assumption (never validation evidence), and surface an explicit **user-owned** item in the routing recommendation to confirm against real data. Unlike the DB gate above this does NOT itself block acceptance — live external verification stays a user-owned item per `final-verification`'s External QA advisory policy — but a synthetic external fixture presented as reality-verified is exactly the mock-only external evidence the `final-verification` test-correctness pass is meant to reject.
48
51
  - **Real-IO test isolation (BLOCKING).** A test that exercises a **real** datastore, HTTP endpoint, external service, message queue, or filesystem — a live DB connection / DSN, a real `fetch` / `axios` / `http` request, an actual S3 / queue client, anything the project's normal CI test suite cannot run because that backend is absent — MUST be written under the task's qa scripts directory `<task_root>/qa/scripts/` (`<TASK_QA_PATH>/scripts`; the `qa/` root itself holds only data sidecars — the Tier 3 conformance manifest and `result-*.json`). It MUST NOT be written into the project source test tree — `src/**`, `test/**`, `tests/**`, `**/__test__/**`, `**/__tests__/**`, `*.spec.*`, `*.test.*`, or anywhere the project's lint/test globs collect. Two reasons: (a) the project's CI / normal suite has no real DB or network, so a real-IO test placed in source silently breaks the pipeline; (b) it is an okstra verification artifact, and the artifact-home rule confines okstra outputs to `.okstra/`. **The dividing line is the IO, not the intent:** a unit test that stubs/spies only *injected collaborators* (mock — no real socket, no real DB handle) is a TDD red-green artifact and stays in source; the moment a test opens a real connection or makes a real network call it belongs in qa. A stage's real-IO requirement check is a Tier 3 conformance script under `<task_root>/qa/scripts/` (declared via the implementation-planning conformance entry) — never smuggle real IO into a `*.spec.*` in source to make it run "as a unit test". The `db-test` real-execution gate above is satisfied by the conformance/db-test path against the replica, NOT by adding a live-DB `*.spec.*` to the project suite. **Author qa specs with the project's own test framework — never hand-roll `describe`/`it`/`expect`.** When the project ships a test runner as a devDependency (jest / vitest / pytest …), the qa spec uses it, invoked with the project config plus a discovery override pointing at the qa scripts dir (jest: `npx jest --config <project jest config> --roots <task_root>/qa/scripts --runInBand <spec-name>`) — the project config keeps module aliases resolving while the default sweep never collects the file; never widen the project's own test config to include qa paths. For TypeScript qa specs also write `<task_root>/qa/scripts/tsconfig.json` (`extends` the project tsconfig, adds the runner's `types` entry, `"include": ["**/*.ts"]`) so editors resolve path aliases and test globals — it is a qa artifact like the rest (untracked). **These qa artifacts stay untracked — never commit them.** `.okstra/**` is gitignored (the artifact-home rule); conformance scripts and their results are *executed* and recorded in the carry sidecar / verifier result, never written into git history. A committed `.okstra/qa` file is a stage-branch defect that leaks okstra internals into the eventual PR (see the `git add` rules below).
49
- - **Stage conformance script (BLOCKING when the approved plan declared `Conformance tests:`).** Planning only declared the path and `requires`. This run MUST write the script to that path under `<task_root>/qa/scripts/` and add the matching `<task_root>/qa/conformance-manifest.json` entry: `stageKey` (= `<task-id>-stage-<N>`), `script`, `runCommand`, `requirementIds`, `requires` (the set the plan declared), `passContract`, `exemption: null`, `waiver: null`. Do not skip this when the plan declared tests. If the plan declared `Conformance exemption:`, do not invent a script — with one exception: when this stage's diff touches a db/io/http/external surface the exemption promised it would not (the verifier's diff-surface cross-check names the surface), write the script and the manifest entry for this stage exactly as for a declared stage, with `requires` covering those surfaces. The approved plan is not rewritten; `validate-run.py` `_declared_conformance_errors` accepts an entry for a stage the plan exempted and still rejects one for a stage the plan does not have. The script's standard interface: a `main` that exits `0`=PASS / non-zero=FAIL, and whose stdout ends with `QA-RESULT: PASS|FAIL` followed by one `REQ <id>: PASS|FAIL: <reason>` line per requirement. The verifier runs `runCommand` from the **worktree cwd**, and that cwd is the tree under test. `runCommand` MUST NOT repoint it: a leading `cd <checkout> &&` sends the script at a tree without this stage's changes. Absolute paths are fine and usually necessary — the script and its `tsconfig` live under `<task_root>/qa/scripts/`, i.e. under `.okstra/`, and a worktree does not carry `.okstra/`. Point at those by absolute path; leave the cwd alone. **Enforced:** `scripts/okstra_ctl/conformance.py` `_check_entry` rejects a `runCommand` whose first word in any `&&` / `;` segment changes directory; `validators/validate-run.py` `_validate_conformance` fails the run if the inherited declaration has no script file.
52
+ - **Stage conformance script (BLOCKING when the approved plan declared `Conformance tests:`).** Planning only declared the path and `requires`. This run MUST write the script to that path under `<task_root>/qa/scripts/` and add the matching `<task_root>/qa/conformance-manifest.json` entry: `stageKey` (= `<task-id>-stage-<N>`), `script`, `runCommand`, `requirementIds`, `requires` (the set the plan declared, widened by any db/io/http/external surface this stage's diff touches that the plan left out — never narrowed; `validate-run.py` `_declared_conformance_errors` accepts a superset and rejects a subset), `passContract`, `exemption: null`, `waiver: null`. Do not skip this when the plan declared tests. If the plan declared `Conformance exemption:`, do not invent a script — with one exception: when this stage's diff touches a db/io/http/external surface the exemption promised it would not (the verifier's diff-surface cross-check names the surface), write the script and the manifest entry for this stage exactly as for a declared stage, with `requires` covering those surfaces. The approved plan is not rewritten; `validate-run.py` `_declared_conformance_errors` accepts an entry for a stage the plan exempted and still rejects one for a stage the plan does not have. The script's standard interface: a `main` that exits `0`=PASS / non-zero=FAIL, and whose stdout ends with `QA-RESULT: PASS|FAIL` followed by one `REQ <id>: PASS|FAIL: <reason>` line per requirement. The verifier runs `runCommand` from the **worktree cwd**, and that cwd is the tree under test. `runCommand` MUST NOT repoint it: a leading `cd <checkout> &&` sends the script at a tree without this stage's changes. Absolute paths are fine and usually necessary — the script and its `tsconfig` live under `<task_root>/qa/scripts/`, i.e. under `.okstra/`, and a worktree does not carry `.okstra/`. Point at those by absolute path; leave the cwd alone. **Enforced:** `scripts/okstra_ctl/conformance.py` `_check_entry` rejects a `runCommand` whose first word in any `&&` / `;` segment changes directory; `validators/validate-run.py` `_validate_conformance` fails the run if the inherited declaration has no script file.
50
53
  - read the approved plan at this prompt's `**Approved plan:**` anchor end-to-end and parse the `## 5.5 Stage Map`. Read this prompt's `**Stage for this implementation run:**` anchor: the single stage number this run owns. The runtime already selected and reserved this stage (one run = one stage) — do NOT recompute the start stage from `consumers.jsonl`. Both anchors are generated headers; when either is missing, stop and report `contract-violated` rather than inferring the value.
51
54
  - load every `runs/<plan-key>/carry/stage-<i>.json` for `i ∈ depends-on(this stage)` and inject them into the executor's working context as "runtime carry-in". For a `depends-on (none)` stage, no sidecar load — task-brief only.
52
55
  - this stage's `depends-on` are all already `status:done`. Its file list, step order, Stage Validation commands, Stage Exit Contract, and rollback path are the authoritative scope.
@@ -9,7 +9,7 @@ at Phase 5, BEFORE constructing the verifier worker dispatch prompts.
9
9
 
10
10
  - Every verdict comes from a fresh session with no shared context, never from the session that wrote the diff. Verifiers MUST NOT call Edit, Write, or any Bash command that mutates files outside the run's artifact directories. If a verifier wants a fix, it records the recommendation in its worker result; it does not apply the fix itself.
11
11
  - Session isolation is the primary self-review safeguard: each verifier is a separate invocation with its own context window. Reusing the executor's model is acceptable. The model comes from the run's stored assignment.
12
- - Verifiers read from the SAME working tree path the Executor used so they observe the exact diff the Executor produced. Source files, lockfiles, Git state, and shared links remain read-only. Declared verification commands may create their normal worktree-local build/cache outputs and install dependencies with a frozen lockfile. This is the bounded exception to the preceding write restriction; it does not permit source repairs, moving shared links, or redirecting build outputs outside the worktree. Run-owned logs remain in the run artifact directories.
12
+ - Verifiers read from the SAME working tree path the Executor used so they observe the exact diff the Executor produced. Source files, lockfiles, Git state, and shared links remain read-only. Declared verification commands may create their normal worktree-local build/cache outputs. **Do not re-run a dependency install** (`npm ci`, `yarn install --frozen-lockfile`, `pnpm install --frozen-lockfile`, and the like — typically the plan's `phase: pre` dependency-precondition row): the executor already installed into this worktree, and the other verifiers of this stage run at the same time against the same `node_modules`, so concurrent installs break each other and report exit codes that say nothing about the code (measured 2026-09-22, jobs dev-10860 stage 1: verifiers dispatched within 4 seconds, `EEXIST` on a workspace symlink in 2 of 3 reruns). Record such a row in `independentValidationRerun` as `not re-run — dependency install precondition, executor exit <code>`; it is outside the Discrepancy rule below. If the installed dependencies are actually missing or broken, the checks that need them fail, and that failure is what you report. This is a guideline: nothing parses the command log for installs. This is the bounded exception to the preceding write restriction; it does not permit source repairs, moving shared links, or redirecting build outputs outside the worktree. Run-owned logs remain in the run artifact directories.
13
13
 
14
14
  **Enforced:** `_validate_verifier_command_log_is_read_only` in `validators/validate-run.py` scans every `verifierResults[].readOnlyCommandLog` for source-mutating commands (`sed -i`, `git checkout --`/`restore`/`reset --hard`/`stash`/`clean`/`apply`, `patch -p`, `rm -rf`, `truncate`). Read-only forms (`git stash list`, `git clean --dry-run`, `git apply --check`) pass.
15
15
 
@@ -23,7 +23,7 @@ Every verifier acts as a QA gate, not just a diff reviewer. Trusting the executo
23
23
 
24
24
  ### Record the verification target
25
25
 
26
- Before each declared check, run `okstra verification-target --project-root <project-root> --run-manifest <run-manifest> --expected-head <executor-head> --command <exact-declared-command>` and preserve its JSON output under this run's artifact directory. This tool reads the active run's recorded worktree and checks its HEAD and source fingerprint; it never executes the command. Execute the declared command through the host's authorized tool with that worktree as its separate working-directory argument. Repeat the target check with `--baseline <saved-json>` after execution. Include both target-check results and the actual command outcome in `readOnlyCommandLog`.
26
+ Before each declared check, run `okstra verification-target --project-root <project-root> --run-manifest <run-manifest> --expected-head <executor-head> --command <exact-declared-command>` and preserve its JSON output under this run's artifact directory. This tool reads the active run's recorded worktree and checks its HEAD and source fingerprint; it never executes the command. Execute exactly the returned `target.command` through the host's authorized tool with `target.cwd` as its separate working-directory argument. The tool separates a legacy trailing `; existing helper: <file>:<lines>` annotation and removes a leading `cd` only when it names that exact worktree. Preserve `declaredCommand` and `commandAnnotation` when returned; never independently trim prose or rewrite shell syntax. Repeat the target check with the same original declaration and `--baseline <saved-json>` after execution. Include both target-check results and the actual command outcome in `readOnlyCommandLog`. Enforcement: `capture_verification_target` and `test_verification_target_returns_one_command_without_executing_it`.
27
27
 
28
28
  A target mismatch or changed fingerprint invalidates this check's evidence. Record it as an execution-target problem, preserve the source, and rerun only the affected verification after the lead resolves it. An unavailable environment or a check that never ran is not a code rejection. A matching target fingerprint proves source stability at the two observations; it does not prove the command ran or that the test covers the requirement. Keep the independent command outcome and coverage assessment.
29
29
 
@@ -150,7 +150,7 @@ The runtime AND the verifier MUST reject any `cmd` containing tokens that imply
150
150
 
151
151
  ### Discrepancy rule
152
152
 
153
- Tier 3 external-advisory discrepancies are excluded from this promotion: preserve the executor/verifier divergence in the advisory evidence and user-owned follow-up without changing the verdict. For Tier 1, Tier 2, and blocking `io`-only Tier 3, if the verifier's re-run result differs from what the executor reported (a passing test fails on re-run, a clean lint surfaces warnings, an exit code mismatches), the verifier MUST issue verdict `FAIL` with the divergence cited. The Okstra lead has no synthesis-time override for that FAIL. It MUST NOT prefer the executor's evidence over a verifier's reproduced result, with or without a cited reason: `_validate_verifier_fail_blocks_verdict` fails any report that publishes a passing `finalVerdict.verdictToken` while a `verifierResults[]` row records `FAIL`, and it reads only that row — no rationale field reaches it. A divergence the lead believes is not the code's (a flaky test, a documented environment delta) is resolved where the verdict is written, not after it: it is carried into the next fix run, where the verifier re-checks the finding and cites it `resolved`. Recording the reason in the report without changing the verdict row is what the routing recommendation and the user-owned follow-up are for.
153
+ Tier 3 external-advisory discrepancies are excluded from this promotion: preserve the executor/verifier divergence in the advisory evidence and user-owned follow-up without changing the verdict. A dependency-install row is not re-run (see the worktree rule above), so it never yields a discrepancy. For Tier 1, Tier 2, and blocking `io`-only Tier 3, if the verifier's re-run result differs from what the executor reported (a passing test fails on re-run, a clean lint surfaces warnings, an exit code mismatches), the verifier MUST issue verdict `FAIL` with the divergence cited. The Okstra lead has no synthesis-time override for that FAIL. It MUST NOT prefer the executor's evidence over a verifier's reproduced result, with or without a cited reason: `_validate_verifier_fail_blocks_verdict` fails any report that publishes a passing `finalVerdict.verdictToken` while a `verifierResults[]` row records `FAIL`, and it reads only that row — no rationale field reaches it. A divergence the lead believes is not the code's (a flaky test, a documented environment delta) is resolved where the verdict is written, not after it: it is carried into the next fix run, where the verifier re-checks the finding and cites it `resolved`. Recording the reason in the report without changing the verdict row is what the routing recommendation and the user-owned follow-up are for.
154
154
 
155
155
  **When the re-run matched, leave `discrepancy` empty and omit the `Discrepancy` line.** The field records a divergence, so an empty field *is* the record of "no divergence" — the schema makes it optional for exactly that. Do not write `None`, `n/a`, or a sentence explaining that nothing diverged: the check reads any non-empty text as a recorded divergence, so a verifier that states its clean result in prose is failed for the result it is reporting.
156
156
 
@@ -0,0 +1,31 @@
1
+ {
2
+ "schemaVersion": "1.0",
3
+ "id": "change-impact-analysis",
4
+ "roles": [
5
+ {
6
+ "mode": "static",
7
+ "roleId": "analyser",
8
+ "dutyId": "analysis-worker",
9
+ "min": 2,
10
+ "recommended": 3,
11
+ "max": 5
12
+ },
13
+ {
14
+ "mode": "static",
15
+ "roleId": "report-writer",
16
+ "dutyId": "report-writer",
17
+ "min": 1,
18
+ "recommended": 1,
19
+ "max": 1
20
+ },
21
+ {
22
+ "mode": "dynamic",
23
+ "roleId": "verifier",
24
+ "dutyId": "reverification-worker",
25
+ "sourceRoleIds": [
26
+ "analyser"
27
+ ],
28
+ "activation": "per-selected-source"
29
+ }
30
+ ]
31
+ }
@@ -1,25 +1,5 @@
1
1
  # Change Impact Analysis Profile
2
2
 
3
- ```yaml
4
- roles:
5
- - role: analyser
6
- min: 2
7
- recommended: 3
8
- max: 5
9
- duty: analysis-worker
10
- - role: report-writer
11
- min: 1
12
- recommended: 1
13
- max: 1
14
- duty: report-writer
15
- - role: verifier
16
- min: 0
17
- recommended: 0
18
- max: 0
19
- duty: reverification-worker
20
- dynamic: true
21
- ```
22
-
23
3
  - Purpose: assess the read-only impact of a proposed change, including preserved behavior, affected dependencies, and constraints that a later planning phase must resolve
24
4
  - Required workers:
25
5
  - claude
@@ -0,0 +1,39 @@
1
+ {
2
+ "schemaVersion": "1.0",
3
+ "id": "error-analysis",
4
+ "roles": [
5
+ {
6
+ "mode": "static",
7
+ "roleId": "analyser",
8
+ "dutyId": "diagnosis-worker",
9
+ "min": 2,
10
+ "recommended": 3,
11
+ "max": 5
12
+ },
13
+ {
14
+ "mode": "static",
15
+ "roleId": "critic",
16
+ "dutyId": "scope-critic",
17
+ "min": 0,
18
+ "recommended": 1,
19
+ "max": 1
20
+ },
21
+ {
22
+ "mode": "static",
23
+ "roleId": "report-writer",
24
+ "dutyId": "report-writer",
25
+ "min": 1,
26
+ "recommended": 1,
27
+ "max": 1
28
+ },
29
+ {
30
+ "mode": "dynamic",
31
+ "roleId": "verifier",
32
+ "dutyId": "reverification-worker",
33
+ "sourceRoleIds": [
34
+ "analyser"
35
+ ],
36
+ "activation": "per-selected-source"
37
+ }
38
+ ]
39
+ }
@@ -1,30 +1,5 @@
1
1
  # Error Analysis Profile
2
2
 
3
- ```yaml
4
- roles:
5
- - role: analyser
6
- min: 2
7
- recommended: 3
8
- max: 5
9
- duty: diagnosis-worker
10
- - role: critic
11
- min: 0
12
- recommended: 1
13
- max: 1
14
- duty: scope-critic
15
- - role: report-writer
16
- min: 1
17
- recommended: 1
18
- max: 1
19
- duty: report-writer
20
- - role: verifier
21
- min: 0
22
- recommended: 0
23
- max: 0
24
- duty: reverification-worker
25
- dynamic: true
26
- ```
27
-
28
3
  - Purpose: analyse reported errors or incidents and identify likely causes, missing evidence, and validation paths
29
4
  - Required workers:
30
5
  - claude