toolroll 0.9.93 → 0.9.95

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 (436) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/dist/activity-line.d.ts +7 -4
  3. package/dist/activity-line.js +9 -7
  4. package/dist/agent-fence.d.ts +10 -0
  5. package/dist/agent-fence.js +6 -3
  6. package/dist/approval-authority.d.ts +7 -1
  7. package/dist/approval-authority.js +11 -2
  8. package/dist/approval-password.d.ts +31 -0
  9. package/dist/approval-password.js +26 -0
  10. package/dist/assignment-status.d.ts +1 -1
  11. package/dist/assignment-ui.js +3 -1
  12. package/dist/assignment.js +5 -0
  13. package/dist/attest.d.ts +3 -0
  14. package/dist/attest.js +10 -0
  15. package/dist/boot-identity.d.ts +2 -1
  16. package/dist/boot-identity.js +6 -4
  17. package/dist/bounded-spawn.d.ts +57 -0
  18. package/dist/bounded-spawn.js +220 -0
  19. package/dist/browser/terminal.js +42 -42
  20. package/dist/browser/workspace.css +1 -1
  21. package/dist/browser/workspace.js +32 -32
  22. package/dist/browser-crew.js +2 -0
  23. package/dist/browser-shell.d.ts +1 -1
  24. package/dist/browser-shell.js +2 -2
  25. package/dist/browser-workspace.d.ts +50 -2
  26. package/dist/browser-workspace.js +3 -3
  27. package/dist/build-review.js +2 -0
  28. package/dist/builder-restart.d.ts +21 -0
  29. package/dist/builder-restart.js +50 -0
  30. package/dist/builder.d.ts +11 -2
  31. package/dist/builder.js +234 -101
  32. package/dist/card-catalog.d.ts +209 -0
  33. package/dist/card-catalog.js +133 -0
  34. package/dist/card-cli.d.ts +43 -0
  35. package/dist/card-cli.js +44 -0
  36. package/dist/card-render.d.ts +26 -0
  37. package/dist/card-render.js +223 -0
  38. package/dist/card-sources.d.ts +68 -0
  39. package/dist/card-sources.js +234 -0
  40. package/dist/chat-acceptance.js +1 -1
  41. package/dist/chat-actions.d.ts +106 -9
  42. package/dist/chat-actions.js +473 -46
  43. package/dist/chat-approval.d.ts +13 -0
  44. package/dist/chat-approval.js +39 -9
  45. package/dist/chat-card-part.d.ts +11 -0
  46. package/dist/chat-card-part.js +18 -0
  47. package/dist/chat-card.d.ts +46 -0
  48. package/dist/chat-card.js +99 -0
  49. package/dist/chat-channel.d.ts +2 -0
  50. package/dist/chat-channel.js +37 -14
  51. package/dist/chat-controls.d.ts +4 -0
  52. package/dist/chat-controls.js +4 -0
  53. package/dist/chat-decide.d.ts +10 -16
  54. package/dist/chat-decide.js +79 -62
  55. package/dist/chat-delivery.js +13 -4
  56. package/dist/chat-flow.d.ts +0 -27
  57. package/dist/chat-flow.js +31 -2
  58. package/dist/chat-quiet.js +4 -4
  59. package/dist/chat-task-actions.d.ts +3 -2
  60. package/dist/chat-task-actions.js +19 -5
  61. package/dist/chat-terms.d.ts +14 -2
  62. package/dist/chat-terms.js +35 -0
  63. package/dist/chat-voice.d.ts +1 -1
  64. package/dist/chat-voice.js +2 -2
  65. package/dist/check-facts.d.ts +3 -1
  66. package/dist/check-facts.js +57 -11
  67. package/dist/check-failure.d.ts +24 -3
  68. package/dist/check-failure.js +89 -13
  69. package/dist/check-levels-ui.d.ts +4 -0
  70. package/dist/check-levels-ui.js +10 -1
  71. package/dist/check-load.d.ts +85 -0
  72. package/dist/check-load.js +147 -0
  73. package/dist/check-only-standing.d.ts +36 -0
  74. package/dist/check-only-standing.js +39 -0
  75. package/dist/check-only.d.ts +15 -0
  76. package/dist/check-only.js +99 -0
  77. package/dist/check-repair.d.ts +47 -0
  78. package/dist/check-repair.js +240 -0
  79. package/dist/check-report.d.ts +2 -0
  80. package/dist/check-report.js +1 -1
  81. package/dist/check-stamp.d.ts +46 -0
  82. package/dist/check-stamp.js +93 -0
  83. package/dist/checkout-cleanup.d.ts +36 -5
  84. package/dist/checkout-cleanup.js +166 -14
  85. package/dist/claim.d.ts +6 -0
  86. package/dist/claim.js +13 -2
  87. package/dist/cli-help.d.ts +4 -1
  88. package/dist/cli-help.js +49 -30
  89. package/dist/cli.js +15 -1
  90. package/dist/code-extract.d.ts +44 -0
  91. package/dist/code-extract.js +88 -0
  92. package/dist/code-map-acts.d.ts +24 -0
  93. package/dist/code-map-acts.js +53 -0
  94. package/dist/code-map-admission.d.ts +52 -0
  95. package/dist/code-map-admission.js +295 -0
  96. package/dist/code-map-build.d.ts +63 -0
  97. package/dist/code-map-build.js +415 -0
  98. package/dist/code-map-cli.d.ts +153 -0
  99. package/dist/code-map-cli.js +103 -0
  100. package/dist/code-map-main.d.ts +1 -0
  101. package/dist/code-map-main.js +48 -0
  102. package/dist/code-map-read.d.ts +106 -0
  103. package/dist/code-map-read.js +171 -0
  104. package/dist/code-map-report.d.ts +37 -0
  105. package/dist/code-map-report.js +95 -0
  106. package/dist/code-map-shape.d.ts +172 -0
  107. package/dist/code-map-shape.js +327 -0
  108. package/dist/code-map-store.d.ts +136 -0
  109. package/dist/code-map-store.js +233 -0
  110. package/dist/coding-provider.d.ts +3 -1
  111. package/dist/coding-provider.js +12 -7
  112. package/dist/coding-update.js +3 -2
  113. package/dist/coding-workspace.d.ts +1 -1
  114. package/dist/coding-workspace.js +8 -8
  115. package/dist/console-chrome.js +2 -2
  116. package/dist/containment.d.ts +5 -0
  117. package/dist/containment.js +14 -1
  118. package/dist/contracts/chat-actions.d.ts +92 -0
  119. package/dist/contracts/chat-actions.js +20 -0
  120. package/dist/contracts/chat-card.d.ts +91 -0
  121. package/dist/contracts/chat-card.js +54 -0
  122. package/dist/contracts/chat-content.d.ts +191 -0
  123. package/dist/contracts/chat-content.js +4 -0
  124. package/dist/contracts/cli.d.ts +52 -0
  125. package/dist/contracts/cli.js +4 -0
  126. package/dist/contracts/console-api.d.ts +12 -3
  127. package/dist/contracts/console-api.js +12 -3
  128. package/dist/contracts/flow-propose.d.ts +8 -1
  129. package/dist/contracts/flow-propose.js +3 -1
  130. package/dist/contracts/flow-send.d.ts +126 -0
  131. package/dist/contracts/flow-send.js +3 -0
  132. package/dist/contracts/gateway-tools.d.ts +17 -0
  133. package/dist/contracts/gateway-tools.js +3 -1
  134. package/dist/contracts/lead-tools.d.ts +234 -0
  135. package/dist/contracts/lead-tools.js +25 -1
  136. package/dist/contracts/project-knowledge.d.ts +38 -5
  137. package/dist/contracts/project-knowledge.js +11 -5
  138. package/dist/contracts/project-memory.d.ts +3 -1
  139. package/dist/contracts/project-memory.js +3 -2
  140. package/dist/contracts/storage.d.ts +14 -0
  141. package/dist/contracts/storage.js +3 -1
  142. package/dist/contracts/store-json.d.ts +18 -0
  143. package/dist/contracts/youiyoux-request.d.ts +1 -1
  144. package/dist/contracts/youiyoux-request.js +1 -1
  145. package/dist/desktop-access.js +6 -2
  146. package/dist/discord-chat.js +14 -0
  147. package/dist/dispatch.d.ts +1 -1
  148. package/dist/dispatch.js +25 -16
  149. package/dist/dispose.d.ts +19 -0
  150. package/dist/dispose.js +55 -9
  151. package/dist/easy-start.js +2 -3
  152. package/dist/eval-oracle.js +4 -1
  153. package/dist/evals.js +2 -1
  154. package/dist/evidence-pair.d.ts +53 -0
  155. package/dist/evidence-pair.js +128 -0
  156. package/dist/exhaustion.d.ts +17 -4
  157. package/dist/exhaustion.js +44 -4
  158. package/dist/export.js +1 -1
  159. package/dist/failing-first.d.ts +31 -0
  160. package/dist/failing-first.js +44 -0
  161. package/dist/fallback-schema.d.ts +8 -0
  162. package/dist/fallback-schema.js +40 -0
  163. package/dist/fallback.d.ts +138 -0
  164. package/dist/fallback.js +605 -0
  165. package/dist/fast-start.d.ts +136 -0
  166. package/dist/fast-start.js +431 -0
  167. package/dist/fast-terms.d.ts +9 -0
  168. package/dist/fast-terms.js +23 -0
  169. package/dist/fenced-check-coverage.d.ts +62 -0
  170. package/dist/fenced-check-coverage.js +54 -0
  171. package/dist/findings.d.ts +224 -0
  172. package/dist/findings.js +449 -0
  173. package/dist/first-run.js +1 -1
  174. package/dist/flaky-ci.d.ts +31 -0
  175. package/dist/flaky-ci.js +149 -0
  176. package/dist/flaky-cli.d.ts +60 -0
  177. package/dist/flaky-cli.js +60 -0
  178. package/dist/flaky.d.ts +75 -9
  179. package/dist/flaky.js +255 -0
  180. package/dist/fleet-schema.d.ts +17 -0
  181. package/dist/fleet-schema.js +135 -0
  182. package/dist/flow-browser.js +2 -2
  183. package/dist/flow-card-task.js +3 -0
  184. package/dist/flow-code.d.ts +2 -2
  185. package/dist/flow-code.js +9 -5
  186. package/dist/flow-result-card.d.ts +22 -0
  187. package/dist/flow-result-card.js +55 -0
  188. package/dist/flow-send.js +12 -9
  189. package/dist/flow-steps.js +1 -1
  190. package/dist/flow-triggers.d.ts +1 -1
  191. package/dist/flow-triggers.js +2 -2
  192. package/dist/flow-unattended.d.ts +26 -0
  193. package/dist/flow-unattended.js +12 -3
  194. package/dist/flows-ui.js +10 -2
  195. package/dist/follow-through-brief.d.ts +12 -0
  196. package/dist/follow-through-brief.js +31 -0
  197. package/dist/follow-through-cli.d.ts +23 -0
  198. package/dist/follow-through-cli.js +153 -0
  199. package/dist/git-incident.d.ts +16 -0
  200. package/dist/git-incident.js +69 -6
  201. package/dist/github-import.d.ts +62 -0
  202. package/dist/github-import.js +146 -0
  203. package/dist/guides.js +15 -10
  204. package/dist/handover-note.d.ts +33 -0
  205. package/dist/handover-note.js +106 -0
  206. package/dist/invoke.d.ts +25 -2
  207. package/dist/invoke.js +157 -58
  208. package/dist/job-object-helper.ps1 +10 -3
  209. package/dist/keymap.d.ts +41 -0
  210. package/dist/keymap.js +51 -0
  211. package/dist/knowledge-cli.js +1 -1
  212. package/dist/knowledge-ui.d.ts +21 -4
  213. package/dist/knowledge-ui.js +75 -7
  214. package/dist/laptop-background.d.ts +16 -0
  215. package/dist/laptop-background.js +108 -2
  216. package/dist/lead-cli.js +26 -24
  217. package/dist/lead-context.js +1 -1
  218. package/dist/lead-contract.d.ts +1 -1
  219. package/dist/lead-contract.js +3 -2
  220. package/dist/lead-doors.d.ts +4 -0
  221. package/dist/lead-doors.js +2 -2
  222. package/dist/lead-progress.js +5 -0
  223. package/dist/lead-status.d.ts +2 -0
  224. package/dist/lead-status.js +6 -2
  225. package/dist/lead-tools.js +162 -34
  226. package/dist/lead.js +2 -2
  227. package/dist/lesson-notes.d.ts +81 -0
  228. package/dist/lesson-notes.js +336 -0
  229. package/dist/limits-ui.js +1 -1
  230. package/dist/local-owner.d.ts +1 -1
  231. package/dist/local-owner.js +16 -0
  232. package/dist/machine-check.d.ts +16 -0
  233. package/dist/machine-check.js +26 -0
  234. package/dist/machine-identity.d.ts +75 -0
  235. package/dist/machine-identity.js +170 -0
  236. package/dist/mcp-core.js +11 -1
  237. package/dist/memory-cli.d.ts +300 -1
  238. package/dist/memory-cli.js +62 -9
  239. package/dist/memory-pass.d.ts +16 -0
  240. package/dist/memory-pass.js +67 -3
  241. package/dist/memory-tidy.d.ts +181 -0
  242. package/dist/memory-tidy.js +523 -0
  243. package/dist/memory-use.d.ts +35 -0
  244. package/dist/memory-use.js +109 -0
  245. package/dist/merge-checklist.d.ts +55 -0
  246. package/dist/merge-checklist.js +182 -0
  247. package/dist/modes.d.ts +6 -6
  248. package/dist/modes.js +3 -1
  249. package/dist/needs-you.d.ts +31 -9
  250. package/dist/needs-you.js +110 -41
  251. package/dist/nightly-github.d.ts +2 -0
  252. package/dist/nightly-github.js +4 -4
  253. package/dist/operate-remote-arguments.js +7 -1
  254. package/dist/operate-remote.js +3 -1
  255. package/dist/operate.d.ts +6 -1
  256. package/dist/operate.js +565 -64
  257. package/dist/phase-routing.js +2 -2
  258. package/dist/plain-schema.d.ts +23 -0
  259. package/dist/plain-schema.js +94 -0
  260. package/dist/plain-words.d.ts +20 -0
  261. package/dist/plain-words.js +66 -0
  262. package/dist/plan-login.d.ts +14 -0
  263. package/dist/plan-login.js +58 -0
  264. package/dist/planner.d.ts +1 -1
  265. package/dist/planner.js +4 -2
  266. package/dist/process-liveness.d.ts +2 -0
  267. package/dist/process-liveness.js +61 -17
  268. package/dist/project-delete-ui.d.ts +19 -2
  269. package/dist/project-delete-ui.js +29 -1
  270. package/dist/project-delete.js +14 -1
  271. package/dist/project-knowledge.d.ts +18 -11
  272. package/dist/project-knowledge.js +39 -9
  273. package/dist/project-learning.d.ts +2 -1
  274. package/dist/project-learning.js +15 -45
  275. package/dist/project-memory.d.ts +5 -0
  276. package/dist/project-memory.js +22 -2
  277. package/dist/project-tools.d.ts +4 -4
  278. package/dist/project-tools.js +23 -7
  279. package/dist/proof.js +30 -5
  280. package/dist/protected-storage-look.d.ts +1 -0
  281. package/dist/protected-storage-look.js +13 -0
  282. package/dist/protected-storage.d.ts +70 -39
  283. package/dist/protected-storage.js +196 -147
  284. package/dist/provider-auth.js +1 -1
  285. package/dist/provider-capture.d.ts +34 -0
  286. package/dist/provider-capture.js +53 -0
  287. package/dist/publish.js +9 -1
  288. package/dist/pull-request-flow.js +5 -0
  289. package/dist/push.d.ts +18 -2
  290. package/dist/push.js +40 -16
  291. package/dist/release-adapters.d.ts +9 -0
  292. package/dist/release-adapters.js +76 -0
  293. package/dist/release-auto.js +13 -13
  294. package/dist/release-card.d.ts +1 -2
  295. package/dist/release-card.js +11 -0
  296. package/dist/release-carry.d.ts +7 -2
  297. package/dist/release-carry.js +49 -10
  298. package/dist/release-cli.d.ts +4 -0
  299. package/dist/release-cli.js +29 -22
  300. package/dist/release-coverage.js +1 -1
  301. package/dist/release-next.d.ts +48 -0
  302. package/dist/release-next.js +161 -0
  303. package/dist/release.d.ts +20 -1
  304. package/dist/release.js +85 -9
  305. package/dist/remote-destination.d.ts +36 -0
  306. package/dist/remote-destination.js +217 -0
  307. package/dist/remote-main.d.ts +48 -0
  308. package/dist/remote-main.js +84 -5
  309. package/dist/repository-context.d.ts +3 -2
  310. package/dist/repository-context.js +112 -70
  311. package/dist/restart-certification.d.ts +1 -0
  312. package/dist/restart-certification.js +8 -6
  313. package/dist/result-acts.d.ts +27 -19
  314. package/dist/result-acts.js +36 -25
  315. package/dist/result-follow-ups.d.ts +2 -2
  316. package/dist/result-follow-ups.js +18 -4
  317. package/dist/result-review.d.ts +1 -1
  318. package/dist/route-change-check.d.ts +4 -0
  319. package/dist/route-change-check.js +13 -0
  320. package/dist/route-change.d.ts +22 -0
  321. package/dist/route-change.js +89 -0
  322. package/dist/route-hooks.js +3 -2
  323. package/dist/route-results.d.ts +6 -1
  324. package/dist/route-results.js +14 -5
  325. package/dist/run-closeout.d.ts +1 -1
  326. package/dist/run-closeout.js +7 -5
  327. package/dist/runner-process.js +3 -2
  328. package/dist/runner.js +2 -1
  329. package/dist/scope.d.ts +28 -4
  330. package/dist/scope.js +92 -28
  331. package/dist/scout.d.ts +1 -1
  332. package/dist/scout.js +2 -1
  333. package/dist/serve.d.ts +1 -1
  334. package/dist/serve.js +26 -18
  335. package/dist/server/chat.js +16 -14
  336. package/dist/server/chrome.d.ts +1 -1
  337. package/dist/server/chrome.js +26 -35
  338. package/dist/server/console.css +73 -0
  339. package/dist/server/guards.js +3 -0
  340. package/dist/server/home.d.ts +6 -0
  341. package/dist/server/home.js +21 -13
  342. package/dist/server/http.js +1 -1
  343. package/dist/server/kit.js +1 -1
  344. package/dist/server/merge-ready-view.d.ts +10 -0
  345. package/dist/server/merge-ready-view.js +30 -0
  346. package/dist/server/pages-assets.d.ts +1 -0
  347. package/dist/server/pages-assets.js +8 -5
  348. package/dist/server/pages.js +103 -82
  349. package/dist/server/render-chat.js +16 -3
  350. package/dist/server/render-code-map.d.ts +30 -0
  351. package/dist/server/render-code-map.js +137 -0
  352. package/dist/server/render-flaky.d.ts +20 -0
  353. package/dist/server/render-flaky.js +59 -0
  354. package/dist/server/render-follow-through.d.ts +11 -0
  355. package/dist/server/render-follow-through.js +68 -0
  356. package/dist/server/render-history.js +1 -1
  357. package/dist/server/render-new-task.d.ts +26 -1
  358. package/dist/server/render-new-task.js +30 -5
  359. package/dist/server/render-pages.d.ts +1 -175
  360. package/dist/server/render-pages.js +38 -313
  361. package/dist/server/render-projects.d.ts +2 -1
  362. package/dist/server/render-projects.js +9 -7
  363. package/dist/server/render-release.d.ts +2 -0
  364. package/dist/server/render-release.js +3 -3
  365. package/dist/server/render-results.d.ts +2 -0
  366. package/dist/server/render-results.js +68 -68
  367. package/dist/server/render-tasks.d.ts +16 -2
  368. package/dist/server/render-tasks.js +177 -141
  369. package/dist/server/retired-routes.d.ts +17 -0
  370. package/dist/server/retired-routes.js +53 -0
  371. package/dist/server/route-table.d.ts +3 -0
  372. package/dist/server/route-table.js +48 -12
  373. package/dist/server/settings.js +166 -34
  374. package/dist/server/task-views.js +66 -26
  375. package/dist/server/tasks.d.ts +2 -6
  376. package/dist/server/tasks.js +250 -321
  377. package/dist/server/welcome.js +1 -1
  378. package/dist/settings-groups-ui.d.ts +1 -0
  379. package/dist/settings-groups-ui.js +4 -2
  380. package/dist/settings-kit.js +3 -2
  381. package/dist/settings-proofs.js +3 -1
  382. package/dist/slack-chat.js +14 -1
  383. package/dist/steer-mailbox.d.ts +8 -1
  384. package/dist/steer-mailbox.js +164 -6
  385. package/dist/stop-proof.js +6 -4
  386. package/dist/storage-sweep-process.js +1 -1
  387. package/dist/storage-sweep.d.ts +5 -3
  388. package/dist/storage-sweep.js +30 -9
  389. package/dist/storage-ui.js +28 -17
  390. package/dist/storage.d.ts +27 -2
  391. package/dist/storage.js +39 -2
  392. package/dist/store.d.ts +89 -7
  393. package/dist/store.js +386 -114
  394. package/dist/surface.js +67 -6
  395. package/dist/sweep-quarantine.d.ts +1 -0
  396. package/dist/sweep-quarantine.js +6 -4
  397. package/dist/tamper-scan.d.ts +4 -0
  398. package/dist/tamper-scan.js +90 -0
  399. package/dist/task-aliases.d.ts +5 -0
  400. package/dist/task-aliases.js +9 -0
  401. package/dist/task-control.d.ts +2 -1
  402. package/dist/task-control.js +4 -1
  403. package/dist/task-outcome-cli.d.ts +2 -0
  404. package/dist/task-outcome-cli.js +17 -17
  405. package/dist/task-sizing.d.ts +5 -0
  406. package/dist/task-sizing.js +2 -0
  407. package/dist/task-source.d.ts +7 -0
  408. package/dist/task-source.js +14 -0
  409. package/dist/task-status.d.ts +9 -0
  410. package/dist/task-status.js +45 -49
  411. package/dist/teardown-git.d.ts +18 -0
  412. package/dist/teardown-git.js +105 -21
  413. package/dist/telegram-lead.js +23 -6
  414. package/dist/telegram-progress.js +5 -3
  415. package/dist/telegram-status.js +6 -4
  416. package/dist/telegram.js +12 -1
  417. package/dist/test-infra.d.ts +8 -0
  418. package/dist/test-infra.js +18 -0
  419. package/dist/tidy.js +1 -1
  420. package/dist/toolroll-update.js +19 -5
  421. package/dist/tui-contract.d.ts +24 -0
  422. package/dist/tui-source.js +67 -8
  423. package/dist/tui.js +51 -51
  424. package/dist/vanished-retry.d.ts +30 -0
  425. package/dist/vanished-retry.js +174 -0
  426. package/dist/work-index.d.ts +2 -0
  427. package/dist/work-index.js +95 -66
  428. package/dist/work-summary.d.ts +1 -1
  429. package/dist/work-summary.js +7 -2
  430. package/dist/workspace-ui.d.ts +7 -3
  431. package/dist/workspace-ui.js +38 -23
  432. package/dist/worktree.d.ts +12 -3
  433. package/dist/worktree.js +91 -14
  434. package/package.json +2 -1
  435. package/dist/server/render-next.d.ts +0 -63
  436. package/dist/server/render-next.js +0 -109
