@north-light/cli 0.3.560 → 0.3.562

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 (573) hide show
  1. package/dist/api/client.d.ts +27 -4
  2. package/dist/api/client.js +3 -3
  3. package/dist/api/contract.d.ts +1 -1
  4. package/dist/api/contract.js +1 -1
  5. package/dist/api/dto/bash-jobs.d.ts +5 -1
  6. package/dist/api/dto/broker-ops.d.ts +25 -1
  7. package/dist/api/dto/charges.d.ts +9 -2
  8. package/dist/api/dto/delivery.d.ts +26 -2
  9. package/dist/api/dto/execution-start.d.ts +57 -0
  10. package/dist/api/dto/execution-start.js +0 -0
  11. package/dist/api/dto/modelauth.d.ts +5 -0
  12. package/dist/api/dto/node-records.d.ts +16 -0
  13. package/dist/api/dto/objects.d.ts +1 -1
  14. package/dist/api/dto/reports.d.ts +0 -3
  15. package/dist/api/dto/run-events.d.ts +7 -1
  16. package/dist/api/dto/settings.d.ts +55 -0
  17. package/dist/api/dto/settings.js +0 -0
  18. package/dist/api/index.d.ts +2 -0
  19. package/dist/api/index.js +1 -1
  20. package/dist/api/routes.d.ts +11 -0
  21. package/dist/api/routes.js +1 -1
  22. package/dist/builtin-memory/crouter-sdk/release-notes.md +4 -0
  23. package/dist/cli.js +7 -7
  24. package/dist/clients/attach/chrome/bash-jobs.d.ts +11 -1
  25. package/dist/clients/attach/chrome/bash-jobs.js +1 -1
  26. package/dist/clients/attach/chrome/canvas-panels.d.ts +1 -1
  27. package/dist/clients/attach/chrome/roster.js +6 -4
  28. package/dist/clients/attach/overlays/auth.js +2 -2
  29. package/dist/clients/attach/render/chat-view.js +1 -1
  30. package/dist/clients/attach/viewer.js +550 -567
  31. package/dist/commands/canvas-config.js +2 -2
  32. package/dist/commands/canvas-use.js +1 -1
  33. package/dist/commands/human.js +2 -2
  34. package/dist/commands/node/bash.js +3 -3
  35. package/dist/commands/node/create.js +2 -2
  36. package/dist/commands/person-settings.d.ts +1 -0
  37. package/dist/commands/person-settings.js +1 -0
  38. package/dist/commands/pkg/market-manage.d.ts +2 -2
  39. package/dist/commands/pkg/market-manage.js +1 -1
  40. package/dist/commands/pkg/plugin-manage.js +13 -13
  41. package/dist/commands/pkg/shared.js +1 -1
  42. package/dist/commands/profile/default.js +1 -1
  43. package/dist/commands/push.js +2 -3
  44. package/dist/commands/sys/config.js +1 -1
  45. package/dist/commands/sys/connect.js +1 -1
  46. package/dist/commands/sys/context/sysprompt.js +2 -2
  47. package/dist/commands/sys/doctor.js +1 -1
  48. package/dist/commands/sys/panels/broker-limits-panel.js +1 -1
  49. package/dist/commands/sys/panels/keybindings-panel.js +1 -1
  50. package/dist/commands/sys/panels/models-panel.d.ts +8 -2
  51. package/dist/commands/sys/panels/models-panel.js +1 -1
  52. package/dist/commands/sys/panels/prefs-panel.d.ts +1 -0
  53. package/dist/commands/sys/panels/prefs-panel.js +1 -1
  54. package/dist/commands/sys/panels/profiles-panel.js +1 -1
  55. package/dist/commands/sys/panels/provider-panel.d.ts +4 -0
  56. package/dist/commands/sys/panels/provider-panel.js +1 -1
  57. package/dist/commands/sys/provider-login.js +5 -5
  58. package/dist/commands/sys/setup-core.js +3 -3
  59. package/dist/commands/sys/setup-settings-runner.js +1 -1
  60. package/dist/commands/sys/spend.js +3 -3
  61. package/dist/commands/sys/update.js +1 -1
  62. package/dist/core/auth-file.d.ts +11 -19
  63. package/dist/core/auth-file.js +1 -2
  64. package/dist/core/auto-update.d.ts +1 -1
  65. package/dist/core/auto-update.js +4 -4
  66. package/dist/core/bash-jobs.d.ts +29 -71
  67. package/dist/core/bash-jobs.js +9 -10
  68. package/dist/core/blobs/directory.d.ts +3 -0
  69. package/dist/core/blobs/directory.js +1 -0
  70. package/dist/core/blobs/fault.d.ts +3 -0
  71. package/dist/core/blobs/fault.js +1 -0
  72. package/dist/core/blobs/index.d.ts +21 -0
  73. package/dist/core/blobs/index.js +1 -0
  74. package/dist/core/blobs/r2.d.ts +9 -0
  75. package/dist/core/blobs/r2.js +4 -0
  76. package/dist/core/bootstrap.d.ts +1 -1
  77. package/dist/core/bootstrap.js +2 -2
  78. package/dist/core/canvas/canvas.d.ts +9 -7
  79. package/dist/core/canvas/canvas.js +8 -8
  80. package/dist/core/canvas/crons.d.ts +3 -2
  81. package/dist/core/canvas/crons.js +3 -3
  82. package/dist/core/canvas/daemon-owner.d.ts +5 -0
  83. package/dist/core/canvas/daemon-owner.js +1 -1
  84. package/dist/core/canvas/db.d.ts +10 -9
  85. package/dist/core/canvas/db.js +4 -4
  86. package/dist/core/canvas/executions.d.ts +100 -4
  87. package/dist/core/canvas/executions.js +1 -1
  88. package/dist/core/canvas/export-sqlite.js +1 -1
  89. package/dist/core/canvas/focuses.d.ts +4 -0
  90. package/dist/core/canvas/focuses.js +1 -1
  91. package/dist/core/canvas/history.js +9 -9
  92. package/dist/core/canvas/managed-worktree-sweep-candidates.d.ts +2 -1
  93. package/dist/core/canvas/managed-worktree-sweep-candidates.js +3 -2
  94. package/dist/core/canvas/node-tokens.d.ts +1 -4
  95. package/dist/core/canvas/node-tokens.js +3 -3
  96. package/dist/core/canvas/paths.d.ts +0 -1
  97. package/dist/core/canvas/paths.js +1 -1
  98. package/dist/core/canvas/person-state.d.ts +26 -0
  99. package/dist/core/canvas/person-state.js +1 -0
  100. package/dist/core/canvas/person.d.ts +3 -10
  101. package/dist/core/canvas/person.js +7 -7
  102. package/dist/core/canvas/reconciler-leases.d.ts +12 -0
  103. package/dist/core/canvas/reconciler-leases.js +8 -0
  104. package/dist/core/canvas/recovery.d.ts +4 -0
  105. package/dist/core/canvas/recovery.js +1 -1
  106. package/dist/core/canvas/schema-pg/003-fleet-removals.sql +1 -1
  107. package/dist/core/canvas/schema-pg/005-fleet-blobs.sql +13 -0
  108. package/dist/core/canvas/schema-pg/006-job-rows.sql +20 -0
  109. package/dist/core/canvas/schema-pg/007-ledger-check.sql +117 -0
  110. package/dist/core/canvas/schema-version.d.ts +1 -1
  111. package/dist/core/canvas/schema-version.js +1 -1
  112. package/dist/core/canvas/sessions.d.ts +8 -11
  113. package/dist/core/canvas/sessions.js +1 -4
  114. package/dist/core/canvas/tree.d.ts +4 -2
  115. package/dist/core/canvas/tree.js +6 -6
  116. package/dist/core/canvas/types.d.ts +7 -0
  117. package/dist/core/command-hooks/transport/exec-lifecycle.js +1 -1
  118. package/dist/core/command-plugins/bundle.d.ts +1 -1
  119. package/dist/core/command-plugins/bundle.js +1 -1
  120. package/dist/core/command-plugins/compose.d.ts +1 -1
  121. package/dist/core/command-plugins/discovery.d.ts +0 -1
  122. package/dist/core/command-plugins/discovery.js +1 -1
  123. package/dist/core/command-plugins/help-addenda.js +1 -1
  124. package/dist/core/command-plugins/presence.d.ts +2 -0
  125. package/dist/core/command-plugins/presence.js +1 -1
  126. package/dist/core/config.d.ts +16 -15
  127. package/dist/core/config.js +1 -1
  128. package/dist/core/conversation-store/client-worker.js +1 -1
  129. package/dist/core/conversation-store/client.js +2 -2
  130. package/dist/core/conversation-store/index.d.ts +9 -11
  131. package/dist/core/conversation-store/index.js +1 -1
  132. package/dist/core/conversation-store/patch.js +1 -1
  133. package/dist/core/conversation-store/row-store.d.ts +14 -0
  134. package/dist/core/conversation-store/row-store.js +13 -0
  135. package/dist/core/directory/join.js +1 -1
  136. package/dist/core/env-name.d.ts +1 -0
  137. package/dist/core/env-name.js +1 -1
  138. package/dist/core/environment-recipe.d.ts +15 -0
  139. package/dist/core/environment-recipe.js +1 -0
  140. package/dist/core/environments.js +1 -1
  141. package/dist/core/events/emit.js +2 -2
  142. package/dist/core/events/run-event-chunks.d.ts +25 -0
  143. package/dist/core/events/run-event-chunks.js +5 -0
  144. package/dist/core/feed/messages.js +5 -5
  145. package/dist/core/grants/mirror.d.ts +15 -0
  146. package/dist/core/grants/mirror.js +15 -3
  147. package/dist/core/grants/on-sync.d.ts +9 -5
  148. package/dist/core/grants/on-sync.js +1 -1
  149. package/dist/core/grants/remove.d.ts +1 -6
  150. package/dist/core/grants/remove.js +1 -1
  151. package/dist/core/grants/sync.js +1 -1
  152. package/dist/core/grants/watch.d.ts +15 -9
  153. package/dist/core/grants/watch.js +1 -1
  154. package/dist/core/graph/custom.js +8 -8
  155. package/dist/core/graph/jobs.d.ts +83 -5
  156. package/dist/core/graph/jobs.js +11 -2
  157. package/dist/core/graph/kinds/job.js +2 -2
  158. package/dist/core/graph/names.js +7 -7
  159. package/dist/core/graph/objects.d.ts +5 -1
  160. package/dist/core/graph/objects.js +11 -11
  161. package/dist/core/graph/reader.d.ts +3 -0
  162. package/dist/core/graph/reader.js +3 -3
  163. package/dist/core/graph/repo-sync/identity.d.ts +3 -1
  164. package/dist/core/graph/repo-sync/identity.js +1 -1
  165. package/dist/core/graph/repo-sync/repos.d.ts +1 -1
  166. package/dist/core/graph/repo-sync/repos.js +2 -2
  167. package/dist/core/graph/repo-sync/sync.js +2 -2
  168. package/dist/core/installed-plugins.js +1 -1
  169. package/dist/core/keybindings/persistence.d.ts +1 -1
  170. package/dist/core/keybindings/persistence.js +1 -1
  171. package/dist/core/layout-migrate/loose-keys.d.ts +1 -1
  172. package/dist/core/layout-migrate/loose-keys.js +1 -1
  173. package/dist/core/ledger/account.d.ts +12 -0
  174. package/dist/core/ledger/account.js +2 -0
  175. package/dist/core/ledger/charges.d.ts +2 -2
  176. package/dist/core/ledger/charges.js +1 -1
  177. package/dist/core/ledger/check.d.ts +5 -1
  178. package/dist/core/ledger/check.js +1 -7
  179. package/dist/core/ledger/chunks.d.ts +8 -0
  180. package/dist/core/ledger/chunks.js +2 -0
  181. package/dist/core/ledger/index.d.ts +2 -1
  182. package/dist/core/ledger/index.js +1 -1
  183. package/dist/core/ledger/infra-allowance.d.ts +9 -0
  184. package/dist/core/ledger/infra-allowance.js +1 -0
  185. package/dist/core/ledger/limits.js +3 -3
  186. package/dist/core/ledger/payer.d.ts +1 -1
  187. package/dist/core/ledger/payer.js +1 -1
  188. package/dist/core/ledger/read.d.ts +14 -4
  189. package/dist/core/ledger/read.js +2 -3
  190. package/dist/core/ledger/totals.d.ts +5 -16
  191. package/dist/core/ledger/totals.js +4 -4
  192. package/dist/core/ledger/types.d.ts +12 -3
  193. package/dist/core/llm/run.js +2 -2
  194. package/dist/core/managed-providers.d.ts +72 -0
  195. package/dist/core/managed-providers.js +1 -0
  196. package/dist/core/model-auth/api-credential-store.d.ts +10 -0
  197. package/dist/core/model-auth/api-credential-store.js +1 -0
  198. package/dist/core/model-auth/daemon-login.d.ts +1 -1
  199. package/dist/core/model-auth/daemon-login.js +1 -1
  200. package/dist/core/model-auth/flows.d.ts +5 -4
  201. package/dist/core/model-auth/flows.js +1 -1
  202. package/dist/core/model-auth/handoff-jti.d.ts +3 -4
  203. package/dist/core/model-auth/handoff-jti.js +1 -1
  204. package/dist/core/model-auth/readiness.js +1 -1
  205. package/dist/core/model-config.d.ts +1 -1
  206. package/dist/core/model-config.js +1 -1
  207. package/dist/core/model-routes-pool.d.ts +14 -0
  208. package/dist/core/model-routes-pool.js +1 -0
  209. package/dist/core/model-routes.d.ts +11 -16
  210. package/dist/core/model-routes.js +1 -1
  211. package/dist/core/peer/ingest.d.ts +2 -0
  212. package/dist/core/peer/ingest.js +1 -1
  213. package/dist/core/peer/stubs.d.ts +8 -0
  214. package/dist/core/peer/stubs.js +1 -1
  215. package/dist/core/permissions/evaluator.d.ts +7 -2
  216. package/dist/core/permissions/evaluator.js +2 -2
  217. package/dist/core/plugin-env.js +1 -1
  218. package/dist/core/pool-credential-store.d.ts +2 -5
  219. package/dist/core/pool-credential-store.js +1 -1
  220. package/dist/core/profiles/app-env-store.d.ts +5 -5
  221. package/dist/core/profiles/app-env-store.js +1 -2
  222. package/dist/core/profiles/default-binding.d.ts +5 -5
  223. package/dist/core/profiles/default-binding.js +1 -1
  224. package/dist/core/profiles/env-store.d.ts +10 -21
  225. package/dist/core/profiles/env-store.js +1 -2
  226. package/dist/core/profiles/manifest.d.ts +14 -18
  227. package/dist/core/profiles/manifest.js +1 -1
  228. package/dist/core/profiles/relocate.js +1 -1
  229. package/dist/core/profiles/select.js +21 -21
  230. package/dist/core/provider-credential.d.ts +1 -1
  231. package/dist/core/provider-credential.js +1 -1
  232. package/dist/core/provider-management.d.ts +3 -4
  233. package/dist/core/provider-management.js +1 -1
  234. package/dist/core/providers/bundles.js +2 -2
  235. package/dist/core/providers/call.d.ts +3 -34
  236. package/dist/core/providers/description.d.ts +34 -0
  237. package/dist/core/providers/description.js +0 -0
  238. package/dist/core/providers/token-cache.d.ts +14 -14
  239. package/dist/core/providers/token-cache.js +1 -1
  240. package/dist/core/review/companion.js +1 -1
  241. package/dist/core/runs/activity.d.ts +1 -1
  242. package/dist/core/runs/activity.js +1 -1
  243. package/dist/core/runs/events.d.ts +7 -5
  244. package/dist/core/runs/events.js +2 -6
  245. package/dist/core/runs/operations.d.ts +1 -1
  246. package/dist/core/runs/operations.js +6 -6
  247. package/dist/core/runtime/bearings.js +1 -1
  248. package/dist/core/runtime/broker/attachments-fetch.d.ts +5 -0
  249. package/dist/core/runtime/broker/attachments-fetch.js +1 -0
  250. package/dist/core/runtime/broker/bash-job-launch.d.ts +4 -2
  251. package/dist/core/runtime/broker/bash-job-launch.js +1 -1
  252. package/dist/core/runtime/broker/boot-state.d.ts +9 -0
  253. package/dist/core/runtime/broker/boot-state.js +1 -0
  254. package/dist/core/runtime/broker/cli-relay.d.ts +2 -1
  255. package/dist/core/runtime/broker/cli-relay.js +1 -1
  256. package/dist/core/runtime/broker/client-registry.d.ts +3 -3
  257. package/dist/core/runtime/broker/daemon-ops.d.ts +10 -1
  258. package/dist/core/runtime/broker/daemon-ops.js +1 -1
  259. package/dist/core/runtime/broker/fault-retry.d.ts +2 -0
  260. package/dist/core/runtime/broker/fault-retry.js +1 -1
  261. package/dist/core/runtime/broker/frame-engine-control.js +1 -1
  262. package/dist/core/runtime/broker/held-deferred-inbox.d.ts +3 -0
  263. package/dist/core/runtime/broker/held-deferred-inbox.js +1 -1
  264. package/dist/core/runtime/broker/job-reporter.d.ts +18 -0
  265. package/dist/core/runtime/broker/job-reporter.js +2 -0
  266. package/dist/core/runtime/broker/model-adapter.d.ts +3 -0
  267. package/dist/core/runtime/broker/model-adapter.js +3 -2
  268. package/dist/core/runtime/broker/signal-stream.js +1 -1
  269. package/dist/core/runtime/broker/spilled-body.d.ts +5 -0
  270. package/dist/core/runtime/broker/spilled-body.js +1 -0
  271. package/dist/core/runtime/broker/summaries-post.d.ts +5 -0
  272. package/dist/core/runtime/broker/summaries-post.js +1 -0
  273. package/dist/core/runtime/broker/tool-groups.d.ts +3 -0
  274. package/dist/core/runtime/broker/tool-groups.js +1 -1
  275. package/dist/core/runtime/broker/turn-replies.d.ts +1 -0
  276. package/dist/core/runtime/broker/turn-replies.js +1 -1
  277. package/dist/core/runtime/broker-persona-guidance.js +2 -2
  278. package/dist/core/runtime/broker.js +1 -1
  279. package/dist/core/runtime/deliver-live.js +1 -1
  280. package/dist/core/runtime/first-boot-rows.d.ts +8 -0
  281. package/dist/core/runtime/first-boot-rows.js +2 -0
  282. package/dist/core/runtime/fleet.d.ts +13 -12
  283. package/dist/core/runtime/front-door-login.js +1 -1
  284. package/dist/core/runtime/front-door.js +6 -6
  285. package/dist/core/runtime/headless-pi.js +1 -1
  286. package/dist/core/runtime/host.d.ts +14 -3
  287. package/dist/core/runtime/host.js +3 -3
  288. package/dist/core/runtime/invocation.d.ts +6 -0
  289. package/dist/core/runtime/kickoff.d.ts +2 -14
  290. package/dist/core/runtime/kickoff.js +17 -19
  291. package/dist/core/runtime/launch-config-worker.js +1 -1
  292. package/dist/core/runtime/launch.js +2 -2
  293. package/dist/core/runtime/model-registry.js +1 -1
  294. package/dist/core/runtime/node-read.d.ts +4 -6
  295. package/dist/core/runtime/node-read.js +3 -3
  296. package/dist/core/runtime/nodes.js +1 -1
  297. package/dist/core/runtime/outcome-document.d.ts +0 -1
  298. package/dist/core/runtime/outcome-document.js +1 -1
  299. package/dist/core/runtime/output-result.d.ts +15 -0
  300. package/dist/core/runtime/output-result.js +5 -0
  301. package/dist/core/runtime/placement.d.ts +17 -1
  302. package/dist/core/runtime/placement.js +1 -1
  303. package/dist/core/runtime/provider-credential-gate.d.ts +6 -2
  304. package/dist/core/runtime/provider-credential-gate.js +2 -2
  305. package/dist/core/runtime/recovery-hold.d.ts +21 -2
  306. package/dist/core/runtime/recovery-hold.js +2 -1
  307. package/dist/core/runtime/recycle.js +1 -1
  308. package/dist/core/runtime/reset.js +1 -1
  309. package/dist/core/runtime/revive.d.ts +2 -2
  310. package/dist/core/runtime/revive.js +2 -2
  311. package/dist/core/runtime/runner/blaxel/client.d.ts +81 -0
  312. package/dist/core/runtime/runner/blaxel/client.js +1 -0
  313. package/dist/core/runtime/runner/blaxel/image-build.d.ts +74 -0
  314. package/dist/core/runtime/runner/blaxel/image-build.js +5 -0
  315. package/dist/core/runtime/runner/blaxel/runner.d.ts +41 -0
  316. package/dist/core/runtime/runner/blaxel/runner.js +3 -0
  317. package/dist/core/runtime/runner/index.d.ts +8 -0
  318. package/dist/core/runtime/runner/index.js +1 -0
  319. package/dist/core/runtime/runner/process.d.ts +11 -0
  320. package/dist/core/runtime/runner/process.js +1 -0
  321. package/dist/core/runtime/runner/types.d.ts +95 -0
  322. package/dist/core/runtime/runner/types.js +0 -0
  323. package/dist/core/runtime/situational-context.d.ts +3 -6
  324. package/dist/core/runtime/situational-context.js +2 -2
  325. package/dist/core/runtime/situational-live.js +1 -1
  326. package/dist/core/runtime/spawn-env-rows.d.ts +5 -0
  327. package/dist/core/runtime/spawn-env-rows.js +1 -0
  328. package/dist/core/runtime/spawn-env.d.ts +4 -18
  329. package/dist/core/runtime/spawn-env.js +1 -1
  330. package/dist/core/runtime/spawn.js +2 -2
  331. package/dist/core/runtime/stop-guard.d.ts +1 -1
  332. package/dist/core/runtime/stop-guard.js +2 -8
  333. package/dist/core/runtime/structured-output.d.ts +0 -67
  334. package/dist/core/runtime/structured-output.js +3 -8
  335. package/dist/core/runtime/tmux-driver.d.ts +8 -0
  336. package/dist/core/runtime/tmux-driver.js +6 -6
  337. package/dist/core/runtime/view-socket.d.ts +20 -0
  338. package/dist/core/runtime/view-socket.js +3 -1
  339. package/dist/core/runtime/warm-pool.d.ts +13 -1
  340. package/dist/core/runtime/warm-pool.js +1 -1
  341. package/dist/core/scoped-state/legacy-profiles.d.ts +11 -0
  342. package/dist/core/scoped-state/legacy-profiles.js +1 -0
  343. package/dist/core/scoped-state/materialized-plugins.d.ts +18 -0
  344. package/dist/core/scoped-state/materialized-plugins.js +1 -0
  345. package/dist/core/scoped-state/migrate.d.ts +8 -1
  346. package/dist/core/scoped-state/migrate.js +3 -3
  347. package/dist/core/scoped-state/person-rows.d.ts +10 -0
  348. package/dist/core/scoped-state/person-rows.js +6 -0
  349. package/dist/core/scoped-state/person-store.d.ts +72 -0
  350. package/dist/core/scoped-state/person-store.js +1 -0
  351. package/dist/core/secrets/blaxel-launch.d.ts +24 -0
  352. package/dist/core/secrets/blaxel-launch.js +4 -0
  353. package/dist/core/secrets/blaxel-register.d.ts +2 -0
  354. package/dist/core/secrets/blaxel-register.js +1 -0
  355. package/dist/core/secrets/env-rows.d.ts +17 -0
  356. package/dist/core/secrets/env-rows.js +4 -0
  357. package/dist/core/secrets/environment-owner.d.ts +8 -0
  358. package/dist/core/secrets/environment-owner.js +1 -0
  359. package/dist/core/secrets/environment-secrets.d.ts +14 -0
  360. package/dist/core/secrets/environment-secrets.js +1 -0
  361. package/dist/core/secrets/index.d.ts +2 -0
  362. package/dist/core/secrets/index.js +1 -0
  363. package/dist/core/secrets/key.d.ts +3 -0
  364. package/dist/core/secrets/key.js +2 -0
  365. package/dist/core/secrets/seal.d.ts +18 -0
  366. package/dist/core/secrets/seal.js +1 -0
  367. package/dist/core/spaces/mounts.d.ts +1 -1
  368. package/dist/core/spaces/mounts.js +1 -1
  369. package/dist/core/spaces/process.d.ts +1 -1
  370. package/dist/core/spaces/stop.d.ts +7 -5
  371. package/dist/core/spaces/stop.js +2 -2
  372. package/dist/core/spaces/users.d.ts +2 -1
  373. package/dist/core/spaces/users.js +1 -1
  374. package/dist/core/storage/attachments.d.ts +11 -8
  375. package/dist/core/storage/attachments.js +1 -1
  376. package/dist/core/storage/tables.d.ts +4 -0
  377. package/dist/core/storage/tables.js +3 -3
  378. package/dist/core/subscription-state.d.ts +34 -72
  379. package/dist/core/subscription-state.js +1 -4
  380. package/dist/core/substrate/delivery/plan.js +1 -1
  381. package/dist/core/user-settings.js +1 -1
  382. package/dist/daemon/api/app-listener.js +10 -10
  383. package/dist/daemon/api/auth/person-bearer.d.ts +14 -0
  384. package/dist/daemon/api/auth/person-bearer.js +1 -0
  385. package/dist/daemon/api/bridge.js +4 -4
  386. package/dist/daemon/api/document-listener.js +3 -3
  387. package/dist/daemon/api/environment-of.d.ts +18 -0
  388. package/dist/daemon/api/environment-of.js +5 -0
  389. package/dist/daemon/api/handlers/analytics.js +1 -1
  390. package/dist/daemon/api/handlers/app-profiles.d.ts +2 -2
  391. package/dist/daemon/api/handlers/app-profiles.js +1 -1
  392. package/dist/daemon/api/handlers/bash-jobs.js +1 -1
  393. package/dist/daemon/api/handlers/bash.js +1 -1
  394. package/dist/daemon/api/handlers/broker-ops.d.ts +3 -0
  395. package/dist/daemon/api/handlers/broker-ops.js +1 -1
  396. package/dist/daemon/api/handlers/broker-recovery.js +1 -1
  397. package/dist/daemon/api/handlers/broker-signals.d.ts +0 -3
  398. package/dist/daemon/api/handlers/broker-signals.js +2 -2
  399. package/dist/daemon/api/handlers/canvas.js +4 -4
  400. package/dist/daemon/api/handlers/charges.d.ts +1 -1
  401. package/dist/daemon/api/handlers/charges.js +1 -1
  402. package/dist/daemon/api/handlers/conversations.d.ts +2 -0
  403. package/dist/daemon/api/handlers/conversations.js +3 -0
  404. package/dist/daemon/api/handlers/crons.js +1 -1
  405. package/dist/daemon/api/handlers/daemon.js +1 -1
  406. package/dist/daemon/api/handlers/delivery.d.ts +1 -1
  407. package/dist/daemon/api/handlers/delivery.js +1 -1
  408. package/dist/daemon/api/handlers/docs.js +1 -1
  409. package/dist/daemon/api/handlers/exec.js +1 -1
  410. package/dist/daemon/api/handlers/execution-start.d.ts +2 -0
  411. package/dist/daemon/api/handlers/execution-start.js +2 -0
  412. package/dist/daemon/api/handlers/files.js +1 -1
  413. package/dist/daemon/api/handlers/git-credential.d.ts +11 -0
  414. package/dist/daemon/api/handlers/git-credential.js +1 -0
  415. package/dist/daemon/api/handlers/hook-exec.d.ts +1 -1
  416. package/dist/daemon/api/handlers/hook-exec.js +1 -1
  417. package/dist/daemon/api/handlers/human-requests.js +1 -1
  418. package/dist/daemon/api/handlers/inbox.js +1 -1
  419. package/dist/daemon/api/handlers/messages.js +1 -1
  420. package/dist/daemon/api/handlers/model-config.js +1 -1
  421. package/dist/daemon/api/handlers/modelauth.d.ts +1 -1
  422. package/dist/daemon/api/handlers/modelauth.js +1 -1
  423. package/dist/daemon/api/handlers/node-events.d.ts +6 -0
  424. package/dist/daemon/api/handlers/node-events.js +3 -3
  425. package/dist/daemon/api/handlers/node-outcomes.js +1 -1
  426. package/dist/daemon/api/handlers/node-records.js +3 -2
  427. package/dist/daemon/api/handlers/nodes.js +1 -1
  428. package/dist/daemon/api/handlers/objects.js +3 -3
  429. package/dist/daemon/api/handlers/profiles.js +1 -1
  430. package/dist/daemon/api/handlers/providers.js +1 -1
  431. package/dist/daemon/api/handlers/reports.js +9 -13
  432. package/dist/daemon/api/handlers/reviews.js +1 -1
  433. package/dist/daemon/api/handlers/runs.js +1 -1
  434. package/dist/daemon/api/handlers/settings.d.ts +2 -0
  435. package/dist/daemon/api/handlers/settings.js +1 -0
  436. package/dist/daemon/api/handlers/structured-requests.js +1 -1
  437. package/dist/daemon/api/handlers/vendor-status.d.ts +1 -1
  438. package/dist/daemon/api/handlers/vendor-status.js +1 -1
  439. package/dist/daemon/api/handlers/worktree.js +1 -1
  440. package/dist/daemon/api/map.d.ts +2 -2
  441. package/dist/daemon/api/map.js +2 -2
  442. package/dist/daemon/api/operations.js +1 -1
  443. package/dist/daemon/api/reader.d.ts +8 -1
  444. package/dist/daemon/api/reader.js +1 -1
  445. package/dist/daemon/api/router.d.ts +4 -2
  446. package/dist/daemon/api/router.js +1 -1
  447. package/dist/daemon/api/server.d.ts +15 -3
  448. package/dist/daemon/api/server.js +1 -1
  449. package/dist/daemon/api/vendor-signin.d.ts +2 -1
  450. package/dist/daemon/api/vendor-signin.js +20 -20
  451. package/dist/daemon/api/viewer-presence.d.ts +20 -6
  452. package/dist/daemon/api/viewer-presence.js +2 -1
  453. package/dist/daemon/broker-signals/bus.d.ts +48 -11
  454. package/dist/daemon/broker-signals/bus.js +1 -1
  455. package/dist/daemon/cron/armed-context.js +1 -1
  456. package/dist/daemon/cron/lease-recovery.js +1 -1
  457. package/dist/daemon/cron/live-runs.d.ts +6 -0
  458. package/dist/daemon/cron/live-runs.js +1 -1
  459. package/dist/daemon/cron/run-socket.js +1 -1
  460. package/dist/daemon/cron-run.js +2 -2
  461. package/dist/daemon/crtrd-cli.js +1 -1
  462. package/dist/daemon/crtrd.d.ts +10 -0
  463. package/dist/daemon/crtrd.js +18 -17
  464. package/dist/daemon/delivery/run-delivery.d.ts +1 -1
  465. package/dist/daemon/delivery/run-delivery.js +1 -1
  466. package/dist/daemon/due-sources.d.ts +1 -1
  467. package/dist/daemon/due-sources.js +1 -1
  468. package/dist/daemon/due.js +7 -6
  469. package/dist/daemon/fleet.d.ts +6 -6
  470. package/dist/daemon/fleet.js +9 -9
  471. package/dist/daemon/park-activity.d.ts +2 -1
  472. package/dist/daemon/park-activity.js +1 -1
  473. package/dist/daemon/park-pending.d.ts +20 -13
  474. package/dist/daemon/park-pending.js +3 -1
  475. package/dist/daemon/profile-delete.js +1 -1
  476. package/dist/daemon/reconcilers/bash-deadline.d.ts +15 -10
  477. package/dist/daemon/reconcilers/bash-deadline.js +1 -3
  478. package/dist/daemon/reconcilers/broker-supervision.d.ts +13 -3
  479. package/dist/daemon/reconcilers/broker-supervision.js +4 -4
  480. package/dist/daemon/reconcilers/cron-lane.d.ts +0 -1
  481. package/dist/daemon/reconcilers/cron-lane.js +1 -1
  482. package/dist/daemon/reconcilers/fault-retry-backstop.d.ts +2 -9
  483. package/dist/daemon/reconcilers/fault-retry-backstop.js +1 -1
  484. package/dist/daemon/reconcilers/grant-effects.d.ts +8 -0
  485. package/dist/daemon/reconcilers/grant-effects.js +1 -0
  486. package/dist/daemon/reconcilers/grove-instance-sweep.d.ts +1 -3
  487. package/dist/daemon/reconcilers/grove-instance-sweep.js +1 -1
  488. package/dist/daemon/reconcilers/ledger-r2.d.ts +27 -0
  489. package/dist/daemon/reconcilers/ledger-r2.js +7 -0
  490. package/dist/daemon/reconcilers/ledger-sandbox.d.ts +55 -0
  491. package/dist/daemon/reconcilers/ledger-sandbox.js +9 -0
  492. package/dist/daemon/reconcilers/ledger-storage.d.ts +22 -0
  493. package/dist/daemon/reconcilers/ledger-storage.js +5 -0
  494. package/dist/daemon/reconcilers/live-obligation.d.ts +3 -2
  495. package/dist/daemon/reconcilers/live-obligation.js +1 -1
  496. package/dist/daemon/reconcilers/managed-worktree-sweep.d.ts +0 -5
  497. package/dist/daemon/reconcilers/managed-worktree-sweep.js +1 -1
  498. package/dist/daemon/reconcilers/node-deadline.d.ts +2 -1
  499. package/dist/daemon/reconcilers/node-deadline.js +1 -1
  500. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.d.ts +1 -2
  501. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +1 -1
  502. package/dist/daemon/reconcilers/node-lifecycle/remote-wake.d.ts +31 -0
  503. package/dist/daemon/reconcilers/node-lifecycle/remote-wake.js +1 -0
  504. package/dist/daemon/reconcilers/node-lifecycle/requested-stops.d.ts +5 -0
  505. package/dist/daemon/reconcilers/node-lifecycle/requested-stops.js +2 -0
  506. package/dist/daemon/reconcilers/node-lifecycle/tick.js +1 -1
  507. package/dist/daemon/reconcilers/pending-review-submit.js +1 -1
  508. package/dist/daemon/reconcilers/recipe-image.d.ts +49 -0
  509. package/dist/daemon/reconcilers/recipe-image.js +7 -0
  510. package/dist/daemon/reconcilers/row-claim.d.ts +19 -0
  511. package/dist/daemon/reconcilers/row-claim.js +2 -0
  512. package/dist/daemon/reconcilers/storage-due-work.d.ts +18 -3
  513. package/dist/daemon/reconcilers/storage-due-work.js +2 -1
  514. package/dist/daemon/reconcilers/storage-maintenance.d.ts +5 -13
  515. package/dist/daemon/reconcilers/storage-maintenance.js +1 -1
  516. package/dist/daemon/reconcilers/structured-request-lane.js +1 -1
  517. package/dist/daemon/reconcilers/throttled-lane.d.ts +11 -0
  518. package/dist/daemon/reconcilers/throttled-lane.js +1 -0
  519. package/dist/daemon/review/finish.js +1 -1
  520. package/dist/daemon/run-notices.d.ts +3 -0
  521. package/dist/daemon/run-notices.js +1 -1
  522. package/dist/daemon/runs/append-section.d.ts +4 -1
  523. package/dist/daemon/runs/append-section.js +2 -2
  524. package/dist/daemon/runs/observer.d.ts +3 -2
  525. package/dist/daemon/runs/observer.js +3 -2
  526. package/dist/daemon/runs/view-transport.d.ts +19 -0
  527. package/dist/daemon/runs/view-transport.js +1 -0
  528. package/dist/daemon/structured-requests/boot.d.ts +2 -1
  529. package/dist/daemon/structured-requests/boot.js +1 -1
  530. package/dist/daemon/structured-requests/effects.d.ts +4 -3
  531. package/dist/daemon/structured-requests/effects.js +1 -1
  532. package/dist/daemon/structured-requests/notifier.d.ts +2 -0
  533. package/dist/daemon/structured-requests/notifier.js +1 -1
  534. package/dist/daemon/structured-requests/sidecar.d.ts +4 -3
  535. package/dist/daemon/structured-requests/sidecar.js +1 -1
  536. package/dist/launcher/validate.js +1 -1
  537. package/dist/migrations/004-canvas-documents/index.js +2 -2
  538. package/dist/pi-extensions/broker-local.d.ts +3 -3
  539. package/dist/pi-extensions/broker-local.js +1 -2
  540. package/dist/pi-extensions/canvas-bash-valve.js +6 -6
  541. package/dist/pi-extensions/canvas-context-intro.js +2 -2
  542. package/dist/pi-extensions/canvas-elapsed.js +2 -2
  543. package/dist/pi-extensions/canvas-goal-capture.js +1 -1
  544. package/dist/pi-extensions/canvas-inbox-watcher.js +2 -2
  545. package/dist/pi-extensions/canvas-review-boundary.d.ts +11 -1
  546. package/dist/pi-extensions/canvas-review-boundary.js +3 -3
  547. package/dist/pi-extensions/canvas-stophook.js +1 -1
  548. package/dist/pi-extensions/canvas-structured-output.d.ts +5 -3
  549. package/dist/pi-extensions/canvas-structured-output.js +6 -11
  550. package/dist/pi-extensions/canvas-tool-guide.d.ts +9 -2
  551. package/dist/pi-extensions/canvas-tool-guide.js +6 -6
  552. package/dist/shared/inbox-entry-body.js +7 -7
  553. package/dist/types.d.ts +12 -0
  554. package/docs/sdk/release-notes.md +4 -0
  555. package/package.json +7 -4
  556. package/packages/crouter-identity/package.json +1 -1
  557. package/runtime.lock.json +13 -13
  558. package/scripts/git-credential-crtr +33 -0
  559. package/scripts/sandbox-bootstrap.sh +54 -0
  560. package/scripts/sandbox-start.mjs +320 -0
  561. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +0 -78
  562. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +0 -1179
  563. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +0 -1406
  564. package/dist/core/conversation-store/file-store.d.ts +0 -37
  565. package/dist/core/conversation-store/file-store.js +0 -9
  566. package/dist/core/conversation-store/listener.d.ts +0 -4
  567. package/dist/core/conversation-store/listener.js +0 -8
  568. package/dist/core/scoped-state/profiles.d.ts +0 -27
  569. package/dist/core/scoped-state/profiles.js +0 -1
  570. package/dist/core/scoped-state/providers.d.ts +0 -24
  571. package/dist/core/scoped-state/providers.js +0 -1
  572. package/dist/core/scoped-state/settings.d.ts +0 -28
  573. package/dist/core/scoped-state/settings.js +0 -1
