okstra 0.186.7 → 0.187.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 (541) hide show
  1. package/README.md +1 -1
  2. package/dist/cli-registry.d.mts +88 -1
  3. package/dist/cli-registry.mjs +68 -111
  4. package/dist/cli-registry.mjs.map +1 -1
  5. package/dist/commands/execute/render-bundle.mjs +0 -1
  6. package/dist/commands/execute/render-bundle.mjs.map +1 -1
  7. package/dist/commands/execute/run.mjs +8 -3
  8. package/dist/commands/execute/run.mjs.map +1 -1
  9. package/dist/commands/lifecycle/check-project.mjs +1 -14
  10. package/dist/commands/lifecycle/check-project.mjs.map +1 -1
  11. package/dist/commands/lifecycle/config.mjs +38 -40
  12. package/dist/commands/lifecycle/config.mjs.map +1 -1
  13. package/dist/commands/lifecycle/doctor.d.mts +22 -7
  14. package/dist/commands/lifecycle/doctor.mjs +77 -49
  15. package/dist/commands/lifecycle/doctor.mjs.map +1 -1
  16. package/dist/commands/lifecycle/install.d.mts +12 -10
  17. package/dist/commands/lifecycle/install.mjs +104 -39
  18. package/dist/commands/lifecycle/install.mjs.map +1 -1
  19. package/dist/commands/lifecycle/paths.mjs +8 -3
  20. package/dist/commands/lifecycle/paths.mjs.map +1 -1
  21. package/dist/commands/lifecycle/preflight.mjs +2 -1
  22. package/dist/commands/lifecycle/preflight.mjs.map +1 -1
  23. package/dist/commands/lifecycle/setup.mjs +22 -36
  24. package/dist/commands/lifecycle/setup.mjs.map +1 -1
  25. package/dist/commands/lifecycle/uninstall.d.mts +4 -2
  26. package/dist/commands/lifecycle/uninstall.mjs +59 -15
  27. package/dist/commands/lifecycle/uninstall.mjs.map +1 -1
  28. package/dist/commands/memory/memory.mjs +7 -1
  29. package/dist/commands/memory/memory.mjs.map +1 -1
  30. package/dist/lib/helper-scripts.d.mts +1 -1
  31. package/dist/lib/helper-scripts.mjs +10 -18
  32. package/dist/lib/helper-scripts.mjs.map +1 -1
  33. package/dist/lib/host-config.d.mts +72 -0
  34. package/dist/lib/host-config.mjs +404 -0
  35. package/dist/lib/host-config.mjs.map +1 -0
  36. package/dist/lib/host-registry-client.d.mts +2 -2
  37. package/dist/lib/host-registry-client.mjs +0 -3
  38. package/dist/lib/host-registry-client.mjs.map +1 -1
  39. package/dist/lib/install-assets.d.mts +1 -0
  40. package/dist/lib/install-assets.mjs +4 -0
  41. package/dist/lib/install-assets.mjs.map +1 -1
  42. package/dist/lib/proc.d.mts +2 -0
  43. package/dist/lib/proc.mjs +12 -0
  44. package/dist/lib/proc.mjs.map +1 -1
  45. package/dist/lib/python-command.d.mts +2 -0
  46. package/dist/lib/python-command.mjs +52 -0
  47. package/dist/lib/python-command.mjs.map +1 -0
  48. package/dist/lib/python-helper.d.mts +2 -5
  49. package/dist/lib/python-helper.mjs +3 -52
  50. package/dist/lib/python-helper.mjs.map +1 -1
  51. package/dist/lib/runtime-payload.d.mts +23 -0
  52. package/dist/lib/runtime-payload.mjs +57 -0
  53. package/dist/lib/runtime-payload.mjs.map +1 -0
  54. package/dist/lib/types.d.mts +24 -13
  55. package/docs/architecture/storage-model.md +21 -5
  56. package/docs/architecture.md +37 -36
  57. package/docs/cli.md +55 -76
  58. package/docs/coding-rules.md +295 -0
  59. package/docs/container.md +10 -36
  60. package/docs/contributor-change-matrix.md +1 -1
  61. package/docs/for-ai/skills/okstra-code-review.md +0 -1
  62. package/docs/for-ai/skills/okstra-container-build.md +8 -41
  63. package/docs/for-ai/skills/okstra-inspect.md +4 -4
  64. package/docs/for-ai/skills/okstra-manager.md +2 -2
  65. package/docs/for-ai/skills/okstra-rollup.md +0 -1
  66. package/docs/for-ai/skills/okstra-run.md +3 -3
  67. package/docs/for-ai/skills/okstra-user-response.md +0 -1
  68. package/docs/performance-improvement-plan-v2.md +1 -1
  69. package/docs/project-structure-overview.md +62 -62
  70. package/docs/task-process/README.md +3 -3
  71. package/docs/task-process/common-flow.md +1 -1
  72. package/docs/task-process/error-analysis.md +3 -3
  73. package/docs/task-process/implementation-planning.md +3 -1
  74. package/docs/task-process/release-handoff.md +1 -1
  75. package/package.json +3 -2
  76. package/runtime/BUILD.json +2 -2
  77. package/runtime/agents/workers/claude-worker.md +6 -11
  78. package/runtime/agents/workers/report-writer-worker.md +1 -1
  79. package/runtime/bin/lib/okstra/cli.sh +4 -0
  80. package/runtime/bin/lib/okstra/globals.sh +2 -0
  81. package/runtime/bin/lib/okstra/interactive.sh +26 -173
  82. package/runtime/bin/lib/okstra/project-resolver.sh +23 -61
  83. package/runtime/bin/lib/okstra/usage.sh +3 -3
  84. package/runtime/bin/okstra-compact-reminder.sh +1 -5
  85. package/runtime/bin/okstra-error-log.py +19 -2
  86. package/runtime/bin/okstra-import-check.py +26 -0
  87. package/runtime/bin/okstra-inject-report-index.py +5 -4
  88. package/runtime/bin/okstra-provider-exec.py +22 -17
  89. package/runtime/bin/okstra-render-final-report.py +16 -6
  90. package/runtime/bin/okstra-render-report-views.py +25 -18
  91. package/runtime/bin/okstra-report-translate.py +30 -18
  92. package/runtime/bin/okstra-spawn-followups.py +4 -0
  93. package/runtime/bin/okstra-token-usage.py +3 -0
  94. package/runtime/bin/okstra.sh +4 -2
  95. package/runtime/bin/okstra_bootstrap.py +58 -0
  96. package/runtime/prompts/coding-preflight/overview.md +1 -1
  97. package/runtime/prompts/duties/planning-worker.md +1 -1
  98. package/runtime/prompts/launch.template.md +35 -9
  99. package/runtime/prompts/lead/convergence.md +96 -87
  100. package/runtime/prompts/lead/okstra-lead-contract.md +63 -12
  101. package/runtime/prompts/lead/plan-body-verification.md +65 -46
  102. package/runtime/prompts/lead/report-writer.md +34 -4
  103. package/runtime/prompts/lead/team-contract.md +4 -1
  104. package/runtime/prompts/profiles/_clarification-recommendation.md +2 -1
  105. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  106. package/runtime/prompts/profiles/_common-contract.md +3 -3
  107. package/runtime/prompts/profiles/_coverage-critic.md +1 -1
  108. package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
  109. package/runtime/prompts/profiles/_implementation-executor.md +3 -1
  110. package/runtime/prompts/profiles/_implementation-verifier.md +16 -2
  111. package/runtime/prompts/profiles/error-analysis.md +4 -3
  112. package/runtime/prompts/profiles/final-verification.md +2 -2
  113. package/runtime/prompts/profiles/implementation-planning.md +22 -17
  114. package/runtime/prompts/profiles/implementation.md +3 -2
  115. package/runtime/prompts/profiles/requirements-discovery.md +2 -1
  116. package/runtime/prompts/wizard/prompts.ko.json +29 -8
  117. package/runtime/python/okstra_ctl/__init__.py +6 -27
  118. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -5
  119. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +21 -2
  120. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +3 -12
  121. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +40 -22
  122. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +14 -21
  123. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +37 -20
  124. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +86 -14
  125. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +12 -12
  126. package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +4 -7
  127. package/runtime/python/okstra_ctl/agent/__init__.py +1 -0
  128. package/runtime/python/okstra_ctl/{agent_activity.py → agent/activity.py} +12 -1
  129. package/runtime/python/okstra_ctl/{agent_invocation.py → agent/invocation.py} +34 -5
  130. package/runtime/python/okstra_ctl/agent/prompt_cli/__init__.py +19 -0
  131. package/runtime/python/okstra_ctl/agent/prompt_cli/__main__.py +11 -0
  132. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +215 -0
  133. package/runtime/python/okstra_ctl/agent/prompt_cli/dynamic_verifier.py +155 -0
  134. package/runtime/python/okstra_ctl/agent/prompt_cli/emit.py +66 -0
  135. package/runtime/python/okstra_ctl/agent/prompt_cli/inputs.py +138 -0
  136. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +408 -0
  137. package/runtime/python/okstra_ctl/agent/prompt_cli/results.py +197 -0
  138. package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +107 -0
  139. package/runtime/python/okstra_ctl/analysis_packet.py +47 -4
  140. package/runtime/python/okstra_ctl/approval_decisions.py +260 -8
  141. package/runtime/python/okstra_ctl/assignment_resolver.py +0 -12
  142. package/runtime/python/okstra_ctl/attempt_evidence.py +12 -24
  143. package/runtime/python/okstra_ctl/blocking_checks.py +251 -0
  144. package/runtime/python/okstra_ctl/brief_frontmatter.py +0 -7
  145. package/runtime/python/okstra_ctl/clarification_items/__init__.py +102 -0
  146. package/runtime/python/okstra_ctl/clarification_items/carry.py +224 -0
  147. package/runtime/python/okstra_ctl/clarification_items/dispositions.py +135 -0
  148. package/runtime/python/okstra_ctl/clarification_items/parsing.py +253 -0
  149. package/runtime/python/okstra_ctl/clarification_items/rows.py +115 -0
  150. package/runtime/python/okstra_ctl/clarification_items/scan.py +215 -0
  151. package/runtime/python/okstra_ctl/clarification_items/sidecars.py +211 -0
  152. package/runtime/python/okstra_ctl/cmux.py +38 -31
  153. package/runtime/python/okstra_ctl/code_review_target.py +163 -1
  154. package/runtime/python/okstra_ctl/conformance.py +23 -0
  155. package/runtime/python/okstra_ctl/container.py +38 -421
  156. package/runtime/python/okstra_ctl/context_cost.py +16 -2
  157. package/runtime/python/okstra_ctl/contract_graph_cli.py +7 -0
  158. package/runtime/python/okstra_ctl/convergence.py +101 -15
  159. package/runtime/python/okstra_ctl/convergence_critic_prompt.py +345 -0
  160. package/runtime/python/okstra_ctl/convergence_engine.py +382 -70
  161. package/runtime/python/okstra_ctl/convergence_store.py +5 -29
  162. package/runtime/python/okstra_ctl/design_prep.py +108 -12
  163. package/runtime/python/okstra_ctl/design_snapshot.py +11 -1
  164. package/runtime/python/okstra_ctl/design_surfaces.py +29 -2
  165. package/runtime/python/okstra_ctl/dispatch_core.py +102 -25
  166. package/runtime/python/okstra_ctl/dispatch_state.py +268 -39
  167. package/runtime/python/okstra_ctl/doctor_cli.py +48 -0
  168. package/runtime/python/okstra_ctl/domain/worker_exec.py +10 -5
  169. package/runtime/python/okstra_ctl/domain/worker_presentation.py +21 -1
  170. package/runtime/python/okstra_ctl/domain/worker_stream.py +52 -21
  171. package/runtime/python/okstra_ctl/domain/write_policy.py +219 -0
  172. package/runtime/python/okstra_ctl/entrypoints/hosts.py +9 -2
  173. package/runtime/python/okstra_ctl/error_log_core.py +1 -1
  174. package/runtime/python/okstra_ctl/error_report.py +14 -0
  175. package/runtime/python/okstra_ctl/error_zip.py +18 -2
  176. package/runtime/python/okstra_ctl/execution_identity.py +100 -7
  177. package/runtime/python/okstra_ctl/execution_manifest.py +67 -20
  178. package/runtime/python/okstra_ctl/execution_mutation_audit.py +90 -8
  179. package/runtime/python/okstra_ctl/final_report_paths.py +17 -0
  180. package/runtime/python/okstra_ctl/final_report_schema.py +90 -16
  181. package/runtime/python/okstra_ctl/git_reconcile.py +22 -2
  182. package/runtime/python/okstra_ctl/handoff.py +24 -1
  183. package/runtime/python/okstra_ctl/ids.py +6 -16
  184. package/runtime/python/okstra_ctl/implementation_direction.py +142 -33
  185. package/runtime/python/okstra_ctl/implementation_options.py +25 -11
  186. package/runtime/python/okstra_ctl/implementation_outcome.py +5 -2
  187. package/runtime/python/okstra_ctl/improvement_lenses.py +0 -14
  188. package/runtime/python/okstra_ctl/incremental_carry.py +69 -8
  189. package/runtime/python/okstra_ctl/incremental_scope.py +173 -12
  190. package/runtime/python/okstra_ctl/index.py +2 -2
  191. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +48 -8
  192. package/runtime/python/okstra_ctl/interactive_cli.py +223 -0
  193. package/runtime/python/okstra_ctl/json_boundary.py +10 -0
  194. package/runtime/python/okstra_ctl/listing.py +0 -91
  195. package/runtime/python/okstra_ctl/locks.py +5 -19
  196. package/runtime/python/okstra_ctl/log_report.py +14 -0
  197. package/runtime/python/okstra_ctl/manager_cli.py +22 -3
  198. package/runtime/python/okstra_ctl/manager_launch.py +4 -8
  199. package/runtime/python/okstra_ctl/material.py +2 -2
  200. package/runtime/python/okstra_ctl/migrate.py +23 -1
  201. package/runtime/python/okstra_ctl/model_cli.py +19 -5
  202. package/runtime/python/okstra_ctl/model_discovery.py +46 -47
  203. package/runtime/python/okstra_ctl/model_io/__init__.py +1 -0
  204. package/runtime/python/okstra_ctl/model_io/lines.py +163 -0
  205. package/runtime/python/okstra_ctl/model_io/references.py +370 -0
  206. package/runtime/python/okstra_ctl/model_io/renderers.py +498 -0
  207. package/runtime/python/okstra_ctl/model_io_cli.py +30 -945
  208. package/runtime/python/okstra_ctl/model_pool.py +10 -6
  209. package/runtime/python/okstra_ctl/models.py +8 -8
  210. package/runtime/python/okstra_ctl/mutation_recovery.py +5 -63
  211. package/runtime/python/okstra_ctl/next_phase.py +121 -69
  212. package/runtime/python/okstra_ctl/pane_reclaim.py +23 -0
  213. package/runtime/python/okstra_ctl/pane_title.py +11 -8
  214. package/runtime/python/okstra_ctl/path_hints.py +36 -11
  215. package/runtime/python/okstra_ctl/paths.py +76 -8
  216. package/runtime/python/okstra_ctl/plan_items.py +132 -16
  217. package/runtime/python/okstra_ctl/plan_items_cli.py +486 -36
  218. package/runtime/python/okstra_ctl/plan_run_root.py +1 -1
  219. package/runtime/python/okstra_ctl/plan_validate_cli.py +51 -0
  220. package/runtime/python/okstra_ctl/plan_verify_cli.py +80 -0
  221. package/runtime/python/okstra_ctl/prepare_error.py +20 -0
  222. package/runtime/python/okstra_ctl/prior_planning.py +157 -0
  223. package/runtime/python/okstra_ctl/profile_show.py +18 -1
  224. package/runtime/python/okstra_ctl/project_setup_cli.py +140 -0
  225. package/runtime/python/okstra_ctl/recap.py +22 -1
  226. package/runtime/python/okstra_ctl/reconcile.py +84 -0
  227. package/runtime/python/okstra_ctl/registry/host_registry.py +18 -1
  228. package/runtime/python/okstra_ctl/render.py +98 -32
  229. package/runtime/python/okstra_ctl/render_final_report.py +3 -7
  230. package/runtime/python/okstra_ctl/report_assembly.py +297 -7
  231. package/runtime/python/okstra_ctl/report_contract.py +30 -7
  232. package/runtime/python/okstra_ctl/report_finalize.py +206 -15
  233. package/runtime/python/okstra_ctl/report_html/common.py +2 -2
  234. package/runtime/python/okstra_ctl/report_html/render.py +8 -12
  235. package/runtime/python/okstra_ctl/report_language.py +8 -4
  236. package/runtime/python/okstra_ctl/report_narrative.py +90 -3
  237. package/runtime/python/okstra_ctl/report_projections.py +8 -2
  238. package/runtime/python/okstra_ctl/report_synthesis_packet.py +258 -1
  239. package/runtime/python/okstra_ctl/report_translation.py +2 -35
  240. package/runtime/python/okstra_ctl/report_views.py +4 -22
  241. package/runtime/python/okstra_ctl/resolve_task_key.py +13 -0
  242. package/runtime/python/okstra_ctl/rollup.py +13 -0
  243. package/runtime/python/okstra_ctl/run.py +639 -138
  244. package/runtime/python/okstra_ctl/run_audit.py +13 -0
  245. package/runtime/python/okstra_ctl/run_index_row.py +0 -12
  246. package/runtime/python/okstra_ctl/seeding.py +21 -11
  247. package/runtime/python/okstra_ctl/sequence.py +1 -1
  248. package/runtime/python/okstra_ctl/session.py +64 -14
  249. package/runtime/python/okstra_ctl/set_work_status.py +18 -0
  250. package/runtime/python/okstra_ctl/stage_integrate.py +15 -1
  251. package/runtime/python/okstra_ctl/stage_ledger.py +1 -1
  252. package/runtime/python/okstra_ctl/stage_map.py +57 -11
  253. package/runtime/python/okstra_ctl/stage_map_cli.py +83 -0
  254. package/runtime/python/okstra_ctl/stage_map_view.py +65 -0
  255. package/runtime/python/okstra_ctl/stage_targets.py +3 -12
  256. package/runtime/python/okstra_ctl/task_list_cli.py +138 -0
  257. package/runtime/python/okstra_ctl/task_show_cli.py +66 -0
  258. package/runtime/python/okstra_ctl/task_target.py +4 -16
  259. package/runtime/python/okstra_ctl/team.py +49 -2
  260. package/runtime/python/okstra_ctl/time_report.py +17 -2
  261. package/runtime/python/okstra_ctl/usage_report.py +11 -0
  262. package/runtime/python/okstra_ctl/user_response.py +55 -218
  263. package/runtime/python/okstra_ctl/user_response_values.py +242 -0
  264. package/runtime/python/okstra_ctl/validation_contract.py +3 -0
  265. package/runtime/python/okstra_ctl/verification_target.py +68 -0
  266. package/runtime/python/okstra_ctl/wizard.py +457 -92
  267. package/runtime/python/okstra_ctl/worker_artifacts.py +75 -16
  268. package/runtime/python/okstra_ctl/worker_audit_check.py +22 -0
  269. package/runtime/python/okstra_ctl/worker_dispatch.py +21 -0
  270. package/runtime/python/okstra_ctl/worker_liveness.py +33 -0
  271. package/runtime/python/okstra_ctl/worker_prompt_body.py +15 -2
  272. package/runtime/python/okstra_ctl/worker_prompt_contract.py +98 -7
  273. package/runtime/python/okstra_ctl/worker_prompt_headers.py +24 -2
  274. package/runtime/python/okstra_ctl/worker_prompt_policy.py +18 -3
  275. package/runtime/python/okstra_ctl/worker_request.py +2 -1
  276. package/runtime/python/okstra_ctl/worker_runner.py +16 -9
  277. package/runtime/python/okstra_ctl/worker_state.py +16 -1
  278. package/runtime/python/okstra_ctl/workflow.py +18 -1
  279. package/runtime/python/okstra_ctl/worktree/__init__.py +92 -0
  280. package/runtime/python/okstra_ctl/worktree/cleanliness.py +89 -0
  281. package/runtime/python/okstra_ctl/worktree/decisions.py +127 -0
  282. package/runtime/python/okstra_ctl/worktree/git_ops.py +165 -0
  283. package/runtime/python/okstra_ctl/worktree/linking.py +249 -0
  284. package/runtime/python/okstra_ctl/worktree/naming.py +91 -0
  285. package/runtime/python/okstra_ctl/worktree/provision.py +385 -0
  286. package/runtime/python/okstra_ctl/worktree/sync_config.py +181 -0
  287. package/runtime/python/okstra_ctl/worktree_cli.py +75 -0
  288. package/runtime/python/okstra_ctl/worktree_lookup_cli.py +39 -0
  289. package/runtime/python/okstra_ctl/worktree_registry.py +7 -0
  290. package/runtime/python/okstra_ctl/worktree_status_cli.py +53 -0
  291. package/runtime/python/okstra_ctl/write_policy.py +90 -213
  292. package/runtime/python/okstra_project/__init__.py +0 -4
  293. package/runtime/python/okstra_project/dirs.py +2 -2
  294. package/runtime/python/okstra_project/phase_pointer.py +84 -0
  295. package/runtime/python/okstra_project/slug.py +24 -0
  296. package/runtime/python/okstra_project/state.py +22 -230
  297. package/runtime/python/okstra_token_usage/claude.py +9 -1
  298. package/runtime/python/okstra_token_usage/cli.py +3 -1
  299. package/runtime/python/okstra_token_usage/collect.py +71 -10
  300. package/runtime/python/okstra_token_usage/cursor.py +2 -0
  301. package/runtime/python/okstra_token_usage/grok.py +1 -5
  302. package/runtime/python/okstra_token_usage/paths.py +26 -0
  303. package/runtime/python/okstra_token_usage/pricing.py +19 -7
  304. package/runtime/schemas/convergence-critic-results-v1.0.schema.json +5 -0
  305. package/runtime/schemas/execution-manifest-v2.schema.json +10 -10
  306. package/runtime/schemas/final-report-v2.0.schema.json +9 -26
  307. package/runtime/schemas/final-report-v3.0.schema.json +173 -41
  308. package/runtime/schemas/report-narrative-v3.0.schema.json +3 -2
  309. package/runtime/skills/okstra-brief-gen/SKILL.md +35 -2
  310. package/runtime/skills/okstra-container-build/SKILL.md +16 -47
  311. package/runtime/skills/okstra-inspect/facets/history.md +1 -1
  312. package/runtime/skills/okstra-inspect/facets/status.md +7 -6
  313. package/runtime/skills/okstra-pr-gen/SKILL.md +1 -1
  314. package/runtime/skills/okstra-run/SKILL.md +7 -5
  315. package/runtime/templates/report-writer-prompt-preamble.md +1 -1
  316. package/runtime/templates/reports/brief.template.md +6 -2
  317. package/runtime/templates/reports/error-analysis-input.template.md +2 -0
  318. package/runtime/templates/reports/final-verification-input.template.md +2 -0
  319. package/runtime/templates/reports/implementation-input.template.md +7 -1
  320. package/runtime/templates/reports/implementation-planning-input.template.md +4 -0
  321. package/runtime/templates/reports/quick-input.template.md +2 -0
  322. package/runtime/templates/reports/release-handoff-input.template.md +6 -1
  323. package/runtime/templates/reports/report.js +20 -3
  324. package/runtime/templates/reports/schedule.template.md +2 -0
  325. package/runtime/templates/reports/settings.template.json +0 -10
  326. package/runtime/templates/reports/task-brief.template.md +3 -0
  327. package/runtime/templates/reports/user-response.template.md +7 -3
  328. package/runtime/validators/checks/fixtures-01.py +57 -0
  329. package/runtime/validators/checks/fixtures-02.py +565 -0
  330. package/runtime/validators/checks/runners-01.py +108 -0
  331. package/runtime/validators/checks/validate-assets-01.py +60 -0
  332. package/runtime/validators/checks/validate-prompt-metadata-01.py +261 -0
  333. package/runtime/validators/checks/validate-tasks-01.py +46 -0
  334. package/runtime/validators/checks/validate-tasks-02.py +85 -0
  335. package/runtime/validators/checks/validate-tasks-03.py +61 -0
  336. package/runtime/validators/checks/validate-tasks-04.py +118 -0
  337. package/runtime/validators/forbidden_actions.py +73 -3
  338. package/runtime/validators/lib/common.sh +5 -0
  339. package/runtime/validators/lib/fixtures.sh +7 -591
  340. package/runtime/validators/lib/paths.sh +13 -0
  341. package/runtime/validators/lib/runners.sh +6 -104
  342. package/runtime/validators/lib/summary.sh +1 -1
  343. package/runtime/validators/lib/validate-assets.sh +6 -56
  344. package/runtime/validators/lib/validate-prompt-metadata.sh +6 -257
  345. package/runtime/validators/lib/validate-tasks.sh +9 -294
  346. package/runtime/validators/validate-implementation-plan-stages.py +4 -4
  347. package/runtime/validators/validate-run.py +1369 -2654
  348. package/runtime/validators/validate-workflow.sh +56 -16
  349. package/runtime/validators/validate_improvement_report.py +2 -1
  350. package/runtime/validators/validate_session_conformance.py +295 -49
  351. package/dist/commands/execute/agent-prompt.d.mts +0 -1
  352. package/dist/commands/execute/agent-prompt.mjs +0 -24
  353. package/dist/commands/execute/agent-prompt.mjs.map +0 -1
  354. package/dist/commands/execute/codex-dispatch.d.mts +0 -3
  355. package/dist/commands/execute/codex-dispatch.mjs +0 -6
  356. package/dist/commands/execute/codex-dispatch.mjs.map +0 -1
  357. package/dist/commands/execute/codex-run.d.mts +0 -3
  358. package/dist/commands/execute/codex-run.mjs +0 -62
  359. package/dist/commands/execute/codex-run.mjs.map +0 -1
  360. package/dist/commands/execute/convergence.d.mts +0 -1
  361. package/dist/commands/execute/convergence.mjs +0 -38
  362. package/dist/commands/execute/convergence.mjs.map +0 -1
  363. package/dist/commands/execute/error-log.d.mts +0 -1
  364. package/dist/commands/execute/error-log.mjs +0 -18
  365. package/dist/commands/execute/error-log.mjs.map +0 -1
  366. package/dist/commands/execute/git-reconcile.d.mts +0 -1
  367. package/dist/commands/execute/git-reconcile.mjs +0 -30
  368. package/dist/commands/execute/git-reconcile.mjs.map +0 -1
  369. package/dist/commands/execute/handoff.d.mts +0 -1
  370. package/dist/commands/execute/handoff.mjs +0 -31
  371. package/dist/commands/execute/handoff.mjs.map +0 -1
  372. package/dist/commands/execute/incremental-carry.d.mts +0 -1
  373. package/dist/commands/execute/incremental-carry.mjs +0 -20
  374. package/dist/commands/execute/incremental-carry.mjs.map +0 -1
  375. package/dist/commands/execute/incremental-scope.d.mts +0 -1
  376. package/dist/commands/execute/incremental-scope.mjs +0 -29
  377. package/dist/commands/execute/incremental-scope.mjs.map +0 -1
  378. package/dist/commands/execute/integrate-stages.d.mts +0 -1
  379. package/dist/commands/execute/integrate-stages.mjs +0 -24
  380. package/dist/commands/execute/integrate-stages.mjs.map +0 -1
  381. package/dist/commands/execute/pane-title.d.mts +0 -1
  382. package/dist/commands/execute/pane-title.mjs +0 -20
  383. package/dist/commands/execute/pane-title.mjs.map +0 -1
  384. package/dist/commands/execute/plan-items.d.mts +0 -1
  385. package/dist/commands/execute/plan-items.mjs +0 -9
  386. package/dist/commands/execute/plan-items.mjs.map +0 -1
  387. package/dist/commands/execute/plan-validate.d.mts +0 -1
  388. package/dist/commands/execute/plan-validate.mjs +0 -68
  389. package/dist/commands/execute/plan-validate.mjs.map +0 -1
  390. package/dist/commands/execute/plan-verify.d.mts +0 -1
  391. package/dist/commands/execute/plan-verify.mjs +0 -43
  392. package/dist/commands/execute/plan-verify.mjs.map +0 -1
  393. package/dist/commands/execute/spawn-followups.d.mts +0 -1
  394. package/dist/commands/execute/spawn-followups.mjs +0 -22
  395. package/dist/commands/execute/spawn-followups.mjs.map +0 -1
  396. package/dist/commands/execute/team.d.mts +0 -3
  397. package/dist/commands/execute/team.mjs +0 -66
  398. package/dist/commands/execute/team.mjs.map +0 -1
  399. package/dist/commands/execute/token-usage.d.mts +0 -1
  400. package/dist/commands/execute/token-usage.mjs +0 -19
  401. package/dist/commands/execute/token-usage.mjs.map +0 -1
  402. package/dist/commands/execute/worker-audit-check.d.mts +0 -1
  403. package/dist/commands/execute/worker-audit-check.mjs +0 -34
  404. package/dist/commands/execute/worker-audit-check.mjs.map +0 -1
  405. package/dist/commands/execute/worker-dispatch.d.mts +0 -7
  406. package/dist/commands/execute/worker-dispatch.mjs +0 -64
  407. package/dist/commands/execute/worker-dispatch.mjs.map +0 -1
  408. package/dist/commands/execute/worker-state.d.mts +0 -1
  409. package/dist/commands/execute/worker-state.mjs +0 -28
  410. package/dist/commands/execute/worker-state.mjs.map +0 -1
  411. package/dist/commands/execute/worktree-lookup.d.mts +0 -1
  412. package/dist/commands/execute/worktree-lookup.mjs +0 -92
  413. package/dist/commands/execute/worktree-lookup.mjs.map +0 -1
  414. package/dist/commands/execute/worktree-status.d.mts +0 -1
  415. package/dist/commands/execute/worktree-status.mjs +0 -121
  416. package/dist/commands/execute/worktree-status.mjs.map +0 -1
  417. package/dist/commands/inspect/code-review.d.mts +0 -1
  418. package/dist/commands/inspect/code-review.mjs +0 -32
  419. package/dist/commands/inspect/code-review.mjs.map +0 -1
  420. package/dist/commands/inspect/container.d.mts +0 -1
  421. package/dist/commands/inspect/container.mjs +0 -25
  422. package/dist/commands/inspect/container.mjs.map +0 -1
  423. package/dist/commands/inspect/context-cost.d.mts +0 -1
  424. package/dist/commands/inspect/context-cost.mjs +0 -25
  425. package/dist/commands/inspect/context-cost.mjs.map +0 -1
  426. package/dist/commands/inspect/design-prep.d.mts +0 -1
  427. package/dist/commands/inspect/design-prep.mjs +0 -22
  428. package/dist/commands/inspect/design-prep.mjs.map +0 -1
  429. package/dist/commands/inspect/error-report.d.mts +0 -1
  430. package/dist/commands/inspect/error-report.mjs +0 -25
  431. package/dist/commands/inspect/error-report.mjs.map +0 -1
  432. package/dist/commands/inspect/error-zip.d.mts +0 -1
  433. package/dist/commands/inspect/error-zip.mjs +0 -24
  434. package/dist/commands/inspect/error-zip.mjs.map +0 -1
  435. package/dist/commands/inspect/log-report.d.mts +0 -1
  436. package/dist/commands/inspect/log-report.mjs +0 -26
  437. package/dist/commands/inspect/log-report.mjs.map +0 -1
  438. package/dist/commands/inspect/model-io.d.mts +0 -1
  439. package/dist/commands/inspect/model-io.mjs +0 -25
  440. package/dist/commands/inspect/model-io.mjs.map +0 -1
  441. package/dist/commands/inspect/profile-show.d.mts +0 -1
  442. package/dist/commands/inspect/profile-show.mjs +0 -28
  443. package/dist/commands/inspect/profile-show.mjs.map +0 -1
  444. package/dist/commands/inspect/recap.d.mts +0 -1
  445. package/dist/commands/inspect/recap.mjs +0 -30
  446. package/dist/commands/inspect/recap.mjs.map +0 -1
  447. package/dist/commands/inspect/resolve-task-key.d.mts +0 -1
  448. package/dist/commands/inspect/resolve-task-key.mjs +0 -24
  449. package/dist/commands/inspect/resolve-task-key.mjs.map +0 -1
  450. package/dist/commands/inspect/rollup.d.mts +0 -1
  451. package/dist/commands/inspect/rollup.mjs +0 -25
  452. package/dist/commands/inspect/rollup.mjs.map +0 -1
  453. package/dist/commands/inspect/run-audit.d.mts +0 -1
  454. package/dist/commands/inspect/run-audit.mjs +0 -25
  455. package/dist/commands/inspect/run-audit.mjs.map +0 -1
  456. package/dist/commands/inspect/set-work-status.d.mts +0 -1
  457. package/dist/commands/inspect/set-work-status.mjs +0 -29
  458. package/dist/commands/inspect/set-work-status.mjs.map +0 -1
  459. package/dist/commands/inspect/stage-map.d.mts +0 -1
  460. package/dist/commands/inspect/stage-map.mjs +0 -131
  461. package/dist/commands/inspect/stage-map.mjs.map +0 -1
  462. package/dist/commands/inspect/task-list.d.mts +0 -1
  463. package/dist/commands/inspect/task-list.mjs +0 -149
  464. package/dist/commands/inspect/task-list.mjs.map +0 -1
  465. package/dist/commands/inspect/task-show.d.mts +0 -1
  466. package/dist/commands/inspect/task-show.mjs +0 -108
  467. package/dist/commands/inspect/task-show.mjs.map +0 -1
  468. package/dist/commands/inspect/time-report.d.mts +0 -1
  469. package/dist/commands/inspect/time-report.mjs +0 -24
  470. package/dist/commands/inspect/time-report.mjs.map +0 -1
  471. package/dist/commands/inspect/usage-report.d.mts +0 -1
  472. package/dist/commands/inspect/usage-report.mjs +0 -23
  473. package/dist/commands/inspect/usage-report.mjs.map +0 -1
  474. package/dist/commands/inspect/user-response.d.mts +0 -1
  475. package/dist/commands/inspect/user-response.mjs +0 -35
  476. package/dist/commands/inspect/user-response.mjs.map +0 -1
  477. package/dist/commands/inspect/worker-liveness.d.mts +0 -1
  478. package/dist/commands/inspect/worker-liveness.mjs +0 -45
  479. package/dist/commands/inspect/worker-liveness.mjs.map +0 -1
  480. package/dist/commands/lifecycle/contract-check.d.mts +0 -1
  481. package/dist/commands/lifecycle/contract-check.mjs +0 -18
  482. package/dist/commands/lifecycle/contract-check.mjs.map +0 -1
  483. package/dist/commands/lifecycle/migrate.d.mts +0 -1
  484. package/dist/commands/lifecycle/migrate.mjs +0 -30
  485. package/dist/commands/lifecycle/migrate.mjs.map +0 -1
  486. package/dist/commands/lifecycle/model.d.mts +0 -1
  487. package/dist/commands/lifecycle/model.mjs +0 -22
  488. package/dist/commands/lifecycle/model.mjs.map +0 -1
  489. package/dist/commands/manager.d.mts +0 -3
  490. package/dist/commands/manager.mjs +0 -50
  491. package/dist/commands/manager.mjs.map +0 -1
  492. package/dist/commands/report/agent-activity.d.mts +0 -1
  493. package/dist/commands/report/agent-activity.mjs +0 -20
  494. package/dist/commands/report/agent-activity.mjs.map +0 -1
  495. package/dist/commands/report/approval-decision.d.mts +0 -1
  496. package/dist/commands/report/approval-decision.mjs +0 -21
  497. package/dist/commands/report/approval-decision.mjs.map +0 -1
  498. package/dist/commands/report/design-snapshot.d.mts +0 -1
  499. package/dist/commands/report/design-snapshot.mjs +0 -19
  500. package/dist/commands/report/design-snapshot.mjs.map +0 -1
  501. package/dist/commands/report/finalize.d.mts +0 -4
  502. package/dist/commands/report/finalize.mjs +0 -64
  503. package/dist/commands/report/finalize.mjs.map +0 -1
  504. package/dist/commands/report/inject-report-index.d.mts +0 -1
  505. package/dist/commands/report/inject-report-index.mjs +0 -21
  506. package/dist/commands/report/inject-report-index.mjs.map +0 -1
  507. package/dist/commands/report/render-final-report.d.mts +0 -1
  508. package/dist/commands/report/render-final-report.mjs +0 -23
  509. package/dist/commands/report/render-final-report.mjs.map +0 -1
  510. package/dist/commands/report/render-views.d.mts +0 -1
  511. package/dist/commands/report/render-views.mjs +0 -27
  512. package/dist/commands/report/render-views.mjs.map +0 -1
  513. package/dist/commands/report/translate.d.mts +0 -1
  514. package/dist/commands/report/translate.mjs +0 -33
  515. package/dist/commands/report/translate.mjs.map +0 -1
  516. package/runtime/bin/lib/okstra/tmux-pane.sh +0 -40
  517. package/runtime/bin/lib/okstra-ctl/cmd-batch.sh +0 -59
  518. package/runtime/bin/lib/okstra-ctl/cmd-list.sh +0 -35
  519. package/runtime/bin/lib/okstra-ctl/cmd-open.sh +0 -36
  520. package/runtime/bin/lib/okstra-ctl/cmd-projects.sh +0 -26
  521. package/runtime/bin/lib/okstra-ctl/cmd-reconcile.sh +0 -29
  522. package/runtime/bin/lib/okstra-ctl/cmd-reindex.sh +0 -38
  523. package/runtime/bin/lib/okstra-ctl/cmd-rerun.sh +0 -345
  524. package/runtime/bin/lib/okstra-ctl/cmd-show.sh +0 -27
  525. package/runtime/bin/lib/okstra-ctl/cmd-tail.sh +0 -92
  526. package/runtime/bin/lib/okstra-ctl/main.sh +0 -41
  527. package/runtime/bin/lib/okstra-ctl/prepare.sh +0 -31
  528. package/runtime/bin/lib/okstra-ctl/usage.sh +0 -23
  529. package/runtime/bin/okstra-central.sh +0 -152
  530. package/runtime/bin/okstra-incremental-carry.py +0 -10
  531. package/runtime/bin/okstra-incremental-scope.py +0 -10
  532. package/runtime/bin/okstra-team-reconcile.sh +0 -36
  533. package/runtime/python/okstra_ctl/agent_prompt_cli.py +0 -1156
  534. package/runtime/python/okstra_ctl/batch.py +0 -60
  535. package/runtime/python/okstra_ctl/clarification_items.py +0 -1050
  536. package/runtime/python/okstra_ctl/container_registry.py +0 -72
  537. package/runtime/python/okstra_ctl/improvement_assignment.py +0 -61
  538. package/runtime/python/okstra_ctl/resolver.py +0 -54
  539. package/runtime/python/okstra_ctl/team_reconcile.py +0 -275
  540. package/runtime/python/okstra_ctl/tmux.py +0 -134
  541. package/runtime/python/okstra_ctl/worktree.py +0 -1099
