okstra 0.186.7 → 0.188.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 (546) 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 +107 -40
  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 +39 -38
  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 +47 -22
  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/adapters/cmux.md +3 -3
  100. package/runtime/prompts/lead/convergence.md +99 -88
  101. package/runtime/prompts/lead/okstra-lead-contract.md +63 -12
  102. package/runtime/prompts/lead/plan-body-verification.md +65 -46
  103. package/runtime/prompts/lead/report-writer.md +35 -5
  104. package/runtime/prompts/lead/team-contract.md +4 -1
  105. package/runtime/prompts/profiles/_clarification-recommendation.md +2 -1
  106. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  107. package/runtime/prompts/profiles/_common-contract.md +3 -3
  108. package/runtime/prompts/profiles/_coverage-critic.md +1 -1
  109. package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
  110. package/runtime/prompts/profiles/_implementation-executor.md +3 -1
  111. package/runtime/prompts/profiles/_implementation-verifier.md +16 -2
  112. package/runtime/prompts/profiles/error-analysis.md +4 -3
  113. package/runtime/prompts/profiles/final-verification.md +2 -2
  114. package/runtime/prompts/profiles/implementation-planning.md +22 -17
  115. package/runtime/prompts/profiles/implementation.md +3 -2
  116. package/runtime/prompts/profiles/requirements-discovery.md +2 -1
  117. package/runtime/prompts/wizard/prompts.ko.json +29 -8
  118. package/runtime/python/okstra_ctl/__init__.py +6 -27
  119. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -5
  120. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +21 -2
  121. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +3 -12
  122. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +63 -22
  123. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +20 -21
  124. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +37 -20
  125. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +86 -14
  126. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +12 -12
  127. package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +4 -7
  128. package/runtime/python/okstra_ctl/agent/__init__.py +1 -0
  129. package/runtime/python/okstra_ctl/{agent_activity.py → agent/activity.py} +16 -2
  130. package/runtime/python/okstra_ctl/{agent_invocation.py → agent/invocation.py} +34 -5
  131. package/runtime/python/okstra_ctl/agent/prompt_cli/__init__.py +19 -0
  132. package/runtime/python/okstra_ctl/agent/prompt_cli/__main__.py +11 -0
  133. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +228 -0
  134. package/runtime/python/okstra_ctl/agent/prompt_cli/dynamic_verifier.py +155 -0
  135. package/runtime/python/okstra_ctl/agent/prompt_cli/emit.py +66 -0
  136. package/runtime/python/okstra_ctl/agent/prompt_cli/inputs.py +138 -0
  137. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +605 -0
  138. package/runtime/python/okstra_ctl/agent/prompt_cli/results.py +197 -0
  139. package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +107 -0
  140. package/runtime/python/okstra_ctl/analysis_packet.py +47 -4
  141. package/runtime/python/okstra_ctl/approval_decisions.py +287 -10
  142. package/runtime/python/okstra_ctl/assignment_resolver.py +0 -12
  143. package/runtime/python/okstra_ctl/attempt_evidence.py +12 -24
  144. package/runtime/python/okstra_ctl/blocking_checks.py +251 -0
  145. package/runtime/python/okstra_ctl/brief_frontmatter.py +0 -7
  146. package/runtime/python/okstra_ctl/clarification_items/__init__.py +102 -0
  147. package/runtime/python/okstra_ctl/clarification_items/carry.py +224 -0
  148. package/runtime/python/okstra_ctl/clarification_items/dispositions.py +135 -0
  149. package/runtime/python/okstra_ctl/clarification_items/parsing.py +253 -0
  150. package/runtime/python/okstra_ctl/clarification_items/rows.py +115 -0
  151. package/runtime/python/okstra_ctl/clarification_items/scan.py +215 -0
  152. package/runtime/python/okstra_ctl/clarification_items/sidecars.py +211 -0
  153. package/runtime/python/okstra_ctl/cmux.py +38 -31
  154. package/runtime/python/okstra_ctl/code_review_target.py +163 -1
  155. package/runtime/python/okstra_ctl/conformance.py +23 -0
  156. package/runtime/python/okstra_ctl/container.py +38 -421
  157. package/runtime/python/okstra_ctl/context_cost.py +32 -4
  158. package/runtime/python/okstra_ctl/contract_graph_cli.py +7 -0
  159. package/runtime/python/okstra_ctl/convergence.py +107 -16
  160. package/runtime/python/okstra_ctl/convergence_critic_prompt.py +345 -0
  161. package/runtime/python/okstra_ctl/convergence_engine.py +382 -70
  162. package/runtime/python/okstra_ctl/convergence_store.py +5 -29
  163. package/runtime/python/okstra_ctl/design_prep.py +108 -12
  164. package/runtime/python/okstra_ctl/design_snapshot.py +11 -1
  165. package/runtime/python/okstra_ctl/design_surfaces.py +29 -2
  166. package/runtime/python/okstra_ctl/dispatch_core.py +236 -49
  167. package/runtime/python/okstra_ctl/dispatch_state.py +417 -41
  168. package/runtime/python/okstra_ctl/doctor_cli.py +48 -0
  169. package/runtime/python/okstra_ctl/domain/worker_exec.py +10 -5
  170. package/runtime/python/okstra_ctl/domain/worker_presentation.py +28 -1
  171. package/runtime/python/okstra_ctl/domain/worker_stream.py +52 -21
  172. package/runtime/python/okstra_ctl/domain/write_policy.py +219 -0
  173. package/runtime/python/okstra_ctl/entrypoints/hosts.py +9 -2
  174. package/runtime/python/okstra_ctl/error_log_core.py +1 -1
  175. package/runtime/python/okstra_ctl/error_report.py +14 -0
  176. package/runtime/python/okstra_ctl/error_zip.py +18 -2
  177. package/runtime/python/okstra_ctl/exact_coverage.py +5 -0
  178. package/runtime/python/okstra_ctl/execution_identity.py +100 -7
  179. package/runtime/python/okstra_ctl/execution_manifest.py +67 -20
  180. package/runtime/python/okstra_ctl/execution_mutation_audit.py +90 -8
  181. package/runtime/python/okstra_ctl/final_report_paths.py +17 -0
  182. package/runtime/python/okstra_ctl/final_report_schema.py +319 -17
  183. package/runtime/python/okstra_ctl/git_reconcile.py +22 -2
  184. package/runtime/python/okstra_ctl/handoff.py +24 -1
  185. package/runtime/python/okstra_ctl/ids.py +6 -16
  186. package/runtime/python/okstra_ctl/implementation_direction.py +142 -33
  187. package/runtime/python/okstra_ctl/implementation_options.py +32 -13
  188. package/runtime/python/okstra_ctl/implementation_outcome.py +5 -2
  189. package/runtime/python/okstra_ctl/improvement_lenses.py +0 -14
  190. package/runtime/python/okstra_ctl/incremental_carry.py +69 -8
  191. package/runtime/python/okstra_ctl/incremental_scope.py +173 -12
  192. package/runtime/python/okstra_ctl/index.py +2 -2
  193. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +48 -8
  194. package/runtime/python/okstra_ctl/interactive_cli.py +223 -0
  195. package/runtime/python/okstra_ctl/json_boundary.py +10 -0
  196. package/runtime/python/okstra_ctl/listing.py +0 -91
  197. package/runtime/python/okstra_ctl/locks.py +5 -19
  198. package/runtime/python/okstra_ctl/log_report.py +14 -0
  199. package/runtime/python/okstra_ctl/manager_cli.py +22 -3
  200. package/runtime/python/okstra_ctl/manager_launch.py +4 -8
  201. package/runtime/python/okstra_ctl/material.py +2 -2
  202. package/runtime/python/okstra_ctl/migrate.py +23 -1
  203. package/runtime/python/okstra_ctl/model_cli.py +19 -5
  204. package/runtime/python/okstra_ctl/model_discovery.py +46 -47
  205. package/runtime/python/okstra_ctl/model_io/__init__.py +1 -0
  206. package/runtime/python/okstra_ctl/model_io/lines.py +163 -0
  207. package/runtime/python/okstra_ctl/model_io/references.py +370 -0
  208. package/runtime/python/okstra_ctl/model_io/renderers.py +498 -0
  209. package/runtime/python/okstra_ctl/model_io_cli.py +30 -945
  210. package/runtime/python/okstra_ctl/model_pool.py +10 -6
  211. package/runtime/python/okstra_ctl/models.py +8 -8
  212. package/runtime/python/okstra_ctl/mutation_recovery.py +5 -63
  213. package/runtime/python/okstra_ctl/next_phase.py +121 -69
  214. package/runtime/python/okstra_ctl/pane_reclaim.py +23 -0
  215. package/runtime/python/okstra_ctl/pane_title.py +11 -8
  216. package/runtime/python/okstra_ctl/path_hints.py +36 -11
  217. package/runtime/python/okstra_ctl/paths.py +76 -8
  218. package/runtime/python/okstra_ctl/plan_items.py +132 -16
  219. package/runtime/python/okstra_ctl/plan_items_cli.py +486 -36
  220. package/runtime/python/okstra_ctl/plan_run_root.py +1 -1
  221. package/runtime/python/okstra_ctl/plan_validate_cli.py +51 -0
  222. package/runtime/python/okstra_ctl/plan_verify_cli.py +80 -0
  223. package/runtime/python/okstra_ctl/prepare_error.py +20 -0
  224. package/runtime/python/okstra_ctl/prior_planning.py +157 -0
  225. package/runtime/python/okstra_ctl/profile_show.py +18 -1
  226. package/runtime/python/okstra_ctl/project_setup_cli.py +140 -0
  227. package/runtime/python/okstra_ctl/recap.py +22 -1
  228. package/runtime/python/okstra_ctl/reconcile.py +84 -0
  229. package/runtime/python/okstra_ctl/registry/host_registry.py +18 -1
  230. package/runtime/python/okstra_ctl/render.py +98 -32
  231. package/runtime/python/okstra_ctl/render_final_report.py +4 -8
  232. package/runtime/python/okstra_ctl/report_assembly.py +297 -7
  233. package/runtime/python/okstra_ctl/report_contract.py +30 -7
  234. package/runtime/python/okstra_ctl/report_finalize.py +213 -16
  235. package/runtime/python/okstra_ctl/report_html/common.py +2 -2
  236. package/runtime/python/okstra_ctl/report_html/render.py +8 -12
  237. package/runtime/python/okstra_ctl/report_language.py +8 -4
  238. package/runtime/python/okstra_ctl/report_narrative.py +220 -34
  239. package/runtime/python/okstra_ctl/report_projections.py +8 -2
  240. package/runtime/python/okstra_ctl/report_synthesis_packet.py +393 -1
  241. package/runtime/python/okstra_ctl/report_translation.py +2 -35
  242. package/runtime/python/okstra_ctl/report_view_artifacts.py +42 -2
  243. package/runtime/python/okstra_ctl/report_views.py +4 -22
  244. package/runtime/python/okstra_ctl/resolve_task_key.py +13 -0
  245. package/runtime/python/okstra_ctl/rollup.py +13 -0
  246. package/runtime/python/okstra_ctl/run.py +639 -138
  247. package/runtime/python/okstra_ctl/run_audit.py +13 -0
  248. package/runtime/python/okstra_ctl/run_index_row.py +0 -12
  249. package/runtime/python/okstra_ctl/seeding.py +21 -11
  250. package/runtime/python/okstra_ctl/sequence.py +1 -1
  251. package/runtime/python/okstra_ctl/session.py +64 -14
  252. package/runtime/python/okstra_ctl/set_work_status.py +18 -0
  253. package/runtime/python/okstra_ctl/stage_integrate.py +15 -1
  254. package/runtime/python/okstra_ctl/stage_ledger.py +1 -1
  255. package/runtime/python/okstra_ctl/stage_map.py +57 -11
  256. package/runtime/python/okstra_ctl/stage_map_cli.py +83 -0
  257. package/runtime/python/okstra_ctl/stage_map_view.py +65 -0
  258. package/runtime/python/okstra_ctl/stage_targets.py +3 -12
  259. package/runtime/python/okstra_ctl/task_list_cli.py +138 -0
  260. package/runtime/python/okstra_ctl/task_show_cli.py +66 -0
  261. package/runtime/python/okstra_ctl/task_target.py +4 -16
  262. package/runtime/python/okstra_ctl/team.py +49 -2
  263. package/runtime/python/okstra_ctl/time_report.py +17 -2
  264. package/runtime/python/okstra_ctl/usage_report.py +11 -0
  265. package/runtime/python/okstra_ctl/user_response.py +55 -218
  266. package/runtime/python/okstra_ctl/user_response_values.py +242 -0
  267. package/runtime/python/okstra_ctl/validation_contract.py +3 -0
  268. package/runtime/python/okstra_ctl/verdict_blocks.py +28 -8
  269. package/runtime/python/okstra_ctl/verification_target.py +68 -0
  270. package/runtime/python/okstra_ctl/wizard.py +457 -92
  271. package/runtime/python/okstra_ctl/worker_artifacts.py +75 -16
  272. package/runtime/python/okstra_ctl/worker_audit_check.py +22 -0
  273. package/runtime/python/okstra_ctl/worker_dispatch.py +21 -0
  274. package/runtime/python/okstra_ctl/worker_liveness.py +33 -0
  275. package/runtime/python/okstra_ctl/worker_prompt_body.py +15 -2
  276. package/runtime/python/okstra_ctl/worker_prompt_contract.py +98 -7
  277. package/runtime/python/okstra_ctl/worker_prompt_headers.py +24 -2
  278. package/runtime/python/okstra_ctl/worker_prompt_policy.py +18 -3
  279. package/runtime/python/okstra_ctl/worker_request.py +2 -1
  280. package/runtime/python/okstra_ctl/worker_runner.py +58 -17
  281. package/runtime/python/okstra_ctl/worker_state.py +16 -1
  282. package/runtime/python/okstra_ctl/workflow.py +18 -1
  283. package/runtime/python/okstra_ctl/worktree/__init__.py +92 -0
  284. package/runtime/python/okstra_ctl/worktree/cleanliness.py +89 -0
  285. package/runtime/python/okstra_ctl/worktree/decisions.py +127 -0
  286. package/runtime/python/okstra_ctl/worktree/git_ops.py +165 -0
  287. package/runtime/python/okstra_ctl/worktree/linking.py +249 -0
  288. package/runtime/python/okstra_ctl/worktree/naming.py +91 -0
  289. package/runtime/python/okstra_ctl/worktree/provision.py +385 -0
  290. package/runtime/python/okstra_ctl/worktree/sync_config.py +181 -0
  291. package/runtime/python/okstra_ctl/worktree_cli.py +75 -0
  292. package/runtime/python/okstra_ctl/worktree_lookup_cli.py +39 -0
  293. package/runtime/python/okstra_ctl/worktree_registry.py +7 -0
  294. package/runtime/python/okstra_ctl/worktree_status_cli.py +53 -0
  295. package/runtime/python/okstra_ctl/write_policy.py +90 -213
  296. package/runtime/python/okstra_project/__init__.py +0 -4
  297. package/runtime/python/okstra_project/dirs.py +2 -2
  298. package/runtime/python/okstra_project/phase_pointer.py +84 -0
  299. package/runtime/python/okstra_project/slug.py +24 -0
  300. package/runtime/python/okstra_project/state.py +22 -230
  301. package/runtime/python/okstra_token_usage/antigravity.py +50 -12
  302. package/runtime/python/okstra_token_usage/claude.py +9 -1
  303. package/runtime/python/okstra_token_usage/cli.py +3 -1
  304. package/runtime/python/okstra_token_usage/collect.py +171 -36
  305. package/runtime/python/okstra_token_usage/cursor.py +2 -0
  306. package/runtime/python/okstra_token_usage/grok.py +1 -5
  307. package/runtime/python/okstra_token_usage/paths.py +26 -0
  308. package/runtime/python/okstra_token_usage/pricing.py +23 -8
  309. package/runtime/schemas/convergence-critic-results-v1.0.schema.json +5 -0
  310. package/runtime/schemas/execution-manifest-v2.schema.json +10 -10
  311. package/runtime/schemas/final-report-v2.0.schema.json +9 -26
  312. package/runtime/schemas/final-report-v3.0.schema.json +173 -41
  313. package/runtime/schemas/report-narrative-v3.0.schema.json +3 -2
  314. package/runtime/skills/okstra-brief-gen/SKILL.md +35 -2
  315. package/runtime/skills/okstra-container-build/SKILL.md +16 -47
  316. package/runtime/skills/okstra-inspect/facets/history.md +1 -1
  317. package/runtime/skills/okstra-inspect/facets/status.md +7 -6
  318. package/runtime/skills/okstra-pr-gen/SKILL.md +1 -1
  319. package/runtime/skills/okstra-run/SKILL.md +7 -5
  320. package/runtime/templates/report-writer-prompt-preamble.md +3 -1
  321. package/runtime/templates/reports/brief.template.md +6 -2
  322. package/runtime/templates/reports/error-analysis-input.template.md +2 -0
  323. package/runtime/templates/reports/final-verification-input.template.md +2 -0
  324. package/runtime/templates/reports/implementation-input.template.md +7 -1
  325. package/runtime/templates/reports/implementation-planning-input.template.md +4 -0
  326. package/runtime/templates/reports/quick-input.template.md +2 -0
  327. package/runtime/templates/reports/release-handoff-input.template.md +6 -1
  328. package/runtime/templates/reports/report.js +20 -3
  329. package/runtime/templates/reports/schedule.template.md +2 -0
  330. package/runtime/templates/reports/settings.template.json +0 -10
  331. package/runtime/templates/reports/task-brief.template.md +3 -0
  332. package/runtime/templates/reports/user-response.template.md +7 -3
  333. package/runtime/validators/checks/fixtures-01.py +57 -0
  334. package/runtime/validators/checks/fixtures-02.py +565 -0
  335. package/runtime/validators/checks/runners-01.py +108 -0
  336. package/runtime/validators/checks/validate-assets-01.py +60 -0
  337. package/runtime/validators/checks/validate-prompt-metadata-01.py +261 -0
  338. package/runtime/validators/checks/validate-tasks-01.py +46 -0
  339. package/runtime/validators/checks/validate-tasks-02.py +85 -0
  340. package/runtime/validators/checks/validate-tasks-03.py +61 -0
  341. package/runtime/validators/checks/validate-tasks-04.py +118 -0
  342. package/runtime/validators/forbidden_actions.py +73 -3
  343. package/runtime/validators/lib/common.sh +5 -0
  344. package/runtime/validators/lib/fixtures.sh +7 -591
  345. package/runtime/validators/lib/paths.sh +13 -0
  346. package/runtime/validators/lib/runners.sh +6 -104
  347. package/runtime/validators/lib/summary.sh +1 -1
  348. package/runtime/validators/lib/validate-assets.sh +6 -56
  349. package/runtime/validators/lib/validate-prompt-metadata.sh +6 -257
  350. package/runtime/validators/lib/validate-tasks.sh +9 -294
  351. package/runtime/validators/validate-implementation-plan-stages.py +4 -4
  352. package/runtime/validators/validate-run.py +1363 -2643
  353. package/runtime/validators/validate-workflow.sh +56 -16
  354. package/runtime/validators/validate_improvement_report.py +2 -1
  355. package/runtime/validators/validate_session_conformance.py +295 -49
  356. package/dist/commands/execute/agent-prompt.d.mts +0 -1
  357. package/dist/commands/execute/agent-prompt.mjs +0 -24
  358. package/dist/commands/execute/agent-prompt.mjs.map +0 -1
  359. package/dist/commands/execute/codex-dispatch.d.mts +0 -3
  360. package/dist/commands/execute/codex-dispatch.mjs +0 -6
  361. package/dist/commands/execute/codex-dispatch.mjs.map +0 -1
  362. package/dist/commands/execute/codex-run.d.mts +0 -3
  363. package/dist/commands/execute/codex-run.mjs +0 -62
  364. package/dist/commands/execute/codex-run.mjs.map +0 -1
  365. package/dist/commands/execute/convergence.d.mts +0 -1
  366. package/dist/commands/execute/convergence.mjs +0 -38
  367. package/dist/commands/execute/convergence.mjs.map +0 -1
  368. package/dist/commands/execute/error-log.d.mts +0 -1
  369. package/dist/commands/execute/error-log.mjs +0 -18
  370. package/dist/commands/execute/error-log.mjs.map +0 -1
  371. package/dist/commands/execute/git-reconcile.d.mts +0 -1
  372. package/dist/commands/execute/git-reconcile.mjs +0 -30
  373. package/dist/commands/execute/git-reconcile.mjs.map +0 -1
  374. package/dist/commands/execute/handoff.d.mts +0 -1
  375. package/dist/commands/execute/handoff.mjs +0 -31
  376. package/dist/commands/execute/handoff.mjs.map +0 -1
  377. package/dist/commands/execute/incremental-carry.d.mts +0 -1
  378. package/dist/commands/execute/incremental-carry.mjs +0 -20
  379. package/dist/commands/execute/incremental-carry.mjs.map +0 -1
  380. package/dist/commands/execute/incremental-scope.d.mts +0 -1
  381. package/dist/commands/execute/incremental-scope.mjs +0 -29
  382. package/dist/commands/execute/incremental-scope.mjs.map +0 -1
  383. package/dist/commands/execute/integrate-stages.d.mts +0 -1
  384. package/dist/commands/execute/integrate-stages.mjs +0 -24
  385. package/dist/commands/execute/integrate-stages.mjs.map +0 -1
  386. package/dist/commands/execute/pane-title.d.mts +0 -1
  387. package/dist/commands/execute/pane-title.mjs +0 -20
  388. package/dist/commands/execute/pane-title.mjs.map +0 -1
  389. package/dist/commands/execute/plan-items.d.mts +0 -1
  390. package/dist/commands/execute/plan-items.mjs +0 -9
  391. package/dist/commands/execute/plan-items.mjs.map +0 -1
  392. package/dist/commands/execute/plan-validate.d.mts +0 -1
  393. package/dist/commands/execute/plan-validate.mjs +0 -68
  394. package/dist/commands/execute/plan-validate.mjs.map +0 -1
  395. package/dist/commands/execute/plan-verify.d.mts +0 -1
  396. package/dist/commands/execute/plan-verify.mjs +0 -43
  397. package/dist/commands/execute/plan-verify.mjs.map +0 -1
  398. package/dist/commands/execute/spawn-followups.d.mts +0 -1
  399. package/dist/commands/execute/spawn-followups.mjs +0 -22
  400. package/dist/commands/execute/spawn-followups.mjs.map +0 -1
  401. package/dist/commands/execute/team.d.mts +0 -3
  402. package/dist/commands/execute/team.mjs +0 -66
  403. package/dist/commands/execute/team.mjs.map +0 -1
  404. package/dist/commands/execute/token-usage.d.mts +0 -1
  405. package/dist/commands/execute/token-usage.mjs +0 -19
  406. package/dist/commands/execute/token-usage.mjs.map +0 -1
  407. package/dist/commands/execute/worker-audit-check.d.mts +0 -1
  408. package/dist/commands/execute/worker-audit-check.mjs +0 -34
  409. package/dist/commands/execute/worker-audit-check.mjs.map +0 -1
  410. package/dist/commands/execute/worker-dispatch.d.mts +0 -7
  411. package/dist/commands/execute/worker-dispatch.mjs +0 -64
  412. package/dist/commands/execute/worker-dispatch.mjs.map +0 -1
  413. package/dist/commands/execute/worker-state.d.mts +0 -1
  414. package/dist/commands/execute/worker-state.mjs +0 -28
  415. package/dist/commands/execute/worker-state.mjs.map +0 -1
  416. package/dist/commands/execute/worktree-lookup.d.mts +0 -1
  417. package/dist/commands/execute/worktree-lookup.mjs +0 -92
  418. package/dist/commands/execute/worktree-lookup.mjs.map +0 -1
  419. package/dist/commands/execute/worktree-status.d.mts +0 -1
  420. package/dist/commands/execute/worktree-status.mjs +0 -121
  421. package/dist/commands/execute/worktree-status.mjs.map +0 -1
  422. package/dist/commands/inspect/code-review.d.mts +0 -1
  423. package/dist/commands/inspect/code-review.mjs +0 -32
  424. package/dist/commands/inspect/code-review.mjs.map +0 -1
  425. package/dist/commands/inspect/container.d.mts +0 -1
  426. package/dist/commands/inspect/container.mjs +0 -25
  427. package/dist/commands/inspect/container.mjs.map +0 -1
  428. package/dist/commands/inspect/context-cost.d.mts +0 -1
  429. package/dist/commands/inspect/context-cost.mjs +0 -25
  430. package/dist/commands/inspect/context-cost.mjs.map +0 -1
  431. package/dist/commands/inspect/design-prep.d.mts +0 -1
  432. package/dist/commands/inspect/design-prep.mjs +0 -22
  433. package/dist/commands/inspect/design-prep.mjs.map +0 -1
  434. package/dist/commands/inspect/error-report.d.mts +0 -1
  435. package/dist/commands/inspect/error-report.mjs +0 -25
  436. package/dist/commands/inspect/error-report.mjs.map +0 -1
  437. package/dist/commands/inspect/error-zip.d.mts +0 -1
  438. package/dist/commands/inspect/error-zip.mjs +0 -24
  439. package/dist/commands/inspect/error-zip.mjs.map +0 -1
  440. package/dist/commands/inspect/log-report.d.mts +0 -1
  441. package/dist/commands/inspect/log-report.mjs +0 -26
  442. package/dist/commands/inspect/log-report.mjs.map +0 -1
  443. package/dist/commands/inspect/model-io.d.mts +0 -1
  444. package/dist/commands/inspect/model-io.mjs +0 -25
  445. package/dist/commands/inspect/model-io.mjs.map +0 -1
  446. package/dist/commands/inspect/profile-show.d.mts +0 -1
  447. package/dist/commands/inspect/profile-show.mjs +0 -28
  448. package/dist/commands/inspect/profile-show.mjs.map +0 -1
  449. package/dist/commands/inspect/recap.d.mts +0 -1
  450. package/dist/commands/inspect/recap.mjs +0 -30
  451. package/dist/commands/inspect/recap.mjs.map +0 -1
  452. package/dist/commands/inspect/resolve-task-key.d.mts +0 -1
  453. package/dist/commands/inspect/resolve-task-key.mjs +0 -24
  454. package/dist/commands/inspect/resolve-task-key.mjs.map +0 -1
  455. package/dist/commands/inspect/rollup.d.mts +0 -1
  456. package/dist/commands/inspect/rollup.mjs +0 -25
  457. package/dist/commands/inspect/rollup.mjs.map +0 -1
  458. package/dist/commands/inspect/run-audit.d.mts +0 -1
  459. package/dist/commands/inspect/run-audit.mjs +0 -25
  460. package/dist/commands/inspect/run-audit.mjs.map +0 -1
  461. package/dist/commands/inspect/set-work-status.d.mts +0 -1
  462. package/dist/commands/inspect/set-work-status.mjs +0 -29
  463. package/dist/commands/inspect/set-work-status.mjs.map +0 -1
  464. package/dist/commands/inspect/stage-map.d.mts +0 -1
  465. package/dist/commands/inspect/stage-map.mjs +0 -131
  466. package/dist/commands/inspect/stage-map.mjs.map +0 -1
  467. package/dist/commands/inspect/task-list.d.mts +0 -1
  468. package/dist/commands/inspect/task-list.mjs +0 -149
  469. package/dist/commands/inspect/task-list.mjs.map +0 -1
  470. package/dist/commands/inspect/task-show.d.mts +0 -1
  471. package/dist/commands/inspect/task-show.mjs +0 -108
  472. package/dist/commands/inspect/task-show.mjs.map +0 -1
  473. package/dist/commands/inspect/time-report.d.mts +0 -1
  474. package/dist/commands/inspect/time-report.mjs +0 -24
  475. package/dist/commands/inspect/time-report.mjs.map +0 -1
  476. package/dist/commands/inspect/usage-report.d.mts +0 -1
  477. package/dist/commands/inspect/usage-report.mjs +0 -23
  478. package/dist/commands/inspect/usage-report.mjs.map +0 -1
  479. package/dist/commands/inspect/user-response.d.mts +0 -1
  480. package/dist/commands/inspect/user-response.mjs +0 -35
  481. package/dist/commands/inspect/user-response.mjs.map +0 -1
  482. package/dist/commands/inspect/worker-liveness.d.mts +0 -1
  483. package/dist/commands/inspect/worker-liveness.mjs +0 -45
  484. package/dist/commands/inspect/worker-liveness.mjs.map +0 -1
  485. package/dist/commands/lifecycle/contract-check.d.mts +0 -1
  486. package/dist/commands/lifecycle/contract-check.mjs +0 -18
  487. package/dist/commands/lifecycle/contract-check.mjs.map +0 -1
  488. package/dist/commands/lifecycle/migrate.d.mts +0 -1
  489. package/dist/commands/lifecycle/migrate.mjs +0 -30
  490. package/dist/commands/lifecycle/migrate.mjs.map +0 -1
  491. package/dist/commands/lifecycle/model.d.mts +0 -1
  492. package/dist/commands/lifecycle/model.mjs +0 -22
  493. package/dist/commands/lifecycle/model.mjs.map +0 -1
  494. package/dist/commands/manager.d.mts +0 -3
  495. package/dist/commands/manager.mjs +0 -50
  496. package/dist/commands/manager.mjs.map +0 -1
  497. package/dist/commands/report/agent-activity.d.mts +0 -1
  498. package/dist/commands/report/agent-activity.mjs +0 -20
  499. package/dist/commands/report/agent-activity.mjs.map +0 -1
  500. package/dist/commands/report/approval-decision.d.mts +0 -1
  501. package/dist/commands/report/approval-decision.mjs +0 -21
  502. package/dist/commands/report/approval-decision.mjs.map +0 -1
  503. package/dist/commands/report/design-snapshot.d.mts +0 -1
  504. package/dist/commands/report/design-snapshot.mjs +0 -19
  505. package/dist/commands/report/design-snapshot.mjs.map +0 -1
  506. package/dist/commands/report/finalize.d.mts +0 -4
  507. package/dist/commands/report/finalize.mjs +0 -64
  508. package/dist/commands/report/finalize.mjs.map +0 -1
  509. package/dist/commands/report/inject-report-index.d.mts +0 -1
  510. package/dist/commands/report/inject-report-index.mjs +0 -21
  511. package/dist/commands/report/inject-report-index.mjs.map +0 -1
  512. package/dist/commands/report/render-final-report.d.mts +0 -1
  513. package/dist/commands/report/render-final-report.mjs +0 -23
  514. package/dist/commands/report/render-final-report.mjs.map +0 -1
  515. package/dist/commands/report/render-views.d.mts +0 -1
  516. package/dist/commands/report/render-views.mjs +0 -27
  517. package/dist/commands/report/render-views.mjs.map +0 -1
  518. package/dist/commands/report/translate.d.mts +0 -1
  519. package/dist/commands/report/translate.mjs +0 -33
  520. package/dist/commands/report/translate.mjs.map +0 -1
  521. package/runtime/bin/lib/okstra/tmux-pane.sh +0 -40
  522. package/runtime/bin/lib/okstra-ctl/cmd-batch.sh +0 -59
  523. package/runtime/bin/lib/okstra-ctl/cmd-list.sh +0 -35
  524. package/runtime/bin/lib/okstra-ctl/cmd-open.sh +0 -36
  525. package/runtime/bin/lib/okstra-ctl/cmd-projects.sh +0 -26
  526. package/runtime/bin/lib/okstra-ctl/cmd-reconcile.sh +0 -29
  527. package/runtime/bin/lib/okstra-ctl/cmd-reindex.sh +0 -38
  528. package/runtime/bin/lib/okstra-ctl/cmd-rerun.sh +0 -345
  529. package/runtime/bin/lib/okstra-ctl/cmd-show.sh +0 -27
  530. package/runtime/bin/lib/okstra-ctl/cmd-tail.sh +0 -92
  531. package/runtime/bin/lib/okstra-ctl/main.sh +0 -41
  532. package/runtime/bin/lib/okstra-ctl/prepare.sh +0 -31
  533. package/runtime/bin/lib/okstra-ctl/usage.sh +0 -23
  534. package/runtime/bin/okstra-central.sh +0 -152
  535. package/runtime/bin/okstra-incremental-carry.py +0 -10
  536. package/runtime/bin/okstra-incremental-scope.py +0 -10
  537. package/runtime/bin/okstra-team-reconcile.sh +0 -36
  538. package/runtime/python/okstra_ctl/agent_prompt_cli.py +0 -1156
  539. package/runtime/python/okstra_ctl/batch.py +0 -60
  540. package/runtime/python/okstra_ctl/clarification_items.py +0 -1050
  541. package/runtime/python/okstra_ctl/container_registry.py +0 -72
  542. package/runtime/python/okstra_ctl/improvement_assignment.py +0 -61
  543. package/runtime/python/okstra_ctl/resolver.py +0 -54
  544. package/runtime/python/okstra_ctl/team_reconcile.py +0 -275
  545. package/runtime/python/okstra_ctl/tmux.py +0 -134
  546. package/runtime/python/okstra_ctl/worktree.py +0 -1099
