@fitlab-ai/agent-infra 0.9.13 → 0.9.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (428) hide show
  1. package/README.md +10 -0
  2. package/README.zh-CN.md +10 -0
  3. package/bin/cli.ts +26 -9
  4. package/bin/internal-cli.sh +45 -0
  5. package/dist/bin/cli.js +173 -157
  6. package/dist/bin/internal-cli.js +126 -42
  7. package/dist/lib/agent-client.js +5 -0
  8. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
  9. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
  10. package/dist/lib/defaults.json +103 -103
  11. package/dist/lib/fs/durable-write.js +37 -0
  12. package/dist/lib/fs/file-lock.js +111 -0
  13. package/dist/lib/host-control/audit.js +19 -0
  14. package/dist/lib/host-control/client.js +142 -0
  15. package/dist/lib/host-control/command.js +49 -0
  16. package/dist/lib/host-control/path.js +160 -0
  17. package/dist/lib/host-control/secure-fs.js +140 -0
  18. package/dist/lib/host-control/server.js +159 -0
  19. package/dist/lib/host-control/service.js +112 -0
  20. package/dist/lib/init.js +11 -2
  21. package/dist/lib/internal/agent-client.js +5 -2
  22. package/dist/lib/internal/cli-route-inventory.js +154 -0
  23. package/dist/lib/internal/codex-lifecycle.js +15 -12
  24. package/dist/lib/internal/codex-sandbox-controller.js +8 -2
  25. package/dist/lib/internal/demo-promotion.js +322 -0
  26. package/dist/lib/internal/demo-transcript.js +454 -0
  27. package/dist/lib/internal/git-workflow.js +10 -6
  28. package/dist/lib/internal/host-control.js +28 -0
  29. package/dist/lib/internal/manual-validation.js +322 -0
  30. package/dist/lib/internal/platform-checks.js +16 -4
  31. package/dist/lib/internal/platform-comment.js +16 -4
  32. package/dist/lib/internal/platform-context.js +4 -1
  33. package/dist/lib/internal/platform-issue.js +12 -4
  34. package/dist/lib/internal/platform-metadata.js +88 -0
  35. package/dist/lib/internal/platform-pr-review.js +16 -3
  36. package/dist/lib/internal/platform-pr.js +42 -14
  37. package/dist/lib/internal/platform-release-notes.js +6 -3
  38. package/dist/lib/internal/platform-security.js +93 -0
  39. package/dist/lib/internal/pr-review-grade.js +14 -3
  40. package/dist/lib/internal/release-workflow.js +82 -89
  41. package/dist/lib/internal/sandbox-control.js +171 -7
  42. package/dist/lib/internal/task-activity.js +13 -4
  43. package/dist/lib/internal/task-artifact.js +19 -107
  44. package/dist/lib/internal/task-context.js +4 -1
  45. package/dist/lib/internal/task-create.js +48 -14
  46. package/dist/lib/internal/task-delivery.js +4 -1
  47. package/dist/lib/internal/task-event.js +38 -35
  48. package/dist/lib/internal/task-finalization.js +4 -1
  49. package/dist/lib/internal/task-invalidation.js +4 -1
  50. package/dist/lib/internal/task-ledger.js +12 -1
  51. package/dist/lib/internal/task-lifecycle.js +4 -1
  52. package/dist/lib/internal/task-operation-registry.js +519 -0
  53. package/dist/lib/internal/task-orchestration.js +12 -0
  54. package/dist/lib/internal/task-override.js +10 -3
  55. package/dist/lib/internal/task-qualification.js +90 -0
  56. package/dist/lib/internal/task-review.js +10 -52
  57. package/dist/lib/internal/task-short-id.js +11 -1
  58. package/dist/lib/internal/task-snapshot.js +4 -1
  59. package/dist/lib/internal/task-validate.js +37 -12
  60. package/dist/lib/internal/task-verify.js +5 -1
  61. package/dist/lib/internal/task-warning.js +8 -1
  62. package/dist/lib/owned-path.js +33 -0
  63. package/dist/lib/platform/check-status.js +11 -0
  64. package/dist/lib/platform/comment-safety.js +229 -0
  65. package/dist/lib/platform/completion-backfill.js +1 -1
  66. package/dist/lib/platform/github-data.js +241 -0
  67. package/dist/lib/platform/github-provider.js +248 -26
  68. package/dist/lib/platform/issue-comments.js +146 -34
  69. package/dist/lib/platform/issues.js +1 -179
  70. package/dist/lib/platform/pr-change-report.js +6 -12
  71. package/dist/lib/platform/pr-checks.js +12 -65
  72. package/dist/lib/platform/pr-summary.js +83 -10
  73. package/dist/lib/platform/provider-contract.d.ts +67 -1
  74. package/dist/lib/platform/provider-contract.js +6 -2
  75. package/dist/lib/platform/provider-validation.js +76 -3
  76. package/dist/lib/platform/pull-requests.js +117 -275
  77. package/dist/lib/platform/repository-metadata.js +377 -0
  78. package/dist/lib/platform/security-alerts.js +155 -0
  79. package/dist/lib/platform/snapshots.js +2 -0
  80. package/dist/lib/platform/verification-sync.js +154 -162
  81. package/dist/lib/process-data/index.js +7 -0
  82. package/dist/lib/release/workflow.js +1 -1
  83. package/dist/lib/run/index.js +5 -0
  84. package/dist/lib/sandbox/commands/control-status.js +22 -0
  85. package/dist/lib/sandbox/commands/create.js +39 -8
  86. package/dist/lib/sandbox/commands/ls.js +5 -0
  87. package/dist/lib/sandbox/commands/rm.js +3 -807
  88. package/dist/lib/sandbox/commands/show.js +16 -0
  89. package/dist/lib/sandbox/control/audit.js +203 -0
  90. package/dist/lib/sandbox/control/client.js +125 -12
  91. package/dist/lib/sandbox/control/completed-reentry.js +85 -0
  92. package/dist/lib/sandbox/control/container-identity.js +28 -31
  93. package/dist/lib/sandbox/control/executor.js +233 -34
  94. package/dist/lib/sandbox/control/finalization-response.js +27 -0
  95. package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
  96. package/dist/lib/sandbox/control/lifecycle.js +656 -128
  97. package/dist/lib/sandbox/control/native-file-lock.js +85 -0
  98. package/dist/lib/sandbox/control/protocol.js +55 -4
  99. package/dist/lib/sandbox/control/server.js +720 -79
  100. package/dist/lib/sandbox/control/state.js +63 -3
  101. package/dist/lib/sandbox/control/task-view.js +183 -0
  102. package/dist/lib/sandbox/control/task-workflow.js +74 -0
  103. package/dist/lib/sandbox/control/workflow-executor.js +99 -0
  104. package/dist/lib/sandbox/engines/authority.js +289 -0
  105. package/dist/lib/sandbox/engines/docker-exact-discovery.js +97 -0
  106. package/dist/lib/sandbox/index.js +7 -2
  107. package/dist/lib/sandbox/recovery.js +49 -5
  108. package/dist/lib/sandbox/removal.js +1717 -0
  109. package/dist/lib/sandbox/task-cleanup.js +217 -0
  110. package/dist/lib/sandbox/workspace-view.js +15 -15
  111. package/dist/lib/sandbox/worktree-safety.js +4 -1
  112. package/dist/lib/server/index.js +8 -0
  113. package/dist/lib/task/activity-intent.js +2 -1
  114. package/dist/lib/task/activity-log.js +30 -24
  115. package/dist/lib/task/artifact-command.js +101 -0
  116. package/dist/lib/task/artifact-lifecycle.js +71 -40
  117. package/dist/lib/task/artifact-name.js +36 -0
  118. package/dist/lib/task/artifact-operations.js +474 -0
  119. package/dist/lib/task/artifact-receipts.js +3 -15
  120. package/dist/lib/task/artifact-repair-intent.js +130 -0
  121. package/dist/lib/task/artifact-schema.js +127 -0
  122. package/dist/lib/task/capabilities.js +83 -19
  123. package/dist/lib/task/codex-orchestration.js +71 -25
  124. package/dist/lib/task/commit-identity.js +28 -0
  125. package/dist/lib/task/commit-operation.js +2 -16
  126. package/dist/lib/task/control-authority.js +375 -5
  127. package/dist/lib/task/control-recovery.js +128 -0
  128. package/dist/lib/task/create-service.js +103 -4
  129. package/dist/lib/task/create.js +80 -2
  130. package/dist/lib/task/decision-details.js +2 -46
  131. package/dist/lib/task/events.js +306 -76
  132. package/dist/lib/task/finalization-state.js +9 -0
  133. package/dist/lib/task/human-override.js +3 -6
  134. package/dist/lib/task/implementation-inputs.js +3 -3
  135. package/dist/lib/task/index.js +11 -3
  136. package/dist/lib/task/intermediate-cleanup.js +383 -0
  137. package/dist/lib/task/invalidation.js +18 -65
  138. package/dist/lib/task/ledger-intents.js +7 -22
  139. package/dist/lib/task/ledger.js +1 -3
  140. package/dist/lib/task/lifecycle.js +19 -1
  141. package/dist/lib/task/local-artifact-finalization.js +190 -290
  142. package/dist/lib/task/manual-validation-completion.js +26 -0
  143. package/dist/lib/task/manual-validation-evidence.js +130 -0
  144. package/dist/lib/task/manual-validation-receipt.js +101 -0
  145. package/dist/lib/task/manual-validation-shared.js +40 -0
  146. package/dist/lib/task/manual-validation-transaction.js +243 -0
  147. package/dist/lib/task/markdown.js +47 -0
  148. package/dist/lib/task/orchestration.js +206 -69
  149. package/dist/lib/task/qualification-audit.js +476 -0
  150. package/dist/lib/task/qualification-confirmation.js +109 -0
  151. package/dist/lib/task/qualification-intents.js +148 -0
  152. package/dist/lib/task/review-artifacts.js +23 -40
  153. package/dist/lib/task/review-command.js +25 -0
  154. package/dist/lib/task/review-finalization.js +146 -97
  155. package/dist/lib/task/review-fingerprint.js +2 -2
  156. package/dist/lib/task/sections.js +34 -54
  157. package/dist/lib/task/verification-config.js +11 -0
  158. package/dist/lib/task/verification-engine.js +170 -327
  159. package/dist/lib/task/verification.js +1 -1
  160. package/dist/lib/task/workflow-command.js +17 -0
  161. package/dist/lib/task/workflow-warning-intents.js +0 -16
  162. package/dist/lib/task/workflow-warnings.js +28 -2
  163. package/dist/lib/update.js +2 -0
  164. package/dist/lifecycle-build-manifest.json +3 -3
  165. package/lib/agent-client.ts +9 -0
  166. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
  167. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
  168. package/lib/fs/durable-write.ts +29 -0
  169. package/lib/fs/file-lock.ts +113 -0
  170. package/lib/host-control/audit.ts +22 -0
  171. package/lib/host-control/client.ts +172 -0
  172. package/lib/host-control/command.ts +60 -0
  173. package/lib/host-control/path.ts +167 -0
  174. package/lib/host-control/secure-fs.ts +135 -0
  175. package/lib/host-control/server.ts +180 -0
  176. package/lib/host-control/service.ts +133 -0
  177. package/lib/init.ts +15 -2
  178. package/lib/internal/agent-client.ts +4 -2
  179. package/lib/internal/cli-route-inventory.ts +142 -0
  180. package/lib/internal/codex-lifecycle.ts +14 -12
  181. package/lib/internal/codex-sandbox-controller.ts +7 -2
  182. package/lib/internal/demo-promotion.ts +354 -0
  183. package/lib/internal/demo-transcript.ts +419 -0
  184. package/lib/internal/git-workflow.ts +9 -6
  185. package/lib/internal/host-control.ts +28 -0
  186. package/lib/internal/manual-validation.ts +306 -0
  187. package/lib/internal/platform-checks.ts +12 -4
  188. package/lib/internal/platform-comment.ts +12 -4
  189. package/lib/internal/platform-context.ts +3 -1
  190. package/lib/internal/platform-issue.ts +11 -4
  191. package/lib/internal/platform-metadata.ts +70 -0
  192. package/lib/internal/platform-pr-review.ts +14 -3
  193. package/lib/internal/platform-pr.ts +38 -14
  194. package/lib/internal/platform-release-notes.ts +5 -3
  195. package/lib/internal/platform-security.ts +83 -0
  196. package/lib/internal/pr-review-grade.ts +10 -3
  197. package/lib/internal/release-workflow.ts +91 -87
  198. package/lib/internal/sandbox-control.ts +181 -6
  199. package/lib/internal/task-activity.ts +12 -4
  200. package/lib/internal/task-artifact.ts +15 -79
  201. package/lib/internal/task-context.ts +3 -1
  202. package/lib/internal/task-create.ts +76 -15
  203. package/lib/internal/task-delivery.ts +3 -1
  204. package/lib/internal/task-event.ts +21 -10
  205. package/lib/internal/task-finalization.ts +3 -1
  206. package/lib/internal/task-invalidation.ts +3 -1
  207. package/lib/internal/task-ledger.ts +11 -1
  208. package/lib/internal/task-lifecycle.ts +3 -1
  209. package/lib/internal/task-operation-registry.ts +590 -0
  210. package/lib/internal/task-orchestration.ts +12 -0
  211. package/lib/internal/task-override.ts +9 -3
  212. package/lib/internal/task-qualification.ts +61 -0
  213. package/lib/internal/task-review.ts +7 -50
  214. package/lib/internal/task-short-id.ts +10 -1
  215. package/lib/internal/task-snapshot.ts +3 -1
  216. package/lib/internal/task-validate.ts +32 -12
  217. package/lib/internal/task-verify.ts +4 -1
  218. package/lib/internal/task-warning.ts +7 -1
  219. package/lib/owned-path.ts +26 -0
  220. package/lib/platform/check-status.ts +9 -0
  221. package/lib/platform/comment-safety.ts +255 -0
  222. package/lib/platform/completion-backfill.ts +1 -1
  223. package/lib/platform/github-data.ts +322 -0
  224. package/lib/platform/github-provider.ts +244 -27
  225. package/lib/platform/issue-comments.ts +159 -35
  226. package/lib/platform/issues.ts +4 -245
  227. package/lib/platform/pr-change-report.ts +6 -12
  228. package/lib/platform/pr-checks.ts +13 -77
  229. package/lib/platform/pr-summary.ts +88 -11
  230. package/lib/platform/provider-contract.ts +78 -3
  231. package/lib/platform/provider-validation.ts +85 -3
  232. package/lib/platform/pull-requests.ts +129 -348
  233. package/lib/platform/repository-metadata.ts +453 -0
  234. package/lib/platform/security-alerts.ts +191 -0
  235. package/lib/platform/snapshots.ts +53 -0
  236. package/lib/platform/verification-sync.ts +171 -177
  237. package/lib/process-data/index.ts +7 -0
  238. package/lib/release/workflow.ts +3 -3
  239. package/lib/run/index.ts +5 -0
  240. package/lib/sandbox/commands/control-status.ts +26 -0
  241. package/lib/sandbox/commands/create.ts +53 -12
  242. package/lib/sandbox/commands/ls.ts +7 -0
  243. package/lib/sandbox/commands/rm.ts +3 -996
  244. package/lib/sandbox/commands/show.ts +16 -0
  245. package/lib/sandbox/control/audit.ts +253 -0
  246. package/lib/sandbox/control/client.ts +150 -13
  247. package/lib/sandbox/control/completed-reentry.ts +98 -0
  248. package/lib/sandbox/control/container-identity.ts +27 -43
  249. package/lib/sandbox/control/executor.ts +262 -37
  250. package/lib/sandbox/control/finalization-response.ts +29 -0
  251. package/lib/sandbox/control/identity-sentinel.ts +137 -0
  252. package/lib/sandbox/control/lifecycle.ts +672 -18
  253. package/lib/sandbox/control/native-file-lock.ts +108 -0
  254. package/lib/sandbox/control/protocol.ts +80 -8
  255. package/lib/sandbox/control/server.ts +779 -82
  256. package/lib/sandbox/control/state.ts +84 -3
  257. package/lib/sandbox/control/task-view.ts +240 -0
  258. package/lib/sandbox/control/task-workflow.ts +95 -0
  259. package/lib/sandbox/control/workflow-executor.ts +102 -0
  260. package/lib/sandbox/engines/authority.ts +365 -0
  261. package/lib/sandbox/engines/docker-exact-discovery.ts +132 -0
  262. package/lib/sandbox/index.ts +9 -2
  263. package/lib/sandbox/recovery.ts +49 -6
  264. package/lib/sandbox/removal.ts +2144 -0
  265. package/lib/sandbox/task-cleanup.ts +209 -0
  266. package/lib/sandbox/workspace-view.ts +26 -18
  267. package/lib/sandbox/worktree-safety.ts +4 -1
  268. package/lib/server/index.ts +10 -0
  269. package/lib/task/activity-intent.ts +2 -2
  270. package/lib/task/activity-log.ts +27 -19
  271. package/lib/task/artifact-command.ts +104 -0
  272. package/lib/task/artifact-lifecycle.ts +66 -39
  273. package/lib/task/artifact-name.ts +55 -0
  274. package/lib/task/artifact-operations.ts +580 -0
  275. package/lib/task/artifact-receipts.ts +3 -14
  276. package/lib/task/artifact-repair-intent.ts +152 -0
  277. package/lib/task/artifact-schema.ts +163 -0
  278. package/lib/task/capabilities.ts +82 -22
  279. package/lib/task/codex-orchestration.ts +85 -24
  280. package/lib/task/commit-identity.ts +31 -0
  281. package/lib/task/commit-operation.ts +2 -16
  282. package/lib/task/control-authority.ts +513 -6
  283. package/lib/task/control-recovery.ts +174 -0
  284. package/lib/task/create-service.ts +131 -6
  285. package/lib/task/create.ts +83 -1
  286. package/lib/task/decision-details.ts +3 -71
  287. package/lib/task/events.ts +292 -75
  288. package/lib/task/finalization-state.ts +11 -0
  289. package/lib/task/human-override.ts +3 -8
  290. package/lib/task/implementation-inputs.ts +3 -3
  291. package/lib/task/index.ts +16 -3
  292. package/lib/task/intermediate-cleanup.ts +455 -0
  293. package/lib/task/invalidation.ts +18 -62
  294. package/lib/task/ledger-intents.ts +7 -24
  295. package/lib/task/ledger.ts +1 -3
  296. package/lib/task/lifecycle.ts +17 -1
  297. package/lib/task/local-artifact-finalization.ts +223 -388
  298. package/lib/task/manual-validation-completion.ts +40 -0
  299. package/lib/task/manual-validation-evidence.ts +209 -0
  300. package/lib/task/manual-validation-receipt.ts +152 -0
  301. package/lib/task/manual-validation-shared.ts +50 -0
  302. package/lib/task/manual-validation-transaction.ts +304 -0
  303. package/lib/task/markdown.ts +70 -0
  304. package/lib/task/orchestration.ts +191 -68
  305. package/lib/task/qualification-audit.ts +588 -0
  306. package/lib/task/qualification-confirmation.ts +117 -0
  307. package/lib/task/qualification-intents.ts +150 -0
  308. package/lib/task/review-artifacts.ts +21 -41
  309. package/lib/task/review-command.ts +23 -0
  310. package/lib/task/review-finalization.ts +154 -153
  311. package/lib/task/review-fingerprint.ts +2 -2
  312. package/lib/task/sections.ts +33 -52
  313. package/lib/task/verification-config.ts +12 -0
  314. package/lib/task/verification-engine.ts +165 -370
  315. package/lib/task/verification-types.ts +17 -0
  316. package/lib/task/verification.ts +1 -1
  317. package/lib/task/workflow-command.ts +18 -0
  318. package/lib/task/workflow-warning-intents.ts +0 -13
  319. package/lib/task/workflow-warnings.ts +21 -2
  320. package/lib/update.ts +6 -0
  321. package/package.json +7 -2
  322. package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
  323. package/templates/.agents/rules/README.en.md +2 -0
  324. package/templates/.agents/rules/README.zh-CN.md +2 -0
  325. package/templates/.agents/rules/decision-qualification.en.md +22 -0
  326. package/templates/.agents/rules/decision-qualification.zh-CN.md +22 -0
  327. package/templates/.agents/rules/evidence-reporting.en.md +28 -0
  328. package/templates/.agents/rules/evidence-reporting.zh-CN.md +28 -0
  329. package/templates/.agents/rules/label-milestone-setup.en.md +7 -7
  330. package/templates/.agents/rules/label-milestone-setup.zh-CN.md +7 -7
  331. package/templates/.agents/rules/local-artifact-repair.en.md +5 -1
  332. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +5 -1
  333. package/templates/.agents/rules/security-alerts.en.md +8 -8
  334. package/templates/.agents/rules/security-alerts.zh-CN.md +8 -8
  335. package/templates/.agents/rules/testing-discipline.en.md +2 -0
  336. package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
  337. package/templates/.agents/scripts/package.json +3 -0
  338. package/templates/.agents/skills/analyze-task/SKILL.en.md +15 -2
  339. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +19 -2
  340. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -12
  341. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -12
  342. package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
  343. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
  344. package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
  345. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
  346. package/templates/.agents/skills/code-task/SKILL.en.md +14 -1
  347. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +14 -1
  348. package/templates/.agents/skills/code-task/config/verify.en.json +5 -14
  349. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +5 -14
  350. package/templates/.agents/skills/code-task/reference/code-rules.en.md +5 -4
  351. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +5 -4
  352. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
  353. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
  354. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +3 -1
  355. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +3 -1
  356. package/templates/.agents/skills/code-task/reference/report-template.en.md +42 -15
  357. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +39 -12
  358. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +37 -21
  359. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +37 -21
  360. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  361. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  362. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +4 -0
  363. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +4 -0
  364. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
  365. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
  366. package/templates/.agents/skills/complete-task/SKILL.en.md +10 -23
  367. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +10 -23
  368. package/templates/.agents/skills/create-task/SKILL.en.md +8 -0
  369. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +7 -0
  370. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  371. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  372. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  373. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  374. package/templates/.agents/skills/init-labels/SKILL.en.md +8 -8
  375. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +8 -8
  376. package/templates/.agents/skills/init-milestones/SKILL.en.md +9 -11
  377. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +9 -11
  378. package/templates/.agents/skills/plan-task/SKILL.en.md +14 -1
  379. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +16 -1
  380. package/templates/.agents/skills/plan-task/config/verify.en.json +4 -14
  381. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +4 -14
  382. package/templates/.agents/skills/release/SKILL.en.md +7 -3
  383. package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
  384. package/templates/.agents/skills/review-analysis/SKILL.en.md +14 -1
  385. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -1
  386. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -18
  387. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -18
  388. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +50 -23
  389. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +45 -18
  390. package/templates/.agents/skills/review-code/SKILL.en.md +14 -1
  391. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +14 -1
  392. package/templates/.agents/skills/review-code/config/verify.en.json +1 -23
  393. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -23
  394. package/templates/.agents/skills/review-code/reference/report-template.en.md +49 -22
  395. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +45 -18
  396. package/templates/.agents/skills/review-plan/SKILL.en.md +14 -1
  397. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -1
  398. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -18
  399. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -18
  400. package/templates/.agents/skills/review-plan/reference/report-template.en.md +49 -22
  401. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +45 -18
  402. package/templates/.agents/skills/review-pr/SKILL.en.md +5 -1
  403. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +5 -1
  404. package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -3
  405. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -3
  406. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +25 -4
  407. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +25 -4
  408. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
  409. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
  410. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +5 -1
  411. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +5 -1
  412. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  413. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  414. package/templates/.agents/skills/test/SKILL.en.md +29 -7
  415. package/templates/.agents/skills/test/SKILL.zh-CN.md +29 -7
  416. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +14 -0
  417. package/templates/.agents/templates/task.en.md +6 -0
  418. package/templates/.agents/templates/task.zh-CN.md +6 -0
  419. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  420. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  421. package/dist/lib/platform/adapters.js +0 -56
  422. package/lib/platform/adapters.ts +0 -197
  423. package/templates/.agents/scripts/security-alerts.github.sh +0 -151
  424. package/templates/.agents/scripts/security-alerts.sh +0 -31
  425. package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +0 -146
  426. package/templates/.agents/skills/init-labels/scripts/init-labels.sh +0 -32
  427. package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +0 -294
  428. package/templates/.agents/skills/init-milestones/scripts/init-milestones.sh +0 -6