@@ -24,13 +24,15 @@
24
24
 
25
25
  This contract governs **Phase 5.5 (Convergence loop)** — a *lead operating phase* inside a single okstra run, not a task-type lifecycle phase. It leaves the 7 task-type lifecycle phases (`requirements-discovery` → `error-analysis` → `implementation-option-selection` → `implementation-planning` → `implementation` → `final-verification` → `release-handoff`, see [okstra-lead-contract](./okstra-lead-contract.md) "Lifecycle Phase Boundaries") unchanged; the lead operating phases (Phase 1 Intake → Phase 7 Persist, see [okstra-lead-contract](./okstra-lead-contract.md) "Quick Reference") drive a *single* task-type run.
26
26
 
27
- **`contested` is a final classification only.** It is NEVER an intermediate queue label. The verification queue carries findings that are *unique to a single worker* (entered in Round 0) or *mixed/unresolved after a re-verification round* (carried forward). The `contested` label is assigned only when the **last executed round** completes and the queue is still non-empty.
27
+ **`contested` is a terminal classification, never an intermediate queue label.** The verification queue carries findings that are *unique to a single worker* (entered in Round 0) or *mixed/unresolved after a re-verification round* (carried forward). A finding is labelled `contested` in two places, both of which remove it from the queue: at the round where an adversarial `counter-evidence` refute lands (§"Adversarial Verification Mode"), and when the **last executed round** completes with the queue still non-empty. A `contested` finding is never re-dispatched.
28
28
 