@@ -6,7 +6,7 @@ import json
6
6
  from collections.abc import Mapping
7
7
  from dataclasses import dataclass, field
8
8
  from pathlib import Path
9
- from typing import Any
9
+ from typing import Any, Literal
10
10
 
11
11
  from .domain.provider import HostModelBinding, ServedModelAttestation
12
12
  from .domain.worker_exec import WorkerWriteCapability
@@ -116,9 +116,6 @@ class Invocation:
116
116
  dispatch_kind: str
117
117
  round: int
118
118
  input_digest: str
119
- write_policy: Mapping[str, Any]
120
- write_policy_digest: str
121
- write_enforcement: Mapping[str, Any]
122
119
 
123
120
  def to_payload(self) -> dict[str, Any]:
124
121
  return {
@@ -131,14 +128,19 @@ class Invocation:
131
128
  "dispatchKind": self.dispatch_kind,
132
129
  "round": self.round,
133
130
  "inputDigest": self.input_digest,
134
- "writePolicy": dict(self.write_policy),
135
- "writePolicyDigest": self.write_policy_digest,
136
- "writeEnforcement": dict(self.write_enforcement),
137
131
  }
138
132
 
139
133
 
140
134
  @dataclass(frozen=True)
141
135
  class Attempt:
136
+ """One dispatch of a logical invocation, with the write contract it ran under.
137
+
138
+ 쓰기 계약이 invocation 이 아니라 여기 있는 이유: 계약의 artifactPolicy 는 그
139
+ 시도가 실제로 쓰는 프롬프트 파생 파일(`...-attempt-2.log` 등)을 이름으로
140
+ 담는다. attempt 마다 다른 값이므로 attempt-불변인 invocation 행에 두면 같은
141
+ invocationRef 의 2회차가 `invocationRef drift` 로 거절된다.
142
+ """
143
+
142
144
  invocation_ref: str
