@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,71 +1,200 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { isDeepStrictEqual } from 'node:util';
3
4
 
4
5
  import type { ArtifactSchemaFamily } from './artifact-schema.ts';
6
+ import { parseArtifactName } from './artifact-name.ts';
5
7
 
6
- type ArtifactRepairIntent = Readonly<{
7
- version: 1;
8
+ export type ArtifactRecoveryPhase =
9
+ | 'orchestration.prepare'
10
+ | 'artifact.finalize-local'
11
+ | 'task-event.completed';
12
+ export type ArtifactRecoveryState =
13
+ | 'awaiting-recovery'
14
+ | 'finalize-ready'
15
+ | 'commit-started'
16
+ | 'passed'
17
+ | 'consumed'
18
+ | 'aborted';
19
+
20
+ export type ArtifactRecoveryIntent = Readonly<{
21
+ version: 3;
8
22
  taskId: string;
9
23
  family: ArtifactSchemaFamily;
10
24
  artifact: string;
11
- state: 'awaiting-repair' | 'passed' | 'consumed';
12
- baselineSemanticDigest: string | null;
13
- artifactSha256: string;
14
- semanticDigest: string;
25
+ round: number;
26
+ state: ArtifactRecoveryState;
27
+ baselineSha256: string;
28
+ baselineSemanticDigest: string;
29
+ stagingId: string;
30
+ candidateSha256: string | null;
31
+ finalArtifactSha256: string | null;
32
+ finalSemanticDigest: string | null;
33
+ recoveryOperationId: string;
34
+ phase: ArtifactRecoveryPhase | null;
35
+ authorityDigest: string | null;
36
+ requestId: string;
37
+ errorCode: string | null;
38
+ errorMessage: string | null;
39
+ createdAt: number;
40
+ updatedAt: number;
15
41
  }>;
16
42
 
17
- function intentRoot(repoRoot: string): string {
43
+ export function artifactRecoveryIntentRoot(repoRoot: string): string {
18
44
  return path.join(repoRoot, '.agents', 'workspace', '.local-artifact-finalization-intents');
19
45
  }
20
46
 
47
+ export function artifactRecoveryRoot(taskDir: string, stagingId: string): string {
48
+ return path.join(taskDir, '.local-artifact-recovery', stagingId);
49
+ }
50
+
21
51
  function intentPath(repoRoot: string, taskId: string, family: ArtifactSchemaFamily, artifact: string): string {
22
- return path.join(intentRoot(repoRoot), `${taskId}-${family}-${artifact}.json`);
52
+ return path.join(artifactRecoveryIntentRoot(repoRoot), `${taskId}-${family}-${artifact}.json`);
53
+ }
54
+
55
+ function validDigest(value: unknown): value is string {
56
+ return typeof value === 'string' && /^[a-f0-9]{64}$/u.test(value);
57
+ }
58
+
59
+ function validTaskId(value: unknown): value is string {
60
+ return typeof value === 'string' && /^TASK-\d{8}-\d{6}$/u.test(value);
61
+ }
62
+
63
+ function validToken(value: unknown): value is string {
64
+ return typeof value === 'string' && value.length > 0 && !/[\r\n]/u.test(value);
65
+ }
66
+
67
+ function validRecoveryId(value: unknown): value is string {
68
+ return typeof value === 'string' && /^[a-f0-9-]{16,64}$/u.test(value);
23
69
  }
24
70
 
25
- function isArtifactRepairIntent(value: unknown): value is ArtifactRepairIntent {
71
+ function validArtifact(value: unknown, family: unknown, round: unknown): value is string {
72
+ if (typeof value !== 'string' || path.basename(value) !== value || !value.endsWith('.md')) return false;
73
+ const parsed = parseArtifactName(value);
74
+ return Boolean(parsed && parsed.family === family && parsed.round === round);
75
+ }
76
+
77
+ function exactKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {
78
+ return Object.keys(value).sort().join(',') === [...keys].sort().join(',');
79
+ }
80
+
81
+ export function isArtifactRecoveryIntent(value: unknown): value is ArtifactRecoveryIntent {
26
82
  if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
27
83
  const intent = value as Record<string, unknown>;
28
- return intent.version === 1
29
- && typeof intent.taskId === 'string' && /^TASK-\d{8}-\d{6}$/.test(intent.taskId)
30
- && typeof intent.family === 'string' && ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'].includes(intent.family)
31
- && typeof intent.artifact === 'string' && intent.artifact.length > 0
32
- && ['awaiting-repair', 'passed', 'consumed'].includes(String(intent.state))
33
- && (intent.baselineSemanticDigest === null || (typeof intent.baselineSemanticDigest === 'string' && /^[a-f0-9]{64}$/.test(intent.baselineSemanticDigest)))
34
- && typeof intent.artifactSha256 === 'string' && /^[a-f0-9]{64}$/.test(intent.artifactSha256)
35
- && typeof intent.semanticDigest === 'string' && /^[a-f0-9]{64}$/.test(intent.semanticDigest)
36
- && (intent.state === 'awaiting-repair' ? intent.baselineSemanticDigest === intent.semanticDigest : true);
84
+ return exactKeys(intent, [
85
+ 'artifact', 'authorityDigest', 'baselineSemanticDigest', 'baselineSha256', 'candidateSha256',
86
+ 'createdAt', 'errorCode', 'errorMessage', 'family', 'finalArtifactSha256', 'finalSemanticDigest',
87
+ 'phase', 'recoveryOperationId', 'requestId', 'round', 'stagingId', 'state', 'taskId', 'updatedAt',
88
+ 'version'
89
+ ])
90
+ && intent.version === 3
91
+ && validTaskId(intent.taskId)
92
+ && typeof intent.family === 'string'
93
+ && ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'].includes(intent.family)
94
+ && Number.isSafeInteger(intent.round) && (intent.round as number) > 0
95
+ && validArtifact(intent.artifact, intent.family, intent.round)
96
+ && ['awaiting-recovery', 'finalize-ready', 'commit-started', 'passed', 'consumed', 'aborted'].includes(String(intent.state))
97
+ && validDigest(intent.baselineSha256)
98
+ && validDigest(intent.baselineSemanticDigest)
99
+ && validRecoveryId(intent.stagingId)
100
+ && (intent.candidateSha256 === null || validDigest(intent.candidateSha256))
101
+ && (intent.finalArtifactSha256 === null || validDigest(intent.finalArtifactSha256))
102
+ && (intent.finalSemanticDigest === null || validDigest(intent.finalSemanticDigest))
103
+ && validRecoveryId(intent.recoveryOperationId)
104
+ && intent.stagingId === intent.recoveryOperationId
105
+ && (intent.phase === null || ['orchestration.prepare', 'artifact.finalize-local', 'task-event.completed'].includes(String(intent.phase)))
106
+ && (intent.authorityDigest === null || validDigest(intent.authorityDigest))
107
+ && validToken(intent.requestId)
108
+ && (intent.errorCode === null || validToken(intent.errorCode))
109
+ && (intent.errorMessage === null || validToken(intent.errorMessage))
110
+ && Number.isSafeInteger(intent.createdAt)
111
+ && Number.isSafeInteger(intent.updatedAt)
112
+ && (intent.state === 'awaiting-recovery'
113
+ ? intent.finalArtifactSha256 === null && intent.finalSemanticDigest === null
114
+ : true)
115
+ && (intent.state === 'finalize-ready' || intent.state === 'commit-started' || intent.state === 'passed' || intent.state === 'consumed'
116
+ ? validDigest(intent.candidateSha256) && validDigest(intent.finalArtifactSha256) && validDigest(intent.finalSemanticDigest)
117
+ : true)
118
+ && (intent.state === 'aborted' ? intent.errorCode !== null && intent.errorMessage !== null : true);
37
119
  }
38
120
 
39
- function readArtifactRepairIntent(
121
+ export function readArtifactRecoveryIntent(
40
122
  repoRoot: string,
41
123
  taskId: string,
42
124
  family: ArtifactSchemaFamily,
43
125
  artifact: string
44
- ): ArtifactRepairIntent | null {
126
+ ): ArtifactRecoveryIntent | null {
127
+ if (!validTaskId(taskId) || !validArtifact(artifact, family, parseArtifactName(artifact)?.round)) {
128
+ throw new Error('ARTIFACT_RECOVERY_INTENT_INVALID: recovery journal identity is invalid');
129
+ }
45
130
  const target = intentPath(repoRoot, taskId, family, artifact);
46
131
  if (!fs.existsSync(target)) return null;
47
132
  let value: unknown;
48
133
  try { value = JSON.parse(fs.readFileSync(target, 'utf8')) as unknown; }
49
- catch (error) { throw new Error(`ARTIFACT_REPAIR_INTENT_INVALID: ${String(error)}`); }
50
- if (!isArtifactRepairIntent(value) || value.taskId !== taskId || value.family !== family || value.artifact !== artifact) {
51
- throw new Error('ARTIFACT_REPAIR_INTENT_INVALID: repair provenance schema is invalid');
134
+ catch (error) { throw new Error(`ARTIFACT_RECOVERY_INTENT_INVALID: ${String(error)}`); }
135
+ if (!isArtifactRecoveryIntent(value) || value.taskId !== taskId || value.family !== family || value.artifact !== artifact) {
136
+ throw new Error('ARTIFACT_RECOVERY_INTENT_INVALID: recovery journal schema is invalid');
52
137
  }
53
138
  return value;
54
139
  }
55
140
 
56
- function writeArtifactRepairIntent(repoRoot: string, value: ArtifactRepairIntent): void {
57
- if (!isArtifactRepairIntent(value)) throw new Error('ARTIFACT_REPAIR_INTENT_INVALID: repair provenance schema is invalid');
58
- const directory = intentRoot(repoRoot);
141
+ export function writeArtifactRecoveryIntent(
142
+ repoRoot: string,
143
+ value: unknown,
144
+ options: Readonly<{ expected?: ArtifactRecoveryIntent | null }> = {}
145
+ ): void {
146
+ if (!isArtifactRecoveryIntent(value)) throw new Error('ARTIFACT_RECOVERY_INTENT_INVALID: recovery journal schema is invalid');
147
+ const intent = value;
148
+ const directory = artifactRecoveryIntentRoot(repoRoot);
59
149
  fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
60
- const target = intentPath(repoRoot, value.taskId, value.family, value.artifact);
61
- const temporary = `${target}.${process.pid}.${Date.now()}.tmp`;
62
- fs.writeFileSync(temporary, `${JSON.stringify(value)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
63
- try { fs.renameSync(temporary, target); }
150
+ const target = intentPath(repoRoot, intent.taskId, intent.family, intent.artifact);
151
+ const lock = `${target}.lock`;
152
+ let descriptor: number;
153
+ try { descriptor = fs.openSync(lock, 'wx', 0o600); }
64
154
  catch (error) {
65
- try { fs.unlinkSync(temporary); } catch { /* preserve primary error */ }
155
+ if ((error as NodeJS.ErrnoException).code === 'EEXIST') {
156
+ throw new Error('ARTIFACT_RECOVERY_INTENT_CONFLICT: another journal writer owns the intent');
157
+ }
66
158
  throw error;
67
159
  }
160
+ try {
161
+ const current = fs.existsSync(target)
162
+ ? readArtifactRecoveryIntent(repoRoot, intent.taskId, intent.family, intent.artifact)
163
+ : null;
164
+ if (!isDeepStrictEqual(current, options.expected ?? null)) {
165
+ throw new Error('ARTIFACT_RECOVERY_INTENT_CONFLICT: recovery journal changed during finalization');
166
+ }
167
+ const temporary = `${target}.${process.pid}.${Date.now()}.tmp`;
168
+ fs.writeFileSync(temporary, `${JSON.stringify(intent)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
169
+ try { fs.renameSync(temporary, target); }
170
+ catch (error) {
171
+ try { fs.unlinkSync(temporary); } catch { /* preserve the primary error */ }
172
+ throw error;
173
+ }
174
+ } finally {
175
+ fs.closeSync(descriptor);
176
+ try { fs.unlinkSync(lock); } catch { /* preserve the primary result */ }
177
+ }
178
+ }
179
+
180
+ export function findArtifactRecoveryIntentsByOperation(
181
+ repoRoot: string,
182
+ operationId: string
183
+ ): readonly ArtifactRecoveryIntent[] {
184
+ if (!validToken(operationId)) throw new Error('ARTIFACT_RECOVERY_OPERATION_INVALID: recovery operation id is invalid');
185
+ const directory = artifactRecoveryIntentRoot(repoRoot);
186
+ if (!fs.existsSync(directory)) return Object.freeze([]);
187
+ return Object.freeze(fs.readdirSync(directory)
188
+ .filter((name) => name.endsWith('.json'))
189
+ .map((name) => {
190
+ const target = path.join(directory, name);
191
+ let value: unknown;
192
+ try { value = JSON.parse(fs.readFileSync(target, 'utf8')) as unknown; }
193
+ catch (error) { throw new Error(`ARTIFACT_RECOVERY_INTENT_INVALID: ${String(error)}`); }
194
+ if (!isArtifactRecoveryIntent(value)) throw new Error('ARTIFACT_RECOVERY_INTENT_INVALID: recovery journal schema is invalid');
195
+ return value;
196
+ })
197
+ .filter((value) => value.recoveryOperationId === operationId));
68
198
  }
69
199
 
70
- export { readArtifactRepairIntent, writeArtifactRepairIntent };
71
- export type { ArtifactRepairIntent };
200
+ export { intentPath as artifactRecoveryIntentPath };
@@ -1,13 +1,5 @@
1
- type ArtifactFamily =
2
- | 'analysis'
3
- | 'review-analysis'
4
- | 'plan'
5
- | 'review-plan'
6
- | 'code'
7
- | 'review-code'
8
- | 'manual-validation'
9
- | 'validation-run'
10
- | 'pr-review';
1
+ import { parseArtifactName } from './artifact-name.ts';
2
+ import type { ArtifactFamily } from './artifact-name.ts';
11
3
 
12
4
  type ArtifactSchemaFamily = Exclude<ArtifactFamily, 'manual-validation' | 'validation-run' | 'pr-review'>;
13
5
  type ArtifactLocale = 'zh-CN' | 'en';
@@ -30,25 +22,6 @@ type ArtifactSkeletonInput = Readonly<{
30
22
  locale?: ArtifactLocale;
31
23
  }>;
32
24
 
33
- type ArtifactFamilySpec = Readonly<{
34
- family: ArtifactFamily;
35
- sectionAliases: readonly [string, string];
36
- heading: string;
37
- labels: readonly [string, string];
38
- }>;
39
-
40
- const ARTIFACT_FAMILY_CATALOG = [
41
- { family: 'analysis', sectionAliases: ['分析', 'Analysis'], heading: '分析', labels: ['需求分析报告', 'Requirements Analysis'] },
42
- { family: 'review-analysis', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['需求分析审查', 'Analysis Review'] },
43
- { family: 'plan', sectionAliases: ['设计', 'Design'], heading: '设计', labels: ['技术方案', 'Technical Plan'] },
44
- { family: 'review-plan', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['技术方案审查', 'Plan Review'] },
45
- { family: 'code', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['实现报告', 'Implementation Report'] },
46
- { family: 'review-code', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['代码审查', 'Code Review'] },
47
- { family: 'manual-validation', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['人工验证', 'Manual Validation'] },
48
- { family: 'validation-run', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['验证运行证据', 'Validation Run Evidence'] },
49
- { family: 'pr-review', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['PR 审查报告', 'PR Review Report'] }
50
- ] as const satisfies readonly ArtifactFamilySpec[];
51
-
52
25
  const LOCAL_SECTIONS = {
53
26
  analysis: [
54
27
  ['source', '需求来源', 'Requirements Source'],
@@ -157,8 +130,9 @@ function renderArtifactSkeleton(input: ArtifactSkeletonInput): string {
157
130
  const schema = getArtifactSchema(input.family);
158
131
  if (!schema) throw new Error(`unknown artifact schema family '${input.family}'`);
159
132
  const locale = input.locale ?? 'zh-CN';
160
- const roundMatch = input.artifact.match(/-r(\d+)\.md$/);
161
- const round = roundMatch ? Number(roundMatch[1]) : 1;
133
+ const identity = parseArtifactName(input.artifact);
134
+ if (identity?.family !== input.family) throw new Error(`invalid artifact identity '${input.artifact}' for '${input.family}'`);
135
+ const round = identity.round;
162
136
  const title = schema.title[locale === 'en' ? 'en' : 'zh'];
163
137
  const headings = locale === 'en' ? 'en' : 'zh';
164
138
  const lines = [
@@ -176,14 +150,11 @@ function renderArtifactSkeleton(input: ArtifactSkeletonInput): string {
176
150
  }
177
151
 
178
152
  export {
179
- ARTIFACT_FAMILY_CATALOG,
180
153
  ARTIFACT_SCHEMAS,
181
154
  getArtifactSchema,
182
155
  renderArtifactSkeleton
183
156
  };
184
157
  export type {
185
- ArtifactFamily,
186
- ArtifactFamilySpec,
187
158
  ArtifactLocale,
188
159
  ArtifactSchema,
189
160
  ArtifactSchemaFamily,
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
 
4
+ import { parseArtifactName } from './artifact-name.ts';
4
5
  import type { InvalidationDocument } from './invalidation.ts';
5
6
  import { invalidationBlocks, isArtifactInvalidated, parseInvalidationDocument } from './invalidation.ts';
6
7
  import { receiptForOutput } from './artifact-receipts.ts';
@@ -141,13 +142,9 @@ function canStart(action: LifecycleAction, facts: LifecycleFacts, trigger: Expli
141
142
  return deny('CAPABILITY_ACTION_UNKNOWN');
142
143
  }
143
144
 
144
- function artifactRound(name: string): number {
145
- const match = /-r(\d+)\.md$/.exec(name);
146
- return match ? Number(match[1]) : 1;
147
- }
148
-
149
145
  function latestArtifact(names: readonly string[]): string | null {
150
- return [...names].sort((left, right) => artifactRound(right) - artifactRound(left) || left.localeCompare(right))[0] ?? null;
146
+ return names.map(parseArtifactName).filter((identity) => identity !== null)
147
+ .sort((left, right) => right.round - left.round || left.name.localeCompare(right.name))[0]?.name ?? null;
151
148
  }
152
149
 
153
150
  function reviewedInputName(content: string, expectedFamily: 'analysis' | 'plan' | 'code'): string | null {
@@ -242,17 +239,11 @@ function buildLifecycleFacts(taskDir: string, content: string, taskState = 'acti
242
239
  const stat = fs.lstatSync(path.join(taskDir, name));
243
240
  return stat.isFile() && !stat.isSymbolicLink();
244
241
  });
245
- const artifactFamilies = {
246
- analysis: /^analysis(?:-r\d+)?\.md$/,
247
- 'review-analysis': /^review-analysis(?:-r\d+)?\.md$/,
248
- plan: /^plan(?:-r\d+)?\.md$/,
249
- 'review-plan': /^review-plan(?:-r\d+)?\.md$/,
250
- code: /^code(?:-r\d+)?\.md$/,
251
- 'review-code': /^review-code(?:-r\d+)?\.md$/
252
- } as const;
253
- const familyFor = (name: string): keyof typeof artifactFamilies | null =>
254
- (Object.keys(artifactFamilies) as Array<keyof typeof artifactFamilies>)
255
- .find((family) => artifactFamilies[family].test(name)) ?? null;
242
+ const artifactFamilies = ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'] as const;
243
+ const familyFor = (name: string) => {
244
+ const family = parseArtifactName(name)?.family;
245
+ return artifactFamilies.find((candidate) => candidate === family) ?? null;
246
+ };
256
247
  const activeFiles = files.filter((name) => {
257
248
  const family = familyFor(name);
258
249
  return !family || !isArtifactInvalidated(invalidation.document, family, name);
@@ -260,13 +251,13 @@ function buildLifecycleFacts(taskDir: string, content: string, taskState = 'acti
260
251
  const artifactHashes: Record<string, string> = {};
261
252
  for (const name of activeFiles) artifactHashes[name] = sha256File(path.join(taskDir, name));
262
253
  const artifacts: Partial<Record<LifecycleAction, readonly string[]>> = Object.fromEntries(
263
- (Object.keys(artifactFamilies) as Array<keyof typeof artifactFamilies>).map((family) => [
264
- family, activeFiles.filter((name) => artifactFamilies[family].test(name))
254
+ artifactFamilies.map((family) => [
255
+ family, activeFiles.filter((name) => familyFor(name) === family)
265
256
  ])
266
257
  ) as Partial<Record<LifecycleAction, readonly string[]>>;
267
258
  const staleArtifacts: Partial<Record<LifecycleAction, readonly string[]>> = Object.fromEntries(
268
- (Object.keys(artifactFamilies) as Array<keyof typeof artifactFamilies>).map((family) => [
269
- family, files.filter((name) => artifactFamilies[family].test(name) && isArtifactInvalidated(invalidation.document, family, name))
259
+ artifactFamilies.map((family) => [
260
+ family, files.filter((name) => familyFor(name) === family && isArtifactInvalidated(invalidation.document, family, name))
270
261
  ])
271
262
  ) as Partial<Record<LifecycleAction, readonly string[]>>;
272
263
  const qualification = parseTaskQualification(content);
@@ -274,7 +265,7 @@ function buildLifecycleFacts(taskDir: string, content: string, taskState = 'acti
274
265
  const qualificationStaleArtifacts: string[] = [];
275
266
  if (qualification.qualification.present) {
276
267
  const constraints = new Map(qualification.qualification.constraints.map((row) => [row.constraintId, row.digest]));
277
- for (const family of Object.keys(artifactFamilies) as Array<keyof typeof artifactFamilies>) {
268
+ for (const family of artifactFamilies) {
278
269
  if (!ARTIFACT_AUDIT_FAMILIES.has(family)) continue;
279
270
  const name = latestArtifact(artifacts[family] ?? []);
280
271
  if (!name) continue;
@@ -29,10 +29,18 @@ import {
29
29
  OrchestrationStateError,
30
30
  pauseMatchingOrchestrationDelegation,
31
31
  prepareOrchestrationDelegation,
32
+ routeOrchestration,
32
33
  reconcileMatchingOrchestrationDelegation,
33
34
  sealMatchingOrchestrationDelegationWithHostEvidence
34
35
  } from './orchestration.ts';
36
+ import { randomUUID, createHash } from 'node:crypto';
35
37
  import type { OrchestrationOptions, OrchestrationResult } from './orchestration.ts';
38
+ import {
39
+ consumeLifecycleRecoveryAttestation,
40
+ issueLifecycleRecoveryAttestation,
41
+ validateLifecycleRecoveryAttestation,
42
+ type LifecycleRecoveryAttestationV1
43
+ } from './control-authority.ts';
36
44
 
37
45
  type LifecycleStore = ReturnType<typeof createCodexLifecycleStore>;
38
46
  type CapabilityStore = ReturnType<typeof createCodexCapabilityStore>;
@@ -46,6 +54,7 @@ type CodexBridgeOptions = Readonly<{
46
54
  buildIdentity?: LifecycleBuildIdentity;
47
55
  orchestrationOptions?: OrchestrationOptions;
48
56
  controllerBinding?: Readonly<{ instanceDigest: string; controlGeneration: string }>;
57
+ deferLifecycleRecoveryConsumption?: boolean;
49
58
  }>;
50
59
 
51
60
  type CodexSpawnIdentity = Readonly<{
@@ -59,6 +68,10 @@ type CodexSpawnIdentity = Readonly<{
59
68
  requestedReasoningEffort?: string;
60
69
  }>;
61
70
 
71
+ function buildIdentityDigest(value: LifecycleBuildIdentity): string {
72
+ return createHash('sha256').update(JSON.stringify(value), 'utf8').digest('hex');
73
+ }
74
+
62
75
  function coreOptions(options: CodexBridgeOptions): OrchestrationOptions {
63
76
  return { ...options.orchestrationOptions, repoRoot: options.repoRoot ?? options.orchestrationOptions?.repoRoot };
64
77
  }
@@ -126,7 +139,8 @@ async function prepareCodexOrchestrationDelegation(
126
139
  client: AgentClientId;
127
140
  requestedModel?: string;
128
141
  requestedReasoningEffort?: string;
129
- capabilityToken?: string;
142
+ capabilityRef?: string;
143
+ lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
130
144
  }>,
131
145
  options: CodexBridgeOptions = {}
132
146
  ): Promise<OrchestrationResult> {
@@ -136,10 +150,11 @@ async function prepareCodexOrchestrationDelegation(
136
150
  const resolved = resolveTaskRef(taskRef, { repoRoot });
137
151
  if (!resolved.ok) return bridgeFailure(resolved.code, resolved.message);
138
152
  const preflight = await (options.preflight ?? preflightCodexLifecycleEvidence)(repoRoot);
139
- if (!input.capabilityToken) {
153
+ const capabilityRef = input.capabilityRef;
154
+ if (!capabilityRef) {
140
155
  return bridgeFailure(
141
156
  'ORCHESTRATION_CODEX_CAPABILITY_REQUIRED',
142
- 'Codex prepare requires a current-session capability token'
157
+ 'Codex prepare requires a current-session capability reference'
143
158
  );
144
159
  }
145
160
  const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(repoRoot);
@@ -168,7 +183,7 @@ async function prepareCodexOrchestrationDelegation(
168
183
  controlGeneration: localController.controlGeneration
169
184
  } : null);
170
185
  const capabilityStore = options.capabilityStore ?? createCodexCapabilityStore();
171
- const attested = capabilityStore.inspect(input.capabilityToken);
186
+ const attested = capabilityStore.inspectReference(capabilityRef);
172
187
  const capabilityIdentity = verifyLifecycleBuildIdentity(attested.buildIdentity, buildIdentity);
173
188
  if (!capabilityIdentity.ok) {
174
189
  return bridgeFailure(capabilityIdentity.code!, capabilityIdentity.message!);
@@ -193,14 +208,60 @@ async function prepareCodexOrchestrationDelegation(
193
208
  controlGeneration: controller.controlGeneration
194
209
  } } : {})
195
210
  };
211
+ if (input.lifecycleRecoveryAttestation) {
212
+ try { validateLifecycleRecoveryAttestation(input.lifecycleRecoveryAttestation); }
213
+ catch (error) {
214
+ return bridgeFailure(
215
+ 'LIFECYCLE_AUTHORITY_ATTESTATION_INVALID',
216
+ error instanceof Error ? error.message : String(error)
217
+ );
218
+ }
219
+ }
220
+ let lifecycleRecoveryAttestation: LifecycleRecoveryAttestationV1 | null = input.lifecycleRecoveryAttestation ?? null;
221
+ const routed = input.capabilityRef && !lifecycleRecoveryAttestation
222
+ ? routeOrchestration(taskRef, coreOptions(options))
223
+ : null;
224
+ if (routed && !routed.next) return routed;
225
+ if (routed?.next && ['analysis', 'plan', 'code'].includes(routed.next.stage)) {
226
+ const recoveryRequest = {
227
+ version: 1 as const,
228
+ requestId: randomUUID(),
229
+ operationId: randomUUID(),
230
+ phase: 'orchestration.prepare' as const,
231
+ taskId: resolved.taskId,
232
+ family: routed.next.stage === 'analysis' ? 'analysis' as const
233
+ : routed.next.stage === 'plan' ? 'plan' as const : 'code' as const,
234
+ artifact: routed.next.artifact,
235
+ round: routed.next.round,
236
+ lifecycleRequestId: randomUUID(),
237
+ authorityRef: capabilityRef,
238
+ expectedControlGeneration: controller?.controlGeneration ?? '',
239
+ expectedControllerInstanceDigest: controller?.instanceDigest ?? '0'.repeat(64),
240
+ expectedBuildIdentityDigest: buildIdentityDigest(buildIdentity),
241
+ expectedHookDefinitionHash: preflight.hookDefinitionHash
242
+ };
243
+ const authority = issueLifecycleRecoveryAttestation(recoveryRequest, {
244
+ capabilityStore,
245
+ ...(controller ? { controllerBinding: controller } : {}),
246
+ buildIdentity
247
+ });
248
+ if (authority.status === 'rejected' || !authority.attestation) {
249
+ return bridgeFailure(
250
+ authority.error?.code ?? 'LIFECYCLE_AUTHORITY_REJECTED',
251
+ authority.error?.message ?? 'lifecycle authority was rejected'
252
+ );
253
+ }
254
+ lifecycleRecoveryAttestation = authority.attestation;
255
+ }
196
256
  const prepared = prepareOrchestrationDelegation(taskRef, {
197
257
  ...input,
198
- lifecycleProvenance
258
+ lifecycleProvenance,
259
+ lifecycleRecoveryAttestation
199
260
  }, {
200
261
  ...coreOptions(options),
201
262
  validateLifecycleCapability: () => {
202
263
  try {
203
- const validated = capabilityStore.validate(input.capabilityToken!, capabilityExpected);
264
+ const validated = capabilityStore.validateReference(capabilityRef, capabilityExpected);
204
265
  if (validated.sessionId !== attested.sessionId
205
266
  || validated.turnId !== attested.turnId
206
267
  || validated.toolUseId !== attested.toolUseId) {
@@ -214,23 +275,18 @@ async function prepareCodexOrchestrationDelegation(
214
275
  return capabilityFailure(error);
215
276
  }
216
277
  },
217
- consumeLifecycleCapability: () => {
218
- try {
219
- const consumed = capabilityStore.consume(input.capabilityToken!, capabilityExpected);
220
- if (consumed.sessionId !== attested.sessionId
221
- || consumed.turnId !== attested.turnId
222
- || consumed.toolUseId !== attested.toolUseId) {
223
- return {
224
- code: 'CODEX_CAPABILITY_IDENTITY_CHANGED',
225
- message: 'capability identity changed during consumption'
226
- };
227
- }
228
- return null;
229
- } catch (error) {
230
- return capabilityFailure(error);
231
- }
232
- }
278
+ consumeLifecycleCapability: () => null
233
279
  });
280
+ if (lifecycleRecoveryAttestation && !options.deferLifecycleRecoveryConsumption && prepared.status !== 'failed') {
281
+ try {
282
+ consumeLifecycleRecoveryAttestation(lifecycleRecoveryAttestation);
283
+ } catch (error) {
284
+ return bridgeFailure(
285
+ error instanceof Error ? error.name : 'LIFECYCLE_AUTHORITY_CONSUME_FAILED',
286
+ error instanceof Error ? error.message : String(error)
287
+ );
288
+ }
289
+ }
234
290
  return identityWarnings.length > 0 && prepared.status !== 'failed'
235
291
  ? { ...prepared, warnings: Object.freeze(identityWarnings) }
236
292
  : prepared;
@@ -283,7 +339,12 @@ async function activateCodexOrchestrationDelegation(
283
339
  const contextVerification = contextPath
284
340
  ? verifySandboxControllerWithWarnings(contextPath, { repoRoot })
285
341
  : null;
286
- const controller = contextVerification?.context ?? null;
342
+ const controller = options.controllerBinding
343
+ ?? brokerControllerBinding()
344
+ ?? (contextVerification?.context ? {
345
+ instanceDigest: contextVerification.context.controllerInstanceDigest,
346
+ controlGeneration: contextVerification.context.controlGeneration
347
+ } : null);
287
348
  const activated = activateMatchingOrchestrationDelegation('codex', {
288
349
  nativeAgent: evidence.nativeAgent,
289
350
  childId: evidence.childThreadId,
@@ -305,7 +366,7 @@ async function activateCodexOrchestrationDelegation(
305
366
  capabilityTurnId: evidence.parentTurnId,
306
367
  spawnToolUseId: evidence.spawnToolUseId,
307
368
  spawnObservedAt: record.spawnObservedAt ?? undefined,
308
- controllerInstanceDigest: controller?.controllerInstanceDigest ?? null,
369
+ controllerInstanceDigest: controller?.instanceDigest ?? null,
309
370
  controlGeneration: controller?.controlGeneration ?? null
310
371
  }
311
372
  }, coreOptions(options));
@@ -0,0 +1,31 @@
1
+ import { execFileSync } from 'node:child_process';
2
+
3
+ import type { CheckpointIntent } from './commit-intent.ts';
4
+
5
+ function gitText(repoRoot: string, args: readonly string[]): string {
6
+ return execFileSync('git', [...args], {
7
+ cwd: repoRoot,
8
+ encoding: 'utf8',
9
+ stdio: ['ignore', 'pipe', 'pipe']
10
+ }).trim();
11
+ }
12
+
13
+ function checkpointCommitMatches(repoRoot: string, intent: CheckpointIntent, head: string): boolean {
14
+ try {
15
+ const parent = gitText(repoRoot, ['rev-parse', `${head}^`]);
16
+ const tree = gitText(repoRoot, ['rev-parse', `${head}^{tree}`]);
17
+ const message = gitText(repoRoot, ['show', '-s', '--format=%s', head]);
18
+ const changed = gitText(repoRoot, ['diff-tree', '--no-commit-id', '--name-only', '-r', head])
19
+ .split('\n')
20
+ .filter(Boolean)
21
+ .sort();
22
+ return parent === intent.expectedHead
23
+ && tree === intent.expectedTree
24
+ && message === intent.message
25
+ && JSON.stringify(changed) === JSON.stringify([...intent.paths].sort());
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ export { checkpointCommitMatches };