29
29
  When this contract says "queue" without qualifier, it means the *verification queue*: the set of findings that are still candidates for re-verification in subsequent rounds. The queue shrinks monotonically as findings get classified as `full-consensus`, `partial-consensus`, or `worker-unique`. Findings classified into any of these three categories MUST NOT appear in any subsequent round's reverify prompt, for any worker.
30
30
 
31
+ **Enforced:** `_validate_resolved_findings_leave_the_queue` in `validators/validate-run.py` fails a resolved finding whose `rounds[]` ledger is not a contiguous `1..N` — a gap is the finding re-entering the queue after classification.
32
+
31
33
  An initial pane role `verifier` is still a Phase 4/5 analysis worker; it does not mean Phase 5.5 reverify. Only a queue-scoped dispatch whose prompt/result path carries `-reverify-r<N>-` performs the reverify step described by this contract.
32
34
 
33
- The end-to-end artifact lifecycle is worker results → Round 0 grouping → reducer-owned queue → analyser-instance re-verification → optional `okstra convergence apply-critic-gaps` transition → validated terminal state (newly finalized v1.3, or unchanged historical v1.0–v1.2 from `reuse-final`) → report-writer narrative → deterministic report assembly. Cross-verification is queue-scoped: it does not mean that one worker reviews another worker's complete result. The reducer asks independent analyser instances to vote only on non-consensus findings selected by the persisted plan; the report writer never votes.
35
+ The end-to-end artifact lifecycle is worker results → Round 0 grouping → reducer-owned queue → analyser-instance re-verification → optional `okstra convergence apply-critic-gaps` transition → validated terminal state (newly finalized v1.4, or unchanged historical v1.0–v1.3 from `reuse-final`) → report-writer narrative → deterministic report assembly. Cross-verification is queue-scoped: it does not mean that one worker reviews another worker's complete result. The reducer asks independent analyser instances to vote only on non-consensus findings selected by the persisted plan; the report writer never votes.
34
36
 
