@fitlab-ai/agent-infra 0.9.14 → 0.10.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 (354) hide show
  1. package/README.md +10 -0
  2. package/README.zh-CN.md +10 -0
  3. package/bin/internal-cli.sh +45 -0
  4. package/dist/bin/internal-cli.js +93 -30
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +9 -4
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
  8. package/dist/lib/defaults.json +103 -103
  9. package/dist/lib/fs/durable-write.js +39 -0
  10. package/dist/lib/fs/file-lock.js +111 -0
  11. package/dist/lib/host-control/audit.js +19 -0
  12. package/dist/lib/host-control/client.js +142 -0
  13. package/dist/lib/host-control/command.js +49 -0
  14. package/dist/lib/host-control/path.js +160 -0
  15. package/dist/lib/host-control/secure-fs.js +140 -0
  16. package/dist/lib/host-control/server.js +159 -0
  17. package/dist/lib/host-control/service.js +112 -0
  18. package/dist/lib/init.js +11 -2
  19. package/dist/lib/internal/cli-route-inventory.js +4 -2
  20. package/dist/lib/internal/codex-lifecycle.js +6 -6
  21. package/dist/lib/internal/demo-promotion.js +322 -0
  22. package/dist/lib/internal/demo-transcript.js +454 -0
  23. package/dist/lib/internal/host-control.js +28 -0
  24. package/dist/lib/internal/manual-validation.js +331 -0
  25. package/dist/lib/internal/platform-pr.js +18 -4
  26. package/dist/lib/internal/release-workflow.js +63 -84
  27. package/dist/lib/internal/sandbox-control.js +149 -2
  28. package/dist/lib/internal/task-artifact.js +16 -263
  29. package/dist/lib/internal/task-create.js +6 -5
  30. package/dist/lib/internal/task-event.js +37 -37
  31. package/dist/lib/internal/task-lifecycle.js +2 -2
  32. package/dist/lib/internal/task-operation-registry.js +101 -4
  33. package/dist/lib/internal/task-review.js +11 -56
  34. package/dist/lib/internal/task-validate.js +30 -10
  35. package/dist/lib/owned-path.js +33 -0
  36. package/dist/lib/platform/check-status.js +11 -0
  37. package/dist/lib/platform/comment-safety.js +4 -1
  38. package/dist/lib/platform/completion-backfill.js +1 -1
  39. package/dist/lib/platform/github-data.js +241 -0
  40. package/dist/lib/platform/github-provider.js +15 -20
  41. package/dist/lib/platform/issue-comments.js +18 -12
  42. package/dist/lib/platform/issues.js +2 -180
  43. package/dist/lib/platform/pr-change-report.js +5 -4
  44. package/dist/lib/platform/pr-checks.js +12 -65
  45. package/dist/lib/platform/pr-summary.js +117 -33
  46. package/dist/lib/platform/pull-requests.js +5 -108
  47. package/dist/lib/platform/snapshots.js +2 -0
  48. package/dist/lib/platform/task-identities.js +2 -9
  49. package/dist/lib/platform/verification-sync.js +132 -143
  50. package/dist/lib/pr-review/evidence-grading.js +4 -8
  51. package/dist/lib/release/workflow.js +1 -1
  52. package/dist/lib/sandbox/commands/create.js +7 -2
  53. package/dist/lib/sandbox/commands/rm.js +19 -1511
  54. package/dist/lib/sandbox/control/audit.js +203 -0
  55. package/dist/lib/sandbox/control/client.js +93 -54
  56. package/dist/lib/sandbox/control/executor.js +129 -65
  57. package/dist/lib/sandbox/control/finalization-response.js +27 -0
  58. package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
  59. package/dist/lib/sandbox/control/lifecycle.js +36 -2
  60. package/dist/lib/sandbox/control/native-file-lock.js +6 -98
  61. package/dist/lib/sandbox/control/protocol.js +55 -4
  62. package/dist/lib/sandbox/control/server.js +518 -127
  63. package/dist/lib/sandbox/control/state.js +64 -0
  64. package/dist/lib/sandbox/control/task-workflow.js +74 -0
  65. package/dist/lib/sandbox/control/workflow-executor.js +89 -0
  66. package/dist/lib/sandbox/index.js +1 -2
  67. package/dist/lib/sandbox/recovery.js +28 -4
  68. package/dist/lib/sandbox/removal.js +822 -0
  69. package/dist/lib/sandbox/task-cleanup.js +217 -0
  70. package/dist/lib/sandbox/workspace-identity.js +17 -1
  71. package/dist/lib/sandbox/workspace-view.js +13 -14
  72. package/dist/lib/task/activity-intent.js +2 -1
  73. package/dist/lib/task/activity-log.js +30 -24
  74. package/dist/lib/task/artifact-command.js +79 -0
  75. package/dist/lib/task/artifact-lifecycle.js +30 -28
  76. package/dist/lib/task/artifact-name.js +36 -0
  77. package/dist/lib/task/artifact-operations.js +29 -289
  78. package/dist/lib/task/artifact-receipts.js +3 -15
  79. package/dist/lib/task/artifact-recovery.js +430 -0
  80. package/dist/lib/task/artifact-repair-intent.js +134 -29
  81. package/dist/lib/task/artifact-schema.js +6 -14
  82. package/dist/lib/task/capabilities.js +12 -19
  83. package/dist/lib/task/codex-orchestration.js +71 -25
  84. package/dist/lib/task/commit-identity.js +28 -0
  85. package/dist/lib/task/commit-operation.js +33 -20
  86. package/dist/lib/task/control-authority.js +403 -6
  87. package/dist/lib/task/control-recovery.js +140 -0
  88. package/dist/lib/task/create-service.js +1 -8
  89. package/dist/lib/task/create.js +62 -9
  90. package/dist/lib/task/decision-details.js +2 -46
  91. package/dist/lib/task/delegation-receipts.js +64 -3
  92. package/dist/lib/task/events.js +275 -22
  93. package/dist/lib/task/finalization-state.js +9 -0
  94. package/dist/lib/task/human-override.js +8 -14
  95. package/dist/lib/task/implementation-inputs.js +3 -3
  96. package/dist/lib/task/intermediate-cleanup.js +441 -0
  97. package/dist/lib/task/invalidation.js +18 -65
  98. package/dist/lib/task/ledger-intents.js +7 -22
  99. package/dist/lib/task/ledger.js +1 -3
  100. package/dist/lib/task/lifecycle-recovery.js +549 -0
  101. package/dist/lib/task/lifecycle.js +31 -4
  102. package/dist/lib/task/local-artifact-finalization.js +197 -197
  103. package/dist/lib/task/manual-validation-completion.js +26 -0
  104. package/dist/lib/task/manual-validation-evidence.js +130 -0
  105. package/dist/lib/task/manual-validation-receipt.js +105 -0
  106. package/dist/lib/task/manual-validation-shared.js +40 -0
  107. package/dist/lib/task/manual-validation-transaction.js +243 -0
  108. package/dist/lib/task/markdown.js +47 -0
  109. package/dist/lib/task/orchestration.js +66 -19
  110. package/dist/lib/task/pr-delivery-fact.js +12 -56
  111. package/dist/lib/task/qualification-audit.js +20 -34
  112. package/dist/lib/task/recovery-warning.js +19 -0
  113. package/dist/lib/task/review-artifacts.js +2 -18
  114. package/dist/lib/task/review-command.js +26 -0
  115. package/dist/lib/task/review-finalization.js +175 -216
  116. package/dist/lib/task/review-fingerprint.js +2 -2
  117. package/dist/lib/task/sections.js +34 -56
  118. package/dist/lib/task/short-id.js +15 -7
  119. package/dist/lib/task/task-execution-lock.js +1 -1
  120. package/dist/lib/task/verification-engine.js +119 -337
  121. package/dist/lib/task/verification.js +1 -1
  122. package/dist/lib/task/workflow-command.js +16 -0
  123. package/dist/lib/task/workflow-warning-intents.js +0 -16
  124. package/dist/lib/task/workflow-warnings.js +28 -2
  125. package/dist/lib/update.js +2 -0
  126. package/dist/lib/version.js +1 -11
  127. package/dist/lifecycle-build-manifest.json +3 -3
  128. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
  129. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +10 -4
  130. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
  131. package/lib/fs/durable-write.ts +30 -0
  132. package/lib/fs/file-lock.ts +113 -0
  133. package/lib/host-control/audit.ts +22 -0
  134. package/lib/host-control/client.ts +172 -0
  135. package/lib/host-control/command.ts +60 -0
  136. package/lib/host-control/path.ts +167 -0
  137. package/lib/host-control/secure-fs.ts +135 -0
  138. package/lib/host-control/server.ts +180 -0
  139. package/lib/host-control/service.ts +133 -0
  140. package/lib/init.ts +15 -2
  141. package/lib/internal/cli-route-inventory.ts +4 -2
  142. package/lib/internal/codex-lifecycle.ts +6 -6
  143. package/lib/internal/demo-promotion.ts +354 -0
  144. package/lib/internal/demo-transcript.ts +419 -0
  145. package/lib/internal/host-control.ts +28 -0
  146. package/lib/internal/manual-validation.ts +315 -0
  147. package/lib/internal/platform-pr.ts +15 -4
  148. package/lib/internal/release-workflow.ts +74 -82
  149. package/lib/internal/sandbox-control.ts +162 -1
  150. package/lib/internal/task-artifact.ts +14 -217
  151. package/lib/internal/task-create.ts +5 -5
  152. package/lib/internal/task-event.ts +20 -11
  153. package/lib/internal/task-lifecycle.ts +4 -3
  154. package/lib/internal/task-operation-registry.ts +111 -4
  155. package/lib/internal/task-review.ts +9 -54
  156. package/lib/internal/task-validate.ts +26 -10
  157. package/lib/owned-path.ts +26 -0
  158. package/lib/platform/check-status.ts +9 -0
  159. package/lib/platform/comment-safety.ts +5 -0
  160. package/lib/platform/completion-backfill.ts +1 -1
  161. package/lib/platform/github-data.ts +322 -0
  162. package/lib/platform/github-provider.ts +15 -20
  163. package/lib/platform/issue-comments.ts +21 -14
  164. package/lib/platform/issues.ts +5 -246
  165. package/lib/platform/pr-change-report.ts +7 -5
  166. package/lib/platform/pr-checks.ts +13 -77
  167. package/lib/platform/pr-summary.ts +123 -35
  168. package/lib/platform/pull-requests.ts +6 -173
  169. package/lib/platform/snapshots.ts +53 -0
  170. package/lib/platform/task-identities.ts +3 -14
  171. package/lib/platform/verification-sync.ts +150 -156
  172. package/lib/pr-review/evidence-grading.ts +4 -9
  173. package/lib/release/workflow.ts +3 -3
  174. package/lib/sandbox/commands/create.ts +7 -6
  175. package/lib/sandbox/commands/rm.ts +21 -1895
  176. package/lib/sandbox/control/audit.ts +253 -0
  177. package/lib/sandbox/control/client.ts +109 -66
  178. package/lib/sandbox/control/executor.ts +134 -74
  179. package/lib/sandbox/control/finalization-response.ts +29 -0
  180. package/lib/sandbox/control/identity-sentinel.ts +137 -0
  181. package/lib/sandbox/control/lifecycle.ts +45 -3
  182. package/lib/sandbox/control/native-file-lock.ts +6 -107
  183. package/lib/sandbox/control/protocol.ts +80 -6
  184. package/lib/sandbox/control/server.ts +567 -132
  185. package/lib/sandbox/control/state.ts +86 -0
  186. package/lib/sandbox/control/task-workflow.ts +95 -0
  187. package/lib/sandbox/control/workflow-executor.ts +92 -0
  188. package/lib/sandbox/index.ts +1 -2
  189. package/lib/sandbox/recovery.ts +28 -5
  190. package/lib/sandbox/removal.ts +1142 -0
  191. package/lib/sandbox/task-cleanup.ts +209 -0
  192. package/lib/sandbox/workspace-identity.ts +19 -3
  193. package/lib/sandbox/workspace-view.ts +17 -15
  194. package/lib/task/activity-intent.ts +2 -2
  195. package/lib/task/activity-log.ts +27 -19
  196. package/lib/task/artifact-command.ts +85 -0
  197. package/lib/task/artifact-lifecycle.ts +31 -26
  198. package/lib/task/artifact-name.ts +55 -0
  199. package/lib/task/artifact-operations.ts +34 -310
  200. package/lib/task/artifact-receipts.ts +3 -14
  201. package/lib/task/artifact-recovery.ts +519 -0
  202. package/lib/task/artifact-repair-intent.ts +162 -33
  203. package/lib/task/artifact-schema.ts +5 -34
  204. package/lib/task/capabilities.ts +13 -22
  205. package/lib/task/codex-orchestration.ts +85 -24
  206. package/lib/task/commit-identity.ts +31 -0
  207. package/lib/task/commit-operation.ts +37 -20
  208. package/lib/task/control-authority.ts +540 -10
  209. package/lib/task/control-recovery.ts +186 -0
  210. package/lib/task/create-service.ts +1 -10
  211. package/lib/task/create.ts +63 -8
  212. package/lib/task/decision-details.ts +3 -71
  213. package/lib/task/delegation-receipts.ts +73 -2
  214. package/lib/task/events.ts +309 -24
  215. package/lib/task/finalization-state.ts +11 -0
  216. package/lib/task/human-override.ts +8 -16
  217. package/lib/task/implementation-inputs.ts +3 -3
  218. package/lib/task/intermediate-cleanup.ts +493 -0
  219. package/lib/task/invalidation.ts +18 -62
  220. package/lib/task/ledger-intents.ts +7 -24
  221. package/lib/task/ledger.ts +1 -3
  222. package/lib/task/lifecycle-recovery.ts +691 -0
  223. package/lib/task/lifecycle.ts +30 -5
  224. package/lib/task/local-artifact-finalization.ts +244 -234
  225. package/lib/task/manual-validation-completion.ts +40 -0
  226. package/lib/task/manual-validation-evidence.ts +209 -0
  227. package/lib/task/manual-validation-receipt.ts +156 -0
  228. package/lib/task/manual-validation-shared.ts +50 -0
  229. package/lib/task/manual-validation-transaction.ts +304 -0
  230. package/lib/task/markdown.ts +70 -0
  231. package/lib/task/orchestration.ts +84 -19
  232. package/lib/task/pr-delivery-fact.ts +15 -62
  233. package/lib/task/qualification-audit.ts +22 -33
  234. package/lib/task/recovery-warning.ts +21 -0
  235. package/lib/task/review-artifacts.ts +1 -20
  236. package/lib/task/review-command.ts +24 -0
  237. package/lib/task/review-finalization.ts +183 -292
  238. package/lib/task/review-fingerprint.ts +2 -2
  239. package/lib/task/sections.ts +33 -54
  240. package/lib/task/short-id.ts +27 -5
  241. package/lib/task/task-execution-lock.ts +1 -1
  242. package/lib/task/verification-engine.ts +113 -383
  243. package/lib/task/verification-types.ts +15 -0
  244. package/lib/task/verification.ts +1 -1
  245. package/lib/task/workflow-command.ts +17 -0
  246. package/lib/task/workflow-warning-intents.ts +0 -13
  247. package/lib/task/workflow-warnings.ts +21 -2
  248. package/lib/update.ts +6 -0
  249. package/lib/version.ts +1 -15
  250. package/package.json +6 -6
  251. package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
  252. package/templates/.agents/rules/create-issue.en.md +1 -1
  253. package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
  254. package/templates/.agents/rules/issue-sync.en.md +4 -2
  255. package/templates/.agents/rules/issue-sync.zh-CN.md +4 -3
  256. package/templates/.agents/rules/local-artifact-repair.en.md +46 -39
  257. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +43 -36
  258. package/templates/.agents/rules/testing-discipline.en.md +2 -0
  259. package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
  260. package/templates/.agents/scripts/package.json +3 -0
  261. package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
  262. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
  263. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
  264. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
  265. package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
  266. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
  267. package/templates/.agents/skills/block-task/config/verify.json +1 -1
  268. package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
  269. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
  270. package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
  271. package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
  272. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +7 -7
  273. package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
  274. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
  275. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  276. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  277. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +33 -21
  278. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +33 -21
  279. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  280. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  281. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  282. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  283. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
  284. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
  285. package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
  286. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
  287. package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
  288. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
  289. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
  290. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
  291. package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
  292. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
  293. package/templates/.agents/skills/create-pr/config/verify.json +1 -1
  294. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
  295. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
  296. package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
  297. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
  298. package/templates/.agents/skills/create-task/config/verify.json +1 -1
  299. package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
  300. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
  301. package/templates/.agents/skills/import-issue/config/verify.json +2 -2
  302. package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
  303. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
  304. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
  305. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
  306. package/templates/.agents/skills/release/SKILL.en.md +7 -3
  307. package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
  308. package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
  309. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
  310. package/templates/.agents/skills/restore-task/config/verify.json +1 -1
  311. package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
  312. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  313. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
  314. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
  315. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
  316. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
  317. package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
  318. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
  319. package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
  320. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
  321. package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
  322. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
  323. package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
  324. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  325. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
  326. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
  327. package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
  328. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
  329. package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
  330. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
  331. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
  332. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
  333. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
  334. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
  335. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
  336. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
  337. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
  338. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
  339. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
  340. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
  341. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  342. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  343. package/templates/.agents/skills/test/SKILL.en.md +17 -6
  344. package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
  345. package/templates/.agents/templates/task.en.md +1 -1
  346. package/templates/.agents/templates/task.zh-CN.md +1 -1
  347. package/templates/.claude/commands/import-issue.en.md +1 -1
  348. package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
  349. package/templates/.claude/commands/restore-task.en.md +1 -1
  350. package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
  351. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  352. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  353. package/dist/lib/platform/adapters.js +0 -56
  354. package/lib/platform/adapters.ts +0 -197
