okstra 0.174.0 → 0.176.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 (419) hide show
  1. package/README.md +18 -9
  2. package/bin/okstra +8 -4
  3. package/dist/cli-registry.d.mts +16 -0
  4. package/dist/cli-registry.mjs +560 -0
  5. package/dist/cli-registry.mjs.map +1 -0
  6. package/dist/commands/execute/agent-prompt.d.mts +1 -0
  7. package/{src → dist}/commands/execute/agent-prompt.mjs +10 -11
  8. package/dist/commands/execute/agent-prompt.mjs.map +1 -0
  9. package/dist/commands/execute/codex-dispatch.d.mts +3 -0
  10. package/dist/commands/execute/codex-dispatch.mjs +6 -0
  11. package/dist/commands/execute/codex-dispatch.mjs.map +1 -0
  12. package/dist/commands/execute/codex-run.d.mts +3 -0
  13. package/{src → dist}/commands/execute/codex-run.mjs +30 -37
  14. package/dist/commands/execute/codex-run.mjs.map +1 -0
  15. package/dist/commands/execute/convergence.d.mts +1 -0
  16. package/{src → dist}/commands/execute/convergence.mjs +15 -16
  17. package/dist/commands/execute/convergence.mjs.map +1 -0
  18. package/dist/commands/execute/error-log.d.mts +1 -0
  19. package/{src → dist}/commands/execute/error-log.mjs +2 -3
  20. package/dist/commands/execute/error-log.mjs.map +1 -0
  21. package/dist/commands/execute/git-reconcile.d.mts +1 -0
  22. package/{src → dist}/commands/execute/git-reconcile.mjs +10 -11
  23. package/dist/commands/execute/git-reconcile.mjs.map +1 -0
  24. package/dist/commands/execute/handoff.d.mts +1 -0
  25. package/{src → dist}/commands/execute/handoff.mjs +10 -11
  26. package/dist/commands/execute/handoff.mjs.map +1 -0
  27. package/dist/commands/execute/incremental-carry.d.mts +1 -0
  28. package/{src → dist}/commands/execute/incremental-carry.mjs +2 -3
  29. package/dist/commands/execute/incremental-carry.mjs.map +1 -0
  30. package/dist/commands/execute/incremental-scope.d.mts +1 -0
  31. package/{src → dist}/commands/execute/incremental-scope.mjs +2 -3
  32. package/dist/commands/execute/incremental-scope.mjs.map +1 -0
  33. package/dist/commands/execute/integrate-stages.d.mts +1 -0
  34. package/{src → dist}/commands/execute/integrate-stages.mjs +10 -11
  35. package/dist/commands/execute/integrate-stages.mjs.map +1 -0
  36. package/dist/commands/execute/pane-title.d.mts +1 -0
  37. package/dist/commands/execute/pane-title.mjs +20 -0
  38. package/dist/commands/execute/pane-title.mjs.map +1 -0
  39. package/dist/commands/execute/plan-items.d.mts +1 -0
  40. package/dist/commands/execute/plan-items.mjs +9 -0
  41. package/dist/commands/execute/plan-items.mjs.map +1 -0
  42. package/dist/commands/execute/plan-validate.d.mts +1 -0
  43. package/dist/commands/execute/plan-validate.mjs +68 -0
  44. package/dist/commands/execute/plan-validate.mjs.map +1 -0
  45. package/dist/commands/execute/plan-verify.d.mts +1 -0
  46. package/{src → dist}/commands/execute/plan-verify.mjs +13 -16
  47. package/dist/commands/execute/plan-verify.mjs.map +1 -0
  48. package/dist/commands/execute/render-bundle.d.mts +3 -0
  49. package/{src → dist}/commands/execute/render-bundle.mjs +65 -61
  50. package/dist/commands/execute/render-bundle.mjs.map +1 -0
  51. package/dist/commands/execute/run.d.mts +24 -0
  52. package/dist/commands/execute/run.mjs +196 -0
  53. package/dist/commands/execute/run.mjs.map +1 -0
  54. package/dist/commands/execute/spawn-followups.d.mts +1 -0
  55. package/{src → dist}/commands/execute/spawn-followups.mjs +6 -7
  56. package/dist/commands/execute/spawn-followups.mjs.map +1 -0
  57. package/dist/commands/execute/team.d.mts +3 -0
  58. package/{src → dist}/commands/execute/team.mjs +29 -36
  59. package/dist/commands/execute/team.mjs.map +1 -0
  60. package/dist/commands/execute/token-usage.d.mts +1 -0
  61. package/{src → dist}/commands/execute/token-usage.mjs +2 -3
  62. package/dist/commands/execute/token-usage.mjs.map +1 -0
  63. package/dist/commands/execute/wizard.d.mts +8 -0
  64. package/dist/commands/execute/wizard.mjs +191 -0
  65. package/dist/commands/execute/wizard.mjs.map +1 -0
  66. package/dist/commands/execute/worker-audit-check.d.mts +1 -0
  67. package/{src → dist}/commands/execute/worker-audit-check.mjs +11 -12
  68. package/dist/commands/execute/worker-audit-check.mjs.map +1 -0
  69. package/dist/commands/execute/worker-dispatch.d.mts +7 -0
  70. package/{src → dist}/commands/execute/worker-dispatch.mjs +34 -46
  71. package/dist/commands/execute/worker-dispatch.mjs.map +1 -0
  72. package/dist/commands/execute/worker-state.d.mts +1 -0
  73. package/{src → dist}/commands/execute/worker-state.mjs +15 -16
  74. package/dist/commands/execute/worker-state.mjs.map +1 -0
  75. package/dist/commands/execute/worktree-lookup.d.mts +1 -0
  76. package/{src → dist}/commands/execute/worktree-lookup.mjs +38 -37
  77. package/dist/commands/execute/worktree-lookup.mjs.map +1 -0
  78. package/dist/commands/execute/worktree-status.d.mts +1 -0
  79. package/{src → dist}/commands/execute/worktree-status.mjs +52 -46
  80. package/dist/commands/execute/worktree-status.mjs.map +1 -0
  81. package/dist/commands/inspect/code-review.d.mts +1 -0
  82. package/{src → dist}/commands/inspect/code-review.mjs +16 -19
  83. package/dist/commands/inspect/code-review.mjs.map +1 -0
  84. package/dist/commands/inspect/container.d.mts +1 -0
  85. package/{src → dist}/commands/inspect/container.mjs +11 -13
  86. package/dist/commands/inspect/container.mjs.map +1 -0
  87. package/dist/commands/inspect/context-cost.d.mts +1 -0
  88. package/{src → dist}/commands/inspect/context-cost.mjs +11 -13
  89. package/dist/commands/inspect/context-cost.mjs.map +1 -0
  90. package/dist/commands/inspect/design-prep.d.mts +1 -0
  91. package/{src → dist}/commands/inspect/design-prep.mjs +10 -11
  92. package/dist/commands/inspect/design-prep.mjs.map +1 -0
  93. package/dist/commands/inspect/error-report.d.mts +1 -0
  94. package/{src → dist}/commands/inspect/error-report.mjs +11 -13
  95. package/dist/commands/inspect/error-report.mjs.map +1 -0
  96. package/dist/commands/inspect/error-zip.d.mts +1 -0
  97. package/{src → dist}/commands/inspect/error-zip.mjs +11 -12
  98. package/dist/commands/inspect/error-zip.mjs.map +1 -0
  99. package/dist/commands/inspect/log-report.d.mts +1 -0
  100. package/{src → dist}/commands/inspect/log-report.mjs +11 -13
  101. package/dist/commands/inspect/log-report.mjs.map +1 -0
  102. package/dist/commands/inspect/profile-show.d.mts +1 -0
  103. package/{src → dist}/commands/inspect/profile-show.mjs +10 -11
  104. package/dist/commands/inspect/profile-show.mjs.map +1 -0
  105. package/dist/commands/inspect/recap.d.mts +1 -0
  106. package/{src → dist}/commands/inspect/recap.mjs +11 -13
  107. package/dist/commands/inspect/recap.mjs.map +1 -0
  108. package/dist/commands/inspect/resolve-task-key.d.mts +1 -0
  109. package/{src → dist}/commands/inspect/resolve-task-key.mjs +11 -13
  110. package/dist/commands/inspect/resolve-task-key.mjs.map +1 -0
  111. package/dist/commands/inspect/rollup.d.mts +1 -0
  112. package/{src → dist}/commands/inspect/rollup.mjs +11 -13
  113. package/dist/commands/inspect/rollup.mjs.map +1 -0
  114. package/dist/commands/inspect/run-audit.d.mts +1 -0
  115. package/{src → dist}/commands/inspect/run-audit.mjs +11 -12
  116. package/dist/commands/inspect/run-audit.mjs.map +1 -0
  117. package/dist/commands/inspect/set-work-status.d.mts +1 -0
  118. package/{src → dist}/commands/inspect/set-work-status.mjs +11 -13
  119. package/dist/commands/inspect/set-work-status.mjs.map +1 -0
  120. package/dist/commands/inspect/stage-map.d.mts +1 -0
  121. package/dist/commands/inspect/stage-map.mjs +110 -0
  122. package/dist/commands/inspect/stage-map.mjs.map +1 -0
  123. package/dist/commands/inspect/task-list.d.mts +1 -0
  124. package/dist/commands/inspect/task-list.mjs +103 -0
  125. package/dist/commands/inspect/task-list.mjs.map +1 -0
  126. package/dist/commands/inspect/task-show.d.mts +1 -0
  127. package/dist/commands/inspect/task-show.mjs +108 -0
  128. package/dist/commands/inspect/task-show.mjs.map +1 -0
  129. package/dist/commands/inspect/time-report.d.mts +1 -0
  130. package/{src → dist}/commands/inspect/time-report.mjs +11 -13
  131. package/dist/commands/inspect/time-report.mjs.map +1 -0
  132. package/dist/commands/inspect/usage-report.d.mts +1 -0
  133. package/{src → dist}/commands/inspect/usage-report.mjs +11 -13
  134. package/dist/commands/inspect/usage-report.mjs.map +1 -0
  135. package/dist/commands/inspect/user-response.d.mts +1 -0
  136. package/{src → dist}/commands/inspect/user-response.mjs +10 -11
  137. package/dist/commands/inspect/user-response.mjs.map +1 -0
  138. package/dist/commands/inspect/worker-liveness.d.mts +1 -0
  139. package/{src → dist}/commands/inspect/worker-liveness.mjs +11 -13
  140. package/dist/commands/inspect/worker-liveness.mjs.map +1 -0
  141. package/dist/commands/lifecycle/check-project.d.mts +21 -0
  142. package/dist/commands/lifecycle/check-project.mjs +175 -0
  143. package/dist/commands/lifecycle/check-project.mjs.map +1 -0
  144. package/dist/commands/lifecycle/config.d.mts +1 -0
  145. package/dist/commands/lifecycle/config.mjs +369 -0
  146. package/dist/commands/lifecycle/config.mjs.map +1 -0
  147. package/dist/commands/lifecycle/doctor.d.mts +10 -0
  148. package/dist/commands/lifecycle/doctor.mjs +317 -0
  149. package/dist/commands/lifecycle/doctor.mjs.map +1 -0
  150. package/dist/commands/lifecycle/install.d.mts +17 -0
  151. package/dist/commands/lifecycle/install.mjs +1077 -0
  152. package/dist/commands/lifecycle/install.mjs.map +1 -0
  153. package/dist/commands/lifecycle/migrate.d.mts +1 -0
  154. package/{src → dist}/commands/lifecycle/migrate.mjs +10 -11
  155. package/dist/commands/lifecycle/migrate.mjs.map +1 -0
  156. package/dist/commands/lifecycle/model.d.mts +1 -0
  157. package/dist/commands/lifecycle/model.mjs +22 -0
  158. package/dist/commands/lifecycle/model.mjs.map +1 -0
  159. package/dist/commands/lifecycle/paths.d.mts +1 -0
  160. package/dist/commands/lifecycle/paths.mjs +102 -0
  161. package/dist/commands/lifecycle/paths.mjs.map +1 -0
  162. package/dist/commands/lifecycle/preflight.d.mts +1 -0
  163. package/dist/commands/lifecycle/preflight.mjs +124 -0
  164. package/dist/commands/lifecycle/preflight.mjs.map +1 -0
  165. package/dist/commands/lifecycle/setup.d.mts +1 -0
  166. package/dist/commands/lifecycle/setup.mjs +297 -0
  167. package/dist/commands/lifecycle/setup.mjs.map +1 -0
  168. package/dist/commands/lifecycle/uninstall.d.mts +4 -0
  169. package/dist/commands/lifecycle/uninstall.mjs +262 -0
  170. package/dist/commands/lifecycle/uninstall.mjs.map +1 -0
  171. package/dist/commands/manager.d.mts +3 -0
  172. package/{src → dist}/commands/manager.mjs +25 -32
  173. package/dist/commands/manager.mjs.map +1 -0
  174. package/dist/commands/memory/memory.d.mts +1 -0
  175. package/dist/commands/memory/memory.mjs +569 -0
  176. package/dist/commands/memory/memory.mjs.map +1 -0
  177. package/dist/commands/pr/pr.d.mts +33 -0
  178. package/dist/commands/pr/pr.mjs +296 -0
  179. package/dist/commands/pr/pr.mjs.map +1 -0
  180. package/dist/commands/report/agent-activity.d.mts +1 -0
  181. package/{src → dist}/commands/report/agent-activity.mjs +11 -12
  182. package/dist/commands/report/agent-activity.mjs.map +1 -0
  183. package/dist/commands/report/finalize.d.mts +4 -0
  184. package/{src → dist}/commands/report/finalize.mjs +25 -32
  185. package/dist/commands/report/finalize.mjs.map +1 -0
  186. package/dist/commands/report/inject-report-index.d.mts +1 -0
  187. package/{src → dist}/commands/report/inject-report-index.mjs +6 -7
  188. package/dist/commands/report/inject-report-index.mjs.map +1 -0
  189. package/dist/commands/report/render-final-report.d.mts +1 -0
  190. package/{src → dist}/commands/report/render-final-report.mjs +6 -7
  191. package/dist/commands/report/render-final-report.mjs.map +1 -0
  192. package/dist/commands/report/render-views.d.mts +1 -0
  193. package/{src → dist}/commands/report/render-views.mjs +6 -7
  194. package/dist/commands/report/render-views.mjs.map +1 -0
  195. package/dist/commands/report/translate.d.mts +1 -0
  196. package/{src → dist}/commands/report/translate.mjs +6 -7
  197. package/dist/commands/report/translate.mjs.map +1 -0
  198. package/dist/lib/helper-scripts.d.mts +3 -0
  199. package/{src → dist}/lib/helper-scripts.mjs +11 -14
  200. package/dist/lib/helper-scripts.mjs.map +1 -0
  201. package/dist/lib/host-registry-client.d.mts +25 -0
  202. package/dist/lib/host-registry-client.mjs +152 -0
  203. package/dist/lib/host-registry-client.mjs.map +1 -0
  204. package/dist/lib/install-assets.d.mts +8 -0
  205. package/{src → dist}/lib/install-assets.mjs +17 -20
  206. package/dist/lib/install-assets.mjs.map +1 -0
  207. package/dist/lib/okstra-dirs.d.mts +4 -0
  208. package/{src → dist}/lib/okstra-dirs.mjs +3 -6
  209. package/dist/lib/okstra-dirs.mjs.map +1 -0
  210. package/dist/lib/paths.d.mts +4 -0
  211. package/dist/lib/paths.mjs +78 -0
  212. package/dist/lib/paths.mjs.map +1 -0
  213. package/dist/lib/proc.d.mts +3 -0
  214. package/dist/lib/proc.mjs +31 -0
  215. package/dist/lib/proc.mjs.map +1 -0
  216. package/dist/lib/python-helper.d.mts +8 -0
  217. package/dist/lib/python-helper.mjs +123 -0
  218. package/dist/lib/python-helper.mjs.map +1 -0
  219. package/dist/lib/runtime-manifest.d.mts +7 -0
  220. package/dist/lib/runtime-manifest.mjs +73 -0
  221. package/dist/lib/runtime-manifest.mjs.map +1 -0
  222. package/dist/lib/skill-catalog.d.mts +4 -0
  223. package/dist/lib/skill-catalog.mjs +50 -0
  224. package/dist/lib/skill-catalog.mjs.map +1 -0
  225. package/dist/lib/types.d.mts +129 -0
  226. package/dist/lib/types.mjs +2 -0
  227. package/dist/lib/types.mjs.map +1 -0
  228. package/dist/lib/version.d.mts +2 -0
  229. package/dist/lib/version.mjs +27 -0
  230. package/dist/lib/version.mjs.map +1 -0
  231. package/docs/architecture/storage-model.md +6 -2
  232. package/docs/architecture.md +29 -20
  233. package/docs/cli.md +40 -16
  234. package/docs/contributor-change-matrix.md +2 -2
  235. package/docs/for-ai/skills/okstra-inspect.md +2 -3
  236. package/docs/for-ai/skills/okstra-rollup.md +1 -0
  237. package/docs/project-structure-overview.md +72 -62
  238. package/docs/task-process/README.md +12 -8
  239. package/docs/task-process/common-flow.md +13 -16
  240. package/docs/task-process/error-analysis.md +9 -10
  241. package/docs/task-process/final-verification.md +7 -7
  242. package/docs/task-process/implementation-planning.md +9 -9
  243. package/docs/task-process/implementation.md +6 -6
  244. package/docs/task-process/release-handoff.md +8 -7
  245. package/docs/task-process/requirements-discovery.md +8 -8
  246. package/package.json +11 -5
  247. package/runtime/BUILD.json +2 -2
  248. package/runtime/agents/workers/claude-worker.md +2 -1
  249. package/runtime/agents/workers/report-writer-worker.md +3 -1
  250. package/runtime/agents/workers/translator-worker.md +3 -1
  251. package/runtime/bin/lib/okstra/cli.sh +12 -0
  252. package/runtime/bin/lib/okstra/globals.sh +3 -0
  253. package/runtime/bin/lib/okstra/interactive.sh +12 -6
  254. package/runtime/bin/lib/okstra/usage.sh +7 -6
  255. package/runtime/bin/okstra-error-log.py +27 -0
  256. package/runtime/bin/okstra-provider-exec.py +217 -5
  257. package/runtime/bin/okstra-spawn-followups.py +4 -2
  258. package/runtime/bin/okstra.sh +10 -0
  259. package/runtime/prompts/launch.template.md +2 -2
  260. package/runtime/prompts/lead/context-loader.md +1 -2
  261. package/runtime/prompts/lead/convergence.md +27 -18
  262. package/runtime/prompts/lead/okstra-lead-contract.md +6 -5
  263. package/runtime/prompts/lead/plan-body-verification.md +10 -3
  264. package/runtime/prompts/lead/report-writer.md +15 -1
  265. package/runtime/prompts/lead/team-contract.md +17 -13
  266. package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
  267. package/runtime/prompts/profiles/_implementation-executor.md +0 -3
  268. package/runtime/prompts/profiles/_implementation-verifier.md +3 -7
  269. package/runtime/prompts/profiles/change-impact-analysis.md +20 -0
  270. package/runtime/prompts/profiles/error-analysis.md +27 -1
  271. package/runtime/prompts/profiles/feature-analysis.md +20 -0
  272. package/runtime/prompts/profiles/final-verification.md +20 -1
  273. package/runtime/prompts/profiles/implementation-option-selection.md +20 -0
  274. package/runtime/prompts/profiles/implementation-planning.md +26 -0
  275. package/runtime/prompts/profiles/implementation.md +19 -0
  276. package/runtime/prompts/profiles/improvement-discovery.md +21 -1
  277. package/runtime/prompts/profiles/project-analysis.md +20 -0
  278. package/runtime/prompts/profiles/release-handoff.md +4 -0
  279. package/runtime/prompts/profiles/requirements-discovery.md +27 -1
  280. package/runtime/prompts/wizard/prompts.ko.json +35 -0
  281. package/runtime/python/okstra_ctl/adapters/dispatch/cli_wrapper.py +8 -0
  282. package/runtime/python/okstra_ctl/adapters/dispatch/cmux.py +8 -0
  283. package/runtime/python/okstra_ctl/adapters/dispatch/native_team.py +13 -0
  284. package/runtime/python/okstra_ctl/adapters/hosts/antigravity/adapter.py +2 -2
  285. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +25 -5
  286. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +2 -2
  287. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +4 -1
  288. package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +2 -2
  289. package/runtime/python/okstra_ctl/adapters/hosts/kimi/adapter.py +2 -2
  290. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +79 -11
  291. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +95 -25
  292. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +59 -14
  293. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +55 -9
  294. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +51 -12
  295. package/runtime/python/okstra_ctl/adapters/runtime/__init__.py +1 -0
  296. package/runtime/python/okstra_ctl/adapters/runtime/assembly.py +27 -0
  297. package/runtime/python/okstra_ctl/adapters/runtime/cli_wrapper.py +49 -0
  298. package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +74 -0
  299. package/runtime/python/okstra_ctl/agent_activity.py +5 -1
  300. package/runtime/python/okstra_ctl/agent_invocation.py +608 -14
  301. package/runtime/python/okstra_ctl/agent_prompt_cli.py +249 -50
  302. package/runtime/python/okstra_ctl/application/open_worker.py +29 -0
  303. package/runtime/python/okstra_ctl/application/resolve_assignment.py +53 -12
  304. package/runtime/python/okstra_ctl/assignment_environment.py +102 -0
  305. package/runtime/python/okstra_ctl/assignment_resolver.py +790 -0
  306. package/runtime/python/okstra_ctl/attempt_evidence.py +342 -0
  307. package/runtime/python/okstra_ctl/convergence.py +409 -3
  308. package/runtime/python/okstra_ctl/convergence_engine.py +248 -15
  309. package/runtime/python/okstra_ctl/convergence_store.py +170 -1
  310. package/runtime/python/okstra_ctl/dispatch_core.py +1204 -155
  311. package/runtime/python/okstra_ctl/dispatch_state.py +696 -11
  312. package/runtime/python/okstra_ctl/doctor.py +14 -0
  313. package/runtime/python/okstra_ctl/domain/host.py +34 -1
  314. package/runtime/python/okstra_ctl/domain/provider.py +212 -6
  315. package/runtime/python/okstra_ctl/domain/role.py +125 -0
  316. package/runtime/python/okstra_ctl/domain/wizard/interaction.py +18 -0
  317. package/runtime/python/okstra_ctl/domain/worker_exec.py +34 -1
  318. package/runtime/python/okstra_ctl/domain/worker_role.py +13 -3
  319. package/runtime/python/okstra_ctl/domain/worker_runtime.py +44 -0
  320. package/runtime/python/okstra_ctl/domain/worker_stream.py +24 -0
  321. package/runtime/python/okstra_ctl/error_log_write.py +9 -0
  322. package/runtime/python/okstra_ctl/error_report.py +21 -1
  323. package/runtime/python/okstra_ctl/execution_identity.py +529 -0
  324. package/runtime/python/okstra_ctl/execution_manifest.py +1281 -0
  325. package/runtime/python/okstra_ctl/execution_mutation_audit.py +619 -0
  326. package/runtime/python/okstra_ctl/implementation_outcome.py +21 -5
  327. package/runtime/python/okstra_ctl/index.py +6 -2
  328. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +43 -3
  329. package/runtime/python/okstra_ctl/legacy_model_selection.py +578 -0
  330. package/runtime/python/okstra_ctl/manager_sync.py +4 -1
  331. package/runtime/python/okstra_ctl/model_cli.py +278 -0
  332. package/runtime/python/okstra_ctl/model_defaults.py +46 -0
  333. package/runtime/python/okstra_ctl/model_discovery.py +26 -7
  334. package/runtime/python/okstra_ctl/model_pool.py +501 -0
  335. package/runtime/python/okstra_ctl/models.py +47 -27
  336. package/runtime/python/okstra_ctl/mutation_recovery.py +293 -0
  337. package/runtime/python/okstra_ctl/next_phase.py +236 -0
  338. package/runtime/python/okstra_ctl/pane_title.py +151 -0
  339. package/runtime/python/okstra_ctl/ports/host_model.py +48 -17
  340. package/runtime/python/okstra_ctl/ports/worker_dispatch.py +4 -0
  341. package/runtime/python/okstra_ctl/ports/worker_runtime.py +26 -0
  342. package/runtime/python/okstra_ctl/recap.py +4 -1
  343. package/runtime/python/okstra_ctl/registry/provider_registry.py +8 -4
  344. package/runtime/python/okstra_ctl/render.py +124 -57
  345. package/runtime/python/okstra_ctl/render_final_report.py +19 -0
  346. package/runtime/python/okstra_ctl/report_contract.py +39 -0
  347. package/runtime/python/okstra_ctl/report_finalize.py +16 -0
  348. package/runtime/python/okstra_ctl/report_html/render.py +1 -0
  349. package/runtime/python/okstra_ctl/role_requirements.py +210 -0
  350. package/runtime/python/okstra_ctl/rollup.py +4 -1
  351. package/runtime/python/okstra_ctl/run.py +1210 -258
  352. package/runtime/python/okstra_ctl/run_index_row.py +7 -1
  353. package/runtime/python/okstra_ctl/stage_fix_carry.py +17 -1
  354. package/runtime/python/okstra_ctl/team.py +14 -18
  355. package/runtime/python/okstra_ctl/time_report.py +38 -4
  356. package/runtime/python/okstra_ctl/usage_identity.py +86 -0
  357. package/runtime/python/okstra_ctl/usage_report.py +15 -3
  358. package/runtime/python/okstra_ctl/wizard.py +1449 -66
  359. package/runtime/python/okstra_ctl/worker_audit_ledger.py +68 -65
  360. package/runtime/python/okstra_ctl/worker_prompt_body.py +18 -2
  361. package/runtime/python/okstra_ctl/worker_prompt_contract.py +40 -9
  362. package/runtime/python/okstra_ctl/worker_prompt_headers.py +16 -1
  363. package/runtime/python/okstra_ctl/worker_request.py +7 -1
  364. package/runtime/python/okstra_ctl/worker_runner.py +130 -5
  365. package/runtime/python/okstra_ctl/workers.py +7 -5
  366. package/runtime/python/okstra_ctl/workflow.py +18 -32
  367. package/runtime/python/okstra_ctl/worktree.py +3 -3
  368. package/runtime/python/okstra_ctl/worktree_registry.py +5 -4
  369. package/runtime/python/okstra_ctl/write_policy.py +571 -0
  370. package/runtime/python/okstra_project/state.py +54 -6
  371. package/runtime/schemas/convergence-groups-v2.0.schema.json +128 -0
  372. package/runtime/schemas/execution-manifest-v2.schema.json +340 -0
  373. package/runtime/schemas/final-report-v2.0.schema.json +68 -5
  374. package/runtime/skills/okstra-inspect/facets/recap.md +2 -0
  375. package/runtime/skills/okstra-inspect/facets/report.md +1 -1
  376. package/runtime/skills/okstra-inspect/facets/status.md +15 -13
  377. package/runtime/skills/okstra-rollup/SKILL.md +1 -0
  378. package/runtime/skills/okstra-run/SKILL.md +14 -14
  379. package/runtime/templates/implementation-worker-preamble.md +3 -18
  380. package/runtime/templates/project-docs/task-index.template.md +0 -1
  381. package/runtime/templates/report-writer-prompt-preamble.md +2 -0
  382. package/runtime/templates/reports/final-report-v2.template.md +7 -1
  383. package/runtime/templates/reports/html/base.template.html +21 -0
  384. package/runtime/templates/reports/html/i18n/en.json +4 -0
  385. package/runtime/templates/reports/html/i18n/ko.json +4 -0
  386. package/runtime/templates/reports/html/macros/forms.html +5 -4
  387. package/runtime/templates/reports/html/tasks/final-verification.template.html +1 -1
  388. package/runtime/templates/reports/html/tasks/implementation.template.html +1 -1
  389. package/runtime/templates/worker-prompt-preamble.md +6 -35
  390. package/runtime/validators/lib/fixtures.sh +49 -16
  391. package/runtime/validators/validate-run.py +486 -134
  392. package/runtime/validators/validate_session_conformance.py +25 -7
  393. package/src/cli-registry.mjs +0 -539
  394. package/src/commands/execute/codex-dispatch.mjs +0 -10
  395. package/src/commands/execute/plan-items.mjs +0 -9
  396. package/src/commands/execute/plan-validate.mjs +0 -68
  397. package/src/commands/execute/run.mjs +0 -208
  398. package/src/commands/execute/wizard.mjs +0 -167
  399. package/src/commands/inspect/stage-map.mjs +0 -98
  400. package/src/commands/inspect/task-list.mjs +0 -97
  401. package/src/commands/inspect/task-show.mjs +0 -100
  402. package/src/commands/lifecycle/check-project.mjs +0 -187
  403. package/src/commands/lifecycle/config.mjs +0 -385
  404. package/src/commands/lifecycle/doctor.mjs +0 -308
  405. package/src/commands/lifecycle/install.mjs +0 -1170
  406. package/src/commands/lifecycle/paths.mjs +0 -104
  407. package/src/commands/lifecycle/preflight.mjs +0 -125
  408. package/src/commands/lifecycle/setup.mjs +0 -333
  409. package/src/commands/lifecycle/uninstall.mjs +0 -279
  410. package/src/commands/memory/memory.mjs +0 -524
  411. package/src/commands/pr/pr.mjs +0 -261
  412. package/src/lib/host-registry-client.mjs +0 -176
  413. package/src/lib/paths.mjs +0 -86
  414. package/src/lib/proc.mjs +0 -31
  415. package/src/lib/python-helper.mjs +0 -142
  416. package/src/lib/runtime-manifest.mjs +0 -70
  417. package/src/lib/skill-catalog.mjs +0 -53
  418. package/src/lib/version.mjs +0 -20
  419. /package/{src → dist}/commands/pr/default.md +0 -0