35
37
  Initial and reverify worker prompts carry `**Audit sidecar path:**`. Initial workers write their reading confirmation there; reverify workers use their own canonical sidecar for the reverify session without reopening the initial worker's full reading packet.
36
38
 
@@ -58,12 +60,15 @@ Configure this in the `convergence` block of `task-manifest.json`. If the block
58
60
  |------|------|------------|
59
61
  | `full-consensus` | All participating workers agree | Required |
60
62
  | `partial-consensus` | Majority of workers agree; dissenting opinions are recorded | Required |
61
- | `contested` | Final classification only. Assigned to a finding that remains in the verification queue after the **last executed round** completes (round index = `effectiveMaxRounds`). Each worker's position across all executed rounds is recorded. NEVER used as an intermediate label. | Required |
63
+ | `contested` | Terminal classification. Assigned to a finding that remains in the verification queue after the **last executed round** completes (round index = `effectiveMaxRounds`), and — in adversarial mode only — to a finding refuted with `counter-evidence` at the round that refute lands. Each worker's position across all executed rounds is recorded. Either way the finding leaves the queue and is never re-dispatched. | Required |
64
+ | `unverified` | Final classification only. Assigned to a finding that reached the last executed round with **every recorded vote** `verification-error` — a terminal non-result dispatch, or no analyser available to vote. Nobody inspected it, so `contested` would state a dispute that never happened. The gap ledger already applies the same rule (§'Gap verification'). | Required |
62
65
  | `worker-unique` | Only the discoverer confirms and ALL other non-error votes are `DISAGREE`. `verification-error` votes are excluded from the tally per §"Worker failure handling in reverify"; a finding where every non-discoverer vote is `verification-error` is carried forward, never classified `worker-unique`. | Required |
63
66
 
64
67
  ## Convergence Algorithm
65
68
 
66
- **Majority definition (BLOCKING).** "Majority" means *strictly greater than half* of the non-error votes for that finding (`verification-error` votes are excluded from both numerator and denominator). Ties — including the 1-AGREE / 1-DISAGREE case in a two-analyser roster — are NOT a majority: in intermediate rounds the finding is **carried forward**; in the final executed round the finding is classified `contested`. This rule applies identically to the plan-body verification round ([plan-body-verification](./plan-body-verification.md)) where the same verdict tokens are reused.
69
+ **Majority definition (BLOCKING).** "Majority" means *strictly greater than half* of the non-error votes for that finding (`verification-error` votes are excluded from both numerator and denominator). Ties — including the 1-AGREE / 1-DISAGREE case in a two-analyser roster — are NOT a majority: in intermediate rounds the finding is **carried forward**; in the final executed round the finding is classified `contested`. In adversarial mode a tie whose DISAGREE carries `counter-evidence` does not carry forward — it is classified `contested` in that round (§"Adversarial Verification Mode"). This rule applies identically to the plan-body verification round ([plan-body-verification](./plan-body-verification.md)) where the same verdict tokens are reused.
70
+
71
+ **Enforced:** the engine owns the classifier and replays it — `scripts/okstra_ctl/convergence_engine.py` `validate_final_state` recomputes each finding's expected final classification from its recorded votes and rejects the state when the persisted value differs, and `finalize` runs that same check before it writes, so a tie scored as a consensus never reaches the artifact. `okstra convergence validate` is the same call on demand. Nothing re-derives the majority rule outside the engine; a second implementation would only drift from it.
67
72
 
68
73
  ### Round 0: Parse worker results
69
74
 
@@ -87,10 +92,7 @@ Read the worker result files generated in Phase 4/5 and extract individual findi
87
92
  5. Author the fixed grouping Markdown accepted by `okstra convergence prepare-groups --run-manifest <run-manifest> --input <grouping.md>`, then run that command. Python owns the artifact identifier, target path, schema version, task identity, run-manifest reference, and every participant reference. Each Markdown group records ticket IDs, origin worker and evidence, discovering workers, source worker item IDs, and optional captured evidence. An analysis sidetrack with no ticket uses an empty `Tickets:` value, never a placeholder. Use the ordered functional roster: finding workers have the `analysis` audience, the report author has `report-writer`, and the lead uses `lead`. A lead source never votes. Never infer live evidence or functional scope from wording, provider, model, or execution label.
88
93
 
89
94
  The command sets each worker's paired `participantRef` and `sourceRoleExecutionRef` from the run manifest's canonical role state. It sets `sourceRoleExecutionRef` to the selected source `RoleExecution` row's `roleExecutionRef`, not that row's `sourceRoleExecutionRef` field.
90
- 6. Do not write a queue or classification in this grouped-input artifact. `okstra convergence seed` classifies Round 0 by mode:
91
- - Collaborative mode: multi-source groups become `full-consensus` immediately; only single-source groups enter the working queue.
92
- - Adversarial mode: every finding enters the working queue regardless of source count. Semantic grouping merges provenance only; it does not decide a finding is reliable.
93
- Section 6 never enters the grouped input.
95
+ 6. Do not write a queue or classification in this grouped-input artifact. `okstra convergence seed` classifies Round 0 the same way in both modes: a group whose sources are **two or more distinct role executions** becomes `full-consensus` immediately, and only single-source groups enter the working queue. Independent co-derivation is already cross-verification — the adversarial burden of proof targets single-source claims, not a finding two roles reached on their own. A source is counted once per analysis worker, and one analysis worker is exactly one `sourceRoleExecutionRef` — the same identity the reverify roster uses for independence — so two roles held by one provider count as two and no role can count twice. **Enforced:** `_parse_workers` rejects a duplicate `workerId` and `_validate_worker_execution_identity` rejects a duplicate `sourceRoleExecutionRef`, both in `scripts/okstra_ctl/convergence_engine.py`. Semantic grouping merges provenance only; it does not decide a single-source finding is reliable. Section 6 never enters the grouped input.
94
96
 
95
97
  ### Round 1-N: Re-verification Loop (queue-pruned)
96
98
 