@@ -1,17 +1,455 @@
1
1
  import { execFileSync } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
+ import os from 'node:os';
3
4
  import path from 'node:path';
4
- import { randomUUID } from 'node:crypto';
5
+ import { createHash, randomUUID } from 'node:crypto';
5
6
  import { buildProcessTreeStopCommand } from '../../server/process-control.js';
6
7
  import { getProcessIdentityState, getProcessStartTime } from '../../server/process-state.js';
7
8
  import { DEFAULT_SANDBOX_CONTROL_TIMING } from './protocol.js';
8
9
  import { inspectSandboxControlContainer } from './container-identity.js';
10
+ import { acquireSandboxResourceLock, resolveSandboxLockNamespace } from './native-file-lock.js';
11
+ import { isSandboxAuthorityEvidence } from '../engines/authority.js';
9
12
  import { parseSandboxControlStatus, readExecution, terminateSandboxControlExecution } from './state.js';
10
13
  const DEFAULT_QUIESCE_TIMEOUT_MS = DEFAULT_SANDBOX_CONTROL_TIMING.quiesceDeadlineMs;
11
14
  const QUIESCING_FILE = 'quiescing.json';
12
15
  const BROKER_STARTING_FILE = 'broker-starting.json';
13
16
  const REPLACEMENT_FILE = 'replacement.json';
