@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,4 +1,6 @@
1
1
  import path from 'node:path';
2
+ import crypto from 'node:crypto';
3
+ import fs from 'node:fs';
2
4
 
3
5
  import { normalizeAgentToken } from '../agent-clients/tokens.ts';
4
6
  import { isAgentClientId } from '../agent-clients/types.ts';
@@ -37,9 +39,484 @@ import {
37
39
  type TaskLifecycleRequest,
38
40
  type TaskLifecycleResult
39
41
  } from './lifecycle.ts';
42
+ import {
43
+ recoverStartedLifecycleUnderLock,
44
+ recoveryFailure,
45
+ type LifecycleRecoveryResult
46
+ } from './lifecycle-recovery.ts';
47
+ import type { LifecycleRecoveryRequest } from './lifecycle-recovery.ts';
48
+ import { locateActivityLog } from './activity-log.ts';
40
49
  import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.ts';
41
50
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
42
51
  import { verifyTaskEvent } from './verification.ts';
52
+ import { createCodexCapabilityStore } from '../agent-clients/adapters/codex-lifecycle/capability-store.ts';
53
+ import {
54
+ findArtifactRecoveryIntentsByOperation,
55
+ writeArtifactRecoveryIntent,
56
+ type ArtifactRecoveryIntent
57
+ } from './artifact-repair-intent.ts';
58
+ import {
59
+ computeLifecycleBuildIdentity,
60
+ verifyLifecycleBuildIdentity,
61
+ type LifecycleBuildIdentity
62
+ } from '../agent-clients/adapters/codex-lifecycle/build-identity.ts';
63
+
64
+ export type LifecycleAuthorityPhase =
65
+ | 'orchestration.prepare'
66
+ | 'artifact.finalize-local'
67
+ | 'task-event.completed';
68
+
69
+ export type LifecycleAuthorityRequestV1 = Readonly<{
70
+ version: 1;
71
+ requestId: string;
72
+ operationId: string;
73
+ phase: LifecycleAuthorityPhase;
74
+ taskId: string;
75
+ family: 'analysis' | 'plan' | 'code';
76
+ artifact: string;
77
+ round: number;
78
+ lifecycleRequestId: string;
79
+ authorityRef: string;
80
+ expectedControlGeneration: string;
81
+ expectedControllerInstanceDigest: string;
82
+ expectedBuildIdentityDigest: string;
83
+ expectedHookDefinitionHash: string;
84
+ }>;
85
+
86
+ export type LifecycleRecoveryAttestationV1 = Readonly<{
87
+ version: 1;
88
+ attestationId: string;
89
+ authorityRefDigest: string;
90
+ operationId: string;
91
+ phase: LifecycleAuthorityPhase;
92
+ requestId: string;
93
+ lifecycleRequestId: string;
94
+ taskId: string;
95
+ family: 'analysis' | 'plan' | 'code';
96
+ artifact: string;
97
+ round: number;
98
+ controlGeneration: string;
99
+ controllerInstanceDigest: string;
100
+ sessionId: string;
101
+ turnId: string;
102
+ toolUseId: string;
103
+ buildIdentityDigest: string;
104
+ hookDefinitionHash: string;
105
+ issuedAt: number;
106
+ expiresAt: number;
107
+ }>;
108
+
109
+ export type LifecycleAuthorityResponseV1 = Readonly<{
110
+ version: 1;
111
+ requestId: string;
112
+ operationId: string;
113
+ phase: LifecycleAuthorityPhase;
114
+ status: 'issued' | 'already-completed' | 'rejected';
115
+ attestation: LifecycleRecoveryAttestationV1 | null;
116
+ error: { code: string; message: string } | null;
117
+ }>;
118
+
119
+ type LifecycleAuthorityIssuerOptions = Readonly<{
120
+ controllerBinding?: TaskControlControllerBinding | null;
121
+ capabilityStore?: ReturnType<typeof createCodexCapabilityStore>;
122
+ buildIdentity?: LifecycleBuildIdentity;
123
+ now?: () => number;
124
+ operationAttestations?: Map<string, LifecycleRecoveryAttestationV1>;
125
+ }>;
126
+
127
+ export type LifecycleRecoveryOperationQueryV1 = Readonly<{
128
+ version: 1;
129
+ operationId: string;
130
+ status: 'unknown' | 'in-progress' | 'committed';
131
+ capabilityState: 'unknown' | 'reserved' | 'consumed';
132
+ phases: readonly Readonly<{
133
+ phase: LifecycleAuthorityPhase;
134
+ requestId: string;
135
+ attestationId: string | null;
136
+ state: 'issued' | 'consumed' | 'observed';
137
+ }>[];
138
+ }>;
139
+
140
+ const lifecycleAuthorityPhases: readonly LifecycleAuthorityPhase[] = [
141
+ 'orchestration.prepare', 'artifact.finalize-local', 'task-event.completed'
142
+ ];
143
+ const lifecycleAuthorityFamilies = ['analysis', 'plan', 'code'] as const;
144
+ const lifecycleAuthorityArtifact = /^(?:analysis|plan|code)(?:-r[1-9]\d*)?\.md$/u;
145
+ const lifecycleAuthorityDigest = (value: string): string => crypto.createHash('sha256').update(value, 'utf8').digest('hex');
146
+ const lifecycleAuthorityBuildDigest = (value: LifecycleBuildIdentity): string => lifecycleAuthorityDigest(JSON.stringify(value));
147
+
148
+ export function lifecycleRecoveryAttestationDigest(value: LifecycleRecoveryAttestationV1): string {
149
+ return lifecycleAuthorityDigest(JSON.stringify(value));
150
+ }
151
+
152
+ function authorityExactKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {
153
+ return Object.keys(value).sort().join(',') === [...keys].sort().join(',');
154
+ }
155
+
156
+ export function validateLifecycleAuthorityRequest(value: unknown): LifecycleAuthorityRequestV1 {
157
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('LIFECYCLE_AUTHORITY_REQUEST_INVALID');
158
+ const request = value as Record<string, unknown>;
159
+ if (!authorityExactKeys(request, [
160
+ 'artifact', 'authorityRef', 'expectedBuildIdentityDigest', 'expectedControlGeneration',
161
+ 'expectedControllerInstanceDigest', 'expectedHookDefinitionHash', 'family', 'lifecycleRequestId',
162
+ 'operationId', 'phase', 'requestId', 'round', 'taskId', 'version'
163
+ ])
164
+ || request.version !== 1
165
+ || typeof request.requestId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(request.requestId)
166
+ || typeof request.operationId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(request.operationId)
167
+ || !lifecycleAuthorityPhases.includes(request.phase as LifecycleAuthorityPhase)
168
+ || typeof request.taskId !== 'string' || !/^TASK-\d{8}-\d{6}$/u.test(request.taskId)
169
+ || !lifecycleAuthorityFamilies.includes(request.family as typeof lifecycleAuthorityFamilies[number])
170
+ || typeof request.artifact !== 'string' || !lifecycleAuthorityArtifact.test(request.artifact)
171
+ || !Number.isSafeInteger(request.round) || (request.round as number) < 1
172
+ || typeof request.lifecycleRequestId !== 'string' || !request.lifecycleRequestId || /[\r\n]/u.test(request.lifecycleRequestId)
173
+ || typeof request.authorityRef !== 'string' || !request.authorityRef || /[\r\n]/u.test(request.authorityRef)
174
+ || typeof request.expectedControlGeneration !== 'string' || !request.expectedControlGeneration
175
+ || typeof request.expectedControllerInstanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(request.expectedControllerInstanceDigest)
176
+ || typeof request.expectedBuildIdentityDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(request.expectedBuildIdentityDigest)
177
+ || typeof request.expectedHookDefinitionHash !== 'string' || !/^[a-f0-9]{64}$/u.test(request.expectedHookDefinitionHash)) {
178
+ throw new Error('LIFECYCLE_AUTHORITY_REQUEST_INVALID');
179
+ }
180
+ const expectedArtifactFamily = request.artifact as string;
181
+ if (!expectedArtifactFamily.startsWith(request.family as string)) throw new Error('LIFECYCLE_AUTHORITY_REQUEST_INVALID');
182
+ return request as LifecycleAuthorityRequestV1;
183
+ }
184
+
185
+ export function validateLifecycleRecoveryAttestation(
186
+ value: unknown,
187
+ now = Date.now()
188
+ ): LifecycleRecoveryAttestationV1 {
189
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('LIFECYCLE_AUTHORITY_ATTESTATION_INVALID');
190
+ const attestation = value as Record<string, unknown>;
191
+ if (!authorityExactKeys(attestation, [
192
+ 'artifact', 'attestationId', 'authorityRefDigest', 'buildIdentityDigest', 'controlGeneration',
193
+ 'controllerInstanceDigest', 'expiresAt', 'family', 'hookDefinitionHash', 'issuedAt',
194
+ 'lifecycleRequestId', 'operationId', 'phase', 'requestId', 'round', 'sessionId',
195
+ 'taskId', 'toolUseId', 'turnId', 'version'
196
+ ])
197
+ || attestation.version !== 1
198
+ || typeof attestation.attestationId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(attestation.attestationId)
199
+ || typeof attestation.authorityRefDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.authorityRefDigest)
200
+ || typeof attestation.operationId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(attestation.operationId)
201
+ || !lifecycleAuthorityPhases.includes(attestation.phase as LifecycleAuthorityPhase)
202
+ || typeof attestation.requestId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(attestation.requestId)
203
+ || typeof attestation.lifecycleRequestId !== 'string' || !attestation.lifecycleRequestId
204
+ || typeof attestation.taskId !== 'string' || !/^TASK-\d{8}-\d{6}$/u.test(attestation.taskId)
205
+ || !lifecycleAuthorityFamilies.includes(attestation.family as typeof lifecycleAuthorityFamilies[number])
206
+ || typeof attestation.artifact !== 'string' || !lifecycleAuthorityArtifact.test(attestation.artifact)
207
+ || !Number.isSafeInteger(attestation.round) || (attestation.round as number) < 1
208
+ || typeof attestation.controlGeneration !== 'string' || !attestation.controlGeneration
209
+ || typeof attestation.controllerInstanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.controllerInstanceDigest)
210
+ || typeof attestation.sessionId !== 'string' || !attestation.sessionId
211
+ || typeof attestation.turnId !== 'string' || !attestation.turnId
212
+ || typeof attestation.toolUseId !== 'string' || !attestation.toolUseId
213
+ || typeof attestation.buildIdentityDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.buildIdentityDigest)
214
+ || typeof attestation.hookDefinitionHash !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.hookDefinitionHash)
215
+ || !Number.isSafeInteger(attestation.issuedAt) || !Number.isSafeInteger(attestation.expiresAt)
216
+ || (attestation.expiresAt as number) <= (attestation.issuedAt as number)
217
+ || (attestation.expiresAt as number) <= now) {
218
+ throw new Error('LIFECYCLE_AUTHORITY_ATTESTATION_INVALID');
219
+ }
220
+ return attestation as LifecycleRecoveryAttestationV1;
221
+ }
222
+
223
+ const authorityOperationMap = new WeakMap<object, Map<string, LifecycleRecoveryAttestationV1>>();
224
+ const authorityRecords = new Map<string, Readonly<{
225
+ store: ReturnType<typeof createCodexCapabilityStore>;
226
+ capabilityRef: string;
227
+ expected: Readonly<{
228
+ taskId: string;
229
+ hookDefinitionHash: string;
230
+ buildIdentity: LifecycleBuildIdentity;
231
+ controller: TaskControlControllerBinding;
232
+ }>;
233
+ }>>();
234
+ const consumedAuthorityPhases = new Set<string>();
235
+
236
+ export function issueLifecycleRecoveryAttestation(
237
+ input: LifecycleAuthorityRequestV1,
238
+ options: LifecycleAuthorityIssuerOptions = {}
239
+ ): LifecycleAuthorityResponseV1 {
240
+ let request: LifecycleAuthorityRequestV1;
241
+ try { request = validateLifecycleAuthorityRequest(input); }
242
+ catch (error) {
243
+ return { version: 1, requestId: String((input as { requestId?: unknown })?.requestId ?? ''), operationId: String((input as { operationId?: unknown })?.operationId ?? ''), phase: (input as { phase?: LifecycleAuthorityPhase })?.phase ?? 'artifact.finalize-local', status: 'rejected', attestation: null, error: { code: 'LIFECYCLE_AUTHORITY_REQUEST_INVALID', message: error instanceof Error ? error.message : String(error) } };
244
+ }
245
+ const binding = options.controllerBinding ?? null;
246
+ if (!binding) return authorityRejected(request, 'LOCAL_EXECUTION_AUTHORITY_UNAVAILABLE', 'current controller authority is unavailable');
247
+ if (binding.controlGeneration !== request.expectedControlGeneration
248
+ || binding.instanceDigest !== request.expectedControllerInstanceDigest) {
249
+ return authorityRejected(request, 'LIFECYCLE_AUTHORITY_CONTROLLER_MISMATCH', 'current controller binding does not match the request');
250
+ }
251
+ const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(process.cwd());
252
+ if (lifecycleAuthorityBuildDigest(buildIdentity) !== request.expectedBuildIdentityDigest) {
253
+ return authorityRejected(request, 'LIFECYCLE_AUTHORITY_BUILD_MISMATCH', 'current lifecycle build does not match the request');
254
+ }
255
+ const store = options.capabilityStore ?? createCodexCapabilityStore();
256
+ const registry = options.operationAttestations ?? authorityOperationMap.get(store) ?? new Map<string, LifecycleRecoveryAttestationV1>();
257
+ authorityOperationMap.set(store, registry);
258
+ const key = `${request.requestId}\0${request.operationId}\0${request.phase}`;
259
+ const prior = registry.get(key);
260
+ if (prior) return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'already-completed', attestation: prior, error: null };
261
+ try {
262
+ const expected = {
263
+ taskId: request.taskId,
264
+ hookDefinitionHash: request.expectedHookDefinitionHash,
265
+ buildIdentity,
266
+ controller: binding
267
+ };
268
+ const record = store.reserveReference(request.authorityRef, request.operationId, expected);
269
+ if (!record.sessionId || !record.turnId || !record.toolUseId) throw new Error('CODEX_CAPABILITY_IDENTITY_INVALID: capability hook attestation is incomplete');
270
+ const now = (options.now ?? Date.now)();
271
+ const attestation = validateLifecycleRecoveryAttestation({
272
+ version: 1,
273
+ attestationId: crypto.randomUUID(),
274
+ authorityRefDigest: lifecycleAuthorityDigest(request.authorityRef),
275
+ operationId: request.operationId,
276
+ phase: request.phase,
277
+ requestId: request.requestId,
278
+ lifecycleRequestId: request.lifecycleRequestId,
279
+ taskId: request.taskId,
280
+ family: request.family,
281
+ artifact: request.artifact,
282
+ round: request.round,
283
+ controlGeneration: binding.controlGeneration,
284
+ controllerInstanceDigest: binding.instanceDigest,
285
+ sessionId: record.sessionId,
286
+ turnId: record.turnId,
287
+ toolUseId: record.toolUseId,
288
+ buildIdentityDigest: lifecycleAuthorityBuildDigest(buildIdentity),
289
+ hookDefinitionHash: request.expectedHookDefinitionHash,
290
+ issuedAt: now,
291
+ expiresAt: Math.min(record.expiresAt, now + 30_000)
292
+ }, now);
293
+ store.reserveRecoveryPhase(
294
+ request.authorityRef,
295
+ request.operationId,
296
+ request.phase,
297
+ request.requestId,
298
+ expected
299
+ );
300
+ registry.set(key, attestation);
301
+ authorityRecords.set(attestation.attestationId, { store, capabilityRef: request.authorityRef, expected });
302
+ return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'issued', attestation, error: null };
303
+ } catch (error) {
304
+ return authorityRejected(request, error instanceof Error && error.name.startsWith('CODEX_') ? error.name : 'LIFECYCLE_AUTHORITY_REJECTED', error instanceof Error ? error.message : String(error));
305
+ }
306
+ }
307
+
308
+ export function consumeLifecycleRecoveryAttestation(
309
+ value: LifecycleRecoveryAttestationV1,
310
+ now = Date.now()
311
+ ): void {
312
+ const attestation = validateLifecycleRecoveryAttestation(value, now);
313
+ if (consumedAuthorityPhases.has(attestation.attestationId)) return;
314
+ const record = authorityRecords.get(attestation.attestationId);
315
+ if (!record) throw new Error('LIFECYCLE_AUTHORITY_ATTESTATION_UNKNOWN');
316
+ record.store.consumeRecoveryPhase(
317
+ record.capabilityRef,
318
+ attestation.operationId,
319
+ attestation.phase,
320
+ attestation.requestId,
321
+ record.expected
322
+ );
323
+ if (attestation.phase === 'task-event.completed') {
324
+ record.store.consumeReference(record.capabilityRef, attestation.operationId, record.expected);
325
+ }
326
+ consumedAuthorityPhases.add(attestation.attestationId);
327
+ }
328
+
329
+ export function queryLifecycleRecoveryOperation(
330
+ operationId: string,
331
+ options: Readonly<{
332
+ repoRoot?: string;
333
+ capabilityStore?: ReturnType<typeof createCodexCapabilityStore>;
334
+ }> = {}
335
+ ): LifecycleRecoveryOperationQueryV1 {
336
+ if (!/^[a-f0-9-]{16,64}$/u.test(operationId)) {
337
+ throw new Error('LIFECYCLE_RECOVERY_OPERATION_INVALID');
338
+ }
339
+ const store = options.capabilityStore ?? createCodexCapabilityStore();
340
+ const registry = authorityOperationMap.get(store);
341
+ const known = [...(registry?.values() ?? [])].filter((value) => value.operationId === operationId);
342
+ const intents = options.repoRoot ? findArtifactRecoveryIntentsByOperation(options.repoRoot, operationId) : [];
343
+ const capabilities = store.findByRecoveryOperation(operationId);
344
+ const phaseMap = new Map<string, LifecycleRecoveryOperationQueryV1['phases'][number]>();
345
+ for (const capability of capabilities) {
346
+ for (const phase of capability.recoveryPhases) {
347
+ phaseMap.set(`${phase.phase}\0${phase.requestId}`, {
348
+ phase: phase.phase,
349
+ requestId: phase.requestId,
350
+ attestationId: null,
351
+ state: phase.state === 'consumed' ? 'consumed' : 'observed'
352
+ });
353
+ }
354
+ }
355
+ for (const attestation of known) {
356
+ const key = `${attestation.phase}\0${attestation.requestId}`;
357
+ const durable = phaseMap.get(key);
358
+ phaseMap.set(key, {
359
+ phase: attestation.phase,
360
+ requestId: attestation.requestId,
361
+ attestationId: attestation.attestationId,
362
+ state: durable?.state === 'consumed' || consumedAuthorityPhases.has(attestation.attestationId)
363
+ ? 'consumed' : 'issued'
364
+ });
365
+ }
366
+ for (const intent of intents) {
367
+ if (!intent.phase) continue;
368
+ const key = `${intent.phase}\0${intent.requestId}`;
369
+ if (phaseMap.has(key)) continue;
370
+ phaseMap.set(key, {
371
+ phase: intent.phase,
372
+ requestId: intent.requestId,
373
+ attestationId: null,
374
+ state: intent.state === 'consumed' ? 'consumed' : 'observed'
375
+ });
376
+ }
377
+ const capabilityState = capabilities.some((record) => record.recoveryState === 'consumed')
378
+ ? 'consumed'
379
+ : capabilities.some((record) => record.recoveryState === 'reserved') ? 'reserved' : 'unknown';
380
+ const committed = capabilityState === 'consumed' || intents.some((intent) => intent.state === 'consumed');
381
+ return {
382
+ version: 1,
383
+ operationId,
384
+ status: committed ? 'committed' : capabilityState === 'reserved' || phaseMap.size > 0 ? 'in-progress' : 'unknown',
385
+ capabilityState,
386
+ phases: Object.freeze([...phaseMap.values()])
387
+ };
388
+ }
389
+
390
+ type LifecycleRecoveryCompensationOptions = Readonly<{
391
+ repoRoot: string;
392
+ controllerBinding: TaskControlControllerBinding;
393
+ capabilityStore?: ReturnType<typeof createCodexCapabilityStore>;
394
+ buildIdentity?: LifecycleBuildIdentity;
395
+ now?: () => number;
396
+ }>;
397
+
398
+ function lifecycleRecoveryCompensationError(code: string, message: string): Error {
399
+ const error = new Error(`${code}: ${message}`);
400
+ error.name = code;
401
+ return error;
402
+ }
403
+
404
+ function lifecycleRecoveryEventCommitted(
405
+ selector: LifecycleAuthorityRequestV1,
406
+ repoRoot: string
407
+ ): boolean {
408
+ const resolved = resolveTaskRef(selector.taskId, { repoRoot });
409
+ if (!resolved.ok || resolved.taskId !== selector.taskId) return false;
410
+ let content: string;
411
+ try { content = fs.readFileSync(resolved.taskMdPath, 'utf8'); }
412
+ catch { return false; }
413
+ const section = locateActivityLog(content);
414
+ if (!section) return false;
415
+ const labels = { analysis: 'Analyze Task', plan: 'Plan Task', code: 'Code Task' } as const;
416
+ const prefix = `${labels[selector.family]} (Round ${selector.round}`;
417
+ return section.entries.some((entry) => {
418
+ if (entry.step.endsWith(' [started]') || !entry.step.startsWith(prefix)) return false;
419
+ const delimiter = entry.step[prefix.length];
420
+ return (delimiter === ',' || delimiter === ')') && entry.note.endsWith(`→ ${selector.artifact}`);
421
+ });
422
+ }
423
+
424
+ function lifecycleRecoveryIntentMatches(
425
+ intent: ArtifactRecoveryIntent,
426
+ selector: LifecycleAuthorityRequestV1
427
+ ): boolean {
428
+ return intent.taskId === selector.taskId
429
+ && intent.family === selector.family
430
+ && intent.artifact === selector.artifact
431
+ && (intent.state === 'commit-started' || intent.state === 'consumed');
432
+ }
433
+
434
+ /**
435
+ * Completes only the durable tail of a committed task event after a process
436
+ * restart. The task event itself is the commit boundary and is never replayed.
437
+ */
438
+ export function recoverLifecycleRecoveryOperation(
439
+ input: LifecycleAuthorityRequestV1,
440
+ options: LifecycleRecoveryCompensationOptions
441
+ ): LifecycleRecoveryOperationQueryV1 {
442
+ const selector = validateLifecycleAuthorityRequest(input);
443
+ if (selector.phase !== 'task-event.completed') {
444
+ throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PHASE_UNSUPPORTED', 'only task-event.completed supports durable compensation');
445
+ }
446
+ const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(options.repoRoot);
447
+ if (selector.expectedBuildIdentityDigest !== lifecycleAuthorityBuildDigest(buildIdentity)) {
448
+ throw lifecycleRecoveryCompensationError('LIFECYCLE_AUTHORITY_BUILD_MISMATCH', 'current lifecycle build does not match the recovery request');
449
+ }
450
+ if (selector.expectedControlGeneration !== options.controllerBinding.controlGeneration
451
+ || selector.expectedControllerInstanceDigest !== options.controllerBinding.instanceDigest) {
452
+ throw lifecycleRecoveryCompensationError('LIFECYCLE_AUTHORITY_CONTROLLER_MISMATCH', 'current controller binding does not match the recovery request');
453
+ }
454
+ const store = options.capabilityStore ?? createCodexCapabilityStore();
455
+ const expected = {
456
+ taskId: selector.taskId,
457
+ hookDefinitionHash: selector.expectedHookDefinitionHash,
458
+ buildIdentity,
459
+ controller: options.controllerBinding
460
+ };
461
+ const capabilities = store.findByRecoveryOperation(selector.operationId);
462
+ if (capabilities.length !== 1 || capabilities[0]!.capabilityRefDigest !== lifecycleAuthorityDigest(selector.authorityRef)) {
463
+ throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PROVENANCE_INVALID', 'durable capability provenance does not identify this operation');
464
+ }
465
+ const capability = capabilities[0]!;
466
+ const build = verifyLifecycleBuildIdentity(capability.buildIdentity, buildIdentity);
467
+ const expiredReserved = capability.status === 'expired' && capability.recoveryState === 'reserved';
468
+ if (!build.ok
469
+ || capability.taskId !== selector.taskId
470
+ || capability.hookDefinitionHash !== selector.expectedHookDefinitionHash
471
+ || capability.controller?.controlGeneration !== options.controllerBinding.controlGeneration
472
+ || capability.controller?.instanceDigest !== options.controllerBinding.instanceDigest
473
+ || capability.recoveryState === 'unreserved'
474
+ || capability.recoveryOperationId !== selector.operationId
475
+ || (!['attested', 'consumed'].includes(capability.status) && !expiredReserved)) {
476
+ throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PROVENANCE_INVALID', 'durable capability provenance does not match the recovery request');
477
+ }
478
+ const phase = capability.recoveryPhases.find((entry) => entry.phase === selector.phase);
479
+ if (!phase) {
480
+ throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PHASE_MISSING', 'durable task completion phase is missing');
481
+ }
482
+ const intents = findArtifactRecoveryIntentsByOperation(options.repoRoot, selector.operationId)
483
+ .filter((intent) => intent.taskId === selector.taskId
484
+ && intent.family === selector.family
485
+ && intent.artifact === selector.artifact);
486
+ if (intents.length !== 1) {
487
+ throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact recovery intent does not identify this task event');
488
+ }
489
+ const intent = intents[0]!;
490
+ if (!lifecycleRecoveryIntentMatches(intent, selector)
491
+ || intent.phase !== selector.phase
492
+ || intent.requestId !== selector.lifecycleRequestId
493
+ || intent.authorityDigest === null) {
494
+ throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact recovery intent does not match the recovery request');
495
+ }
496
+ if (!lifecycleRecoveryEventCommitted(selector, options.repoRoot)) {
497
+ throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_COMMIT_UNCONFIRMED', 'task completion is not durably present; compensation is not allowed');
498
+ }
499
+ if (capability.recoveryState === 'reserved') {
500
+ if (phase.state === 'issued') {
501
+ store.consumeRecoveryPhase(selector.authorityRef, selector.operationId, selector.phase, phase.requestId, expected, { allowExpiredReserved: true });
502
+ }
503
+ store.consumeReference(selector.authorityRef, selector.operationId, expected, { allowExpiredReserved: true });
504
+ } else if (phase.state !== 'consumed') {
505
+ throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_STATE_INVALID', 'consumed capability has an unconsumed completion phase');
506
+ }
507
+ if (intent.state !== 'consumed') {
508
+ writeArtifactRecoveryIntent(options.repoRoot, {
509
+ ...intent,
510
+ state: 'consumed',
511
+ updatedAt: (options.now ?? Date.now)()
512
+ }, { expected: intent });
513
+ }
514
+ return queryLifecycleRecoveryOperation(selector.operationId, { repoRoot: options.repoRoot, capabilityStore: store });
515
+ }
516
+
517
+ function authorityRejected(request: LifecycleAuthorityRequestV1, code: string, message: string): LifecycleAuthorityResponseV1 {
518
+ return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'rejected', attestation: null, error: { code, message } };
519
+ }
43
520
 