@@ -111,12 +113,12 @@ Follow this protocol exactly:
111
113
  0. Version-selected schemas describe what the reducer reads: `schemas/convergence-groups-v1.0.schema.json` accepts only legacy groups, `schemas/convergence-groups-v2.0.schema.json` accepts only explicit v2 execution identity, and `schemas/convergence-round-results-v1.0.schema.json` feeds step 4's `apply-round --results`. `schemas/convergence-critic-results-v1.0.schema.json` is a fourth shape but **not** a reducer input — it describes the critic worker's own result document. Step 6's `apply-critic-gaps --results` takes the coverage batch you assemble from those candidates plus each analyser's vote (`{schemaVersion, taskKey, mode, provider, modelExecutionValue, dispatches[], gaps[]}`, spelled out in §"Coverage critic pass" §"State"); feeding the critic document straight in is rejected, by design. `okstra convergence example --kind <groups|round-results|critic-results>` prints a deterministic valid v1 instance of each and writes only JSON to stdout.
112
114
  1. Run `okstra convergence seed --groups <groups> --run-manifest <current-run-manifest> --work-state <work> --final-state <final> --migration-dir <state/migrations>` for a v2 worker roster. `--run-manifest` must be the exact current manifest named by the groups document's `runManifestPath`; a previous run from the same task is not interchangeable. Omit the flag for a legacy v1 roster. A `reuse-final` action means validate the existing final and continue to Phase 6. `create-work`, `resume-work`, and `restart-round0` continue with planning.
113
115
  2. Run `okstra convergence plan-round --work-state <work> --plan <round-plan>`. This is read-only with respect to the working state.
114
- 3. When the plan action is `dispatch`, create exactly one reverify prompt for each `dispatches[]` row and dispatch it through the selected runtime adapter. Its findings are exactly that row's `findingIds`.
116
+ 3. Every plan carries `dispatchable`: `true` on an `action: "dispatch"` plan, `false` on an `action: "finalize"` plan. When the plan action is `dispatch`, create exactly one reverify prompt for each `dispatches[]` row and dispatch it through the selected runtime adapter. Its findings are exactly that row's `findingIds`.
115
117
  4. Convert parsed verdicts and every terminal dispatch outcome into `convergence-round-<N>-results-<task-type>-<seq>.json`; then run `okstra convergence apply-round --work-state <work> --plan <round-plan> --results <round-results>`.
116
- 5. Repeat `plan-round` and `apply-round` until the plan action is `finalize`.
118
+ 5. Repeat `plan-round` and `apply-round` until the plan action is `finalize`. A `finalize` plan is **never dispatched**: it carries `dispatchable: false`, an empty `dispatches[]`, and a `note` naming the next command. Its `round` is only the `<N>` in `convergence-round-<N>-plan-<task-type>-<seq>.json` — not a round to run — so do not open reverify workers for it. **Enforced:** `okstra convergence apply-round` refuses a non-dispatch plan and returns the gate's closing reason with the remedy (`scripts/okstra_ctl/convergence_engine.py` `apply_round_results`).
117
119
  6. When the coverage critic is enabled, convert its verification batch to the canonical `dispatches[]` / `gaps[]` result shape and run `okstra convergence apply-critic-gaps --work-state <work> --results <critic-results>` exactly once. The reducer, not the lead, merges verified gaps.
118
120
  7. Run `okstra convergence finalize --work-state <work> --output <final>`.
119
- 8. Run `okstra convergence validate --state <final> --kind final`. Newly finalized convergence output is schema v1.3. Under `reuse-final`, a valid historical final schema v1.0, v1.1, or v1.2 remains consumable by the report-writer without rewrite; do not finalize, upgrade, or otherwise rewrite that reused artifact. Deliver the validated terminal state to the report-writer, which does not vote.
121
+ 8. Run `okstra convergence validate --state <final> --kind final`. Newly finalized convergence output is schema v1.4. Under `reuse-final`, a valid historical final schema v1.0, v1.1, v1.2, or v1.3 remains consumable by the report-writer without rewrite; do not finalize, upgrade, or otherwise rewrite that reused artifact. Deliver the validated terminal state to the report-writer, which does not vote.
120
122
 
121
123
  The planner preserves roster and queue order, excludes that finding's origin worker, emits at most one batch per analysis worker per round, and ensures the report-writer never appears in `dispatches` or `skippedWorkers`. Queue pruning is monotonic: a finding absent from the current queue cannot reappear in a later plan.
122
124
 
@@ -130,10 +132,14 @@ A valid historical final schema v1.0, v1.1, or v1.2 is reused unchanged under `r
130
132
 
131
133
  `plan-round` applies gate precedence in one place: auto-disabled, all reverify non-result, effective maximum of one, empty queue, then maximum rounds reached. `finalize` maps that internal reason to the public `round2SkippedReason` and `finalState`. The lead and adapters never reproduce this predicate.
132
134
 
135
+ A gate-closed plan states its own terminality in `dispatchable: false` and `note`; read those, not `round`. With `effectiveMaxRounds: 2` the closing plan reads `{"action": "finalize", "round": 3, "dispatchable": false, "reason": "max-rounds-reached"}` — `round: 3` names the artifact, and there is no round 3 to dispatch.
136
+
133
137
  #### Worker failure handling in reverify (BLOCKING)
134
138
 
135
139
  A reverify dispatch that returns a **terminal non-result** (`timeout`, `error`, no result file, or the wrapper records `cli-failure`) MUST NOT be aggregated as `DISAGREE`. Misclassifying a worker failure as DISAGREE biases the queue toward `contested`/`worker-unique` and produces meaningless final classifications.
136
140
 
141
+ **Enforced:** `_validate_worker_failure_is_not_a_disagree` in `validators/validate-run.py` matches each round's `dispatches[].status` against that round's `votes` and fails a `disagree` recorded for a worker whose dispatch status is `timeout` / `error` / `not-run`.
142
+
137
143
  Rules:
138
144
 
139
145
  1. For each failed dispatch, put its actual terminal status and duration in the round-results `dispatches[]`; do not invent a vote. `okstra convergence apply-round` appends `votes[W].verdict = "verification-error"` with the terminal reason for every affected finding. A completed dispatch may separately return `UNVERIFIABLE` for a particular finding; the input alias is persisted as `verification-error` with its required non-empty explanation while the dispatch remains `completed`.
@@ -174,7 +180,7 @@ If every required analysis worker produces a non-result, the run verdict is `blo
174
180
 
175
181
  ### Scoped full-reanalysis
176
182
 
177
- Adversarial mode forces `verificationMode = "full-reanalysis"`, but the re-analysis is **scoped to the evidence the finding under attack cites** (the file paths / line ranges / log lines in its `originEvidence`), plus the immediately surrounding context. The verifier MUST NOT re-read the whole task brief, instruction-set, or `final-report-template.md`. This keeps the documented "single largest avoidable cost in requirements-discovery, error-analysis, and implementation-planning" (see §"Reverify prompt: required-reading suppression") bounded while making the refutation real rather than a text-only argument.
183
+ Adversarial mode forces `verificationMode = "full-reanalysis"`, but the re-analysis is **scoped to the evidence the finding under attack cites** (the file paths / line ranges / log lines in its `originEvidence`), plus the immediately surrounding context. The verifier MUST NOT re-read the whole task brief, instruction-set, or `final-report-template.md`. **Enforced (template clause only):** `_validate_full_reanalysis_prompt_omits_the_report_template` in `validators/validate-run.py` fails a reverify prompt that references `final-report-template.md`; the brief and instruction-set clauses have no fixed literal to match on. This keeps the documented "single largest avoidable cost in requirements-discovery, error-analysis, and implementation-planning" (see §"Reverify prompt: required-reading suppression") bounded while making the refutation real rather than a text-only argument.
178
184
 
179
185
  ### Adversarial verdict semantics
180
186
 
@@ -194,7 +200,7 @@ Each `disagree` vote records a new field `disagreeBasis`:
194
200
  | `counter-evidence` | The verifier opened and inspected the cited evidence and found a contradiction (`file:line` / log line) recorded in `explanation`. A **hard refute**. |
195
201
  | `burden-not-met` | The verifier opened and inspected the cited evidence, but its contents were insufficient to establish the claim. |
196
202
 
197
- A `disagree` with `disagreeBasis == null` is a contract violation in adversarial mode — every refutation must state which of the two grounds it rests on. Bare "I disagree" without re-inspection is not allowed. If capability, credential, network, or service state prevents that inspection, the verdict is `UNVERIFIABLE`, persisted as `verification-error`; verifier failure is never converted to `DISAGREE` or `burden-not-met`. A live/external claim without `evidenceArtifacts` remains schema-valid, but a verifier that needs the missing artifact and cannot independently access the source MUST answer `UNVERIFIABLE` with a non-empty explanation.
203
+ A `disagree` with `disagreeBasis == null` is a contract violation in adversarial mode — every refutation must state which of the two grounds it rests on. **Enforced:** `_validate_adversarial_disagree_carries_a_basis` in `validators/validate-run.py` fails such a vote when `config.adversarial` is set; non-adversarial rounds are out of scope because their verdict semantics differ. Bare "I disagree" without re-inspection is not allowed. If capability, credential, network, or service state prevents that inspection, the verdict is `UNVERIFIABLE`, persisted as `verification-error`; verifier failure is never converted to `DISAGREE` or `burden-not-met`. A live/external claim without `evidenceArtifacts` remains schema-valid, but a verifier that needs the missing artifact and cannot independently access the source MUST answer `UNVERIFIABLE` with a non-empty explanation.
198
204
 
199
205
  ### Adversarial classification (replaces the §"Convergence Algorithm" per-round classifier when `adversarial == true`)
200
206
 
@@ -212,7 +218,7 @@ ELIF all_others_disagree:
212
218
  resolve F as "worker-unique" # only the discoverer still holds it
213
219
  ELIF len(hard_refutes) >= 1:
214
220
  # an evidence-backed refute exists and the roster is split → the claim is disputed
215
- carry F forward; at the LAST executed round classify it "contested"
221
+ resolve F as "contested" IN THIS ROUND; F leaves the queue
216
222
  ELIF burden-not-met disagrees are a majority of non-error votes (per the Majority definition in the Convergence Algorithm section):
217
223
  carry F forward; at the LAST executed round classify it "contested"
218
224
  ELSE:
@@ -220,12 +226,14 @@ ELSE:
220
226
  resolve F as "partial-consensus"
221
227
  ```
222
228
 
223
- `contested` remains a **final classification only** (per §"Scope and Terminology"): a disputed finding is carried forward through intermediate rounds and labelled `contested` only at the last executed round. For `requirements-discovery` (`effectiveMaxRounds = 1`) the single round IS the last round, so a split-with-hard-refute finding is labelled `contested` in that one round. The final-classifier block of §"Convergence Algorithm" honours this: its first branch classifies an adversarially carried-forward finding `contested` regardless of the AGREE tally, so the two sections cannot assign the same finding different labels.
229
+ `contested` stays terminal (per §"Scope and Terminology") and is reached by two routes. A finding split on `burden-not-met` doubt alone is carried forward through intermediate rounds and labelled `contested` at the last executed round; a finding carrying a `counter-evidence` hard refute is labelled `contested` in the round that refute lands and leaves the queue there. For `requirements-discovery` (`effectiveMaxRounds = 1`) the two routes coincide — the single round IS the last round. The final-classifier block of §"Convergence Algorithm" honours this: its first branch classifies an adversarially carried-forward finding `contested` regardless of the AGREE tally, so the two sections cannot assign the same finding different labels.
224
230
 
225
- Design intent: one `counter-evidence` refute denies a claim consensus (it cannot rise above `contested` however many others AGREE); later-round agreement does not erase that refutation history. The only resolution that overrides prior `counter-evidence` is a later round where every non-discoverer non-error worker disagrees, producing `worker-unique`. A lone `burden-not-met` doubt does not sink an otherwise-surviving claim — only a majority of them does. When every non-discoverer refutes (all_others_disagree) the finding is worker-unique regardless of refute basis — only the discoverer still holds it. A caveat is weighed the same way a weak doubt is: with zero disagrees, SUPPLEMENT lands partial-consensus only when caveats are a **majority** of the non-error votes. A single verifier's scope note does not by itself deny a claim the rest of the roster passed cleanly — the caveat is still recorded in the dissent log either way, so the majority rule changes the label, never the record. (The collaborative classifier is more permissive still: there SUPPLEMENT counts as full agreement at any count.)
231
+ Design intent: one `counter-evidence` refute denies a claim consensus (it cannot rise above `contested` however many others AGREE). Because that refute is permanent, re-dispatching the finding buys nothing — the verifier gets no new information and re-casts the same refute, so the round is spent by construction. The refute therefore settles the finding where it lands, and the finding is reported under `## 6.2 Differences` exactly as a last-round `contested` finding is. A lone `burden-not-met` doubt does not sink an otherwise-surviving claim — only a majority of them does; that doubt IS resolvable by another round, so it still carries forward. When every non-discoverer refutes (all_others_disagree) the finding is worker-unique regardless of refute basis — only the discoverer still holds it. A caveat is weighed the same way a weak doubt is: with zero disagrees, SUPPLEMENT lands partial-consensus only when caveats are a **majority** of the non-error votes. A single verifier's scope note does not by itself deny a claim the rest of the roster passed cleanly — the caveat is still recorded in the dissent log either way, so the majority rule changes the label, never the record. (The collaborative classifier is more permissive still: there SUPPLEMENT counts as full agreement at any count.)
226
232
 
