@fitlab-ai/agent-infra 0.9.13 → 0.9.15

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 (428) hide show
  1. package/README.md +10 -0
  2. package/README.zh-CN.md +10 -0
  3. package/bin/cli.ts +26 -9
  4. package/bin/internal-cli.sh +45 -0
  5. package/dist/bin/cli.js +173 -157
  6. package/dist/bin/internal-cli.js +126 -42
  7. package/dist/lib/agent-client.js +5 -0
  8. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
  9. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
  10. package/dist/lib/defaults.json +103 -103
  11. package/dist/lib/fs/durable-write.js +37 -0
  12. package/dist/lib/fs/file-lock.js +111 -0
  13. package/dist/lib/host-control/audit.js +19 -0
  14. package/dist/lib/host-control/client.js +142 -0
  15. package/dist/lib/host-control/command.js +49 -0
  16. package/dist/lib/host-control/path.js +160 -0
  17. package/dist/lib/host-control/secure-fs.js +140 -0
  18. package/dist/lib/host-control/server.js +159 -0
  19. package/dist/lib/host-control/service.js +112 -0
  20. package/dist/lib/init.js +11 -2
  21. package/dist/lib/internal/agent-client.js +5 -2
  22. package/dist/lib/internal/cli-route-inventory.js +154 -0
  23. package/dist/lib/internal/codex-lifecycle.js +15 -12
  24. package/dist/lib/internal/codex-sandbox-controller.js +8 -2
  25. package/dist/lib/internal/demo-promotion.js +322 -0
  26. package/dist/lib/internal/demo-transcript.js +454 -0
  27. package/dist/lib/internal/git-workflow.js +10 -6
  28. package/dist/lib/internal/host-control.js +28 -0
  29. package/dist/lib/internal/manual-validation.js +322 -0
  30. package/dist/lib/internal/platform-checks.js +16 -4
  31. package/dist/lib/internal/platform-comment.js +16 -4
  32. package/dist/lib/internal/platform-context.js +4 -1
  33. package/dist/lib/internal/platform-issue.js +12 -4
  34. package/dist/lib/internal/platform-metadata.js +88 -0
  35. package/dist/lib/internal/platform-pr-review.js +16 -3
  36. package/dist/lib/internal/platform-pr.js +42 -14
  37. package/dist/lib/internal/platform-release-notes.js +6 -3
  38. package/dist/lib/internal/platform-security.js +93 -0
  39. package/dist/lib/internal/pr-review-grade.js +14 -3
  40. package/dist/lib/internal/release-workflow.js +82 -89
  41. package/dist/lib/internal/sandbox-control.js +171 -7
  42. package/dist/lib/internal/task-activity.js +13 -4
  43. package/dist/lib/internal/task-artifact.js +19 -107
  44. package/dist/lib/internal/task-context.js +4 -1
  45. package/dist/lib/internal/task-create.js +48 -14
  46. package/dist/lib/internal/task-delivery.js +4 -1
  47. package/dist/lib/internal/task-event.js +38 -35
  48. package/dist/lib/internal/task-finalization.js +4 -1
  49. package/dist/lib/internal/task-invalidation.js +4 -1
  50. package/dist/lib/internal/task-ledger.js +12 -1
  51. package/dist/lib/internal/task-lifecycle.js +4 -1
  52. package/dist/lib/internal/task-operation-registry.js +519 -0
  53. package/dist/lib/internal/task-orchestration.js +12 -0
  54. package/dist/lib/internal/task-override.js +10 -3
  55. package/dist/lib/internal/task-qualification.js +90 -0
  56. package/dist/lib/internal/task-review.js +10 -52
  57. package/dist/lib/internal/task-short-id.js +11 -1
  58. package/dist/lib/internal/task-snapshot.js +4 -1
  59. package/dist/lib/internal/task-validate.js +37 -12
  60. package/dist/lib/internal/task-verify.js +5 -1
  61. package/dist/lib/internal/task-warning.js +8 -1
  62. package/dist/lib/owned-path.js +33 -0
  63. package/dist/lib/platform/check-status.js +11 -0
  64. package/dist/lib/platform/comment-safety.js +229 -0
  65. package/dist/lib/platform/completion-backfill.js +1 -1
  66. package/dist/lib/platform/github-data.js +241 -0
  67. package/dist/lib/platform/github-provider.js +248 -26
  68. package/dist/lib/platform/issue-comments.js +146 -34
  69. package/dist/lib/platform/issues.js +1 -179
  70. package/dist/lib/platform/pr-change-report.js +6 -12
  71. package/dist/lib/platform/pr-checks.js +12 -65
  72. package/dist/lib/platform/pr-summary.js +83 -10
  73. package/dist/lib/platform/provider-contract.d.ts +67 -1
  74. package/dist/lib/platform/provider-contract.js +6 -2
  75. package/dist/lib/platform/provider-validation.js +76 -3
  76. package/dist/lib/platform/pull-requests.js +117 -275
  77. package/dist/lib/platform/repository-metadata.js +377 -0
  78. package/dist/lib/platform/security-alerts.js +155 -0
  79. package/dist/lib/platform/snapshots.js +2 -0
  80. package/dist/lib/platform/verification-sync.js +154 -162
  81. package/dist/lib/process-data/index.js +7 -0
  82. package/dist/lib/release/workflow.js +1 -1
  83. package/dist/lib/run/index.js +5 -0
  84. package/dist/lib/sandbox/commands/control-status.js +22 -0
  85. package/dist/lib/sandbox/commands/create.js +39 -8
  86. package/dist/lib/sandbox/commands/ls.js +5 -0
  87. package/dist/lib/sandbox/commands/rm.js +3 -807
  88. package/dist/lib/sandbox/commands/show.js +16 -0
  89. package/dist/lib/sandbox/control/audit.js +203 -0
  90. package/dist/lib/sandbox/control/client.js +125 -12
  91. package/dist/lib/sandbox/control/completed-reentry.js +85 -0
  92. package/dist/lib/sandbox/control/container-identity.js +28 -31
  93. package/dist/lib/sandbox/control/executor.js +233 -34
  94. package/dist/lib/sandbox/control/finalization-response.js +27 -0
  95. package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
  96. package/dist/lib/sandbox/control/lifecycle.js +656 -128
  97. package/dist/lib/sandbox/control/native-file-lock.js +85 -0
  98. package/dist/lib/sandbox/control/protocol.js +55 -4
  99. package/dist/lib/sandbox/control/server.js +720 -79
  100. package/dist/lib/sandbox/control/state.js +63 -3
  101. package/dist/lib/sandbox/control/task-view.js +183 -0
  102. package/dist/lib/sandbox/control/task-workflow.js +74 -0
  103. package/dist/lib/sandbox/control/workflow-executor.js +99 -0
  104. package/dist/lib/sandbox/engines/authority.js +289 -0
  105. package/dist/lib/sandbox/engines/docker-exact-discovery.js +97 -0
  106. package/dist/lib/sandbox/index.js +7 -2
  107. package/dist/lib/sandbox/recovery.js +49 -5
  108. package/dist/lib/sandbox/removal.js +1717 -0
  109. package/dist/lib/sandbox/task-cleanup.js +217 -0
  110. package/dist/lib/sandbox/workspace-view.js +15 -15
  111. package/dist/lib/sandbox/worktree-safety.js +4 -1
  112. package/dist/lib/server/index.js +8 -0
  113. package/dist/lib/task/activity-intent.js +2 -1
  114. package/dist/lib/task/activity-log.js +30 -24
  115. package/dist/lib/task/artifact-command.js +101 -0
  116. package/dist/lib/task/artifact-lifecycle.js +71 -40
  117. package/dist/lib/task/artifact-name.js +36 -0
  118. package/dist/lib/task/artifact-operations.js +474 -0
  119. package/dist/lib/task/artifact-receipts.js +3 -15
  120. package/dist/lib/task/artifact-repair-intent.js +130 -0
  121. package/dist/lib/task/artifact-schema.js +127 -0
  122. package/dist/lib/task/capabilities.js +83 -19
  123. package/dist/lib/task/codex-orchestration.js +71 -25
  124. package/dist/lib/task/commit-identity.js +28 -0
  125. package/dist/lib/task/commit-operation.js +2 -16
  126. package/dist/lib/task/control-authority.js +375 -5
  127. package/dist/lib/task/control-recovery.js +128 -0
  128. package/dist/lib/task/create-service.js +103 -4
  129. package/dist/lib/task/create.js +80 -2
  130. package/dist/lib/task/decision-details.js +2 -46
  131. package/dist/lib/task/events.js +306 -76
  132. package/dist/lib/task/finalization-state.js +9 -0
  133. package/dist/lib/task/human-override.js +3 -6
  134. package/dist/lib/task/implementation-inputs.js +3 -3
  135. package/dist/lib/task/index.js +11 -3
  136. package/dist/lib/task/intermediate-cleanup.js +383 -0
  137. package/dist/lib/task/invalidation.js +18 -65
  138. package/dist/lib/task/ledger-intents.js +7 -22
  139. package/dist/lib/task/ledger.js +1 -3
  140. package/dist/lib/task/lifecycle.js +19 -1
  141. package/dist/lib/task/local-artifact-finalization.js +190 -290
  142. package/dist/lib/task/manual-validation-completion.js +26 -0
  143. package/dist/lib/task/manual-validation-evidence.js +130 -0
  144. package/dist/lib/task/manual-validation-receipt.js +101 -0
  145. package/dist/lib/task/manual-validation-shared.js +40 -0
  146. package/dist/lib/task/manual-validation-transaction.js +243 -0
  147. package/dist/lib/task/markdown.js +47 -0
  148. package/dist/lib/task/orchestration.js +206 -69
  149. package/dist/lib/task/qualification-audit.js +476 -0
  150. package/dist/lib/task/qualification-confirmation.js +109 -0
  151. package/dist/lib/task/qualification-intents.js +148 -0
  152. package/dist/lib/task/review-artifacts.js +23 -40
  153. package/dist/lib/task/review-command.js +25 -0
  154. package/dist/lib/task/review-finalization.js +146 -97
  155. package/dist/lib/task/review-fingerprint.js +2 -2
  156. package/dist/lib/task/sections.js +34 -54
  157. package/dist/lib/task/verification-config.js +11 -0
  158. package/dist/lib/task/verification-engine.js +170 -327
  159. package/dist/lib/task/verification.js +1 -1
  160. package/dist/lib/task/workflow-command.js +17 -0
  161. package/dist/lib/task/workflow-warning-intents.js +0 -16
  162. package/dist/lib/task/workflow-warnings.js +28 -2
  163. package/dist/lib/update.js +2 -0
  164. package/dist/lifecycle-build-manifest.json +3 -3
  165. package/lib/agent-client.ts +9 -0
  166. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
  167. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
  168. package/lib/fs/durable-write.ts +29 -0
  169. package/lib/fs/file-lock.ts +113 -0
  170. package/lib/host-control/audit.ts +22 -0
  171. package/lib/host-control/client.ts +172 -0
  172. package/lib/host-control/command.ts +60 -0
  173. package/lib/host-control/path.ts +167 -0
  174. package/lib/host-control/secure-fs.ts +135 -0
  175. package/lib/host-control/server.ts +180 -0
  176. package/lib/host-control/service.ts +133 -0
  177. package/lib/init.ts +15 -2
  178. package/lib/internal/agent-client.ts +4 -2
  179. package/lib/internal/cli-route-inventory.ts +142 -0
  180. package/lib/internal/codex-lifecycle.ts +14 -12
  181. package/lib/internal/codex-sandbox-controller.ts +7 -2
  182. package/lib/internal/demo-promotion.ts +354 -0
  183. package/lib/internal/demo-transcript.ts +419 -0
  184. package/lib/internal/git-workflow.ts +9 -6
  185. package/lib/internal/host-control.ts +28 -0
  186. package/lib/internal/manual-validation.ts +306 -0
  187. package/lib/internal/platform-checks.ts +12 -4
  188. package/lib/internal/platform-comment.ts +12 -4
  189. package/lib/internal/platform-context.ts +3 -1
  190. package/lib/internal/platform-issue.ts +11 -4
  191. package/lib/internal/platform-metadata.ts +70 -0
  192. package/lib/internal/platform-pr-review.ts +14 -3
  193. package/lib/internal/platform-pr.ts +38 -14
  194. package/lib/internal/platform-release-notes.ts +5 -3
  195. package/lib/internal/platform-security.ts +83 -0
  196. package/lib/internal/pr-review-grade.ts +10 -3
  197. package/lib/internal/release-workflow.ts +91 -87
  198. package/lib/internal/sandbox-control.ts +181 -6
  199. package/lib/internal/task-activity.ts +12 -4
  200. package/lib/internal/task-artifact.ts +15 -79
  201. package/lib/internal/task-context.ts +3 -1
  202. package/lib/internal/task-create.ts +76 -15
  203. package/lib/internal/task-delivery.ts +3 -1
  204. package/lib/internal/task-event.ts +21 -10
  205. package/lib/internal/task-finalization.ts +3 -1
  206. package/lib/internal/task-invalidation.ts +3 -1
  207. package/lib/internal/task-ledger.ts +11 -1
  208. package/lib/internal/task-lifecycle.ts +3 -1
  209. package/lib/internal/task-operation-registry.ts +590 -0
  210. package/lib/internal/task-orchestration.ts +12 -0
  211. package/lib/internal/task-override.ts +9 -3
  212. package/lib/internal/task-qualification.ts +61 -0
  213. package/lib/internal/task-review.ts +7 -50
  214. package/lib/internal/task-short-id.ts +10 -1
  215. package/lib/internal/task-snapshot.ts +3 -1
  216. package/lib/internal/task-validate.ts +32 -12
  217. package/lib/internal/task-verify.ts +4 -1
  218. package/lib/internal/task-warning.ts +7 -1
  219. package/lib/owned-path.ts +26 -0
  220. package/lib/platform/check-status.ts +9 -0
  221. package/lib/platform/comment-safety.ts +255 -0
  222. package/lib/platform/completion-backfill.ts +1 -1
  223. package/lib/platform/github-data.ts +322 -0
  224. package/lib/platform/github-provider.ts +244 -27
  225. package/lib/platform/issue-comments.ts +159 -35
  226. package/lib/platform/issues.ts +4 -245
  227. package/lib/platform/pr-change-report.ts +6 -12
  228. package/lib/platform/pr-checks.ts +13 -77
  229. package/lib/platform/pr-summary.ts +88 -11
  230. package/lib/platform/provider-contract.ts +78 -3
  231. package/lib/platform/provider-validation.ts +85 -3
  232. package/lib/platform/pull-requests.ts +129 -348
  233. package/lib/platform/repository-metadata.ts +453 -0
  234. package/lib/platform/security-alerts.ts +191 -0
  235. package/lib/platform/snapshots.ts +53 -0
  236. package/lib/platform/verification-sync.ts +171 -177
  237. package/lib/process-data/index.ts +7 -0
  238. package/lib/release/workflow.ts +3 -3
  239. package/lib/run/index.ts +5 -0
  240. package/lib/sandbox/commands/control-status.ts +26 -0
  241. package/lib/sandbox/commands/create.ts +53 -12
  242. package/lib/sandbox/commands/ls.ts +7 -0
  243. package/lib/sandbox/commands/rm.ts +3 -996
  244. package/lib/sandbox/commands/show.ts +16 -0
  245. package/lib/sandbox/control/audit.ts +253 -0
  246. package/lib/sandbox/control/client.ts +150 -13
  247. package/lib/sandbox/control/completed-reentry.ts +98 -0
  248. package/lib/sandbox/control/container-identity.ts +27 -43
  249. package/lib/sandbox/control/executor.ts +262 -37
  250. package/lib/sandbox/control/finalization-response.ts +29 -0
  251. package/lib/sandbox/control/identity-sentinel.ts +137 -0
  252. package/lib/sandbox/control/lifecycle.ts +672 -18
  253. package/lib/sandbox/control/native-file-lock.ts +108 -0
  254. package/lib/sandbox/control/protocol.ts +80 -8
  255. package/lib/sandbox/control/server.ts +779 -82
  256. package/lib/sandbox/control/state.ts +84 -3
  257. package/lib/sandbox/control/task-view.ts +240 -0
  258. package/lib/sandbox/control/task-workflow.ts +95 -0
  259. package/lib/sandbox/control/workflow-executor.ts +102 -0
  260. package/lib/sandbox/engines/authority.ts +365 -0
  261. package/lib/sandbox/engines/docker-exact-discovery.ts +132 -0
  262. package/lib/sandbox/index.ts +9 -2
  263. package/lib/sandbox/recovery.ts +49 -6
  264. package/lib/sandbox/removal.ts +2144 -0
  265. package/lib/sandbox/task-cleanup.ts +209 -0
  266. package/lib/sandbox/workspace-view.ts +26 -18
  267. package/lib/sandbox/worktree-safety.ts +4 -1
  268. package/lib/server/index.ts +10 -0
  269. package/lib/task/activity-intent.ts +2 -2
  270. package/lib/task/activity-log.ts +27 -19
  271. package/lib/task/artifact-command.ts +104 -0
  272. package/lib/task/artifact-lifecycle.ts +66 -39
  273. package/lib/task/artifact-name.ts +55 -0
  274. package/lib/task/artifact-operations.ts +580 -0
  275. package/lib/task/artifact-receipts.ts +3 -14
  276. package/lib/task/artifact-repair-intent.ts +152 -0
  277. package/lib/task/artifact-schema.ts +163 -0
  278. package/lib/task/capabilities.ts +82 -22
  279. package/lib/task/codex-orchestration.ts +85 -24
  280. package/lib/task/commit-identity.ts +31 -0
  281. package/lib/task/commit-operation.ts +2 -16
  282. package/lib/task/control-authority.ts +513 -6
  283. package/lib/task/control-recovery.ts +174 -0
  284. package/lib/task/create-service.ts +131 -6
  285. package/lib/task/create.ts +83 -1
  286. package/lib/task/decision-details.ts +3 -71
  287. package/lib/task/events.ts +292 -75
  288. package/lib/task/finalization-state.ts +11 -0
  289. package/lib/task/human-override.ts +3 -8
  290. package/lib/task/implementation-inputs.ts +3 -3
  291. package/lib/task/index.ts +16 -3
  292. package/lib/task/intermediate-cleanup.ts +455 -0
  293. package/lib/task/invalidation.ts +18 -62
  294. package/lib/task/ledger-intents.ts +7 -24
  295. package/lib/task/ledger.ts +1 -3
  296. package/lib/task/lifecycle.ts +17 -1
  297. package/lib/task/local-artifact-finalization.ts +223 -388
  298. package/lib/task/manual-validation-completion.ts +40 -0
  299. package/lib/task/manual-validation-evidence.ts +209 -0
  300. package/lib/task/manual-validation-receipt.ts +152 -0
  301. package/lib/task/manual-validation-shared.ts +50 -0
  302. package/lib/task/manual-validation-transaction.ts +304 -0
  303. package/lib/task/markdown.ts +70 -0
  304. package/lib/task/orchestration.ts +191 -68
  305. package/lib/task/qualification-audit.ts +588 -0
  306. package/lib/task/qualification-confirmation.ts +117 -0
  307. package/lib/task/qualification-intents.ts +150 -0
  308. package/lib/task/review-artifacts.ts +21 -41
  309. package/lib/task/review-command.ts +23 -0
  310. package/lib/task/review-finalization.ts +154 -153
  311. package/lib/task/review-fingerprint.ts +2 -2
  312. package/lib/task/sections.ts +33 -52
  313. package/lib/task/verification-config.ts +12 -0
  314. package/lib/task/verification-engine.ts +165 -370
  315. package/lib/task/verification-types.ts +17 -0
  316. package/lib/task/verification.ts +1 -1
  317. package/lib/task/workflow-command.ts +18 -0
  318. package/lib/task/workflow-warning-intents.ts +0 -13
  319. package/lib/task/workflow-warnings.ts +21 -2
  320. package/lib/update.ts +6 -0
  321. package/package.json +7 -2
  322. package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
  323. package/templates/.agents/rules/README.en.md +2 -0
  324. package/templates/.agents/rules/README.zh-CN.md +2 -0
  325. package/templates/.agents/rules/decision-qualification.en.md +22 -0
  326. package/templates/.agents/rules/decision-qualification.zh-CN.md +22 -0
  327. package/templates/.agents/rules/evidence-reporting.en.md +28 -0
  328. package/templates/.agents/rules/evidence-reporting.zh-CN.md +28 -0
  329. package/templates/.agents/rules/label-milestone-setup.en.md +7 -7
  330. package/templates/.agents/rules/label-milestone-setup.zh-CN.md +7 -7
  331. package/templates/.agents/rules/local-artifact-repair.en.md +5 -1
  332. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +5 -1
  333. package/templates/.agents/rules/security-alerts.en.md +8 -8
  334. package/templates/.agents/rules/security-alerts.zh-CN.md +8 -8
  335. package/templates/.agents/rules/testing-discipline.en.md +2 -0
  336. package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
  337. package/templates/.agents/scripts/package.json +3 -0
  338. package/templates/.agents/skills/analyze-task/SKILL.en.md +15 -2
  339. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +19 -2
  340. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -12
  341. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -12
  342. package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
  343. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
  344. package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
  345. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
  346. package/templates/.agents/skills/code-task/SKILL.en.md +14 -1
  347. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +14 -1
  348. package/templates/.agents/skills/code-task/config/verify.en.json +5 -14
  349. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +5 -14
  350. package/templates/.agents/skills/code-task/reference/code-rules.en.md +5 -4
  351. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +5 -4
  352. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
  353. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
  354. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +3 -1
  355. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +3 -1
  356. package/templates/.agents/skills/code-task/reference/report-template.en.md +42 -15
  357. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +39 -12
  358. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +37 -21
  359. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +37 -21
  360. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  361. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  362. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +4 -0
  363. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +4 -0
  364. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
  365. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
  366. package/templates/.agents/skills/complete-task/SKILL.en.md +10 -23
  367. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +10 -23
  368. package/templates/.agents/skills/create-task/SKILL.en.md +8 -0
  369. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +7 -0
  370. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  371. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  372. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  373. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  374. package/templates/.agents/skills/init-labels/SKILL.en.md +8 -8
  375. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +8 -8
  376. package/templates/.agents/skills/init-milestones/SKILL.en.md +9 -11
  377. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +9 -11
  378. package/templates/.agents/skills/plan-task/SKILL.en.md +14 -1
  379. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +16 -1
  380. package/templates/.agents/skills/plan-task/config/verify.en.json +4 -14
  381. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +4 -14
  382. package/templates/.agents/skills/release/SKILL.en.md +7 -3
  383. package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
  384. package/templates/.agents/skills/review-analysis/SKILL.en.md +14 -1
  385. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -1
  386. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -18
  387. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -18
  388. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +50 -23
  389. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +45 -18
  390. package/templates/.agents/skills/review-code/SKILL.en.md +14 -1
  391. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +14 -1
  392. package/templates/.agents/skills/review-code/config/verify.en.json +1 -23
  393. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -23
  394. package/templates/.agents/skills/review-code/reference/report-template.en.md +49 -22
  395. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +45 -18
  396. package/templates/.agents/skills/review-plan/SKILL.en.md +14 -1
  397. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -1
  398. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -18
  399. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -18
  400. package/templates/.agents/skills/review-plan/reference/report-template.en.md +49 -22
  401. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +45 -18
  402. package/templates/.agents/skills/review-pr/SKILL.en.md +5 -1
  403. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +5 -1
  404. package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -3
  405. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -3
  406. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +25 -4
  407. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +25 -4
  408. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
  409. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
  410. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +5 -1
  411. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +5 -1
  412. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  413. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  414. package/templates/.agents/skills/test/SKILL.en.md +29 -7
  415. package/templates/.agents/skills/test/SKILL.zh-CN.md +29 -7
  416. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +14 -0
  417. package/templates/.agents/templates/task.en.md +6 -0
  418. package/templates/.agents/templates/task.zh-CN.md +6 -0
  419. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  420. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  421. package/dist/lib/platform/adapters.js +0 -56
  422. package/lib/platform/adapters.ts +0 -197
  423. package/templates/.agents/scripts/security-alerts.github.sh +0 -151
  424. package/templates/.agents/scripts/security-alerts.sh +0 -31
  425. package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +0 -146
  426. package/templates/.agents/skills/init-labels/scripts/init-labels.sh +0 -32
  427. package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +0 -294
  428. package/templates/.agents/skills/init-milestones/scripts/init-milestones.sh +0 -6