44
521
  export type TaskControlControllerBinding = Readonly<{
45
522
  instanceDigest: string;
@@ -53,6 +530,7 @@ export type TaskControlExecutionContext =
53
530
  repoRoot: string;
54
531
  runtimeDir?: string;
55
532
  controllerBinding?: TaskControlControllerBinding | null;
533
+ lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
56
534
  }>
57
535
  | Readonly<{
58
536
  source: 'sandbox-executor';
@@ -66,6 +544,7 @@ export type TaskControlExecutionContext =
66
544
  requestId: string;
67
545
  diagnosticLog?: OrchestrationDiagnosticLogger;
68
546
  controllerBinding?: TaskControlControllerBinding | null;
547
+ lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
69
548
  }>;
70
549
 
71
550
  export type TaskControlOrchestrationIntent =
@@ -92,7 +571,15 @@ export type TaskControlOperation =
92
571
  options?: Omit<OrchestrationOptions, 'repoRoot'>;
93
572
  }>;
94
573
 
95
- export type TaskControlDispatchResult = TaskLifecycleResult | TaskFinalizationResult | OrchestrationResult;
574
+ export type TaskControlDispatchResult = TaskLifecycleResult | LifecycleRecoveryResult | TaskFinalizationResult | OrchestrationResult;
575
+
576
+ export function taskControlOperationKey(operation: TaskControlOperation): string {
577
+ if (operation.family === 'task-finalization') return 'complete';
578
+ if (operation.family === 'task-lifecycle') return operation.request.intent;
579
+ return operation.intent === 'route'
580
+ ? operation.input && operation.input.completed === true ? 'route.clean-completion' : 'route.read'
581
+ : operation.intent;
582
+ }
96
583
 