227
233
  ## Re-verification Dispatch
228
234
 
235
+ Every finding re-verification, coverage critic, acceptance critic, and critic-verification instruction passes [okstra-lead-contract](./okstra-lead-contract.md) "Worker instruction quality gate" before materialization. The lead resolves exact finding IDs, evidence paths, requested verdict fields, allowed verdict literals, and completion conditions from the current convergence state and cited results; a worker is not asked to infer them from a round summary.
236
+
229
237
  ### Invocation materialization gate (BLOCKING)
230
238
 
231
239
  For every finding reverify row and critic-gap verification row, first write a
@@ -273,7 +281,7 @@ call specification; it does not prove which bytes the host primitive delivered.
273
281
 
274
282
  ### Sponsorship Optimization
275
283
 
276
- For each persisted round plan, build exactly one prompt per `dispatches[]` row and call `redispatch_worker(assignment, prompt, reason)` once through the selected runtime adapter. The prompt contains exactly that row's `findingIds` in plan order and MUST NOT add, remove, or reorder findings. This excludes Section 6, every resolved finding, and every finding owned by the receiving origin worker because none can appear in the engine row. The assignment, model, prompt path, Result Path, worker-results path, errors paths, and `dispatchKind` come from the current run artifacts. Every reverify is a fresh one-shot session.
284
+ For each persisted round plan, build exactly one prompt per `dispatches[]` row and call `redispatch_worker(assignment, prompt, reason)` once through the selected runtime adapter. The prompt contains exactly that row's `findingIds` in plan order and MUST NOT add, remove, or reorder findings. **Enforced (membership only):** `_validate_reverify_prompt_matches_plan` in `validators/validate-run.py` replays `plan == prompt` as a set — it fails an added or dropped finding. Order is not machine-checked; the engine row is the order of record. This excludes Section 6, every resolved finding, and every finding owned by the receiving origin worker because none can appear in the engine row. **Ownership is compared by `sourceRoleExecutionRef`, not by `participantRef`** — a worker sharing the origin's provider and model in a *different* role is a different role contract, a different duty and a different session, so it stays in the panel (ADR-0017; the same doctrine §"Critic gaps" states for critics). **Enforced:** `_worker_is_independent_from_finding` in `scripts/okstra_ctl/convergence_engine.py`. The assignment, model, prompt path, Result Path, worker-results path, errors paths, and `dispatchKind` come from the current run artifacts. Every reverify is a fresh one-shot session.
277
285
 
278
286
  The persisted round plan is the audit record for batch membership. The lead and adapter do not branch on task type, provider, model identity, classification labels, or their own view of the queue. They dispatch only the engine-returned row through the selected runtime adapter.
279
287
 
@@ -304,7 +312,7 @@ The two error-path anchors carry the same absolute values the lead forwarded in
304
312
 
305
313
  Relative to the Phase 4 anchor set rendered by `okstra_ctl.worker_prompt_headers.worker_prompt_headers()`, a reverify prompt drops two anchors whose targets lightweight mode never reads: `**Worker Preamble Path:**` and `**Worker Error Contract Path:**`.
306
314
 
307
- **Where the composer's sections go.** `okstra agent-prompt materialize` (§"Invocation materialization gate") writes the dispatched body itself, as: these anchors, then the model-assignment block it appends (`**Provider:**`, `**Model:**`, `**Model execution value:**`, `**Runner:**`, `**Host runtime:**`, and `**Host model value:**` for a native host), then `## Duty Contract`, then `## Task Instructions` followed verbatim by the task-instructions file the lead wrote. So the lead authors only the last part, and every rule below about ordering — the phase boundary before the instruction headings, the `**Model:** <role>, <modelExecutionValue>` line — is about the lead's own file, not about the composed document. The composer's `**Model:** <modelExecutionValue>` anchor is a different line with a different shape; do not try to reshape it, and do not count it among the 8.
315
+ **Where the composer's sections go.** `okstra agent-prompt materialize` (§"Invocation materialization gate") writes the dispatched body itself, as: these anchors, then the model-assignment block (`**Provider:**`, `**Model execution value:**`, `**Runner:**`, `**Host runtime:**`, and `**Host model value:**` for a native host), then `## Duty Contract`, then `## Task Instructions` followed verbatim by the task-instructions file the lead wrote. The composed prompt carries exactly one `**Model:**` header. Materialization preserves a lead-authored `**Model:** <role>, <modelExecutionValue>` line and omits its own model line in that case; when the task instructions have no model line, materialization writes `**Model:** <modelExecutionValue>` in the assignment block. The lead counts the composed document rather than assuming that both forms may coexist.
308
316
 
309
317
  For an `antigravity` assignment, append the exact `PLAIN_FILE_WRITE_HEADER`
310
318
  value from `okstra_ctl.worker_prompt_headers` immediately after
@@ -327,6 +335,15 @@ The task-instructions file the lead writes MUST open with this block, before any
327
335
  <active-run-context workflow.forbiddenActions, verbatim>
328
336
  ```
329
337
 
338
+ Nothing may sit between the forbidden-actions text and the file's first `##`
339
+ heading — not even the prompt's own opening sentence. The block's value is read
340
+ from the line after `**Forbidden actions:**` up to the next `## ` heading or
341
+ `**X:**` header, so any line placed in that gap is folded into the value and the
342
+ exact-match check fails while the text is plainly correct. This is why each
343
+ prompt-body example below opens at `## Instructions` and states its round line
344
+ inside that section. **Enforced:** `_section_values` in
345
+ `scripts/okstra_ctl/worker_prompt_contract.py`.
346
+
330
347
  This is the same placement `okstra_ctl.worker_prompt_body` uses for an initial
331
348
  Phase 4 prompt, and it is where the checks look: `validate_reverify_prompt()`
332
349
  reads the region after `## Task Instructions`, so a `**Model:**` line left in
@@ -357,6 +374,8 @@ If none of the three is available, **abort the reverify dispatch for that role**
357
374
  Every lightweight, adversarial, full-reanalysis, and plan-body reverify prompt
358
375
  MUST append this block verbatim after its variant-specific response format:
359
376
 
377
+ **Enforced:** `okstra_ctl.worker_prompt_contract.validate_reverify_prompt` runs at dispatch (`dispatch_state.py`) and rejects a prompt without this block, or with the heading but a missing clause. It matches the three clauses by their distinctive tokens rather than byte-for-byte — a hand-copied block's whitespace must not decide whether a round runs.
378
+
360
379
  ```markdown
361
380
  ## Output Contract
362
381
 
@@ -369,6 +388,8 @@ MUST append this block verbatim after its variant-specific response format:
369
388
 
370
389
  Reverify prompts MUST NOT inject the Phase 2 `[Required reading]` clause:
371
390
 
391
+ **Enforced:** `_validate_reverify_prompt_suppresses_required_reading` in `validators/validate-run.py` fails any `*-reverify-r*.md` prompt containing the clause.
392
+
372
393
  Lightweight reverify does not require the original `analysis-packet.md`, `analysis-profile.md`, `task-brief.md`, or instruction set. Its complete input is the receiving worker's current engine-planned `findingIds` batch and the evidence embedded in those findings.
373
394
 
374
395
  - **Lightweight mode**: the clause directly contradicts the "Do NOT re-analyze the original source materials" instruction below. Including it forces workers to re-read the entire instruction-set per round per worker (3 workers × 2 rounds × 5+ files in the worst case) for no quality gain.
@@ -379,10 +400,10 @@ This is the single largest avoidable cost in `requirements-discovery`, `error-an
379
400
  ### Lightweight Re-verification Prompt
380
401
 
381
402
  ```
382
- Perform re-verification for <task-key> (round <N>).
383
-
384
403
  ## Instructions
385
404
 
405
+ Perform re-verification for <task-key> (round <N>).
406
+
386
407
  Review the following findings discovered by other workers.
387
408
  For EACH finding, respond with exactly one verdict:
388
409
 
@@ -421,10 +442,10 @@ For each finding, respond as:
421
442
  Used instead of the lightweight/full-reanalysis prompt when `config.adversarial == true`. The required anchor headers (§"Required reverify-prompt anchor headers") are identical. The `[Required reading]` clause is suppressed; only the cited-evidence paths of the items under attack are injected (see §"Adversarial Verification Mode" → Scoped full-reanalysis).
422
443
 
423
444
  ```
424
- Perform ADVERSARIAL re-verification for <task-key> (round <N>).
425
-
426
445
  ## Instructions
427
446
 
447
+ Perform ADVERSARIAL re-verification for <task-key> (round <N>).
448
+
428
449
  Your job is to BREAK each finding below, not to confirm it. For EACH finding,
429
450
  open the cited evidence directly and actively search for evidence that the claim
430
451
  is wrong, overstated, or unproven. Then respond with exactly one verdict:
@@ -472,10 +493,10 @@ UNVERIFIABLE is **not** `verification-error`. A verifier that opened the evidenc
472
493
  ### Full Re-analysis Re-verification Prompt
473
494
 