14
17
  const REPLACEMENT_STATE_SUFFIX = '.replacement-state.json';
18
+ const REMOVAL_JOURNAL_ROOT = path.join('.agent-infra', 'sandbox-removal-journal');
19
+ export const SANDBOX_REMOVAL_JOURNAL_PHASES = [
20
+ 'prepared', 'target-committed', 'container-removal', 'container-absent',
21
+ 'carrier-finalizing', 'carrier-removed', 'workspace-finalizing', 'workspace-removed',
22
+ 'branch-finalizing', 'branch-removed', 'tool-finalizing', 'tool-removed',
23
+ 'shell-finalizing', 'shell-removed', 'share-finalizing', 'share-removed', 'completed'
24
+ ];
25
+ function removalCarrierDigest(engine, containerId, generation) {
26
+ return createHash('sha256').update(`${engine}\0${containerId}\0${generation}`).digest('hex');
27
+ }
28
+ function assertPrivateDirectory(directory) {
29
+ const home = path.resolve(os.homedir());
30
+ const resolved = path.resolve(directory);
31
+ const relative = path.relative(home, resolved);
32
+ if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
33
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
34
+ }
35
+ let current = home;
36
+ for (const segment of relative.split(path.sep).filter(Boolean)) {
37
+ current = path.join(current, segment);
38
+ const stat = fs.lstatSync(current);
39
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
40
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
41
+ }
42
+ if (process.platform !== 'win32' && typeof process.getuid === 'function' && stat.uid !== process.getuid()) {
43
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
44
+ }
45
+ }
46
+ }
47
+ function removalJournalDomain(lockDomain, create = true) {
48
+ const namespace = resolveSandboxLockNamespace('sandbox-removal-journal', { lockDomain });
49
+ const root = path.join(path.dirname(namespace.lockRoot), REMOVAL_JOURNAL_ROOT.split(path.sep).at(-1));
50
+ if (create)
51
+ fs.mkdirSync(root, { recursive: true, mode: 0o700 });
52
+ assertPrivateDirectory(root);
53
+ const domain = path.join(root, lockDomain);
54
+ if (create)
55
+ fs.mkdirSync(domain, { recursive: true, mode: 0o700 });
56
+ assertPrivateDirectory(domain);
57
+ return domain;
58
+ }
59
+ function removalJournalPath(manifest) {
60
+ return path.join(removalJournalDomain(manifest.authorityEvidence.lockDomain), `${removalCarrierDigest(manifest.engine, manifest.containerIdentity.id, manifest.generation)}.json`);
61
+ }
62
+ function removalJournalPathForRecord(record) {
63
+ return path.join(removalJournalDomain(record.lockDomain, false), `${record.carrierIdentityDigest}.json`);
64
+ }
65
+ function isRecord(value) {
66
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
67
+ }
68
+ function hasExactKeys(value, keys) {
69
+ const actual = Object.keys(value).sort();
70
+ return actual.length === keys.length && actual.every((key, index) => key === keys[index]);
71
+ }
72
+ function isRemovalPermitCommit(value) {
73
+ if (!isRecord(value) || typeof value.path !== 'string' || !path.isAbsolute(value.path)
74
+ || (value.mode !== 'clean' && value.mode !== 'discard') || !isRecord(value.snapshot)
75
+ || typeof value.snapshot.worktree !== 'string' || !path.isAbsolute(value.snapshot.worktree)
76
+ || typeof value.snapshot.branch !== 'string' || typeof value.snapshot.head !== 'string'
77
+ || typeof value.snapshot.identity !== 'string' || !Array.isArray(value.snapshot.changes)
78
+ || value.snapshot.changes.some((change) => !isRecord(change)
79
+ || typeof change.indexStatus !== 'string' || typeof change.worktreeStatus !== 'string'
80
+ || typeof change.path !== 'string'
81
+ || (change.originalPath !== undefined && typeof change.originalPath !== 'string'))
82
+ || (value.snapshot.source !== undefined
83
+ && value.snapshot.source !== 'registered' && value.snapshot.source !== 'recovered'))
84
+ return false;
85
+ if (value.snapshot.recovery === undefined)
86
+ return true;
87
+ const recovery = value.snapshot.recovery;
88
+ return isRecord(recovery)
89
+ && typeof recovery.repoRoot === 'string' && path.isAbsolute(recovery.repoRoot)
90
+ && typeof recovery.worktreeBase === 'string' && path.isAbsolute(recovery.worktreeBase)
91
+ && typeof recovery.branch === 'string'
92
+ && (recovery.identitySource === 'branch-only' || recovery.identitySource === 'task-bound')
93
+ && (recovery.taskId === null || typeof recovery.taskId === 'string');
94
+ }
95
+ function parseRemovalJournal(raw) {
96
+ let value;
97
+ try {
98
+ value = JSON.parse(raw);
99
+ }
100
+ catch {
101
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
102
+ }
103
+ if (!isRecord(value) || !hasExactKeys(value, [
104
+ 'authorityFingerprint', 'carrierIdentityDigest', 'containerId', 'engine', 'expectedOldJournalRevision',
105
+ 'generation', 'handoffId', 'lockDomain', 'newOperation', 'oldOperation', 'operation', 'owner', 'phase',
106
+ 'recordedAt', 'revision', 'target', 'transitionId', 'version'
107
+ ]) || !isRecord(value.owner) || !hasExactKeys(value.owner, ['leaseNonce', 'pid', 'startTime'])
108
+ || !isRecord(value.target) || !hasExactKeys(value.target, [
109
+ 'branch', 'controlRoot', 'permitDigest', 'permits', 'project', 'removeBranch', 'removeShare',
110
+ 'removeWorktree', 'sharePath', 'shellPaths', 'targetDigest', 'toolPaths', 'workspaceViewPaths',
111
+ 'worktreePaths'
112
+ ]) || value.version !== 2 || value.operation !== 'sandbox-rm'
113
+ || value.oldOperation !== 'sandbox-rm' || value.newOperation !== 'sandbox-rm'
114
+ || typeof value.handoffId !== 'string' || value.handoffId.length === 0
115
+ || typeof value.transitionId !== 'string' || value.transitionId.length === 0
116
+ || typeof value.engine !== 'string' || value.engine.length === 0
117
+ || typeof value.containerId !== 'string' || value.containerId.length === 0
118
+ || typeof value.generation !== 'string' || value.generation.length === 0
119
+ || typeof value.authorityFingerprint !== 'string' || !/^[a-f0-9]{64}$/u.test(value.authorityFingerprint)
120
+ || typeof value.carrierIdentityDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(value.carrierIdentityDigest)
121
+ || typeof value.lockDomain !== 'string' || !/^[a-f0-9]{64}$/u.test(value.lockDomain)
122
+ || !Number.isSafeInteger(value.owner.pid) || value.owner.pid <= 0
123
+ || !Number.isSafeInteger(value.owner.startTime) || typeof value.owner.leaseNonce !== 'string'
124
+ || value.owner.leaseNonce.length === 0
125
+ || !SANDBOX_REMOVAL_JOURNAL_PHASES.includes(value.phase)
126
+ || !(value.expectedOldJournalRevision === null
127
+ || (Number.isSafeInteger(value.expectedOldJournalRevision) && value.expectedOldJournalRevision >= 0))
128
+ || !Number.isSafeInteger(value.revision) || value.revision <= 0
129
+ || typeof value.target.branch !== 'string' || value.target.branch.length === 0
130
+ || typeof value.target.project !== 'string' || value.target.project.length === 0
131
+ || typeof value.target.controlRoot !== 'string' || path.isAbsolute(value.target.controlRoot) === false
132
+ || typeof value.target.targetDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(value.target.targetDigest)
133
+ || typeof value.target.permitDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(value.target.permitDigest)
134
+ || typeof value.target.removeWorktree !== 'boolean'
135
+ || typeof value.target.removeBranch !== 'boolean'
136
+ || typeof value.target.removeShare !== 'boolean'
137
+ || typeof value.target.sharePath !== 'string' || !path.isAbsolute(value.target.sharePath)
138
+ || !Array.isArray(value.target.worktreePaths) || value.target.worktreePaths.some((entry) => typeof entry !== 'string' || !path.isAbsolute(entry))
139
+ || !Array.isArray(value.target.workspaceViewPaths) || value.target.workspaceViewPaths.some((entry) => typeof entry !== 'string' || !path.isAbsolute(entry))
140
+ || !Array.isArray(value.target.toolPaths) || value.target.toolPaths.some((entry) => typeof entry !== 'string' || !path.isAbsolute(entry))
141
+ || !Array.isArray(value.target.shellPaths) || value.target.shellPaths.some((entry) => typeof entry !== 'string' || !path.isAbsolute(entry))
142
+ || !Array.isArray(value.target.permits)
143
+ || value.target.permits.some((permit) => !isRemovalPermitCommit(permit))
144
+ || !Number.isSafeInteger(value.recordedAt)) {
145
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
146
+ }
147
+ const expected = value.expectedOldJournalRevision;
148
+ const revision = value.revision;
149
+ if ((expected === null && revision !== 1) || (expected !== null && revision !== expected + 1)) {
150
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
151
+ }
152
+ return {
153
+ version: 2,
154
+ operation: 'sandbox-rm',
155
+ oldOperation: 'sandbox-rm',
156
+ newOperation: 'sandbox-rm',
157
+ handoffId: value.handoffId,
158
+ transitionId: value.transitionId,
159
+ engine: value.engine,
160
+ containerId: value.containerId,
161
+ generation: value.generation,
162
+ authorityFingerprint: value.authorityFingerprint,
163
+ carrierIdentityDigest: value.carrierIdentityDigest,
164
+ lockDomain: value.lockDomain,
165
+ owner: {
166
+ pid: value.owner.pid,
167
+ startTime: value.owner.startTime,
168
+ leaseNonce: value.owner.leaseNonce
169
+ },
170
+ phase: value.phase,
171
+ expectedOldJournalRevision: expected,
172
+ revision,
173
+ target: {
174
+ branch: value.target.branch,
175
+ project: value.target.project,
176
+ controlRoot: value.target.controlRoot,
177
+ targetDigest: value.target.targetDigest,
178
+ permitDigest: value.target.permitDigest,
179
+ removeWorktree: value.target.removeWorktree,
180
+ removeBranch: value.target.removeBranch,
181
+ removeShare: value.target.removeShare,
182
+ worktreePaths: value.target.worktreePaths,
183
+ workspaceViewPaths: value.target.workspaceViewPaths,
184
+ toolPaths: value.target.toolPaths,
185
+ shellPaths: value.target.shellPaths,
186
+ sharePath: value.target.sharePath,
187
+ permits: value.target.permits
188
+ },
189
+ recordedAt: value.recordedAt
190
+ };
191
+ }
192
+ function readRemovalJournalAt(target) {
193
+ if (!fs.existsSync(target))
194
+ return null;
195
+ const stat = fs.lstatSync(target);
196
+ if (!stat.isFile() || stat.isSymbolicLink())
197
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
198
+ return parseRemovalJournal(fs.readFileSync(target, 'utf8'));
199
+ }
200
+ export function readSandboxRemovalJournal(manifest) {
201
+ return readRemovalJournalAt(removalJournalPath(manifest));
202
+ }
203
+ export function listSandboxRemovalJournals(filter = {}) {
204
+ const namespace = resolveSandboxLockNamespace('sandbox-removal-journal');
205
+ const root = path.join(path.dirname(namespace.lockRoot), REMOVAL_JOURNAL_ROOT.split(path.sep).at(-1));
206
+ if (!fs.existsSync(root))
207
+ return [];
208
+ assertPrivateDirectory(root);
209
+ const journals = [];
210
+ for (const domainEntry of fs.readdirSync(root, { withFileTypes: true })) {
211
+ if (!domainEntry.isDirectory() || domainEntry.isSymbolicLink()) {
212
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
213
+ }
214
+ const domain = path.join(root, domainEntry.name);
215
+ assertPrivateDirectory(domain);
216
+ for (const entry of fs.readdirSync(domain, { withFileTypes: true })) {
217
+ if (!entry.isFile() || entry.isSymbolicLink() || !entry.name.endsWith('.json')) {
218
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
219
+ }
220
+ const record = readRemovalJournalAt(path.join(domain, entry.name));
221
+ if (!record || record.lockDomain !== domainEntry.name
222
+ || `${record.carrierIdentityDigest}.json` !== entry.name) {
223
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
224
+ }
225
+ if ((!filter.branch || record.target.branch === filter.branch)
226
+ && (!filter.project || record.target.project === filter.project)
227
+ && (!filter.targetDigest || record.target.targetDigest === filter.targetDigest)) {
228
+ journals.push(record);
229
+ }
230
+ }
231
+ }
232
+ return journals;
233
+ }
234
+ export function clearSandboxRemovalJournalRecord(record) {
235
+ const target = removalJournalPathForRecord(record);
236
+ if (fs.existsSync(target)) {
237
+ const current = readRemovalJournalAt(target);
238
+ if (!current || current.revision !== record.revision || current.handoffId !== record.handoffId) {
239
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_REVISION_MISMATCH');
240
+ }
241
+ fs.rmSync(target, { force: true });
242
+ }
243
+ }
244
+ export function clearSandboxRemovalJournal(manifest) {
245
+ const target = removalJournalPath(manifest);
246
+ if (fs.existsSync(target))
247
+ fs.rmSync(target, { force: true });
248
+ }
249
+ function defaultRemovalTarget(manifest) {
250
+ const targetDigest = createHash('sha256')
251
+ .update(`${manifest.project}\0${manifest.branch}\0${manifest.container}\0${manifest.channelDir}`)
252
+ .digest('hex');
253
+ return {
254
+ branch: manifest.branch,
255
+ project: manifest.project,
256
+ controlRoot: path.dirname(manifest.channelDir),
257
+ targetDigest,
258
+ permitDigest: createHash('sha256').update('none').digest('hex'),
259
+ removeWorktree: false,
260
+ removeBranch: false,
261
+ removeShare: false,
262
+ worktreePaths: [],
263
+ workspaceViewPaths: [],
264
+ toolPaths: [],
265
+ shellPaths: [],
266
+ sharePath: path.join(path.dirname(manifest.channelDir), 'share'),
267
+ permits: []
268
+ };
269
+ }
270
+ export function isDefaultSandboxRemovalJournal(journal) {
271
+ if (journal.phase !== 'carrier-removed' && journal.phase !== 'completed')
272
+ return false;
273
+ const { target } = journal;
274
+ const expected = defaultRemovalTarget({
275
+ project: target.project, branch: target.branch,
276
+ container: path.basename(path.dirname(target.controlRoot)),
277
+ channelDir: path.join(target.controlRoot, 'channel')
278
+ });
279
+ return Object.entries(expected).every(([key, value]) => JSON.stringify(target[key]) === JSON.stringify(value));
280
+ }
281
+ function sameRemovalTarget(left, right) {
282
+ return JSON.stringify(left) === JSON.stringify(right);
283
+ }
284
+ function phaseIndex(phase) {
285
+ return SANDBOX_REMOVAL_JOURNAL_PHASES.indexOf(phase);
286
+ }
287
+ export function sandboxRemovalPhaseIndex(phase) {
288
+ return phaseIndex(phase);
289
+ }
290
+ function assertNextRemovalPhase(current, next) {
291
+ const currentIndex = phaseIndex(current);
292
+ const nextIndex = phaseIndex(next);
293
+ if (nextIndex === currentIndex)
294
+ return false;
295
+ if (current === 'target-committed' && next === 'container-absent')
296
+ return true;
297
+ if (nextIndex !== currentIndex + 1) {
298
+ throw new Error('SANDBOX_CONTROL_REMOVAL_PHASE_TRANSITION_INVALID');
299
+ }
300
+ return true;
301
+ }
302
+ function assertRemovalJournalLock(record, resourceLock) {
303
+ const namespace = resolveSandboxLockNamespace(`${record.engine}:${record.containerId}`, {
304
+ lockDomain: record.lockDomain
305
+ });
306
+ if (resourceLock.lockDomain !== record.lockDomain || resourceLock.path !== namespace.lockPath) {
307
+ throw new Error('SANDBOX_CONTROL_REMOVAL_LOCK_MISMATCH');
308
+ }
309
+ }
310
+ function writeRemovalJournalAt(target, base, phase, expectedOldJournalRevision) {
311
+ const existing = readRemovalJournalAt(target);
312
+ if (expectedOldJournalRevision === null
313
+ ? existing !== null
314
+ : existing?.revision !== expectedOldJournalRevision) {
315
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_REVISION_MISMATCH');
316
+ }
317
+ const record = {
318
+ ...base,
319
+ phase,
320
+ expectedOldJournalRevision,
321
+ revision: expectedOldJournalRevision === null ? 1 : expectedOldJournalRevision + 1,
322
+ recordedAt: Date.now()
323
+ };
324
+ const temporary = `${target}.${process.pid}.${randomUUID()}.tmp`;
325
+ try {
326
+ fs.writeFileSync(temporary, `${JSON.stringify(record)}\n`, { mode: 0o600, flag: 'wx' });
327
+ if (expectedOldJournalRevision === null && fs.existsSync(target)) {
328
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_REVISION_MISMATCH');
329
+ }
330
+ fs.renameSync(temporary, target);
331
+ }
332
+ finally {
333
+ fs.rmSync(temporary, { force: true });
334
+ }
335
+ return record;
336
+ }
337
+ function writeRemovalJournal(manifest, base, phase, expectedOldJournalRevision) {
338
+ return writeRemovalJournalAt(removalJournalPath(manifest), base, phase, expectedOldJournalRevision);
339
+ }
340
+ function startRemovalJournal(manifest, options) {
341
+ const target = removalJournalPath(manifest);
342
+ const existing = readRemovalJournalAt(target);
343
+ if (existing && (existing.engine !== manifest.engine || existing.containerId !== manifest.containerIdentity.id
344
+ || existing.generation !== manifest.generation
345
+ || existing.authorityFingerprint !== manifest.authorityEvidence.authorityFingerprint
346
+ || existing.lockDomain !== manifest.authorityEvidence.lockDomain)) {
347
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_IDENTITY_MISMATCH');
348
+ }
349
+ const targetCommit = options.target ?? existing?.target ?? defaultRemovalTarget(manifest);
350
+ if (existing && options.target && !sameRemovalTarget(existing.target, options.target)) {
351
+ throw new Error('SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH');
352
+ }
353
+ if (existing) {
354
+ const state = options.identityProbe(existing.owner);
355
+ if (state === 'alive')
356
+ throw new Error('SANDBOX_CONTROL_REMOVE_RETRY_IN_PROGRESS');
357
+ if (state === 'unknown')
358
+ throw new Error('SANDBOX_CONTROL_REMOVE_OWNER_UNKNOWN');
359
+ }
360
+ const startTime = getProcessStartTime(process.pid);
361
+ if (startTime === null)
362
+ throw new Error('SANDBOX_CONTROL_REMOVE_OWNER_UNAVAILABLE');
363
+ const base = {
364
+ version: 2,
365
+ operation: 'sandbox-rm',
366
+ oldOperation: 'sandbox-rm',
367
+ newOperation: 'sandbox-rm',
368
+ handoffId: randomUUID(),
369
+ transitionId: randomUUID(),
370
+ engine: manifest.engine,
371
+ containerId: manifest.containerIdentity.id,
372
+ generation: manifest.generation,
373
+ authorityFingerprint: manifest.authorityEvidence.authorityFingerprint,
374
+ carrierIdentityDigest: removalCarrierDigest(manifest.engine, manifest.containerIdentity.id, manifest.generation),
375
+ lockDomain: manifest.authorityEvidence.lockDomain,
376
+ owner: { pid: process.pid, startTime, leaseNonce: randomUUID() },
377
+ target: targetCommit
378
+ };
379
+ const initialPhase = existing?.phase ?? 'prepared';
380
+ let record = writeRemovalJournal(manifest, base, initialPhase, existing?.revision ?? null);
381
+ return {
382
+ get record() { return record; },
383
+ write(phase) {
384
+ if (!assertNextRemovalPhase(record.phase, phase))
385
+ return record;
386
+ record = writeRemovalJournal(manifest, base, phase, record.revision);
387
+ return record;
388
+ }
389
+ };
390
+ }
391
+ export function claimSandboxRemovalJournal(journal, options = {}) {
392
+ const target = removalJournalPathForRecord(journal);
393
+ const resourceLock = options.resourceLock
394
+ ?? acquireSandboxResourceLock(`${journal.engine}:${journal.containerId}`, { lockDomain: journal.lockDomain });
395
+ const ownedLock = options.resourceLock ? null : resourceLock;
396
+ try {
397
+ assertRemovalJournalLock(journal, resourceLock);
398
+ const current = readRemovalJournalAt(target);
399
+ if (!current || current.revision !== journal.revision || current.handoffId !== journal.handoffId) {
400
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_REVISION_MISMATCH');
401
+ }
402
+ const state = (options.identityProbe ?? getProcessIdentityState)(current.owner);
403
+ if (state === 'alive')
404
+ throw new Error('SANDBOX_CONTROL_REMOVE_RETRY_IN_PROGRESS');
405
+ if (state === 'unknown')
406
+ throw new Error('SANDBOX_CONTROL_REMOVE_OWNER_UNKNOWN');
407
+ const startTime = getProcessStartTime(process.pid);
408
+ if (startTime === null)
409
+ throw new Error('SANDBOX_CONTROL_REMOVE_OWNER_UNAVAILABLE');
410
+ const base = {
411
+ ...current,
412
+ handoffId: randomUUID(),
413
+ transitionId: randomUUID(),
414
+ owner: { pid: process.pid, startTime, leaseNonce: randomUUID() }
415
+ };
416
+ return writeRemovalJournalAt(target, base, current.phase, current.revision);
417
+ }
418
+ finally {
419
+ ownedLock?.release();
420
+ }
421
+ }
422
+ export function advanceSandboxRemovalJournalPhase(journal, phase, options = {}) {
423
+ const target = removalJournalPathForRecord(journal);
424
+ const resourceLock = options.resourceLock
425
+ ?? acquireSandboxResourceLock(`${journal.engine}:${journal.containerId}`, { lockDomain: journal.lockDomain });
426
+ const ownedLock = options.resourceLock ? null : resourceLock;
427
+ try {
428
+ assertRemovalJournalLock(journal, resourceLock);
429
+ const current = readRemovalJournalAt(target);
430
+ if (!current || current.revision !== journal.revision || current.handoffId !== journal.handoffId) {
431
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_REVISION_MISMATCH');
432
+ }
433
+ if (!assertNextRemovalPhase(current.phase, phase))
434
+ return current;
435
+ const { phase: _phase, expectedOldJournalRevision: _expected, revision: _revision, recordedAt: _recordedAt, ...base } = current;
436
+ return writeRemovalJournalAt(target, base, phase, current.revision);
437
+ }
438
+ finally {
439
+ ownedLock?.release();
440
+ }
441
+ }
442
+ export function advanceSandboxRemovalJournalToPhase(journal, phase, resourceLock) {
443
+ let current = journal;
444
+ while (phaseIndex(current.phase) < phaseIndex(phase)) {
445
+ const next = current.phase === 'target-committed' && phase === 'container-absent'
446
+ ? phase : SANDBOX_REMOVAL_JOURNAL_PHASES[phaseIndex(current.phase) + 1];
447
+ if (!next)
448
+ throw new Error('SANDBOX_CONTROL_REMOVAL_PHASE_TRANSITION_INVALID');
449
+ current = advanceSandboxRemovalJournalPhase(current, next, { resourceLock });
450
+ }
451
+ return current;
452
+ }
15
453
  async function awaitWithDeadline(operation, deadlineAt, errorCode) {
16
454
  const remaining = deadlineAt - Date.now();
17
455
  if (remaining <= 0)
@@ -62,6 +500,25 @@ function markSandboxControlRootQuiescing(root) {
62
500
  throw new Error('SANDBOX_CONTROL_CHANNEL_INVALID');
63
501
  }
64
502
  }