97
584
  export type TaskLifecycleControlRequest = TaskLifecycleRequest & Readonly<{
98
585
  overrideTicket?: string;
@@ -121,13 +608,18 @@ export function createDirectHostExecutionContext(params: Readonly<{
121
608
  repoRoot: string;
122
609
  runtimeDir?: string;
123
610
  controllerBinding?: TaskControlControllerBinding | null;
611
+ lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
124
612
  }>): TaskControlExecutionContext {
125
613
  return {
126
614
  source: 'direct-host',
127
615
  mode: 'direct-host',
128
616
  repoRoot: absolute('repoRoot', params.repoRoot),
129
617
  ...(params.runtimeDir === undefined ? {} : { runtimeDir: absolute('runtimeDir', params.runtimeDir) }),
130
- ...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) })
618
+ ...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
619
+ ...(params.lifecycleRecoveryAttestation === undefined ? {} : {
620
+ lifecycleRecoveryAttestation: params.lifecycleRecoveryAttestation === null
621
+ ? null : validateLifecycleRecoveryAttestation(params.lifecycleRecoveryAttestation)
622
+ })
131
623
  };
132
624
  }
133
625
 
@@ -141,6 +633,7 @@ export function createSandboxExecutorExecutionContext(params: Readonly<{
141
633
  requestId: string;
142
634
  diagnosticLog?: OrchestrationDiagnosticLogger;
143
635
  controllerBinding?: TaskControlControllerBinding | null;
636
+ lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
144
637
  }>): TaskControlExecutionContext {
145
638
  if (!/^TASK-\d{8}-\d{6}$/u.test(params.taskId) || !params.generation || !params.requestId) {
146
639
  invalidContext('sandbox binding is incomplete');
@@ -160,7 +653,11 @@ export function createSandboxExecutorExecutionContext(params: Readonly<{
160
653
  manifestPath: absolute('manifestPath', params.manifestPath),
161
654
  requestId: params.requestId,
162
655
  ...(params.diagnosticLog === undefined ? {} : { diagnosticLog: params.diagnosticLog }),
163
- ...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) })
656
+ ...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
657
+ ...(params.lifecycleRecoveryAttestation === undefined ? {} : {
658
+ lifecycleRecoveryAttestation: params.lifecycleRecoveryAttestation === null
659
+ ? null : validateLifecycleRecoveryAttestation(params.lifecycleRecoveryAttestation)
660
+ })
164
661
  };