474
495
  ```
475
- Perform deep re-verification for <task-key> (round <N>).
476
-
477
496
  ## Instructions
478
497
 
498
+ Perform deep re-verification for <task-key> (round <N>).
499
+
479
500
  Independently verify the following findings by examining the original materials.
480
501
  Use each finding as a starting point, NOT as a confirmed conclusion.
481
502
  If capability, credential, network, or service state prevents access to evidence
@@ -577,13 +598,13 @@ Save it to `runs/<task-type>/state/convergence-<task-type>-<seq>.json`.
577
598
 
578
599
  Schema rules:
579
600
 
580
- - `schemaVersion`: literal string `"1.3"` for all new runs — both adversarial and collaborative. Historical readers accept `"1.0"` / `"1.1"` / `"1.2"` unchanged and never rewrite those artifacts during validation. v1.3 adds the strict coverage-critic ledger and rejects unknown top-level fields; work-state remains v1.0.
601
+ - `schemaVersion`: literal string `"1.4"` for all new runs — both adversarial and collaborative. Historical readers accept `"1.0"` / `"1.1"` / `"1.2"` / `"1.3"` unchanged and never rewrite those artifacts during validation. v1.3 added the strict coverage-critic ledger and the rejection of unknown top-level fields; v1.4 adds the `unverified` classification and its `finalClassificationCounts.unverified` key. Work-state remains v1.0.
581
602
  - `config.adversarial`: boolean. `true` when this run used adversarial verification (default for `requirements-discovery` / `error-analysis` / `implementation-option-selection` / `implementation-planning` / `project-analysis` / `feature-analysis` / `change-impact-analysis`). When `true`, `config.verificationMode` is `"full-reanalysis"` (scoped) and every `disagree` vote carries a non-null `disagreeBasis`.
582
- - `config.effectiveMaxRounds`: the integer the lead actually used after resolving the phase-aware default (`1` for `requirements-discovery`, `2` otherwise). MUST equal `config.maxRounds` when the manifest explicitly set it.
603
+ - `config.effectiveMaxRounds`: the integer the lead actually used after resolving the phase-aware default (`1` for `requirements-discovery`, `2` otherwise). It may be lower than `config.maxRounds` — a phase-aware default that resolves below the manifest ceiling is the normal case — but never higher. **Enforced:** `scripts/okstra_ctl/convergence_engine.py` `_parse_config` raises `ConvergenceContractError` on `effectiveMaxRounds > maxRounds`, so a state carrying that pair cannot be loaded, and `plan_next_round` finalizes with reason `max-rounds-reached` once the executed rounds reach the effective budget rather than dispatching another one. `totalRounds` is therefore bounded by construction, not by a later re-count.
583
604
  - `findings[].ticketIds`: array of ticket keys from Phase 4 grouping (parsed per the Round 0 step 5 rule). It is empty when the phase does not require ticket tagging; `"unknown"` is not a ticket key and must not be synthesized.
584
605
  - `findings[].rounds[].votes.<worker>.verdict`: enum, one of `agree | disagree | supplement | verification-error`. Lower-case tokens; map upper-case AGREE/DISAGREE/SUPPLEMENT verdicts emitted by workers to their lower-case form and map the input alias `unverifiable` to persisted `verification-error`. The latter represents either a terminal non-result dispatch or a completed dispatch that could not verify a particular finding (§"Worker failure handling in reverify"). Every vote has a non-empty `explanation`.
585
606
  - `findings[].rounds[].votes.<worker>.disagreeBasis`: enum `counter-evidence | burden-not-met | null`. Non-null only when `verdict == "disagree"` AND `config.adversarial == true`; `null` (or absent, treated as null) otherwise. See §"Adversarial Verification Mode".
586
- - `findings[].classification`: enum, one of `full-consensus | partial-consensus | worker-unique | contested`. No other value is permitted.
607
+ - `findings[].classification`: enum, one of `full-consensus | partial-consensus | worker-unique | contested | unverified`. No other value is permitted. `unverified` exists from final schema v1.4 onward; a historical v1.0-v1.3 artifact read under `reuse-final` uses the four-value vocabulary and MUST NOT be rewritten to add it.
587
608
  - `roundHistory[].inputQueueSize`: queue size at the start of this round.
588
609
  - `roundHistory[].resolvedCount`: number of findings that exited the queue this round (sum of full+partial+worker-unique classifications produced this round).
589
610
  - `roundHistory[].carriedForwardCount`: queue size at the END of this round — the single definition. In-round insertions into the queue are forbidden, so this always equals `inputQueueSize - resolvedCount`. The pseudocode's per-item `carriedForwardCount += 1` accumulator is a counting convenience that lands on the same value; persist the post-round queue length, not the loop accumulator, if the two ever diverge.
@@ -596,17 +617,20 @@ Schema rules:
596
617
 
597
618
  ## Coverage critic pass
598
619
 
599
- Runs when `convergence.critic.enabled == true`. Critic is required on `requirements-discovery`, `error-analysis`, `implementation-planning`, and `final-verification` (role `min`/`max` 1; the user picks the model). `--critic off` is rejected. For `final-verification` the critic runs in a different mode — see §"Acceptance critic pass (final-verification)". This pass targets **scope in both directions** — findings that are missing (coverage) and work the findings propose that no requirement asked for (over-scope) — distinct from convergence, which targets **agreement quality** among the findings already raised. The pass keeps its `coverage` mode id and `gaps` vocabulary for both halves; the two are told apart by each candidate's `category`, so no schema or reducer distinguishes them. In `implementation-planning` the same critic slot also settles plan-body analyser 1-1 splits as `critic-worker`.
620
+ Runs when `convergence.critic.enabled == true`. Critic is opt-in on `requirements-discovery`, `error-analysis`, `implementation-planning`, and `final-verification` (role `min` 0, `recommended`/`max` 1; the user chooses whether to add the slot and picks the model). A run without a critic slot renders `enabled: false` and skips this pass. For `final-verification` the critic runs in a different mode — see §"Acceptance critic pass (final-verification)". This pass targets **scope in both directions** — findings that are missing (coverage) and work the findings propose that no requirement asked for (over-scope) — distinct from convergence, which targets **agreement quality** among the findings already raised. The pass keeps its `coverage` mode id and `gaps` vocabulary for both halves; the two are told apart by each candidate's `category`, so no schema or reducer distinguishes them. In `implementation-planning` the same critic slot also settles plan-body analyser 1-1 splits as `critic-worker`.
600
621
 
601
622
  ### When
602
623
 
603
- The critic input is the Round 0 consolidated finding list. Reverify rounds only classify findings — they never add or remove them (in-round queue insertions are forbidden, see §"Convergence State Artifact" `carriedForwardCount`) — so the critic dispatch MUST NOT wait for classification to finish:
624
+ The critic input is the Round 0 consolidated finding list. Reverify rounds only classify findings — they never add or remove them (in-round queue insertions are forbidden, see §"Convergence State Artifact" `carriedForwardCount`) — **Enforced:** `_validate_no_in_round_queue_insertion` in `validators/validate-run.py` fails a finding whose earliest `rounds[].round` is greater than 1 — so the critic dispatch MUST NOT wait for classification to finish:
604
625
 
605
626
  - **Dispatch**: immediately after Round 0 grouping, CONCURRENTLY with the first reverify round's dispatches. When the verification queue is empty after Round 0 (no reverify round runs), dispatch right after grouping. Concurrent dispatch to the same provider is safe — the critic result path (`<provider>-worker-critic-...`) never collides with a reverify result path.
606
627
  - **Gap verification + merge**: only after BOTH the finding-convergence loop has exited AND the critic result is collected, and BEFORE the Phase 6 report-writer dispatch. If the loop exited `aborted-non-result`, do NOT dispatch a gap-verification round — record every gap in `unverifiedGaps[]` per §"Gap verification".
607
628
 
608
629
  ### Dispatch (fresh one-shot)
609
- Write the critic-only task instructions, then run `okstra agent-prompt
630
+ Render the critic-only task instructions with `okstra convergence critic-prompt
631
+ --run-manifest <run-manifest>` and write that output to the instructions file
632
+ verbatim — the same pattern as `okstra plan-items prompt` at round 1. Then run
633
+ `okstra agent-prompt
610
634
  materialize` with `--audience scope-critic`, `--assignment-ref critic/scope`,
611
635
  the critic worker ID, and `--dispatch-kind critic`. Verify the returned
612
636
  `metadataPath` before dispatch and use its `promptPath` without modification.
@@ -619,33 +643,37 @@ persisted assignment or either model value required by its runner is absent,
619
643
  record `critic-skipped: model-unresolved`; never resolve a replacement model.
620
644
  Result path: `runs/<task-type>/worker-results/<provider>-worker-critic-<task-type>-<seq>.md`.
621
645
 
622
- **What the critic task-instructions file MUST contain (BLOCKING).** A critic
623
- dispatch is not a reverify dispatch: `dispatchKind = "critic"` keeps
646
+ **What the generated critic task-instructions file contains.** A critic dispatch
647
+ is not a reverify dispatch: `dispatchKind = "critic"` keeps
624
648
  `audience = "analysis"`, so `worker_prompt_contract.validate_initial_prompts`
625
649
  judges it by the full initial-analysis contract. Two of those requirements are
626
650
  satisfied by the generated body for a Phase 4 worker