@@ -0,0 +1,233 @@
1
+ /**
2
+ * The code map's records (docs/plans/code-map.md §3): immutable versions, the pointer to the current one, and the
3
+ * per-project refresh record whose lease fences every later write. Delivery and measurement rows sit here too. The
4
+ * rows are Toolroll's own; nothing goes in a repository.
5
+ */
6
+ import { createHash } from "node:crypto";
7
+ import { gunzipSync, gzipSync } from "node:zlib";
8
+ import { trustedRemoteIdentity } from "./store.js";
9
+ import { LIMITS } from "./code-map-shape.js";
10
+ export const V152_SCHEMA = `
11
+ CREATE TABLE IF NOT EXISTS code_map_version (
12
+ id INTEGER PRIMARY KEY, repo TEXT NOT NULL, remote_digest TEXT NOT NULL, remote_record INTEGER NOT NULL, commit_sha TEXT NOT NULL,
13
+ extractor TEXT NOT NULL, built_at TEXT NOT NULL, manifest BLOB NOT NULL, manifest_sha TEXT NOT NULL, graph BLOB NOT NULL, graph_sha TEXT NOT NULL, report TEXT NOT NULL
14
+ );
15
+ CREATE INDEX IF NOT EXISTS code_map_version_repo ON code_map_version (repo, id);
16
+ CREATE TABLE IF NOT EXISTS code_map_current (repo TEXT PRIMARY KEY, version INTEGER NOT NULL REFERENCES code_map_version(id));
17
+ CREATE TABLE IF NOT EXISTS code_map_refresh (
18
+ repo TEXT PRIMARY KEY, lease_token INTEGER NOT NULL DEFAULT 0, lease_owner TEXT, lease_until TEXT,
19
+ request_id INTEGER NOT NULL DEFAULT 0, requested_at TEXT, requested_by TEXT,
20
+ failures INTEGER NOT NULL DEFAULT 0, retry_after TEXT, last_error TEXT, last_checked_at TEXT,
21
+ seen_digest TEXT, seen_record INTEGER, seen_commit TEXT, seen_extractor TEXT, seen_state TEXT,
22
+ spawn_pid INTEGER, spawn_pgid INTEGER, spawn_started TEXT, spawn_folder TEXT, enforced TEXT, proxy TEXT
23
+ );
24
+ CREATE TABLE IF NOT EXISTS code_map_family (
25
+ root_task TEXT PRIMARY KEY, repo TEXT NOT NULL, arm TEXT NOT NULL CHECK (arm IN ('map','holdout')), enrolled_at TEXT NOT NULL, observe_until TEXT NOT NULL
26
+ );
27
+ CREATE INDEX IF NOT EXISTS code_map_family_repo ON code_map_family (repo);
28
+ CREATE TABLE IF NOT EXISTS code_map_snapshot (
29
+ run INTEGER PRIMARY KEY, state TEXT NOT NULL CHECK (state IN ('map','off','none','remote-changed','holdout','unavailable')),
30
+ version INTEGER, excerpt TEXT NOT NULL, excerpt_sha TEXT NOT NULL, file_selected INTEGER NOT NULL CHECK (file_selected IN (0,1))
31
+ );
32
+ CREATE INDEX IF NOT EXISTS code_map_snapshot_version ON code_map_snapshot (version);
33
+ CREATE TABLE IF NOT EXISTS code_map_delivery (
34
+ id INTEGER PRIMARY KEY, run INTEGER NOT NULL, attempt INTEGER NOT NULL, at TEXT NOT NULL,
35
+ outcome TEXT NOT NULL CHECK (outcome IN ('delivered','not-delivered')),
36
+ reason TEXT CHECK (reason IS NULL OR reason IN ('no-confinement','write-failed','sha-mismatch','version-pruned','not-selected')), path_sha TEXT,
37
+ UNIQUE (run, attempt)
38
+ );
39
+ CREATE TABLE IF NOT EXISTS code_map_meta (id INTEGER PRIMARY KEY CHECK (id = 1), run_watermark INTEGER NOT NULL);
40
+ CREATE TRIGGER IF NOT EXISTS code_map_version_no_update BEFORE UPDATE ON code_map_version BEGIN SELECT RAISE(ABORT,'a code map version is immutable'); END;
41
+ CREATE TRIGGER IF NOT EXISTS code_map_version_guarded_delete BEFORE DELETE ON code_map_version
42
+ WHEN EXISTS (SELECT 1 FROM code_map_current WHERE version = OLD.id)
43
+ OR EXISTS (SELECT 1 FROM code_map_snapshot s JOIN run r ON r.id = s.run WHERE s.version = OLD.id AND r.outcome IS NULL)
44
+ BEGIN SELECT RAISE(ABORT,'a code map version in use stays'); END;
45
+ CREATE TRIGGER IF NOT EXISTS code_map_family_no_update BEFORE UPDATE ON code_map_family BEGIN SELECT RAISE(ABORT,'a family arm is frozen'); END;
46
+ CREATE TRIGGER IF NOT EXISTS code_map_family_no_delete BEFORE DELETE ON code_map_family BEGIN SELECT RAISE(ABORT,'a family arm is frozen'); END;
47
+ CREATE TRIGGER IF NOT EXISTS code_map_snapshot_no_update BEFORE UPDATE ON code_map_snapshot BEGIN SELECT RAISE(ABORT,'a code map snapshot is immutable'); END;
48
+ CREATE TRIGGER IF NOT EXISTS code_map_snapshot_no_delete BEFORE DELETE ON code_map_snapshot BEGIN SELECT RAISE(ABORT,'a code map snapshot is immutable'); END;
49
+ CREATE TRIGGER IF NOT EXISTS code_map_delivery_no_update BEFORE UPDATE ON code_map_delivery BEGIN SELECT RAISE(ABORT,'a code map delivery is immutable'); END;
50
+ CREATE TRIGGER IF NOT EXISTS code_map_delivery_no_delete BEFORE DELETE ON code_map_delivery BEGIN SELECT RAISE(ABORT,'a code map delivery is immutable'); END;
51
+ CREATE TRIGGER IF NOT EXISTS code_map_meta_no_update BEFORE UPDATE ON code_map_meta BEGIN SELECT RAISE(ABORT,'the watermark is set once'); END;
52
+ CREATE TRIGGER IF NOT EXISTS code_map_meta_no_delete BEFORE DELETE ON code_map_meta BEGIN SELECT RAISE(ABORT,'the watermark is set once'); END;
53
+ `;
54
+ /** The tables and triggers V152_SCHEMA makes, for the startup check and the tests. */
55
+ export const V152_TABLES = Object.freeze(["code_map_version", "code_map_current", "code_map_refresh", "code_map_family", "code_map_snapshot", "code_map_delivery", "code_map_meta"]);
56
+ export const V152_TRIGGERS = Object.freeze(["code_map_version_no_update", "code_map_version_guarded_delete", "code_map_family_no_update", "code_map_family_no_delete", "code_map_snapshot_no_update",
57
+ "code_map_snapshot_no_delete", "code_map_delivery_no_update", "code_map_delivery_no_delete", "code_map_meta_no_update", "code_map_meta_no_delete"]);
58
+ /** v152: a saved project reads the map on, with no enrollment yet. */
59
+ export const V152_ADDED_COLUMNS = Object.freeze({
60
+ project: {
61
+ code_map: { definition: "INTEGER NOT NULL DEFAULT 1 CHECK (code_map IN (0,1))", saved: "1" },
62
+ code_map_enroll_until: { definition: "TEXT", saved: "NULL" },
63
+ },
64
+ });
65
+ const sha = (value) => createHash("sha256").update(value).digest("hex");
66
+ export const digestOfUrl = (url) => sha(url);
67
+ export const CODE_MAP_ACT = "code map check";
68
+ const sameIdentity = (a, b) => a !== null && b !== null && a.url === b.url && a.record === b.record;
69
+ export function codeMapOn(store, repo) {
70
+ const row = store.handle.prepare("SELECT code_map FROM project WHERE path = ?").get(repo);
71
+ return row !== undefined && Number(row["code_map"]) === 1;
72
+ }
73
+ const textOf = (value) => (typeof value === "string" ? value : null);
74
+ const intOf = (value) => (value === null || value === undefined ? null : Number(value));
75
+ function refreshOf(row) {
76
+ return {
77
+ repo: String(row["repo"]), leaseToken: Number(row["lease_token"]), leaseOwner: textOf(row["lease_owner"]), leaseUntil: textOf(row["lease_until"]), requestId: Number(row["request_id"]),
78
+ requestedAt: textOf(row["requested_at"]), requestedBy: textOf(row["requested_by"]), failures: Number(row["failures"]), retryAfter: textOf(row["retry_after"]), lastError: textOf(row["last_error"]),
79
+ lastCheckedAt: textOf(row["last_checked_at"]), seen: { digest: textOf(row["seen_digest"]), record: intOf(row["seen_record"]), commit: textOf(row["seen_commit"]), extractor: textOf(row["seen_extractor"]), state: textOf(row["seen_state"]) },
80
+ spawn: { pid: intOf(row["spawn_pid"]), pgid: intOf(row["spawn_pgid"]), startedAt: textOf(row["spawn_started"]), folder: textOf(row["spawn_folder"]) }, enforced: textOf(row["enforced"]), proxy: textOf(row["proxy"]),
81
+ };
82
+ }
83
+ export function readRefresh(store, repo) {
84
+ const row = store.handle.prepare("SELECT * FROM code_map_refresh WHERE repo = ?").get(repo);
85
+ return row === undefined ? null : refreshOf(row);
86
+ }
87
+ const ensureRefresh = (store, repo) => { store.handle.prepare("INSERT OR IGNORE INTO code_map_refresh (repo) VALUES (?)").run(repo); };
88
+ export const LEASE_MS = 10 * 60_000;
89
+ /** Take the lease in one statement. A takeover always gets a higher token; null when it is held. */
90
+ export function takeLease(store, repo, owner, now) {
91
+ ensureRefresh(store, repo);
92
+ const before = readRefresh(store, repo);
93
+ const taken = store.handle.prepare(`UPDATE code_map_refresh SET lease_token = lease_token + 1, lease_owner = ?, lease_until = ?
94
+ WHERE repo = ? AND (lease_owner IS NULL OR lease_until < ?)`).run(owner, new Date(now.getTime() + LEASE_MS).toISOString(), repo, now.toISOString());
95
+ if (Number(taken.changes) !== 1)
96
+ return null;
97
+ const after = readRefresh(store, repo);
98
+ return { repo, token: after.leaseToken, owner, requestId: after.requestId, previous: before.spawn };
99
+ }
100
+ /** A fenced write: changes rows only while this lease stands. Zero rows means the lease was lost. */
101
+ function fenced(store, lease, set, values, extra = "", extraValues = []) {
102
+ const ran = store.handle.prepare(`UPDATE code_map_refresh SET ${set} WHERE repo = ? AND lease_token = ? AND lease_owner = ?${extra}`).run(...values, lease.repo, lease.token, lease.owner, ...extraValues);
103
+ return Number(ran.changes) === 1;
104
+ }
105
+ export const extendLease = (store, lease, now) => fenced(store, lease, "lease_until = ?", [new Date(now.getTime() + LEASE_MS).toISOString()]);
106
+ export const releaseLease = (store, lease) => fenced(store, lease, "lease_owner = NULL, lease_until = NULL, spawn_pid = NULL, spawn_pgid = NULL, spawn_started = NULL, spawn_folder = NULL", []);
107
+ export const clearOldSpawn = (store, lease) => fenced(store, lease, "spawn_pid = NULL, spawn_pgid = NULL, spawn_started = NULL, spawn_folder = NULL", []);
108
+ export const recordFolder = (store, lease, folder) => fenced(store, lease, "spawn_folder = ?", [folder]);
109
+ export const recordSpawn = (store, lease, spawn) => fenced(store, lease, "spawn_pid = ?, spawn_pgid = ?, spawn_started = ?", [spawn.pid, spawn.pgid, spawn.startedAt]);
110
+ /** Clear the request this lease saw, and only that one: a request made during the build stays queued. */
111
+ export const clearRequest = (store, lease) => fenced(store, lease, "requested_at = NULL, requested_by = NULL", [], " AND request_id = ?", [lease.requestId]);
112
+ /** A finished check that found nothing wrong: failures reset. */
113
+ export const recordCheck = (store, lease, facts, now) => fenced(store, lease, "failures = 0, retry_after = NULL, last_error = NULL, last_checked_at = ?, seen_digest = ?, seen_record = ?, seen_commit = ?, seen_extractor = ?, seen_state = ?, enforced = COALESCE(?, enforced), proxy = ?", [now.toISOString(), facts.digest, facts.record, facts.commit, facts.extractor, facts.state, facts.enforced ?? null, facts.proxy ?? null]);
114
+ /** What the peek saw, kept even if the build then fails: the page's "Main has moved to …". */
115
+ export const recordPeek = (store, lease, commit) => fenced(store, lease, "seen_commit = ?", [commit]);
116
+ export const BACKOFF_MS = [30 * 60_000, 2 * 3_600_000, 6 * 3_600_000];
117
+ /** A failed check: 30 minutes, then 2 hours, then 6 hours. */
118
+ export function recordFailure(store, lease, why, state, now) {
119
+ const failures = readRefresh(store, lease.repo)?.failures ?? 0;
120
+ const wait = BACKOFF_MS[Math.min(failures, BACKOFF_MS.length - 1)];
121
+ return fenced(store, lease, "failures = failures + 1, retry_after = ?, last_error = ?, last_checked_at = ?, seen_state = ?", [new Date(now.getTime() + wait).toISOString(), why.slice(0, 300), now.toISOString(), state]);
122
+ }
123
+ export const REQUEST_GAP_MS = 10 * 60_000;
124
+ /** Queue a manual check: at most one per project per 10 minutes. It never bypasses back-off. */
125
+ export function requestRefresh(store, repo, by, now) {
126
+ return store.transact(() => {
127
+ ensureRefresh(store, repo);
128
+ const held = readRefresh(store, repo);
129
+ if (held.requestedAt !== null)
130
+ return "already-queued";
131
+ const last = store.handle.prepare("SELECT at FROM action_ledger WHERE repo = ? AND action = 'code map refresh asked' ORDER BY id DESC LIMIT 1").get(repo);
132
+ if (last !== undefined && now.getTime() - Date.parse(String(last["at"])) < REQUEST_GAP_MS)
133
+ return "already-queued";
134
+ store.handle.prepare("UPDATE code_map_refresh SET request_id = request_id + 1, requested_at = ?, requested_by = ? WHERE repo = ?").run(now.toISOString(), by, repo);
135
+ store.recordAction({ at: now.toISOString(), actor: by, repo, taskId: null, runId: null, action: "code map refresh asked", outcome: "queued", source: "request", detail: null });
136
+ return "queued";
137
+ });
138
+ }
139
+ /** The remote-change transaction's part: the pointer clears and a build still running for the old remote is fenced out.
140
+ * Called from store.recordProjectRemote, inside the transaction that records the new remote. */
141
+ export function codeMapRemoteChanged(store, repo) {
142
+ store.handle.prepare("DELETE FROM code_map_current WHERE repo = ?").run(repo);
143
+ store.handle.prepare("UPDATE code_map_refresh SET lease_token = lease_token + 1, lease_owner = NULL, lease_until = NULL, seen_commit = NULL, seen_digest = NULL, seen_record = NULL, failures = 0, retry_after = NULL WHERE repo = ?").run(repo);
144
+ }
145
+ const versionOf = (row) => ({ id: Number(row["id"]), repo: String(row["repo"]), remoteDigest: String(row["remote_digest"]), remoteRecord: Number(row["remote_record"]),
146
+ commit: String(row["commit_sha"]), extractor: String(row["extractor"]), builtAt: String(row["built_at"]), report: String(row["report"]) });
147
+ const VERSION_COLUMNS = "id, repo, remote_digest, remote_record, commit_sha, extractor, built_at, report";
148
+ export function currentVersion(store, repo) {
149
+ const row = store.handle.prepare(`SELECT ${VERSION_COLUMNS} FROM code_map_version WHERE id = (SELECT version FROM code_map_current WHERE repo = ?)`).get(repo);
150
+ return row === undefined ? null : versionOf(row);
151
+ }
152
+ export function versionRow(store, id) {
153
+ const row = store.handle.prepare(`SELECT ${VERSION_COLUMNS} FROM code_map_version WHERE id = ?`).get(id);
154
+ return row === undefined ? null : versionOf(row);
155
+ }
156
+ /** The stored bytes, checked against their digests before they are parsed; null when they don't verify. */
157
+ export function readVersionMap(store, id) {
158
+ const row = store.handle.prepare("SELECT manifest, manifest_sha, graph, graph_sha FROM code_map_version WHERE id = ?").get(id);
159
+ if (row === undefined)
160
+ return null;
161
+ try {
162
+ const manifest = gunzipSync(row["manifest"], { maxOutputLength: LIMITS.storedBytes * 2 });
163
+ const graph = gunzipSync(row["graph"], { maxOutputLength: LIMITS.storedBytes * 2 });
164
+ if (sha(manifest) !== row["manifest_sha"] || sha(graph) !== row["graph_sha"])
165
+ return null;
166
+ return { ...JSON.parse(manifest.toString("utf8")), ...JSON.parse(graph.toString("utf8")) };
167
+ }
168
+ catch {
169
+ return null;
170
+ }
171
+ }
172
+ export function encodeMap(map) {
173
+ const { edges, ...rest } = map;
174
+ const manifest = Buffer.from(JSON.stringify(rest));
175
+ const graph = Buffer.from(JSON.stringify({ edges }));
176
+ return { manifest: gzipSync(manifest), manifestSha: sha(manifest), graph: gzipSync(graph), graphSha: sha(graph), bytes: manifest.length + graph.length };
177
+ }
178
+ /**
179
+ * Store a built version and, in the same transaction, make it current only if the identity captured before the fetch
180
+ * still stands, the map is still on, the build still holds its lease, and the pointer is where it found it. A build that
181
+ * lost its lease stores nothing at all.
182
+ */
183
+ export function publishVersion(store, lease, input, now) {
184
+ return store.transact(() => {
185
+ const held = readRefresh(store, lease.repo);
186
+ if (held === null || held.leaseToken !== lease.token || held.leaseOwner !== lease.owner)
187
+ return null;
188
+ const { map, encoded } = input;
189
+ const stored = store.handle.prepare(`INSERT INTO code_map_version (repo, remote_digest, remote_record, commit_sha, extractor, built_at, manifest, manifest_sha, graph, graph_sha, report)
190
+ VALUES (?,?,?,?,?,?,?,?,?,?,?)`).run(lease.repo, map.remote.urlDigest, map.remote.record, map.commit, map.extractor, now.toISOString(), encoded.manifest, encoded.manifestSha, encoded.graph, encoded.graphSha, input.report);
191
+ const version = Number(stored.lastInsertRowid);
192
+ const pointer = store.handle.prepare("SELECT version FROM code_map_current WHERE repo = ?").get(lease.repo);
193
+ const at = pointer === undefined ? null : Number(pointer["version"]);
194
+ if (!sameIdentity(input.identity, trustedRemoteIdentity(store, lease.repo)))
195
+ return { version, current: false, why: "remote-changed" };
196
+ if (!codeMapOn(store, lease.repo))
197
+ return { version, current: false, why: "off" };
198
+ if (at !== input.startedFrom)
199
+ return { version, current: false, why: "pointer-moved" };
200
+ store.handle.prepare("INSERT INTO code_map_current (repo, version) VALUES (?, ?) ON CONFLICT (repo) DO UPDATE SET version = excluded.version").run(lease.repo, version);
201
+ return { version, current: true, why: "published" };
202
+ });
203
+ }
204
+ /** Keep the current version, the 10 before it and any named by an unfinished run. */
205
+ export function tidyVersions(store, repo) {
206
+ return store.transact(() => {
207
+ const current = store.handle.prepare("SELECT version FROM code_map_current WHERE repo = ?").get(repo);
208
+ if (current === undefined)
209
+ return 0;
210
+ const ran = store.handle.prepare(`DELETE FROM code_map_version WHERE repo = ? AND id < ? AND id NOT IN (
211
+ SELECT id FROM code_map_version WHERE repo = ? AND id < ? ORDER BY id DESC LIMIT 10)
212
+ AND id NOT IN (SELECT s.version FROM code_map_snapshot s JOIN run r ON r.id = s.run WHERE s.version IS NOT NULL AND r.outcome IS NULL)`).run(repo, Number(current["version"]), repo, Number(current["version"]));
213
+ return Number(ran.changes);
214
+ });
215
+ }
216
+ /** A project removed from the lists: its map rows go with it, except what stays with its runs (snapshots, deliveries,
217
+ * family arms) and any version an unfinished run still names. */
218
+ export function forgetCodeMap(store, repo) {
219
+ store.handle.prepare("DELETE FROM code_map_current WHERE repo = ?").run(repo);
220
+ store.handle.prepare("DELETE FROM code_map_refresh WHERE repo = ?").run(repo);
221
+ store.handle.prepare(`DELETE FROM code_map_version WHERE repo = ? AND id NOT IN (SELECT s.version FROM code_map_snapshot s JOIN run r ON r.id = s.run WHERE s.version IS NOT NULL AND r.outcome IS NULL)`).run(repo);
222
+ }
223
+ /** The startup check: a store at this version holds every table and trigger, or its map can't be trusted. */
224
+ export function codeMapShapeProblem(db) {
225
+ const has = db.prepare("SELECT 1 FROM sqlite_master WHERE type = ? AND name = ?");
226
+ for (const table of V152_TABLES)
227
+ if (has.get("table", table) === undefined)
228
+ return `the code map table ${table} is missing`;
229
+ for (const trigger of V152_TRIGGERS)
230
+ if (has.get("trigger", trigger) === undefined)
231
+ return `the code map trigger ${trigger} is missing`;
232
+ return null;
233
+ }
@@ -15,6 +15,8 @@ export type CodingCustody = {
15
15
  observationFailures?: ProcessObservationFailure[];
16
16
  host: string;
17
17
  bootId: string | null;
18
+ /** v150: this machine's stable id (machine-identity.ts); absent in custody saved before it. */
19
+ machineId?: string | null;
18
20
  container?: {
19
21
  backend: string;
20
22
  id: string;
@@ -83,4 +85,4 @@ export declare function createCodexCodingProvider(options?: {
83
85
  export declare function codingSessionPermissions(store: Pick<Store, "databaseFile" | "listWorktrees" | "projectSandbox">, worktreeRoot: string): (input: {
84
86
  repo: string;
85
87
  worktree: string;
86
- }) => CodingPermissions;
88
+ }) => Promise<CodingPermissions>;
@@ -1,4 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { prepareProtectedStorage, storageRefusalOf } from "./protected-storage.js";
2
3
  import { TextDecoder } from "node:util";
3
4
  import { homedir, hostname } from "node:os";
4
5
  import { readDenyPath } from "./project-sandbox.js";
@@ -6,6 +7,7 @@ import { dirname, join } from "node:path";
6
7
  import { codexFenceConfig, fenceFor, FENCE_POLICY_CURRENT, runFolders, taskBoundary, trustedWorktreeBinding } from "./agent-fence.js";
7
8
  import { poolRootOf } from "./worktree.js";
8
9
  import { currentBootId } from "./boot-identity.js";
10
+ import { currentMachineId } from "./machine-identity.js";
9
11
  import { createContainer, currentContainment } from "./containment.js";
10
12
  import { processMayBeAlive } from "./process-liveness.js";
11
13
  import { observeProcessTree, sampleProcessTree, stopProcessTree } from "./process-tree.js";
@@ -82,7 +84,7 @@ export function createCodexCodingProvider(options = {}) {
82
84
  const descendants = new Map();
83
85
  let ancestryUnknown = false;
84
86
  const observationFailures = [];
85
- const host = hostname();
87
+ const host = hostname(), machineId = currentMachineId();
86
88
  // Bind the boot at creation, not when an old snapshot is requested later.
87
89
  const bootId = currentBootId();
88
90
  let nextId = 0;
@@ -94,7 +96,7 @@ export function createCodexCodingProvider(options = {}) {
94
96
  return {
95
97
  pid, group: pid !== null && process.platform !== "win32",
96
98
  descendants: [...descendants.values()].map(one => ({ ...one })),
97
- observationUnknown: ancestryUnknown, host, bootId,
99
+ observationUnknown: ancestryUnknown, host, bootId, machineId,
98
100
  ...(observationFailures.length === 0 ? {} : { observationFailures: observationFailures.map(one => ({ ...one })) }),
99
101
  ...(container === null ? {} : { container: { backend: container.backend, id: container.id, ...(container.identity === undefined ? {} : { identity: container.identity }) } }),
100
102
  };
@@ -440,20 +442,23 @@ export function createCodexCodingProvider(options = {}) {
440
442
  export function codingSessionPermissions(store, worktreeRoot) {
441
443
  return input => sessionPermissions(store, { ...input, worktreeRoot });
442
444
  }
443
- function sessionPermissions(store, input) {
445
+ async function sessionPermissions(store, input) {
444
446
  const databaseFile = store.databaseFile();
445
447
  if (databaseFile === null || databaseFile === "" || databaseFile === ":memory:")
446
448
  return { problem: "The coding session has no state folder to fence, so it was not started." };
447
449
  try {
448
450
  const poolRoots = [join(dirname(databaseFile), "worktrees"), input.worktreeRoot, ...store.listWorktrees().map(record => poolRootOf(record.path, record.repo))];
449
451
  const binding = trustedWorktreeBinding(input.repo, input.worktree, poolRoots);
450
- const boundary = taskBoundary({ worktree: input.worktree, provider: "codex", binding, policy: FENCE_POLICY_CURRENT });
452
+ // The storage check, awaited off this thread (protected-storage.ts).
453
+ const storage = await prepareProtectedStorage();
454
+ const boundary = taskBoundary({ worktree: input.worktree, provider: "codex", binding, policy: FENCE_POLICY_CURRENT, storage });
451
455
  // The project's read denies, read live (a session seals none; they only narrow).
452
456
  const readDeny = store.projectSandbox(input.repo).readDeny.map(one => readDenyPath(one, homedir()));
453
- const launch = fenceFor("build", { databaseFile, worktree: input.worktree, boundary, binding, runFolders: runFolders(null), poolRoots, readDeny, policy: FENCE_POLICY_CURRENT });
457
+ const launch = fenceFor("build", { databaseFile, worktree: input.worktree, boundary, binding, runFolders: runFolders(null), poolRoots, readDeny, policy: FENCE_POLICY_CURRENT, storage });
454
458
  return { config: codexFenceConfig(launch.fence, false, launch.fenceGrants) };
455
459
  }
456
- catch {
457
- return { problem: "The session's worktree couldn't be bound to its project, so the session was not started." };
460
+ catch (error) {
461
+ // The storage check's own words survive: what didn't finish, and which program macOS may need to allow.
462
+ return { problem: storageRefusalOf(error)?.message ?? "The session's worktree couldn't be bound to its project, so the session was not started." };
458
463
  }
459
464
  }
@@ -1,6 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { chmodSync, closeSync, fsyncSync, lstatSync, openSync } from 'node:fs';
3
- import { hostname } from 'node:os';
3
+ import { isThisMachine } from './machine-identity.js';
4
4
  import { sqliteRuntime } from './sqlite-runtime.js';
5
5
  import { processMayBeAlive } from './process-liveness.js';
6
6
  const gatePrefix = 'so_coding_update_';
@@ -141,7 +141,8 @@ export function releaseStaleCodingOwner(orders, stoppedPids, alive = processMayB
141
141
  c = {};
142
142
  }
143
143
  const processes = Array.isArray(c.descendants) ? c.descendants : null;
144
- if (c.host !== hostname() || c.container || c.observationUnknown !== false || processes === null)
144
+ if (typeof c.host !== 'string' || (c.machineId !== undefined && c.machineId !== null && typeof c.machineId !== 'string')
145
+ || !isThisMachine({ host: c.host, machineId: (c.machineId ?? null) }) || c.container || c.observationUnknown !== false || processes === null)
145
146
  refuse("the agent's tool processes could not be accounted for");
146
147
  const recorded = [...(c.pid === null ? [] : [{ pid: c.pid, group: c.group }]), ...processes];
147
148
  for (const one of recorded) {
@@ -39,7 +39,7 @@ type Options = {
39
39
  permissions: (input: {
40
40
  repo: string;
41
41
  worktree: string;
42
- }) => CodingPermissions;
42
+ }) => CodingPermissions | Promise<CodingPermissions>;
43
43
  provider?: () => CodingProvider;
44
44
  admissionPaused?: () => boolean;
45
45
  authorize?: (actor: CodingActor, repo: string) => boolean;
@@ -2,8 +2,8 @@ import { checkoutGit } from './teardown-git.js';
2
2
  import { createHash, randomUUID } from 'node:crypto';
3
3
  import { mkdirSync, chmodSync, realpathSync } from 'node:fs';
4
4
  import { dirname, join } from 'node:path';
5
- import { hostname } from 'node:os';
6
- import { provenDeadByBootChange } from './boot-identity.js';
5
+ import { currentBootId, provenDeadByBootChange } from './boot-identity.js';
6
+ import { isThisMachine } from './machine-identity.js';
7
7
  import { containerEmptiness } from './container-state.js';
8
8
  import { sqliteRuntime } from './sqlite-runtime.js';
9
9
  import { run } from './exec.js';
@@ -280,8 +280,8 @@ export class CodingWorkspace {
280
280
  return { id, type, text, status: string(item['status']) || null, ...(type === 'userMessage' && clientId ? { clientId } : {}) };
281
281
  }
282
282
  /** The thread's confinement, made right before each start or resume from the plane's own records. */
283
- permissionsFor(repo, worktree) {
284
- const made = this.options.permissions({ repo, worktree });
283
+ async permissionsFor(repo, worktree) {
284
+ const made = await this.options.permissions({ repo, worktree });
285
285
  if ('problem' in made)
286
286
  throw Error(made.problem);
287
287
  return made;
@@ -455,7 +455,7 @@ export class CodingWorkspace {
455
455
  this.authorize(actor, repo);
456
456
  this.admit();
457
457
  nativeStartupAttempted = true;
458
- const result = object(await this.request('thread/start', { cwd: worktree, approvalPolicy: 'on-request', approvalsReviewer: 'user', ...this.permissionsFor(repo, worktree), ...(input.model ? { model: input.model } : {}), developerInstructions: CODING_INSTRUCTIONS + (context?.text ?? ''), persistExtendedHistory: true }));
458
+ const result = object(await this.request('thread/start', { cwd: worktree, approvalPolicy: 'on-request', approvalsReviewer: 'user', ...(await this.permissionsFor(repo, worktree)), ...(input.model ? { model: input.model } : {}), developerInstructions: CODING_INSTRUCTIONS + (context?.text ?? ''), persistExtendedHistory: true }));
459
459
  const thread = object(result['thread']), nativeThreadId = string(thread['id']);
460
460
  if (!nativeThreadId)
461
461
  throw Error('The agent did not return a session identity.');
@@ -504,7 +504,7 @@ export class CodingWorkspace {
504
504
  this.authorize({ name: session.owner, generation: session.generation }, session.repo);
505
505
  if (session.context)
506
506
  verifyCodingContext(session.context, session.repo, session.base);
507
- const result = object(await this.request('thread/resume', { threadId: session.nativeThreadId, cwd: session.worktree, approvalPolicy: 'on-request', approvalsReviewer: 'user', ...this.permissionsFor(session.repo, session.worktree), ...(session.model ? { model: session.model } : {}), developerInstructions: CODING_INSTRUCTIONS + (session.context?.text ?? ''), persistExtendedHistory: true }));
507
+ const result = object(await this.request('thread/resume', { threadId: session.nativeThreadId, cwd: session.worktree, approvalPolicy: 'on-request', approvalsReviewer: 'user', ...(await this.permissionsFor(session.repo, session.worktree)), ...(session.model ? { model: session.model } : {}), developerInstructions: CODING_INSTRUCTIONS + (session.context?.text ?? ''), persistExtendedHistory: true }));
508
508
  const thread = object(result['thread']);
509
509
  if (thread['id'] !== session.nativeThreadId)
510
510
  throw Error('The agent returned a different conversation. Continuity could not be verified.');
@@ -536,9 +536,9 @@ export class CodingWorkspace {
536
536
  return false;
537
537
  try {
538
538
  const c = readCodingCustodyWitness(String(row['payload']));
539
- if (c === null || c.host !== hostname())
539
+ if (c === null || typeof c.host !== 'string' || !isThisMachine(c))
540
540
  return false;
541
- if (provenDeadByBootChange(c))
541
+ if (provenDeadByBootChange(c, { host: c.host, bootId: currentBootId() }))
542
542
  return true;
543
543
  if (c.container)
544
544
  return containerEmptiness(c.container.backend, c.container.id, process.platform, c.container.identity) === 'empty';
@@ -49,11 +49,11 @@ export function projectSummaryOf(store, now, access, actor, projects, evidenceRo
49
49
  };
50
50
  const rows = projects.map(one => {
51
51
  const state = states.get(one.path);
52
- return { path: one.path, name: one.name, href: `/work?project=${encodeURIComponent(one.path)}`, state: state?.state ?? "idle", counts: state?.counts ?? { ...NO_COUNTS }, ...railOf(state) };
52
+ return { path: one.path, name: one.name, href: `/tasks?project=${encodeURIComponent(one.path)}`, state: state?.state ?? "idle", counts: state?.counts ?? { ...NO_COUNTS }, ...railOf(state) };
53
53
  });
54
54
  const unplaced = states.get(null);
55
55
  if (unplaced !== undefined && unplaced.state !== "idle")
56
- rows.push({ path: null, name: "No project", href: "/work", state: unplaced.state, counts: unplaced.counts, ...railOf(unplaced) });
56
+ rows.push({ path: null, name: "No project", href: "/tasks", state: unplaced.state, counts: unplaced.counts, ...railOf(unplaced) });
57
57
  return boundProjectSummary(rows);
58
58
  }
59
59
  /** One access, canonically and in full (every field it has, so a field added later keys too): the same reader and
@@ -161,6 +161,11 @@ export type Container = {
161
161
  waitEmpty(timeoutMs: number): Promise<boolean>;
162
162
  /** Remove the OS object (only an empty one can go) and dismiss the janitor. */
163
163
  release(): void;
164
+ /** Hard limits on the whole object, set before `launch`; false when this object can't enforce them. */
165
+ limit?(limits: {
166
+ memoryBytes: number;
167
+ pids: number;
168
+ }): boolean;
164
169
  };
165
170
  export type ContainerFactory = (effective: EffectiveContainment, label: string) => Container;
166
171
  /** Tests: substitute the OS object (a scripted or file-backed stand-in). */
@@ -353,6 +353,17 @@ export function cgroup2Container(capability, label, mountOverride) {
353
353
  backend: "cgroup2",
354
354
  id: dir,
355
355
  identity,
356
+ limit({ memoryBytes, pids }) {
357
+ // The controllers exist here only if the delegated parent enabled them for its children.
358
+ try {
359
+ writeFileSync(join(dir, "memory.max"), String(memoryBytes));
360
+ writeFileSync(join(dir, "pids.max"), String(pids));
361
+ return true;
362
+ }
363
+ catch {
364
+ return false;
365
+ }
366
+ },
356
367
  launch(file, args) {
357
368
  return {
358
369
  file: "/bin/sh",
@@ -445,6 +456,7 @@ export function jobObjectContainer(capability, label) {
445
456
  let authorized = false;
446
457
  let cancelled = false;
447
458
  let helperChild = null;
459
+ let memoryLimit = 0;
448
460
  const fail = (detail) => { failure ??= detail; if (state !== "empty")
449
461
  state = "lost"; connection?.destroy(); };
450
462
  const authorize = () => {
@@ -517,10 +529,11 @@ export function jobObjectContainer(capability, label) {
517
529
  };
518
530
  return {
519
531
  backend: "job-object", id, identity,
532
+ limit({ memoryBytes }) { memoryLimit = memoryBytes; return true; },
520
533
  launch(file, args) {
521
534
  return {
522
535
  file: capability.powershell,
523
- args: ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", capability.helper, "-Pipe", pipe, "-Encoded", Buffer.from(JSON.stringify([file, ...args]), "utf8").toString("base64"), "-JobName", id],
536
+ args: ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", capability.helper, "-Pipe", pipe, "-Encoded", Buffer.from(JSON.stringify([file, ...args]), "utf8").toString("base64"), "-JobName", id, ...(memoryLimit > 0 ? ["-MemoryLimit", String(memoryLimit)] : [])],
524
537
  extraStdio: [],
525
538
  async attach(child) {
526
539
  // Called only after the durable onSpawn callback succeeded.
@@ -260,6 +260,80 @@ export declare const chatActionRequestSchemas: {
260
260
  repo: z.ZodString;
261
261
  trial: z.ZodInt;
262
262
  }, z.core.$strict>;
263
+ readonly code_map_switch: z.ZodObject<{
264
+ repo: z.ZodString;
265
+ on: z.ZodBoolean;
266
+ }, z.core.$strict>;
267
+ readonly mode_revoke: z.ZodObject<{
268
+ repo: z.ZodString;
269
+ }, z.core.$strict>;
270
+ readonly provider_resume: z.ZodObject<{
271
+ provider: z.ZodString;
272
+ }, z.core.$strict>;
273
+ readonly worker_note: z.ZodObject<{
274
+ runner: z.ZodString;
275
+ note: z.ZodString;
276
+ }, z.core.$strict>;
277
+ readonly lead_identity: z.ZodObject<{
278
+ name: z.ZodString;
279
+ persona: z.ZodString;
280
+ }, z.core.$strict>;
281
+ readonly task_archive: z.ZodObject<{
282
+ task: z.ZodString;
283
+ reason: z.ZodOptional<z.ZodString>;
284
+ }, z.core.$strict>;
285
+ readonly task_restore: z.ZodObject<{
286
+ task: z.ZodString;
287
+ }, z.core.$strict>;
288
+ readonly flow_archive: z.ZodObject<{
289
+ flow: z.ZodInt;
290
+ }, z.core.$strict>;
291
+ readonly flow_unattended_off: z.ZodObject<{
292
+ flow: z.ZodInt;
293
+ }, z.core.$strict>;
294
+ readonly incident_resolve: z.ZodObject<{
295
+ incident: z.ZodInt;
296
+ }, z.core.$strict>;
297
+ readonly finding_send: z.ZodObject<{
298
+ task: z.ZodString;
299
+ summary: z.ZodOptional<z.ZodString>;
300
+ file: z.ZodOptional<z.ZodString>;
301
+ line: z.ZodOptional<z.ZodInt>;
302
+ pointer: z.ZodOptional<z.ZodString>;
303
+ run: z.ZodOptional<z.ZodInt>;
304
+ }, z.core.$strict>;
305
+ readonly finding_resend: z.ZodObject<{
306
+ finding: z.ZodInt;
307
+ }, z.core.$strict>;
308
+ readonly task_reopen: z.ZodObject<{
309
+ task: z.ZodString;
310
+ }, z.core.$strict>;
311
+ readonly revision_accept: z.ZodObject<{
312
+ task: z.ZodString;
313
+ revision: z.ZodOptional<z.ZodInt>;
314
+ }, z.core.$strict>;
315
+ readonly revision_reject: z.ZodObject<{
316
+ task: z.ZodString;
317
+ revision: z.ZodOptional<z.ZodInt>;
318
+ }, z.core.$strict>;
319
+ readonly flow_unattended: z.ZodObject<{
320
+ flow: z.ZodInt;
321
+ days: z.ZodInt;
322
+ budget: z.ZodOptional<z.ZodString>;
323
+ runs: z.ZodOptional<z.ZodString>;
324
+ include: z.ZodOptional<z.ZodArray<z.ZodInt>>;
325
+ }, z.core.$strict>;
326
+ readonly memory_decide: z.ZodObject<{
327
+ repo: z.ZodString;
328
+ proposal: z.ZodInt;
329
+ decision: z.ZodEnum<{
330
+ accept: "accept";
331
+ reject: "reject";
332
+ }>;
333
+ }, z.core.$strict>;
334
+ readonly code_map_holdout_end: z.ZodObject<{
335
+ repo: z.ZodString;
336
+ }, z.core.$strict>;
263
337
  /** The line, and (from 1) the line it replaces; 0 or none adds it. */
264
338
  readonly lead_about_you: z.ZodObject<{
265
339
  line: z.ZodString;
@@ -295,8 +369,13 @@ export declare const SHARED_ACTION_VERSION = 1;
295
369
  export declare const sharedActionSchema: z.ZodObject<{
296
370
  version: z.ZodLiteral<1>;
297
371
  operation: z.ZodEnum<{
372
+ code_map_holdout_end: "code_map_holdout_end";
373
+ code_map_switch: "code_map_switch";
298
374
  decision_record: "decision_record";
299
375
  decision_retire: "decision_retire";
376
+ finding_resend: "finding_resend";
377
+ finding_send: "finding_send";
378
+ flow_archive: "flow_archive";
300
379
  flow_browser: "flow_browser";
301
380
  flow_browser_off: "flow_browser_off";
302
381
  flow_card_add: "flow_card_add";
@@ -316,13 +395,22 @@ export declare const sharedActionSchema: z.ZodObject<{
316
395
  flow_trigger_pause: "flow_trigger_pause";
317
396
  flow_trigger_remove: "flow_trigger_remove";
318
397
  flow_trigger_resume: "flow_trigger_resume";
398
+ flow_unattended: "flow_unattended";
399
+ flow_unattended_off: "flow_unattended_off";
400
+ incident_resolve: "incident_resolve";
319
401
  kit_setup: "kit_setup";
320
402
  knowledge_instructions: "knowledge_instructions";
321
403
  knowledge_remove: "knowledge_remove";
322
404
  knowledge_restore: "knowledge_restore";
323
405
  knowledge_save: "knowledge_save";
324
406
  lead_about_you: "lead_about_you";
407
+ lead_identity: "lead_identity";
408
+ memory_decide: "memory_decide";
409
+ mode_revoke: "mode_revoke";
410
+ provider_resume: "provider_resume";
325
411
  result_accept: "result_accept";
412
+ revision_accept: "revision_accept";
413
+ revision_reject: "revision_reject";
326
414
  scope_approve: "scope_approve";
327
415
  skill_disable: "skill_disable";
328
416
  skill_enable: "skill_enable";
@@ -342,11 +430,15 @@ export declare const sharedActionSchema: z.ZodObject<{
342
430
  suggestion_not_now: "suggestion_not_now";
343
431
  sweep_cancel_rest: "sweep_cancel_rest";
344
432
  sweep_file: "sweep_file";
433
+ task_archive: "task_archive";
345
434
  task_cancel: "task_cancel";
435
+ task_reopen: "task_reopen";
436
+ task_restore: "task_restore";
346
437
  task_resume: "task_resume";
347
438
  tool_add: "tool_add";
348
439
  tool_remove: "tool_remove";
349
440
  trial_stop: "trial_stop";
441
+ worker_note: "worker_note";
350
442
  }>;
351
443
  request: z.ZodObject<{}, z.core.$catchall<z.ZodUnknown>>;
352
444
  repo: z.ZodString;
@@ -85,6 +85,26 @@ export const chatActionRequestSchemas = {
85
85
  suggestion_not_now: z.strictObject({ repo: words, suggestion: words }),
86
86
  // Slice 3: stopping a trial (starting one stays in Settings → Models, behind a step-up).
87
87
  trial_stop: z.strictObject({ repo: words, trial: id }),
88
+ code_map_switch: z.strictObject({ repo: words, on: z.boolean() }),
89
+ // Chat and console parity (docs/plans/toolroll-cards.md): each runs the console's own write.
90
+ mode_revoke: z.strictObject({ repo: words }),
91
+ provider_resume: z.strictObject({ provider: words }),
92
+ worker_note: z.strictObject({ runner: words, note: words }),
93
+ lead_identity: z.strictObject({ name: words, persona: words }),
94
+ task_archive: z.strictObject({ task: words, reason: words.optional() }),
95
+ task_restore: z.strictObject({ task: words }),
96
+ flow_archive: z.strictObject({ flow: id }),
97
+ flow_unattended_off: z.strictObject({ flow: id }),
98
+ incident_resolve: z.strictObject({ incident: id }),
99
+ // Follow-through: send a finding to the task that owns the code. Text is checked and cleaned when the card is made.
100
+ finding_send: z.strictObject({ task: words, summary: words.optional(), file: words.optional(), line: z.int().min(1).optional(), pointer: words.optional(), run: id.optional() }),
101
+ finding_resend: z.strictObject({ finding: id }),
102
+ task_reopen: z.strictObject({ task: words }),
103
+ revision_accept: z.strictObject({ task: words, revision: id.optional() }),
104
+ revision_reject: z.strictObject({ task: words, revision: id.optional() }),
105
+ flow_unattended: z.strictObject({ flow: id, days: z.int().min(1), budget: words.optional(), runs: words.optional(), include: z.array(id).optional() }),
106
+ memory_decide: z.strictObject({ repo: words, proposal: id, decision: z.enum(["accept", "reject"]) }),
107
+ code_map_holdout_end: z.strictObject({ repo: words }),
88
108
  /** The line, and (from 1) the line it replaces; 0 or none adds it. */
89
109
  lead_about_you: z.strictObject({ line: words, replaces: z.int().min(0).optional() }),
90
110
  scope_approve: z.strictObject({ task: words }),