@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
@@ -4,14 +4,13 @@ import path from 'node:path';
4
4
  import { appendActivityEntry, locateActivityLog, pairEntries, startedBackedRows } from './activity-log.ts';
5
5
  import {
6
6
  buildArtifactLinkSection,
7
- artifactName,
8
7
  inspectArtifactDirectory,
9
8
  parseReviewedInputReference,
10
9
  parseCodePlanInputReference,
11
- parseArtifactName,
12
10
  resolveArtifactContext,
13
11
  validateCompletedArtifact
14
12
  } from './artifact-lifecycle.ts';
13
+ import { artifactName, parseArtifactName } from './artifact-name.ts';
15
14
  import type { ArtifactContextResult, ArtifactErrorCode, ArtifactFamily, ArtifactIdentity } from './artifact-lifecycle.ts';
16
15
  import { ArtifactReceiptError, parseArtifactReceipts, sha256File, upsertArtifactReceipt } from './artifact-receipts.ts';
17
16
  import type { ArtifactReceipt } from './artifact-receipts.ts';
@@ -39,6 +38,9 @@ import {
39
38
  readLocalArtifactFinalizationIntent,
40
39
  validateLocalArtifact
41
40
  } from './local-artifact-finalization.ts';
41
+ import { writeArtifactRecoveryIntent, readArtifactRecoveryIntent } from './artifact-repair-intent.ts';
42
+ import { reconcileArtifactRecovery, recoveryContextFromIntent, sha256Content } from './artifact-recovery.ts';
43
+ import type { ArtifactRecoveryIntent } from './artifact-repair-intent.ts';
42
44
  import type { LocalArtifactFamily, LocalArtifactFinalizationIntent } from './local-artifact-finalization.ts';
43
45
  import { buildLifecycleFacts, canStart } from './capabilities.ts';
44
46
  import type { ExplicitTrigger, LifecycleAction, TriggerInitiator, TriggerReason } from './capabilities.ts';