627
- (`okstra_ctl.worker_prompt_body`) and by nothing at all for a critic, whose
628
- instructions the lead writes — the materializer's anchor block supplies neither.
629
- Put both in the instructions file:
630
-
631
- ```markdown
632
- **Prompt Delivery Mode:** eager-include
633
- ```
634
-
635
- and, under the file's `## Inputs`, exactly one line in this shape — the literal
636
- label and the backticks are what the check matches, so a bare path or a
637
- differently-worded label counts as zero:
638
-
639
- ```markdown
640
- - Primary analysis packet: `<path ending in analysis-packet.md>`
641
- ```
642
-
643
- Omitting either one fails `okstra team dispatch --dispatch-kind critic` before
644
- any process starts, reported as `<task-type> prompt contract: <worker>: exactly
645
- one Primary analysis packet path is required (found 0)` and `exactly one
646
- non-empty **Prompt Delivery Mode:** header is required`. Fix the instructions
647
- file and re-materialize with `--replace-undispatched` (§"Invocation
648
- materialization gate") rather than editing the published prompt.
651
+ (`okstra_ctl.worker_prompt_body`) and by nothing in the materializer's anchor
652
+ block for a critic — `critic-prompt` emits both itself: the
653
+ `**Prompt Delivery Mode:** eager-include` header, and under `## Inputs` exactly
654
+ one `Primary analysis packet` line whose backticked path ends in
655
+ `analysis-packet.md`, read from the run manifest's `analysisPacketPath` (the
656
+ command fails when that field is missing, rather than emitting a body the
657
+ dispatch will reject). The rest of the body is:
658
+
659
+ - the Round 0 consolidated finding list, from the run's published grouping;
660
+ - one line per Phase 4 analyser — worker id, its result path, and the finding
661
+ ids that worker sourced — so "open the named result" points at a real file;
662
+ - when the task already has an implementation-planning report on disk (a rerun),
663
+ an **already-covered** index from it: requirement-coverage row ids,
664
+ clarification row ids, and stage titles. Ids and titles only, never body text;
665
+ - the two mandates below, including the `duplicateOf` declaration rule.
666
+
667
+ The lead writes none of it and edits none of it. **Enforced:**
668
+ `tests/contract/test_critic_prompt_equality_exemption.py`
669
+ `test_generated_critic_seed_satisfies_the_real_dispatch_checks` runs the
670
+ generated body through the same two checks the dispatch runs. A hand-edited file
671
+ that drops either line fails `okstra team dispatch --dispatch-kind critic`
672
+ before any process starts, reported as `<task-type> prompt contract: <worker>:
673
+ exactly one Primary analysis packet path is required (found 0)` and `exactly one
674
+ non-empty **Prompt Delivery Mode:** header is required`. Re-render and
675
+ re-materialize with `--replace-undispatched` (§"Invocation materialization
676
+ gate") rather than editing the published prompt.
649
677
 
650
678
  The `-worker-` token is load-bearing, not decoration: the critic prompt carries the same generated anchor headers as every other worker ([team-contract](./team-contract.md) §"Worker prompts"), and its `**Audit sidecar path:**` comes from passing that result path through `okstra_ctl.worker_artifact_paths.audit_sidecar_rel()`, which inserts `-audit-` after the token and raises without it. A `<provider>-critic-...` name leaves the lead choosing between breaking the contract and hand-inventing the sidecar name. Note that `originWorker` stays `"<provider>-critic"` — that is a worker id in the convergence state, not a filename, and the two do not have to match.
651
679
 
@@ -661,35 +689,16 @@ Required reading before proposing a gap or an over-scope candidate:
661
689
 
662
690
  Operational guardrails are not task requirements. A gap must trace to a brief requirement, an analysis-packet scope item, a source path the packet authorizes, or an evidence claim in a worker result. Do NOT infer missing verification from a one-line summary; open the named result and audit sidecar first.
663
691
 
664
- ```
665
- Inspect scope coverage for <task-key>. Below are the consolidated findings the
666
- workers produced. Your job has exactly two halves. Answer both.
667
-
668
- (1) MISSING — name what nobody covered:
669
- - files / directories / execution paths nobody inspected,
670
- - requirements or acceptance points with zero findings,
671
- - claims raised but never verified.
672
- For each, emit a NEW finding with evidence (file:line or the requirement quote).
673
-
674
- (2) UNREQUESTED — name work these findings propose that no requirement asked for:
675
- - a finding whose proposed change serves no requirement, scope item, or
676
- acceptance point you can QUOTE from the analysis packet,
677
- - an abstraction, configuration knob, or generalization proposed for a caller or
678
- a case nobody has stated,
679
- - a rewrite, migration, or cleanup of code the requirements never mention.
680
- For each, emit a candidate with `category: "unrequested-scope"`, quote the
681
- proposed work verbatim, and state which requirement you searched for and did not
682
- find.
683
-
684
- Do NOT restate an existing finding. Judge (2) against the analysis packet's
685
- requirements and scope, never against your own preference for how the code should
686
- look — "I would have done it differently" is not unrequested work, and neither is
687
- work the packet authorizes but you consider unnecessary. If a half has nothing,
688
- say so explicitly for that half; silence on one half is an incomplete result.
689
- ```
692
+ The two mandates and the `duplicateOf` rule live in the generator
693
+ (`okstra_ctl/convergence_critic_prompt.py` `_MANDATES`), not here. There is no
694
+ second copy to keep in step: `critic-prompt` renders that text under `## Mandate`
695
+ above the Round 0 list, and the lead pastes the output whole.
696
+
697
+ **A candidate that overlaps an existing finding is declared, not restated.** The
698
+ critic sets `duplicateOf` to that finding's id (`schemas/convergence-critic-results-v1.0.schema.json` `$defs.Candidate`). A restated finding costs a whole gap-verification round to reject; a declared duplicate costs none — it is recorded in the ledger, never dispatched, and counted in `config.critic.gapsDuplicate`. **Enforced:** `okstra convergence apply-critic-gaps` rejects a `duplicateOf` that names no finding the state carries, and rejects a duplicate row carrying votes.
690
699
 
691
700
  ### Gap verification (1 adversarial reverify round)
692
- Each critic gap enters the verification queue as a finding with `originWorker = "<provider>-critic"` and `source = "critic"`. The lead runs ONE adversarial reverify round (§"Adversarial Verification Mode" classifier) with **every Phase 4 analyser as a voter**. Choosing a critic provider that is already in the analyser roster costs nothing: the critic is a different role contract, a different duty and a different session, so an analyser is not disqualified by sharing its provider name (ADR-0017 — provider and model are not role identity, and the same model assigned to two roles gets two independent workers). The critic cannot judge its own gaps because it is not an analyser: the voter roster is `workers[]` filtered to `audience == "analysis"`, and a critic is not even representable there (the allowed values are `analysis` / `lead` / `report-writer`). `okstra apply-critic-gaps` refuses a vote from anyone outside that roster (`critic voter must be a non-critic analyser`). Only gaps classified `full-consensus` / `partial-consensus` merge into the final report findings; `contested` / `worker-unique` gaps are treated as hallucinations and dropped (recorded in the convergence state, not promoted).
701
+ Each critic gap enters the verification queue as a finding with `originWorker = "<provider>-critic"` and `source = "critic"`, except a gap the critic declared `duplicateOf` — that one is recorded and never dispatched. The lead runs ONE adversarial reverify round (§"Adversarial Verification Mode" classifier) in which **each gap is verified by exactly one Phase 4 analyser**: walk the analyser roster in `criticVerification.analyserRoster` order and assign gap *i* to `roster[i % len(roster)]`, then dispatch each assigned analyser once with its own gaps. Rejecting a gap costs the same as accepting one and this round is off the books (`rounds: []`, no `roundHistory` entry) on the serial path, so a batch of two gaps no longer wakes four analysers. **Enforced:** `okstra_ctl.convergence_engine._critic_gap_coverage_errors` accepts a dispatch set that is either the assignee set or the full roster, and rejects anything else — the full roster stays valid because a run finished before this rule cannot say which shape it used, the same dual acceptance `_validate_round_ledger_counts` gives the two round-counting arithmetics. Choosing a critic provider that is already in the analyser roster costs nothing: the critic is a different role contract, a different duty and a different session, so an analyser is not disqualified by sharing its provider name (ADR-0017 — provider and model are not role identity, and the same model assigned to two roles gets two independent workers). The critic cannot judge its own gaps because it is not an analyser: the voter roster is `workers[]` filtered to `audience == "analysis"`, and a critic is not even representable there (the allowed values are `analysis` / `lead` / `report-writer`). `okstra apply-critic-gaps` refuses a vote from anyone outside that roster (`critic voter must be a non-critic analyser`). Only gaps classified `full-consensus` / `partial-consensus` merge into the final report findings; `contested` / `worker-unique` gaps are treated as hallucinations and dropped (recorded in the convergence state, not promoted).
693
702
 
694
703
  **A gap that received no verdict is NOT a rejected gap (BLOCKING).** Dropping applies only to gaps the voters actually judged. A gap can also end the round *unjudged* — the verification dispatch returned a terminal non-result (`timeout`, `error`, no result file), the returned result covered only some of the gaps, or no non-critic analyser was available to vote at all. Nobody inspected those, so classifying them as hallucinations is a fabricated verdict. Each one MUST be recorded as a `## 5. Missing Information and Risks` row (`missingInformation`, `source: "critic-unverified"`) whose `risk` names the gap and the reason verification did not complete, and counted in `config.critic.gapsUnverified`. They are **not** promoted to findings (unverified) and **not** raised as `clarification` items — an unverified gap needs an analyser to verify it on the next run, not a decision from the user. Silently losing them is a contract violation: the batch that times out is exactly the batch of gaps too expensive to check, so the highest-risk items are the ones that vanish.
695
704
 
@@ -704,13 +713,13 @@ The asymmetry is deliberate and runs the opposite way from the coverage half: a
704
713
  ### State
705
714
  - `convergence.critic` manifest block: `{ enabled, provider, modelExecutionValue }`.
706
715
  - Each candidate's `category` tells the two halves apart: literal `"unrequested-scope"` for the over-scope half, any other value for a coverage gap. `schemas/convergence-critic-results-v1.0.schema.json` leaves `category` a free string, so this needs no schema or reducer change — but it also means nothing machine-checks the spelling. A misspelled category is read as a coverage gap and silently takes the drop-on-contested path.
707
- - The lead passes one canonical coverage batch with `{ schemaVersion, taskKey, mode, provider, modelExecutionValue, dispatches, gaps }`; each gap carries its candidate fields plus `gapId` and `votes`. `dispatches[]` contains exactly one row for every Phase 4 analyser except the critic, even when execution did not produce a result: persist `status: timeout | error | not-run` and the elapsed `durationMs` instead of omitting that analyser. `apply-critic-gaps` rejects a non-terminal main queue, duplicate or missing analysers, unknown workers, critic dispatches/votes, votes without a completed dispatch, and a second batch.
708
- - Convergence state artifact: merged gaps appear in `findings[]` with `source: "critic"` and `rounds: []`. The separate `criticVerification.gaps[]` ledger retains each gap's `summary`, `category`, `ticketIds`, `originEvidence`, optional `evidenceArtifacts`, classification, merge link, and votes. Strict v1.3 validation deterministically replays each complete critic-origin finding from that ledger; a critic batch never increments `roundHistory` or `totalRounds` and never creates a fake main round.
709
- - `config.critic` is `{ provider, modelExecutionValue, gapsProposed, gapsMerged, gapsRejected, gapsUnverified }`, with `gapsProposed = gapsMerged + gapsRejected + gapsUnverified`. `full-consensus` / `partial-consensus` gaps merge, `contested` / `worker-unique` gaps count as rejected, and gaps with no usable analyser vote appear in both the ledger and final `unverifiedGaps[]`.
716
+ - The lead passes one canonical coverage batch with `{ schemaVersion, taskKey, mode, provider, modelExecutionValue, dispatches, gaps }`; each gap carries its candidate fields plus `gapId` and `votes`, or `duplicateOf` and no votes. `dispatches[]` contains exactly one row per **assigned** analyser (§"Gap verification"), even when execution did not produce a result: persist `status: timeout | error | not-run` and the elapsed `durationMs` instead of omitting that analyser. A batch that dispatched every Phase 4 analyser instead is still accepted. `apply-critic-gaps` rejects a non-terminal main queue, a dispatch set matching neither shape, duplicate analysers, unknown workers, critic dispatches/votes, votes without a completed dispatch, a `duplicateOf` naming no existing finding, and a second batch.
717
+ - Convergence state artifact: merged gaps appear in `findings[]` with `source: "critic"` and `rounds: []`. The separate `criticVerification.gaps[]` ledger retains each gap's `summary`, `category`, `ticketIds`, `originEvidence`, optional `evidenceArtifacts`, classification, merge link, votes, and `duplicateOf` when the critic declared one. Strict validation deterministically replays each complete critic-origin finding from that ledger; a critic batch never increments `roundHistory` or `totalRounds` and never creates a fake main round.
718
+ - `config.critic` is `{ provider, modelExecutionValue, gapsProposed, gapsMerged, gapsRejected, gapsUnverified, gapsDuplicate }`, with `gapsProposed = gapsMerged + gapsRejected + gapsUnverified + gapsDuplicate`. `full-consensus` / `partial-consensus` gaps merge, `contested` / `worker-unique` gaps count as rejected, gaps with no usable analyser vote appear in both the ledger and final `unverifiedGaps[]`, and a declared duplicate takes classification `duplicate` — it makes no finding and joins no other counter. A state written before `gapsDuplicate` existed omits the key and stays valid; the validator reads its absence as 0.
710
719
 
711
720
  ## Acceptance critic pass (final-verification)
712
721
 
713
- The `final-verification` phase uses the same fresh one-shot `redispatch_worker` pattern and the same dispatch timing as §"Coverage critic pass" §"When" (provider + `config.critic.modelExecutionValue` from the `convergence.critic` block; critic is required; same model-unresolved skip rule) — the delivered work the critic inspects is likewise fixed before the reverify round starts. Only the prompt, the verification semantics, and the output sink differ — final-verification's findings are defects/blockers, so the critic acts as an **acceptance devil's advocate** (find reasons NOT to accept), and its candidate blockers are NEVER dropped (that would suppress real defects).
722
+ The `final-verification` phase uses the same fresh one-shot `redispatch_worker` pattern and the same dispatch timing as §"Coverage critic pass" §"When" (provider + `config.critic.modelExecutionValue` from the `convergence.critic` block; critic is opt-in — the pass is skipped when the run resolved no critic; same model-unresolved skip rule) — the delivered work the critic inspects is likewise fixed before the reverify round starts. Only the prompt, the verification semantics, and the output sink differ — final-verification's findings are defects/blockers, so the critic acts as an **acceptance devil's advocate** (find reasons NOT to accept), and its candidate blockers are NEVER dropped (that would suppress real defects).
714
723
 
715
724
  Before that call, write the acceptance-only task instructions and run `okstra
716
725
  agent-prompt materialize` with `--audience acceptance-critic`,
@@ -751,13 +760,13 @@ Promoted blockers enter `## 5.8 Acceptance Blockers`; since `accepted` requires
751
760
 
752
761
  ### State
753
762
 
754
- Critic output lives in the run's `worker-results/` directory (`runs/final-verification/worker-results/` for whole-task verification, `runs/final-verification/stage-<N>/worker-results/` for single-stage), filename `<provider>-worker-critic-final-verification-<seq>.md` (same `-worker-` token rule as §"Coverage critic pass" — the audit sidecar is derived from it). The convergence state `config.critic` summary records `mode: "acceptance-devils-advocate"`, `candidatesProposed`, `confirmedBlockers`, `downgradedToResidual`; v1.3 enforces `candidatesProposed = confirmedBlockers + downgradedToResidual`, so no candidate can be silently dropped.
763
+ Critic output lives in the run's `worker-results/` directory (`runs/final-verification/worker-results/` for whole-task verification, `runs/final-verification/stage-<N>/worker-results/` for single-stage), filename `<provider>-worker-critic-final-verification-<seq>.md` (same `-worker-` token rule as §"Coverage critic pass" — the audit sidecar is derived from it). The convergence state `config.critic` summary records `mode: "acceptance-devils-advocate"`, `candidatesProposed`, `confirmedBlockers`, `downgradedToResidual`; v1.4 enforces `candidatesProposed = confirmedBlockers + downgradedToResidual`, so no candidate can be silently dropped.
755
764
 
756
765
  ## Output
757
766
 
758
767
  Information to be passed to Phase 6 after completing this contract:
759
768
 
760
- - Newly finalized convergence output is schema v1.3. Under `reuse-final`, a valid historical final schema v1.0, v1.1, or v1.2 remains consumable by the report-writer without rewrite. Either validated terminal artifact contains the four-category classification of all findings; the report-writer consumes it and does not vote
769
+ - Newly finalized convergence output is schema v1.4. Under `reuse-final`, a valid historical final schema v1.0, v1.1, v1.2, or v1.3 remains consumable by the report-writer without rewrite. Either validated terminal artifact contains the classification of every finding; the report-writer consumes it and does not vote
761
770
  - Round history and votes per worker for each finding
762
771
  - Path to the convergence state artifact
763
772
  - Convergence summary (count per category)