@@ -1 +1 @@
1
- var A=Object.defineProperty;var o=(a,t)=>A(a,"name",{value:t,configurable:!0});import{filterAsync as g}from"../../core/async-array.js";import{listPendingProviderRetries as y,readLiveTurn as _}from"../../core/canvas/recovery.js";import{probeOnline as I}from"../../core/runtime/connectivity.js";import{nextFaultRetry as b}from"../../core/runtime/fault-recovery.js";import{emitEvent as f}from"../../core/events/emit.js";import{operationIdContext as p}from"../../core/events/operation-id.js";import{isSafeNodeId as R}from"../../core/canvas/paths.js";import{brokerSignalStreams as v}from"../api/handlers/broker-signals.js";const h=2*6e4,S=15e3;function m(a){const{fault:t}=a;if(t.retry.nextAt!==void 0){const i=Date.parse(t.retry.nextAt);return Number.isFinite(i)?i:null}const n=Date.parse(a.updatedAt),s=b(t,n);return Number.isFinite(n)&&s!==null?n+s.delayMs:null}o(m,"dueAtMs");class D{static{o(this,"FaultRetryBackstopReconciler")}deps;lastScanAt=Number.NEGATIVE_INFINITY;scanning=null;nudgedAt=new Map;constructor(t={}){this.deps=t}run(t,n){if(this.scanning!==null)return this.scanning;if(!n.lifecycle.acceptsDetachedWork()||t-this.lastScanAt<S)return Promise.resolve();this.lastScanAt=t;const s=this.scan(t,n).catch(i=>{p.fresh(()=>{f({level:"warn",event:"fault_retry_backstop.scan_failed",error:i})})}).finally(()=>{this.scanning=null});return this.scanning=s,n.lifecycle.registerDetached(s),s}async scan(t,n){const s=await(this.deps.list??y)(),i=new Set(s.map(e=>e.fault.faultId));for(const e of this.nudgedAt.keys())i.has(e)||this.nudgedAt.delete(e);const d=this.deps.busy??(async e=>(await _(e))?.busy===!0),u=await g(s,async e=>{if(!n.fleet.has(e.nodeId)||await d(e.nodeId))return!1;const r=m(e);if(r===null||t<r+h)return!1;const l=this.nudgedAt.get(e.fault.faultId);return l===void 0||t-l>=h});if(u.length===0)return;let c;for(const e of u){if(e.fault.kind==="connection"&&(c??=await(this.deps.probeOnline??(()=>I()))(),!c)||!n.fleet.has(e.nodeId)||await d(e.nodeId))continue;this.nudgedAt.set(e.fault.faultId,t);const r=await(this.deps.nudge??(async l=>await v.nudgeFaultRetry(l)))(e.nodeId);p.fresh(()=>{f({level:"info",event:"fault_retry_backstop.nudged",...R(e.nodeId)?{node_id:e.nodeId}:{},fields:{kind:e.fault.kind,since:e.fault.since,delivered:r,overdue_ms:t-(m(e)??t)}})})}}}export{S as FAULT_RETRY_SCAN_INTERVAL_MS,h as FAULT_RETRY_STALE_GRACE_MS,D as FaultRetryBackstopReconciler,m as dueAtMs};
1
+ var _=Object.defineProperty;var o=(i,t)=>_(i,"name",{value:t,configurable:!0});import{filterAsync as y}from"../../core/async-array.js";import{withCanvasWrite as h}from"../../core/canvas/db.js";import{claimFaultNudge as A,listPendingProviderRetries as g,readLiveTurn as b}from"../../core/canvas/recovery.js";import{probeOnline as E}from"../../core/runtime/connectivity.js";import{nextFaultRetry as I}from"../../core/runtime/fault-recovery.js";import{emitEvent as c}from"../../core/events/emit.js";import{operationIdContext as f}from"../../core/events/operation-id.js";import{isSafeNodeId as R}from"../../core/canvas/paths.js";import{runThrottledLane as v}from"./throttled-lane.js";const l=2*6e4,L=15e3;function p(i){const{fault:t}=i;if(t.retry.nextAt!==void 0){const a=Date.parse(t.retry.nextAt);return Number.isFinite(a)?a:null}const n=Date.parse(i.updatedAt),r=I(t,n);return Number.isFinite(n)&&r!==null?n+r.delayMs:null}o(p,"dueAtMs");const S=6e4,T="fault-retry-backstop";class U{static{o(this,"FaultRetryBackstopReconciler")}deps;constructor(t={}){this.deps=t}run(t,n){if(!n.lifecycle.acceptsDetachedWork())return Promise.resolve();const r=v(T,"",t,L,S,()=>this.scan(t,n)).then(()=>{}).catch(a=>{f.fresh(()=>{c({level:"warn",event:"fault_retry_backstop.scan_failed",error:a})})});return n.lifecycle.registerDetached(r),r}async scan(t,n){const r=await(this.deps.list??g)(),a=this.deps.busy??(async e=>(await b(e))?.busy===!0),d=await y(r,async e=>{if(!await n.fleet.has(e.nodeId)||await a(e.nodeId))return!1;const s=p(e);return s===null||t<s+l?!1:e.nudgedAt===null||t-Date.parse(e.nudgedAt)>=l});if(d.length===0)return;let u;for(const e of d)e.fault.kind==="connection"&&(u??=await(this.deps.probeOnline??(()=>E()))(),!u)||!await n.fleet.has(e.nodeId)||await a(e.nodeId)||!await h(async m=>await A(e.fault.faultId,t,l)?(await m.prepare("SELECT pg_notify(?, ?)").get("crtr_signal",`fault_retry:${e.nodeId}`),!0):!1)||f.fresh(()=>{c({level:"info",event:"fault_retry_backstop.nudged",...R(e.nodeId)?{node_id:e.nodeId}:{},fields:{kind:e.fault.kind,since:e.fault.since,overdue_ms:t-(p(e)??t)}})})}}export{L as FAULT_RETRY_SCAN_INTERVAL_MS,l as FAULT_RETRY_STALE_GRACE_MS,U as FaultRetryBackstopReconciler,p as dueAtMs};
@@ -0,0 +1,8 @@
1
+ export interface GrantEffectsRoles {
2
+ serves: boolean;
3
+ runsLanes: boolean;
4
+ }
5
+ /** Do this process's part of whatever changed grant `grantId`, from its row as it reads now. Idempotent. */
6
+ export declare function applyGrantEffects(roles: GrantEffectsRoles, grantId: string): Promise<void>;
7
+ /** Subscribe this process to `grant:<id>` signals. Returns the unsubscribe. */
8
+ export declare function startGrantEffects(roles: GrantEffectsRoles): Promise<() => void>;
@@ -0,0 +1 @@
1
+ var w=Object.defineProperty;var a=(t,e)=>w(t,"name",{value:e,configurable:!0});import{openDb as d}from"../../core/canvas/db.js";import{endLiveExecution as _,liveExecutionForNode as v}from"../../core/canvas/executions.js";import{emitEvent as s}from"../../core/events/emit.js";import{afterRevocationStop as E}from"../../core/grants/on-sync.js";import{allGrants as u,grantIdsWhere as S,grantStanding as m,mirrorOnly as y}from"../../core/grants/mirror.js";import{removeApp as h}from"../../core/grants/remove.js";import{wakeAllGrantWaiters as g,wakeGrantWaiters as k}from"../../core/grants/watch.js";import{discardOrphanedTokens as p,discardTokensForGrant as x}from"../../core/providers/token-cache.js";import{runnerForExecution as N}from"../../core/runtime/runner/index.js";import{closeGrantStreams as O,heldGrantIds as T}from"../../core/runs/events.js";import{stopNodeSandboxes as b}from"../../core/spaces/stop.js";import{brokerSignalBus as L}from"../broker-signals/bus.js";import{withRowClaim as R}from"./row-claim.js";const D=6e4;function c(t,e,r){s({level:"error",event:`daemon.grants.${t}`,error:e,fields:r})}a(c,"failed");function G(t,e,r){try{const o=e.sub===null||e.grantee===null?p(r):x({sub:e.sub,grantee:e.grantee,active:e.standing==="active"},r);o.tokens>0&&s({level:"info",event:"daemon.grants.outbound_tokens_discarded",fields:{grant:t,tokens:o.tokens}})}catch(o){c("outbound_tokens_discard_failed",o,{grant:t})}}a(G,"discardTokens");async function A(t,e){if(k(t),e.standing==="inactive"){const r=O(t);r>0&&s({level:"info",event:"daemon.grants.streams_closed",fields:{grant:t,grantee:e.grantee,streams:r}})}try{await(await import("../../core/providers/bundles.js")).syncProviderBundles()}catch(r){s({level:"error",event:"daemon.providers.bundle_sync_failed",error:r})}}a(A,"serveEffects");async function C(t,e){const r=await R("nodes",t,D,async o=>{const n=await v(t);if(n===null)return;await o.assertOwned(),s({level:"info",event:"daemon.grants.node_stop",node_id:t,fields:{grant:e,execution_id:n.executionId}});const i=a(async()=>{await o.assertOwned(),await _(t,{reason:"permission_revoked"}),await E(t)},"finish");if(n.runner==="process"){const l=await b(t,"permission_revoked",{holdRecovery:!0,after:i});if(!l.confirmed)throw l.error??new Error(`stop of ${t} was not confirmed`);return}const f=await N(n.runner).stop(n,{reason:"permission_revoked"});if(!f.confirmed)throw f.error??new Error(`stop of ${t} was not confirmed`);await i()})}a(C,"stopRevokedNode");async function P(t,e){if(!(e.grantee!==null&&y({grantee:e.grantee}))){if(e.standing==="inactive"){const r=await(await d()).prepare("SELECT DISTINCT n.node_id FROM nodes n JOIN executions e ON e.node_id = n.node_id AND e.state != 'exited' WHERE n.grant_id = ?").all(t);for(const{node_id:o}of r)try{await C(o,t)}catch(n){c("node_stop_failed",n,{grant:t,node_id:o})}}if(e.status==="removed"&&e.removal_done_at===null&&e.grantee!==null&&e.sub!==null)try{await h(e.grantee,e.sub),await(await d()).prepare("UPDATE grants SET removal_done_at = ? WHERE id = ? AND removal_done_at IS NULL").run(new Date().toISOString(),t)}catch(r){c("removal_failed",r,{grant:t,grantee:e.grantee})}}}a(P,"laneEffects");async function W(t,e){const r=await m(e);G(e,r,await u()),await Promise.all([t.serves?A(e,r):void 0,t.runsLanes&&r.standing!=="lagging"?P(e,r):void 0])}a(W,"applyGrantEffects");async function F(t){const e=new Set;if(t.serves)for(const o of T())e.add(o);if(t.runsLanes){const o=await(await d()).prepare("SELECT DISTINCT n.grant_id FROM nodes n JOIN executions e ON e.node_id = n.node_id AND e.state != 'exited' WHERE n.grant_id IS NOT NULL").all();for(const{grant_id:n}of o)e.add(n)}const r=[];for(const o of e)(await m(o)).status===null&&r.push(o);return r}a(F,"goneGrantIds");async function Z(t){const e=new Map,r=a(n=>{const i=(e.get(n)??Promise.resolve()).then(()=>W(t,n)).catch(f=>c("effects_failed",f,{grant:n})).finally(()=>{e.get(n)===i&&e.delete(n)});return e.set(n,i),i},"run"),o=a(async()=>{try{t.serves&&g(),p(await u());const n=new Set(await S(`status <> 'active' OR expires_at <= '${new Date().toISOString()}'`));for(const i of await F(t))n.add(i);await Promise.all([...n].map(r))}catch(n){c("catch_up_failed",n,{})}},"catchUp");return await L.subscribe(n=>{r(n)},{kinds:["grant"],onListenStart:a(()=>{o()},"onListenStart")})}a(Z,"startGrantEffects");export{W as applyGrantEffects,Z as startGrantEffects};
@@ -25,9 +25,7 @@ export interface GroveInstanceSweepContext {
25
25
  }