143
145
  attempt: int
144
146
  started_at: str
@@ -148,6 +150,9 @@ class Attempt:
148
150
  error_path: str | None
149
151
  change_summary: Mapping[str, Any]
150
152
  evidence_seal_ref: str | None
153
+ write_policy: Mapping[str, Any]
154
+ write_policy_digest: str
155
+ write_enforcement: Mapping[str, Any]
151
156
 
152
157
  def to_payload(self) -> dict[str, Any]:
153
158
  return {
@@ -160,6 +165,9 @@ class Attempt:
160
165
  "errorPath": self.error_path,
161
166
  "changeSummary": dict(self.change_summary),
162
167
  "evidenceSealRef": self.evidence_seal_ref,
168
+ "writePolicy": dict(self.write_policy),
169
+ "writePolicyDigest": self.write_policy_digest,
170
+ "writeEnforcement": dict(self.write_enforcement),
163
171
  }
164
172
 
165
173
 
@@ -528,3 +536,88 @@ def _required_index_text(
528
536
  def _optional_text(value: object) -> str | None:
529
537
  text = str(value or "").strip()
530
538
  return text or None
539
+
540
+
541
+ # ---- 무거운 모듈에서 옮겨 온 값 타입 ------------------------------------
542
+ # 아래 둘은 dispatch_state(2,092줄) 와 mutation_recovery 안에 있었다. 값 하나가
543
+ # 필요한 모듈이 그 모듈 전체를 끌어오게 되어, 반대 방향은 함수 본문 지연
544
+ # import 로 밀려나 순환이 됐다. 원래 소유 모듈이 이름을 재노출한다.
545
+
546
+ def build_dispatch_id(invocation_id: str, attempt: int) -> str:
547
+ """The only place Python assembles a dispatch id.
548
+
549
+ Two generators drifted: this module pinned the attempt to 1 while
550
+ `dispatch_core` used the real one, so a re-dispatch took the first attempt's
551
+ id, team-state kept the same row twice, and `validate-run` failed that run
552
+ with `agent dispatch ID is duplicated`.
553
+
554
+ Prose mirrors of this shape live outside Python, where no guard reaches
555
+ them: `prompts/lead/convergence.md`, `prompts/lead/report-writer.md`,
556
+ `prompts/lead/plan-body-verification.md` and the five
557
+ `adapters/hosts/*/relay.md` each spell an `--dispatch-id` argument out for
558
+ the lead. They agree with this function only because
559
+ `record_verified_agent_dispatch` records the first attempt and no other;
560
+ changing that convention has to change them too.
561
+ """
562
+ return f"{invocation_id}:attempt-{attempt}"
563
+
564
+
565
+ @dataclass(frozen=True)
566
+ class MutationRecoveryDecision:
567
+ recovery_ref: str
568
+ source_invocation_ref: str
569
+ source_attempt_ref: str
570
+ source_evidence_seal_ref: str | None
571
+ original_baseline_digest: str
572
+ terminal_source_diff_digest: str
573
+ terminal_git_chain_digest: str
574
+ verifier_role_execution_ref: str | None
575
+ verifier_invocation_ref: str | None
576
+ recovered_evidence_refs: tuple[Mapping[str, Any], ...]
577
+ recovered_out_of_plan_edits: tuple[Mapping[str, Any], ...]
578
+ unmet_obligations: tuple[str, ...]
579
+ decision: Literal["carry-forward", "reject", "cleanup-confirmed"]
580
+ reason: str = ""
581
+
582
+ def to_payload(self) -> dict[str, Any]:
583
+ return {
584
+ "recoveryRef": self.recovery_ref,
585
+ "sourceInvocationRef": self.source_invocation_ref,
586
+ "sourceAttemptRef": self.source_attempt_ref,
587
+ "sourceEvidenceSealRef": self.source_evidence_seal_ref,
588
+ "originalBaselineDigest": self.original_baseline_digest,
589
+ "terminalSourceDiffDigest": self.terminal_source_diff_digest,
590
+ "terminalGitChainDigest": self.terminal_git_chain_digest,
591
+ "verifierRoleExecutionRef": self.verifier_role_execution_ref,
592
+ "verifierInvocationRef": self.verifier_invocation_ref,
593
+ "recoveredEvidenceRefs": [dict(row) for row in self.recovered_evidence_refs],
594
+ "recoveredOutOfPlanEdits": [
595
+ dict(row) for row in self.recovered_out_of_plan_edits
596
+ ],
597
+ "unmetObligations": list(self.unmet_obligations),
598
+ "decision": self.decision,
599
+ "reason": self.reason,
600
+ }
601
+
602
+ @classmethod
603
+ def from_payload(cls, payload: Mapping[str, Any]) -> "MutationRecoveryDecision":
604
+ return cls(
605
+ recovery_ref=str(payload["recoveryRef"]),
606
+ source_invocation_ref=str(payload["sourceInvocationRef"]),
607
+ source_attempt_ref=str(payload["sourceAttemptRef"]),
608
+ source_evidence_seal_ref=payload.get("sourceEvidenceSealRef"),
609
+ original_baseline_digest=str(payload.get("originalBaselineDigest") or ""),
610
+ terminal_source_diff_digest=str(
611
+ payload.get("terminalSourceDiffDigest") or ""
612
+ ),
613
+ terminal_git_chain_digest=str(payload.get("terminalGitChainDigest") or ""),
614
+ verifier_role_execution_ref=payload.get("verifierRoleExecutionRef"),
615
+ verifier_invocation_ref=payload.get("verifierInvocationRef"),
616
+ recovered_evidence_refs=tuple(payload.get("recoveredEvidenceRefs") or ()),
617
+ recovered_out_of_plan_edits=tuple(
618
+ payload.get("recoveredOutOfPlanEdits") or ()
619
+ ),
620
+ unmet_obligations=tuple(payload.get("unmetObligations") or ()),
621
+ decision=str(payload["decision"]),
622
+ reason=str(payload.get("reason") or ""),
623
+ )
@@ -4,7 +4,8 @@ from __future__ import annotations
4
4
  import json
5
5
  import os
6
6
  import tempfile
7
- from collections.abc import Mapping, Sequence
7
+ from collections.abc import Mapping, MutableMapping, Sequence
8
+ from dataclasses import replace
8
9
  from functools import lru_cache
9
10
  from pathlib import Path
10
11
  from typing import Any, Callable
@@ -13,6 +14,7 @@ from .domain.provider import HostModelBinding, ServedModelAttestation
13
14
  from .domain.worker_exec import WorkerWriteCapability
14
15
  from .execution_identity import (
15
16
  Attempt,
17
+ build_dispatch_id,
16
18
  EXECUTION_IDENTITY_VERSION,
17
19
  ExecutionManifest,
18
20
  ExecutionManifestError,
@@ -62,10 +64,50 @@ def read_execution_manifest_view(
62
64
  surface = read_identity_surface(path, surface="run-manifest")
63
65
  if surface.legacy:
64
66
  return synthesize_v1_execution_view(surface.payload), surface.payload
67
+ hoist_legacy_attempt_write_contract(surface.payload)
65
68
  validate_execution_manifest_payload(surface.payload)
66
69
  return _execution_manifest_from_payload(surface.payload), surface.payload
67
70
 
68
71
 
72
+ WRITE_CONTRACT_KEYS = ("writePolicy", "writePolicyDigest", "writeEnforcement")
73
+
74
+
75
+ def hoist_legacy_attempt_write_contract(payload: Any) -> None:
76
+ """쓰기 계약이 invocation 에 실린 매니페스트를 attempt 소유로 옮긴다.
77
+
78
+ 계약은 attempt 마다 다르다(`execution_identity.Attempt` 참고). 그 이전에
79
+ 쓰인 매니페스트는 계약을 invocation 행에 담고 attempt 행에는 안 담으므로,
80
+ 새 검증기가 보면 attempt 가 계약 없는 행이 된다 — 진행 중인 태스크가 전부
81
+ 그 상태다. 여기서 옮겨 두면 다음 쓰기에서 파일 자체가 새 모양이 된다.
82
+
83
+ 이 함수는 옮기기만 하고 값은 만들지 않는다. attempt 가 없는 invocation 의
84
+ 계약은 버린다 — 그 값을 읽는 소비자는 없고, 디스패치가 attempt 를 열 때
85
+ 다시 계산한다. 진행 중인 태스크가 모두 끝나면 통째로 지울 수 있다.
86
+ """
87
+ if not isinstance(payload, MutableMapping):
88
+ return
89
+ invocations = payload.get("invocations")
90
+ attempts = payload.get("attempts")
91
+ if not isinstance(invocations, list):
92
+ return
93
+ carried: dict[str, dict[str, Any]] = {}
94
+ for row in invocations:
95
+ if not isinstance(row, MutableMapping) or "writePolicy" not in row:
96
+ continue
97
+ contract = {key: row.pop(key) for key in WRITE_CONTRACT_KEYS if key in row}
98
+ ref = row.get("invocationRef")
99
+ if isinstance(ref, str):
100
+ carried[ref] = contract
101
+ if not carried or not isinstance(attempts, list):
102
+ return
103
+ for row in attempts:
104
+ if not isinstance(row, MutableMapping) or "writePolicy" in row:
105
+ continue
106
+ contract = carried.get(row.get("invocationRef"))
107
+ if contract:
108
+ row.update(contract)
109
+
110
+
69
111
  def write_execution_manifest(path: Path, manifest: ExecutionManifest) -> None:
70
112
  if manifest.legacy:
71
113
  raise ExecutionManifestError("cannot write a v1 memory view")
@@ -130,7 +172,7 @@ def _drift_detail(existing: Invocation, incoming: Invocation) -> str:
130
172
  before = existing.to_payload()
131
173
  after = incoming.to_payload()
132
174
  changed = sorted(
133
- key for key in (*before, *after) if before.get(key) != after.get(key)
175
+ {key for key in (*before, *after) if before.get(key) != after.get(key)}
134
176
  )
135
177
  return ", ".join(changed) if changed else "(no field differs)"
136
178
 
@@ -349,10 +391,8 @@ def finish_attempt_mutation(
349
391
  if index is None:
350
392
  raise ExecutionManifestError("attempt to finish is unknown")
351
393
  existing = manifest.attempts[index]
352
- closed = Attempt(
353
- invocation_ref=invocation_ref,
354
- attempt=attempt,
355
- started_at=existing.started_at,
394
+ closed = replace(
395
+ existing,
356
396
  finished_at=finished_at,
357
397
  status=status,
358
398
  result_path=result_path,
@@ -422,7 +462,7 @@ def validate_execution_manifest_payload(payload: Mapping[str, Any]) -> None:
422
462
  invocations = _indexed_rows(payload["invocations"], "invocationRef")
423
463
  _validate_roles(payload, participants)
424
464
  _validate_invocations(participants, roles, invocations)
425
- _validate_attempts(payload["attempts"], invocations)
465
+ _validate_attempts(payload["attempts"], invocations, participants, roles)
426
466
  _validate_seals_and_recoveries(payload)
427
467
 
428
468
 
@@ -613,10 +653,9 @@ def _validate_invocations(
613
653
  source = row.get("sourceInvocationRef")
614
654
  if source is not None and source not in invocations:
615
655
  raise ExecutionManifestError("invocation sourceInvocationRef is unknown")
616
- _validate_invocation_write_contract(row, participants[participant_ref], roles[role_ref])
617
656
 
618
657
 
619
- def _validate_invocation_write_contract(
658
+ def _validate_attempt_write_contract(
620
659
  row: Mapping[str, Any],
621
660
  participant: Mapping[str, Any],
622
661
  role: Mapping[str, Any],
@@ -624,21 +663,21 @@ def _validate_invocation_write_contract(
624
663
  policy = row.get("writePolicy")
625
664
  enforcement = row.get("writeEnforcement")
626
665
  if not isinstance(policy, Mapping) or not isinstance(enforcement, Mapping):
627
- raise ExecutionManifestError("invocation write contract must be structured")
666
+ raise ExecutionManifestError("attempt write contract must be structured")
628
667
  try:
629
668
  validate_write_policy_payload(policy)
630
669
  validate_write_enforcement_payload(enforcement)
631
670
  expected_digest = write_policy_digest(policy)
632
671
  except WritePolicyError as exc:
633
- raise ExecutionManifestError(f"invocation write contract: {exc}") from exc
672
+ raise ExecutionManifestError(f"attempt write contract: {exc}") from exc
634
673
  if row.get("writePolicyDigest") != expected_digest:
635
- raise ExecutionManifestError("invocation writePolicyDigest does not match")
674
+ raise ExecutionManifestError("attempt writePolicyDigest does not match")
636
675
  source = policy.get("sourcePolicy")
637
676
  expected_mode = (
638
677
  "project-mutation" if role.get("role") == "implementer" else "source-readonly"
639
678
  )
640
679
  if not isinstance(source, Mapping) or source.get("mode") != expected_mode:
641
- raise ExecutionManifestError("invocation sourcePolicy does not match role")
680
+ raise ExecutionManifestError("attempt sourcePolicy does not match role")
642
681
  git_policy = policy.get("gitPolicy")
643
682
  expected_git_mode = (
644
683
  "fast-forward-descendant-chain"
@@ -649,10 +688,10 @@ def _validate_invocation_write_contract(
649
688
  not isinstance(git_policy, Mapping)
650
689
  or git_policy.get("mode") != expected_git_mode
651
690
  ):
652
- raise ExecutionManifestError("invocation gitPolicy does not match role")
691
+ raise ExecutionManifestError("attempt gitPolicy does not match role")
653
692
  if expected_mode == "source-readonly" and source.get("plannedPaths"):
654
693
  raise ExecutionManifestError(
655
- "source-readonly invocation cannot declare planned paths"
694
+ "source-readonly attempt cannot declare planned paths"
656
695
  )
657
696
  _validate_enforcement_capability(enforcement, participant, role)
658
697
 
@@ -674,7 +713,7 @@ def _validate_enforcement_capability(
674
713
  )
675
714
  return
676
715
  if not isinstance(capability, Mapping):
677
- raise ExecutionManifestError("worker invocation has no write capability")
716
+ raise ExecutionManifestError("worker attempt has no write capability")
678
717
  rank = {"none": 0, "directory-boundary": 1, "exact-path": 2}
679
718
  maximum = capability.get("maxBoundaryPrecision")
680
719
  actual = enforcement.get("boundaryPrecision")
@@ -685,6 +724,8 @@ def _validate_enforcement_capability(
685
724
  def _validate_attempts(
686
725
  rows: object,
687
726
  invocations: Mapping[str, Mapping[str, Any]],
727
+ participants: Mapping[str, Mapping[str, Any]],
728
+ roles: Mapping[str, Mapping[str, Any]],
688
729
  ) -> None:
689
730
  if not isinstance(rows, list):
690
731
  raise ExecutionManifestError("execution manifest attempts must be an array")
@@ -698,6 +739,12 @@ def _validate_attempts(
698
739
  attempt = row.get("attempt")
699
740
  if not isinstance(attempt, int) or attempt < 1:
700
741
  raise ExecutionManifestError("attempt number must be positive")
742
+ invocation = invocations[invocation_ref]
743
+ participant = participants.get(str(invocation.get("participantRef")))
744
+ role = roles.get(str(invocation.get("roleExecutionRef")))
745
+ if participant is None or role is None:
746
+ raise ExecutionManifestError("attempt references unknown execution identity")
747
+ _validate_attempt_write_contract(row, participant, role)
701
748
  attempts_by_invocation.setdefault(invocation_ref, []).append(attempt)
702
749
  for invocation_ref, values in attempts_by_invocation.items():
703
750
  if sorted(values) != list(range(1, len(values) + 1)):
@@ -710,7 +757,6 @@ def _validate_seals_and_recoveries(payload: Mapping[str, Any]) -> None:
710
757
  attempts = payload.get("attempts")
711
758
  if not isinstance(attempts, list):
712
759
  return
713
- from .dispatch_state import build_dispatch_id
714
760
 
715
761
  attempt_refs = {
716
762
  build_dispatch_id(str(row.get("invocationRef")), int(row.get("attempt") or 0))
@@ -925,9 +971,6 @@ def _invocation_from_payload(row: Mapping[str, Any]) -> Invocation:
925
971
  dispatch_kind=str(row["dispatchKind"]),
926
972
  round=int(row["round"]),
927
973
  input_digest=str(row["inputDigest"]),
928
- write_policy=dict(row["writePolicy"]),
929
- write_policy_digest=str(row["writePolicyDigest"]),
930
- write_enforcement=dict(row["writeEnforcement"]),
931
974
  )
932
975
 
933
976
 
@@ -942,6 +985,9 @@ def _attempt_from_payload(row: Mapping[str, Any]) -> Attempt:
942
985
  error_path=_optional_text(row.get("errorPath")),
943
986
  change_summary=dict(row["changeSummary"]),
944
987
  evidence_seal_ref=_optional_text(row.get("evidenceSealRef")),
988
+ write_policy=dict(row["writePolicy"]),
989
+ write_policy_digest=str(row["writePolicyDigest"]),
990
+ write_enforcement=dict(row["writeEnforcement"]),
945
991
  )
946
992
 
947
993
 
@@ -1223,6 +1269,7 @@ def _role_execution_index_from_runs(
1223
1269
  surface = read_identity_surface(resolved_path, surface="run-manifest")
1224
1270
  if surface.legacy:
1225
1271
  continue
1272
+ hoist_legacy_attempt_write_contract(surface.payload)
1226
1273
  validate_execution_manifest_payload(surface.payload)
1227
1274
  run_ref = resolved_path.relative_to(resolved_reference_root).as_posix()
1228
1275
  rows.extend(
@@ -83,6 +83,10 @@ class MutationSnapshot:
83
83
  class MutationAuditResult:
84
84
  status: str
85
85
  changed_paths: tuple[str, ...]
86
+ # 추적되지 않는 채로 생기거나 바뀐 경로. 소스 변경이 아니라 명령이 남긴
87
+ # 산출물이라 판정을 막지 않는다 — 그래도 기록은 남긴다. 없던 일로 하면
88
+ # 워커가 워크트리에 무엇을 남겼는지 아무도 모른다.
89
+ untracked_paths: tuple[str, ...]
86
90
  source_changed: bool
87
91
  git_changed: bool
88
92
  attribution: str
@@ -96,6 +100,7 @@ class MutationAuditResult:
96
100
  return {
97
101
  "mutationStatus": self.status,
98
102
  "changedPaths": list(self.changed_paths),
103
+ "untrackedPaths": list(self.untracked_paths),
99
104
  "sourceChanged": self.source_changed,
100
105
  "gitChanged": self.git_changed,
101
106
  "attribution": self.attribution,
@@ -118,11 +123,13 @@ class ExecutionMutationAudit:
118
123
  rows = tuple(policies)
119
124
  root, artifact_root = _validate_batch(rows)
120
125
  generated = _generated_paths(rows)
121
- file_digests = _content_snapshot(root, generated)
126
+ file_digests = _content_snapshot(root, generated | _ignored_paths(root))
122
127
  artifact_digests = (
123
128
  file_digests
124
129
  if artifact_root == root
125
- else _content_snapshot(artifact_root, generated)
130
+ else _content_snapshot(
131
+ artifact_root, generated | _ignored_paths(artifact_root)
132
+ )
126
133
  )
127
134
  scratch_digests = _scratch_snapshot(rows)
128
135
  git_projection = _git_projection(root)
@@ -174,7 +181,9 @@ class ExecutionMutationAudit:
174
181
  scratch_changed = _changed_keys(
175
182
  before.scratch_digests, after.scratch_digests
176
183
  )
177
- source_changes = _source_changes(changed, rows, before)
184
+ source_changes, untracked_changes = _split_tracked(
185
+ before.root, _source_changes(changed, rows, before)
186
+ )
178
187
  # Reported and, through `_retry_allowed`, load-bearing: a stat-cache
179
188
  # refresh must not read as "this worker touched Git" and must not
180
189
  # withhold the retry a worker that failed for another reason is owed.
@@ -203,6 +212,7 @@ class ExecutionMutationAudit:
203
212
  return MutationAuditResult(
204
213
  status=status,
205
214
  changed_paths=tuple(sorted(source_changes)),
215
+ untracked_paths=tuple(sorted(untracked_changes)),
206
216
  source_changed=bool(source_changes),
207
217
  git_changed=git_changed,
208
218
  attribution="batch-unattributed",
@@ -273,7 +283,9 @@ when a run produces the same evidence for it, not before.
273
283
  """
274
284
 
275
285
 
276
- def _content_snapshot(root: Path, generated: frozenset[str]) -> dict[str, str]:
286
+ def _content_snapshot(root: Path, excluded: frozenset[str]) -> dict[str, str]:
287
+ """감사 대상 트리의 내용 다이제스트. `excluded` 는 정책이 선언한 생성
288
+ 경로와 레포가 무시하는 경로를 합친 접두사 집합이다."""
277
289
  rows: dict[str, str] = {}
278
290
  for current, directories, filenames in os.walk(root, followlinks=False):
279
291
  current_path = Path(current)
@@ -284,7 +296,7 @@ def _content_snapshot(root: Path, generated: frozenset[str]) -> dict[str, str]:
284
296
  if (
285
297
  name == ".git"
286
298
  or name in _INSTALLED_DEPENDENCY_DIRS
287
- or _excluded(relative_path, generated)
299
+ or _excluded(relative_path, excluded)
288
300
  ):
289
301
  continue
290
302
  if path.is_symlink():
@@ -295,7 +307,7 @@ def _content_snapshot(root: Path, generated: frozenset[str]) -> dict[str, str]:
295
307
  for name in sorted(filenames):
296
308
  path = current_path / name
297
309
  relative = path.relative_to(root).as_posix()
298
- if relative == ".git" or _excluded(Path(relative), generated):
310
+ if relative == ".git" or _excluded(Path(relative), excluded):
299
311
  continue
300
312
  rows[relative] = _path_digest(path)
301
313
  return rows
@@ -327,9 +339,79 @@ def _path_digest(path: Path) -> str:
327
339
  return "sha256:" + hashlib.sha256(value).hexdigest()
328
340
 
329
341
 
330
- def _excluded(path: Path, generated: frozenset[str]) -> bool:
342
+ def _excluded(path: Path, excluded: frozenset[str]) -> bool:
331
343
  relative = path.as_posix()
332
- return any(relative == item or relative.startswith(item + "/") for item in generated)
344
+ return any(relative == item or relative.startswith(item + "/") for item in excluded)
345
+
346
+
347
+ def _ignored_paths(root: Path) -> frozenset[str]:
348
+ """레포가 스스로 소스가 아니라고 선언한 경로.
349
+
350
+ 감사는 워커가 *소스* 를 바꿨는지를 묻는다. `.gitignore` 는 그 레포에서
351
+ 무엇이 소스가 아닌지에 대한 사람이 쓴 답이므로, 그 답을 여기서 다시 쓴다 —
352
+ okstra 가 파일 이름 목록을 따로 들고 있으면 레포마다 틀린다.
353
+
354
+ 이 면제가 필요한 이유는 감사 창이 사람·OS 와 공유되기 때문이다. 실측
355
+ (2026-08-26, `fontsninja-nlpvibe` planning run 019~022): 완주한 워커의 결과가
356
+ 버려진 10건 중 2건의 유발자가 Finder 가 쓴 `.DS_Store` 와 IDE 가 쓴
357
+ `.idea/workspace.xml` 이었다. 어느 쪽도 워커가 쓴 것이 아니고, 둘 다 그
358
+ 레포의 무시 대상이다.
359
+
360
+ 추적 중인 파일은 git 이 무시 대상으로 세지 않으므로 그대로 감사한다 —
361
+ 나머지 8건처럼 사람이 추적 소스를 동시에 고친 경우는 이 면제가 가리지
362
+ 않는다. git 이 없거나 레포가 아니면 빈 집합을 돌려 종전대로 전부 감사한다.
363
+ """
364
+ result = subprocess.run(
365
+ ["git", "-C", str(root), "ls-files", "-z", "--others", "--ignored",
366
+ "--exclude-standard", "--directory"],
367
+ capture_output=True,
368
+ text=True,
369
+ check=False,
370
+ )
371
+ if result.returncode != 0:
372
+ return frozenset()
373
+ # `--directory` 는 통째로 무시되는 디렉터리를 `name/` 한 줄로 접는다.
374
+ # `_excluded` 는 접두사로 대조하므로 그 꼬리 슬래시를 떼어 준다.
375
+ return frozenset(
376
+ entry.rstrip("/") for entry in result.stdout.split("\0") if entry
377
+ )
378
+
379
+
380
+ def _tracked_paths(root: Path) -> frozenset[str] | None:
381
+ """이 레포가 추적 중인 파일. git 이 없거나 레포가 아니면 ``None``.
382
+
383
+ `source-readonly` 가 묻는 것은 워커가 **소스** 를 바꿨는가이다. 추적 중인
384
+ 파일의 변경이 그 답이다. 추적되지 않는 새 파일은 소스의 변경이 아니라
385
+ 명령이 남긴 산출물이고, 그 둘을 한 등급으로 묶으면 규칙을 만족시킬 수 있는
386
+ 실물 조건이 없어진다 — 프로필은 검증자에게 승인 계획의 Tier 1 명령을 독립
387
+ 재실행하라고 요구하는데, 실제 프로젝트의 그 명령들은 빌드 산출물과 평가
388
+ 리포트를 쓴다. 실측(2026-08-26, `fontsninja-nlpvibe` final-verification
389
+ run 005): 계획대로 게이트를 돌린 검증자 셋이 전부
390
+ `readonly source changed [eval/reports/clip-baseline.{html,json}]` 로
391
+ 버려졌다. 두 파일은 추적되지 않는 생성 산출물이고, 계획이 승인 시점에
392
+ 적어 둔 명령이 만든 것이다.
393
+
394
+ ``None`` 은 "판정할 수 없음" 이다 — 그때는 좁히지 않고 종전대로 전부
395
+ 소스 변경으로 본다. 추적 여부를 모르면서 면제하면 진짜 소스 변경을 놓친다.
396
+ """
397
+ result = subprocess.run(
398
+ ["git", "-C", str(root), "ls-files", "-z"],
399
+ capture_output=True,
400
+ text=True,
401
+ check=False,
402
+ )
403
+ if result.returncode != 0:
404
+ return None
405
+ return frozenset(entry for entry in result.stdout.split("\0") if entry)
406
+
407
+
408
+ def _split_tracked(root: Path, changes: set[str]) -> tuple[set[str], set[str]]:
409
+ """``(추적 중인 소스 변경, 추적되지 않는 산출물)``."""
410
+ tracked = _tracked_paths(root)
411
+ if tracked is None:
412
+ return set(changes), set()
413
+ untracked = {path for path in changes if path not in tracked}
414
+ return changes - untracked, untracked
333
415
 
334
416
 
335
417
  def _generated_paths(policies: Sequence[WritePolicy]) -> frozenset[str]:
@@ -119,3 +119,20 @@ def translation_sidecar_path(data_path: Path, lang: str) -> Path:
119
119
  overwrite it.
120
120
  """
121
121
  return data_path.with_name(f"{_stem(data_path)}.i18n.{lang}.json")
122
+
123
+
124
+ def sidecar_source_rel(path: Path) -> str:
125
+ """사이드카 프론트매터가 `source-report` / `source-data` 에 적는 경로.
126
+
127
+ 기준점은 **태스크 루트**다 (`runs/<task-type>/reports/...`). 사이드카를
128
+ 읽는 쪽은 리포트를 태스크 루트에서 되짚으므로
129
+ (`implementation_direction._relative_source`), 다른 기준으로 적힌 값은
130
+ 같은 파일을 가리켜도 대조에 실패한다.
131
+
132
+ HTML 열람본을 렌더할 때 넘기는 `--source-report` 와 CLI 가 직접 쓰는
133
+ 사이드카가 이 함수 하나를 공유한다. 둘이 각자 계산하던 동안 HTML Export 는
134
+ 프로젝트 루트 기준을 적었고, 그 사이드카는 방향 선택 게이트를 통과할 수
135
+ 없었다.
136
+ """
137
+ indices = [index for index, part in enumerate(path.parts) if part == "runs"]
138
+ return Path(*path.parts[indices[-1]:]).as_posix() if indices else path.name