@@ -48,7 +50,14 @@ import { consumeReworkIntents, parseReworkIntentDocument, reworkIntentMutation,
48
50
  import { ARTIFACT_FAMILIES, expectedQualificationRelations, parseQualificationAudit, parseTaskQualification, upstreamArtifactDigest, validateQualificationAudit } from './qualification-audit.ts';
49
51
  import type { QualificationAudit, UpstreamRelation } from './qualification-audit.ts';
50
52
  import { getArtifactSchema } from './artifact-schema.ts';
51
- import { inspectArtifactContract } from './artifact-operations.ts';
53
+ import { canonicalSemanticDigest, inspectArtifactContract } from './artifact-operations.ts';
54
+ import {
55
+ consumeLifecycleRecoveryAttestation,
56
+ lifecycleRecoveryAttestationDigest,
57
+ validateLifecycleRecoveryAttestation,
58
+ type LifecycleRecoveryAttestationV1
59
+ } from './control-authority.ts';
60
+ import { readManualValidationCompletion } from './manual-validation-completion.ts';
52
61
 
53
62
  const eventCatalog = [
54
63
  'analyze.started', 'analyze.awaiting-input', 'analyze.completed',
@@ -69,6 +78,10 @@ type TaskEventErrorCode =
69
78
  | 'EVENT_LOG_CONFLICT' | 'EVENT_ARTIFACT_CONFLICT' | 'EVENT_FINDING_COUNT_MISMATCH'
70
79
  | 'EVENT_VERDICT_INVALID'
71
80
  | 'EVENT_ORCHESTRATION_COMMIT_FAILED'
81
+ | 'MANUAL_VALIDATION_RECEIPT_MISSING' | 'MANUAL_VALIDATION_RECEIPT_INVALID'
82
+ | 'MANUAL_VALIDATION_RECEIPT_IDENTITY_MISMATCH'
83
+ | 'MANUAL_VALIDATION_TRANSACTION_MISSING' | 'MANUAL_VALIDATION_TRANSACTION_INVALID'
84
+ | 'MANUAL_VALIDATION_TRANSACTION_IDENTITY_MISMATCH' | 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID'
72
85
  | ArtifactErrorCode | TaskWriteErrorCode;
73
86
  type TaskEventRequest = {
74
87
  taskRef: string; event: TaskEventName | string; agent: string; dryRun?: boolean; orchestrated?: boolean;
@@ -80,11 +93,14 @@ type TaskEventRequest = {
80
93
  verdict?: Verdict; blockers?: number; major?: number; minor?: number;
81
94
  manualValidation?: number; filesModified?: number; testsPassed?: number;
82
95
  summaryResult?: string;
96
+ transactionId?: string; receiptDigest?: string; prHeadSha?: string;
83
97
  };
84
98
  type TaskEventError = { code: TaskEventErrorCode; message: string };
85
99
  type TaskEventOptions = TaskWriteOptions & {
86
100
  commitOrchestrationCompletion?: (plan: OrchestrationStageCompletion) => void;
87
101
  lockAlreadyHeld?: boolean;
102
+ lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
103
+ deferLifecycleRecoveryConsumption?: boolean;
88
104
  };
89
105
  type TaskEventResult = {
90
106
  status: 'planned' | 'applied' | 'no-op' | 'failed'; changed: boolean;
@@ -112,8 +128,8 @@ const SCHEMAS: Record<TaskEventName, { required?: string[]; optional?: string[]
112
128
  'code.completed': { required: ['artifact', 'artifactSha256', 'semanticDigest'], optional: ['round', 'fixFor', 'implementationInput', 'filesModified', 'testsPassed', 'blockers', 'major', 'minor', 'manualValidation', 'orchestrated'] },
113
129
  'review-code.started': { optional: ['round'] },
114
130
  'review-code.completed': { required: ['artifact', 'verdict', 'blockers', 'major', 'minor', 'manualValidation'], optional: ['round', 'orchestrated'] },
115
- 'manual-validation.started': { optional: ['round'] },
116
- 'manual-validation.completed': { required: ['artifact', 'summaryResult'], optional: ['round'] },
131
+ 'manual-validation.started': { optional: ['round', 'transactionId'] },
132
+ 'manual-validation.completed': { required: ['artifact', 'summaryResult', 'transactionId', 'receiptDigest', 'prHeadSha'], optional: ['round'] },
117
133
  'validation-run.started': { optional: ['round'] },
118
134
  'validation-run.completed': { required: ['artifact'], optional: ['round'] }
119
135
  };
@@ -146,12 +162,23 @@ function validateTaskEventRequest(request: TaskEventRequest): TaskEventError | n
146
162
  const forbidden = fix ? ['filesModified', 'testsPassed'] : ['fixFor', 'blockers', 'major', 'minor', 'manualValidation'];
147
163
  if (modeRequired.some((key) => request[key as keyof TaskEventRequest] === undefined) || forbidden.some((key) => request[key as keyof TaskEventRequest] !== undefined)) return { code: 'EVENT_PAYLOAD_INVALID', message: 'code.completed requires either initial or fix completion payload' };
148
164
  }
149
- if (request.fixFor && !/^review-code(?:-r(?:[2-9]|[1-9]\d+))?\.md$/.test(request.fixFor)) return { code: 'EVENT_PAYLOAD_INVALID', message: 'fixFor must reference a canonical review-code artifact' };
165
+ if (request.fixFor && parseArtifactName(request.fixFor)?.family !== 'review-code') return { code: 'EVENT_PAYLOAD_INVALID', message: 'fixFor must reference a canonical review-code artifact' };
150
166
  if (request.implementationInput && !/^II-[1-9]\d*$/.test(request.implementationInput)) return { code: 'EVENT_PAYLOAD_INVALID', message: 'implementationInput must be a canonical II-N id' };
151
167
  if (request.artifactSha256 !== undefined && !/^[0-9a-f]{64}$/i.test(request.artifactSha256)) return { code: 'EVENT_PAYLOAD_INVALID', message: 'artifactSha256 must be a 64-character hexadecimal digest' };
152
168
  if (request.semanticDigest !== undefined && !/^[0-9a-f]{64}$/i.test(request.semanticDigest)) return { code: 'EVENT_PAYLOAD_INVALID', message: 'semanticDigest must be a 64-character hexadecimal digest' };
153
169
  if (request.fixFor && request.implementationInput) return { code: 'EVENT_PAYLOAD_INVALID', message: 'fixFor and implementationInput are mutually exclusive' };
154
170
  if (request.summaryResult !== undefined && (!request.summaryResult.trim() || /[\r\n]/.test(request.summaryResult))) return { code: 'EVENT_PAYLOAD_INVALID', message: 'summaryResult must be a non-empty single line' };
171
+ if (request.event === 'manual-validation.completed') {
172
+ for (const [name, value, pattern] of [
173
+ ['transactionId', request.transactionId, /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u],
174
+ ['receiptDigest', request.receiptDigest, /^[a-f0-9]{64}$/u],
175
+ ['prHeadSha', request.prHeadSha, /^[a-f0-9]{40}$/u]
176
+ ] as const) if (!value || !pattern.test(value)) return { code: 'EVENT_PAYLOAD_INVALID', message: `manual-validation.completed requires a valid ${name}` };
177
+ }
178
+ if (request.event === 'manual-validation.started' && request.transactionId !== undefined
179
+ && !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(request.transactionId)) {
180
+ return { code: 'EVENT_PAYLOAD_INVALID', message: 'manual-validation.started requires a valid transactionId' };
181
+ }
155
182
  if (request.event !== 'analyze.awaiting-input' && /\.(?:started|completed)$/.test(request.event)) {
156
183
  if (!request.initiator || !request.requestId || !request.reasonCode) {
157
184
  return { code: 'EVENT_TRIGGER_REQUIRED', message: `${request.event} requires --initiator, --request-id, and --reason-code` };
@@ -254,7 +281,9 @@ function identity(request: TaskEventRequest) {
254
281
  if (family === 'manual-validation') {
255
282
  return {
256
283
  family, phase, action: spec.label,
257
- note: phase === 'started' ? 'started' : `Manual validation passed → ${request.artifact}; ${request.summaryResult}`,
284
+ note: phase === 'started'
285
+ ? `started${request.transactionId ? `; transaction=${request.transactionId}` : ''}`
286
+ : `Manual validation passed → ${request.artifact}; ${request.summaryResult}; transaction=${request.transactionId}; receipt=${request.receiptDigest}; head=${request.prHeadSha}`,
258
287
  target: null
259
288
  } as const;
260
289
  }
@@ -303,6 +332,28 @@ function normalizeStarted(request: TaskEventRequest, repoRoot: string): { reques
303
332
  return { request: { ...request, round, artifact: context.next.name, fixFor: expectedFix, implementationInput: expectedImplementation }, context };
304
333
  }
305
334
 
335
+ function validateManualValidationCompletion(
336
+ taskDir: string,
337
+ taskId: string,
338
+ request: TaskEventRequest,
339
+ artifactPath: string
340
+ ): TaskEventError | null {
341
+ const completion = readManualValidationCompletion(taskDir, {
342
+ transactionId: request.transactionId,
343
+ taskId,
344
+ prHeadSha: request.prHeadSha,
345
+ artifact: request.artifact,
346
+ receiptDigest: request.receiptDigest
347
+ });
348
+ if (!completion.ok) return completion.error;
349
+ const { receipt, transaction } = completion.value;
350
+ if (!['receipt-committed', 'final-promotion-in-progress', 'committed'].includes(transaction.phase)) {
351
+ return { code: 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID', message: 'manual validation completion requires the receipt-backed transaction phase' };
352
+ }
353
+ if (sha256File(artifactPath) !== receipt.artifactSha256) return { code: 'MANUAL_VALIDATION_RECEIPT_INVALID', message: 'manual-validation artifact digest does not match the receipt' };
354
+ return null;
355
+ }
356
+
306
357
  function openStartedIdentity(rows: ReturnType<typeof pairEntries>, family: EventFamily) {
307
358
  const spec = FAMILY[family];
308
359
  if (family === 'manual-validation') {
@@ -324,6 +375,67 @@ function reviewInputFamily(family: EventFamily): ArtifactFamily {
324
375
  return family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
325
376
  }
326
377
 
378
+ function reconcileReviewCompletionReplay(
379
+ repoRoot: string,
380
+ taskDir: string,
381
+ taskId: string,
382
+ family: EventFamily,
383
+ artifact: string,
384
+ round: number
385
+ ): TaskEventError | null {
386
+ const stage = REVIEW_LEDGER_STAGES[family];
387
+ if (!stage) return null;
388
+ const reviewFamily = family as 'review-analysis' | 'review-plan' | 'review-code';
389
+ try {
390
+ let intent = readArtifactRecoveryIntent(repoRoot, taskId, reviewFamily, artifact);
391
+ if (!intent) return null;
392
+ if (intent.state === 'commit-started') {
393
+ const reconciled = reconcileArtifactRecovery(
394
+ recoveryContextFromIntent(repoRoot, taskDir, intent),
395
+ { lockAlreadyHeld: true }
396
+ );
397
+ if (reconciled.status !== 'passed' && reconciled.status !== 'consumed') {
398
+ return {
399
+ code: 'EVENT_ARTIFACT_CONFLICT',
400
+ message: `review finalizer recovery remains ${reconciled.status} for ${artifact}`
401
+ };
402
+ }
403
+ intent = reconciled.intent;
404
+ }
405
+ if (!['passed', 'consumed'].includes(intent.state)) {
406
+ return {
407
+ code: 'EVENT_ARTIFACT_CONFLICT',
408
+ message: `review finalizer provenance is incomplete for ${artifact}`
409
+ };
410
+ }
411
+ const validated = validateCompletedArtifact(taskDir, FAMILY[family].artifact, artifact, round);
412
+ if (!validated.ok) {
413
+ return { code: 'EVENT_ARTIFACT_CONFLICT', message: validated.error.message };
414
+ }
415
+ const content = fs.readFileSync(validated.artifact.path, 'utf8');
416
+ const expectedRequestId = `review-finalize:${taskId}:${stage}:${round}`;
417
+ if (intent.round !== round
418
+ || intent.requestId !== expectedRequestId
419
+ || intent.finalArtifactSha256 !== sha256Content(content)
420
+ || intent.finalSemanticDigest !== canonicalSemanticDigest(content)) {
421
+ return {
422
+ code: 'EVENT_ARTIFACT_CONFLICT',
423
+ message: `review finalizer provenance does not match ${artifact}`
424
+ };
425
+ }
426
+ if (intent.state === 'passed') {
427
+ const consumed = { ...intent, state: 'consumed' as const, updatedAt: Date.now() };
428
+ writeArtifactRecoveryIntent(repoRoot, consumed, { expected: intent });
429
+ }
430
+ return null;
431
+ } catch (error) {
432
+ return {
433
+ code: 'EVENT_ARTIFACT_CONFLICT',
434
+ message: `review finalizer recovery could not be reconciled for ${artifact}: ${error instanceof Error ? error.message : String(error)}`
435
+ };
436
+ }
437
+ }
438
+
327
439
  function relationForStartedInput(family: EventFamily, input: ArtifactIdentity): UpstreamRelation['relation'] {
328
440
  if (family.startsWith('review-')) {
329
441
  return input.family === reviewInputFamily(family) ? 'reviewed-input' : 'approval-context';
@@ -626,6 +738,10 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
626
738
  if (openIdentity && 'conflict' in openIdentity) return failed(request, { code: 'EVENT_LOG_CONFLICT', message: 'artifact family has more than one open started event' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
627
739
  if (openIdentity) {
628
740
  if (openIdentity.row.agent !== request.agent) return failed(request, { code: 'EVENT_LOG_CONFLICT', message: 'open started event has a different agent' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
741
+ if (initialParts.family === 'manual-validation' && request.transactionId !== undefined) {
742
+ const existingTransactionId = /(?:^|;\s*)transaction=([A-Za-z0-9][A-Za-z0-9._-]{0,127})(?:;|$)/u.exec(openIdentity.row.note)?.[1];
743
+ if (existingTransactionId !== undefined && existingTransactionId !== request.transactionId) return failed(request, { code: 'EVENT_LOG_CONFLICT', message: 'open manual-validation started event has a different transactionId' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
744
+ }
629
745
  if (request.round !== undefined && request.round !== openIdentity.round) return failed(request, { code: 'EVENT_ARTIFACT_CONFLICT', message: `round ${request.round} conflicts with open round ${openIdentity.round}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
630
746
  if (request.fixFor !== undefined && request.fixFor !== openIdentity.fixFor) return failed(request, { code: 'EVENT_ARTIFACT_CONFLICT', message: `fixFor '${request.fixFor}' conflicts with open event` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
631
747
  if (request.implementationInput !== openIdentity.implementationInput) return failed(request, { code: 'EVENT_ARTIFACT_CONFLICT', message: `implementationInput '${request.implementationInput ?? ''}' conflicts with open event` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
@@ -641,6 +757,23 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
641
757
  if (identity?.family === FAMILY[initialParts.family].artifact) normalized = { ...request, round: identity.round };
642
758
  }
643
759
  const eventIdentity = identity(normalized);
760
+ const lifecycleAuthority = options.lifecycleRecoveryAttestation ?? null;
761
+ if (lifecycleAuthority) {
762
+ try { validateLifecycleRecoveryAttestation(lifecycleAuthority); }
763
+ catch (error) {
764
+ return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: error instanceof Error ? error.message : String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
765
+ }
766
+ const expectedFamily = eventIdentity.family === 'analyze' ? 'analysis' : eventIdentity.family;
767
+ if (eventIdentity.phase !== 'completed'
768
+ || lifecycleAuthority.phase !== 'task-event.completed'
769
+ || lifecycleAuthority.taskId !== resolved.taskId
770
+ || lifecycleAuthority.family !== expectedFamily
771
+ || lifecycleAuthority.artifact !== normalized.artifact
772
+ || lifecycleAuthority.round !== normalized.round
773
+ || (normalized.requestId !== undefined && lifecycleAuthority.lifecycleRequestId !== normalized.requestId)) {
774
+ return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: 'lifecycle completion authority does not match the event tuple' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
775
+ }
776
+ }
644
777
  const currentStep = typeof frontmatter.current_step === 'string' ? frontmatter.current_step : '';
645
778
  const matchingRows = rows.filter((item) => item.step === eventIdentity.action);
646
779
  const manual = eventIdentity.family === 'manual-validation';
@@ -650,21 +783,50 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
650
783
  const row = manual ? openRows.at(-1) : matchingRows[0];
651
784
  let completedArtifact: ArtifactIdentity | null = null;
652
785
  let localFinalizationIntent: LocalArtifactFinalizationIntent | null = null;
786
+ let reviewFinalizationIntent: ArtifactRecoveryIntent | null = null;
787
+ let reviewContent: string | null = null;
653
788
  if (eventIdentity.phase === 'started' && row) {
654
789
  if (row.agent !== normalized.agent) return failed(normalized, { code: 'EVENT_LOG_CONFLICT', message: 'open started event has a different agent' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
790
+ if (manual && normalized.transactionId !== undefined) {
791
+ const existingTransactionId = /(?:^|;\s*)transaction=([A-Za-z0-9][A-Za-z0-9._-]{0,127})(?:;|$)/u.exec(row.note)?.[1];
792
+ if (existingTransactionId !== undefined && existingTransactionId !== normalized.transactionId) {
793
+ return failed(normalized, { code: 'EVENT_LOG_CONFLICT', message: 'open manual-validation started event has a different transactionId' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
794
+ }
795
+ }
655
796
  return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, row.started, frontmatter, artifactContext);
656
797
  }
657
798
  if (eventIdentity.phase === 'started' && !manual && completedRows.length > 0) return failed(normalized, { code: 'EVENT_ALREADY_COMPLETED', message: 'event identity is already completed' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
658
799
  const done = completedRows.find((item) => item.note === eventIdentity.note);
659
- if (eventIdentity.phase === 'completed' && done) return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, done.done, frontmatter, artifactContext);
800
+ if (eventIdentity.phase === 'completed' && done) {
801
+ if (!normalized.dryRun && eventIdentity.family.startsWith('review-') && normalized.artifact && normalized.round) {
802
+ const recoveryError = reconcileReviewCompletionReplay(
803
+ resolved.repoRoot,
804
+ resolved.taskDir,
805
+ resolved.taskId,
806
+ eventIdentity.family,
807
+ normalized.artifact,
808
+ normalized.round
809
+ );
810
+ if (recoveryError) return failed(normalized, recoveryError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
811
+ }
812
+ return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, done.done, frontmatter, artifactContext);
813
+ }
660
814
  if (eventIdentity.phase === 'completed' && completedRows.length > 0 && openRows.length === 0) return failed(normalized, { code: 'EVENT_LOG_CONFLICT', message: 'event identity is already completed with a different payload' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
661
815
  if (eventIdentity.phase === 'completed' && !row?.started && !allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_START_MISSING')) return failed(normalized, { code: 'EVENT_START_MISSING', message: 'completion requires one open matching started event' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
662
816
  if (eventIdentity.phase === 'completed') {
663
817
  const validated = validateCompletedArtifact(resolved.taskDir, FAMILY[eventIdentity.family].artifact, normalized.artifact!, normalized.round);
664
818
  if (!validated.ok) return failed(normalized, validated.error, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
665
819
  completedArtifact = validated.artifact;
820
+ if (eventIdentity.family === 'manual-validation') {
821
+ const validationError = validateManualValidationCompletion(
822
+ resolved.taskDir,
823
+ resolved.taskId,
824
+ normalized,
825
+ completedArtifact.path
826
+ );
827
+ if (validationError) return failed(normalized, validationError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
828
+ }
666
829
  if (eventIdentity.family.startsWith('review-')) {
667
- let reviewContent: string;
668
830
  try { reviewContent = fs.readFileSync(completedArtifact.path, 'utf8'); }
669
831
  catch (error) {
670
832
  return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `cannot read qualification audit from ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
@@ -691,6 +853,23 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
691
853
  const localFamily = eventIdentity.family === 'analyze'
692
854
  ? 'analysis'
693
855
  : eventIdentity.family === 'plan' ? 'plan' : 'code';
856
+ try {
857
+ localFinalizationIntent = readLocalArtifactFinalizationIntent(
858
+ resolved.repoRoot, resolved.taskId, localFamily, completedArtifact.name
859
+ );
860
+ if (localFinalizationIntent?.state === 'commit-started') {
861
+ const reconciled = reconcileArtifactRecovery(
862
+ recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, localFinalizationIntent),
863
+ { lockAlreadyHeld: true }
864
+ );
865
+ if (reconciled.status === 'passed' || reconciled.status === 'consumed') localFinalizationIntent = reconciled.intent;
866
+ }
867
+ } catch (error) {
868
+ return failed(normalized, {
869
+ code: 'EVENT_ARTIFACT_CONFLICT',
870
+ message: `local finalizer recovery could not be reconciled for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
871
+ }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
872
+ }
694
873
  let artifactContent: string;
695
874
  try { artifactContent = fs.readFileSync(completedArtifact.path, 'utf8'); }
696
875
  catch (error) {
@@ -720,28 +899,29 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
720
899
  message: `semantic digest does not match finalizer result for ${completedArtifact.name}`
721
900
  }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
722
901
  }
723
- try {
724
- localFinalizationIntent = readLocalArtifactFinalizationIntent(
725
- resolved.repoRoot, resolved.taskId, localFamily, completedArtifact.name
726
- );
727
- } catch (error) {
728
- return failed(normalized, {
729
- code: 'EVENT_ARTIFACT_CONFLICT',
730
- message: `cannot read local finalizer provenance for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
731
- }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
732
- }
733
902
  if (!localFinalizationIntent || !['passed', 'consumed'].includes(localFinalizationIntent.state)) {
734
903
  return failed(normalized, {
735
904
  code: 'EVENT_ARTIFACT_CONFLICT',
736
905
  message: `local finalizer provenance is missing or incomplete for ${completedArtifact.name}`
737
906
  }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
738
907
  }
739
- if (localFinalizationIntent.artifactSha256 !== actualSha256 || localFinalizationIntent.semanticDigest !== local.semanticDigest) {
908
+ if (localFinalizationIntent.finalArtifactSha256 !== actualSha256 || localFinalizationIntent.finalSemanticDigest !== local.semanticDigest) {
740
909
  return failed(normalized, {
741
910
  code: 'EVENT_ARTIFACT_CONFLICT',
742
911
  message: `local finalizer provenance does not match ${completedArtifact.name}`
743
912
  }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
744
913
  }
914
+ if (localFinalizationIntent.authorityDigest !== null) {
915
+ if (!lifecycleAuthority
916
+ || localFinalizationIntent.recoveryOperationId !== lifecycleAuthority.operationId
917
+ || localFinalizationIntent.requestId !== lifecycleAuthority.lifecycleRequestId
918
+ || localFinalizationIntent.authorityDigest !== lifecycleRecoveryAttestationDigest(lifecycleAuthority)) {
919
+ return failed(normalized, {
920
+ code: 'EVENT_ARTIFACT_CONFLICT',
921
+ message: `local finalizer provenance does not match lifecycle completion authority for ${completedArtifact.name}`
922
+ }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
923
+ }
924
+ }
745
925
  }
746
926
  }
747
927
  if (eventIdentity.phase === 'waiting' && section.entries.some((entry) => entry.step === eventIdentity.action && entry.note === eventIdentity.note)) {
@@ -768,6 +948,49 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
768
948
  completedArtifact?.path ?? null
769
949
  );
770
950
  if (findingCountError) return failed(normalized, findingCountError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
951
+ if (eventIdentity.phase === 'completed' && eventIdentity.family.startsWith('review-') && completedArtifact && reviewContent !== null) {
952
+ try {
953
+ reviewFinalizationIntent = readArtifactRecoveryIntent(
954
+ resolved.repoRoot,
955
+ resolved.taskId,
956
+ eventIdentity.family as 'review-analysis' | 'review-plan' | 'review-code',
957
+ completedArtifact.name
958
+ );
959
+ if (!normalized.dryRun && reviewFinalizationIntent?.state === 'commit-started') {
960
+ const reconciled = reconcileArtifactRecovery(
961
+ recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, reviewFinalizationIntent),
962
+ { lockAlreadyHeld: true }
963
+ );
964
+ if (reconciled.status === 'passed' || reconciled.status === 'consumed') reviewFinalizationIntent = reconciled.intent;
965
+ }
966
+ if (!reviewFinalizationIntent || !['passed', 'consumed'].includes(reviewFinalizationIntent.state)) {
967
+ return failed(normalized, {
968
+ code: 'EVENT_ARTIFACT_CONFLICT',
969
+ message: `review finalizer provenance is missing or incomplete for ${completedArtifact.name}`
970
+ }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
971
+ }
972
+ const reviewStage = eventIdentity.family === 'review-analysis'
973
+ ? 'analysis'
974
+ : eventIdentity.family === 'review-plan' ? 'plan' : 'code';
975
+ const actualSha256 = sha256File(completedArtifact.path);
976
+ const actualSemanticDigest = canonicalSemanticDigest(reviewContent);
977
+ const expectedRequestId = `review-finalize:${resolved.taskId}:${reviewStage}:${normalized.round}`;
978
+ if (reviewFinalizationIntent.round !== normalized.round
979
+ || reviewFinalizationIntent.requestId !== expectedRequestId
980
+ || reviewFinalizationIntent.finalArtifactSha256 !== actualSha256
981
+ || reviewFinalizationIntent.finalSemanticDigest !== actualSemanticDigest) {
982
+ return failed(normalized, {
983
+ code: 'EVENT_ARTIFACT_CONFLICT',
984
+ message: `review finalizer provenance does not match ${completedArtifact.name}`
985
+ }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
986
+ }
987
+ } catch (error) {
988
+ return failed(normalized, {
989
+ code: 'EVENT_ARTIFACT_CONFLICT',
990
+ message: `review finalizer recovery could not be reconciled for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
991
+ }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
992
+ }
993
+ }
771
994
  let orchestrationCompletion: OrchestrationStageCompletion | null = null;
772
995
  if (eventIdentity.phase === 'completed' && eventIdentity.family !== 'manual-validation' && eventIdentity.family !== 'validation-run') {
773
996
  const orchestrationStage = eventIdentity.family === 'analyze' ? 'analysis' : eventIdentity.family;
@@ -887,13 +1110,51 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
887
1110
  });
888
1111
  }
889
1112
  mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body });
890
- if (!normalized.dryRun && localFinalizationIntent && completedArtifact && localFinalizationIntent.state === 'passed') {
1113
+ if (!normalized.dryRun && localFinalizationIntent && completedArtifact) {
1114
+ try {
1115
+ if (lifecycleAuthority && localFinalizationIntent.state === 'passed') {
1116
+ const expectedIntent = localFinalizationIntent;
1117
+ localFinalizationIntent = {
1118
+ ...localFinalizationIntent,
1119
+ state: 'commit-started',
1120
+ phase: 'task-event.completed',
1121
+ authorityDigest: lifecycleRecoveryAttestationDigest(lifecycleAuthority),
1122
+ requestId: lifecycleAuthority.lifecycleRequestId,
1123
+ updatedAt: Date.now()
1124
+ };
1125
+ writeArtifactRecoveryIntent(resolved.repoRoot, localFinalizationIntent, { expected: expectedIntent });
1126
+ } else if (!lifecycleAuthority && localFinalizationIntent.state === 'passed') {
1127
+ localFinalizationIntent = consumeLocalArtifactFinalizationIntent(resolved.repoRoot, localFinalizationIntent, { lockAlreadyHeld: options.lockAlreadyHeld });
1128
+ }
1129
+ } catch (error) {
1130
+ return failed(normalized, {
1131
+ code: 'EVENT_ARTIFACT_CONFLICT',
1132
+ message: `local finalizer provenance could not be advanced before task write for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
1133
+ }, {
1134
+ taskId: resolved.taskId,
1135
+ taskMdPath: resolved.taskMdPath,
1136
+ fromStep: currentStep,
1137
+ toStep: step,
1138
+ action: eventIdentity.action,
1139
+ phase: eventIdentity.phase,
1140
+ artifactContext
1141
+ });
1142
+ }
1143
+ }
1144
+ if (!normalized.dryRun && reviewFinalizationIntent && completedArtifact && reviewFinalizationIntent.state === 'passed') {
891
1145
  try {
892
- localFinalizationIntent = consumeLocalArtifactFinalizationIntent(resolved.repoRoot, localFinalizationIntent);
1146
+ const expectedIntent = reviewFinalizationIntent;
1147
+ reviewFinalizationIntent = {
1148
+ ...reviewFinalizationIntent,
1149
+ state: 'commit-started',
1150
+ phase: 'task-event.completed',
1151
+ updatedAt: Date.now()
1152
+ };
1153
+ writeArtifactRecoveryIntent(resolved.repoRoot, reviewFinalizationIntent, { expected: expectedIntent });
893
1154
  } catch (error) {
894
1155
  return failed(normalized, {
895
1156
  code: 'EVENT_ARTIFACT_CONFLICT',
896
- message: `local finalizer provenance could not be consumed before task write for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
1157
+ message: `review finalizer provenance could not be advanced before task write for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
897
1158
  }, {
898
1159
  taskId: resolved.taskId,
899
1160
  taskMdPath: resolved.taskMdPath,
@@ -909,6 +1170,30 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
909
1170
  && ['analyze', 'plan', 'code'].includes(eventIdentity.family);
910
1171
  const result = writeTask({ taskRef: normalized.taskRef, expectedState: stateOverride ? resolved.state : 'active', dryRun: normalized.dryRun, mutations }, { ...options, invalidationContext: sourceCompletion ? 'source-completion' : 'standard', metadataProvider: () => metadata });
911
1172
  if (result.status === 'failed') return failed(normalized, result.error, { taskId: result.taskId, taskMdPath: result.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion, operations: result.operations, artifactContext });
1173
+ if (!normalized.dryRun && lifecycleAuthority && localFinalizationIntent && completedArtifact) {
1174
+ try {
1175
+ if (!options.deferLifecycleRecoveryConsumption) consumeLifecycleRecoveryAttestation(lifecycleAuthority);
1176
+ writeArtifactRecoveryIntent(resolved.repoRoot, { ...localFinalizationIntent, state: 'consumed', updatedAt: Date.now() }, { expected: localFinalizationIntent });
1177
+ } catch (error) {
1178
+ return failed(normalized, {
1179
+ code: 'EVENT_ARTIFACT_CONFLICT',
1180
+ message: `task completion was written but lifecycle recovery consumption is incomplete: ${error instanceof Error ? error.message : String(error)}`
1181
+ }, { taskId: result.taskId, taskMdPath: result.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion, operations: result.operations, artifactContext });
1182
+ }
1183
+ }
1184
+ if (!normalized.dryRun && reviewFinalizationIntent && completedArtifact) {
1185
+ try {
1186
+ if (reviewFinalizationIntent.state === 'commit-started') {
1187
+ const consumed = { ...reviewFinalizationIntent, state: 'consumed' as const, updatedAt: Date.now() };
1188
+ writeArtifactRecoveryIntent(resolved.repoRoot, consumed, { expected: reviewFinalizationIntent });
1189
+ }
1190
+ } catch (error) {
1191
+ return failed(normalized, {
1192
+ code: 'EVENT_ARTIFACT_CONFLICT',
1193
+ message: `review finalizer recovery consumption is incomplete: ${error instanceof Error ? error.message : String(error)}`
1194
+ }, { taskId: result.taskId, taskMdPath: result.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion, operations: result.operations, artifactContext });
1195
+ }
1196
+ }
912
1197
  if (!normalized.dryRun && orchestrationCompletion) {
913
1198
  try {
914
1199
  (options.commitOrchestrationCompletion ?? commitOrchestrationStageCompletion)(orchestrationCompletion);
@@ -956,7 +1241,7 @@ function applyTaskEvent(request: TaskEventRequest, options: TaskEventOptions = {
956
1241
  resolved.repoRoot,
957
1242
  resolved.taskId,
958
1243
  `task-event.${request.event}`,
959
- () => applyTaskEventUnlocked(request, options)
1244
+ () => applyTaskEventUnlocked(request, { ...options, lockAlreadyHeld: true })
960
1245
  );
961
1246
  } catch (error) {
962
1247
  if (!(error instanceof TaskExecutionLockError)) throw error;
@@ -0,0 +1,11 @@
1
+ import type { TaskFinalizationReceipt } from './finalization.ts';
2
+
3
+ export type TaskControlBinding = NonNullable<TaskFinalizationReceipt['controlBinding']>;
4
+ export type TaskControlBindingEvidence = (taskId: string, binding: TaskControlBinding) => 'terminal' | 'pending' | 'mismatch';
5
+
6
+ export function taskFinalizationReceiptState(receipt: TaskFinalizationReceipt): 'pending' | 'unresolved' | 'complete' {
7
+ if (receipt.lifecycle !== 'done' || receipt.taskComment === 'pending'
8
+ || receipt.verification === 'pending' || receipt.warningProjection !== 'done') return 'pending';
9
+ if (receipt.lastError !== null || receipt.warnings.some((warning) => warning.status === 'open')) return 'unresolved';
10
+ return 'complete';
11
+ }
@@ -2,6 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { createHash, randomUUID } from 'node:crypto';
4
4
 
5
+ import { parseArtifactName } from './artifact-name.ts';
5
6
  import { locateActivityLog } from './activity-log.ts';
6
7
  import { applyPrReviewActivityIntent } from './activity-intent.ts';
7
8
  import { applyHumanDecision } from './decision-intents.ts';
@@ -616,10 +617,7 @@ function resourceForSnapshot(
616
617
  if (policy.producerId === 'platform.issue') {
617
618
  const identity = options.probeIssueNumber !== undefined
618
619
  ? resourceIdentity(options.probeIssueNumber)
619
- : taskIssueIdentity((frontmatter || {}) as Record<string, string | number | boolean | null>) || (() => {
620
- const number = toPositiveInteger(frontmatter?.issue_number);
621
- return number === null ? null : resourceIdentity(number);
622
- })();
620
+ : taskIssueIdentity((frontmatter || {}) as Record<string, string | number | boolean | null>);
623
621
  return identity ? { kind: 'issue', identity, number: resourceIdentityNumber(identity) } : null;
624
622
  }
625
623
  if (policy.producerId === 'platform.pull-request') {
@@ -718,11 +716,11 @@ async function probePlatformFailure(
718
716
  }
719
717
  const issueIdentity = options.probeIssueNumber !== undefined
720
718
  ? resourceIdentity(options.probeIssueNumber)
721
- : taskIssueIdentity(frontmatter) || (() => {
722
- const number = toPositiveInteger(frontmatter.issue_number);
723
- return number === null ? null : resourceIdentity(number);
724
- })();
719
+ : taskIssueIdentity(frontmatter);
725
720
  const fact = readPrDeliveryFact(frontmatter);
721
+ if (fact.status === 'invalid') {
722
+ return error(fact.error.code, fact.error.message);
723
+ }
726
724
  const pullRequestIdentity = options.probePullRequestNumber !== undefined
727
725
  ? resourceIdentity(options.probePullRequestNumber)
728
726
  : fact.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.resource : null;
@@ -814,14 +812,8 @@ async function probeVerificationFailure(taskRef: string, policy: FailurePolicy,
814
812
  function latestArtifact(taskDir: string, family: string): string | undefined {
815
813
  let names: string[];
816
814
  try { names = fs.readdirSync(taskDir); } catch { return undefined; }
817
- const candidates = names.filter((name) => (
818
- name === `${family}.md` || new RegExp(`^${family}-r\\d+\\.md$`).test(name)
819
- ));
820
- candidates.sort((a, b) => {
821
- const round = (name: string) => Number(/-r(\d+)\.md$/.exec(name)?.[1] ?? 1);
822
- return round(b) - round(a);
823
- });
824
- return candidates[0];
815
+ return names.map(parseArtifactName).filter((identity) => identity !== null)
816
+ .filter((identity) => identity.family === family).sort((left, right) => right.round - left.round)[0]?.name;
825
817
  }
826
818
 
827
819
  async function probeVerificationEngineFailure(policy: FailurePolicy, resolved: ProbeTask): Promise<ProducerFailure | OverrideError> {
@@ -1,3 +1,4 @@
1
+ import { parseArtifactName } from './artifact-name.ts';
1
2
  import { parseTable } from './sections.ts';
2
3
 
3
4
  const SECTION_ALIASES = ['实现输入', 'Implementation Inputs'] as const;
@@ -6,7 +7,6 @@ const COLUMNS = [
6
7
  'decided_at', 'status', 'consumed_by'
7
8
  ] as const;
8
9
  const TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$/;
9
- const ARTIFACT_RE = /^code(?:-r(?:[2-9]|[1-9]\d+))?\.md$/;
10
10
 
11
11
  type ImplementationInputStatus = 'declared' | 'pending' | 'not-required' | 'consumed';
12
12
  type ImplementationInput = {
@@ -36,7 +36,7 @@ function validateInput(row: ImplementationInput): void {
36
36
  (row.status === 'declared' && row.decidedAt === '' && row.consumedBy === '') ||
37
37
  (hasValidTimestamp && row.needsImplementation && row.status === 'pending' && row.consumedBy === '') ||
38
38
  (hasValidTimestamp && !row.needsImplementation && row.status === 'not-required' && row.consumedBy === '') ||
39
- (hasValidTimestamp && row.needsImplementation && row.status === 'consumed' && ARTIFACT_RE.test(row.consumedBy));
39
+ (hasValidTimestamp && row.needsImplementation && row.status === 'consumed' && parseArtifactName(row.consumedBy)?.family === 'code');
40
40
  if (!valid) throw new Error(`implementation input ${row.id} has an invalid state combination`);
41
41
  }
42
42
 
@@ -143,7 +143,7 @@ function selectPendingImplementationInput(
143
143
  function consumeImplementationInput(
144
144
  rows: readonly ImplementationInput[], id: string, artifact: string
145
145
  ): ImplementationInput[] {
146
- if (!ARTIFACT_RE.test(artifact)) throw new Error(`implementation input artifact '${artifact}' is invalid`);
146
+ if (parseArtifactName(artifact)?.family !== 'code') throw new Error(`implementation input artifact '${artifact}' is invalid`);
147
147
  let found = false;
148
148
  const next = rows.map((row) => {
149
149
  if (row.id !== id) return row;