@@ -1,3 +1,5 @@
1
+ import { finalizationTerminalResponse } from './finalization-response.ts';
2
+ import { isDeepStrictEqual } from 'node:util';
1
3
  import { completedReentryView } from './completed-reentry.ts';
2
4
  import fs from 'node:fs';
3
5
  import path from 'node:path';
@@ -10,13 +12,13 @@ import {
10
12
  SANDBOX_CONTROL_MAX_RESPONSE_BYTES,
11
13
  SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES,
12
14
  type SandboxControlManifest,
15
+ type SandboxControlRecoveryWarning,
13
16
  type SandboxControlRequest,
14
17
  type SandboxControlResponse,
15
18
  type SandboxControlTimingPolicy
16
19
  } from './protocol.ts';
17
20
  import { prepareSandboxControlExecution, type PreparedSandboxControlExecution, type SandboxControlExecutionResult } from './executor.ts';
18
21
  import {
19
- appendSandboxControlAudit,
20
22
  atomicWriteJsonNoReplace,
21
23
  createSandboxControlPayload,
22
24
  cleanupStaleSandboxControlLease,
@@ -26,6 +28,8 @@ import {
26
28
  readExecution,
27
29
  readSandboxControlPayload,
28
30
  readSandboxControlResultEvidence,
31
+ readSandboxControlTerminalResult,
32
+ createSandboxControlTerminalResult,
29
33
  sandboxControlEncodedJsonBytes,
30
34
  sandboxControlGenerationUsage,
31
35
  sanitizeSandboxControlOutput,
@@ -34,11 +38,37 @@ import {
34
38
  writeSandboxControlPayload,
35
39
  writeSandboxControlReservation,
36
40
  writeSandboxControlResultEvidence,
41
+ writeSandboxControlTerminalResult,
42
+ appendSandboxControlAudit,
37
43
  terminateSandboxControlExecution,
38
44
  writeSandboxControlStatus,
39
45
  readSandboxControlStatus
40
46
  } from './state.ts';
41
47
  import { validateSandboxControlRequest } from './protocol.ts';
48
+ import {
49
+ requestAuditFields,
50
+ resultAuditFields,
51
+ appendCriticalAudit,
52
+ appendDiagnosticAudit,
53
+ createSandboxControlAuditContext,
54
+ writeSandboxControlTransition,
55
+ readSandboxControlTransition
56
+ } from './audit.ts';
57
+ import { parseTaskControlOperation } from '../../task/control-authority.ts';
58
+ import {
59
+ parseControlOutput,
60
+ classifySandboxControlRecovery,
61
+ findSandboxControlRecoveryOperation,
62
+ operationRecoveryBinding,
63
+ SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES
64
+ } from '../../task/control-recovery.ts';
65
+ import { readRun } from '../../task/orchestration.ts';
66
+ import { readLifecycleRecoveryDomainEvidence } from '../../task/lifecycle-recovery.ts';
67
+ import { captureRepositorySnapshot } from '../../task/workspace-snapshot.ts';
68
+ import { parseTypedTaskFrontmatter } from '../../task/frontmatter.ts';
69
+ import { readLifecycleJournalEvidence } from '../../task/lifecycle.ts';
70
+ import { resolveTaskRef } from '../../task/resolve-ref.ts';
71
+ import { loadShortIdByTaskId } from '../../task/short-id.ts';
42
72
  import { inspectSandboxControlContainer, type ContainerObservation } from './container-identity.ts';
43
73
  import {
44
74
  acquireSandboxControlBrokerStartup,
@@ -49,7 +79,8 @@ import {
49
79
  import type { BrokerOwner } from './lifecycle.ts';
50
80
  import { nextSandboxControlBackoff } from './timing.ts';
51
81
  import { readTaskFinalizationReceipt } from '../../task/finalization.ts';
52
- import { resolveTaskRef } from '../../task/resolve-ref.ts';
82
+ import { readCodexControllerRegistration } from './controller-registration.ts';
83
+ import { validateSandboxControlIdentity } from './identity-sentinel.ts';
53
84
  import {
54
85
  mergeSandboxTaskView,
55
86
  taskViewAfterFinalization,
@@ -57,22 +88,68 @@ import {
57
88
  type SandboxTaskView
58
89
  } from './task-view.ts';
59
90
  import { taskCreateOutputUnavailableResult } from '../../task/create-service.ts';
91
+ import {
92
+ consumeLifecycleRecoveryAttestation,
93
+ issueLifecycleRecoveryAttestation,
94
+ recoverLifecycleRecoveryOperation,
95
+ type LifecycleAuthorityResponseV1
96
+ } from '../../task/control-authority.ts';
97
+ import { createCodexCapabilityStore } from '../../agent-clients/adapters/codex-lifecycle/capability-store.ts';
98
+ import { computeLifecycleBuildIdentity } from '../../agent-clients/adapters/codex-lifecycle/build-identity.ts';
60
99
 
61
100
  type ActiveExecution = {
62
101
  request: SandboxControlRequest;
63
102
  prepared: PreparedSandboxControlExecution;
103
+ lifecycleAuthority: LifecycleAuthorityResponseV1 | undefined;
64
104
  result: SandboxControlExecutionResult | null;
65
105
  resultEvidenceWritten: boolean;
66
106
  failure: unknown;
67
107
  settled: boolean;
68
108
  };
69
109
 
70
- function safeRealpath(value: string): string | null {
110
+ function consumeLifecycleAuthorityForResult(
111
+ manifest: SandboxControlManifest,
112
+ execution: ActiveExecution
113
+ ): boolean {
114
+ const authority = execution.lifecycleAuthority?.attestation;
115
+ if (!authority || execution.result?.exitCode !== 0) return true;
116
+ try {
117
+ consumeLifecycleRecoveryAttestation(authority);
118
+ return true;
119
+ } catch (error) {
120
+ appendBrokerAudit(manifest, 'lifecycle-authority-consume-retry', {
121
+ ...requestAuditFields(manifest, execution.request),
122
+ errorCode: error instanceof Error ? error.name : 'LIFECYCLE_AUTHORITY_CONSUME_FAILED'
123
+ });
124
+ return false;
125
+ }
126
+ }
127
+
128
+ function lifecycleAuthorityForRequest(
129
+ manifest: SandboxControlManifest,
130
+ manifestPath: string,
131
+ request: SandboxControlRequest
132
+ ): LifecycleAuthorityResponseV1 | undefined {
133
+ const selector = request.family === 'task-workflow' || request.family === 'task-orchestration'
134
+ ? request.authority
135
+ : undefined;
136
+ if (!selector) return undefined;
137
+ let controllerBinding: { instanceDigest: string; controlGeneration: string } | null = null;
71
138
  try {
72
- return fs.realpathSync.native(value);
139
+ const registration = readCodexControllerRegistration(manifestPath);
140
+ controllerBinding = {
141
+ instanceDigest: registration.controllerInstanceDigest,
142
+ controlGeneration: registration.controlGeneration
143
+ };
73
144
  } catch {
74
- return null;
145
+ // The issuer reports the stable unavailable result; the broker never trusts
146
+ // a controller binding supplied by the request.
75
147
  }
148
+ return issueLifecycleRecoveryAttestation(selector, {
149
+ controllerBinding,
150
+ capabilityStore: createCodexCapabilityStore(),
151
+ buildIdentity: computeLifecycleBuildIdentity(manifest.repoRoot)
152
+ });
76
153
  }
77
154
 
78
155
  function appendBrokerAudit(
@@ -80,53 +157,72 @@ function appendBrokerAudit(
80
157
  event: string,
81
158
  fields: Record<string, string | number | boolean | null> = {}
82
159
  ): void {
160
+ appendDiagnosticAudit(manifest, event, { source: 'broker', ...fields });
161
+ }
162
+
163
+ function operationKey(request: SandboxControlRequest, output?: string): string | null {
164
+ if (request.family === 'task-finalization') return request.operation;
165
+ if (request.family === 'task-create') return 'create';
166
+ if (request.family === 'codex-controller') return request.command;
167
+ if (request.family === 'task-workflow') return request.workflow.operation;
168
+ if (request.family !== 'task-lifecycle' && request.family !== 'task-orchestration') return null;
169
+ if (request.family === 'task-orchestration' && request.args[1] === 'route') {
170
+ if (!output) return 'route';
171
+ const result = parseControlOutput(output);
172
+ return result?.changed === true && result.status === 'completed' ? 'route.clean-completion' : 'route.read';
173
+ }
83
174
  try {
84
- appendSandboxControlAudit(manifest, event, { source: 'broker', ...fields });
175
+ const operation = parseTaskControlOperation(request.family, request.args);
176
+ return operation.family === 'task-orchestration' ? operation.intent : operation.request.intent;
85
177
  } catch {
86
- // Diagnostics must never change the control protocol.
178
+ return null;
87
179
  }
88
180
  }
89
181
 
90
- function requestAuditFields(
182
+ function recoveryOperationKey(
183
+ request: SandboxControlRequest,
184
+ terminalResult: ReturnType<typeof readSandboxControlTerminalResult> | null,
185
+ output: string | undefined
186
+ ): string | null {
187
+ const operation = operationKey(request, output);
188
+ if (operation !== 'route') return operation;
189
+ const digest = terminalResult?.intentDigest;
190
+ for (const candidate of ['route.read', 'route.clean-completion'] as const) {
191
+ const expected = operationRecoveryBinding(request.id, request.generation, null, request.family, candidate).intentDigest;
192
+ if (digest === expected) return candidate;
193
+ }
194
+ return null;
195
+ }
196
+
197
+ function terminalResultMatchesRequest(
198
+ request: SandboxControlRequest,
199
+ terminalResult: ReturnType<typeof readSandboxControlTerminalResult>
200
+ ): boolean {
201
+ const operation = operationKey(request);
202
+ const candidates = operation === 'route'
203
+ ? ['route.read', 'route.clean-completion']
204
+ : operation ? [operation] : [];
205
+ return candidates.some((candidate) => createHash('sha256').update(`${request.family}\0${candidate}`, 'utf8').digest('hex') === terminalResult.intentDigest);
206
+ }
207
+
208
+ function criticalRequestPhase(
91
209
  manifest: SandboxControlManifest,
92
- manifestPath: string,
93
- request: SandboxControlRequest
94
- ): Record<string, string | number | boolean | null> {
95
- const args = 'args' in request ? request.args : [];
96
- const encodedArgs = JSON.stringify(args);
97
- return {
210
+ request: SandboxControlRequest,
211
+ phase: Parameters<typeof createSandboxControlAuditContext>[1]['phase'],
212
+ outcome: Parameters<typeof createSandboxControlAuditContext>[1]['outcome'],
213
+ reference?: string | null
214
+ ): void {
215
+ const context = createSandboxControlAuditContext(manifest, {
98
216
  requestId: request.id,
99
- requestFamily: request.family,
100
- sandboxTaskId: manifest.taskId,
101
- requestGeneration: request.generation,
102
- requestIssuedAt: request.issuedAt,
103
- requestExpiresAt: request.expiresAt,
104
- requestArgCount: args.length,
105
- requestArgsSha256: createHash('sha256').update(encodedArgs, 'utf8').digest('hex'),
106
- requestTaskRef: args[0] ?? null,
107
- requestCommand: args[1] ?? null,
108
- controllerProofPresent: request.controllerProof !== null,
109
- hostCwd: process.cwd(),
110
- manifestPath,
111
- manifestPathRealpath: safeRealpath(manifestPath),
112
- repoRoot: manifest.repoRoot,
113
- repoRootRealpath: safeRealpath(manifest.repoRoot),
114
- worktreeRoot: manifest.worktreeRoot,
115
- worktreeRootRealpath: safeRealpath(manifest.worktreeRoot),
116
- runtimeDir: manifest.runtimeDir,
117
- runtimeDirRealpath: safeRealpath(manifest.runtimeDir)
118
- };
217
+ family: request.family,
218
+ operation: operationKey(request),
219
+ phase,
220
+ outcome
221
+ });
222
+ appendCriticalAudit(manifest, context, { reference: reference ?? null });
223
+ writeSandboxControlTransition(manifest, { requestId: request.id, phase, reference });
119
224
  }
120
225
 
121
- function resultAuditFields(result: SandboxControlExecutionResult): Record<string, string | number | boolean | null> {
122
- return {
123
- exitCode: result.exitCode,
124
- stdoutBytes: Buffer.byteLength(result.stdout, 'utf8'),
125
- stderrBytes: Buffer.byteLength(result.stderr, 'utf8'),
126
- stdoutSha256: createHash('sha256').update(result.stdout, 'utf8').digest('hex'),
127
- stderrSha256: createHash('sha256').update(result.stderr, 'utf8').digest('hex')
128
- };
129
- }
130
226
 
131
227
  function assertRealDirectory(directory: string, parent?: string): void {
132
228
  const stat = fs.lstatSync(directory);
@@ -212,33 +308,7 @@ function finalizationRecoveryResponse(
212
308
  } catch {
213
309
  return { status: 'deferred' };
214
310
  }
215
- const pendingSteps = [
216
- receipt.taskComment === 'pending' ? 'task-comment' : null,
217
- receipt.verification === 'pending' ? 'verification' : null
218
- ].filter((step): step is string => step !== null);
219
- const completedSteps = ['lifecycle', receipt.taskComment === 'pending' ? null : 'task-comment', receipt.verification === 'pending' ? null : 'verification']
220
- .filter((step): step is string => step !== null);
221
- const warnings = receipt.warnings
222
- .filter((warning) => warning.status === 'open')
223
- .map(({ status: _status, resolvedAt: _resolvedAt, ...warning }) => warning);
224
- const result = {
225
- status: 'completed', changed: false, taskId: manifest.taskId,
226
- lifecycle: { status: 'no-op', changed: false, error: null },
227
- taskComment: receipt.taskComment === 'pending' ? null : { status: 'no-op', changed: false, error: null },
228
- verification: receipt.verification === 'pending' ? null : { status: 'no-op', changed: false, error: null },
229
- completedSteps, pendingSteps,
230
- result: pendingSteps.length > 0 || receipt.warningProjection === 'pending' || warnings.length > 0
231
- ? 'completed_with_warnings' : 'completed',
232
- warnings, error: null
233
- };
234
- return {
235
- status: 'matched',
236
- response: {
237
- version: 2, id: requestId, phase: 'completed', exitCode: 0,
238
- stdout: `${JSON.stringify({ version: 1, status: 'completed', changed: false, accepted: true, result, error: null })}\n`,
239
- stderr: '', error: null
240
- }
241
- };
311
+ return { status: 'matched', response: finalizationTerminalResponse(manifest.taskId, requestId, receipt) };
242
312
  }
243
313
 
244
314
  function publishFinalizationTaskView(
@@ -300,11 +370,12 @@ function outputMatchesEvidence(output: string, bytes: number, sha256: string): b
300
370
  && createHash('sha256').update(output, 'utf8').digest('hex') === sha256;
301
371
  }
302
372
 
303
- function genericRecoveryResponse(
373
+ export function genericRecoveryResponse(
304
374
  request: SandboxControlRequest,
305
375
  exitCode: number,
306
376
  payload: ReturnType<typeof readSandboxControlPayload> | null,
307
- cause: 'publish' | 'recovery' = 'recovery'
377
+ cause: 'publish' | 'recovery' = 'recovery',
378
+ warning: SandboxControlRecoveryWarning | null = null
308
379
  ): SandboxControlResponse {
309
380
  const outputUnavailable = request.family === 'task-create' && !payload;
310
381
  return {
@@ -313,38 +384,295 @@ function genericRecoveryResponse(
313
384
  phase: 'completed',
314
385
  exitCode,
315
386
  stdout: outputUnavailable ? `${JSON.stringify(taskCreateOutputUnavailableResult(request.id))}\n` : '',
316
- stderr: payload ? '' : cause === 'publish'
317
- ? 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: output payload was not retained\n'
318
- : 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: broker restarted after executor completion\n',
387
+ stderr: payload ? '' : warning
388
+ ? `${warning.code}: ${warning.message}\nAction: ${warning.action}\n`
389
+ : cause === 'publish'
390
+ ? 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: output payload was not retained\n'
391
+ : 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: broker restarted after executor completion\n',
319
392
  error: null,
320
393
  outputState: payload ? 'available' : 'unavailable',
321
394
  payload: payload ? payloadReference(payload) : null
322
395
  };
323
396
  }
324
397
 
325
- function recoveryResponse(
398
+ type RecoveryDomainEvidence = Readonly<{
399
+ domain: Readonly<Record<string, unknown>> | null;
400
+ journal?: ReturnType<typeof readLifecycleJournalEvidence>;
401
+ }>;
402
+
403
+ function taskCreateDomainEvidence(
404
+ manifest: SandboxControlManifest,
405
+ output: Record<string, unknown> | null
406
+ ): Readonly<Record<string, unknown>> {
407
+ if (!output || !['applied', 'no-op', 'degraded'].includes(String(output.status))) {
408
+ return { consistent: false };
409
+ }
410
+ const task = output.task && typeof output.task === 'object' && !Array.isArray(output.task)
411
+ ? output.task as Record<string, unknown> : null;
412
+ const taskId = typeof task?.id === 'string' ? task.id : null;
413
+ const shortId = typeof task?.shortId === 'string' ? task.shortId : null;
414
+ if (!taskId || !shortId) return { consistent: false };
415
+ try {
416
+ const resolved = resolveTaskRef(taskId, { repoRoot: manifest.repoRoot });
417
+ const shortIds = loadShortIdByTaskId(manifest.repoRoot);
418
+ return {
419
+ consistent: resolved.ok && resolved.taskId === taskId && resolved.state === 'active' && shortIds.get(taskId) === shortId,
420
+ taskId,
421
+ shortId
422
+ };
423
+ } catch {
424
+ return { consistent: false };
425
+ }
426
+ }
427
+
428
+ function controllerDomainEvidence(
429
+ manifest: SandboxControlManifest,
430
+ manifestPath: string,
431
+ request: SandboxControlRequest,
432
+ output: Record<string, unknown> | null
433
+ ): Readonly<Record<string, unknown>> {
434
+ if (!output || output.error !== null || typeof output.status !== 'string') return { consistent: false };
435
+ try {
436
+ const registration = readCodexControllerRegistration(manifestPath);
437
+ if (request.family !== 'codex-controller') return { consistent: false };
438
+ if (request.command === 'open') {
439
+ const lease = output.lease && typeof output.lease === 'object' && !Array.isArray(output.lease)
440
+ ? output.lease as Record<string, unknown> : null;
441
+ return {
442
+ consistent: output.status === 'opened' && output.changed === true
443
+ && lease?.taskId === registration.taskId
444
+ && lease.controlGeneration === registration.controlGeneration
445
+ && lease.controllerInstanceDigest === registration.controllerInstanceDigest
446
+ && registration.taskId === manifest.taskId
447
+ && registration.controlGeneration === manifest.generation
448
+ };
449
+ }
450
+ if (request.command === 'verify') {
451
+ const binding = output.binding && typeof output.binding === 'object' && !Array.isArray(output.binding)
452
+ ? output.binding as Record<string, unknown> : null;
453
+ return {
454
+ consistent: output.status === 'verified' && output.changed === false
455
+ && binding?.taskId === registration.taskId
456
+ && binding.controlGeneration === registration.controlGeneration
457
+ && binding.controllerInstanceDigest === registration.controllerInstanceDigest
458
+ };
459
+ }
460
+ return { consistent: false };
461
+ } catch (error) {
462
+ return { consistent: request.family === 'codex-controller' && request.command === 'close'
463
+ && (error as { code?: string }).code === 'CODEX_SANDBOX_CONTROLLER_REGISTRATION_MISSING'
464
+ && output?.status === 'closed' && typeof output.changed === 'boolean' };
465
+ }
466
+ }
467
+
468
+ function orchestrationDomainEvidence(
469
+ operation: ReturnType<typeof findSandboxControlRecoveryOperation>,
470
+ terminalResult: ReturnType<typeof readSandboxControlTerminalResult>,
471
+ output: Record<string, unknown> | null,
472
+ run: ReturnType<typeof readRun> | null
473
+ ): Readonly<Record<string, unknown>> {
474
+ if (!operation || !output) return { consistent: false };
475
+ const outputRun = output.run && typeof output.run === 'object' && !Array.isArray(output.run)
476
+ ? output.run as Record<string, unknown> : null;
477
+ const runMatches = outputRun === null ? run === null : run !== null && JSON.stringify(run) === JSON.stringify(outputRun);
478
+ if (operation.class === 'read-only') {
479
+ return {
480
+ consistent: output.status === terminalResult.status && output.changed === terminalResult.changed && runMatches,
481
+ snapshotValid: true,
482
+ status: run?.status ?? terminalResult.status,
483
+ pendingDelegation: run?.pendingDelegation ?? null
484
+ };
485
+ }
486
+ if (!run || !outputRun) return { consistent: false };
487
+ return {
488
+ consistent: output.status === terminalResult.status && output.changed === terminalResult.changed && runMatches,
489
+ status: run.status,
490
+ pendingDelegation: run.pendingDelegation
491
+ };
492
+ }
493
+
494
+ function readRecoveryDomain(
326
495
  manifest: SandboxControlManifest,
496
+ manifestPath: string,
497
+ request: SandboxControlRequest,
498
+ operation: ReturnType<typeof findSandboxControlRecoveryOperation>,
499
+ terminalResult: ReturnType<typeof readSandboxControlTerminalResult>,
500
+ payloadOutput: string | null
501
+ ): RecoveryDomainEvidence {
502
+ if (!operation) return { domain: null };
503
+ const taskRef = request.family === 'task-finalization' || request.family === 'task-workflow'
504
+ ? manifest.taskId
505
+ : 'args' in request ? request.args[0] ?? null : manifest.taskId;
506
+ const output = parseControlOutput(payloadOutput);
507
+ if (!taskRef && operation.family !== 'task-create' && operation.family !== 'codex-controller') {
508
+ return { domain: null };
509
+ }
510
+
511
+ if (operation.family === 'task-lifecycle') {
512
+ if (operation.intent === 'recover-started') {
513
+ try {
514
+ if (!('args' in request)) return { domain: null };
515
+ const parsed = parseTaskControlOperation('task-lifecycle', request.args);
516
+ if (parsed.family !== 'task-lifecycle' || parsed.request.intent !== 'recover-started') {
517
+ return { domain: null };
518
+ }
519
+ return {
520
+ domain: readLifecycleRecoveryDomainEvidence(manifest.repoRoot, parsed.request, terminalResult),
521
+ journal: readLifecycleJournalEvidence(manifest.repoRoot, taskRef!)
522
+ };
523
+ } catch {
524
+ return { domain: null };
525
+ }
526
+ }
527
+ if (!taskRef || !terminalResult.targetState) return { domain: null };
528
+ const journal = readLifecycleJournalEvidence(manifest.repoRoot, taskRef);
529
+ try {
530
+ const resolved = resolveTaskRef(taskRef, { repoRoot: manifest.repoRoot });
531
+ const shortIds = loadShortIdByTaskId(manifest.repoRoot);
532
+ const shortIdMatches = terminalResult.targetState === 'active'
533
+ ? shortIds.has(resolved.ok ? resolved.taskId : taskRef)
534
+ : !shortIds.has(resolved.ok ? resolved.taskId : taskRef);
535
+ return {
536
+ domain: { consistent: resolved.ok && resolved.state === terminalResult.targetState && shortIdMatches },
537
+ journal
538
+ };
539
+ } catch {
540
+ return { domain: { consistent: false }, journal };
541
+ }
542
+ }
543
+
544
+ if (operation.family === 'task-orchestration') {
545
+ if (!taskRef) return { domain: null };
546
+ try {
547
+ const resolved = resolveTaskRef(taskRef, { repoRoot: manifest.repoRoot });
548
+ if (!resolved.ok) return { domain: { consistent: false } };
549
+ const run = readRun(resolved.taskDir);
550
+ if (!run && operation.class !== 'read-only') return { domain: { consistent: false } };
551
+ if (operation.class === 'route.clean-completion') {
552
+ const snapshot = captureRepositorySnapshot(manifest.repoRoot);
553
+ const metadata = parseTypedTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8'));
554
+ const completion = run?.completionEvidence ?? null;
555
+ const consistent = run !== null && completion !== null
556
+ && terminalResult.completionEvidence !== null
557
+ && isDeepStrictEqual(completion, terminalResult.completionEvidence);
558
+ return {
559
+ domain: {
560
+ consistent,
561
+ status: run?.status ?? null,
562
+ pendingDelegation: run?.pendingDelegation ?? null,
563
+ completionEvidence: completion,
564
+ snapshot,
565
+ lastReviewedCommit: metadata.last_reviewed_commit
566
+ }
567
+ };
568
+ }
569
+ return { domain: orchestrationDomainEvidence(operation, terminalResult, output, run) };
570
+ } catch {
571
+ return { domain: { consistent: false } };
572
+ }
573
+ }
574
+
575
+ if (operation.family === 'task-create') {
576
+ return { domain: taskCreateDomainEvidence(manifest, output) };
577
+ }
578
+ if (operation.family === 'codex-controller') {
579
+ return { domain: controllerDomainEvidence(manifest, manifestPath, request, output) };
580
+ }
581
+ if (operation.family === 'task-workflow') {
582
+ return {
583
+ domain: { consistent: output?.status === terminalResult.status && output.changed === terminalResult.changed, snapshotValid: true }
584
+ };
585
+ }
586
+ return { domain: null };
587
+ }
588
+
589
+ function readCommittedCriticalPhases(manifest: SandboxControlManifest, requestId: string): readonly string[] {
590
+ return SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES.filter((phase) => {
591
+ try {
592
+ readSandboxControlTransition(manifest, requestId, phase);
593
+ return true;
594
+ } catch {
595
+ return false;
596
+ }
597
+ });
598
+ }
599
+
600
+ export function recoveryResponse(
601
+ manifest: SandboxControlManifest,
602
+ manifestPath: string,
327
603
  request: SandboxControlRequest,
328
604
  evidence: ReturnType<typeof readSandboxControlResultEvidence>,
329
- payload: ReturnType<typeof readSandboxControlPayload> | null
605
+ payload: ReturnType<typeof readSandboxControlPayload> | null,
606
+ terminalResult: ReturnType<typeof readSandboxControlTerminalResult>
330
607
  ): SandboxControlResponse | null {
608
+ const operationName = recoveryOperationKey(request, terminalResult, payload?.stdout);
609
+ const operation = operationName ? findSandboxControlRecoveryOperation(request.family, operationName) : null;
610
+ if (!operation) return null;
611
+ if (payload) {
612
+ const payloadTerminal = createSandboxControlTerminalResult(manifest, {
613
+ id: request.id, family: request.family, operation: operationName
614
+ }, payload.stdout);
615
+ if (JSON.stringify(payloadTerminal) !== JSON.stringify(terminalResult)) return unknown(request.id);
616
+ }
617
+ const finalization = request.family === 'task-finalization' ? finalizationRecoveryResponse(manifest, request.id, 0) : null;
618
+ if (evidence.exitCode === 0 && finalization?.status === 'deferred') return null;
619
+ if (evidence.exitCode === 0 && request.family === 'task-workflow' && request.authority?.phase === 'task-event.completed') {
620
+ try {
621
+ const registration = readCodexControllerRegistration(manifestPath);
622
+ const recovered = recoverLifecycleRecoveryOperation(request.authority, {
623
+ repoRoot: manifest.repoRoot,
624
+ capabilityStore: createCodexCapabilityStore(),
625
+ buildIdentity: computeLifecycleBuildIdentity(manifest.repoRoot),
626
+ controllerBinding: {
627
+ instanceDigest: registration.controllerInstanceDigest,
628
+ controlGeneration: registration.controlGeneration
629
+ }
630
+ });
631
+ if (recovered.status !== 'committed') return null;
632
+ } catch (error) {
633
+ appendBrokerAudit(manifest, 'lifecycle-authority-recovery-failed', {
634
+ ...requestAuditFields(manifest, request),
635
+ errorCode: error instanceof Error ? error.name : 'LIFECYCLE_AUTHORITY_RECOVERY_FAILED'
636
+ });
637
+ return null;
638
+ }
639
+ }
640
+ const recovery: RecoveryDomainEvidence = finalization
641
+ ? { domain: { consistent: finalization.status === 'matched' } }
642
+ : readRecoveryDomain(manifest, manifestPath, request, operation, terminalResult, payload?.stdout ?? null);
643
+ const binding = operationRecoveryBinding(request.id, manifest.generation, manifest.taskId, request.family, operationName!);
644
+ const decision = classifySandboxControlRecovery({
645
+ operation,
646
+ binding,
647
+ startedCommitted: true,
648
+ terminalResult,
649
+ criticalPhases: readCommittedCriticalPhases(manifest, request.id),
650
+ domain: recovery.domain,
651
+ journal: recovery.journal
652
+ });
653
+ if (decision.outcome === 'unknown' || decision.outcome === 'in-progress') return unknown(request.id);
654
+ if (decision.outcome === 'not-executed') return notExecuted(request.id);
655
+ if (decision.outcome !== 'success' && decision.outcome !== 'failure') return null;
331
656
  if (request.family === 'task-finalization' && evidence.exitCode === 0) {
332
- const finalization = finalizationRecoveryResponse(manifest, request.id, evidence.exitCode);
333
- return finalization.status === 'matched' ? finalization.response ?? null : null;
657
+ if (finalization?.status !== 'matched') return null;
658
+ return finalization.response ?? null;
334
659
  }
335
- return genericRecoveryResponse(request, evidence.exitCode, payload);
660
+ return genericRecoveryResponse(request, evidence.exitCode, payload, 'recovery', terminalResult.warning ?? null);
336
661
  }
337
662
 
338
663
  function terminalMatchesEvidence(
339
664
  manifest: SandboxControlManifest,
665
+ manifestPath: string,
340
666
  request: SandboxControlRequest,
341
667
  response: SandboxControlResponse,
342
668
  evidence: ReturnType<typeof readSandboxControlResultEvidence>,
343
669
  payload: ReturnType<typeof readSandboxControlPayload> | null,
344
- payloadInvalid: boolean
670
+ payloadInvalid: boolean,
671
+ terminalResult: ReturnType<typeof readSandboxControlTerminalResult> | null
345
672
  ): { valid: boolean; payloadReferenced: boolean } {
346
673
  if (request.family === 'task-finalization' && evidence.exitCode === 0) {
347
- const expected = recoveryResponse(manifest, request, evidence, payload);
674
+ if (!terminalResult) return { valid: false, payloadReferenced: false };
675
+ const expected = recoveryResponse(manifest, manifestPath, request, evidence, payload, terminalResult);
348
676
  return {
349
677
  valid: expected !== null && JSON.stringify(response) === JSON.stringify(expected),
350
678
  payloadReferenced: false
@@ -373,7 +701,7 @@ function terminalMatchesEvidence(
373
701
  if (response.outputState === 'unavailable') {
374
702
  const causes = ['recovery', 'publish'] as const;
375
703
  const valid = causes.some((cause) => JSON.stringify(response)
376
- === JSON.stringify(genericRecoveryResponse(request, evidence.exitCode, null, cause)));
704
+ === JSON.stringify(genericRecoveryResponse(request, evidence.exitCode, null, cause, terminalResult?.warning ?? null)));
377
705
  return { valid, payloadReferenced: false };
378
706
  }
379
707
  return {
@@ -392,6 +720,16 @@ function publishExecutionResult(
392
720
  brokerOwns: () => boolean
393
721
  ): boolean {
394
722
  const normalized = sanitizeSandboxControlResult(manifest, result);
723
+ const terminalResult = writeSandboxControlTerminalResult(manifest, {
724
+ id: request.id,
725
+ family: request.family,
726
+ operation: operationKey(request, normalized.stdout)
727
+ }, normalized.stdout);
728
+ if (fs.existsSync(path.join(manifest.processingDir, request.id, 'transitions'))) {
729
+ criticalRequestPhase(manifest, request, 'completed', normalized.exitCode === 0 ? 'success' : 'failure');
730
+ criticalRequestPhase(manifest, request, 'evidence-written', normalized.exitCode === 0 ? 'success' : 'failure');
731
+ criticalRequestPhase(manifest, request, 'publish-authorized', normalized.exitCode === 0 ? 'success' : 'failure');
732
+ }
395
733
  let terminal: SandboxControlResponse | null = null;
396
734
  if (request.family === 'task-finalization') {
397
735
  const finalization = finalizationRecoveryResponse(manifest, request.id, normalized.exitCode);
@@ -419,7 +757,7 @@ function publishExecutionResult(
419
757
  } catch {
420
758
  payload = null;
421
759
  }
422
- terminal = genericRecoveryResponse(request, normalized.exitCode, payload, 'publish');
760
+ terminal = genericRecoveryResponse(request, normalized.exitCode, payload, 'publish', terminalResult.warning ?? null);
423
761
  }
424
762
  }
425
763
  if (!brokerOwns()) return false;
@@ -427,7 +765,11 @@ function publishExecutionResult(
427
765
  const view = publishFinalizationTaskView(manifest, broker, request.id, 'healthy', null, null);
428
766
  if (view.state === 'unknown') return false;
429
767
  }
430
- return writeSandboxControlResponse(manifest, terminal);
768
+ const committed = writeSandboxControlResponse(manifest, terminal);
769
+ if (committed && fs.existsSync(path.join(manifest.processingDir, request.id, 'transitions'))) {
770
+ criticalRequestPhase(manifest, request, 'published-committed', normalized.exitCode === 0 ? 'success' : 'failure');
771
+ }
772
+ return committed;
431
773
  }
432
774
 
433
775
  function writeAcceptedResponse(manifest: SandboxControlManifest, response: SandboxControlResponse): void {
@@ -492,21 +834,40 @@ function bindingReason(manifest: SandboxControlManifest): string | null {
492
834
  }
493
835
  }
494
836
 
495
- function recoverProcessing(manifest: SandboxControlManifest, broker: BrokerOwner, brokerOwns: () => boolean): boolean {
837
+ function assertCurrentSandboxControlIdentity(manifest: SandboxControlManifest, manifestPath: string): void {
838
+ const result = validateSandboxControlIdentity({
839
+ publicStatusDir: manifest.publicStatusDir,
840
+ root: path.dirname(path.resolve(manifestPath)),
841
+ mode: manifest.mode,
842
+ taskId: manifest.taskId,
843
+ generation: manifest.generation,
844
+ controlRootId: manifest.controlRootId
845
+ });
846
+ if (result.state !== 'valid') {
847
+ throw new Error(`SANDBOX_CONTROL_IDENTITY_${result.state.replaceAll('-', '_').toUpperCase()}`);
848
+ }
849
+ }
850
+
851
+ function recoverProcessing(manifest: SandboxControlManifest, manifestPath: string, broker: BrokerOwner, brokerOwns: () => boolean): boolean {
496
852
  for (const entry of fs.readdirSync(manifest.processingDir, { withFileTypes: true })) {
497
853
  if (!entry.isDirectory() || !/^[a-f0-9-]{16,64}$/.test(entry.name)) continue;
498
854
  if (!brokerOwns()) return false;
499
855
  const descriptor = executionPath(manifest, entry.name);
500
856
  const existingResponse = responsePath(manifest, entry.name);
501
857
  const processingDirectory = path.join(manifest.processingDir, entry.name);
858
+ const transitionsDirectory = path.join(processingDirectory, 'transitions');
859
+ const transitionProtocolActive = fs.existsSync(transitionsDirectory);
860
+ const startedCommitted = fs.existsSync(path.join(transitionsDirectory, 'started-committed.json'));
502
861
  let payloadReferenced = false;
503
862
  if (fs.existsSync(descriptor)) {
504
863
  const execution = readExecution(descriptor);
505
864
  let resultEvidence: ReturnType<typeof readSandboxControlResultEvidence> | null = null;
865
+ let terminalResult: ReturnType<typeof readSandboxControlTerminalResult> | null = null;
506
866
  let payload: ReturnType<typeof readSandboxControlPayload> | null = null;
507
867
  let payloadInvalid = false;
508
868
  let request: SandboxControlRequest | null = null;
509
869
  let existingTerminalResponse: SandboxControlResponse | null = null;
870
+ let terminalReconciled = false;
510
871
  try {
511
872
  const rawRequest = JSON.parse(fs.readFileSync(path.join(processingDirectory, 'request.json'), 'utf8')) as Record<string, unknown>;
512
873
  request = validateSandboxControlRequest(rawRequest, manifest, {
@@ -526,6 +887,18 @@ function recoverProcessing(manifest: SandboxControlManifest, broker: BrokerOwner
526
887
  resultEvidence = null;
527
888
  }
528
889
  }
890
+ const terminalResultPathValue = path.join(processingDirectory, 'terminal-result.json');
891
+ if (fs.existsSync(terminalResultPathValue)) {
892
+ try {
893
+ terminalResult = readSandboxControlTerminalResult(terminalResultPathValue);
894
+ if (terminalResult.requestId !== entry.name || terminalResult.generation !== manifest.generation
895
+ || !request || !terminalResultMatchesRequest(request, terminalResult)) {
896
+ terminalResult = null;
897
+ }
898
+ } catch {
899
+ terminalResult = null;
900
+ }
901
+ }
529
902
  const publishedPayloadPath = payloadPath(manifest, entry.name);
530
903
  if (fs.existsSync(publishedPayloadPath)) {
531
904
  try {
@@ -538,11 +911,21 @@ function recoverProcessing(manifest: SandboxControlManifest, broker: BrokerOwner
538
911
  payloadInvalid = true;
539
912
  }
540
913
  }
914
+ if (transitionProtocolActive && startedCommitted && !terminalResult) {
915
+ if (!brokerOwns()) return false;
916
+ if (!terminateSandboxControlExecution(execution)) {
917
+ throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${entry.name}`);
918
+ }
919
+ appendDiagnosticAudit(manifest, 'orphan-tree-terminated', { requestId: entry.name });
920
+ writeSandboxControlResponse(manifest, unknown(entry.name));
921
+ continue;
922
+ }
923
+ if (transitionProtocolActive && !startedCommitted && terminalResult) continue;
541
924
  if (!terminateSandboxControlExecution(execution)) {
542
925
  throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${entry.name}`);
543
926
  }
544
927
  if (!brokerOwns()) return false;
545
- appendSandboxControlAudit(manifest, 'orphan-tree-terminated', { requestId: entry.name });
928
+ appendDiagnosticAudit(manifest, 'orphan-tree-terminated', { requestId: entry.name });
546
929
  let terminal = false;
547
930
  if (fs.existsSync(existingResponse)) {
548
931
  try {
@@ -571,28 +954,61 @@ function recoverProcessing(manifest: SandboxControlManifest, broker: BrokerOwner
571
954
  }
572
955
  if (terminal && (!request || !resultEvidence)) continue;
573
956
  if (terminal && request && resultEvidence) {
574
- const reconciliation = terminalMatchesEvidence(manifest, request, existingTerminalResponse!, resultEvidence, payload, payloadInvalid);
957
+ if (!terminalResult && request.family !== 'task-create') continue;
958
+ const reconciliation = terminalMatchesEvidence(
959
+ manifest,
960
+ manifestPath,
961
+ request,
962
+ existingTerminalResponse!,
963
+ resultEvidence,
964
+ payload,
965
+ payloadInvalid,
966
+ terminalResult
967
+ );
575
968
  if (!reconciliation.valid) continue;
969
+ terminalReconciled = true;
576
970
  payloadReferenced = reconciliation.payloadReferenced;
577
971
  if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
578
972
  const view = publishFinalizationTaskView(manifest, broker, request.id, 'starting', null, null);
579
973
  if (view.state === 'unknown') continue;
580
974
  }
581
975
  }
976
+ if (!terminalResult && !terminalReconciled) {
977
+ if (terminal) continue;
978
+ if (!brokerOwns()) return false;
979
+ if (!resultEvidence || !request || payloadInvalid) continue;
980
+ if (request.family === 'task-create' && resultEvidence.exitCode === 0 && payload === null) {
981
+ writeSandboxControlResponse(manifest, genericRecoveryResponse(request, resultEvidence.exitCode, null));
982
+ terminalReconciled = true;
983
+ terminal = true;
984
+ } else {
985
+ writeSandboxControlResponse(manifest, unknown(entry.name));
986
+ continue;
987
+ }
988
+ }
582
989
  if (!terminal) {
583
990
  if (!brokerOwns()) return false;
584
991
  if (!resultEvidence || !request || payloadInvalid) continue;
585
- const recovered = recoveryResponse(manifest, request, resultEvidence, payload);
992
+ const recovered = recoveryResponse(manifest, manifestPath, request, resultEvidence, payload, terminalResult!);
586
993
  if (!recovered) continue;
587
994
  if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
588
995
  const view = publishFinalizationTaskView(manifest, broker, request.id, 'starting', null, null);
589
996
  if (view.state === 'unknown') continue;
590
997
  }
591
998
  writeSandboxControlResponse(manifest, recovered);
999
+ const preserveRecoveryEvidence = recovered.error?.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN';
1000
+ if (transitionProtocolActive && !preserveRecoveryEvidence) {
1001
+ writeSandboxControlTransition(manifest, { requestId: entry.name, phase: 'recovered' });
1002
+ }
1003
+ if (preserveRecoveryEvidence) continue;
592
1004
  payloadReferenced = Boolean(payload) && request.family !== 'task-finalization';
593
1005
  }
594
1006
  } else {
595
1007
  if (!brokerOwns()) return false;
1008
+ if (transitionProtocolActive && startedCommitted) {
1009
+ writeSandboxControlResponse(manifest, unknown(entry.name));
1010
+ continue;
1011
+ }
596
1012
  writeSandboxControlResponse(manifest, notExecuted(entry.name));
597
1013
  }
598
1014
  if (!brokerOwns()) return false;
@@ -634,6 +1050,7 @@ export async function serveSandboxControl(
634
1050
  const prepareExecution = options.prepareExecution ?? prepareSandboxControlExecution;
635
1051
  const manifest = readSandboxControlManifest(manifestPath);
636
1052
  const root = path.dirname(manifestPath);
1053
+ assertCurrentSandboxControlIdentity(manifest, manifestPath);
637
1054
  const startTime = getProcessStartTime(process.pid);
638
1055
  if (!startTime) throw new Error('SANDBOX_CONTROL_BROKER_IDENTITY_UNAVAILABLE');
639
1056
  const brokerId = randomUUID();
@@ -721,22 +1138,9 @@ export async function serveSandboxControl(
721
1138
  if (!brokerOwns()) return;
722
1139
  appendBrokerAudit(manifest, 'broker-start', {
723
1140
  pid: broker.pid,
724
- brokerId: broker.brokerId,
725
- hostCwd: process.cwd(),
726
- manifestPath,
727
- manifestPathRealpath: safeRealpath(manifestPath),
728
- repoRoot: manifest.repoRoot,
729
- repoRootRealpath: safeRealpath(manifest.repoRoot),
730
- worktreeRoot: manifest.worktreeRoot,
731
- worktreeRootRealpath: safeRealpath(manifest.worktreeRoot),
732
- runtimeDir: manifest.runtimeDir,
733
- runtimeDirRealpath: safeRealpath(manifest.runtimeDir),
734
- channelDir: manifest.channelDir,
735
- publicStatusDir: manifest.publicStatusDir,
736
- processingDir: manifest.processingDir,
737
- internalCliPath: options.internalCliPath ?? process.argv[1] ?? null
1141
+ brokerId: broker.brokerId
738
1142
  });
739
- if (!recoverProcessing(manifest, broker, brokerOwns)) return;
1143
+ if (!recoverProcessing(manifest, manifestPath, broker, brokerOwns)) return;
740
1144
  try {
741
1145
  const published = readSandboxControlStatus(manifest.publicStatusDir);
742
1146
  if (published.generation === manifest.generation) taskView = published.taskView;
@@ -749,6 +1153,7 @@ export async function serveSandboxControl(
749
1153
  let current: SandboxControlManifest;
750
1154
  try {
751
1155
  current = readSandboxControlManifest(manifestPath);
1156
+ assertCurrentSandboxControlIdentity(current, manifestPath);
752
1157
  } catch {
753
1158
  break;
754
1159
  }
@@ -779,7 +1184,7 @@ export async function serveSandboxControl(
779
1184
  nextContainerHeartbeatAt = Date.now() + timing.containerHeartbeatMs;
780
1185
  } else if (observation.state === 'unknown') {
781
1186
  containerReasonCode = 'SANDBOX_CONTROL_CONTAINER_UNKNOWN';
782
- appendSandboxControlAudit(manifest, 'container-heartbeat-unknown', { reason: observation.reason });
1187
+ appendDiagnosticAudit(manifest, 'container-heartbeat-unknown', { reason: observation.reason });
783
1188
  const backoff = nextSandboxControlBackoff(containerBackoffMs, timing.slowCheckMs);
784
1189
  nextContainerHeartbeatAt = Date.now() + backoff.delayMs;
785
1190
  containerBackoffMs = backoff.nextDelayMs;
@@ -795,7 +1200,7 @@ export async function serveSandboxControl(
795
1200
  } catch (error) {
796
1201
  if (!brokerOwns()) break;
797
1202
  containerReasonCode = 'SANDBOX_CONTROL_CONTAINER_ABSENT_GC_FAILED';
798
- appendSandboxControlAudit(manifest, 'container-gc-failed', { reason: controlError(error).code });
1203
+ appendDiagnosticAudit(manifest, 'container-gc-failed', { reason: controlError(error).code });
799
1204
  const backoff = nextSandboxControlBackoff(containerBackoffMs, timing.slowCheckMs);
800
1205
  nextContainerHeartbeatAt = Date.now() + backoff.delayMs;
801
1206
  containerBackoffMs = backoff.nextDelayMs;
@@ -858,6 +1263,10 @@ export async function serveSandboxControl(
858
1263
  if (!brokerOwns()) break;
859
1264
  let terminalCommitted = false;
860
1265
  if (settledExecution.result && settledExecution.resultEvidenceWritten) {
1266
+ if (!consumeLifecycleAuthorityForResult(manifest, settledExecution)) {
1267
+ active = settledExecution;
1268
+ continue;
1269
+ }
861
1270
  terminalCommitted = publishExecutionResult(manifest, settledExecution.request, settledExecution.result, broker, brokerOwns);
862
1271
  if (terminalCommitted && settledExecution.request.family === 'task-finalization') {
863
1272
  try {
@@ -872,7 +1281,7 @@ export async function serveSandboxControl(
872
1281
  }
873
1282
  if (!terminalCommitted) return;
874
1283
  appendBrokerAudit(manifest, 'executor-result-published', {
875
- ...requestAuditFields(manifest, manifestPath, settledExecution.request),
1284
+ ...requestAuditFields(manifest, settledExecution.request),
876
1285
  resultAvailable: settledExecution.result !== null,
877
1286
  resultEvidenceWritten: settledExecution.resultEvidenceWritten,
878
1287
  ...(settledExecution.result ? resultAuditFields(settledExecution.result) : {})
@@ -889,6 +1298,7 @@ export async function serveSandboxControl(
889
1298
  const source = path.join(requestsDir, name);
890
1299
  let claimed: string | null = null;
891
1300
  let validatedRequest: SandboxControlRequest | null = null;
1301
+ let prepared: PreparedSandboxControlExecution | null = null;
892
1302
  try {
893
1303
  if (!brokerOwns()) {
894
1304
  retiring = true;
@@ -914,69 +1324,84 @@ export async function serveSandboxControl(
914
1324
  if (active) throw new Error('SANDBOX_CONTROL_BUSY');
915
1325
  const request = validateSandboxControlRequest(JSON.parse(fs.readFileSync(claimed, 'utf8')), manifest);
916
1326
  validatedRequest = request;
1327
+ criticalRequestPhase(manifest, request, 'validated', 'in-progress');
917
1328
  appendBrokerAudit(manifest, 'request-validated', {
918
- ...requestAuditFields(manifest, manifestPath, request),
1329
+ ...requestAuditFields(manifest, request),
919
1330
  requestPath: claimed
920
1331
  });
921
1332
  if (bindingCheck(manifest)) throw new Error('SANDBOX_WORKTREE_BINDING_LOST');
922
1333
  if (readActiveLease(manifest)) throw new Error('SANDBOX_CONTROL_HANDOFF_ACTIVE');
1334
+ criticalRequestPhase(manifest, request, 'gated', 'in-progress');
923
1335
  appendBrokerAudit(manifest, 'request-gates-passed', {
924
- ...requestAuditFields(manifest, manifestPath, request),
1336
+ ...requestAuditFields(manifest, request),
925
1337
  requestPath: claimed,
926
1338
  bindingChecked: true,
927
1339
  handoffLeaseChecked: true
928
1340
  });
1341
+ criticalRequestPhase(manifest, request, 'reserved', 'in-progress');
929
1342
  writeSandboxControlReservation(manifest, request.id, sandboxControlGenerationUsage(manifest));
930
1343
  appendBrokerAudit(manifest, 'executor-reservation-written', {
931
- ...requestAuditFields(manifest, manifestPath, request),
1344
+ ...requestAuditFields(manifest, request),
932
1345
  requestPath: claimed,
933
1346
  executionPath: executionPath(manifest, request.id)
934
1347
  });
935
1348
  appendBrokerAudit(manifest, 'executor-prepare-start', {
936
- ...requestAuditFields(manifest, manifestPath, request),
1349
+ ...requestAuditFields(manifest, request),
937
1350
  requestPath: claimed,
938
1351
  executorCwd: manifest.repoRoot,
939
1352
  executorEntry: options.internalCliPath ?? process.argv[1] ?? null
940
1353
  });
941
- const prepared = await prepareExecution({
1354
+ const lifecycleAuthority = lifecycleAuthorityForRequest(manifest, manifestPath, request);
1355
+ if (lifecycleAuthority?.status === 'rejected') {
1356
+ throw Object.assign(
1357
+ new Error(lifecycleAuthority.error?.message ?? 'lifecycle authority was rejected'),
1358
+ { code: lifecycleAuthority.error?.code ?? 'LIFECYCLE_AUTHORITY_REJECTED' }
1359
+ );
1360
+ }
1361
+ prepared = await prepareExecution({
942
1362
  manifest, manifestPath, request, requestPath: claimed,
943
- internalCliPath: options.internalCliPath ?? process.argv[1]!
1363
+ internalCliPath: options.internalCliPath ?? process.argv[1]!,
1364
+ ...(lifecycleAuthority ? { lifecycleAuthority } : {})
944
1365
  });
1366
+ const preparedExecution = prepared;
1367
+ if (!preparedExecution) throw new Error('SANDBOX_CONTROL_EXECUTION_PREPARE_INVALID');
1368
+ criticalRequestPhase(manifest, request, 'prepared', 'in-progress', `${preparedExecution.execution.child.pid}`);
945
1369
  appendBrokerAudit(manifest, 'executor-prepared', {
946
- ...requestAuditFields(manifest, manifestPath, request),
1370
+ ...requestAuditFields(manifest, request),
947
1371
  requestPath: claimed,
948
1372
  executorCwd: manifest.repoRoot,
949
- childPid: prepared.execution.child.pid,
950
- childStartTime: prepared.execution.child.startTime,
951
- childProcessGroupId: prepared.execution.child.processGroupId,
1373
+ childPid: preparedExecution.execution.child.pid,
1374
+ childStartTime: preparedExecution.execution.child.startTime,
1375
+ childProcessGroupId: preparedExecution.execution.child.processGroupId,
952
1376
  executionPath: executionPath(manifest, request.id)
953
1377
  });
954
1378
  const execution: ActiveExecution = {
955
- request, prepared, result: null, resultEvidenceWritten: false, failure: null, settled: false
1379
+ request, prepared: preparedExecution, lifecycleAuthority, result: null,
1380
+ resultEvidenceWritten: false, failure: null, settled: false
956
1381
  };
957
1382
  active = execution;
958
- prepared.completion.then(
1383
+ preparedExecution.completion.then(
959
1384
  (result) => {
960
1385
  execution.result = sanitizeSandboxControlResult(manifest, result);
961
1386
  appendBrokerAudit(manifest, 'executor-completed', {
962
- ...requestAuditFields(manifest, manifestPath, request),
1387
+ ...requestAuditFields(manifest, request),
963
1388
  ...resultAuditFields(execution.result),
964
- childPid: prepared.execution.child.pid,
965
- childStartTime: prepared.execution.child.startTime
1389
+ childPid: preparedExecution.execution.child.pid,
1390
+ childStartTime: preparedExecution.execution.child.startTime
966
1391
  });
967
1392
  try {
968
1393
  writeSandboxControlResultEvidence(manifest, request.id, execution.result);
969
1394
  execution.resultEvidenceWritten = true;
970
1395
  execution.settled = true;
971
1396
  appendBrokerAudit(manifest, 'executor-result-evidence-written', {
972
- ...requestAuditFields(manifest, manifestPath, request),
1397
+ ...requestAuditFields(manifest, request),
973
1398
  resultPath: path.join(manifest.processingDir, request.id, 'result.json'),
974
1399
  ...resultAuditFields(execution.result)
975
1400
  });
976
1401
  } catch (error) {
977
1402
  execution.failure = error;
978
1403
  appendBrokerAudit(manifest, 'executor-result-evidence-failed', {
979
- ...requestAuditFields(manifest, manifestPath, request),
1404
+ ...requestAuditFields(manifest, request),
980
1405
  errorType: error instanceof Error ? error.name : typeof error,
981
1406
  });
982
1407
  }
@@ -985,9 +1410,9 @@ export async function serveSandboxControl(
985
1410
  execution.failure = error;
986
1411
  execution.settled = true;
987
1412
  appendBrokerAudit(manifest, 'executor-failed', {
988
- ...requestAuditFields(manifest, manifestPath, request),
989
- childPid: prepared.execution.child.pid,
990
- childStartTime: prepared.execution.child.startTime,
1413
+ ...requestAuditFields(manifest, request),
1414
+ childPid: preparedExecution.execution.child.pid,
1415
+ childStartTime: preparedExecution.execution.child.startTime,
991
1416
  errorType: error instanceof Error ? error.name : typeof error,
992
1417
  });
993
1418
  }
@@ -1004,11 +1429,13 @@ export async function serveSandboxControl(
1004
1429
  retiring = true;
1005
1430
  continue;
1006
1431
  }
1432
+ criticalRequestPhase(manifest, request, 'accepted-authorized', 'in-progress');
1007
1433
  writeAcceptedResponse(manifest, {
1008
1434
  version: 2, id, phase: 'accepted', exitCode: null, stdout: '', stderr: '', error: null
1009
1435
  });
1436
+ writeSandboxControlTransition(manifest, { requestId: request.id, phase: 'accepted-committed' });
1010
1437
  appendBrokerAudit(manifest, 'request-accepted', {
1011
- ...requestAuditFields(manifest, manifestPath, request),
1438
+ ...requestAuditFields(manifest, request),
1012
1439
  acceptedPath: acceptedResponsePath(manifest, id),
1013
1440
  executionPhase: 'prepared'
1014
1441
  });
@@ -1019,8 +1446,9 @@ export async function serveSandboxControl(
1019
1446
  break;
1020
1447
  }
1021
1448
  try {
1449
+ criticalRequestPhase(manifest, request, 'start-authorized', 'in-progress', `${prepared.execution.child.pid}`);
1022
1450
  appendBrokerAudit(manifest, 'executor-start', {
1023
- ...requestAuditFields(manifest, manifestPath, request),
1451
+ ...requestAuditFields(manifest, request),
1024
1452
  childPid: prepared.execution.child.pid,
1025
1453
  childStartTime: prepared.execution.child.startTime,
1026
1454
  executionPath: executionPath(manifest, request.id)
@@ -1028,7 +1456,7 @@ export async function serveSandboxControl(
1028
1456
  prepared.start(brokerOwns);
1029
1457
  } catch (error) {
1030
1458
  appendBrokerAudit(manifest, 'executor-start-failed', {
1031
- ...requestAuditFields(manifest, manifestPath, request),
1459
+ ...requestAuditFields(manifest, request),
1032
1460
  errorType: error instanceof Error ? error.name : typeof error,
1033
1461
  });
1034
1462
  const owned = brokerOwns();
@@ -1056,6 +1484,11 @@ export async function serveSandboxControl(
1056
1484
  continue;
1057
1485
  }
1058
1486
  } catch (error) {
1487
+ if (prepared) {
1488
+ try { prepared.terminate(brokerOwns()); } catch { /* cleanup is best effort after a failed admission */ }
1489
+ prepared = null;
1490
+ active = null;
1491
+ }
1059
1492
  if (!brokerOwns()) {
1060
1493
  retiring = true;
1061
1494
  break;
@@ -1075,6 +1508,7 @@ export async function serveSandboxControl(
1075
1508
  retiring = true;
1076
1509
  break;
1077
1510
  }
1511
+ removeAcceptedResponse(manifest, id);
1078
1512
  if (claimed) fs.rmSync(path.dirname(claimed), { recursive: true, force: true });
1079
1513
  }
1080
1514
  }
@@ -1096,7 +1530,8 @@ export async function serveSandboxControl(
1096
1530
  }
1097
1531
  }
1098
1532
  if (owned && active.result && active.resultEvidenceWritten) {
1099
- if (publishExecutionResult(manifest, active.request, active.result, broker, brokerOwns)) {
1533
+ if (consumeLifecycleAuthorityForResult(manifest, active)
1534
+ && publishExecutionResult(manifest, active.request, active.result, broker, brokerOwns)) {
1100
1535
  if (brokerOwns()) {
1101
1536
  removeAcceptedResponse(manifest, active.request.id);
1102
1537
  fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
@@ -1126,7 +1561,7 @@ export async function serveSandboxControl(
1126
1561
  }
1127
1562
  }
1128
1563
  if (brokerOwns()) {
1129
- appendSandboxControlAudit(manifest, 'broker-stop', { pid: broker.pid, brokerId: broker.brokerId });
1564
+ appendDiagnosticAudit(manifest, 'broker-stop', { pid: broker.pid, brokerId: broker.brokerId });
1130
1565
  try {
1131
1566
  if (fs.readFileSync(brokerPath, 'utf8') === brokerRecord) fs.unlinkSync(brokerPath);
1132
1567
  } catch {