@@ -202,6 +202,7 @@ def append_observed(
202
202
  stderr_excerpt,
203
203
  context,
204
204
  now=None,
205
+ identity=None,
205
206
  ):
206
207
  """Append a lead-observed error event to errors.jsonl."""
207
208
  if error_type not in ALLOWED_ERROR_TYPES:
@@ -232,6 +233,9 @@ def append_observed(
232
233
  "stderrExcerpt": truncate_stderr(stderr_excerpt),
233
234
  "context": context,
234
235
  }
236
+ from okstra_ctl.execution_identity import stored_identity
237
+
238
+ rec.update(stored_identity(identity))
235
239
  append_jsonl_line(out_path, rec)
236
240
  return rec
237
241
 
@@ -245,6 +249,7 @@ def dump_from_worker_sidecar(
245
249
  agent_role,
246
250
  model,
247
251
  now=None,
252
+ identity=None,
248
253
  ):
249
254
  """Read worker sidecar errors[] and append each to errors.jsonl with
250
255
  Lead-side metadata filled in. Returns number of records appended.
@@ -303,6 +308,10 @@ def dump_from_worker_sidecar(
303
308
  "stderrExcerpt": truncate_stderr(e.get("stderrExcerpt")),
304
309
  "context": entry_context,
305
310
  }
311
+ from okstra_ctl.execution_identity import stored_identity
312
+
313
+ rec.update(stored_identity(identity))
314
+ rec.update(stored_identity(e))
306
315
  append_jsonl_line(out_path, rec)
307
316
  count += 1
308
317
  return count
@@ -21,6 +21,22 @@ from okstra_ctl.error_log_core import (
21
21
  )
22
22
 
23
23
 
24
+ def render_error(
25
+ run_root: Path, *, role_execution_ref: str
26
+ ) -> str:
27
+ """Render one error line using the stored execution label."""
28
+ from okstra_ctl.execution_identity import (
29
+ execution_identity_for_ref,
30
+ stored_execution_label,
31
+ )
32
+ from okstra_ctl.usage_identity import run_execution_manifest
33
+
34
+ manifest = run_execution_manifest(run_root)
35
+ role = execution_identity_for_ref(manifest, role_execution_ref)
36
+ label = stored_execution_label({"executionLabel": role.execution_label})
37
+ return f"{label}: error"
38
+
39
+
24
40
  def _escape_cell(value: object) -> str:
25
41
  return str(value).replace("|", "\\|").replace("\n", " ")
26
42
 
@@ -119,9 +135,13 @@ def render_markdown(*, task_key, records, agg, parse_skipped, generated_at) -> s
119
135
  "## 개별 에러 이벤트",
120
136
  "",
121
137
  _md_table(
122
- ["ts", "phase", "agent", "errorType", "command", "exitCode", "message"],
138
+ [
139
+ "ts", "phase", "agent", "executionLabel", "errorType",
140
+ "command", "exitCode", "message",
141
+ ],
123
142
  [[
124
143
  str(r.get("ts", "")), str(r.get("phase", "")), str(r.get("agent", "")),
144
+ str(r.get("executionLabel") or ""),
125
145
  str(r.get("errorType", "")), f"`{r.get('command', '')}`",
126
146
  str(r.get("exitCode", "")),
127
147
  str(r.get("message", "")),
@@ -0,0 +1,529 @@
1
+ """Provider-neutral execution identity value objects and versioned readers."""
2
+ from __future__ import annotations
3
+
4
+ import hashlib
5
+ import json
6
+ from collections.abc import Mapping
7
+ from dataclasses import dataclass, field
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from .domain.provider import HostModelBinding, ServedModelAttestation
12
+ from .domain.worker_exec import WorkerWriteCapability
13
+
14
+
15
+ V2_SCHEMA_VERSION = "2.0"
16
+ EXECUTION_IDENTITY_VERSION = 2
17
+ _V1_SCHEMA_VERSIONS = {1, "1", "1.0"}
18
+ _V2_IDENTITY_KEYS = frozenset({
19
+ "executionIdentityVersion",
20
+ "participantAssignments",
21
+ "roleExecutions",
22
+ "invocations",
23
+ "attempts",
24
+ "attemptEvidenceSeals",
25
+ "mutationRecoveryDecisions",
26
+ "roleExecutionIndex",
27
+ "staticParticipantAssignments",
28
+ "staticRoleExecutions",
29
+ "dynamicRolePolicy",
30
+ })
31
+
32
+
33
+ class ExecutionManifestError(ValueError):
34
+ """Raised when execution identity state is ambiguous or inconsistent."""
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class ParticipantAssignment:
39
+ participant_ref: str
40
+ provider: str
41
+ model_ref: str | None
42
+ model_id: str
43
+ runner: str
44
+ host_runtime: str
45
+ host_model_value: str | None
46
+ session_ref: str | None
47
+ window_ref: str | None
48
+ worker_write_capability: WorkerWriteCapability | None
49
+ entry_mode: str
50
+ status: str
51
+
52
+ def to_payload(self) -> dict[str, Any]:
53
+ return {
54
+ "participantRef": self.participant_ref,
55
+ "provider": self.provider,
56
+ "modelRef": self.model_ref,
57
+ "modelId": self.model_id,
58
+ "runner": self.runner,
59
+ "hostRuntime": self.host_runtime,
60
+ "hostModelValue": self.host_model_value,
61
+ "sessionRef": self.session_ref,
62
+ "windowRef": self.window_ref,
63
+ "workerWriteCapability": _capability_payload(
64
+ self.worker_write_capability
65
+ ),
66
+ "entryMode": self.entry_mode,
67
+ "status": self.status,
68
+ }
69
+
70
+
71
+ @dataclass(frozen=True)
72
+ class RoleExecution:
73
+ role_execution_ref: str
74
+ participant_ref: str
75
+ source_role_execution_ref: str | None
76
+ role: str
77
+ provider: str
78
+ model_ref: str | None
79
+ model_id: str
80
+ ordinal: int
81
+ execution_label: str
82
+ model_spec_digest: str | None
83
+ binding: HostModelBinding | None
84
+ served_model_attestation: ServedModelAttestation
85
+ status: str
86
+
87
+ def to_payload(self) -> dict[str, Any]:
88
+ return {
89
+ "roleExecutionRef": self.role_execution_ref,
90
+ "participantRef": self.participant_ref,
91
+ "sourceRoleExecutionRef": self.source_role_execution_ref,
92
+ "role": self.role,
93
+ "provider": self.provider,
94
+ "modelRef": self.model_ref,
95
+ "modelId": self.model_id,
96
+ "ordinal": self.ordinal,
97
+ "executionLabel": self.execution_label,
98
+ "modelSpecDigest": self.model_spec_digest,
99
+ "binding": _binding_payload(self.binding),
100
+ "servedModelAttestation": _attestation_payload(
101
+ self.served_model_attestation
102
+ ),
103
+ "status": self.status,
104
+ }
105
+
106
+
107
+ @dataclass(frozen=True)
108
+ class Invocation:
109
+ invocation_ref: str
110
+ participant_ref: str
111
+ role_execution_ref: str
112
+ source_invocation_ref: str | None
113
+ recovery_ref: str | None
114
+ duty_id: str
115
+ dispatch_kind: str
116
+ round: int
117
+ input_digest: str
118
+ write_policy: Mapping[str, Any]
119
+ write_policy_digest: str
120
+ write_enforcement: Mapping[str, Any]
121
+
122
+ def to_payload(self) -> dict[str, Any]:
123
+ return {
124
+ "invocationRef": self.invocation_ref,
125
+ "participantRef": self.participant_ref,
126
+ "roleExecutionRef": self.role_execution_ref,
127
+ "sourceInvocationRef": self.source_invocation_ref,
128
+ "recoveryRef": self.recovery_ref,
129
+ "dutyId": self.duty_id,
130
+ "dispatchKind": self.dispatch_kind,
131
+ "round": self.round,
132
+ "inputDigest": self.input_digest,
133
+ "writePolicy": dict(self.write_policy),
134
+ "writePolicyDigest": self.write_policy_digest,
135
+ "writeEnforcement": dict(self.write_enforcement),
136
+ }
137
+
138
+
139
+ @dataclass(frozen=True)
140
+ class Attempt:
141
+ invocation_ref: str
142
+ attempt: int
143
+ started_at: str
144
+ finished_at: str | None
145
+ status: str
146
+ result_path: str | None
147
+ error_path: str | None
148
+ change_summary: Mapping[str, Any]
149
+ evidence_seal_ref: str | None
150
+
151
+ def to_payload(self) -> dict[str, Any]:
152
+ return {
153
+ "invocationRef": self.invocation_ref,
154
+ "attempt": self.attempt,
155
+ "startedAt": self.started_at,
156
+ "finishedAt": self.finished_at,
157
+ "status": self.status,
158
+ "resultPath": self.result_path,
159
+ "errorPath": self.error_path,
160
+ "changeSummary": dict(self.change_summary),
161
+ "evidenceSealRef": self.evidence_seal_ref,
162
+ }
163
+
164
+
165
+ @dataclass(frozen=True)
166
+ class ExecutionManifest:
167
+ entry_mode: str
168
+ participant_assignments: tuple[ParticipantAssignment, ...] = ()
169
+ role_executions: tuple[RoleExecution, ...] = ()
170
+ invocations: tuple[Invocation, ...] = ()
171
+ attempts: tuple[Attempt, ...] = ()
172
+ attempt_evidence_seals: tuple[Mapping[str, Any], ...] = ()
173
+ mutation_recovery_decisions: tuple[Mapping[str, Any], ...] = ()
174
+ legacy: bool = False
175
+ legacy_payload: Mapping[str, Any] = field(default_factory=dict, compare=False)
176
+
177
+ def to_payload(self) -> dict[str, Any]:
178
+ return {
179
+ "schemaVersion": V2_SCHEMA_VERSION,
180
+ "executionIdentityVersion": EXECUTION_IDENTITY_VERSION,
181
+ "entryMode": self.entry_mode,
182
+ "participantAssignments": [
183
+ row.to_payload() for row in self.participant_assignments
184
+ ],
185
+ "roleExecutions": [row.to_payload() for row in self.role_executions],
186
+ "invocations": [row.to_payload() for row in self.invocations],
187
+ "attempts": [row.to_payload() for row in self.attempts],
188
+ "attemptEvidenceSeals": list(self.attempt_evidence_seals),
189
+ "mutationRecoveryDecisions": list(self.mutation_recovery_decisions),
190
+ }
191
+
192
+
193
+ @dataclass(frozen=True)
194
+ class IdentitySurface:
195
+ surface: str
196
+ legacy: bool
197
+ payload: Mapping[str, Any]
198
+
199
+
200
+ def execution_label(role: str, provider: str, model_id: str, ordinal: int) -> str:
201
+ return f"{role}-{provider}-{model_id}-{ordinal}"
202
+
203
+
204
+ def execution_identity_for_ref(
205
+ manifest: ExecutionManifest, ref: str
206
+ ) -> RoleExecution:
207
+ """Resolve one canonical role execution for state, usage, errors and activity views."""
208
+ match = next(
209
+ (row for row in manifest.role_executions if row.role_execution_ref == ref),
210
+ None,
211
+ )
212
+ if match is None:
213
+ raise ExecutionManifestError(f"unknown roleExecutionRef: {ref}")
214
+ return match
215
+
216
+
217
+ def stored_execution_label(row: Mapping[str, Any]) -> str:
218
+ """Return the stored label; do not parse it into role/provider/model."""
219
+ label = row.get("executionLabel")
220
+ if not isinstance(label, str) or not label.strip():
221
+ raise ExecutionManifestError("executionLabel is missing")
222
+ return label
223
+
224
+
225
+ STORED_IDENTITY_KEYS = (
226
+ "participantRef",
227
+ "roleExecutionRef",
228
+ "invocationRef",
229
+ "attempt",
230
+ "executionLabel",
231
+ )
232
+
233
+
234
+ def stored_identity(payload: Mapping[str, Any] | None) -> dict[str, Any]:
235
+ """Copy stored identity fields. Never infer them from labels."""
236
+ if not isinstance(payload, Mapping):
237
+ return {}
238
+ copied: dict[str, Any] = {}
239
+ for key in STORED_IDENTITY_KEYS:
240
+ if key not in payload:
241
+ continue
242
+ value = payload[key]
243
+ if value in (None, ""):
244
+ continue
245
+ copied[key] = value
246
+ if "executionLabel" in copied:
247
+ copied["executionLabel"] = stored_execution_label(copied)
248
+ return copied if "roleExecutionRef" in copied or "executionLabel" in copied else {}
249
+
250
+
251
+ def reject_partial_v2_payload(payload: Mapping[str, Any]) -> None:
252
+ present = [key for key in _V2_IDENTITY_KEYS if key in payload]
253
+ if not present:
254
+ return
255
+ required = (
256
+ "executionIdentityVersion",
257
+ "participantAssignments",
258
+ "roleExecutions",
259
+ "invocations",
260
+ "attempts",
261
+ )
262
+ missing = [key for key in required if key not in payload]
263
+ if missing:
264
+ raise ExecutionManifestError(
265
+ "partial v2 execution identity payload is rejected: "
266
+ + ", ".join(missing)
267
+ )
268
+
269
+
270
+ def model_spec_digest(
271
+ model_spec: Mapping[str, Any] | object,
272
+ binding: HostModelBinding,
273
+ *,
274
+ write_policy_digest: str | None = None,
275
+ served_model_attestation: ServedModelAttestation | None = None,
276
+ ) -> str:
277
+ """Digest preparation facts; invocation policy and observations are excluded."""
278
+ del write_policy_digest, served_model_attestation
279
+ canonical = {
280
+ "modelSpec": _canonical_model_spec(model_spec),
281
+ "binding": _binding_payload(binding),
282
+ }
283
+ encoded = json.dumps(
284
+ canonical, sort_keys=True, separators=(",", ":"), ensure_ascii=False
285
+ ).encode("utf-8")
286
+ return "sha256:" + hashlib.sha256(encoded).hexdigest()
287
+
288
+
289
+ def read_identity_surface(path: Path, *, surface: str) -> IdentitySurface:
290
+ payload = _read_json_object(path)
291
+ return identity_surface_from_payload(payload, surface=surface)
292
+
293
+
294
+ def identity_surface_from_payload(
295
+ payload: Mapping[str, Any],
296
+ *,
297
+ surface: str,
298
+ ) -> IdentitySurface:
299
+ version = payload.get("schemaVersion")
300
+ has_v2_identity = bool(_V2_IDENTITY_KEYS.intersection(payload))
301
+ if version == V2_SCHEMA_VERSION and surface == "run-manifest":
302
+ reject_partial_v2_payload(payload)
303
+ if version in _V1_SCHEMA_VERSIONS:
304
+ if has_v2_identity:
305
+ raise ExecutionManifestError("mixed execution identity version")
306
+ return IdentitySurface(surface, True, payload)
307
+ if version != V2_SCHEMA_VERSION:
308
+ raise ExecutionManifestError(f"unsupported execution identity schema: {version!r}")
309
+ if payload.get("executionIdentityVersion") != EXECUTION_IDENTITY_VERSION:
310
+ raise ExecutionManifestError("mixed execution identity version")
311
+ required = _surface_required_keys(surface)
312
+ if any(key not in payload for key in required):
313
+ raise ExecutionManifestError("mixed execution identity version")
314
+ if surface == "task-manifest":
315
+ validate_task_role_execution_index(payload)
316
+ return IdentitySurface(surface, False, payload)
317
+
318
+
319
+ def validate_task_role_execution_index(payload: Mapping[str, Any]) -> None:
320
+ rows = payload.get("roleExecutionIndex")
321
+ if not isinstance(rows, list):
322
+ raise ExecutionManifestError("roleExecutionIndex must be an array")
323
+ required_keys = {"runRef", "roleExecutionRef"}
324
+ seen: set[tuple[str, str]] = set()
325
+ for index, row in enumerate(rows):
326
+ if not isinstance(row, Mapping):
327
+ raise ExecutionManifestError(
328
+ f"roleExecutionIndex[{index}] must be an object"
329
+ )
330
+ if set(row) != required_keys:
331
+ raise ExecutionManifestError(
332
+ f"roleExecutionIndex[{index}] must contain exactly "
333
+ "runRef and roleExecutionRef"
334
+ )
335
+ run_ref = _required_index_text(row, "runRef", index)
336
+ role_ref = _required_index_text(row, "roleExecutionRef", index)
337
+ pair = (run_ref, role_ref)
338
+ if pair in seen:
339
+ raise ExecutionManifestError(
340
+ f"roleExecutionIndex contains a duplicate pair: {run_ref}, {role_ref}"
341
+ )
342
+ seen.add(pair)
343
+
344
+
345
+ def synthesize_v1_execution_view(payload: Mapping[str, Any]) -> ExecutionManifest:
346
+ assignments = _legacy_assignments(payload)
347
+ participants: list[ParticipantAssignment] = []
348
+ roles: list[RoleExecution] = []
349
+ role_ordinals: dict[str, int] = {}
350
+ for index, assignment in enumerate(assignments, start=1):
351
+ participant, role = _legacy_identity_rows(assignment, index, role_ordinals)
352
+ participants.append(participant)
353
+ roles.append(role)
354
+ return ExecutionManifest(
355
+ entry_mode="legacy",
356
+ participant_assignments=tuple(participants),
357
+ role_executions=tuple(roles),
358
+ legacy=True,
359
+ legacy_payload=dict(payload),
360
+ )
361
+
362
+
363
+ def _legacy_assignments(payload: Mapping[str, Any]) -> list[Mapping[str, Any]]:
364
+ assignments: list[Mapping[str, Any]] = []
365
+ lead = payload.get("leadAssignment")
366
+ if isinstance(lead, Mapping):
367
+ assignments.append({"role": "leader", **lead})
368
+ workers = payload.get("workerAssignments")
369
+ if isinstance(workers, list):
370
+ assignments.extend(row for row in workers if isinstance(row, Mapping))
371
+ return assignments
372
+
373
+
374
+ def _legacy_identity_rows(
375
+ assignment: Mapping[str, Any],
376
+ index: int,
377
+ role_ordinals: dict[str, int],
378
+ ) -> tuple[ParticipantAssignment, RoleExecution]:
379
+ role = _canonical_legacy_role(str(assignment.get("role") or "analyser"))
380
+ role_ordinals[role] = role_ordinals.get(role, 0) + 1
381
+ ordinal = role_ordinals[role]
382
+ provider = str(assignment.get("provider") or "unknown")
383
+ model_id = str(
384
+ assignment.get("modelExecutionValue") or assignment.get("model") or "unknown"
385
+ )
386
+ participant_ref = f"legacy-participant-{index:03d}"
387
+ participant = ParticipantAssignment(
388
+ participant_ref=participant_ref,
389
+ provider=provider,
390
+ model_ref=None,
391
+ model_id=model_id,
392
+ runner=str(assignment.get("runner") or "legacy"),
393
+ host_runtime=str(assignment.get("hostRuntime") or "legacy"),
394
+ host_model_value=_optional_text(assignment.get("hostModelValue")),
395
+ session_ref=_optional_text(assignment.get("sessionId")),
396
+ window_ref=_optional_text(assignment.get("windowId")),
397
+ worker_write_capability=None,
398
+ entry_mode="legacy",
399
+ status="legacy",
400
+ )
401
+ role_row = RoleExecution(
402
+ role_execution_ref=f"legacy-role-exec-{role}-{ordinal:03d}",
403
+ participant_ref=participant_ref,
404
+ source_role_execution_ref=None,
405
+ role=role,
406
+ provider=provider,
407
+ model_ref=None,
408
+ model_id=model_id,
409
+ ordinal=ordinal,
410
+ execution_label=execution_label(role, provider, model_id, ordinal),
411
+ model_spec_digest=None,
412
+ binding=None,
413
+ served_model_attestation=ServedModelAttestation.unknown(),
414
+ status="legacy",
415
+ )
416
+ return participant, role_row
417
+
418
+
419
+ def _canonical_legacy_role(role: str) -> str:
420
+ return {"lead": "leader", "executor": "implementer"}.get(role, role)
421
+
422
+
423
+ def _canonical_model_spec(model_spec: Mapping[str, Any] | object) -> dict[str, Any]:
424
+ if isinstance(model_spec, Mapping):
425
+ return {key: _canonical_value(value) for key, value in model_spec.items()}
426
+ fields = {
427
+ "modelRef": str(getattr(model_spec, "model_ref")),
428
+ "providerId": getattr(model_spec, "provider_id"),
429
+ "modelId": getattr(getattr(model_spec, "model_ref"), "model_id"),
430
+ "displayName": getattr(model_spec, "display_name"),
431
+ "executionValue": getattr(model_spec, "execution_value"),
432
+ "aliases": getattr(model_spec, "aliases"),
433
+ "versionKind": getattr(model_spec, "version_kind"),
434
+ "channelFamily": getattr(model_spec, "channel_family"),
435
+ "supportedRoles": getattr(model_spec, "supported_roles"),
436
+ "selectable": getattr(model_spec, "selectable"),
437
+ "pricing": getattr(model_spec, "pricing"),
438
+ }
439
+ return {key: _canonical_value(value) for key, value in fields.items()}
440
+
441
+
442
+ def _canonical_value(value: Any) -> Any:
443
+ if isinstance(value, (set, frozenset, tuple)):
444
+ return sorted(value) if all(isinstance(item, str) for item in value) else list(value)
445
+ return value
446
+
447
+
448
+ def _binding_payload(binding: HostModelBinding | None) -> dict[str, Any] | None:
449
+ if binding is None:
450
+ return None
451
+ return {
452
+ "runner": binding.runner,
453
+ "catalogExecutionValue": binding.catalog_execution_value,
454
+ "resolvedExecutionValue": binding.resolved_execution_value,
455
+ "hostModelValue": binding.host_model_value,
456
+ "bindingFidelity": binding.binding_fidelity,
457
+ "workerWriteCapability": _capability_payload(
458
+ binding.worker_write_capability
459
+ ),
460
+ }
461
+
462
+
463
+ def _attestation_payload(attestation: ServedModelAttestation) -> dict[str, Any]:
464
+ return {
465
+ "observedModel": attestation.observed_model,
466
+ "normalizedModelRef": attestation.normalized_model_ref,
467
+ "level": attestation.level,
468
+ "source": attestation.source,
469
+ }
470
+
471
+
472
+ def _capability_payload(
473
+ capability: WorkerWriteCapability | None,
474
+ ) -> dict[str, str] | None:
475
+ if capability is None:
476
+ return None
477
+ return {"maxBoundaryPrecision": capability.max_boundary_precision}
478
+
479
+
480
+ def _read_json_object(path: Path) -> dict[str, Any]:
481
+ try:
482
+ payload = json.loads(path.read_text(encoding="utf-8"))
483
+ except (OSError, json.JSONDecodeError) as exc:
484
+ raise ExecutionManifestError(f"cannot read execution identity: {path}") from exc
485
+ if not isinstance(payload, dict):
486
+ raise ExecutionManifestError("execution identity surface must be an object")
487
+ return payload
488
+
489
+
490
+ def _surface_required_keys(surface: str) -> tuple[str, ...]:
491
+ required = {
492
+ "task-manifest": ("roleExecutionIndex",),
493
+ "run-manifest": (
494
+ "entryMode",
495
+ "participantAssignments",
496
+ "roleExecutions",
497
+ "invocations",
498
+ "attempts",
499
+ "attemptEvidenceSeals",
500
+ "mutationRecoveryDecisions",
501
+ ),
502
+ "input-snapshot": (
503
+ "staticParticipantAssignments",
504
+ "staticRoleExecutions",
505
+ "dynamicRolePolicy",
506
+ ),
507
+ }
508
+ try:
509
+ return required[surface]
510
+ except KeyError as exc:
511
+ raise ExecutionManifestError(f"unknown execution identity surface: {surface}") from exc
512
+
513
+
514
+ def _required_index_text(
515
+ row: Mapping[str, Any],
516
+ key: str,
517
+ index: int,
518
+ ) -> str:
519
+ value = row.get(key)
520
+ if not isinstance(value, str) or not value.strip():
521
+ raise ExecutionManifestError(
522
+ f"roleExecutionIndex[{index}].{key} must be a non-empty string"
523
+ )
524
+ return value
525
+
526
+
527
+ def _optional_text(value: object) -> str | None:
528
+ text = str(value or "").strip()
529
+ return text or None