165
662
  }
166
663
 
@@ -170,6 +667,7 @@ export function assertTaskControlExecutionContext(context: TaskControlExecutionC
170
667
  absolute('repoRoot', context.repoRoot);
171
668
  if (context.runtimeDir !== undefined) absolute('runtimeDir', context.runtimeDir);
172
669
  binding(context.controllerBinding);
670
+ if (context.lifecycleRecoveryAttestation) validateLifecycleRecoveryAttestation(context.lifecycleRecoveryAttestation);
173
671
  return;
174
672
  }
175
673
  if (context.mode !== 'task-bound-sandbox' || !/^TASK-\d{8}-\d{6}$/u.test(context.taskId)
@@ -184,6 +682,7 @@ export function assertTaskControlExecutionContext(context: TaskControlExecutionC
184
682
  invalidContext('runtimeDir is not bound to the manifest control root');
185
683
  }
186
684
  binding(context.controllerBinding);
685
+ if (context.lifecycleRecoveryAttestation) validateLifecycleRecoveryAttestation(context.lifecycleRecoveryAttestation);
187
686
  }
188
687
 
189
688
  function operationTaskId(context: TaskControlExecutionContext, taskRef: string): void {
@@ -219,12 +718,15 @@ function lifecycleTaskId(request: TaskLifecycleControlRequest, repoRoot: string)
219
718
  async function applyLifecycleWithAuthority(
220
719
  context: TaskControlExecutionContext,
221
720
  request: TaskLifecycleControlRequest
222
- ): Promise<TaskLifecycleResult & { humanOverride?: unknown }> {
721
+ ): Promise<(TaskLifecycleResult | LifecycleRecoveryResult) & { humanOverride?: unknown }> {
223
722
  const conflict = overrideDryRunConflict(request as unknown as Record<string, unknown>);
224
723
  if (conflict) {
225
724
  return taskLifecycleFailure(request, { code: 'LIFECYCLE_PAYLOAD_INVALID', message: conflict.message });
226
725
  }
227
- const execute = async (): Promise<TaskLifecycleResult & { humanOverride?: unknown }> => {
726
+ const execute = async (): Promise<(TaskLifecycleResult | LifecycleRecoveryResult) & { humanOverride?: unknown }> => {
727
+ if (request.intent === 'recover-started') {
728
+ return recoverStartedLifecycleUnderLock(request as LifecycleRecoveryRequest, { repoRoot: context.repoRoot });
729
+ }
228
730
  const lifecycleResult = applyTaskLifecycle(request, { repoRoot: context.repoRoot });
229
731
  if (lifecycleResult.status !== 'failed' || !request.overrideTicket) return lifecycleResult;
230
732
  if (!request.overrideTarget || !request.overrideScope) {
@@ -258,6 +760,9 @@ async function applyLifecycleWithAuthority(
258
760
  );
259
761
  } catch (error) {
260
762
  if (!(error instanceof TaskExecutionLockError)) throw error;
763
+ if (request.intent === 'recover-started') {
764
+ return recoveryFailure(request as LifecycleRecoveryRequest, 'owner-unknown', error.code, error.message);
765
+ }
261
766
  return taskLifecycleFailure(request, { code: error.code, message: error.message }, taskId);
262
767
  }
263
768
  }
@@ -284,12 +789,16 @@ function orchestration(
284
789
  client: input.client as AgentClientId,
285
790
  requestedModel: input.requestedModel as string | undefined,
286
791
  requestedReasoningEffort: input.requestedReasoningEffort as string | undefined,
287
- ...(input.capabilityToken === undefined ? {} : { capabilityToken: input.capabilityToken as string })
792
+ ...(input.capabilityRef === undefined ? {} : { capabilityRef: input.capabilityRef as string }),
793
+ ...(context.lifecycleRecoveryAttestation === undefined
794
+ ? {}
795
+ : { lifecycleRecoveryAttestation: context.lifecycleRecoveryAttestation })
288
796
  };
289
797
  if (prepareInput.client === 'codex') {
290
798
  return prepareCodexOrchestrationDelegation(operation.taskRef, prepareInput, {
291
799
  repoRoot: context.repoRoot,
292
800
  orchestrationOptions: options,
801
+ deferLifecycleRecoveryConsumption: context.source === 'sandbox-executor',
293
802
  ...(context.controllerBinding ? { controllerBinding: context.controllerBinding } : {})
294
803
  });
295
804
  }
@@ -332,7 +841,7 @@ function orchestrationFailure(error: unknown): OrchestrationResult {
332
841
  export function dispatchTaskControlOperation(
333
842
  context: TaskControlExecutionContext,
334
843
  operation: Extract<TaskControlOperation, { family: 'task-lifecycle' }>
335
- ): Promise<TaskLifecycleResult & { humanOverride?: unknown }>;
844
+ ): Promise<(TaskLifecycleResult | LifecycleRecoveryResult) & { humanOverride?: unknown }>;
336
845
  export function dispatchTaskControlOperation(
337
846
  context: TaskControlExecutionContext,
338
847
  operation: Extract<TaskControlOperation, { family: 'task-finalization' }>
@@ -395,7 +904,7 @@ function operationInvalid(message: string): never {
395
904
 
396
905
  const LIFECYCLE_FLAGS = new Set([
397
906
  '--agent', '--reason', '--unblock-condition', '--note', '--alert-number', '--staging-dir', '--issue-number',
398
- '--override-ticket', '--override-target', '--override-scope', '--dry-run'
907
+ '--stage', '--round', '--artifact', '--override-ticket', '--override-target', '--override-scope', '--dry-run'
399
908
  ]);
400
909
 
401
910
  const FINALIZATION_FLAGS = new Set(['--agent']);
@@ -403,7 +912,7 @@ const FINALIZATION_FLAGS = new Set(['--agent']);
403
912
  const ORCHESTRATION_FLAGS = new Set([
404
913
  '--agent', '--max-steps', '--executor-model', '--executor-reasoning-effort', '--reviewer-model',
405
914
  '--reviewer-reasoning-effort', '--client', '--requested-model', '--requested-reasoning-effort',
406
- '--capability-token', '--parent-id', '--before-fingerprint', '--stage', '--round', '--artifact', '--role',
915
+ '--capability-ref', '--parent-id', '--before-fingerprint', '--stage', '--round', '--artifact', '--role',
407
916
  '--native-agent', '--child-id', '--spawn-mode', '--actual-model', '--actual-reasoning-effort',
408
917
  '--model-fallback-reason', '--reasoning-effort-fallback-reason', '--exit-code', '--after-fingerprint',
409
918
  '--changed-paths', '--code', '--message', '--recoverable', '--git-worktree-root'
@@ -475,8 +984,29 @@ export function parseTaskControlOperation(
475
984
  ...(value(values, '--override-scope') ? { overrideScope: value(values, '--override-scope') } : {}),
476
985
  ...(value(values, '--alert-number') ? { alertNumber: Number(value(values, '--alert-number')) } : {}),
477
986
  ...(value(values, '--issue-number') ? { issueNumber: Number(value(values, '--issue-number')) } : {}),
987
+ ...(value(values, '--stage') ? { stage: value(values, '--stage') } : {}),
988
+ ...(value(values, '--round') ? { round: Number(value(values, '--round')) } : {}),
989
+ ...(value(values, '--artifact') ? { artifact: value(values, '--artifact') } : {}),
478
990
  ...(values['--dry-run'] === true ? { dryRun: true } : {})
479
991
  };
992
+ if (intent === 'recover-started') {
993
+ const recoveryFlags = new Set(['--agent', '--stage', '--round', '--artifact', '--reason']);
994
+ for (const flag of Object.keys(values)) {
995
+ if (!recoveryFlags.has(flag)) operationInvalid(`option '${flag}' is not supported for recover-started`);
996
+ }
997
+ required(values, ['--stage', '--round', '--artifact', '--reason']);
998
+ const stage = value(values, '--stage');
999
+ if (!['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'].includes(stage ?? '')) {
1000
+ operationInvalid('--stage must be a supported lifecycle stage');
1001
+ }
1002
+ const round = Number(value(values, '--round'));
1003
+ if (!Number.isSafeInteger(round) || round < 1) operationInvalid('--round must be a positive integer');
1004
+ if (value(values, '--reason')!.includes('\n') || value(values, '--reason')!.includes('\r')) {
1005
+ operationInvalid('--reason must be a single line');
1006
+ }
1007
+ } else if (values['--stage'] !== undefined || values['--round'] !== undefined || values['--artifact'] !== undefined) {
1008
+ operationInvalid('--stage, --round, and --artifact are only supported for recover-started');
1009
+ }
480
1010
  return { family, request: input as unknown as TaskLifecycleControlRequest };
481
1011
  }
482
1012
 
@@ -519,7 +1049,7 @@ export function parseTaskControlOperation(
519
1049
  if (parsedIntent === 'prepare') {
520
1050
  input.requestedModel = value(values, '--requested-model');
521
1051
  input.requestedReasoningEffort = value(values, '--requested-reasoning-effort');
522
- input.capabilityToken = value(values, '--capability-token');
1052
+ input.capabilityRef = value(values, '--capability-ref');
523
1053
  }
524
1054
  if (parsedIntent === 'await-activation') {
525
1055
  required(values, ['--stage', '--round', '--artifact', '--role']);