@@ -1,3 +1,6 @@
1
+ import { finalizationTerminalResponse } from './finalization-response.ts';
2
+ import { isDeepStrictEqual } from 'node:util';
3
+ import { completedReentryView } from './completed-reentry.ts';
1
4
  import fs from 'node:fs';
2
5
  import path from 'node:path';
3
6
  import { createHash, randomUUID } from 'node:crypto';
@@ -15,7 +18,6 @@ import {
15
18
  } from './protocol.ts';
16
19
  import { prepareSandboxControlExecution, type PreparedSandboxControlExecution, type SandboxControlExecutionResult } from './executor.ts';
17
20
  import {
18
- appendSandboxControlAudit,
19
21
  atomicWriteJsonNoReplace,
20
22
  createSandboxControlPayload,
21
23
  cleanupStaleSandboxControlLease,
@@ -25,6 +27,8 @@ import {
25
27
  readExecution,
26
28
  readSandboxControlPayload,
27
29
  readSandboxControlResultEvidence,
30
+ readSandboxControlTerminalResult,
31
+ createSandboxControlTerminalResult,
28
32
  sandboxControlEncodedJsonBytes,
29
33
  sandboxControlGenerationUsage,
30
34
  sanitizeSandboxControlOutput,
@@ -33,10 +37,36 @@ import {
33
37
  writeSandboxControlPayload,
34
38
  writeSandboxControlReservation,
35
39
  writeSandboxControlResultEvidence,
40
+ writeSandboxControlTerminalResult,
41
+ appendSandboxControlAudit,
36
42
  terminateSandboxControlExecution,
37
- writeSandboxControlStatus
43
+ writeSandboxControlStatus,
44
+ readSandboxControlStatus
38
45
  } from './state.ts';
39
46
  import { validateSandboxControlRequest } from './protocol.ts';
47
+ import {
48
+ requestAuditFields,
49
+ resultAuditFields,
50
+ appendCriticalAudit,
51
+ appendDiagnosticAudit,
52
+ createSandboxControlAuditContext,
53
+ writeSandboxControlTransition,
54
+ readSandboxControlTransition
55
+ } from './audit.ts';
56
+ import { parseTaskControlOperation } from '../../task/control-authority.ts';
57
+ import {
58
+ parseControlOutput,
59
+ classifySandboxControlRecovery,
60
+ findSandboxControlRecoveryOperation,
61
+ operationRecoveryBinding,
62
+ SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES
63
+ } from '../../task/control-recovery.ts';
64
+ import { readRun } from '../../task/orchestration.ts';
65
+ import { captureRepositorySnapshot } from '../../task/workspace-snapshot.ts';
66
+ import { parseTypedTaskFrontmatter } from '../../task/frontmatter.ts';
67
+ import { readLifecycleJournalEvidence } from '../../task/lifecycle.ts';
68
+ import { resolveTaskRef } from '../../task/resolve-ref.ts';
69
+ import { loadShortIdByTaskId } from '../../task/short-id.ts';
40
70
  import { inspectSandboxControlContainer, type ContainerObservation } from './container-identity.ts';
41
71
  import {
42
72
  acquireSandboxControlBrokerStartup,
@@ -47,17 +77,151 @@ import {
47
77
  import type { BrokerOwner } from './lifecycle.ts';
48
78
  import { nextSandboxControlBackoff } from './timing.ts';
49
79
  import { readTaskFinalizationReceipt } from '../../task/finalization.ts';
50
- import { resolveTaskRef } from '../../task/resolve-ref.ts';
80
+ import { readCodexControllerRegistration } from './controller-registration.ts';
81
+ import { validateSandboxControlIdentity } from './identity-sentinel.ts';
82
+ import {
83
+ mergeSandboxTaskView,
84
+ taskViewAfterFinalization,
85
+ taskViewForManifest,
86
+ type SandboxTaskView
87
+ } from './task-view.ts';
88
+ import { taskCreateOutputUnavailableResult } from '../../task/create-service.ts';
89
+ import {
90
+ consumeLifecycleRecoveryAttestation,
91
+ issueLifecycleRecoveryAttestation,
92
+ recoverLifecycleRecoveryOperation,
93
+ type LifecycleAuthorityResponseV1
94
+ } from '../../task/control-authority.ts';
95
+ import { createCodexCapabilityStore } from '../../agent-clients/adapters/codex-lifecycle/capability-store.ts';
96
+ import { computeLifecycleBuildIdentity } from '../../agent-clients/adapters/codex-lifecycle/build-identity.ts';
51
97
 
52
98
  type ActiveExecution = {
53
99
  request: SandboxControlRequest;
54
100
  prepared: PreparedSandboxControlExecution;
101
+ lifecycleAuthority: LifecycleAuthorityResponseV1 | undefined;
55
102
  result: SandboxControlExecutionResult | null;
56
103
  resultEvidenceWritten: boolean;
57
104
  failure: unknown;
58
105
  settled: boolean;
59
106
  };
60
107
 
108
+ function consumeLifecycleAuthorityForResult(
109
+ manifest: SandboxControlManifest,
110
+ execution: ActiveExecution
111
+ ): boolean {
112
+ const authority = execution.lifecycleAuthority?.attestation;
113
+ if (!authority || execution.result?.exitCode !== 0) return true;
114
+ try {
115
+ consumeLifecycleRecoveryAttestation(authority);
116
+ return true;
117
+ } catch (error) {
118
+ appendBrokerAudit(manifest, 'lifecycle-authority-consume-retry', {
119
+ ...requestAuditFields(manifest, execution.request),
120
+ errorCode: error instanceof Error ? error.name : 'LIFECYCLE_AUTHORITY_CONSUME_FAILED'
121
+ });
122
+ return false;
123
+ }
124
+ }
125
+
126
+ function lifecycleAuthorityForRequest(
127
+ manifest: SandboxControlManifest,
128
+ manifestPath: string,
129
+ request: SandboxControlRequest
130
+ ): LifecycleAuthorityResponseV1 | undefined {
131
+ const selector = request.family === 'task-workflow' || request.family === 'task-orchestration'
132
+ ? request.authority
133
+ : undefined;
134
+ if (!selector) return undefined;
135
+ let controllerBinding: { instanceDigest: string; controlGeneration: string } | null = null;
136
+ try {
137
+ const registration = readCodexControllerRegistration(manifestPath);
138
+ controllerBinding = {
139
+ instanceDigest: registration.controllerInstanceDigest,
140
+ controlGeneration: registration.controlGeneration
141
+ };
142
+ } catch {
143
+ // The issuer reports the stable unavailable result; the broker never trusts
144
+ // a controller binding supplied by the request.
145
+ }
146
+ return issueLifecycleRecoveryAttestation(selector, {
147
+ controllerBinding,
148
+ capabilityStore: createCodexCapabilityStore(),
149
+ buildIdentity: computeLifecycleBuildIdentity(manifest.repoRoot)
150
+ });
151
+ }
152
+
153
+ function appendBrokerAudit(
154
+ manifest: SandboxControlManifest,
155
+ event: string,
156
+ fields: Record<string, string | number | boolean | null> = {}
157
+ ): void {
158
+ appendDiagnosticAudit(manifest, event, { source: 'broker', ...fields });
159
+ }
160
+
161
+ function operationKey(request: SandboxControlRequest, output?: string): string | null {
162
+ if (request.family === 'task-finalization') return request.operation;
163
+ if (request.family === 'task-create') return 'create';
164
+ if (request.family === 'codex-controller') return request.command;
165
+ if (request.family === 'task-workflow') return request.workflow.operation;
166
+ if (request.family !== 'task-lifecycle' && request.family !== 'task-orchestration') return null;
167
+ if (request.family === 'task-orchestration' && request.args[1] === 'route') {
168
+ if (!output) return 'route';
169
+ const result = parseControlOutput(output);
170
+ return result?.changed === true && result.status === 'completed' ? 'route.clean-completion' : 'route.read';
171
+ }
172
+ try {
173
+ const operation = parseTaskControlOperation(request.family, request.args);
174
+ return operation.family === 'task-orchestration' ? operation.intent : operation.request.intent;
175
+ } catch {
176
+ return null;
177
+ }
178
+ }
179
+
180
+ function recoveryOperationKey(
181
+ request: SandboxControlRequest,
182
+ terminalResult: ReturnType<typeof readSandboxControlTerminalResult> | null,
183
+ output: string | undefined
184
+ ): string | null {
185
+ const operation = operationKey(request, output);
186
+ if (operation !== 'route') return operation;
187
+ const digest = terminalResult?.intentDigest;
188
+ for (const candidate of ['route.read', 'route.clean-completion'] as const) {
189
+ const expected = operationRecoveryBinding(request.id, request.generation, null, request.family, candidate).intentDigest;
190
+ if (digest === expected) return candidate;
191
+ }
192
+ return null;
193
+ }
194
+
195
+ function terminalResultMatchesRequest(
196
+ request: SandboxControlRequest,
197
+ terminalResult: ReturnType<typeof readSandboxControlTerminalResult>
198
+ ): boolean {
199
+ const operation = operationKey(request);
200
+ const candidates = operation === 'route'
201
+ ? ['route.read', 'route.clean-completion']
202
+ : operation ? [operation] : [];
203
+ return candidates.some((candidate) => createHash('sha256').update(`${request.family}\0${candidate}`, 'utf8').digest('hex') === terminalResult.intentDigest);
204
+ }
205
+
206
+ function criticalRequestPhase(
207
+ manifest: SandboxControlManifest,
208
+ request: SandboxControlRequest,
209
+ phase: Parameters<typeof createSandboxControlAuditContext>[1]['phase'],
210
+ outcome: Parameters<typeof createSandboxControlAuditContext>[1]['outcome'],
211
+ reference?: string | null
212
+ ): void {
213
+ const context = createSandboxControlAuditContext(manifest, {
214
+ requestId: request.id,
215
+ family: request.family,
216
+ operation: operationKey(request),
217
+ phase,
218
+ outcome
219
+ });
220
+ appendCriticalAudit(manifest, context, { reference: reference ?? null });
221
+ writeSandboxControlTransition(manifest, { requestId: request.id, phase, reference });
222
+ }
223
+
224
+
61
225
  function assertRealDirectory(directory: string, parent?: string): void {
62
226
  const stat = fs.lstatSync(directory);
63
227
  if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error('SANDBOX_CONTROL_CHANNEL_INVALID');
@@ -142,33 +306,37 @@ function finalizationRecoveryResponse(
142
306
  } catch {
143
307
  return { status: 'deferred' };
144
308
  }
145
- const pendingSteps = [
146
- receipt.taskComment === 'pending' ? 'task-comment' : null,
147
- receipt.verification === 'pending' ? 'verification' : null
148
- ].filter((step): step is string => step !== null);
149
- const completedSteps = ['lifecycle', receipt.taskComment === 'pending' ? null : 'task-comment', receipt.verification === 'pending' ? null : 'verification']
150
- .filter((step): step is string => step !== null);
151
- const warnings = receipt.warnings
152
- .filter((warning) => warning.status === 'open')
153
- .map(({ status: _status, resolvedAt: _resolvedAt, ...warning }) => warning);
154
- const result = {
155
- status: 'completed', changed: false, taskId: manifest.taskId,
156
- lifecycle: { status: 'no-op', changed: false, error: null },
157
- taskComment: receipt.taskComment === 'pending' ? null : { status: 'no-op', changed: false, error: null },
158
- verification: receipt.verification === 'pending' ? null : { status: 'no-op', changed: false, error: null },
159
- completedSteps, pendingSteps,
160
- result: pendingSteps.length > 0 || receipt.warningProjection === 'pending' || warnings.length > 0
161
- ? 'completed_with_warnings' : 'completed',
162
- warnings, error: null
163
- };
164
- return {
165
- status: 'matched',
166
- response: {
167
- version: 2, id: requestId, phase: 'completed', exitCode: 0,
168
- stdout: `${JSON.stringify({ version: 1, status: 'completed', changed: false, accepted: true, result, error: null })}\n`,
169
- stderr: '', error: null
170
- }
171
- };
309
+ return { status: 'matched', response: finalizationTerminalResponse(manifest.taskId, requestId, receipt) };
310
+ }
311
+
312
+ function publishFinalizationTaskView(
313
+ manifest: SandboxControlManifest,
314
+ broker: BrokerOwner,
315
+ requestId: string,
316
+ state: 'starting' | 'healthy' | 'busy' | 'parked',
317
+ reasonCode: string | null,
318
+ activeRequestId: string | null
319
+ ): SandboxTaskView {
320
+ let view: SandboxTaskView;
321
+ try {
322
+ const resolved = resolveTaskRef(manifest.taskId ?? '', { repoRoot: manifest.repoRoot });
323
+ const receipt = readTaskFinalizationReceipt(manifest.repoRoot, manifest.taskId ?? '');
324
+ view = resolved.ok && resolved.state === 'completed'
325
+ ? taskViewAfterFinalization({
326
+ taskId: manifest.taskId ?? '', generation: manifest.generation, requestId, receipt
327
+ })
328
+ : {
329
+ state: 'unknown', taskId: manifest.taskId, observedSource: 'unknown', receipt: null,
330
+ reasonCode: 'SANDBOX_TASK_VIEW_SOURCE_UNCONFIRMED'
331
+ };
332
+ } catch {
333
+ view = {
334
+ state: 'unknown', taskId: manifest.taskId, observedSource: 'unknown', receipt: null,
335
+ reasonCode: 'SANDBOX_TASK_VIEW_RECEIPT_INVALID'
336
+ };
337
+ }
338
+ writeSandboxControlStatus(manifest, broker, state, reasonCode, activeRequestId, Date.now(), view);
339
+ return view;
172
340
  }
173
341
 
174
342
  function payloadReference(payload: ReturnType<typeof createSandboxControlPayload>) {
@@ -201,46 +369,290 @@ function outputMatchesEvidence(output: string, bytes: number, sha256: string): b
201
369
  }
202
370
 
203
371
  function genericRecoveryResponse(
204
- requestId: string,
205
- evidence: ReturnType<typeof readSandboxControlResultEvidence>,
206
- payload: ReturnType<typeof readSandboxControlPayload> | null
372
+ request: SandboxControlRequest,
373
+ exitCode: number,
374
+ payload: ReturnType<typeof readSandboxControlPayload> | null,
375
+ cause: 'publish' | 'recovery' = 'recovery'
207
376
  ): SandboxControlResponse {
377
+ const outputUnavailable = request.family === 'task-create' && !payload;
208
378
  return {
209
379
  version: 2,
210
- id: requestId,
380
+ id: request.id,
211
381
  phase: 'completed',
212
- exitCode: evidence.exitCode,
213
- stdout: '',
214
- stderr: payload ? '' : 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: broker restarted after executor completion\n',
382
+ exitCode,
383
+ stdout: outputUnavailable ? `${JSON.stringify(taskCreateOutputUnavailableResult(request.id))}\n` : '',
384
+ stderr: payload ? '' : cause === 'publish'
385
+ ? 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: output payload was not retained\n'
386
+ : 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: broker restarted after executor completion\n',
215
387
  error: null,
216
388
  outputState: payload ? 'available' : 'unavailable',
217
389
  payload: payload ? payloadReference(payload) : null
218
390
  };
219
391
  }
220
392
 
393
+ type RecoveryDomainEvidence = Readonly<{
394
+ domain: Readonly<Record<string, unknown>> | null;
395
+ journal?: ReturnType<typeof readLifecycleJournalEvidence>;
396
+ }>;
397
+
398
+ function taskCreateDomainEvidence(
399
+ manifest: SandboxControlManifest,
400
+ output: Record<string, unknown> | null
401
+ ): Readonly<Record<string, unknown>> {
402
+ if (!output || !['applied', 'no-op', 'degraded'].includes(String(output.status))) {
403
+ return { consistent: false };
404
+ }
405
+ const task = output.task && typeof output.task === 'object' && !Array.isArray(output.task)
406
+ ? output.task as Record<string, unknown> : null;
407
+ const taskId = typeof task?.id === 'string' ? task.id : null;
408
+ const shortId = typeof task?.shortId === 'string' ? task.shortId : null;
409
+ if (!taskId || !shortId) return { consistent: false };
410
+ try {
411
+ const resolved = resolveTaskRef(taskId, { repoRoot: manifest.repoRoot });
412
+ const shortIds = loadShortIdByTaskId(manifest.repoRoot);
413
+ return {
414
+ consistent: resolved.ok && resolved.taskId === taskId && resolved.state === 'active' && shortIds.get(taskId) === shortId,
415
+ taskId,
416
+ shortId
417
+ };
418
+ } catch {
419
+ return { consistent: false };
420
+ }
421
+ }
422
+
423
+ function controllerDomainEvidence(
424
+ manifest: SandboxControlManifest,
425
+ manifestPath: string,
426
+ request: SandboxControlRequest,
427
+ output: Record<string, unknown> | null
428
+ ): Readonly<Record<string, unknown>> {
429
+ if (!output || output.error !== null || typeof output.status !== 'string') return { consistent: false };
430
+ try {
431
+ const registration = readCodexControllerRegistration(manifestPath);
432
+ if (request.family !== 'codex-controller') return { consistent: false };
433
+ if (request.command === 'open') {
434
+ const lease = output.lease && typeof output.lease === 'object' && !Array.isArray(output.lease)
435
+ ? output.lease as Record<string, unknown> : null;
436
+ return {
437
+ consistent: output.status === 'opened' && output.changed === true
438
+ && lease?.taskId === registration.taskId
439
+ && lease.controlGeneration === registration.controlGeneration
440
+ && lease.controllerInstanceDigest === registration.controllerInstanceDigest
441
+ && registration.taskId === manifest.taskId
442
+ && registration.controlGeneration === manifest.generation
443
+ };
444
+ }
445
+ if (request.command === 'verify') {
446
+ const binding = output.binding && typeof output.binding === 'object' && !Array.isArray(output.binding)
447
+ ? output.binding as Record<string, unknown> : null;
448
+ return {
449
+ consistent: output.status === 'verified' && output.changed === false
450
+ && binding?.taskId === registration.taskId
451
+ && binding.controlGeneration === registration.controlGeneration
452
+ && binding.controllerInstanceDigest === registration.controllerInstanceDigest
453
+ };
454
+ }
455
+ return { consistent: false };
456
+ } catch (error) {
457
+ return { consistent: request.family === 'codex-controller' && request.command === 'close'
458
+ && (error as { code?: string }).code === 'CODEX_SANDBOX_CONTROLLER_REGISTRATION_MISSING'
459
+ && output?.status === 'closed' && typeof output.changed === 'boolean' };
460
+ }
461
+ }
462
+
463
+ function orchestrationDomainEvidence(
464
+ operation: ReturnType<typeof findSandboxControlRecoveryOperation>,
465
+ terminalResult: ReturnType<typeof readSandboxControlTerminalResult>,
466
+ output: Record<string, unknown> | null,
467
+ run: ReturnType<typeof readRun> | null
468
+ ): Readonly<Record<string, unknown>> {
469
+ if (!operation || !output) return { consistent: false };
470
+ const outputRun = output.run && typeof output.run === 'object' && !Array.isArray(output.run)
471
+ ? output.run as Record<string, unknown> : null;
472
+ const runMatches = outputRun === null ? run === null : run !== null && JSON.stringify(run) === JSON.stringify(outputRun);
473
+ if (operation.class === 'read-only') {
474
+ return {
475
+ consistent: output.status === terminalResult.status && output.changed === terminalResult.changed && runMatches,
476
+ snapshotValid: true,
477
+ status: run?.status ?? terminalResult.status,
478
+ pendingDelegation: run?.pendingDelegation ?? null
479
+ };
480
+ }
481
+ if (!run || !outputRun) return { consistent: false };
482
+ return {
483
+ consistent: output.status === terminalResult.status && output.changed === terminalResult.changed && runMatches,
484
+ status: run.status,
485
+ pendingDelegation: run.pendingDelegation
486
+ };
487
+ }
488
+
489
+ function readRecoveryDomain(
490
+ manifest: SandboxControlManifest,
491
+ manifestPath: string,
492
+ request: SandboxControlRequest,
493
+ operation: ReturnType<typeof findSandboxControlRecoveryOperation>,
494
+ terminalResult: ReturnType<typeof readSandboxControlTerminalResult>,
495
+ payloadOutput: string | null
496
+ ): RecoveryDomainEvidence {
497
+ if (!operation) return { domain: null };
498
+ const taskRef = request.family === 'task-finalization' || request.family === 'task-workflow'
499
+ ? manifest.taskId
500
+ : 'args' in request ? request.args[0] ?? null : manifest.taskId;
501
+ const output = parseControlOutput(payloadOutput);
502
+ if (!taskRef && operation.family !== 'task-create' && operation.family !== 'codex-controller') {
503
+ return { domain: null };
504
+ }
505
+
506
+ if (operation.family === 'task-lifecycle') {
507
+ if (!taskRef || !terminalResult.targetState) return { domain: null };
508
+ const journal = readLifecycleJournalEvidence(manifest.repoRoot, taskRef);
509
+ try {
510
+ const resolved = resolveTaskRef(taskRef, { repoRoot: manifest.repoRoot });
511
+ const shortIds = loadShortIdByTaskId(manifest.repoRoot);
512
+ const shortIdMatches = terminalResult.targetState === 'active'
513
+ ? shortIds.has(resolved.ok ? resolved.taskId : taskRef)
514
+ : !shortIds.has(resolved.ok ? resolved.taskId : taskRef);
515
+ return {
516
+ domain: { consistent: resolved.ok && resolved.state === terminalResult.targetState && shortIdMatches },
517
+ journal
518
+ };
519
+ } catch {
520
+ return { domain: { consistent: false }, journal };
521
+ }
522
+ }
523
+
524
+ if (operation.family === 'task-orchestration') {
525
+ if (!taskRef) return { domain: null };
526
+ try {
527
+ const resolved = resolveTaskRef(taskRef, { repoRoot: manifest.repoRoot });
528
+ if (!resolved.ok) return { domain: { consistent: false } };
529
+ const run = readRun(resolved.taskDir);
530
+ if (!run && operation.class !== 'read-only') return { domain: { consistent: false } };
531
+ if (operation.class === 'route.clean-completion') {
532
+ const snapshot = captureRepositorySnapshot(manifest.repoRoot);
533
+ const metadata = parseTypedTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8'));
534
+ const completion = run?.completionEvidence ?? null;
535
+ const consistent = run !== null && completion !== null
536
+ && terminalResult.completionEvidence !== null
537
+ && isDeepStrictEqual(completion, terminalResult.completionEvidence);
538
+ return {
539
+ domain: {
540
+ consistent,
541
+ status: run?.status ?? null,
542
+ pendingDelegation: run?.pendingDelegation ?? null,
543
+ completionEvidence: completion,
544
+ snapshot,
545
+ lastReviewedCommit: metadata.last_reviewed_commit
546
+ }
547
+ };
548
+ }
549
+ return { domain: orchestrationDomainEvidence(operation, terminalResult, output, run) };
550
+ } catch {
551
+ return { domain: { consistent: false } };
552
+ }
553
+ }
554
+
555
+ if (operation.family === 'task-create') {
556
+ return { domain: taskCreateDomainEvidence(manifest, output) };
557
+ }
558
+ if (operation.family === 'codex-controller') {
559
+ return { domain: controllerDomainEvidence(manifest, manifestPath, request, output) };
560
+ }
561
+ if (operation.family === 'task-workflow') {
562
+ return {
563
+ domain: { consistent: output?.status === terminalResult.status && output.changed === terminalResult.changed, snapshotValid: true }
564
+ };
565
+ }
566
+ return { domain: null };
567
+ }
568
+
569
+ function readCommittedCriticalPhases(manifest: SandboxControlManifest, requestId: string): readonly string[] {
570
+ return SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES.filter((phase) => {
571
+ try {
572
+ readSandboxControlTransition(manifest, requestId, phase);
573
+ return true;
574
+ } catch {
575
+ return false;
576
+ }
577
+ });
578
+ }
579
+
221
580
  function recoveryResponse(
222
581
  manifest: SandboxControlManifest,
582
+ manifestPath: string,
223
583
  request: SandboxControlRequest,
224
584
  evidence: ReturnType<typeof readSandboxControlResultEvidence>,
225
- payload: ReturnType<typeof readSandboxControlPayload> | null
585
+ payload: ReturnType<typeof readSandboxControlPayload> | null,
586
+ terminalResult: ReturnType<typeof readSandboxControlTerminalResult>
226
587
  ): SandboxControlResponse | null {
588
+ const operationName = recoveryOperationKey(request, terminalResult, payload?.stdout);
589
+ const operation = operationName ? findSandboxControlRecoveryOperation(request.family, operationName) : null;
590
+ if (!operation) return null;
591
+ if (payload) {
592
+ const payloadTerminal = createSandboxControlTerminalResult(manifest, {
593
+ id: request.id, family: request.family, operation: operationName
594
+ }, payload.stdout);
595
+ if (JSON.stringify(payloadTerminal) !== JSON.stringify(terminalResult)) return unknown(request.id);
596
+ }
597
+ const finalization = request.family === 'task-finalization' ? finalizationRecoveryResponse(manifest, request.id, 0) : null;
598
+ if (evidence.exitCode === 0 && finalization?.status === 'deferred') return null;
599
+ if (evidence.exitCode === 0 && request.family === 'task-workflow' && request.authority?.phase === 'task-event.completed') {
600
+ try {
601
+ const registration = readCodexControllerRegistration(manifestPath);
602
+ const recovered = recoverLifecycleRecoveryOperation(request.authority, {
603
+ repoRoot: manifest.repoRoot,
604
+ capabilityStore: createCodexCapabilityStore(),
605
+ buildIdentity: computeLifecycleBuildIdentity(manifest.repoRoot),
606
+ controllerBinding: {
607
+ instanceDigest: registration.controllerInstanceDigest,
608
+ controlGeneration: registration.controlGeneration
609
+ }
610
+ });
611
+ if (recovered.status !== 'committed') return null;
612
+ } catch (error) {
613
+ appendBrokerAudit(manifest, 'lifecycle-authority-recovery-failed', {
614
+ ...requestAuditFields(manifest, request),
615
+ errorCode: error instanceof Error ? error.name : 'LIFECYCLE_AUTHORITY_RECOVERY_FAILED'
616
+ });
617
+ return null;
618
+ }
619
+ }
620
+ const recovery: RecoveryDomainEvidence = finalization
621
+ ? { domain: { consistent: finalization.status === 'matched' } }
622
+ : readRecoveryDomain(manifest, manifestPath, request, operation, terminalResult, payload?.stdout ?? null);
623
+ const binding = operationRecoveryBinding(request.id, manifest.generation, manifest.taskId, request.family, operationName!);
624
+ const decision = classifySandboxControlRecovery({
625
+ operation,
626
+ binding,
627
+ startedCommitted: true,
628
+ terminalResult,
629
+ criticalPhases: readCommittedCriticalPhases(manifest, request.id),
630
+ domain: recovery.domain,
631
+ journal: recovery.journal
632
+ });
633
+ if (decision.outcome === 'unknown' || decision.outcome === 'in-progress') return unknown(request.id);
634
+ if (decision.outcome === 'not-executed') return notExecuted(request.id);
635
+ if (decision.outcome !== 'success' && decision.outcome !== 'failure') return null;
227
636
  if (request.family === 'task-finalization' && evidence.exitCode === 0) {
228
- const finalization = finalizationRecoveryResponse(manifest, request.id, evidence.exitCode);
229
- return finalization.status === 'matched' ? finalization.response ?? null : null;
637
+ if (finalization?.status !== 'matched') return null;
638
+ return finalization.response ?? null;
230
639
  }
231
- return genericRecoveryResponse(request.id, evidence, payload);
640
+ return genericRecoveryResponse(request, evidence.exitCode, payload);
232
641
  }
233
642
 
234
643
  function terminalMatchesEvidence(
235
644
  manifest: SandboxControlManifest,
645
+ manifestPath: string,
236
646
  request: SandboxControlRequest,
237
647
  response: SandboxControlResponse,
238
648
  evidence: ReturnType<typeof readSandboxControlResultEvidence>,
239
649
  payload: ReturnType<typeof readSandboxControlPayload> | null,
240
- payloadInvalid: boolean
650
+ payloadInvalid: boolean,
651
+ terminalResult: ReturnType<typeof readSandboxControlTerminalResult> | null
241
652
  ): { valid: boolean; payloadReferenced: boolean } {
242
653
  if (request.family === 'task-finalization' && evidence.exitCode === 0) {
243
- const expected = recoveryResponse(manifest, request, evidence, payload);
654
+ if (!terminalResult) return { valid: false, payloadReferenced: false };
655
+ const expected = recoveryResponse(manifest, manifestPath, request, evidence, payload, terminalResult);
244
656
  return {
245
657
  valid: expected !== null && JSON.stringify(response) === JSON.stringify(expected),
246
658
  payloadReferenced: false
@@ -267,8 +679,10 @@ function terminalMatchesEvidence(
267
679
  };
268
680
  }
269
681
  if (response.outputState === 'unavailable') {
270
- const expected = genericRecoveryResponse(request.id, evidence, null);
271
- return { valid: JSON.stringify(response) === JSON.stringify(expected), payloadReferenced: false };
682
+ const causes = ['recovery', 'publish'] as const;
683
+ const valid = causes.some((cause) => JSON.stringify(response)
684
+ === JSON.stringify(genericRecoveryResponse(request, evidence.exitCode, null, cause)));
685
+ return { valid, payloadReferenced: false };
272
686
  }
273
687
  return {
274
688
  valid: response.payload === undefined
@@ -282,9 +696,20 @@ function publishExecutionResult(
282
696
  manifest: SandboxControlManifest,
283
697
  request: SandboxControlRequest,
284
698
  result: SandboxControlExecutionResult,
699
+ broker: BrokerOwner,
285
700
  brokerOwns: () => boolean
286
701
  ): boolean {
287
702
  const normalized = sanitizeSandboxControlResult(manifest, result);
703
+ writeSandboxControlTerminalResult(manifest, {
704
+ id: request.id,
705
+ family: request.family,
706
+ operation: operationKey(request, normalized.stdout)
707
+ }, normalized.stdout);
708
+ if (fs.existsSync(path.join(manifest.processingDir, request.id, 'transitions'))) {
709
+ criticalRequestPhase(manifest, request, 'completed', normalized.exitCode === 0 ? 'success' : 'failure');
710
+ criticalRequestPhase(manifest, request, 'evidence-written', normalized.exitCode === 0 ? 'success' : 'failure');
711
+ criticalRequestPhase(manifest, request, 'publish-authorized', normalized.exitCode === 0 ? 'success' : 'failure');
712
+ }
288
713
  let terminal: SandboxControlResponse | null = null;
289
714
  if (request.family === 'task-finalization') {
290
715
  const finalization = finalizationRecoveryResponse(manifest, request.id, normalized.exitCode);
@@ -299,7 +724,7 @@ function publishExecutionResult(
299
724
  if (sandboxControlEncodedJsonBytes(inline) <= SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES) {
300
725
  terminal = inline;
301
726
  }
302
- let payload = null;
727
+ let payload: ReturnType<typeof createSandboxControlPayload> | null = null;
303
728
  if (!terminal) {
304
729
  try {
305
730
  payload = createSandboxControlPayload(manifest, request.id, normalized);
@@ -312,21 +737,19 @@ function publishExecutionResult(
312
737
  } catch {
313
738
  payload = null;
314
739
  }
315
- terminal = {
316
- version: 2,
317
- id: request.id,
318
- phase: 'completed',
319
- exitCode: normalized.exitCode,
320
- stdout: '',
321
- stderr: payload ? '' : 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: output payload was not retained\n',
322
- error: null,
323
- outputState: payload ? 'available' : 'unavailable',
324
- payload: payload ? payloadReference(payload) : null
325
- };
740
+ terminal = genericRecoveryResponse(request, normalized.exitCode, payload, 'publish');
326
741
  }
327
742
  }
328
743
  if (!brokerOwns()) return false;
329
- return writeSandboxControlResponse(manifest, terminal);
744
+ if (request.family === 'task-finalization' && normalized.exitCode === 0) {
745
+ const view = publishFinalizationTaskView(manifest, broker, request.id, 'healthy', null, null);
746
+ if (view.state === 'unknown') return false;
747
+ }
748
+ const committed = writeSandboxControlResponse(manifest, terminal);
749
+ if (committed && fs.existsSync(path.join(manifest.processingDir, request.id, 'transitions'))) {
750
+ criticalRequestPhase(manifest, request, 'published-committed', normalized.exitCode === 0 ? 'success' : 'failure');
751
+ }
752
+ return committed;
330
753
  }
331
754
 
332
755
  function writeAcceptedResponse(manifest: SandboxControlManifest, response: SandboxControlResponse): void {
@@ -391,21 +814,40 @@ function bindingReason(manifest: SandboxControlManifest): string | null {
391
814
  }
392
815
  }
393
816
 
394
- function recoverProcessing(manifest: SandboxControlManifest, brokerOwns: () => boolean): boolean {
817
+ function assertCurrentSandboxControlIdentity(manifest: SandboxControlManifest, manifestPath: string): void {
818
+ const result = validateSandboxControlIdentity({
819
+ publicStatusDir: manifest.publicStatusDir,
820
+ root: path.dirname(path.resolve(manifestPath)),
821
+ mode: manifest.mode,
822
+ taskId: manifest.taskId,
823
+ generation: manifest.generation,
824
+ controlRootId: manifest.controlRootId
825
+ });
826
+ if (result.state !== 'valid') {
827
+ throw new Error(`SANDBOX_CONTROL_IDENTITY_${result.state.replaceAll('-', '_').toUpperCase()}`);
828
+ }
829
+ }
830
+
831
+ function recoverProcessing(manifest: SandboxControlManifest, manifestPath: string, broker: BrokerOwner, brokerOwns: () => boolean): boolean {
395
832
  for (const entry of fs.readdirSync(manifest.processingDir, { withFileTypes: true })) {
396
833
  if (!entry.isDirectory() || !/^[a-f0-9-]{16,64}$/.test(entry.name)) continue;
397
834
  if (!brokerOwns()) return false;
398
835
  const descriptor = executionPath(manifest, entry.name);
399
836
  const existingResponse = responsePath(manifest, entry.name);
400
837
  const processingDirectory = path.join(manifest.processingDir, entry.name);
838
+ const transitionsDirectory = path.join(processingDirectory, 'transitions');
839
+ const transitionProtocolActive = fs.existsSync(transitionsDirectory);
840
+ const startedCommitted = fs.existsSync(path.join(transitionsDirectory, 'started-committed.json'));
401
841
  let payloadReferenced = false;
402
842
  if (fs.existsSync(descriptor)) {
403
843
  const execution = readExecution(descriptor);
404
844
  let resultEvidence: ReturnType<typeof readSandboxControlResultEvidence> | null = null;
845
+ let terminalResult: ReturnType<typeof readSandboxControlTerminalResult> | null = null;
405
846
  let payload: ReturnType<typeof readSandboxControlPayload> | null = null;
406
847
  let payloadInvalid = false;
407
848
  let request: SandboxControlRequest | null = null;
408
849
  let existingTerminalResponse: SandboxControlResponse | null = null;
850
+ let terminalReconciled = false;
409
851
  try {
410
852
  const rawRequest = JSON.parse(fs.readFileSync(path.join(processingDirectory, 'request.json'), 'utf8')) as Record<string, unknown>;
411
853
  request = validateSandboxControlRequest(rawRequest, manifest, {
@@ -425,6 +867,18 @@ function recoverProcessing(manifest: SandboxControlManifest, brokerOwns: () => b
425
867
  resultEvidence = null;
426
868
  }
427
869
  }
870
+ const terminalResultPathValue = path.join(processingDirectory, 'terminal-result.json');
871
+ if (fs.existsSync(terminalResultPathValue)) {
872
+ try {
873
+ terminalResult = readSandboxControlTerminalResult(terminalResultPathValue);
874
+ if (terminalResult.requestId !== entry.name || terminalResult.generation !== manifest.generation
875
+ || !request || !terminalResultMatchesRequest(request, terminalResult)) {
876
+ terminalResult = null;
877
+ }
878
+ } catch {
879
+ terminalResult = null;
880
+ }
881
+ }
428
882
  const publishedPayloadPath = payloadPath(manifest, entry.name);
429
883
  if (fs.existsSync(publishedPayloadPath)) {
430
884
  try {
@@ -437,11 +891,21 @@ function recoverProcessing(manifest: SandboxControlManifest, brokerOwns: () => b
437
891
  payloadInvalid = true;
438
892
  }
439
893
  }
894
+ if (transitionProtocolActive && startedCommitted && !terminalResult) {
895
+ if (!brokerOwns()) return false;
896
+ if (!terminateSandboxControlExecution(execution)) {
897
+ throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${entry.name}`);
898
+ }
899
+ appendDiagnosticAudit(manifest, 'orphan-tree-terminated', { requestId: entry.name });
900
+ writeSandboxControlResponse(manifest, unknown(entry.name));
901
+ continue;
902
+ }
903
+ if (transitionProtocolActive && !startedCommitted && terminalResult) continue;
440
904
  if (!terminateSandboxControlExecution(execution)) {
441
905
  throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${entry.name}`);
442
906
  }
443
907
  if (!brokerOwns()) return false;
444
- appendSandboxControlAudit(manifest, 'orphan-tree-terminated', { requestId: entry.name });
908
+ appendDiagnosticAudit(manifest, 'orphan-tree-terminated', { requestId: entry.name });
445
909
  let terminal = false;
446
910
  if (fs.existsSync(existingResponse)) {
447
911
  try {
@@ -459,7 +923,7 @@ function recoverProcessing(manifest: SandboxControlManifest, brokerOwns: () => b
459
923
  || response.payload.stdoutSha256 !== payload.stdoutSha256
460
924
  || response.payload.stderrSha256 !== payload.stderrSha256) throw new Error('payload missing or mismatched');
461
925
  payloadReferenced = true;
462
- } else if (terminal && (response.outputState !== undefined
926
+ } else if (terminal && ((response.outputState !== undefined && response.outputState !== 'unavailable')
463
927
  || response.payload !== undefined && response.payload !== null)) {
464
928
  throw new Error('payload state invalid');
465
929
  }
@@ -470,20 +934,61 @@ function recoverProcessing(manifest: SandboxControlManifest, brokerOwns: () => b
470
934
  }
471
935
  if (terminal && (!request || !resultEvidence)) continue;
472
936
  if (terminal && request && resultEvidence) {
473
- const reconciliation = terminalMatchesEvidence(manifest, request, existingTerminalResponse!, resultEvidence, payload, payloadInvalid);
937
+ if (!terminalResult && request.family !== 'task-create') continue;
938
+ const reconciliation = terminalMatchesEvidence(
939
+ manifest,
940
+ manifestPath,
941
+ request,
942
+ existingTerminalResponse!,
943
+ resultEvidence,
944
+ payload,
945
+ payloadInvalid,
946
+ terminalResult
947
+ );
474
948
  if (!reconciliation.valid) continue;
949
+ terminalReconciled = true;
475
950
  payloadReferenced = reconciliation.payloadReferenced;
951
+ if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
952
+ const view = publishFinalizationTaskView(manifest, broker, request.id, 'starting', null, null);
953
+ if (view.state === 'unknown') continue;
954
+ }
955
+ }
956
+ if (!terminalResult && !terminalReconciled) {
957
+ if (terminal) continue;
958
+ if (!brokerOwns()) return false;
959
+ if (!resultEvidence || !request || payloadInvalid) continue;
960
+ if (request.family === 'task-create' && resultEvidence.exitCode === 0 && payload === null) {
961
+ writeSandboxControlResponse(manifest, genericRecoveryResponse(request, resultEvidence.exitCode, null));
962
+ terminalReconciled = true;
963
+ terminal = true;
964
+ } else {
965
+ writeSandboxControlResponse(manifest, unknown(entry.name));
966
+ continue;
967
+ }
476
968
  }
477
969
  if (!terminal) {
478
970
  if (!brokerOwns()) return false;
479
971
  if (!resultEvidence || !request || payloadInvalid) continue;
480
- const recovered = recoveryResponse(manifest, request, resultEvidence, payload);
972
+ const recovered = recoveryResponse(manifest, manifestPath, request, resultEvidence, payload, terminalResult!);
481
973
  if (!recovered) continue;
974
+ if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
975
+ const view = publishFinalizationTaskView(manifest, broker, request.id, 'starting', null, null);
976
+ if (view.state === 'unknown') continue;
977
+ }
482
978
  writeSandboxControlResponse(manifest, recovered);
979
+ const preserveRecoveryEvidence = recovered.error?.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN';
980
+ if (transitionProtocolActive && !preserveRecoveryEvidence) {
981
+ writeSandboxControlTransition(manifest, { requestId: entry.name, phase: 'recovered' });
982
+ }
983
+ if (preserveRecoveryEvidence) continue;
483
984
  payloadReferenced = Boolean(payload) && request.family !== 'task-finalization';
484
985
  }
485
986
  } else {
486
987
  if (!brokerOwns()) return false;
988
+ if (transitionProtocolActive && startedCommitted) {
989
+ writeSandboxControlResponse(manifest, unknown(entry.name));
990
+ continue;
991
+ }
487
992
  writeSandboxControlResponse(manifest, notExecuted(entry.name));
488
993
  }
489
994
  if (!brokerOwns()) return false;
@@ -499,7 +1004,13 @@ function delay(ms: number): Promise<void> {
499
1004
  }
500
1005
 
501
1006
  export function sandboxControlSafeEnv(env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
502
- return Object.fromEntries(Object.entries(env).filter(([key]) => !key.toUpperCase().startsWith('AGENT_INFRA_CONTROL_')));
1007
+ return Object.fromEntries(Object.entries(env).filter(([key]) => {
1008
+ const normalized = key.toUpperCase();
1009
+ return !normalized.startsWith('AGENT_INFRA_CONTROL_')
1010
+ && normalized !== 'AGENT_INFRA_TASK_ID'
1011
+ && normalized !== 'AGENT_INFRA_RUNTIME_DIR'
1012
+ && normalized !== 'AGENT_INFRA_EXECUTOR_MANIFEST';
1013
+ }));
503
1014
  }
504
1015
 
505
1016
  export async function serveSandboxControl(
@@ -519,6 +1030,7 @@ export async function serveSandboxControl(
519
1030
  const prepareExecution = options.prepareExecution ?? prepareSandboxControlExecution;
520
1031
  const manifest = readSandboxControlManifest(manifestPath);
521
1032
  const root = path.dirname(manifestPath);
1033
+ assertCurrentSandboxControlIdentity(manifest, manifestPath);
522
1034
  const startTime = getProcessStartTime(process.pid);
523
1035
  if (!startTime) throw new Error('SANDBOX_CONTROL_BROKER_IDENTITY_UNAVAILABLE');
524
1036
  const brokerId = randomUUID();
@@ -562,6 +1074,25 @@ export async function serveSandboxControl(
562
1074
  releaseStartup();
563
1075
  }
564
1076
  let active: ActiveExecution | null = null;
1077
+ let startupReceipt: unknown = null;
1078
+ if (manifest.taskId) {
1079
+ try {
1080
+ startupReceipt = readTaskFinalizationReceipt(manifest.repoRoot, manifest.taskId);
1081
+ } catch {
1082
+ // Preserve malformed receipt evidence as unknown rather than treating it as no receipt.
1083
+ startupReceipt = {};
1084
+ }
1085
+ }
1086
+ let taskView = taskViewForManifest({
1087
+ ...manifest,
1088
+ receipt: startupReceipt
1089
+ });
1090
+ try {
1091
+ const previous = readSandboxControlStatus(manifest.publicStatusDir);
1092
+ if (previous.generation === manifest.generation) taskView = mergeSandboxTaskView(taskView, previous.taskView);
1093
+ } catch {
1094
+ // A fresh control root has no previous task-view projection.
1095
+ }
565
1096
  let lastState = '';
566
1097
  let lastStatusAt = 0;
567
1098
  let nextBindingCheckAt = 0;
@@ -572,16 +1103,37 @@ export async function serveSandboxControl(
572
1103
  let bindingBackoffMs: number = timing.parkedBindingInitialMs;
573
1104
  try {
574
1105
  if (!brokerOwns()) return;
575
- writeSandboxControlStatus(manifest, broker, 'starting', null, null);
1106
+ if (taskView.state === 'current' && taskView.observedSource === 'completed') {
1107
+ // Recheck durable re-entry evidence before publishing a trusted startup view.
1108
+ const unverified = { ...taskView, state: 'unknown' as const,
1109
+ reasonCode: 'SANDBOX_COMPLETED_REENTRY_EVIDENCE_INVALID' };
1110
+ try {
1111
+ taskView = await completedReentryView(manifest, inspectContainer) ?? unverified;
1112
+ } catch {
1113
+ taskView = unverified;
1114
+ }
1115
+ if (!brokerOwns()) return;
1116
+ }
1117
+ writeSandboxControlStatus(manifest, broker, 'starting', null, null, Date.now(), taskView);
576
1118
  if (!brokerOwns()) return;
577
- appendSandboxControlAudit(manifest, 'broker-start', { pid: broker.pid });
578
- if (!recoverProcessing(manifest, brokerOwns)) return;
1119
+ appendBrokerAudit(manifest, 'broker-start', {
1120
+ pid: broker.pid,
1121
+ brokerId: broker.brokerId
1122
+ });
1123
+ if (!recoverProcessing(manifest, manifestPath, broker, brokerOwns)) return;
1124
+ try {
1125
+ const published = readSandboxControlStatus(manifest.publicStatusDir);
1126
+ if (published.generation === manifest.generation) taskView = published.taskView;
1127
+ } catch {
1128
+ // The regular heartbeat will publish an invalid or missing projection as unknown.
1129
+ }
579
1130
  while (!signal.aborted) {
580
1131
  let settledExecution: ActiveExecution | null = null;
581
1132
  if (!brokerOwns()) break;
582
1133
  let current: SandboxControlManifest;
583
1134
  try {
584
1135
  current = readSandboxControlManifest(manifestPath);
1136
+ assertCurrentSandboxControlIdentity(current, manifestPath);
585
1137
  } catch {
586
1138
  break;
587
1139
  }
@@ -612,7 +1164,7 @@ export async function serveSandboxControl(
612
1164
  nextContainerHeartbeatAt = Date.now() + timing.containerHeartbeatMs;
613
1165
  } else if (observation.state === 'unknown') {
614
1166
  containerReasonCode = 'SANDBOX_CONTROL_CONTAINER_UNKNOWN';
615
- appendSandboxControlAudit(manifest, 'container-heartbeat-unknown', { reason: observation.reason });
1167
+ appendDiagnosticAudit(manifest, 'container-heartbeat-unknown', { reason: observation.reason });
616
1168
  const backoff = nextSandboxControlBackoff(containerBackoffMs, timing.slowCheckMs);
617
1169
  nextContainerHeartbeatAt = Date.now() + backoff.delayMs;
618
1170
  containerBackoffMs = backoff.nextDelayMs;
@@ -628,7 +1180,7 @@ export async function serveSandboxControl(
628
1180
  } catch (error) {
629
1181
  if (!brokerOwns()) break;
630
1182
  containerReasonCode = 'SANDBOX_CONTROL_CONTAINER_ABSENT_GC_FAILED';
631
- appendSandboxControlAudit(manifest, 'container-gc-failed', { reason: controlError(error).code });
1183
+ appendDiagnosticAudit(manifest, 'container-gc-failed', { reason: controlError(error).code });
632
1184
  const backoff = nextSandboxControlBackoff(containerBackoffMs, timing.slowCheckMs);
633
1185
  nextContainerHeartbeatAt = Date.now() + backoff.delayMs;
634
1186
  containerBackoffMs = backoff.nextDelayMs;
@@ -647,7 +1199,7 @@ export async function serveSandboxControl(
647
1199
  if (!brokerOwns()) break;
648
1200
  if (cleanupStaleSandboxControlLease(manifest)) {
649
1201
  if (!brokerOwns()) break;
650
- appendSandboxControlAudit(manifest, 'lease-stale-cleanup');
1202
+ appendBrokerAudit(manifest, 'lease-stale-cleanup');
651
1203
  }
652
1204
  if (readActiveLease(manifest)) reasonCode = 'SANDBOX_CONTROL_HANDOFF_ACTIVE';
653
1205
  } catch {
@@ -667,25 +1219,53 @@ export async function serveSandboxControl(
667
1219
  }
668
1220
  reasonCode ??= containerReasonCode;
669
1221
  reasonCode ??= bindingReasonCode;
1222
+ if (!reasonCode && !active && taskView.state !== 'current') {
1223
+ try {
1224
+ const reentered = await completedReentryView(manifest, inspectContainer);
1225
+ if (reentered && brokerOwns() && !readActiveLease(manifest) && !bindingCheck(manifest)) {
1226
+ taskView = reentered;
1227
+ }
1228
+ } catch {
1229
+ // Invalid or changed re-entry evidence never clears the existing task view.
1230
+ }
1231
+ }
670
1232
  const state = reasonCode ? 'parked' : active ? 'busy' : 'healthy';
671
1233
  const stateKey = `${state}:${reasonCode ?? ''}:${active?.request.id ?? ''}`;
672
1234
  if (brokerOwns() && (stateKey !== lastState || now - lastStatusAt >= timing.controlTickMs)) {
673
- writeSandboxControlStatus(manifest, broker, state, reasonCode, active?.request.id ?? null, now);
1235
+ writeSandboxControlStatus(manifest, broker, state, reasonCode, active?.request.id ?? null, now, taskView);
674
1236
  lastStatusAt = now;
675
1237
  }
676
1238
  if (brokerOwns() && stateKey !== lastState) {
677
- appendSandboxControlAudit(manifest, 'broker-state', { state, reasonCode, requestId: active?.request.id ?? null });
1239
+ appendBrokerAudit(manifest, 'broker-state', { state, reasonCode, requestId: active?.request.id ?? null });
678
1240
  lastState = stateKey;
679
1241
  }
680
1242
  if (settledExecution) {
681
1243
  if (!brokerOwns()) break;
682
1244
  let terminalCommitted = false;
683
1245
  if (settledExecution.result && settledExecution.resultEvidenceWritten) {
684
- terminalCommitted = publishExecutionResult(manifest, settledExecution.request, settledExecution.result, brokerOwns);
1246
+ if (!consumeLifecycleAuthorityForResult(manifest, settledExecution)) {
1247
+ active = settledExecution;
1248
+ continue;
1249
+ }
1250
+ terminalCommitted = publishExecutionResult(manifest, settledExecution.request, settledExecution.result, broker, brokerOwns);
1251
+ if (terminalCommitted && settledExecution.request.family === 'task-finalization') {
1252
+ try {
1253
+ const published = readSandboxControlStatus(manifest.publicStatusDir);
1254
+ if (published.generation === manifest.generation) taskView = published.taskView;
1255
+ } catch {
1256
+ taskView = { state: 'unknown', taskId: manifest.taskId, observedSource: 'unknown', receipt: null, reasonCode: 'SANDBOX_TASK_VIEW_STATUS_INVALID' };
1257
+ }
1258
+ }
685
1259
  } else {
686
1260
  terminalCommitted = writeSandboxControlResponse(manifest, unknown(settledExecution.request.id));
687
1261
  }
688
1262
  if (!terminalCommitted) return;
1263
+ appendBrokerAudit(manifest, 'executor-result-published', {
1264
+ ...requestAuditFields(manifest, settledExecution.request),
1265
+ resultAvailable: settledExecution.result !== null,
1266
+ resultEvidenceWritten: settledExecution.resultEvidenceWritten,
1267
+ ...(settledExecution.result ? resultAuditFields(settledExecution.result) : {})
1268
+ });
689
1269
  if (!brokerOwns()) break;
690
1270
  removeAcceptedResponse(manifest, settledExecution.request.id);
691
1271
  fs.rmSync(path.join(manifest.processingDir, settledExecution.request.id), { recursive: true, force: true });
@@ -697,12 +1277,20 @@ export async function serveSandboxControl(
697
1277
  const id = name.slice(0, -5);
698
1278
  const source = path.join(requestsDir, name);
699
1279
  let claimed: string | null = null;
1280
+ let validatedRequest: SandboxControlRequest | null = null;
1281
+ let prepared: PreparedSandboxControlExecution | null = null;
700
1282
  try {
701
1283
  if (!brokerOwns()) {
702
1284
  retiring = true;
703
1285
  break;
704
1286
  }
705
1287
  claimed = claimRequest(manifest, source, id);
1288
+ appendBrokerAudit(manifest, 'request-claimed', {
1289
+ requestId: id,
1290
+ requestSourcePath: source,
1291
+ requestClaimedPath: claimed,
1292
+ requestProcessingDir: path.dirname(claimed)
1293
+ });
706
1294
  if (!brokerOwns()) {
707
1295
  retiring = true;
708
1296
  break;
@@ -715,29 +1303,99 @@ export async function serveSandboxControl(
715
1303
  if (reasonCode) throw new Error(reasonCode);
716
1304
  if (active) throw new Error('SANDBOX_CONTROL_BUSY');
717
1305
  const request = validateSandboxControlRequest(JSON.parse(fs.readFileSync(claimed, 'utf8')), manifest);
1306
+ validatedRequest = request;
1307
+ criticalRequestPhase(manifest, request, 'validated', 'in-progress');
1308
+ appendBrokerAudit(manifest, 'request-validated', {
1309
+ ...requestAuditFields(manifest, request),
1310
+ requestPath: claimed
1311
+ });
718
1312
  if (bindingCheck(manifest)) throw new Error('SANDBOX_WORKTREE_BINDING_LOST');
719
1313
  if (readActiveLease(manifest)) throw new Error('SANDBOX_CONTROL_HANDOFF_ACTIVE');
1314
+ criticalRequestPhase(manifest, request, 'gated', 'in-progress');
1315
+ appendBrokerAudit(manifest, 'request-gates-passed', {
1316
+ ...requestAuditFields(manifest, request),
1317
+ requestPath: claimed,
1318
+ bindingChecked: true,
1319
+ handoffLeaseChecked: true
1320
+ });
1321
+ criticalRequestPhase(manifest, request, 'reserved', 'in-progress');
720
1322
  writeSandboxControlReservation(manifest, request.id, sandboxControlGenerationUsage(manifest));
721
- const prepared = await prepareExecution({
1323
+ appendBrokerAudit(manifest, 'executor-reservation-written', {
1324
+ ...requestAuditFields(manifest, request),
1325
+ requestPath: claimed,
1326
+ executionPath: executionPath(manifest, request.id)
1327
+ });
1328
+ appendBrokerAudit(manifest, 'executor-prepare-start', {
1329
+ ...requestAuditFields(manifest, request),
1330
+ requestPath: claimed,
1331
+ executorCwd: manifest.repoRoot,
1332
+ executorEntry: options.internalCliPath ?? process.argv[1] ?? null
1333
+ });
1334
+ const lifecycleAuthority = lifecycleAuthorityForRequest(manifest, manifestPath, request);
1335
+ if (lifecycleAuthority?.status === 'rejected') {
1336
+ throw Object.assign(
1337
+ new Error(lifecycleAuthority.error?.message ?? 'lifecycle authority was rejected'),
1338
+ { code: lifecycleAuthority.error?.code ?? 'LIFECYCLE_AUTHORITY_REJECTED' }
1339
+ );
1340
+ }
1341
+ prepared = await prepareExecution({
722
1342
  manifest, manifestPath, request, requestPath: claimed,
723
- internalCliPath: options.internalCliPath ?? process.argv[1]!
1343
+ internalCliPath: options.internalCliPath ?? process.argv[1]!,
1344
+ ...(lifecycleAuthority ? { lifecycleAuthority } : {})
1345
+ });
1346
+ const preparedExecution = prepared;
1347
+ if (!preparedExecution) throw new Error('SANDBOX_CONTROL_EXECUTION_PREPARE_INVALID');
1348
+ criticalRequestPhase(manifest, request, 'prepared', 'in-progress', `${preparedExecution.execution.child.pid}`);
1349
+ appendBrokerAudit(manifest, 'executor-prepared', {
1350
+ ...requestAuditFields(manifest, request),
1351
+ requestPath: claimed,
1352
+ executorCwd: manifest.repoRoot,
1353
+ childPid: preparedExecution.execution.child.pid,
1354
+ childStartTime: preparedExecution.execution.child.startTime,
1355
+ childProcessGroupId: preparedExecution.execution.child.processGroupId,
1356
+ executionPath: executionPath(manifest, request.id)
724
1357
  });
725
1358
  const execution: ActiveExecution = {
726
- request, prepared, result: null, resultEvidenceWritten: false, failure: null, settled: false
1359
+ request, prepared: preparedExecution, lifecycleAuthority, result: null,
1360
+ resultEvidenceWritten: false, failure: null, settled: false
727
1361
  };
728
1362
  active = execution;
729
- prepared.completion.then(
1363
+ preparedExecution.completion.then(
730
1364
  (result) => {
731
1365
  execution.result = sanitizeSandboxControlResult(manifest, result);
1366
+ appendBrokerAudit(manifest, 'executor-completed', {
1367
+ ...requestAuditFields(manifest, request),
1368
+ ...resultAuditFields(execution.result),
1369
+ childPid: preparedExecution.execution.child.pid,
1370
+ childStartTime: preparedExecution.execution.child.startTime
1371
+ });
732
1372
  try {
733
1373
  writeSandboxControlResultEvidence(manifest, request.id, execution.result);
734
1374
  execution.resultEvidenceWritten = true;
735
1375
  execution.settled = true;
1376
+ appendBrokerAudit(manifest, 'executor-result-evidence-written', {
1377
+ ...requestAuditFields(manifest, request),
1378
+ resultPath: path.join(manifest.processingDir, request.id, 'result.json'),
1379
+ ...resultAuditFields(execution.result)
1380
+ });
736
1381
  } catch (error) {
737
1382
  execution.failure = error;
1383
+ appendBrokerAudit(manifest, 'executor-result-evidence-failed', {
1384
+ ...requestAuditFields(manifest, request),
1385
+ errorType: error instanceof Error ? error.name : typeof error,
1386
+ });
738
1387
  }
739
1388
  },
740
- (error) => { execution.failure = error; execution.settled = true; }
1389
+ (error) => {
1390
+ execution.failure = error;
1391
+ execution.settled = true;
1392
+ appendBrokerAudit(manifest, 'executor-failed', {
1393
+ ...requestAuditFields(manifest, request),
1394
+ childPid: preparedExecution.execution.child.pid,
1395
+ childStartTime: preparedExecution.execution.child.startTime,
1396
+ errorType: error instanceof Error ? error.name : typeof error,
1397
+ });
1398
+ }
741
1399
  );
742
1400
  if (!brokerOwns()) {
743
1401
  prepared.terminate(false);
@@ -751,9 +1409,16 @@ export async function serveSandboxControl(
751
1409
  retiring = true;
752
1410
  continue;
753
1411
  }
1412
+ criticalRequestPhase(manifest, request, 'accepted-authorized', 'in-progress');
754
1413
  writeAcceptedResponse(manifest, {
755
1414
  version: 2, id, phase: 'accepted', exitCode: null, stdout: '', stderr: '', error: null
756
1415
  });
1416
+ writeSandboxControlTransition(manifest, { requestId: request.id, phase: 'accepted-committed' });
1417
+ appendBrokerAudit(manifest, 'request-accepted', {
1418
+ ...requestAuditFields(manifest, request),
1419
+ acceptedPath: acceptedResponsePath(manifest, id),
1420
+ executionPhase: 'prepared'
1421
+ });
757
1422
  if (!brokerOwns()) {
758
1423
  prepared.terminate(false);
759
1424
  active = null;
@@ -761,8 +1426,19 @@ export async function serveSandboxControl(
761
1426
  break;
762
1427
  }
763
1428
  try {
1429
+ criticalRequestPhase(manifest, request, 'start-authorized', 'in-progress', `${prepared.execution.child.pid}`);
1430
+ appendBrokerAudit(manifest, 'executor-start', {
1431
+ ...requestAuditFields(manifest, request),
1432
+ childPid: prepared.execution.child.pid,
1433
+ childStartTime: prepared.execution.child.startTime,
1434
+ executionPath: executionPath(manifest, request.id)
1435
+ });
764
1436
  prepared.start(brokerOwns);
765
1437
  } catch (error) {
1438
+ appendBrokerAudit(manifest, 'executor-start-failed', {
1439
+ ...requestAuditFields(manifest, request),
1440
+ errorType: error instanceof Error ? error.name : typeof error,
1441
+ });
766
1442
  const owned = brokerOwns();
767
1443
  prepared.terminate(owned);
768
1444
  if (!owned || !brokerOwns()) {
@@ -784,19 +1460,35 @@ export async function serveSandboxControl(
784
1460
  retiring = true;
785
1461
  break;
786
1462
  }
787
- appendSandboxControlAudit(manifest, 'executor-gate-failed', { requestId: id });
1463
+ appendBrokerAudit(manifest, 'executor-gate-failed', { requestId: id });
788
1464
  continue;
789
1465
  }
790
1466
  } catch (error) {
1467
+ if (prepared) {
1468
+ try { prepared.terminate(brokerOwns()); } catch { /* cleanup is best effort after a failed admission */ }
1469
+ prepared = null;
1470
+ active = null;
1471
+ }
791
1472
  if (!brokerOwns()) {
792
1473
  retiring = true;
793
1474
  break;
794
1475
  }
1476
+ const detail = controlError(error);
1477
+ appendBrokerAudit(manifest, 'request-rejected', {
1478
+ requestId: id,
1479
+ requestFamily: validatedRequest?.family ?? null,
1480
+ sandboxTaskId: manifest.taskId,
1481
+ requestPath: claimed,
1482
+ errorCode: detail.code,
1483
+ errorRetryable: detail.retryable,
1484
+ errorType: error instanceof Error ? error.name : typeof error,
1485
+ });
795
1486
  writeSandboxControlResponse(manifest, rejected(id, error));
796
1487
  if (!brokerOwns()) {
797
1488
  retiring = true;
798
1489
  break;
799
1490
  }
1491
+ removeAcceptedResponse(manifest, id);
800
1492
  if (claimed) fs.rmSync(path.dirname(claimed), { recursive: true, force: true });
801
1493
  }
802
1494
  }
@@ -818,7 +1510,8 @@ export async function serveSandboxControl(
818
1510
  }
819
1511
  }
820
1512
  if (owned && active.result && active.resultEvidenceWritten) {
821
- if (publishExecutionResult(manifest, active.request, active.result, brokerOwns)) {
1513
+ if (consumeLifecycleAuthorityForResult(manifest, active)
1514
+ && publishExecutionResult(manifest, active.request, active.result, broker, brokerOwns)) {
822
1515
  if (brokerOwns()) {
823
1516
  removeAcceptedResponse(manifest, active.request.id);
824
1517
  fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
@@ -830,7 +1523,11 @@ export async function serveSandboxControl(
830
1523
  const terminationConfirmed = active.prepared.terminate(owned);
831
1524
  if (owned && brokerOwns() && active.request.family === 'task-finalization') {
832
1525
  const recovered = finalizationRecoveryResponse(manifest, active.request.id, 0);
833
- if (recovered.status === 'matched' && recovered.response && writeSandboxControlResponse(manifest, recovered.response)) {
1526
+ const view = recovered.status === 'matched'
1527
+ ? publishFinalizationTaskView(manifest, broker, active.request.id, 'healthy', null, null)
1528
+ : null;
1529
+ if (view?.state !== 'unknown' && recovered.status === 'matched' && recovered.response
1530
+ && writeSandboxControlResponse(manifest, recovered.response)) {
834
1531
  if (terminationConfirmed && brokerOwns()) {
835
1532
  removeAcceptedResponse(manifest, active.request.id);
836
1533
  fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
@@ -844,7 +1541,7 @@ export async function serveSandboxControl(
844
1541
  }
845
1542
  }
846
1543
  if (brokerOwns()) {
847
- appendSandboxControlAudit(manifest, 'broker-stop', { pid: broker.pid, brokerId: broker.brokerId });
1544
+ appendDiagnosticAudit(manifest, 'broker-stop', { pid: broker.pid, brokerId: broker.brokerId });
848
1545
  try {
849
1546
  if (fs.readFileSync(brokerPath, 'utf8') === brokerRecord) fs.unlinkSync(brokerPath);
850
1547
  } catch {