503
+ function recordSandboxRemovalPending(root, manifest, phase, journal) {
504
+ const pendingPath = path.join(root, 'removal-pending.json');
505
+ const temporary = `${pendingPath}.${process.pid}.${randomUUID()}.tmp`;
506
+ try {
507
+ fs.writeFileSync(temporary, `${JSON.stringify({
508
+ version: 1,
509
+ phase,
510
+ containerId: manifest.containerIdentity.id,
511
+ generation: manifest.generation,
512
+ authorityFingerprint: manifest.authorityEvidence.authorityFingerprint,
513
+ recordedAt: Date.now()
514
+ })}\n`, { mode: 0o600, flag: 'wx' });
515
+ fs.renameSync(temporary, pendingPath);
516
+ }
517
+ finally {
518
+ fs.rmSync(temporary, { force: true });
519
+ }
520
+ journal.write(phase === 'container-verification' ? 'container-absent' : 'container-removal');
521
+ }
65
522
  function readStartupOwner(filePath) {
66
523
  if (!fs.existsSync(filePath))
67
524
  return null;
@@ -446,9 +903,10 @@ function readSandboxControlManifestValue(manifestPath) {
446
903
  const expectedKeys = [
447
904
  'branch', 'channelDir', 'container', 'containerIdentity', 'engine', 'generation',
448
905
  'mode', 'processingDir', 'project', 'publicStatusDir', 'repoRoot', 'runtimeDir',
449
- 'taskId', 'token', 'worktreeRoot'
906
+ 'taskId', 'token', 'worktreeRoot', 'authorityEvidence', 'controlRootId',
450
907
  ];
451
- if (Object.keys(manifest).sort().join(',') !== expectedKeys.sort().join(',')) {
908
+ const actualKeys = Object.keys(manifest).sort().join(',');
909
+ if (actualKeys !== expectedKeys.sort().join(',')) {
452
910
  throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
453
911
  }
454
912
  if (typeof candidate.containerIdentity !== 'object' || candidate.containerIdentity === null
@@ -470,8 +928,10 @@ function readSandboxControlManifestValue(manifestPath) {
470
928
  || typeof candidate.processingDir !== 'string' || typeof candidate.runtimeDir !== 'string'
471
929
  || typeof candidate.token !== 'string'
472
930
  || typeof candidate.generation !== 'string'
931
+ || typeof candidate.controlRootId !== 'string' || !/^[a-f0-9]{64,128}$/u.test(candidate.controlRootId)
473
932
  || (candidate.mode === 'task-bound' && (typeof candidate.taskId !== 'string' || candidate.taskId.length === 0))
474
- || (candidate.mode === 'branch-only' && candidate.taskId !== null))
933
+ || (candidate.mode === 'branch-only' && candidate.taskId !== null)
934
+ || !isSandboxAuthorityEvidence(candidate.authorityEvidence))
475
935
  throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
476
936
  const root = path.dirname(path.resolve(manifestPath));
477
937
  if (path.resolve(candidate.channelDir) !== path.join(root, 'channel')
@@ -492,15 +952,17 @@ function readSandboxControlManifestValue(manifestPath) {
492
952
  id: containerIdentity.id,
493
953
  labels: { ...containerIdentity.labels }
494
954
  },
955
+ authorityEvidence: candidate.authorityEvidence,
495
956
  branch: candidate.branch,
496
957
  mode: candidate.mode,
497
958
  taskId: candidate.taskId,
498
959
  token: candidate.token,
499
960
  generation: candidate.generation,
961
+ controlRootId: candidate.controlRootId,
500
962
  channelDir: candidate.channelDir,
501
963
  publicStatusDir: candidate.publicStatusDir,
502
964
  processingDir: candidate.processingDir,
503
- runtimeDir: candidate.runtimeDir
965
+ runtimeDir: candidate.runtimeDir,
504
966
  };
505
967
  return normalized;
506
968
  }
@@ -754,6 +1216,16 @@ export async function quiesceSandboxControlRoot(root, options = {}) {
754
1216
  return 'stopped';
755
1217
  }
756
1218
  export async function removeSandboxControlRoot(root, options) {
1219
+ try {
1220
+ await removeSandboxControlRootAttempt(root, options);
1221
+ }
1222
+ catch (error) {
1223
+ if (!(error instanceof Error) || error.message !== 'SANDBOX_CONTROL_OWNER_UNAVAILABLE')
1224
+ throw error;
1225
+ await removeSandboxControlRootAttempt(root, options);
1226
+ }
1227
+ }
1228
+ async function removeSandboxControlRootAttempt(root, options) {
757
1229
  const resolvedRoot = path.resolve(root);
758
1230
  if (!fs.existsSync(resolvedRoot))
759
1231
  return;
@@ -761,142 +1233,198 @@ export async function removeSandboxControlRoot(root, options) {
761
1233
  if (!stat.isDirectory() || stat.isSymbolicLink())
762
1234
  throw new Error('SANDBOX_CONTROL_CHANNEL_INVALID');
763
1235
  const manifestPath = path.join(resolvedRoot, 'manifest.json');
1236
+ const manifestRawBeforeLock = fs.readFileSync(manifestPath, 'utf8');
764
1237
  const manifest = readSandboxControlManifest(manifestPath);
765
- const inspectContainer = options.inspectContainer
766
- ?? ((timeoutMs) => inspectSandboxControlContainer(manifest, { timeoutMs }));
767
- const timeoutMs = options.timeoutMs ?? options.timing?.quiesceDeadlineMs ?? DEFAULT_QUIESCE_TIMEOUT_MS;
768
- const identityProbe = options.identityProbe ?? getProcessIdentityState;
769
- const deadlineAt = Date.now() + timeoutMs;
770
- const forceAt = forceDeadline(deadlineAt, timeoutMs);
771
- const remaining = () => Math.max(0, deadlineAt - Date.now());
772
- const observation = await awaitWithDeadline(() => inspectContainer(remaining()), deadlineAt, 'SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED');
773
- if (observation.state === 'unknown')
774
- throw new Error(`SANDBOX_CONTROL_CONTAINER_UNKNOWN: ${observation.reason}`);
775
- if (options.requireAbsent && observation.state !== 'absent') {
776
- throw new Error('SANDBOX_CONTROL_CONTAINER_REAPPEARED');
777
- }
778
- markSandboxControlRootQuiescing(resolvedRoot);
779
- await waitForStartupTransition(resolvedRoot, Math.max(0, forceAt - Date.now()), identityProbe);
780
- const brokerPath = path.join(resolvedRoot, 'broker.json');
781
- const statusPath = path.join(resolvedRoot, 'public', 'status.json');
782
- const broker = readBrokerOwner(brokerPath);
783
- const status = readStatusOwner(statusPath);
784
- if (broker && (broker.token !== manifest.token || broker.generation !== manifest.generation)) {
785
- throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
786
- }
787
- if (status && status.generation !== manifest.generation) {
788
- throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
789
- }
790
- if (broker && status && !sameOwner(broker, status.broker)
791
- && (ownerLive(broker, identityProbe) || ownerLive(status.broker, identityProbe))) {
792
- throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
793
- }
794
- const platform = options.platform ?? process.platform;
795
- const owner = broker && ownerLive(broker, identityProbe) ? broker
796
- : status && ownerLive(status.broker, identityProbe) ? status.broker : null;
797
- const selfOwner = options.selfOwner;
798
- const selfOwned = Boolean(selfOwner && broker && sameOwner(broker, selfOwner));
799
- if (selfOwner && (!broker || !selfOwned || broker.token !== manifest.token || broker.generation !== manifest.generation)) {
800
- throw new Error('SANDBOX_CONTROL_OWNER_TRANSITION');
801
- }
802
- let executions = readExecutions(manifest);
803
- if (owner && !selfOwned)
804
- signalOwner(owner, platform, false);
805
- for (const execution of executions) {
806
- terminateSandboxControlExecution(execution, { platform, timeoutMs: 0, allowForce: false, identityProbe });
807
- }
808
- const softDeadlineAt = Math.min(forceAt, Date.now() + Math.floor(Math.max(0, forceAt - Date.now()) / 2));
809
- while (Date.now() < softDeadlineAt) {
810
- if ((!owner || selfOwned || !ownerLive(owner, identityProbe))
811
- && !executions.some((execution) => executionAlive(execution, platform, identityProbe)))
812
- break;
813
- await new Promise((resolve) => setTimeout(resolve, 25));
814
- }
815
- if (observation.state === 'found') {
816
- if (Date.now() >= deadlineAt)
817
- throw new Error('SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED');
818
- await awaitWithDeadline(() => options.removeContainer(remaining()), deadlineAt, 'SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED');
819
- const afterRemoval = await awaitWithDeadline(() => inspectContainer(remaining()), deadlineAt, 'SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED');
820
- if (afterRemoval.state !== 'absent') {
821
- throw new Error(afterRemoval.state === 'unknown'
822
- ? `SANDBOX_CONTROL_CONTAINER_UNKNOWN: ${afterRemoval.reason}`
823
- : 'SANDBOX_CONTROL_CONTAINER_STILL_EXISTS');
1238
+ const resourceLock = options.resourceLock ?? acquireSandboxResourceLock(`${manifest.engine}:${manifest.containerIdentity.id}`, { lockDomain: manifest.authorityEvidence.lockDomain });
1239
+ try {
1240
+ if (fs.readFileSync(manifestPath, 'utf8') !== manifestRawBeforeLock) {
1241
+ throw new Error('SANDBOX_CONTROL_MANIFEST_CHANGED');
824
1242
  }
825
- }
826
- const currentManifest = readSandboxControlManifest(manifestPath);
827
- if (currentManifest.token !== manifest.token || currentManifest.generation !== manifest.generation
828
- || !isSandboxControlRootQuiescing(resolvedRoot)) {
829
- throw new Error('SANDBOX_CONTROL_OWNER_TRANSITION');
830
- }
831
- const currentBroker = readBrokerOwner(brokerPath);
832
- if (selfOwned && (!currentBroker || !selfOwner || !sameOwner(selfOwner, currentBroker)
833
- || currentBroker.token !== manifest.token || currentBroker.generation !== manifest.generation)) {
834
- throw new Error('SANDBOX_CONTROL_OWNER_REPLACED');
835
- }
836
- if (owner && currentBroker && !sameOwner(owner, currentBroker)) {
837
- throw new Error('SANDBOX_CONTROL_OWNER_REPLACED');
838
- }
839
- if (!owner && currentBroker && ownerLive(currentBroker, identityProbe)) {
840
- throw new Error('SANDBOX_CONTROL_OWNER_REPLACED');
841
- }
842
- if (owner && ownerLive(owner, identityProbe) && !currentBroker) {
843
- throw new Error('SANDBOX_CONTROL_OWNER_EVIDENCE_MISSING');
844
- }
845
- executions = mergeExecutions(executions, readExecutions(manifest));
846
- if (Date.now() >= deadlineAt)
847
- throw new Error('SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED');
848
- for (const execution of executions) {
849
- if (!terminateSandboxControlExecution(execution, {
850
- platform, timeoutMs: remaining(), deadlineAt, forceAt: Date.now(), identityProbe
851
- })) {
852
- throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${execution.requestId}`);
1243
+ const inspectContainer = options.inspectContainer
1244
+ ?? ((timeoutMs) => inspectSandboxControlContainer(manifest, { timeoutMs }));
1245
+ const timeoutMs = options.timeoutMs ?? options.timing?.quiesceDeadlineMs ?? DEFAULT_QUIESCE_TIMEOUT_MS;
1246
+ const identityProbe = options.identityProbe ?? getProcessIdentityState;
1247
+ const deadlineAt = Date.now() + timeoutMs;
1248
+ const forceAt = forceDeadline(deadlineAt, timeoutMs);
1249
+ const remaining = () => Math.max(0, deadlineAt - Date.now());
1250
+ const journal = startRemovalJournal(manifest, {
1251
+ target: options.removalTarget,
1252
+ identityProbe
1253
+ });
1254
+ if (journal.record.phase === 'carrier-finalizing') {
1255
+ if (fs.readFileSync(manifestPath, 'utf8') !== manifestRawBeforeLock
1256
+ || !isSandboxControlRootQuiescing(resolvedRoot)) {
1257
+ throw new Error('SANDBOX_CONTROL_MANIFEST_CHANGED');
1258
+ }
1259
+ fs.rmSync(resolvedRoot, { recursive: true, force: true });
1260
+ if (fs.existsSync(resolvedRoot))
1261
+ throw new Error('SANDBOX_CONTROL_TARGET_MISMATCH');
1262
+ journal.write('carrier-removed');
1263
+ if (!options.retainRemovalJournal)
1264
+ clearSandboxRemovalJournal(manifest);
1265
+ return;
853
1266
  }
854
- }
855
- if (owner && currentBroker && ownerLive(currentBroker, identityProbe) && !selfOwned) {
856
- if (remaining() <= 0)
857
- throw new Error('SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED');
858
- signalOwner(currentBroker, platform, true);
859
- if (remaining() <= 0 || !await waitForExit(currentBroker, remaining(), deadlineAt, identityProbe)) {
860
- throw new Error('SANDBOX_CONTROL_BROKER_STILL_RUNNING');
1267
+ if (sandboxRemovalPhaseIndex(journal.record.phase) >= sandboxRemovalPhaseIndex('carrier-removed')) {
1268
+ throw new Error('SANDBOX_CONTROL_TARGET_MISMATCH');
861
1269
  }
862
- }
863
- const finalExecutions = mergeExecutions(executions, readExecutions(manifest));
864
- if (finalExecutions.some((execution) => executionAlive(execution, platform, identityProbe))) {
865
- throw new Error('SANDBOX_CONTROL_EXECUTION_STILL_RUNNING');
866
- }
867
- const finalBrokerRaw = fs.existsSync(brokerPath) ? fs.readFileSync(brokerPath, 'utf8') : null;
868
- if (finalBrokerRaw !== null) {
869
- const finalBroker = readBrokerOwner(brokerPath);
870
- if (!finalBroker)
1270
+ if (journal.record.phase === 'prepared')
1271
+ journal.write('target-committed');
1272
+ const observation = await awaitWithDeadline(() => inspectContainer(remaining()), deadlineAt, 'SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED');
1273
+ if (observation.state === 'unknown')
1274
+ throw new Error(`SANDBOX_CONTROL_CONTAINER_UNKNOWN: ${observation.reason}`);
1275
+ journal.write(observation.state === 'absent' ? 'container-absent' : 'container-removal');
1276
+ if (options.requireAbsent && observation.state !== 'absent') {
1277
+ throw new Error('SANDBOX_CONTROL_CONTAINER_REAPPEARED');
1278
+ }
1279
+ markSandboxControlRootQuiescing(resolvedRoot);
1280
+ await waitForStartupTransition(resolvedRoot, Math.max(0, forceAt - Date.now()), identityProbe);
1281
+ const brokerPath = path.join(resolvedRoot, 'broker.json');
1282
+ const statusPath = path.join(resolvedRoot, 'public', 'status.json');
1283
+ const broker = readBrokerOwner(brokerPath);
1284
+ const status = readStatusOwner(statusPath);
1285
+ if (broker && (broker.token !== manifest.token || broker.generation !== manifest.generation)) {
871
1286
  throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
872
- const finalSelfOwner = Boolean(selfOwner && sameOwner(finalBroker, selfOwner)
873
- && finalBroker.token === manifest.token && finalBroker.generation === manifest.generation);
874
- if (ownerLive(finalBroker, identityProbe) && !finalSelfOwner) {
875
- throw new Error('SANDBOX_CONTROL_BROKER_STILL_RUNNING');
876
1287
  }
877
- if (fs.readFileSync(brokerPath, 'utf8') !== finalBrokerRaw) {
878
- throw new Error('SANDBOX_CONTROL_OWNER_TRANSITION');
1288
+ if (status && status.generation !== manifest.generation) {
1289
+ throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
879
1290
  }
880
- fs.unlinkSync(brokerPath);
881
- }
882
- const finalStatusRaw = fs.existsSync(statusPath) ? fs.readFileSync(statusPath, 'utf8') : null;
883
- if (finalStatusRaw !== null) {
884
- const finalStatus = readStatusOwner(statusPath);
885
- if (!finalStatus)
1291
+ if (broker && status && !sameOwner(broker, status.broker)
1292
+ && (ownerLive(broker, identityProbe) || ownerLive(status.broker, identityProbe))) {
886
1293
  throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
887
- if (ownerLive(finalStatus.broker, identityProbe)
888
- && !(selfOwner && sameOwner(finalStatus.broker, selfOwner))) {
889
- throw new Error('SANDBOX_CONTROL_BROKER_STILL_RUNNING');
890
1294
  }
891
- if (fs.readFileSync(statusPath, 'utf8') !== finalStatusRaw) {
1295
+ const platform = options.platform ?? process.platform;
1296
+ const owner = broker && ownerLive(broker, identityProbe) ? broker
1297
+ : status && ownerLive(status.broker, identityProbe) ? status.broker : null;
1298
+ const selfOwner = options.selfOwner;
1299
+ const selfOwned = Boolean(selfOwner && broker && sameOwner(broker, selfOwner));
1300
+ if (selfOwner && (!broker || !selfOwned || broker.token !== manifest.token || broker.generation !== manifest.generation)) {
892
1301
  throw new Error('SANDBOX_CONTROL_OWNER_TRANSITION');
893
1302
  }
894
- fs.unlinkSync(statusPath);
1303
+ let executions = readExecutions(manifest);
1304
+ if (owner && !selfOwned)
1305
+ signalOwner(owner, platform, false);
1306
+ for (const execution of executions) {
1307
+ terminateSandboxControlExecution(execution, { platform, timeoutMs: 0, allowForce: false, identityProbe });
1308
+ }
1309
+ const softDeadlineAt = Math.min(forceAt, Date.now() + Math.floor(Math.max(0, forceAt - Date.now()) / 2));
1310
+ while (Date.now() < softDeadlineAt) {
1311
+ if ((!owner || selfOwned || !ownerLive(owner, identityProbe))
1312
+ && !executions.some((execution) => executionAlive(execution, platform, identityProbe)))
1313
+ break;
1314
+ await new Promise((resolve) => setTimeout(resolve, 25));
1315
+ }
1316
+ if (observation.state === 'found') {
1317
+ if (Date.now() >= deadlineAt) {
1318
+ recordSandboxRemovalPending(resolvedRoot, manifest, 'container-removal', journal);
1319
+ throw new Error('SANDBOX_CONTROL_REMOVE_PENDING');
1320
+ }
1321
+ let afterRemoval;
1322
+ try {
1323
+ await awaitWithDeadline(() => options.removeContainer(remaining()), deadlineAt, 'SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED');
1324
+ afterRemoval = await awaitWithDeadline(() => inspectContainer(remaining()), deadlineAt, 'SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED');
1325
+ }
1326
+ catch (error) {
1327
+ if (error instanceof Error && error.message === 'SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED') {
1328
+ recordSandboxRemovalPending(resolvedRoot, manifest, 'container-removal', journal);
1329
+ throw new Error('SANDBOX_CONTROL_REMOVE_PENDING');
1330
+ }
1331
+ throw error;
1332
+ }
1333
+ if (afterRemoval.state !== 'absent') {
1334
+ throw new Error(afterRemoval.state === 'unknown'
1335
+ ? `SANDBOX_CONTROL_CONTAINER_UNKNOWN: ${afterRemoval.reason}`
1336
+ : 'SANDBOX_CONTROL_CONTAINER_STILL_EXISTS');
1337
+ }
1338
+ journal.write('container-absent');
1339
+ }
1340
+ const currentManifestRaw = fs.readFileSync(manifestPath, 'utf8');
1341
+ const currentManifest = readSandboxControlManifest(manifestPath);
1342
+ if (currentManifestRaw !== manifestRawBeforeLock
1343
+ || currentManifest.engine !== manifest.engine
1344
+ || currentManifest.containerIdentity.id !== manifest.containerIdentity.id
1345
+ || currentManifest.authorityEvidence.lockDomain !== manifest.authorityEvidence.lockDomain
1346
+ || currentManifest.token !== manifest.token || currentManifest.generation !== manifest.generation
1347
+ || !isSandboxControlRootQuiescing(resolvedRoot)) {
1348
+ throw new Error('SANDBOX_CONTROL_MANIFEST_CHANGED');
1349
+ }
1350
+ const currentBroker = readBrokerOwner(brokerPath);
1351
+ if (selfOwned && (!currentBroker || !selfOwner || !sameOwner(selfOwner, currentBroker)
1352
+ || currentBroker.token !== manifest.token || currentBroker.generation !== manifest.generation)) {
1353
+ throw new Error('SANDBOX_CONTROL_OWNER_REPLACED');
1354
+ }
1355
+ if (owner && currentBroker && !sameOwner(owner, currentBroker)) {
1356
+ throw new Error('SANDBOX_CONTROL_OWNER_REPLACED');
1357
+ }
1358
+ if (!owner && currentBroker && ownerLive(currentBroker, identityProbe)) {
1359
+ throw new Error('SANDBOX_CONTROL_OWNER_REPLACED');
1360
+ }
1361
+ if (owner && ownerLive(owner, identityProbe) && !currentBroker) {
1362
+ throw new Error('SANDBOX_CONTROL_OWNER_EVIDENCE_MISSING');
1363
+ }
1364
+ executions = mergeExecutions(executions, readExecutions(manifest));
1365
+ if (Date.now() >= deadlineAt)
1366
+ throw new Error('SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED');
1367
+ for (const execution of executions) {
1368
+ if (!terminateSandboxControlExecution(execution, {
1369
+ platform, timeoutMs: remaining(), deadlineAt, forceAt: Date.now(), identityProbe
1370
+ })) {
1371
+ throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${execution.requestId}`);
1372
+ }
1373
+ }
1374
+ if (owner && currentBroker && ownerLive(currentBroker, identityProbe) && !selfOwned) {
1375
+ if (remaining() <= 0)
1376
+ throw new Error('SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED');
1377
+ signalOwner(currentBroker, platform, true);
1378
+ if (remaining() <= 0 || !await waitForExit(currentBroker, remaining(), deadlineAt, identityProbe)) {
1379
+ throw new Error('SANDBOX_CONTROL_BROKER_STILL_RUNNING');
1380
+ }
1381
+ }
1382
+ const finalExecutions = mergeExecutions(executions, readExecutions(manifest));
1383
+ if (finalExecutions.some((execution) => executionAlive(execution, platform, identityProbe))) {
1384
+ throw new Error('SANDBOX_CONTROL_EXECUTION_STILL_RUNNING');
1385
+ }
1386
+ const finalBrokerRaw = fs.existsSync(brokerPath) ? fs.readFileSync(brokerPath, 'utf8') : null;
1387
+ if (finalBrokerRaw !== null) {
1388
+ const finalBroker = readBrokerOwner(brokerPath);
1389
+ if (!finalBroker)
1390
+ throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
1391
+ const finalSelfOwner = Boolean(selfOwner && sameOwner(finalBroker, selfOwner)
1392
+ && finalBroker.token === manifest.token && finalBroker.generation === manifest.generation);
1393
+ if (ownerLive(finalBroker, identityProbe) && !finalSelfOwner) {
1394
+ throw new Error('SANDBOX_CONTROL_BROKER_STILL_RUNNING');
1395
+ }
1396
+ if (fs.readFileSync(brokerPath, 'utf8') !== finalBrokerRaw) {
1397
+ throw new Error('SANDBOX_CONTROL_OWNER_TRANSITION');
1398
+ }
1399
+ fs.unlinkSync(brokerPath);
1400
+ }
1401
+ const finalStatusRaw = fs.existsSync(statusPath) ? fs.readFileSync(statusPath, 'utf8') : null;
1402
+ if (finalStatusRaw !== null) {
1403
+ const finalStatus = readStatusOwner(statusPath);
1404
+ if (!finalStatus)
1405
+ throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
1406
+ if (ownerLive(finalStatus.broker, identityProbe)
1407
+ && !(selfOwner && sameOwner(finalStatus.broker, selfOwner))) {
1408
+ throw new Error('SANDBOX_CONTROL_BROKER_STILL_RUNNING');
1409
+ }
1410
+ if (fs.readFileSync(statusPath, 'utf8') !== finalStatusRaw) {
1411
+ throw new Error('SANDBOX_CONTROL_OWNER_TRANSITION');
1412
+ }
1413
+ fs.unlinkSync(statusPath);
1414
+ }
1415
+ if (fs.existsSync(brokerPath) || fs.existsSync(statusPath)) {
1416
+ throw new Error('SANDBOX_CONTROL_OWNER_EVIDENCE_REMAINS');
1417
+ }
1418
+ journal.write('carrier-finalizing');
1419
+ fs.rmSync(resolvedRoot, { recursive: true, force: true });
1420
+ journal.write('carrier-removed');
1421
+ if (!options.retainRemovalJournal)
1422
+ clearSandboxRemovalJournal(manifest);
895
1423
  }
896
- if (fs.existsSync(brokerPath) || fs.existsSync(statusPath)) {
897
- throw new Error('SANDBOX_CONTROL_OWNER_EVIDENCE_REMAINS');
1424
+ finally {
1425
+ if (!options.resourceLock)
1426
+ resourceLock.release();
898
1427
  }
899
- fs.rmSync(resolvedRoot, { recursive: true, force: true });
900
1428
  }
901
1429
  export async function garbageCollectSandboxControlRoot(root, options = {}) {
902
1430
  await removeSandboxControlRoot(path.resolve(root), {