26
26
  export declare class GroveInstanceSweepReconciler {
27
27
  private readonly operations;
28
- private lastSweepAt;
29
- private inFlight;
30
- private cursor;
28
+ /** Failed uproots wait here; a process that takes the lane next retries them at once, which is harmless (uproot is idempotent). */
31
29
  private readonly retryAfter;
32
30
  constructor(operations?: GroveInstanceSweepOperations);
33
31
  run(now: number, ctx: GroveInstanceSweepContext): void;
@@ -1 +1 @@
1
- var A=Object.defineProperty;var c=(e,t)=>A(e,"name",{value:t,configurable:!0});import{spawn as S}from"node:child_process";import{statSync as I}from"node:fs";import{delimiter as T,join as j}from"node:path";import{getRow as G,listNodes as R}from"../../core/canvas/index.js";import{isSafeNodeId as w}from"../../core/canvas/paths.js";import{captureLivenessSnapshotAsync as b,recordedPidLiveness as x}from"../../core/canvas/pid.js";import{emitEvent as p}from"../../core/events/emit.js";import{operationIdContext as h}from"../../core/events/operation-id.js";import{isParked as C}from"../../core/runtime/lifecycle.js";const F=60*1e3,P=3,L=300*1e3,E=["done","dead","canceled"];function _(e){return E.includes(e.status)&&!C(e)}c(_,"isClosed");function M(){const e=process.env.PATH;if(e===void 0)return null;for(const t of e.split(T)){const i=j(t===""?process.cwd():t,"grove");try{const r=I(i);if(r.isFile()&&(r.mode&73)!==0)return i}catch{}}return null}c(M,"resolveGroveBinary");function O(e,t){return new Promise(i=>{let r;try{r=S(e,[...t],{stdio:["ignore","pipe","pipe"]})}catch(n){i({code:null,signal:null,stdout:"",stderr:"",error:n instanceof Error?n:new Error(String(n))});return}let a="",s="",l=!1;const u=c(n=>{l||(l=!0,i(n))},"settle");r.stdout.on("data",n=>{a+=n.toString()}),r.stderr.on("data",n=>{s+=n.toString()}),r.on("error",n=>u({code:null,signal:null,stdout:a,stderr:s,error:n})),r.on("close",(n,f)=>u({code:n,signal:f,stdout:a,stderr:s}))})}c(O,"executeGrove");const V={resolveBinary:M,listTerminalNodes:c(async()=>await R({status:[...E]}),"listTerminalNodes"),getNodeRow:G,execute:O,captureLiveness:b};function B(e){let t;try{t=JSON.parse(e)}catch{return null}if(typeof t!="object"||t===null||!Array.isArray(t.projects))return null;const i=[];for(const r of t.projects){if(typeof r!="object"||r===null)continue;const a=r.name,s=r.instances;if(!(typeof a!="string"||!Array.isArray(s)))for(const l of s){if(typeof l!="object"||l===null)continue;const u=l.name,n=l.slot,f=l.spec?.labels,m=typeof f=="object"&&f!==null?f.owner:void 0;typeof u=="string"&&typeof n=="number"&&typeof m=="string"&&i.push({project:a,name:u,slot:n,owner:m})}}return i}c(B,"instancesFromInventory");function k(e,t,i){return _(e)&&!t.isClaimed(e.node_id)&&e.frozen_at===null&&x(e.pi_pid,e.pi_pid_identity,i)==="dead"}c(k,"engineIsGone");async function D(e,t,i){if(t.isClaimed(e.node_id))return!1;const r=await i(e.node_id);return r!==null&&_(r)&&r.frozen_at===null&&r.pi_pid===e.pi_pid&&r.pi_pid_identity===e.pi_pid_identity}c(D,"stillEligible");function N(e){if(e.error!==void 0)return e.error;const t=e.stderr.trim()||e.stdout.trim()||`Grove exited with ${e.signal??e.code??"an unknown status"}`;return new Error(t)}c(N,"executionError");function g(e){return`${e.project}/${e.name}`}c(g,"instanceKey");class X{static{c(this,"GroveInstanceSweepReconciler")}operations;lastSweepAt=Number.NEGATIVE_INFINITY;inFlight=!1;cursor=0;retryAfter=new Map;constructor(t=V){this.operations=t}run(t,i){if(this.inFlight||t-this.lastSweepAt<F||!i.lifecycle.acceptsDetachedWork())return;this.lastSweepAt=t,this.inFlight=!0;const r=Promise.resolve().then(()=>this.sweep(t,i.fleet)).finally(()=>{this.inFlight=!1});i.lifecycle.registerDetached(r)}async sweep(t,i){const r=this.operations.resolveBinary();if(r===null)return;let a;try{a=(await this.operations.listTerminalNodes()).filter(_)}catch(o){h.fresh(()=>{p({level:"error",event:"grove.sweep.scan_failed",error:o})});return}if(a.length===0)return;const s=await this.operations.execute(r,["list","--json"]);if(s.error!==void 0||s.code!==0||s.signal!==null){h.fresh(()=>{p({level:"error",event:"grove.sweep.list_failed",error:N(s)})});return}const l=B(s.stdout);if(l===null){h.fresh(()=>{p({level:"error",event:"grove.sweep.list_failed",error:new Error("grove list --json returned invalid inventory JSON")})});return}const u=new Map(a.map(o=>[o.node_id,o])),n=l.filter(o=>u.get(o.owner)!==void 0&&(this.retryAfter.get(g(o))??Number.NEGATIVE_INFINITY)<=t).sort((o,d)=>o.project.localeCompare(d.project)||o.slot-d.slot||o.name.localeCompare(d.name));if(n.length===0)return;this.cursor>=n.length&&(this.cursor=0);const f=Math.min(P,n.length),m=Array.from({length:f},(o,d)=>n[(this.cursor+d)%n.length]);this.cursor=(this.cursor+f)%n.length;const v=await this.operations.captureLiveness();if(v!==null)for(const o of m){const d=u.get(o.owner);if(!k(d,i,v)||!await D(d,i,this.operations.getNodeRow))continue;const y=await this.operations.execute(r,["uproot",g(o),"--force"]);if(y.error===void 0&&y.code===0&&y.signal===null){this.retryAfter.delete(g(o)),h.fresh(()=>{p({level:"info",event:"grove.sweep.reconciled",outcome:"succeeded",...w(o.owner)?{node_id:o.owner}:{fields:{affected_node_id:o.owner}}})});continue}this.retryAfter.set(g(o),t+L),h.fresh(()=>{p({level:"error",event:"grove.sweep.failed",outcome:"failed",...w(o.owner)?{node_id:o.owner}:{fields:{affected_node_id:o.owner}},error:N(y)})})}}}export{P as GROVE_INSTANCE_SWEEP_BUDGET,F as GROVE_INSTANCE_SWEEP_INTERVAL_MS,L as GROVE_INSTANCE_SWEEP_RETRY_DELAY_MS,X as GroveInstanceSweepReconciler};
1
+ var I=Object.defineProperty;var l=(e,t)=>I(e,"name",{value:t,configurable:!0});import{spawn as T}from"node:child_process";import{statSync as j}from"node:fs";import{delimiter as R,join as b}from"node:path";import{getRow as G,listNodes as C}from"../../core/canvas/index.js";import{isSafeNodeId as E}from"../../core/canvas/paths.js";import{captureLivenessSnapshotAsync as x,recordedPidLiveness as L}from"../../core/canvas/pid.js";import{emitEvent as m}from"../../core/events/emit.js";import{operationIdContext as y}from"../../core/events/operation-id.js";import{isParked as P}from"../../core/runtime/lifecycle.js";import{runThrottledLane as M}from"./throttled-lane.js";const O=60*1e3,V=3,B=300*1e3,W=300*1e3,N=["done","dead","canceled"];function g(e){return N.includes(e.status)&&!P(e)}l(g,"isClosed");function k(){const e=process.env.PATH;if(e===void 0)return null;for(const t of e.split(R)){const o=b(t===""?process.cwd():t,"grove");try{const r=j(o);if(r.isFile()&&(r.mode&73)!==0)return o}catch{}}return null}l(k,"resolveGroveBinary");function D(e,t){return new Promise(o=>{let r;try{r=T(e,[...t],{stdio:["ignore","pipe","pipe"]})}catch(i){o({code:null,signal:null,stdout:"",stderr:"",error:i instanceof Error?i:new Error(String(i))});return}let s="",d="",a=!1;const u=l(i=>{a||(a=!0,o(i))},"settle");r.stdout.on("data",i=>{s+=i.toString()}),r.stderr.on("data",i=>{d+=i.toString()}),r.on("error",i=>u({code:null,signal:null,stdout:s,stderr:d,error:i})),r.on("close",(i,c)=>u({code:i,signal:c,stdout:s,stderr:d}))})}l(D,"executeGrove");const F={resolveBinary:k,listTerminalNodes:l(async()=>await C({status:[...N]}),"listTerminalNodes"),getNodeRow:G,execute:D,captureLiveness:x};function Y(e){let t;try{t=JSON.parse(e)}catch{return null}if(typeof t!="object"||t===null||!Array.isArray(t.projects))return null;const o=[];for(const r of t.projects){if(typeof r!="object"||r===null)continue;const s=r.name,d=r.instances;if(!(typeof s!="string"||!Array.isArray(d)))for(const a of d){if(typeof a!="object"||a===null)continue;const u=a.name,i=a.slot,c=a.spec?.labels,p=typeof c=="object"&&c!==null?c.owner:void 0;typeof u=="string"&&typeof i=="number"&&typeof p=="string"&&o.push({project:s,name:u,slot:i,owner:p})}}return o}l(Y,"instancesFromInventory");async function $(e,t,o){return g(e)&&!await t.isClaimed(e.node_id)&&e.frozen_at===null&&L(e.pi_pid,e.pi_pid_identity,o)==="dead"}l($,"engineIsGone");async function z(e,t,o){if(await t.isClaimed(e.node_id))return!1;const r=await o(e.node_id);return r!==null&&g(r)&&r.frozen_at===null&&r.pi_pid===e.pi_pid&&r.pi_pid_identity===e.pi_pid_identity}l(z,"stillEligible");function S(e){if(e.error!==void 0)return e.error;const t=e.stderr.trim()||e.stdout.trim()||`Grove exited with ${e.signal??e.code??"an unknown status"}`;return new Error(t)}l(S,"executionError");function h(e){return`${e.project}/${e.name}`}l(h,"instanceKey");class ne{static{l(this,"GroveInstanceSweepReconciler")}operations;retryAfter=new Map;constructor(t=F){this.operations=t}run(t,o){if(!o.lifecycle.acceptsDetachedWork())return;const r=M("grove-instance-sweep","",t,O,B,s=>this.sweep(t,o.fleet,s)).then(()=>{}).catch(s=>{y.fresh(()=>m({level:"error",event:"grove.sweep.failed",error:s}))});o.lifecycle.registerDetached(r)}async sweep(t,o,r){const s=this.operations.resolveBinary();if(s===null)return;let d;try{d=(await this.operations.listTerminalNodes()).filter(g)}catch(n){y.fresh(()=>{m({level:"error",event:"grove.sweep.scan_failed",error:n})});return}if(d.length===0)return;const a=await this.operations.execute(s,["list","--json"]);if(a.error!==void 0||a.code!==0||a.signal!==null){y.fresh(()=>{m({level:"error",event:"grove.sweep.list_failed",error:S(a)})});return}const u=Y(a.stdout);if(u===null){y.fresh(()=>{m({level:"error",event:"grove.sweep.list_failed",error:new Error("grove list --json returned invalid inventory JSON")})});return}const i=new Map(d.map(n=>[n.node_id,n])),c=u.filter(n=>i.get(n.owner)!==void 0&&(this.retryAfter.get(h(n))??Number.NEGATIVE_INFINITY)<=t).sort((n,f)=>n.project.localeCompare(f.project)||n.slot-f.slot||n.name.localeCompare(f.name));if(c.length===0)return;let p=Number(r.state??0);(!Number.isInteger(p)||p>=c.length)&&(p=0);const v=Math.min(V,c.length),A=Array.from({length:v},(n,f)=>c[(p+f)%c.length]);if(!await r.save(String((p+v)%c.length)))return;const w=await this.operations.captureLiveness();if(w!==null)for(const n of A){const f=i.get(n.owner);if(!await $(f,o,w)||!await z(f,o,this.operations.getNodeRow))continue;if(!await r.holds())return;const _=await this.operations.execute(s,["uproot",h(n),"--force"]);if(_.error===void 0&&_.code===0&&_.signal===null){this.retryAfter.delete(h(n)),y.fresh(()=>{m({level:"info",event:"grove.sweep.reconciled",outcome:"succeeded",...E(n.owner)?{node_id:n.owner}:{fields:{affected_node_id:n.owner}}})});continue}this.retryAfter.set(h(n),t+W),y.fresh(()=>{m({level:"error",event:"grove.sweep.failed",outcome:"failed",...E(n.owner)?{node_id:n.owner}:{fields:{affected_node_id:n.owner}},error:S(_)})})}}}export{V as GROVE_INSTANCE_SWEEP_BUDGET,O as GROVE_INSTANCE_SWEEP_INTERVAL_MS,W as GROVE_INSTANCE_SWEEP_RETRY_DELAY_MS,ne as GroveInstanceSweepReconciler};
@@ -0,0 +1,27 @@
1
+ import { type BlobStore } from '../../core/blobs/index.js';
2
+ import type { DetachedWorkLifecycle } from './broker-supervision.js';
3
+ import { type LanePass } from './throttled-lane.js';
4
+ export declare const LEDGER_R2_INTERVAL_MS: number;
5
+ export declare const LEDGER_R2_GRACE_MS: number;
6
+ /** (month, payer) pairs moved per pass. */
7
+ export declare const LEDGER_R2_BUDGET = 25;
8
+ /** The blob's key names its content (`hash` is the SHA-256 of the rows it holds), so two movers that read different versions of the
9
+ * rows write different blobs and the committed `charge_chunks.blob_key` always matches the totals stored beside it. */
10
+ export declare const chargeChunkKey: (month: string, payer: string, hash: string) => string;
11
+ export interface LedgerR2Result {
12
+ moved: number;
13
+ deferred: number;
14
+ }
15
+ export declare class LedgerR2Reconciler {
16
+ private readonly blobs;
17
+ constructor(blobs?: () => Promise<BlobStore>);
18
+ run(now: number, ctx: {
19
+ lifecycle: DetachedWorkLifecycle;
20
+ }): void;
21
+ /** One pass. Exposed for tests; `afterBlob` runs between the blob write and the transaction (a test kills the process there). */
22
+ pass(now: number, lane?: LanePass, afterBlob?: () => Promise<void>): Promise<LedgerR2Result>;
23
+ private move;
24
+ /** The transaction half of a move: true when the `charge_chunks` row named `key` was inserted and the rows deleted. */
25
+ private commit;
26
+ private movable;
27
+ }
@@ -0,0 +1,7 @@
1
+ var g=Object.defineProperty;var u=(d,e)=>g(d,"name",{value:e,configurable:!0});import{createHash as _}from"node:crypto";import{zstdCompressSync as S}from"node:zlib";import{blobStore as A}from"../../core/blobs/index.js";import{openDb as E,withCanvasWrite as N}from"../../core/canvas/db.js";import{readConfig as O}from"../../core/config.js";import{emitEvent as f}from"../../core/events/emit.js";import{operationIdContext as R}from"../../core/events/operation-id.js";import{usageMonth as D}from"../../core/ledger/months.js";import{sandboxUnbilledIn as L}from"./ledger-sandbox.js";import{storageSettledThrough as w}from"./ledger-storage.js";import{runThrottledLane as y}from"./throttled-lane.js";const T=60*6e4,v=2880*6e4,C=25,M=10*6e4,I=u((d,e,t)=>`ledger/${d}/${e}.${t}.jsonl.zst`,"chargeChunkKey"),p="(billed_usd IS NULL OR billed_usd = 0 OR invoice_id IS NOT NULL)";class P{static{u(this,"LedgerR2Reconciler")}blobs;constructor(e=A){this.blobs=e}run(e,t){if(!t.lifecycle.acceptsDetachedWork())return;const a=R.fresh(()=>y("ledger-r2","",e,T,M,async r=>{await this.pass(e,r)})).then(()=>{}).catch(r=>{R.fresh(()=>f({level:"error",event:"ledger.r2.failed",error:r}))});t.lifecycle.registerDetached(a)}async pass(e,t,a){const r={moved:0,deferred:0};if(O("user").ledger?.r2_move!==!0)return r;const i=await w();if(i===null)return r;const o=D(new Date(e-v)),s=await(await E()).prepare(`SELECT c.month, c.payer FROM charges c
2
+ WHERE c.month < ? AND (?::text IS NULL OR c.month <= ?) AND NOT EXISTS (SELECT 1 FROM charge_chunks k WHERE k.month = c.month AND k.payer = c.payer)
3
+ GROUP BY c.month, c.payer HAVING bool_and(${p.replaceAll("billed_usd","c.billed_usd").replaceAll("invoice_id","c.invoice_id")})
4
+ ORDER BY c.month, c.payer LIMIT ?`).all(o,i??null,i??null,C),l=await this.blobs();for(const{month:n,payer:h}of s){if(t&&!await t.holds())break;if(await L(n,e)){r.deferred+=1;continue}await this.move(l,n,h,a)?r.moved+=1:r.deferred+=1}return r.moved>0&&f({level:"info",event:"ledger.r2.moved",outcome:"succeeded",fields:{...r}}),r}async move(e,t,a,r){const o=await(await E()).prepare("SELECT id, to_jsonb(charges)::text AS body FROM charges WHERE month = ? AND payer = ? ORDER BY id").all(t,a);if(o.length===0||!await this.movable(t,a))return!1;const c=o.map(n=>n.body).join(`
5
+ `)+`
6
+ `,s=I(t,a,_("sha256").update(c).digest("hex").slice(0,16));await e.put(s,S(Buffer.from(c)));const l=await this.commit(t,a,s,o,r);return!l&&await(await E()).prepare("SELECT 1 FROM charge_chunks WHERE blob_key = ?").get(s)===void 0&&await e.delete(s),l}async commit(e,t,a,r,i){return await i?.(),await N(async o=>{const c=await o.prepare("SELECT id FROM charges WHERE month = ? AND payer = ? ORDER BY id FOR UPDATE").all(e,t),s=await o.prepare(`SELECT count(*) AS n FROM charges WHERE month = ? AND payer = ? AND ${p}`).get(e,t);if(c.length!==r.length||Number(s.n)!==r.length||c.some((m,b)=>m.id!==r[b].id)||(await o.prepare("SELECT string_agg(to_jsonb(c)::text, E'\\n' ORDER BY c.id) AS body FROM charges c WHERE month = ? AND payer = ?").get(e,t)).body!==r.map(m=>m.body).join(`
7
+ `))return!1;const n=await o.prepare("SELECT sum(usd) AS usd, sum(billed_usd) AS billed, min(at) AS first_at, max(at) AS last_at FROM charges WHERE month = ? AND payer = ?").get(e,t);return(await o.prepare('INSERT INTO charge_chunks (month, payer, blob_key, "rows", usd, billed_usd, first_at, last_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (month, payer) DO NOTHING').run(e,t,a,r.length,n.usd,n.billed,n.first_at,n.last_at)).changes===0?!1:(await o.prepare("DELETE FROM charges WHERE month = ? AND payer = ?").run(e,t),!0)})}async movable(e,t){const a=await(await E()).prepare(`SELECT count(*) AS n FROM charges WHERE month = ? AND payer = ? AND NOT ${p}`).get(e,t);return Number(a.n)===0}}export{C as LEDGER_R2_BUDGET,v as LEDGER_R2_GRACE_MS,T as LEDGER_R2_INTERVAL_MS,P as LedgerR2Reconciler,I as chargeChunkKey};
@@ -0,0 +1,55 @@
1
+ import type { DetachedWorkLifecycle } from './broker-supervision.js';
2
+ import { type LanePass } from './throttled-lane.js';
3
+ export declare const LEDGER_SANDBOX_INTERVAL_MS = 60000;
4
+ export declare const DEFAULT_STANDBY_S = 30;
5
+ /** Executions billed per pass; the rest wait for the next pass. */
6
+ export declare const LEDGER_SANDBOX_BUDGET = 200;
7
+ interface Rates {
8
+ usdPerGbHour: number | null;
9
+ standbyMs: number;
10
+ }
11
+ export declare function sandboxRates(): Rates;
12
+ /** The `[from, to]` chunks to bill: every whole hour, plus the remainder when the period is closed. The hours stay aligned to `from`
13
+ * whatever the month; an hour or remainder that crosses a UTC month boundary is cut there into one chunk per month. */
14
+ export declare function chunksOf(from: number, end: number, closed: boolean): Array<[number, number]>;
15
+ /** When a chunk's seconds accrued, for the row's `at` (and so its month): its last millisecond, so a chunk ending at exactly 00:00Z
16
+ * of a month belongs to the month before it. */
17
+ export declare const chunkAccruedAt: (chunkEnd: number) => Date;
18
+ /** The row id of the billed chunk of an execution that starts at `chunkStart`: the start time in whole seconds makes it
19
+ * the same in every process and unique across the awake periods of one execution. */
20
+ export declare const sandboxChargeId: (executionId: string, chunkStart: number) => string;
21
+ /** An awake period that a wake ended: whose it was, where it began, when the broker went idle and how far it had been billed. */
22
+ interface EndedPeriod {
23
+ execution_id: string;
24
+ awake_since: string;
25
+ idle_at: string;
26
+ charged_through: string | null;
27
+ }
28
+ export interface LedgerSandboxResult {
29
+ executions: number;
30
+ rows: number;
31
+ failed: number;
32
+ }
33
+ export declare class LedgerSandboxReconciler {
34
+ run(now: number, ctx: {
35
+ lifecycle: DetachedWorkLifecycle;
36
+ }): void;
37
+ /** One pass over the executions with awake time not yet billed. Exposed for tests. */
38
+ pass(now: number, lane?: LanePass): Promise<LedgerSandboxResult>;
39
+ /** Bills one execution; returns the rows it wrote. Payer and memory are read first; the rows and `charged_through` go in one
40
+ * transaction that holds the execution row's lock, from which the period is recomputed. `settle` is the wake of a resident
41
+ * broker: an idle period is closed at `min(now, idle_at + standby tail)` and billed whole, because the wake resets `awake_since`;
42
+ * a period that is not idle is left to the lane. `ended` is a period a wake ended before its settle succeeded: it is billed from
43
+ * the period saved with it, whatever the execution row says now, and its row is deleted in the same transaction. */
44
+ bill(executionId: string, now: number, usdPerGbHour: number, standbyMs: number, settle?: boolean, ended?: EndedPeriod): Promise<number>;
45
+ }
46
+ /** Whether any awake time that accrued in `month` (UTC) is not billed yet: an execution's current period, or an ended period a failed
47
+ * settle saved, whose unbilled span `[max(awake_since, charged_through), end]` overlaps the month. `ledger-r2` moves no month while
48
+ * this is true, because the month's allowance is summed from the live `charges` rows and a row written after the move would get it
49
+ * again. With no sandbox rate the lane writes nothing, so nothing is unbilled. */
50
+ export declare function sandboxUnbilledIn(month: string, now: number): Promise<boolean>;
51
+ /** Before a wake resets `awake_since` (`recordExecutionAwake`): bills the idle period that is ending, which the lane would
52
+ * otherwise lose. Never throws: a settle that fails must not block the wake, and the failure is logged. Settling the same
53
+ * period twice charges once (`charged_through` and the deterministic row ids). */
54
+ export declare function settleSandboxBeforeWake(executionId: string, now?: number): Promise<void>;
55
+ export {};
@@ -0,0 +1,9 @@
1
+ var W=Object.defineProperty;var d=(r,e)=>W(r,"name",{value:e,configurable:!0});import{openDb as x,withCanvasWrite as F}from"../../core/canvas/db.js";import{getNode as A}from"../../core/canvas/canvas.js";import{readConfig as z}from"../../core/config.js";import{emitEvent as E}from"../../core/events/emit.js";import{operationIdContext as T}from"../../core/events/operation-id.js";import{billingAccountOf as $}from"../../core/ledger/account.js";import{writeInfraCharge as B}from"../../core/ledger/infra-allowance.js";import{resetAt as G}from"../../core/ledger/months.js";import{startCandidateOf as P}from"../../core/runtime/start-check.js";import{runThrottledLane as J}from"./throttled-lane.js";const Y=6e4,V=5*6e4,b=36e5,X=30,y=200;function N(){const r=z("user").ledger;return{usdPerGbHour:r?.sandbox_usd_per_gb_hour??null,standbyMs:(r?.sandbox_standby_s??X)*1e3}}d(N,"sandboxRates");function j(r,e,n){const t=r.exited_at??r.idle_at;if(t===null)return{end:e,closed:!1};const a=Date.parse(t)+n;return a<=e?{end:a,closed:!0}:{end:e,closed:!1}}d(j,"periodEnd");const K=d(r=>{const e=new Date(r);return Date.UTC(e.getUTCFullYear(),e.getUTCMonth()+1,1)},"monthEndAfter");function R(r,e){const n=[];for(let t=r;t<e;){const a=Math.min(e,K(t));n.push([t,a]),t=a}return n}d(R,"withinMonths");function k(r,e,n){const t=[];let a=r;for(;e-a>=b;)t.push(...R(a,a+b)),a+=b;return n&&e>a&&t.push(...R(a,e)),t}d(k,"chunksOf");const Z=d(r=>new Date(r-1),"chunkAccruedAt"),q=d((r,e)=>`sandbox:${r}:${Math.floor(e/1e3)}`,"sandboxChargeId"),S="ledger-sandbox-unsettled",M=d((r,e)=>`${r}@${Date.parse(e)}`,"unsettledKey");class Q{static{d(this,"LedgerSandboxReconciler")}run(e,n){if(!n.lifecycle.acceptsDetachedWork())return;const t=T.fresh(()=>J("ledger-sandbox","",e,Y,V,async a=>{await this.pass(e,a)})).then(()=>{}).catch(a=>{T.fresh(()=>E({level:"error",event:"ledger.sandbox.failed",error:a}))});n.lifecycle.registerDetached(t)}async pass(e,n){const t={executions:0,rows:0,failed:0},{usdPerGbHour:a,standbyMs:l}=N();if(a===null)return t;const i=await x(),_=await i.prepare(`SELECT execution_id, node_id, awake_since, idle_at, exited_at, charged_through FROM executions
2
+ WHERE runner = 'blaxel' AND awake_since IS NOT NULL
3
+ AND (state <> 'exited' OR charged_through IS NULL OR charged_through::timestamptz < exited_at::timestamptz + make_interval(secs => ?::float8 / 1000))
4
+ ORDER BY execution_id LIMIT ?`).all(l,y);for(const s of _){if(n&&!await n.holds())break;try{const o=await this.bill(s.execution_id,e,a,l);o>0&&(t.executions+=1),t.rows+=o}catch(o){t.failed+=1,E({level:"warn",event:"ledger.sandbox.execution_failed",error:o,fields:{execution_id:s.execution_id}})}}const f=await i.prepare("SELECT cursor FROM reconciler_leases WHERE lane = ? ORDER BY key LIMIT ?").all(S,y);for(const{cursor:s}of f){const o=JSON.parse(s);if(n&&!await n.holds())break;try{const c=await this.bill(o.execution_id,e,a,l,!1,o);c>0&&(t.executions+=1),t.rows+=c}catch(c){t.failed+=1,E({level:"warn",event:"ledger.sandbox.execution_failed",error:c,fields:{execution_id:o.execution_id}})}}return(t.rows>0||t.failed>0)&&E({level:"info",event:"ledger.sandbox.settled",outcome:"succeeded",fields:{...t}}),t}async bill(e,n,t,a,l=!1,i){const _=await x(),f=await _.prepare("SELECT node_id FROM executions WHERE execution_id = ?").get(e);if(f===void 0)return 0;const s=await A(f.node_id);if(s===null)throw new Error(`execution ${e} names node ${f.node_id}, which has no row`);const o=await P(s);if(o===null)throw new Error(`node ${s.node_id} has no person yet, so its sandbox hours have no payer`);const c=await _.prepare("SELECT e.memory_mb AS mb FROM profiles p JOIN environments e ON e.object = p.environment_id WHERE p.id = ?").get(s.profile_id??null);if(c===void 0)throw new Error(`node ${s.node_id} has no environment, so its sandbox size is unknown`);const p=Number(c.mb),{account:C,billable:v}=await $(_,o.payer),H=(s.run_id?await A(s.run_id):null)?.caused_by??s.caused_by??null;return await F(async D=>{const u=await D.prepare(`SELECT execution_id, node_id, awake_since, idle_at, exited_at, charged_through FROM executions
5
+ WHERE execution_id = ? AND runner = 'blaxel' AND awake_since IS NOT NULL FOR UPDATE`).get(e);if(i===void 0&&u===void 0||i===void 0&&l&&u.idle_at===null)return 0;let g,h;if(i!==void 0)g=Math.max(Date.parse(i.awake_since),i.charged_through===null?0:Date.parse(i.charged_through)),h=k(g,Date.parse(i.idle_at)+a,!0);else{const{end:m,closed:w}=l?{end:Math.min(n,Date.parse(u.idle_at)+a),closed:!0}:j(u,n,a);g=Math.max(Date.parse(u.awake_since),u.charged_through===null?0:Date.parse(u.charged_through)),h=k(g,m,w)}let L=0;for(const[m,w]of h){const I=Math.round((w-m)/1e3),O=p/1024*((w-m)/b),U=O*t;(await B({id:q(e,m),at:Z(w),kind:"sandbox",what:`${p} MB`,units:{gb_hours:O,seconds:I,memory_mb:p},usd:U,payer:o.payer,funding:"bill",sub:o.sub,grantee:o.grantee,node:s.node_id,run:s.run_id??null,provider:"blaxel",causedBy:H,account:C},v)).written&&(L+=1)}return i!==void 0?await D.prepare("DELETE FROM reconciler_leases WHERE lane = ? AND key = ?").run(S,M(e,i.awake_since)):h.length>0&&await D.prepare("UPDATE executions SET charged_through = ? WHERE execution_id = ?").run(new Date(h[h.length-1][1]).toISOString(),e),L})}}async function pe(r,e){const{usdPerGbHour:n,standbyMs:t}=N();if(n===null)return!1;const a=await x(),l=new Date(`${r}-01T00:00:00Z`).toISOString(),i=G(r);return await a.prepare(`SELECT 1 AS found FROM executions e,
6
+ LATERAL (SELECT greatest(e.awake_since::timestamptz, COALESCE(e.charged_through::timestamptz, '-infinity'::timestamptz)) AS from_at,
7
+ CASE WHEN COALESCE(e.exited_at, e.idle_at) IS NULL THEN ?::timestamptz
8
+ ELSE least(COALESCE(e.exited_at, e.idle_at)::timestamptz + make_interval(secs => ?::float8 / 1000), ?::timestamptz) END AS end_at) p
9
+ WHERE e.runner = 'blaxel' AND e.awake_since IS NOT NULL AND p.from_at < p.end_at AND p.from_at < ?::timestamptz AND p.end_at > ?::timestamptz LIMIT 1`).get(new Date(e).toISOString(),t,new Date(e).toISOString(),i,l)!==void 0?!0:(await a.prepare("SELECT cursor FROM reconciler_leases WHERE lane = ?").all(S)).some(({cursor:s})=>{const o=JSON.parse(s),c=Math.max(Date.parse(o.awake_since),o.charged_through===null?0:Date.parse(o.charged_through)),p=Date.parse(o.idle_at)+t;return c<p&&c<Date.parse(i)&&p>Date.parse(l)})}d(pe,"sandboxUnbilledIn");async function he(r,e=Date.now()){try{const{usdPerGbHour:n,standbyMs:t}=N();if(n===null)return;await new Q().bill(r,e,n,t,!0)}catch(n){E({level:"warn",event:"ledger.sandbox.settle_failed",error:n,fields:{execution_id:r}}),await ee(r)}}d(he,"settleSandboxBeforeWake");async function ee(r){try{const e=await x(),n=await e.prepare("SELECT execution_id, awake_since, idle_at, charged_through FROM executions WHERE execution_id = ? AND runner = 'blaxel' AND awake_since IS NOT NULL AND idle_at IS NOT NULL").get(r);if(n===void 0)return;await e.prepare("INSERT INTO reconciler_leases (lane, key, owner, expires_at, cursor) VALUES (?, ?, ?, ?, ?) ON CONFLICT (lane, key) DO NOTHING").run(S,M(r,n.awake_since),"settle",new Date(0).toISOString(),JSON.stringify(n))}catch(e){E({level:"error",event:"ledger.sandbox.unsettled_lost",error:e,fields:{execution_id:r}})}}d(ee,"keepUnsettled");export{X as DEFAULT_STANDBY_S,y as LEDGER_SANDBOX_BUDGET,Y as LEDGER_SANDBOX_INTERVAL_MS,Q as LedgerSandboxReconciler,Z as chunkAccruedAt,k as chunksOf,q as sandboxChargeId,N as sandboxRates,pe as sandboxUnbilledIn,he as settleSandboxBeforeWake};
@@ -0,0 +1,22 @@
1
+ import type { DetachedWorkLifecycle } from './broker-supervision.js';
2
+ import { type LanePass } from './throttled-lane.js';
3
+ export declare const LEDGER_STORAGE_INTERVAL_MS: number;
4
+ /** The last month the storage lane settled (its saved cursor state), null when it has settled none, and `undefined` when no storage
5
+ * rate is configured, so the lane writes nothing and there is nothing to wait for. `ledger-r2` moves no month past it. */
6
+ export declare function storageSettledThrough(): Promise<string | null | undefined>;
7
+ export interface LedgerStorageResult {
8
+ month: string | null;
9
+ rows: number;
10
+ failed: number;
11
+ }
12
+ export declare class LedgerStorageReconciler {
13
+ run(now: number, ctx: {
14
+ lifecycle: DetachedWorkLifecycle;
15
+ }): void;
16
+ /** Settles every closed month the lane has not: the months after the one its cursor saved, through the month before `now`'s
17
+ * (with no saved month, only that one). A month's save follows its own success, so a failure leaves it and the later months
18
+ * for the next pass. Exposed for tests. */
19
+ pass(now: number, lane?: LanePass): Promise<LedgerStorageResult>;
20
+ /** Writes the month's rows; false when a payer failed or the lane was lost, so the month is not saved as settled. */
21
+ private settleMonth;
22
+ }
@@ -0,0 +1,5 @@
1
+ var M=Object.defineProperty;var p=(s,e)=>M(s,"name",{value:e,configurable:!0});import{openDb as N}from"../../core/canvas/db.js";import{getNode as D}from"../../core/canvas/canvas.js";import{readConfig as O}from"../../core/config.js";import{emitEvent as y}from"../../core/events/emit.js";import{operationIdContext as S}from"../../core/events/operation-id.js";import{billingAccountOf as T}from"../../core/ledger/account.js";import{writeInfraCharge as v}from"../../core/ledger/infra-allowance.js";import{usageMonth as w}from"../../core/ledger/months.js";import{startCandidateOf as I}from"../../core/runtime/start-check.js";import{runThrottledLane as L}from"./throttled-lane.js";const A=60*6e4,C=10*6e4,G=1024**3;async function Q(){const s=O("user").ledger;if((s?.storage_usd_per_gb_month??null)===null&&(s?.sandbox_disk_usd_per_gb_month??null)===null)return;const e=await(await N()).prepare("SELECT cursor FROM reconciler_leases WHERE lane = 'ledger-storage' AND key = ''").get(),r=e?.cursor==null?null:JSON.parse(e.cursor).state;return r===null?null:r.slice(0,7)}p(Q,"storageSettledThrough");function U(s){const[e,r]=s.split("-").map(Number);return w(new Date(Date.UTC(e,r-2,1)))}p(U,"previousMonth");const x=p(s=>{const[e,r]=s.split("-").map(Number);return w(new Date(Date.UTC(e,r,1)))},"nextMonth"),F=p(s=>{const[e,r]=s.split("-").map(Number);return new Date(Date.UTC(e,r,1)).toISOString()},"endOf");class X{static{p(this,"LedgerStorageReconciler")}run(e,r){if(!r.lifecycle.acceptsDetachedWork())return;const u=S.fresh(()=>L("ledger-storage","",e,A,C,async o=>{await this.pass(e,o)})).then(()=>{}).catch(o=>{S.fresh(()=>y({level:"error",event:"ledger.storage.failed",error:o}))});r.lifecycle.registerDetached(u)}async pass(e,r){const u=U(w(new Date(e))),o={month:null,rows:0,failed:0},_=O("user").ledger,f={r2:_?.storage_usd_per_gb_month??null,disk:_?.sandbox_disk_usd_per_gb_month??null};if(f.r2===null&&f.disk===null)return o;const i=r?.state?.slice(0,7)??null,l=[];if(i===null)l.push(u);else for(let n=x(i);n<=u;n=x(n))l.push(n);for(const n of l){if(o.month=n,!await this.settleMonth(n,f,r,o))break;await r?.save(n)}return(o.rows>0||o.failed>0)&&y({level:"info",event:"ledger.storage.settled",outcome:"succeeded",fields:{...o}}),o}async settleMonth(e,r,u,o){const{r2:_,disk:f}=r,i=F(e),l=await N(),n=new Map,g=p(t=>{let a=n.get(t);return a||n.set(t,a={bytes:0,diskGb:0}),a},"entry");for(const t of await l.prepare("SELECT split_part(blob_key, '/', 2) AS node, sum(bytes) AS bytes FROM conversation_parts WHERE created < ? GROUP BY 1").all(i))g(t.node).bytes+=Number(t.bytes);for(const t of await l.prepare(`SELECT x.node, sum(u.size) AS bytes FROM (
2
+ SELECT DISTINCT upload_id, split_part(blob_key, '/', 2) AS node FROM upload_uses WHERE blob_key LIKE 'attachments/%') x
3
+ JOIN uploads u ON u.upload_id = x.upload_id WHERE u.created_at < ? GROUP BY x.node`).all(i))g(t.node).bytes+=Number(t.bytes);for(const t of await l.prepare(`SELECT x.node_id AS node, sum(e.memory_mb) AS mb FROM (
4
+ SELECT DISTINCT node_id, sandbox_name FROM executions WHERE runner = 'blaxel' AND sandbox_name IS NOT NULL AND started_at < ?) x
5
+ JOIN nodes n ON n.node_id = x.node_id JOIN objects o ON o.id = n.node_id AND (o.deleted_at IS NULL OR o.deleted_at >= ?) JOIN profiles p ON p.id = n.profile_id JOIN environments e ON e.object = p.environment_id GROUP BY x.node_id`).all(i,i))g(t.node).diskGb+=Number(t.mb)/2/1024;const h=new Map;for(const[t,a]of n){const b=await D(t),c=b===null?null:await I(b);if(c===null)continue;const m=h.get(c.payer)??{sub:c.sub,grantee:c.grantee,bytes:0,diskGb:0,node:t};m.bytes+=a.bytes,m.diskGb+=a.diskGb,h.set(c.payer,m)}const R=new Date(Date.parse(i)-1);let E=0;for(const[t,a]of h){if(u&&!await u.holds())return!1;try{const{account:b,billable:c}=await T(l,t),m=[{id:`storage:${t}:${e}:r2`,provider:"r2",gbMonths:a.bytes/G,rate:_},{id:`storage:${t}:${e}:blaxel`,provider:"blaxel",gbMonths:a.diskGb,rate:f}];for(const d of m){if(d.rate===null||d.gbMonths<=0)continue;const k=d.gbMonths*d.rate;(await v({id:d.id,at:R,kind:"storage",what:d.provider==="r2"?"object storage":"sandbox disk",units:{gb_months:d.gbMonths},usd:k,payer:t,funding:"bill",sub:a.sub,grantee:a.grantee,provider:d.provider,causedBy:null,account:b},c)).written&&(o.rows+=1)}}catch(b){o.failed+=1,E+=1,y({level:"warn",event:"ledger.storage.payer_failed",error:b,fields:{payer:t,month:e}})}}return E===0}}export{A as LEDGER_STORAGE_INTERVAL_MS,X as LedgerStorageReconciler,Q as storageSettledThrough};
@@ -1,4 +1,5 @@
1
1
  import { type Lifecycle } from '../../core/canvas/index.js';
2
+ import { type ViewerProbe } from '../../core/runtime/placement.js';
2
3
  import { type LifecycleEvent } from '../../core/runtime/lifecycle.js';
3
4
  import type { FleetRegistry } from '../../core/runtime/fleet.js';
4
5
  /** Durable work that requires a node to remain automatically wakeable. */
@@ -23,7 +24,7 @@ export declare function retentionHold(fleet: Pick<FleetRegistry, 'isClaimed'>):
23
24
  * `attends: false` is a system observer and deliberately does not count, which
24
25
  * is how Northlight identifies itself — so an open product page is
25
26
  * invisible here, while crouter's own local and remote viewers are not. */
26
- export declare function hasHumanViewer(nodeId: string): Promise<boolean>;
27
+ export declare function hasHumanViewer(nodeId: string, viewer?: ViewerProbe): Promise<boolean>;
27
28
  /** What parking an unattended node actually amounts to — the one rule both the
28
29
  * live-broker park clock and the lifecycle tick apply.
29
30
  *
@@ -48,7 +49,7 @@ export declare function hasHumanViewer(nodeId: string): Promise<boolean>;
48
49
  * invariant is that content a sender already handed over is never dropped.
49
50
  */
50
51
  export type ParkEvent = Extract<LifecycleEvent, 'release' | 'park' | 'cancel'>;
51
- export declare function parkEvent(nodeId: string, lifecycle: Lifecycle): Promise<ParkEvent>;
52
+ export declare function parkEvent(nodeId: string, lifecycle: Lifecycle, viewer?: ViewerProbe): Promise<ParkEvent>;
52
53
  /** Apply an unattended decision and notify subscribers of automatic completion.
53
54
  * This is the DEGRADED enactment: no summary turn ran, so subscribers get the
54
55
  * doctrine notice instead of the node's own report. The live clock's good path
@@ -1 +1 @@
1
- var f=Object.defineProperty;var a=(t,r)=>f(t,"name",{value:r,configurable:!0});import{hasActiveLiveSubscription as l,hasLiveSubscriber as p,hasPendingCronWake as m}from"../../core/canvas/index.js";import{fullName as w}from"../../core/canvas/labels.js";import{hasLiveControllerWait as h,hasLiveObjectWait as k}from"../../core/canvas/waits.js";import{newestReportName as y}from"../../core/feed/reports.js";import{countTickets as b}from"../../core/canvas/attention.js";import{hasUnseenWakeMail as i}from"../../core/canvas/deliveries.js";import{readOutputRequest as g}from"../../core/runtime/structured-output.js";import{focusOf as v}from"../../core/runtime/placement.js";import{hasAttachedBrokerViewers as x}from"../api/viewer-presence.js";import{publishNotice as L}from"../../core/runtime/close.js";import{readFault as W}from"../../core/canvas/recovery.js";import{transition as c}from"../../core/runtime/lifecycle.js";async function s(t){return await l(t)||await h(t)||await k(t)||await m(t)||await b(t)>0||g(t).state!=="absent"}a(s,"hasLiveObligation");async function B(t){return await s(t)||await i(t)?!0:await n(t)}a(B,"hasPendingWake");async function F(t){return async r=>t.isClaimed(r)||await s(r)||await n(r)}a(F,"retentionHold");async function n(t){return await v(t)!==null||x(t)}a(n,"hasHumanViewer");async function M(t,r){if(await n(t)||await s(t))return"release";const e=await W(t);return e?.link==="pi\u2192provider"&&e.retry.disposition==="fatal"?"release":r==="resident"?await i(t)?"release":"park":await i(t)||await p(t)?"release":"cancel"}a(M,"parkEvent");async function T(t,r){const e=r==="cancel"?await c(t,r,{reason:"stranded"}):await c(t,r);if(r!=="park")return;const o=await y(t),u=o===null?`It left no report behind; \`crtr node inspect show ${t}\` shows where it stands.`:`Its latest report is where it left things: \`crtr canvas read ${o}\`.`;await L(t,"notice.close",`Resident concluded \u2014 ${w(e)} (${t}) had nothing left to wake it through its unattended grace, so its conversation was concluded. It submitted no final report. ${u} A message to it reopens it on a fresh cycle.`,{reason:"child-auto-done",child:t},"deferred")}a(T,"applyParkEvent");export{T as applyParkEvent,n as hasHumanViewer,s as hasLiveObligation,B as hasPendingWake,M as parkEvent,F as retentionHold};
1
+ var l=Object.defineProperty;var r=(t,a)=>l(t,"name",{value:a,configurable:!0});import{hasActiveLiveSubscription as f,hasLiveSubscriber as p,hasPendingCronWake as w}from"../../core/canvas/index.js";import{fullName as m}from"../../core/canvas/labels.js";import{hasLiveControllerWait as h,hasLiveObjectWait as k}from"../../core/canvas/waits.js";import{newestReportName as y}from"../../core/feed/reports.js";import{countTickets as b}from"../../core/canvas/attention.js";import{hasUnseenWakeMail as n}from"../../core/canvas/deliveries.js";import{readOutputRequest as g}from"../../core/runtime/output-result.js";import{focusOf as v}from"../../core/runtime/placement.js";import{hasAttachedBrokerViewers as x}from"../api/viewer-presence.js";import{publishNotice as L}from"../../core/runtime/close.js";import{readFault as W}from"../../core/canvas/recovery.js";import{transition as c}from"../../core/runtime/lifecycle.js";async function s(t){return await f(t)||await h(t)||await k(t)||await w(t)||await b(t)>0||await g(t)!==null}r(s,"hasLiveObligation");async function B(t){return await s(t)||await n(t)?!0:await o(t)}r(B,"hasPendingWake");async function F(t){return async a=>await t.isClaimed(a)||await s(a)||await o(a)}r(F,"retentionHold");async function o(t,a){return(a!==void 0?a.live!==null:await v(t)!==null)||await x(t)}r(o,"hasHumanViewer");async function M(t,a,e){if(await o(t,e)||await s(t))return"release";const i=await W(t);return i?.link==="pi\u2192provider"&&i.retry.disposition==="fatal"?"release":a==="resident"?await n(t)?"release":"park":await n(t)||await p(t)?"release":"cancel"}r(M,"parkEvent");async function T(t,a){const e=a==="cancel"?await c(t,a,{reason:"stranded"}):await c(t,a);if(a!=="park")return;const i=await y(t),u=i===null?`It left no report behind; \`crtr node inspect show ${t}\` shows where it stands.`:`Its latest report is where it left things: \`crtr canvas read ${i}\`.`;await L(t,"notice.close",`Resident concluded \u2014 ${m(e)} (${t}) had nothing left to wake it through its unattended grace, so its conversation was concluded. It submitted no final report. ${u} A message to it reopens it on a fresh cycle.`,{reason:"child-auto-done",child:t},"deferred")}r(T,"applyParkEvent");export{T as applyParkEvent,o as hasHumanViewer,s as hasLiveObligation,B as hasPendingWake,M as parkEvent,F as retentionHold};
@@ -8,11 +8,6 @@ export interface ManagedWorktreeSweepContext {
8
8
  fleet: FleetRegistry;
9
9
  }
10
10
  export declare class ManagedWorktreeSweepReconciler {
11
- private lastSweepAt;
12
- private inFlight;
13
- /** Round-robin cursor over candidate node ids, so a permanently stuck record
14
- * at the head of the list cannot starve the ones behind it. */
15
- private cursor;
16
11
  run(now: number, ctx: ManagedWorktreeSweepContext): void;
17
12
  /** Terminal nodes whose managed worktree is unreconciled and whose backoff has
18
13
  * elapsed. Canvas reads only — engine liveness is classified later, for the
@@ -1 +1 @@
1
- var w=Object.defineProperty;var o=(t,e)=>w(t,"name",{value:e,configurable:!0});import{getNode as p,getRow as h}from"../../core/canvas/index.js";import{managedWorktreeSweepCandidates as y}from"../../core/canvas/managed-worktree-sweep-candidates.js";import{isSafeNodeId as _}from"../../core/canvas/paths.js";import{captureLivenessSnapshotAsync as E,recordedPidLiveness as k}from"../../core/canvas/pid.js";import{emitEvent as c}from"../../core/events/emit.js";import{operationIdContext as u}from"../../core/events/operation-id.js";import{reconcileManagedWorktree as S}from"../../core/worktree-sweep.js";const I=60*1e3,v=3,T=["done","dead","canceled"];function m(t){return T.includes(t.status)}o(m,"isTerminal");function g(t,e){return e?.state==="closed"||e?.state==="open"&&t.status==="done"&&t.final_report!==null}o(g,"hasSweepDisposition");async function A(t,e,n){return e.isClaimed(t.node_id)||t.frozen_at!==null||!m(t)||!g(t,(await p(t.node_id))?.managed_worktree)?!1:k(t.pi_pid,t.pi_pid_identity,n)==="dead"}o(A,"engineIsGone");function N(t,e){const n=t.node_id,r=t.pi_pid,a=t.pi_pid_identity;return{stillEligible:o(async()=>{if(e.isClaimed(n))return!1;const s=await h(n);return s===null||!m(s)||s.frozen_at!==null||!g(s,(await p(n))?.managed_worktree)?!1:s.pi_pid===r&&s.pi_pid_identity===a},"stillEligible")}}o(N,"eligibilityGuard");class L{static{o(this,"ManagedWorktreeSweepReconciler")}lastSweepAt=Number.NEGATIVE_INFINITY;inFlight=!1;cursor=0;run(e,n){if(this.inFlight||e-this.lastSweepAt<I||!n.lifecycle.acceptsDetachedWork())return;this.lastSweepAt=e,this.inFlight=!0;const r=this.sweep(e,n.fleet).finally(()=>{this.inFlight=!1});n.lifecycle.registerDetached(r)}async candidates(e){try{return await y(e)}catch(n){return u.fresh(()=>{c({level:"error",event:"worktree.sweep.scan_failed",error:n})}),null}}async sweep(e,n){const r=await this.candidates(e);if(r===null||r.length===0)return;this.cursor>=r.length&&(this.cursor=0);const a=this.cursor,s=Math.min(v,r.length),d=[];for(let l=0;l<s;l+=1)d.push(r[(a+l)%r.length]);this.cursor=(a+s)%r.length;const i=await E();if(i!==null)for(const l of d){const f=await h(l);f===null||!await A(f,n,i)||await this.reconcileOne(f,e,n)}}async reconcileOne(e,n,r){const a=e.node_id,s=_(a)?{node_id:a}:{},d=_(a)?{}:{affected_node_id:a};try{const i=await S(a,N(e,r),n);u.fresh(()=>{if(i.status==="complete"){c({level:"info",event:"worktree.sweep.reconciled",outcome:"succeeded",...s,fields:{...d,...i.stash===void 0?{}:{base_checkout_stash:i.stash}}});return}i.status!=="skipped"&&(i.status==="refused"&&i.unchanged===!0||c({level:i.status==="failed"?"error":"warn",event:i.status==="failed"?"worktree.sweep.failed":"worktree.sweep.refused",outcome:i.status==="failed"?"failed":"skipped",...s,fields:{...d,reason:i.reason,...i.detail===void 0?{}:{detail:i.detail}}}))})}catch(i){u.fresh(()=>{c({level:"error",event:"worktree.sweep.failed",...s,error:i,fields:d})})}}}export{L as ManagedWorktreeSweepReconciler,v as WORKTREE_SWEEP_BUDGET,I as WORKTREE_SWEEP_INTERVAL_MS};
1
+ var E=Object.defineProperty;var o=(e,t)=>E(e,"name",{value:t,configurable:!0});import{getNode as _,getRow as m}from"../../core/canvas/index.js";import{managedWorktreeSweepCandidates as k}from"../../core/canvas/managed-worktree-sweep-candidates.js";import{isSafeNodeId as h}from"../../core/canvas/paths.js";import{captureLivenessSnapshotAsync as y,recordedPidLiveness as S}from"../../core/canvas/pid.js";import{emitEvent as c}from"../../core/events/emit.js";import{operationIdContext as f}from"../../core/events/operation-id.js";import{reconcileManagedWorktree as v}from"../../core/worktree-sweep.js";import{runThrottledLane as R}from"./throttled-lane.js";const T=60*1e3,W=300*1e3,I=3,b=["done","dead","canceled"];function w(e){return b.includes(e.status)}o(w,"isTerminal");function g(e,t){return t?.state==="closed"||t?.state==="open"&&e.status==="done"&&e.final_report!==null}o(g,"hasSweepDisposition");async function L(e,t,i){return await t.isClaimed(e.node_id)||e.frozen_at!==null||!w(e)||!g(e,(await _(e.node_id))?.managed_worktree)?!1:S(e.pi_pid,e.pi_pid_identity,i)==="dead"}o(L,"engineIsGone");function M(e,t){const i=e.node_id,s=e.pi_pid,r=e.pi_pid_identity;return{stillEligible:o(async()=>{if(await t.isClaimed(i))return!1;const a=await m(i);return a===null||!w(a)||a.frozen_at!==null||!g(a,(await _(i))?.managed_worktree)?!1:a.pi_pid===s&&a.pi_pid_identity===r},"stillEligible")}}o(M,"eligibilityGuard");class z{static{o(this,"ManagedWorktreeSweepReconciler")}run(t,i){if(!i.lifecycle.acceptsDetachedWork())return;const s=R("managed-worktree-sweep","",t,T,W,r=>this.sweep(t,i.fleet,r)).then(()=>{}).catch(r=>{f.fresh(()=>c({level:"error",event:"worktree.sweep.failed",error:r}))});i.lifecycle.registerDetached(s)}async candidates(t){try{return await k(t)}catch(i){return f.fresh(()=>{c({level:"error",event:"worktree.sweep.scan_failed",error:i})}),null}}async sweep(t,i,s){const r=await this.candidates(t);if(r===null||r.length===0)return;let a=Number(s.state??0);(!Number.isInteger(a)||a>=r.length)&&(a=0);const d=Math.min(I,r.length),n=[];for(let l=0;l<d;l+=1)n.push(r[(a+l)%r.length]);if(!await s.save(String((a+d)%r.length)))return;const p=await y();if(p!==null)for(const l of n){const u=await m(l);if(!(u===null||!await L(u,i,p))){if(!await s.holds())return;await this.reconcileOne(u,t,i)}}}async reconcileOne(t,i,s){const r=t.node_id,a=h(r)?{node_id:r}:{},d=h(r)?{}:{affected_node_id:r};try{const n=await v(r,M(t,s),i);f.fresh(()=>{if(n.status==="complete"){c({level:"info",event:"worktree.sweep.reconciled",outcome:"succeeded",...a,fields:{...d,...n.stash===void 0?{}:{base_checkout_stash:n.stash}}});return}n.status!=="skipped"&&(n.status==="refused"&&n.unchanged===!0||c({level:n.status==="failed"?"error":"warn",event:n.status==="failed"?"worktree.sweep.failed":"worktree.sweep.refused",outcome:n.status==="failed"?"failed":"skipped",...a,fields:{...d,reason:n.reason,...n.detail===void 0?{}:{detail:n.detail}}}))})}catch(n){f.fresh(()=>{c({level:"error",event:"worktree.sweep.failed",...a,error:n,fields:d})})}}}export{z as ManagedWorktreeSweepReconciler,I as WORKTREE_SWEEP_BUDGET,T as WORKTREE_SWEEP_INTERVAL_MS};
@@ -1,7 +1,8 @@
1
+ import { type RowClaim } from './row-claim.js';
1
2
  /** Enforce absolute spawn deadlines independently of the tick's live-row scan. */
2
3
  export declare class NodeDeadlineReconciler {
3
4
  run(now: number): Promise<void>;
4
5
  }
5
6
  /** The one deadline terminalization path. Structured-request expiry reuses it
6
7
  * even when a profile is paused, because its dedicated broker must not resume. */
7
- export declare function expireNodeDeadline(nodeId: string, deadlineAt: string, now: number): Promise<void>;
8
+ export declare function expireNodeDeadline(nodeId: string, deadlineAt: string, now: number, claim?: RowClaim): Promise<void>;
@@ -1 +1 @@
1
- var m=Object.defineProperty;var s=(r,a)=>m(r,"name",{value:a,configurable:!0});import{dueNodeDeadlines as _,getNode as u}from"../../core/canvas/index.js";import{emitEvent as i}from"../../core/events/emit.js";import{isProfilePaused as p}from"../../core/profiles/manifest.js";import{operationIdContext as w}from"../../core/events/operation-id.js";import{errorClassFromFault as h}from"../../core/fault-classifier.js";import{publishNotice as v}from"../../core/runtime/close.js";import{readFault as x}from"../../core/canvas/recovery.js";import{requestBrokerTeardown as f}from"../../core/runtime/host.js";import{transition as y}from"../../core/runtime/lifecycle.js";class z{static{s(this,"NodeDeadlineReconciler")}async run(a){let o;try{o=await _(new Date(a).toISOString())}catch(e){i({level:"error",event:"node_deadline.scan_failed",error:e});return}for(const e of o)p(e.profile_id)||e.deadline_at===null||e.deadline_at===void 0||w.fresh(()=>{D(e.node_id,e.deadline_at,a).catch(d=>{i({level:"error",event:"node_deadline.expire_failed",node_id:e.node_id,error:d,fields:{deadline_at:e.deadline_at}})})})}}async function D(r,a,o){const e=await u(r);if(e===null||e.status==="done")return;if(e.status==="canceled"||e.status==="dead"){const t=await f(r);if(!t.confirmed)throw t.error??new Error("broker teardown was not confirmed");return}const d=await x(r),n=d?.retry.disposition==="fatal"?d:null;let l;try{l=await y(r,"cancel",{reason:"deadline_exceeded",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",deadline:{deadline_at:a,elapsed_ms:o-Date.parse(a)},...n===null?{}:{message:n.message,fault_kind:n.kind,error_class:h(n).class}}}})}catch(t){throw i({level:"error",event:"node_deadline.terminalize_failed",node_id:r,error:t,fields:{deadline_at:a}}),t}if(l.status!=="canceled"||l.outcome_reason!=="deadline_exceeded")return;const c=await f(r);if(!c.confirmed)throw c.error??new Error("broker teardown was not confirmed");try{await v(r,"notice.deadline","child-deadline-exceeded",{reason:"child-deadline-exceeded",child:r})}catch(t){i({level:"error",event:"node_deadline.notify_failed",node_id:r,error:t,fields:{deadline_at:a}})}try{i({level:"error",event:"node_deadline.expired",node_id:r,fields:{deadline_at:a,elapsed_ms:o-Date.parse(a)}})}catch{}}s(D,"expireNodeDeadline");export{z as NodeDeadlineReconciler,D as expireNodeDeadline};
1
+ var m=Object.defineProperty;var s=(r,t)=>m(r,"name",{value:t,configurable:!0});import{dueNodeDeadlines as w,getNode as u}from"../../core/canvas/index.js";import{emitEvent as d}from"../../core/events/emit.js";import{isProfilePaused as p}from"../../core/profiles/manifest.js";import{operationIdContext as h}from"../../core/events/operation-id.js";import{errorClassFromFault as v}from"../../core/fault-classifier.js";import{publishNotice as x}from"../../core/runtime/close.js";import{readFault as y}from"../../core/canvas/recovery.js";import{requestBrokerTeardown as _}from"../../core/runtime/host.js";import{transition as D}from"../../core/runtime/lifecycle.js";import{withRowClaim as N}from"./row-claim.js";const O=6e4;class z{static{s(this,"NodeDeadlineReconciler")}async run(t){let i;try{i=await w(new Date(t).toISOString())}catch(e){d({level:"error",event:"node_deadline.scan_failed",error:e});return}for(const e of i)p(e.profile_id)||e.deadline_at===null||e.deadline_at===void 0||h.fresh(()=>{N("nodes",e.node_id,O,a=>k(e.node_id,e.deadline_at,t,a)).catch(a=>{d({level:"error",event:"node_deadline.expire_failed",node_id:e.node_id,error:a,fields:{deadline_at:e.deadline_at}})})})}}async function k(r,t,i,e){await e?.assertOwned();const a=await u(r);if(a===null||a.status==="done")return;if(a.status==="canceled"||a.status==="dead"){await e?.assertOwned();const o=await _(r);if(!o.confirmed)throw o.error??new Error("broker teardown was not confirmed");return}const c=await y(r),n=c?.retry.disposition==="fatal"?c:null;let l;await e?.assertOwned();try{l=await D(r,"cancel",{reason:"deadline_exceeded",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",deadline:{deadline_at:t,elapsed_ms:i-Date.parse(t)},...n===null?{}:{message:n.message,fault_kind:n.kind,error_class:v(n).class}}}})}catch(o){throw d({level:"error",event:"node_deadline.terminalize_failed",node_id:r,error:o,fields:{deadline_at:t}}),o}if(l.status!=="canceled"||l.outcome_reason!=="deadline_exceeded")return;await e?.assertOwned();const f=await _(r);if(!f.confirmed)throw f.error??new Error("broker teardown was not confirmed");await e?.assertOwned();try{await x(r,"notice.deadline","child-deadline-exceeded",{reason:"child-deadline-exceeded",child:r})}catch(o){d({level:"error",event:"node_deadline.notify_failed",node_id:r,error:o,fields:{deadline_at:t}})}try{d({level:"error",event:"node_deadline.expired",node_id:r,fields:{deadline_at:t,elapsed_ms:i-Date.parse(t)}})}catch{}}s(k,"expireNodeDeadline");export{z as NodeDeadlineReconciler,k as expireNodeDeadline};
@@ -37,10 +37,9 @@ export declare function thawTier(row: NodeRow, pendingWake: boolean): ThawTier;
37
37
  * splits the saturation counts). A candidate with no `frozen_at` has not waited at all and sorts last. */
38
38
  export declare function compareThaw(a: ThawEntry, b: ThawEntry): number;
39
39
  export declare class FreezeLaneReconciler {
40
- private readonly relaunchState;
41
40
  /** End a stuck-delivery episode the tick decided is over — the node's profile
42
41
  * paused, or it has no wake-worthy mail left. */
43
- endEpisode(nodeId: string): void;
42
+ endEpisode(nodeId: string): Promise<void>;
44
43
  run(now: number, ctx: FreezeLaneContext): Promise<FrozenCounts>;
45
44
  private enact;
46
45
  /** A parked resident owed wake mail: the message-delivery reopen (resident, latch check, `revive`
@@ -1 +1 @@
1
- var E=Object.defineProperty;var d=(o,t)=>E(o,"name",{value:t,configurable:!0});import{clearFrozen as f,getNode as h,getRow as R}from"../../../core/canvas/index.js";import{fullName as S}from"../../../core/canvas/labels.js";import{isSafeNodeId as s}from"../../../core/canvas/paths.js";import{emitEvent as c}from"../../../core/events/emit.js";import{operationIdContext as g}from"../../../core/events/operation-id.js";import{acknowledgedNodeMailCount as z}from"../../../core/canvas/deliveries.js";import{publishNotice as A}from"../../../core/runtime/close.js";import{transition as l}from"../../../core/runtime/lifecycle.js";import{isStartRefusal as w}from"../../../core/runtime/start-check.js";import{commitReopenResident as k}from"../../../core/runtime/reopen.js";import{reviveNode as p}from"../../../core/runtime/revive.js";import{hasPendingWake as v}from"../live-obligation.js";import{readInterruptedTurn as L}from"../../../core/canvas/recovery.js";import{mapAsync as M}from"../../../core/async-array.js";const m=6,C=10*6e4,N=2e4;function D(o,t){if(t)return 2;const a=o.launched_at;if(a==null)return 1;const e=o.last_settled_at;return e==null?2:Date.parse(e)>Date.parse(a)?1:2}d(D,"thawTier");function T(o,t){const a=o.row.frozen_at,e=t.row.frozen_at;return a==null?e==null?0:1:e==null?-1:Date.parse(a)-Date.parse(e)}d(T,"compareThaw");class G{static{d(this,"FreezeLaneReconciler")}relaunchState=new Map;endEpisode(t){this.relaunchState.delete(t)}async run(t,a){const e=await M(a.candidates,async n=>({...n,tier:D(n.row,await v(n.row.node_id))}));e.sort(T);const r={stalled:0,waitingOnMail:0};for(const n of e)await g.fresh(()=>this.enact(t,n,a.fleet))==="frozen"&&(n.tier===1?r.stalled+=1:r.waitingOnMail+=1);return r}async enact(t,a,e){const r=a.row.node_id;try{return a.enact==="thaw"?await this.enactThaw(a.row):a.enact==="reopen"?await this.enactReopen(a.row):a.enact==="idle-release"?await this.enactIdleRelease(t,a.row):(await e.applyDeadRowPolicy(r),e.has(r)?"launched":(await R(r))?.frozen_at!=null?"frozen":"skipped")}catch(n){return c({level:"error",event:"node.lifecycle.enact_failed",...s(r)?{node_id:r}:{},error:n,fields:{...s(r)?{}:{affected_node_id:r},enact:a.enact}}),"skipped"}}async enactReopen(t){await k(t.node_id);try{return(await p(t.node_id,{resume:!0,capacity:"freeze"})).outcome==="frozen"?"frozen":"launched"}catch(a){if(w(a))return await this.settleRefusedStart(t.node_id,a,"reopen");throw a}}async settleRefusedStart(t,a,e){return await l(t,"release"),await f(t),c({level:"warn",event:"node.start.refused",node_id:t,error:a,fields:{enact:e}}),"skipped"}async enactThaw(t){const a=(await L(t.node_id))?.interrupted===!0;if(!a&&t.lifecycle==="resident"&&(await h(t.node_id))?.pi_session_id!=null&&!await v(t.node_id))return await l(t.node_id,"release"),await f(t.node_id),"skipped";try{return(await p(t.node_id,{resume:!0,capacity:"freeze",recovery:!0,...a?{wakeReason:"runtime-restart-abort"}:{}})).outcome==="frozen"?"frozen":"launched"}catch(e){if(w(e))return await this.settleRefusedStart(t.node_id,e,"thaw");const r=await h(t.node_id);throw r!==null&&(r.status==="active"||r.status==="idle")&&(await l(t.node_id,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:e instanceof Error?e.message:String(e)}}}),await f(t.node_id)),e}}async enactIdleRelease(t,a){const e=a.node_id,r=await z(e),n=this.relaunchState.get(e),u=n===void 0||n.acknowledgedCount!==r,i=u?0:n.attempts;if(i>=m)return this.relaunchState.delete(e),c({level:"warn",event:"broker.idle_release.exhausted",...s(e)?{node_id:e}:{},fields:{...s(e)?{}:{affected_node_id:e},attempts:i,max_attempts:m}}),await l(e,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",respawn_failures:i}}}),await A(e,"notice.crash",`Child crashed \u2014 ${S(a)} (${e}) failed to relaunch after ${i} attempts while trying to deliver a pending inbox message (no delivery progress across any attempt), and is now dead. It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`,{reason:"child-crashed",child:e}),"skipped";const _=u?void 0:n?.lastAttempt,y=Math.min(N*2**i,C);return _!==void 0&&t-_<y?"skipped":(await p(e,{resume:!0,capacity:"freeze"})).outcome==="frozen"?"frozen":(this.relaunchState.set(e,{acknowledgedCount:r,attempts:i+1,lastAttempt:t}),c({level:"info",event:"broker.revive.idle_release",...s(e)?{node_id:e}:{},attempt:i+1,fields:{...s(e)?{}:{affected_node_id:e},attempts:i+1,max_attempts:m}}),"launched")}}export{G as FreezeLaneReconciler,T as compareThaw,D as thawTier};
1
+ var g=Object.defineProperty;var s=(i,e)=>g(i,"name",{value:e,configurable:!0});import{clearFrozen as p,getNode as h,getRow as y}from"../../../core/canvas/index.js";import{openDb as c}from"../../../core/canvas/db.js";import{fullName as k}from"../../../core/canvas/labels.js";import{isSafeNodeId as d}from"../../../core/canvas/paths.js";import{emitEvent as _}from"../../../core/events/emit.js";import{operationIdContext as S}from"../../../core/events/operation-id.js";import{acknowledgedNodeMailCount as D}from"../../../core/canvas/deliveries.js";import{publishNotice as N}from"../../../core/runtime/close.js";import{transition as u}from"../../../core/runtime/lifecycle.js";import{isStartRefusal as E}from"../../../core/runtime/start-check.js";import{EnvironmentImagePending as R}from"./remote-wake.js";import{commitReopenResident as z}from"../../../core/runtime/reopen.js";import{reviveNode as m}from"../../../core/runtime/revive.js";import{hasPendingWake as v}from"../live-obligation.js";import{readInterruptedTurn as T}from"../../../core/canvas/recovery.js";import{mapAsync as L}from"../../../core/async-array.js";const f=6,C=10*6e4,b=2e4;async function I(i){const e=await(await c()).prepare("SELECT idle_release_attempts, idle_release_last_attempt_at, idle_release_acked_count FROM nodes WHERE node_id = ?").get(i);return{attempts:Number(e?.idle_release_attempts??0),acknowledgedCount:Number(e?.idle_release_acked_count??0),lastAttempt:e?.idle_release_last_attempt_at==null?void 0:Date.parse(e.idle_release_last_attempt_at)}}s(I,"readEpisode");async function M(i,e,a,t,r){const n=await(await c()).prepare("UPDATE nodes SET idle_release_attempts = ?, idle_release_last_attempt_at = ?, idle_release_acked_count = ? WHERE node_id = ? AND idle_release_attempts = ?").run(a,new Date(r).toISOString(),t,i,e);return Number(n.changes)===1}s(M,"claimAttempt");async function O(i,e){await(await c()).prepare("UPDATE nodes SET idle_release_attempts = ?, idle_release_last_attempt_at = ?, idle_release_acked_count = ? WHERE node_id = ?").run(e.attempts,e.lastAttempt===void 0?null:new Date(e.lastAttempt).toISOString(),e.acknowledgedCount,i)}s(O,"writeEpisode");function P(i,e){if(e)return 2;const a=i.launched_at;if(a==null)return 1;const t=i.last_settled_at;return t==null?2:Date.parse(t)>Date.parse(a)?1:2}s(P,"thawTier");function U(i,e){const a=i.row.frozen_at,t=e.row.frozen_at;return a==null?t==null?0:1:t==null?-1:Date.parse(a)-Date.parse(t)}s(U,"compareThaw");class te{static{s(this,"FreezeLaneReconciler")}async endEpisode(e){await(await c()).prepare("UPDATE nodes SET idle_release_attempts = 0, idle_release_last_attempt_at = NULL, idle_release_acked_count = 0 WHERE node_id = ? AND (idle_release_attempts <> 0 OR idle_release_last_attempt_at IS NOT NULL OR idle_release_acked_count <> 0)").run(e)}async run(e,a){const t=await L(a.candidates,async n=>({...n,tier:P(n.row,await v(n.row.node_id))}));t.sort(U);const r={stalled:0,waitingOnMail:0};for(const n of t)await S.fresh(()=>this.enact(e,n,a.fleet))==="frozen"&&(n.tier===1?r.stalled+=1:r.waitingOnMail+=1);return r}async enact(e,a,t){const r=a.row.node_id;try{return a.enact==="thaw"?await this.enactThaw(a.row):a.enact==="reopen"?await this.enactReopen(a.row):a.enact==="idle-release"?await this.enactIdleRelease(e,a.row):(await t.applyDeadRowPolicy(r),await t.has(r)?"launched":(await y(r))?.frozen_at!=null?"frozen":"skipped")}catch(n){return _({level:"error",event:"node.lifecycle.enact_failed",...d(r)?{node_id:r}:{},error:n,fields:{...d(r)?{}:{affected_node_id:r},enact:a.enact}}),"skipped"}}async enactReopen(e){await z(e.node_id);try{return(await m(e.node_id,{resume:!0,capacity:"freeze"})).outcome==="frozen"?"frozen":"launched"}catch(a){if(E(a)||a instanceof R)return await this.settleRefusedStart(e.node_id,a,"reopen");throw a}}async settleRefusedStart(e,a,t){return await u(e,"release"),await p(e),_({level:"warn",event:"node.start.refused",node_id:e,error:a,fields:{enact:t}}),"skipped"}async enactThaw(e){const a=(await T(e.node_id))?.interrupted===!0;if(!a&&e.lifecycle==="resident"&&(await h(e.node_id))?.pi_session_id!=null&&!await v(e.node_id))return await u(e.node_id,"release"),await p(e.node_id),"skipped";try{return(await m(e.node_id,{resume:!0,capacity:"freeze",recovery:!0,...a?{wakeReason:"runtime-restart-abort"}:{}})).outcome==="frozen"?"frozen":"launched"}catch(t){if(E(t)||t instanceof R)return await this.settleRefusedStart(e.node_id,t,"thaw");const r=await h(e.node_id);throw r!==null&&(r.status==="active"||r.status==="idle")&&(await u(e.node_id,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:t instanceof Error?t.message:String(t)}}}),await p(e.node_id)),t}}async enactIdleRelease(e,a){const t=a.node_id,r=await D(t),n=await I(t),l=n.attempts===0||n.acknowledgedCount!==r,o=l?0:n.attempts;if(o>=f)return await this.endEpisode(t),_({level:"warn",event:"broker.idle_release.exhausted",...d(t)?{node_id:t}:{},fields:{...d(t)?{}:{affected_node_id:t},attempts:o,max_attempts:f}}),await u(t,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",respawn_failures:o}}}),await N(t,"notice.crash",`Child crashed \u2014 ${k(a)} (${t}) failed to relaunch after ${o} attempts while trying to deliver a pending inbox message (no delivery progress across any attempt), and is now dead. It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${t}\`.`,{reason:"child-crashed",child:t}),"skipped";const w=l?void 0:n.lastAttempt,A=Math.min(b*2**o,C);return w!==void 0&&e-w<A||!await M(t,n.attempts,o+1,r,e)?"skipped":(await m(t,{resume:!0,capacity:"freeze"})).outcome==="frozen"?(await O(t,{attempts:l?0:o,lastAttempt:l?void 0:n.lastAttempt,acknowledgedCount:r}),"frozen"):(_({level:"info",event:"broker.revive.idle_release",...d(t)?{node_id:t}:{},attempt:o+1,fields:{...d(t)?{}:{affected_node_id:t},attempts:o+1,max_attempts:f}}),"launched")}}export{te as FreezeLaneReconciler,U as compareThaw,P as thawTier};
@@ -0,0 +1,31 @@
1
+ import { type Execution } from '../../../core/canvas/executions.js';
2
+ import type { NodeMeta } from '../../../core/canvas/index.js';
3
+ import { type EnvironmentImage } from '../recipe-image.js';
4
+ /** A `launching` execution running past this since `started_at` is stopped: the 30-minute recipe setup limit plus boot. */
5
+ export declare const LAUNCH_DEADLINE_MS: number;
6
+ /** A `launching` execution with no process is given this long before it is called lost, so the starter's own call is not raced. */
7
+ export declare const LAUNCH_LOST_GRACE_MS = 120000;
8
+ /** The release the control plane runs, which is the release a wake names. */
9
+ export declare const acceptedRelease: () => string;
10
+ /** The Blaxel environment the node's profile points at, or null when it runs on a machine. Reads rows only, so it is safe before any environment is registered. */
11
+ export declare function blaxelEnvironmentOf(profileId: string | null): Promise<string | null>;
12
+ /** Step 2: the `funding bill` check. A refusal writes `nodes.spend_refusal` and one refused `sandbox` row per node, limit and month,
13
+ * then throws `402 cap_exhausted`; no execution row is written and no runner is called. A pass clears `spend_refusal`. */
14
+ export declare function checkBilledWake(meta: NodeMeta): Promise<void>;
15
+ /** A recipe environment with no built image: the wake is not started, the mail stays pending, and the image-built tick wakes it. */
16
+ export declare class EnvironmentImagePending extends Error {
17
+ readonly environmentId: string;
18
+ readonly imageError: string | null;
19
+ constructor(environmentId: string, imageError: string | null);
20
+ }
21
+ /** D95: the image a new sandbox is created from. `pending` means the environment has a recipe and no image has built. */
22
+ export declare function imageForWake(environmentId: string): Promise<EnvironmentImage>;
23
+ /** Step 3: a `running` Blaxel execution is a resident broker, possibly frozen. On the accepted release it is woken and nothing else
24
+ * is written. On another release it is asked to stop for the release first, so the thawed broker exits and the next claim starts
25
+ * a new attempt on the accepted one. `lost` means no broker answered: the execution is ended and the caller starts a new attempt. */
26
+ export declare function wakeResident(execution: Execution): Promise<'woken' | 'lost'>;
27
+ /** A `running` remote execution is live while its runner reports the broker process running, frozen or not. When the runner
28
+ * reports it gone (or ended non-zero), that exact execution is ended; a runner that cannot tell leaves it as it is. */
29
+ export declare function endRemoteExecutionIfGone(execution: Execution): Promise<boolean>;
30
+ /** The recovery table for a `launching` Blaxel execution found by a claim after its starter is gone. */
31
+ export declare function recoverLaunching(nodeId: string, now: number): Promise<void>;
@@ -0,0 +1 @@
1
+ var p=Object.defineProperty;var a=(e,t)=>p(e,"name",{value:t,configurable:!0});import{liveExecutionForNode as m,endExecution as d,recordExecutionAwake as w,endLaunchingExecution as s,requestExecutionStop as h}from"../../../core/canvas/executions.js";import{openDb as c}from"../../../core/canvas/db.js";import{emitEvent as u}from"../../../core/events/emit.js";import{findRefusal as _,refusalError as x,refusedMark as E,writeCharge as g}from"../../../core/ledger/index.js";import{transition as f}from"../../../core/runtime/lifecycle.js";import{runnerForExecution as l}from"../../../core/runtime/runner/index.js";import{startCandidateOf as b}from"../../../core/runtime/start-check.js";import{settleSandboxBeforeWake as v}from"../ledger-sandbox.js";import{crtrVersion as k}from"../../../shared/crtr-version.js";import{resolveEnvironmentImage as I}from"../recipe-image.js";const S=45*6e4,y=12e4,N=a(()=>k(),"acceptedRelease");async function j(e){return e===null?null:(await(await c()).prepare("SELECT e.object AS object FROM profiles p JOIN environments e ON e.object = p.environment_id WHERE p.id = ? AND e.kind = 'blaxel'").get(e))?.object??null}a(j,"blaxelEnvironmentOf");async function F(e){const t=await b(e);if(t===null)return;const n={scope:"call",payer:t.payer,funding:"bill",sub:t.sub,grantee:t.grantee,provider:"blaxel"},r=await _(n),o=await c();if(r===null){await o.prepare("UPDATE nodes SET spend_refusal = NULL WHERE node_id = ? AND spend_refusal IS NOT NULL").run(e.node_id);return}throw await o.prepare("UPDATE nodes SET spend_refusal = ? WHERE node_id = ?").run(JSON.stringify({limit:r.limit.id,usd:r.usd,reset_at:r.reset_at,at:new Date().toISOString()}),e.node_id),await g({id:`refused:sandbox:${e.node_id}:${r.limit.id}:${r.month}`,kind:"sandbox",what:"wake",usd:null,payer:n.payer,funding:"bill",sub:n.sub,grantee:n.grantee,node:e.node_id,run:e.run_id??null,provider:"blaxel",refused:E(r)}),x(r)}a(F,"checkBilledWake");class M extends Error{static{a(this,"EnvironmentImagePending")}environmentId;imageError;constructor(t,n){super(`environment ${t} has no built image yet${n===null?"":` (${n})`}; the wake waits for the build.`),this.environmentId=t,this.imageError=n,this.name="EnvironmentImagePending"}}async function P(e){return await I(e)}a(P,"imageForWake");async function B(e){return e.release!==N()&&await h(e.nodeId,{afterTurn:!1,reason:"release"}),await l(e.runner).wake(e)==="no_broker"?(await d(e.executionId,{reason:"lost"}),"lost"):(await v(e.executionId,Date.now()),await w(e.executionId),"woken")}a(B,"wakeResident");async function G(e){const t=await l(e.runner).status(e);if(t.state!=="gone"&&t.state!=="failed")return!1;const n=await d(e.executionId,{reason:"lost"});return n&&u({level:"warn",event:"execution.lost",node_id:e.nodeId}),n}a(G,"endRemoteExecutionIfGone");async function J(e,t){const n=await m(e);if(n===null||n.state!=="launching"||n.runner==="process")return;const r=l(n.runner),o=t-Date.parse(n.startedAt),i=await r.status(n);if(i.state!=="unknown"){if(i.state==="running"){if(o<=S||!await s(n.executionId,{reason:"launch_timeout"}))return;await r.stop(n,{reason:"launch_timeout"}),await f(e,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:"the sandbox launch did not report within the launch deadline"}}}),u({level:"warn",event:"execution.launch_timeout",node_id:e});return}if(i.state==="failed"){if(!await s(n.executionId,{reason:"launch_failed"}))return;await f(e,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:`the sandbox start script failed: ${i.reason}`}}});return}if(o>y){if(!await s(n.executionId,{reason:"launch_lost"}))return;u({level:"warn",event:"execution.launch_lost",node_id:e})}}}a(J,"recoverLaunching");export{M as EnvironmentImagePending,S as LAUNCH_DEADLINE_MS,y as LAUNCH_LOST_GRACE_MS,N as acceptedRelease,j as blaxelEnvironmentOf,F as checkBilledWake,G as endRemoteExecutionIfGone,P as imageForWake,J as recoverLaunching,B as wakeResident};
@@ -0,0 +1,5 @@
1
+ /** A stop request this old was queued by a process that is no longer finishing it: the requesting process runs its own
2
+ * teardown within seconds (after its response settles). */
3
+ export declare const ORPHANED_STOP_AGE_MS = 30000;
4
+ /** Enact every stop whose requesting process is gone, from the execution row's `stop_*` columns. */
5
+ export declare function enactRequestedStops(now: number): Promise<void>;
@@ -0,0 +1,2 @@
1
+ var d=Object.defineProperty;var t=(e,r)=>d(e,"name",{value:r,configurable:!0});import{endLiveExecution as s,getNode as p}from"../../../core/canvas/index.js";import{executionsWithStopRequestedBefore as i}from"../../../core/canvas/executions.js";import{openDb as f}from"../../../core/canvas/db.js";import{emitEvent as o}from"../../../core/events/emit.js";import{runnerForExecution as u}from"../../../core/runtime/runner/index.js";const l=3e4;async function m(e,r){return r==null?!1:(await(await f()).prepare(`SELECT type FROM run_events WHERE node_id = ? AND type IN ('node.turn.started', 'node.turn.completed')
2
+ ORDER BY sequence_number DESC LIMIT 1`).get(e))?.type==="node.turn.started"}t(m,"turnIsOpen");async function c(e){const r=await p(e.nodeId);if(r===null||e.stopAfterTurn&&await m(e.nodeId,r.run_id))return;const a=await u(e.runner).stop(e,{reason:e.stopReason??"stopped"});if(!a.confirmed){o({level:"error",event:"broker.teardown.unconfirmed",node_id:e.nodeId,error:a.error});return}await s(e.nodeId,{reason:e.stopReason??"stopped"}),o({level:"info",event:"broker.stop.enacted_from_row",node_id:e.nodeId,fields:{reason:e.stopReason,after_turn:e.stopAfterTurn}})}t(c,"enact");async function S(e){for(const r of await i(new Date(e-l).toISOString()))try{await c(r)}catch(n){o({level:"error",event:"broker.stop.enact_failed",node_id:r.nodeId,error:n})}}t(S,"enactRequestedStops");export{l as ORPHANED_STOP_AGE_MS,S as enactRequestedStops};
@@ -1 +1 @@
1
- var y=Object.defineProperty;var f=(r,t)=>y(r,"name",{value:t,configurable:!0});import{clearFrozen as p,endLiveExecution as o,getNode as P}from"../../../core/canvas/index.js";import{isSafeNodeId as k}from"../../../core/canvas/paths.js";import{recordedPidLiveness as u}from"../../../core/canvas/pid.js";import{emitEvent as c}from"../../../core/events/emit.js";import{operationIdContext as v}from"../../../core/events/operation-id.js";import{readInterruptedTurn as L}from"../../../core/canvas/recovery.js";import{isProfilePaused as l}from"../../../core/profiles/manifest.js";import{isRecoveryHeld as m}from"../../../core/runtime/recovery-hold.js";import{hasUnseenWakeMail as h}from"../../../core/canvas/deliveries.js";import{hasIdleReleaseIntent as R,isParkedResident as z}from"../../../core/runtime/lifecycle.js";import{classifyDeadNode as I}from"../../fleet.js";import{applyParkEvent as E,hasPendingWake as M,parkEvent as g}from"../live-obligation.js";import{FreezeLaneReconciler as N}from"./freeze-lane.js";import{isInBackoff as _}from"./respawn-policy.js";import{handleTerminatingRow as b}from"./terminating.js";import{wantsExecution as C}from"./wants-execution.js";function D(r){return r.status==="done"||r.status==="canceled"||r.status==="dead"}f(D,"isTerminal");const F=12e4;function T(r,t){const e=Math.max(...[r.created,r.launched_at].map(a=>a==null?0:Date.parse(a)||0));return e>0&&t-e<F}f(T,"isLaunchInFlight");class ${static{f(this,"NodeLifecycleReconciler")}freezeLane=new N;async run(t,e){const a=[];for(const n of e.rows)await v.fresh(async()=>{try{await this.classifyRow(t,n,e,a)}catch(i){const s=n.node_id;c({level:"error",event:"node.lifecycle.classify_failed",...k(s)?{node_id:s}:{fields:{affected_node_id:s}},error:i})}});return this.freezeLane.run(t,{fleet:e.fleet,candidates:a})}async classifyRow(t,e,a,n){const i=e.node_id;if(D(e)){if(b(e,a.fleet.has(i)))return;if(z(e)&&!a.fleet.has(i)&&!l(e.profile_id)&&!m(i)&&!_(e,t)&&await h(i)){n.push({row:e,enact:"reopen"});return}e.pi_pid!=null&&u(e.pi_pid,e.pi_pid_identity)==="dead"&&await o(i,{reason:"observed-dead"}),e.frozen_at!=null&&(e.final_report!=null?await p(i):n.push({row:e,enact:"thaw"}));return}if(a.fleet.has(i)){l(e.profile_id)?a.fleet.interruptForProfilePause(i):await this.thawMark(e);return}if(l(e.profile_id)){this.freezeLane.endEpisode(i);return}if(m(i))return;const s=e.pi_pid!=null&&u(e.pi_pid,e.pi_pid_identity)==="dead";if(s&&!a.fleet.hasProfilePauseInterruption(i)&&await o(i,{reason:"observed-dead"}),!await C(e)){await this.thawMark(e),await this.settleParked(e);return}if(_(e,t))return;if(e.status==="idle"&&R(e)&&await h(i)){n.push({row:e,enact:"idle-release"});return}if(e.pi_pid==null){if(e.respawn_not_before!=null){n.push({row:e,enact:"policy"});return}if(e.frozen_at!=null){n.push({row:e,enact:"thaw"});return}if(a.fleet.isClaimed(i)||T(e,t))return;c({level:"warn",event:"node.lifecycle.stranded_launch",node_id:i}),n.push({row:e,enact:"thaw"});return}if(!s)return;const d=I(await P(i),(await L(i))?.interrupted===!0,await M(i));if(d==="respawn-fresh"||d==="respawn-cycle"||d==="respawn-resume"){n.push({row:e,enact:"policy"});return}await a.fleet.applyDeadRowPolicy(i),await this.thawMark(e)}async thawMark(t){t.frozen_at!=null&&await p(t.node_id)}async settleParked(t){if(t.status!=="idle")return;this.freezeLane.endEpisode(t.node_id);const e=await g(t.node_id,t.lifecycle);e!=="release"&&await E(t.node_id,e)}}export{F as LAUNCH_GRACE_MS,$ as NodeLifecycleReconciler,T as isLaunchInFlight};
1
+ var k=Object.defineProperty;var l=(r,t)=>k(r,"name",{value:t,configurable:!0});import{clearFrozen as m,endLiveExecution as h,getNode as R}from"../../../core/canvas/index.js";import{isSafeNodeId as b}from"../../../core/canvas/paths.js";import{recordedPidLiveness as _}from"../../../core/canvas/pid.js";import{hasObservedExit as E,hasAbsorbedProfilePauseStop as L}from"../../../core/canvas/executions.js";import{emitEvent as y}from"../../../core/events/emit.js";import{operationIdContext as z}from"../../../core/events/operation-id.js";import{readInterruptedTurn as I}from"../../../core/canvas/recovery.js";import{isProfilePaused as d}from"../../../core/profiles/manifest.js";import{isRecoveryHeld as v}from"../../../core/runtime/recovery-hold.js";import{hasUnseenWakeMail as p}from"../../../core/canvas/deliveries.js";import{hasIdleReleaseIntent as g,isParkedResident as w}from"../../../core/runtime/lifecycle.js";import{classifyDeadNode as M}from"../../fleet.js";import{applyParkEvent as N,hasPendingWake as F,parkEvent as x}from"../live-obligation.js";import{FreezeLaneReconciler as A}from"./freeze-lane.js";import{isInBackoff as P}from"./respawn-policy.js";import{handleTerminatingRow as C}from"./terminating.js";import{wantsExecution as D}from"./wants-execution.js";import{enactRequestedStops as S}from"./requested-stops.js";import{endRemoteExecutionIfGone as T,recoverLaunching as G,wakeResident as H}from"./remote-wake.js";import{liveExecutionForNode as U}from"../../../core/canvas/executions.js";function W(r){return r.status==="done"||r.status==="canceled"||r.status==="dead"}l(W,"isTerminal");const q=12e4;function B(r,t){const e=Math.max(...[r.created,r.launched_at].map(a=>a==null?0:Date.parse(a)||0));return e>0&&t-e<q}l(B,"isLaunchInFlight");class pe{static{l(this,"NodeLifecycleReconciler")}freezeLane=new A;async run(t,e){const a=[];await S(t);for(const n of e.rows)await z.fresh(async()=>{try{await this.classifyRow(t,n,e,a)}catch(i){const s=n.node_id;y({level:"error",event:"node.lifecycle.classify_failed",...b(s)?{node_id:s}:{fields:{affected_node_id:s}},error:i})}});return this.freezeLane.run(t,{fleet:e.fleet,candidates:a})}async classifyRow(t,e,a,n){const i=e.node_id;if(W(e)){if(C(e,await a.fleet.has(i)))return;if(w(e)&&!await a.fleet.has(i)&&!d(e.profile_id)&&!await v(i)&&!P(e,t)&&await p(i)){n.push({row:e,enact:"reopen"});return}e.pi_pid!=null&&_(e.pi_pid,e.pi_pid_identity)==="dead"&&await h(i,{reason:"observed-dead"}),e.frozen_at!=null&&(e.final_report!=null||e.status==="dead"?await m(i):n.push({row:e,enact:"thaw"}));return}if(await a.fleet.has(i)){const f=await U(i);if(f?.runner==="blaxel"&&f.state==="running"&&!d(e.profile_id)&&await p(i)){if(await H(f)==="lost")return}else f?.runner==="blaxel"&&f.state==="running"&&await T(f);if(await a.fleet.has(i)){d(e.profile_id)?await a.fleet.interruptForProfilePause(i):await this.thawMark(e);return}}if(await G(i,t),d(e.profile_id)){await this.freezeLane.endEpisode(i);return}if(await v(i))return;const s=e.pi_pid==null&&await E(i),u=e.pi_pid==null&&!s&&await L(i),c=s||u||e.pi_pid!=null&&_(e.pi_pid,e.pi_pid_identity)==="dead";if(c&&!await a.fleet.hasProfilePauseInterruption(i)&&await h(i,{reason:"observed-dead"}),!await D(e)){await this.thawMark(e),await this.settleParked(e);return}if(P(e,t))return;if(e.status==="idle"&&g(e)&&await p(i)){n.push({row:e,enact:"idle-release"});return}if(e.pi_pid==null&&(!s||e.frozen_at!=null)&&!u){if(e.respawn_not_before!=null){n.push({row:e,enact:"policy"});return}if(e.frozen_at!=null){n.push({row:e,enact:"thaw"});return}if(await a.fleet.isClaimed(i)||B(e,t))return;y({level:"warn",event:"node.lifecycle.stranded_launch",node_id:i}),n.push({row:e,enact:"thaw"});return}if(!c)return;const o=M(await R(i),(await I(i))?.interrupted===!0,await F(i));if(o==="respawn-fresh"||o==="respawn-cycle"||o==="respawn-resume"){n.push({row:e,enact:"policy"});return}await a.fleet.applyDeadRowPolicy(i),await this.thawMark(e)}async thawMark(t){t.frozen_at!=null&&await m(t.node_id)}async settleParked(t){if(t.status!=="idle")return;await this.freezeLane.endEpisode(t.node_id);const e=await x(t.node_id,t.lifecycle);e!=="release"&&await N(t.node_id,e)}}export{q as LAUNCH_GRACE_MS,pe as NodeLifecycleReconciler,B as isLaunchInFlight};
@@ -1 +1 @@
1
- var f=Object.defineProperty;var n=(t,e)=>f(t,"name",{value:e,configurable:!0});import{emitEvent as s}from"../../core/events/emit.js";import{operationIdContext as c}from"../../core/events/operation-id.js";import{listSubmitRequestedRequests as l}from"../../core/human/requests.js";import{completeRequestedSubmit as m}from"../review/finish.js";class b{static{n(this,"PendingReviewSubmitReconciler")}async run(e,i){if(!i.lifecycle.acceptsDetachedWork())return;let o;try{o=await l()}catch(r){c.fresh(()=>{s({level:"error",event:"review.deferred_submit.scan_failed",error:r})});return}for(const r of o)i.lifecycle.registerDetached(m(r.request_id,{atCompanionTurnEnd:!1}).then(()=>{},d=>{c.fresh(()=>{s({level:"error",event:"review.deferred_submit.failed",error:d})})}))}}export{b as PendingReviewSubmitReconciler};
1
+ var c=Object.defineProperty;var s=(i,r)=>c(i,"name",{value:r,configurable:!0});import{emitEvent as a}from"../../core/events/emit.js";import{operationIdContext as m}from"../../core/events/operation-id.js";import{listSubmitRequestedRequests as d}from"../../core/human/requests.js";import{completeRequestedSubmit as f}from"../review/finish.js";import{withRowClaim as l}from"./row-claim.js";const u=6e4;class R{static{s(this,"PendingReviewSubmitReconciler")}async run(r,o){if(!o.lifecycle.acceptsDetachedWork())return;let n;try{n=await d()}catch(e){m.fresh(()=>{a({level:"error",event:"review.deferred_submit.scan_failed",error:e})});return}for(const e of n)o.lifecycle.registerDetached(l("human_requests",e.request_id,u,async t=>{await t.assertOwned(),await f(e.request_id,{atCompanionTurnEnd:!1})}).then(()=>{},t=>{m.fresh(()=>{a({level:"error",event:"review.deferred_submit.failed",error:t})})}))}}export{R as PendingReviewSubmitReconciler};
@@ -0,0 +1,49 @@
1
+ import { type Recipe } from '../../core/runtime/runner/blaxel/image-build.js';
2
+ import type { DetachedWorkLifecycle } from './broker-supervision.js';
3
+ /** How long a claim holds without renewal. The holder renews at a third of it. */
4
+ export declare const LEASE_MS: number;
5
+ /** After a failed build the same revision is not tried again for this long. */
6
+ export declare const RETRY_AFTER_FAILURE_MS: number;
7
+ export type EnvironmentImage = {
8
+ state: 'base';
9
+ } | {
10
+ state: 'image';
11
+ ref: string;
12
+ revision: number;
13
+ } | {
14
+ state: 'pending';
15
+ error: string | null;
16
+ };
17
+ /** What a wake of a node in `environmentId` creates its sandbox from (D25, D95). `base`: the environment has no recipe content (never set, or cleared). `pending`: it has one and no image has ever built, so the wake must not start a runner call. */
18
+ export declare function resolveEnvironmentImage(environmentId: string): Promise<EnvironmentImage>;
19
+ /** Starts a supervision tick when an environment's image is built, so the nodes waiting on it wake now instead of at the next beat. Returns the unsubscribe. */
20
+ export declare function subscribeImageBuilt(requestTick: (environmentId: string) => void): Promise<() => void>;
21
+ export interface RecipeBuildInput {
22
+ environmentId: string;
23
+ revision: number;
24
+ recipe: Recipe;
25
+ person: string;
26
+ }
27
+ export interface RecipeImageDeps {
28
+ /** Builds and pushes the image; returns its reference. The default clones with a G-1 token and uses Blaxel's build API. */
29
+ build?: (input: RecipeBuildInput) => Promise<{
30
+ ref: string;
31
+ }>;
32
+ now?: () => number;
33
+ }
34
+ export declare class RecipeImageReconciler {
35
+ private readonly deps;
36
+ constructor(deps?: RecipeImageDeps);
37
+ /** One pass: claim every environment with an unbuilt revision and build it detached from the tick. */
38
+ run(nowMs: number, ctx: {
39
+ lifecycle: DetachedWorkLifecycle;
40
+ }): Promise<void>;
41
+ private claim;
42
+ private buildClaimed;
43
+ private recordFailure;
44
+ }
45
+ /** Nodes of profiles that run in `environmentId` with wake mail unseen and no execution: the ones a missing image keeps dormant. */
46
+ export declare function nodesWaitingOnImage(environmentId: string): Promise<Array<{
47
+ node_id: string;
48
+ run_id: string;
49
+ }>>;