@north-light/cli 0.3.561 → 0.3.563

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 (579) 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/build-root.d.ts +1 -0
  23. package/dist/build-root.js +1 -1
  24. package/dist/builtin-memory/crouter-sdk/release-notes.md +4 -0
  25. package/dist/cli.js +7 -7
  26. package/dist/clients/attach/chrome/bash-jobs.d.ts +11 -1
  27. package/dist/clients/attach/chrome/bash-jobs.js +1 -1
  28. package/dist/clients/attach/chrome/canvas-panels.d.ts +1 -1
  29. package/dist/clients/attach/chrome/roster.js +6 -4
  30. package/dist/clients/attach/overlays/auth.js +2 -2
  31. package/dist/clients/attach/render/chat-view.js +1 -1
  32. package/dist/clients/attach/viewer.js +550 -567
  33. package/dist/commands/canvas-config.js +2 -2
  34. package/dist/commands/canvas-use.js +1 -1
  35. package/dist/commands/human.js +2 -2
  36. package/dist/commands/node/bash.js +3 -3
  37. package/dist/commands/node/create.js +2 -2
  38. package/dist/commands/person-settings.d.ts +1 -0
  39. package/dist/commands/person-settings.js +1 -0
  40. package/dist/commands/pkg/market-manage.d.ts +2 -2
  41. package/dist/commands/pkg/market-manage.js +1 -1
  42. package/dist/commands/pkg/plugin-manage.js +13 -13
  43. package/dist/commands/pkg/shared.js +1 -1
  44. package/dist/commands/profile/default.js +1 -1
  45. package/dist/commands/push.js +2 -3
  46. package/dist/commands/sys/config.js +1 -1
  47. package/dist/commands/sys/connect.js +1 -1
  48. package/dist/commands/sys/context/sysprompt.js +2 -2
  49. package/dist/commands/sys/doctor.js +1 -1
  50. package/dist/commands/sys/join.js +1 -1
  51. package/dist/commands/sys/panels/broker-limits-panel.js +1 -1
  52. package/dist/commands/sys/panels/keybindings-panel.js +1 -1
  53. package/dist/commands/sys/panels/models-panel.d.ts +8 -2
  54. package/dist/commands/sys/panels/models-panel.js +1 -1
  55. package/dist/commands/sys/panels/prefs-panel.d.ts +1 -0
  56. package/dist/commands/sys/panels/prefs-panel.js +1 -1
  57. package/dist/commands/sys/panels/profiles-panel.js +1 -1
  58. package/dist/commands/sys/panels/provider-panel.d.ts +4 -0
  59. package/dist/commands/sys/panels/provider-panel.js +1 -1
  60. package/dist/commands/sys/provider-login.js +5 -5
  61. package/dist/commands/sys/setup-core.js +3 -3
  62. package/dist/commands/sys/setup-settings-runner.js +1 -1
  63. package/dist/commands/sys/spend.js +3 -3
  64. package/dist/commands/sys/update.js +1 -1
  65. package/dist/core/auth-file.d.ts +11 -19
  66. package/dist/core/auth-file.js +1 -2
  67. package/dist/core/auto-update.d.ts +1 -1
  68. package/dist/core/auto-update.js +4 -4
  69. package/dist/core/bash-jobs.d.ts +29 -71
  70. package/dist/core/bash-jobs.js +9 -10
  71. package/dist/core/blobs/directory.d.ts +3 -0
  72. package/dist/core/blobs/directory.js +1 -0
  73. package/dist/core/blobs/fault.d.ts +3 -0
  74. package/dist/core/blobs/fault.js +1 -0
  75. package/dist/core/blobs/index.d.ts +21 -0
  76. package/dist/core/blobs/index.js +1 -0
  77. package/dist/core/blobs/r2.d.ts +9 -0
  78. package/dist/core/blobs/r2.js +4 -0
  79. package/dist/core/bootstrap.d.ts +1 -1
  80. package/dist/core/bootstrap.js +2 -2
  81. package/dist/core/canvas/canvas.d.ts +9 -7
  82. package/dist/core/canvas/canvas.js +8 -8
  83. package/dist/core/canvas/crons.d.ts +3 -2
  84. package/dist/core/canvas/crons.js +3 -3
  85. package/dist/core/canvas/daemon-owner.d.ts +5 -0
  86. package/dist/core/canvas/daemon-owner.js +1 -1
  87. package/dist/core/canvas/db.d.ts +10 -9
  88. package/dist/core/canvas/db.js +4 -4
  89. package/dist/core/canvas/executions.d.ts +100 -4
  90. package/dist/core/canvas/executions.js +1 -1
  91. package/dist/core/canvas/export-sqlite.js +1 -1
  92. package/dist/core/canvas/focuses.d.ts +4 -0
  93. package/dist/core/canvas/focuses.js +1 -1
  94. package/dist/core/canvas/history.js +9 -9
  95. package/dist/core/canvas/managed-worktree-sweep-candidates.d.ts +2 -1
  96. package/dist/core/canvas/managed-worktree-sweep-candidates.js +3 -2
  97. package/dist/core/canvas/node-tokens.d.ts +1 -4
  98. package/dist/core/canvas/node-tokens.js +3 -3
  99. package/dist/core/canvas/paths.d.ts +0 -1
  100. package/dist/core/canvas/paths.js +1 -1
  101. package/dist/core/canvas/person-state.d.ts +26 -0
  102. package/dist/core/canvas/person-state.js +1 -0
  103. package/dist/core/canvas/person.d.ts +3 -10
  104. package/dist/core/canvas/person.js +7 -7
  105. package/dist/core/canvas/reconciler-leases.d.ts +12 -0
  106. package/dist/core/canvas/reconciler-leases.js +8 -0
  107. package/dist/core/canvas/recovery.d.ts +4 -0
  108. package/dist/core/canvas/recovery.js +1 -1
  109. package/dist/core/canvas/schema-pg/003-fleet-removals.sql +1 -1
  110. package/dist/core/canvas/schema-pg/005-fleet-blobs.sql +13 -0
  111. package/dist/core/canvas/schema-pg/006-job-rows.sql +20 -0
  112. package/dist/core/canvas/schema-pg/007-ledger-check.sql +117 -0
  113. package/dist/core/canvas/schema-version.d.ts +1 -1
  114. package/dist/core/canvas/schema-version.js +1 -1
  115. package/dist/core/canvas/sessions.d.ts +8 -11
  116. package/dist/core/canvas/sessions.js +1 -4
  117. package/dist/core/canvas/tree.d.ts +4 -2
  118. package/dist/core/canvas/tree.js +6 -6
  119. package/dist/core/canvas/types.d.ts +7 -0
  120. package/dist/core/command-hooks/transport/exec-lifecycle.js +1 -1
  121. package/dist/core/command-plugins/bundle.d.ts +1 -1
  122. package/dist/core/command-plugins/bundle.js +1 -1
  123. package/dist/core/command-plugins/compose.d.ts +1 -1
  124. package/dist/core/command-plugins/discovery.d.ts +0 -1
  125. package/dist/core/command-plugins/discovery.js +1 -1
  126. package/dist/core/command-plugins/help-addenda.js +1 -1
  127. package/dist/core/command-plugins/presence.d.ts +2 -0
  128. package/dist/core/command-plugins/presence.js +1 -1
  129. package/dist/core/config.d.ts +16 -15
  130. package/dist/core/config.js +1 -1
  131. package/dist/core/conversation-store/client-worker.js +1 -1
  132. package/dist/core/conversation-store/client.js +2 -2
  133. package/dist/core/conversation-store/index.d.ts +9 -11
  134. package/dist/core/conversation-store/index.js +1 -1
  135. package/dist/core/conversation-store/patch.js +1 -1
  136. package/dist/core/conversation-store/row-store.d.ts +14 -0
  137. package/dist/core/conversation-store/row-store.js +13 -0
  138. package/dist/core/directory/join.d.ts +5 -3
  139. package/dist/core/directory/join.js +1 -1
  140. package/dist/core/env-name.d.ts +1 -0
  141. package/dist/core/env-name.js +1 -1
  142. package/dist/core/environment-recipe.d.ts +15 -0
  143. package/dist/core/environment-recipe.js +1 -0
  144. package/dist/core/environments.js +1 -1
  145. package/dist/core/events/emit.js +2 -2
  146. package/dist/core/events/run-event-chunks.d.ts +25 -0
  147. package/dist/core/events/run-event-chunks.js +5 -0
  148. package/dist/core/feed/messages.js +5 -5
  149. package/dist/core/grants/mirror.d.ts +15 -0
  150. package/dist/core/grants/mirror.js +15 -3
  151. package/dist/core/grants/on-sync.d.ts +9 -5
  152. package/dist/core/grants/on-sync.js +1 -1
  153. package/dist/core/grants/remove.d.ts +1 -6
  154. package/dist/core/grants/remove.js +1 -1
  155. package/dist/core/grants/sync.js +1 -1
  156. package/dist/core/grants/watch.d.ts +15 -9
  157. package/dist/core/grants/watch.js +1 -1
  158. package/dist/core/graph/custom.js +8 -8
  159. package/dist/core/graph/jobs.d.ts +83 -5
  160. package/dist/core/graph/jobs.js +11 -2
  161. package/dist/core/graph/kinds/job.js +2 -2
  162. package/dist/core/graph/names.js +7 -7
  163. package/dist/core/graph/objects.d.ts +5 -1
  164. package/dist/core/graph/objects.js +11 -11
  165. package/dist/core/graph/reader.d.ts +3 -0
  166. package/dist/core/graph/reader.js +3 -3
  167. package/dist/core/graph/repo-sync/identity.d.ts +3 -1
  168. package/dist/core/graph/repo-sync/identity.js +1 -1
  169. package/dist/core/graph/repo-sync/repos.d.ts +1 -1
  170. package/dist/core/graph/repo-sync/repos.js +2 -2
  171. package/dist/core/graph/repo-sync/sync.js +2 -2
  172. package/dist/core/installed-plugins.js +1 -1
  173. package/dist/core/keybindings/persistence.d.ts +1 -1
  174. package/dist/core/keybindings/persistence.js +1 -1
  175. package/dist/core/layout-migrate/loose-keys.d.ts +1 -1
  176. package/dist/core/layout-migrate/loose-keys.js +1 -1
  177. package/dist/core/ledger/account.d.ts +12 -0
  178. package/dist/core/ledger/account.js +2 -0
  179. package/dist/core/ledger/charges.d.ts +2 -2
  180. package/dist/core/ledger/charges.js +1 -1
  181. package/dist/core/ledger/check.d.ts +5 -1
  182. package/dist/core/ledger/check.js +1 -7
  183. package/dist/core/ledger/chunks.d.ts +8 -0
  184. package/dist/core/ledger/chunks.js +2 -0
  185. package/dist/core/ledger/index.d.ts +2 -1
  186. package/dist/core/ledger/index.js +1 -1
  187. package/dist/core/ledger/infra-allowance.d.ts +9 -0
  188. package/dist/core/ledger/infra-allowance.js +1 -0
  189. package/dist/core/ledger/limits.js +3 -3
  190. package/dist/core/ledger/payer.d.ts +1 -1
  191. package/dist/core/ledger/payer.js +1 -1
  192. package/dist/core/ledger/read.d.ts +14 -4
  193. package/dist/core/ledger/read.js +2 -3
  194. package/dist/core/ledger/totals.d.ts +5 -16
  195. package/dist/core/ledger/totals.js +4 -4
  196. package/dist/core/ledger/types.d.ts +12 -3
  197. package/dist/core/llm/run.js +2 -2
  198. package/dist/core/managed-providers.d.ts +72 -0
  199. package/dist/core/managed-providers.js +1 -0
  200. package/dist/core/model-auth/api-credential-store.d.ts +10 -0
  201. package/dist/core/model-auth/api-credential-store.js +1 -0
  202. package/dist/core/model-auth/daemon-login.d.ts +1 -1
  203. package/dist/core/model-auth/daemon-login.js +1 -1
  204. package/dist/core/model-auth/flows.d.ts +5 -4
  205. package/dist/core/model-auth/flows.js +1 -1
  206. package/dist/core/model-auth/handoff-jti.d.ts +3 -4
  207. package/dist/core/model-auth/handoff-jti.js +1 -1
  208. package/dist/core/model-auth/readiness.js +1 -1
  209. package/dist/core/model-config.d.ts +1 -1
  210. package/dist/core/model-config.js +1 -1
  211. package/dist/core/model-routes-pool.d.ts +14 -0
  212. package/dist/core/model-routes-pool.js +1 -0
  213. package/dist/core/model-routes.d.ts +11 -16
  214. package/dist/core/model-routes.js +1 -1
  215. package/dist/core/peer/ingest.d.ts +2 -0
  216. package/dist/core/peer/ingest.js +1 -1
  217. package/dist/core/peer/stubs.d.ts +8 -0
  218. package/dist/core/peer/stubs.js +1 -1
  219. package/dist/core/permissions/evaluator.d.ts +7 -2
  220. package/dist/core/permissions/evaluator.js +2 -2
  221. package/dist/core/plugin-env.js +1 -1
  222. package/dist/core/pool-credential-store.d.ts +2 -5
  223. package/dist/core/pool-credential-store.js +1 -1
  224. package/dist/core/profiles/app-env-store.d.ts +5 -5
  225. package/dist/core/profiles/app-env-store.js +1 -2
  226. package/dist/core/profiles/default-binding.d.ts +5 -5
  227. package/dist/core/profiles/default-binding.js +1 -1
  228. package/dist/core/profiles/env-store.d.ts +10 -21
  229. package/dist/core/profiles/env-store.js +1 -2
  230. package/dist/core/profiles/manifest.d.ts +14 -18
  231. package/dist/core/profiles/manifest.js +1 -1
  232. package/dist/core/profiles/relocate.js +1 -1
  233. package/dist/core/profiles/select.js +21 -21
  234. package/dist/core/provider-credential.d.ts +1 -1
  235. package/dist/core/provider-credential.js +1 -1
  236. package/dist/core/provider-management.d.ts +3 -4
  237. package/dist/core/provider-management.js +1 -1
  238. package/dist/core/providers/bundles.js +2 -2
  239. package/dist/core/providers/call.d.ts +3 -34
  240. package/dist/core/providers/description.d.ts +34 -0
  241. package/dist/core/providers/description.js +0 -0
  242. package/dist/core/providers/token-cache.d.ts +14 -14
  243. package/dist/core/providers/token-cache.js +1 -1
  244. package/dist/core/review/companion.js +1 -1
  245. package/dist/core/runs/activity.d.ts +1 -1
  246. package/dist/core/runs/activity.js +1 -1
  247. package/dist/core/runs/events.d.ts +7 -5
  248. package/dist/core/runs/events.js +2 -6
  249. package/dist/core/runs/operations.d.ts +1 -1
  250. package/dist/core/runs/operations.js +6 -6
  251. package/dist/core/runtime/bearings.js +1 -1
  252. package/dist/core/runtime/broker/attachments-fetch.d.ts +5 -0
  253. package/dist/core/runtime/broker/attachments-fetch.js +1 -0
  254. package/dist/core/runtime/broker/bash-job-launch.d.ts +4 -2
  255. package/dist/core/runtime/broker/bash-job-launch.js +1 -1
  256. package/dist/core/runtime/broker/boot-state.d.ts +9 -0
  257. package/dist/core/runtime/broker/boot-state.js +1 -0
  258. package/dist/core/runtime/broker/cli-relay.d.ts +2 -1
  259. package/dist/core/runtime/broker/cli-relay.js +1 -1
  260. package/dist/core/runtime/broker/client-registry.d.ts +3 -3
  261. package/dist/core/runtime/broker/daemon-ops.d.ts +10 -1
  262. package/dist/core/runtime/broker/daemon-ops.js +1 -1
  263. package/dist/core/runtime/broker/fault-retry.d.ts +2 -0
  264. package/dist/core/runtime/broker/fault-retry.js +1 -1
  265. package/dist/core/runtime/broker/frame-engine-control.js +1 -1
  266. package/dist/core/runtime/broker/held-deferred-inbox.d.ts +3 -0
  267. package/dist/core/runtime/broker/held-deferred-inbox.js +1 -1
  268. package/dist/core/runtime/broker/job-reporter.d.ts +18 -0
  269. package/dist/core/runtime/broker/job-reporter.js +2 -0
  270. package/dist/core/runtime/broker/model-adapter.d.ts +3 -0
  271. package/dist/core/runtime/broker/model-adapter.js +3 -2
  272. package/dist/core/runtime/broker/signal-stream.js +1 -1
  273. package/dist/core/runtime/broker/spilled-body.d.ts +5 -0
  274. package/dist/core/runtime/broker/spilled-body.js +1 -0
  275. package/dist/core/runtime/broker/summaries-post.d.ts +5 -0
  276. package/dist/core/runtime/broker/summaries-post.js +1 -0
  277. package/dist/core/runtime/broker/tool-groups.d.ts +3 -0
  278. package/dist/core/runtime/broker/tool-groups.js +1 -1
  279. package/dist/core/runtime/broker/turn-replies.d.ts +1 -0
  280. package/dist/core/runtime/broker/turn-replies.js +1 -1
  281. package/dist/core/runtime/broker-persona-guidance.js +2 -2
  282. package/dist/core/runtime/broker.js +1 -1
  283. package/dist/core/runtime/deliver-live.js +1 -1
  284. package/dist/core/runtime/first-boot-rows.d.ts +8 -0
  285. package/dist/core/runtime/first-boot-rows.js +2 -0
  286. package/dist/core/runtime/fleet.d.ts +13 -12
  287. package/dist/core/runtime/front-door-login.js +1 -1
  288. package/dist/core/runtime/front-door.js +6 -6
  289. package/dist/core/runtime/headless-pi.js +1 -1
  290. package/dist/core/runtime/host.d.ts +14 -3
  291. package/dist/core/runtime/host.js +3 -3
  292. package/dist/core/runtime/invocation.d.ts +6 -0
  293. package/dist/core/runtime/kickoff.d.ts +2 -14
  294. package/dist/core/runtime/kickoff.js +17 -19
  295. package/dist/core/runtime/launch-config-worker.js +1 -1
  296. package/dist/core/runtime/launch.js +2 -2
  297. package/dist/core/runtime/model-registry.js +1 -1
  298. package/dist/core/runtime/node-read.d.ts +4 -6
  299. package/dist/core/runtime/node-read.js +3 -3
  300. package/dist/core/runtime/nodes.js +1 -1
  301. package/dist/core/runtime/outcome-document.d.ts +0 -1
  302. package/dist/core/runtime/outcome-document.js +1 -1
  303. package/dist/core/runtime/output-result.d.ts +15 -0
  304. package/dist/core/runtime/output-result.js +5 -0
  305. package/dist/core/runtime/placement.d.ts +17 -1
  306. package/dist/core/runtime/placement.js +1 -1
  307. package/dist/core/runtime/provider-credential-gate.d.ts +6 -2
  308. package/dist/core/runtime/provider-credential-gate.js +2 -2
  309. package/dist/core/runtime/recovery-hold.d.ts +21 -2
  310. package/dist/core/runtime/recovery-hold.js +2 -1
  311. package/dist/core/runtime/recycle.js +1 -1
  312. package/dist/core/runtime/reset.js +1 -1
  313. package/dist/core/runtime/revive.d.ts +2 -2
  314. package/dist/core/runtime/revive.js +2 -2
  315. package/dist/core/runtime/runner/blaxel/client.d.ts +81 -0
  316. package/dist/core/runtime/runner/blaxel/client.js +1 -0
  317. package/dist/core/runtime/runner/blaxel/image-build.d.ts +74 -0
  318. package/dist/core/runtime/runner/blaxel/image-build.js +5 -0
  319. package/dist/core/runtime/runner/blaxel/runner.d.ts +41 -0
  320. package/dist/core/runtime/runner/blaxel/runner.js +3 -0
  321. package/dist/core/runtime/runner/index.d.ts +8 -0
  322. package/dist/core/runtime/runner/index.js +1 -0
  323. package/dist/core/runtime/runner/process.d.ts +11 -0
  324. package/dist/core/runtime/runner/process.js +1 -0
  325. package/dist/core/runtime/runner/types.d.ts +95 -0
  326. package/dist/core/runtime/runner/types.js +0 -0
  327. package/dist/core/runtime/situational-context.d.ts +3 -6
  328. package/dist/core/runtime/situational-context.js +2 -2
  329. package/dist/core/runtime/situational-live.js +1 -1
  330. package/dist/core/runtime/spawn-env-rows.d.ts +5 -0
  331. package/dist/core/runtime/spawn-env-rows.js +1 -0
  332. package/dist/core/runtime/spawn-env.d.ts +4 -18
  333. package/dist/core/runtime/spawn-env.js +1 -1
  334. package/dist/core/runtime/spawn.js +2 -2
  335. package/dist/core/runtime/stop-guard.d.ts +1 -1
  336. package/dist/core/runtime/stop-guard.js +2 -8
  337. package/dist/core/runtime/structured-output.d.ts +0 -67
  338. package/dist/core/runtime/structured-output.js +3 -8
  339. package/dist/core/runtime/tmux-driver.d.ts +8 -0
  340. package/dist/core/runtime/tmux-driver.js +6 -6
  341. package/dist/core/runtime/view-socket.d.ts +20 -0
  342. package/dist/core/runtime/view-socket.js +3 -1
  343. package/dist/core/runtime/warm-pool.d.ts +13 -1
  344. package/dist/core/runtime/warm-pool.js +1 -1
  345. package/dist/core/scoped-state/legacy-profiles.d.ts +11 -0
  346. package/dist/core/scoped-state/legacy-profiles.js +1 -0
  347. package/dist/core/scoped-state/materialized-plugins.d.ts +18 -0
  348. package/dist/core/scoped-state/materialized-plugins.js +1 -0
  349. package/dist/core/scoped-state/migrate.d.ts +8 -1
  350. package/dist/core/scoped-state/migrate.js +3 -3
  351. package/dist/core/scoped-state/person-rows.d.ts +10 -0
  352. package/dist/core/scoped-state/person-rows.js +6 -0
  353. package/dist/core/scoped-state/person-store.d.ts +72 -0
  354. package/dist/core/scoped-state/person-store.js +1 -0
  355. package/dist/core/secrets/blaxel-launch.d.ts +24 -0
  356. package/dist/core/secrets/blaxel-launch.js +4 -0
  357. package/dist/core/secrets/blaxel-register.d.ts +2 -0
  358. package/dist/core/secrets/blaxel-register.js +1 -0
  359. package/dist/core/secrets/env-rows.d.ts +17 -0
  360. package/dist/core/secrets/env-rows.js +4 -0
  361. package/dist/core/secrets/environment-owner.d.ts +8 -0
  362. package/dist/core/secrets/environment-owner.js +1 -0
  363. package/dist/core/secrets/environment-secrets.d.ts +14 -0
  364. package/dist/core/secrets/environment-secrets.js +1 -0
  365. package/dist/core/secrets/index.d.ts +2 -0
  366. package/dist/core/secrets/index.js +1 -0
  367. package/dist/core/secrets/key.d.ts +3 -0
  368. package/dist/core/secrets/key.js +2 -0
  369. package/dist/core/secrets/seal.d.ts +18 -0
  370. package/dist/core/secrets/seal.js +1 -0
  371. package/dist/core/spaces/mounts.d.ts +1 -1
  372. package/dist/core/spaces/mounts.js +1 -1
  373. package/dist/core/spaces/process.d.ts +1 -1
  374. package/dist/core/spaces/stop.d.ts +7 -5
  375. package/dist/core/spaces/stop.js +2 -2
  376. package/dist/core/spaces/users.d.ts +2 -1
  377. package/dist/core/spaces/users.js +1 -1
  378. package/dist/core/storage/attachments.d.ts +11 -8
  379. package/dist/core/storage/attachments.js +1 -1
  380. package/dist/core/storage/tables.d.ts +4 -0
  381. package/dist/core/storage/tables.js +3 -3
  382. package/dist/core/subscription-state.d.ts +34 -72
  383. package/dist/core/subscription-state.js +1 -4
  384. package/dist/core/substrate/delivery/plan.js +1 -1
  385. package/dist/core/user-settings.js +1 -1
  386. package/dist/daemon/api/app-listener.js +10 -10
  387. package/dist/daemon/api/auth/person-bearer.d.ts +14 -0
  388. package/dist/daemon/api/auth/person-bearer.js +1 -0
  389. package/dist/daemon/api/bridge.js +4 -4
  390. package/dist/daemon/api/document-listener.js +3 -3
  391. package/dist/daemon/api/environment-of.d.ts +18 -0
  392. package/dist/daemon/api/environment-of.js +5 -0
  393. package/dist/daemon/api/handlers/analytics.js +1 -1
  394. package/dist/daemon/api/handlers/app-profiles.d.ts +2 -2
  395. package/dist/daemon/api/handlers/app-profiles.js +1 -1
  396. package/dist/daemon/api/handlers/bash-jobs.js +1 -1
  397. package/dist/daemon/api/handlers/bash.js +1 -1
  398. package/dist/daemon/api/handlers/broker-ops.d.ts +3 -0
  399. package/dist/daemon/api/handlers/broker-ops.js +1 -1
  400. package/dist/daemon/api/handlers/broker-recovery.js +1 -1
  401. package/dist/daemon/api/handlers/broker-signals.d.ts +0 -3
  402. package/dist/daemon/api/handlers/broker-signals.js +2 -2
  403. package/dist/daemon/api/handlers/canvas.js +4 -4
  404. package/dist/daemon/api/handlers/charges.d.ts +1 -1
  405. package/dist/daemon/api/handlers/charges.js +1 -1
  406. package/dist/daemon/api/handlers/conversations.d.ts +2 -0
  407. package/dist/daemon/api/handlers/conversations.js +3 -0
  408. package/dist/daemon/api/handlers/crons.js +1 -1
  409. package/dist/daemon/api/handlers/daemon.js +1 -1
  410. package/dist/daemon/api/handlers/delivery.d.ts +1 -1
  411. package/dist/daemon/api/handlers/delivery.js +1 -1
  412. package/dist/daemon/api/handlers/docs.js +1 -1
  413. package/dist/daemon/api/handlers/exec.js +1 -1
  414. package/dist/daemon/api/handlers/execution-start.d.ts +2 -0
  415. package/dist/daemon/api/handlers/execution-start.js +2 -0
  416. package/dist/daemon/api/handlers/files.js +1 -1
  417. package/dist/daemon/api/handlers/git-credential.d.ts +11 -0
  418. package/dist/daemon/api/handlers/git-credential.js +1 -0
  419. package/dist/daemon/api/handlers/hook-exec.d.ts +1 -1
  420. package/dist/daemon/api/handlers/hook-exec.js +1 -1
  421. package/dist/daemon/api/handlers/human-requests.js +1 -1
  422. package/dist/daemon/api/handlers/inbox.js +1 -1
  423. package/dist/daemon/api/handlers/messages.js +1 -1
  424. package/dist/daemon/api/handlers/model-config.js +1 -1
  425. package/dist/daemon/api/handlers/modelauth.d.ts +1 -1
  426. package/dist/daemon/api/handlers/modelauth.js +1 -1
  427. package/dist/daemon/api/handlers/node-events.d.ts +6 -0
  428. package/dist/daemon/api/handlers/node-events.js +3 -3
  429. package/dist/daemon/api/handlers/node-outcomes.js +1 -1
  430. package/dist/daemon/api/handlers/node-records.js +3 -2
  431. package/dist/daemon/api/handlers/nodes.js +1 -1
  432. package/dist/daemon/api/handlers/objects.js +3 -3
  433. package/dist/daemon/api/handlers/profiles.js +1 -1
  434. package/dist/daemon/api/handlers/providers.js +1 -1
  435. package/dist/daemon/api/handlers/reports.js +9 -13
  436. package/dist/daemon/api/handlers/reviews.js +1 -1
  437. package/dist/daemon/api/handlers/runs.js +1 -1
  438. package/dist/daemon/api/handlers/settings.d.ts +2 -0
  439. package/dist/daemon/api/handlers/settings.js +1 -0
  440. package/dist/daemon/api/handlers/structured-requests.js +1 -1
  441. package/dist/daemon/api/handlers/vendor-status.d.ts +1 -1
  442. package/dist/daemon/api/handlers/vendor-status.js +1 -1
  443. package/dist/daemon/api/handlers/worktree.js +1 -1
  444. package/dist/daemon/api/map.d.ts +2 -2
  445. package/dist/daemon/api/map.js +2 -2
  446. package/dist/daemon/api/operations.js +1 -1
  447. package/dist/daemon/api/reader.d.ts +8 -1
  448. package/dist/daemon/api/reader.js +1 -1
  449. package/dist/daemon/api/router.d.ts +4 -2
  450. package/dist/daemon/api/router.js +1 -1
  451. package/dist/daemon/api/server.d.ts +15 -3
  452. package/dist/daemon/api/server.js +1 -1
  453. package/dist/daemon/api/vendor-signin.d.ts +2 -1
  454. package/dist/daemon/api/vendor-signin.js +19 -19
  455. package/dist/daemon/api/viewer-presence.d.ts +20 -6
  456. package/dist/daemon/api/viewer-presence.js +2 -1
  457. package/dist/daemon/boot-settings.d.ts +3 -0
  458. package/dist/daemon/boot-settings.js +1 -0
  459. package/dist/daemon/broker-signals/bus.d.ts +48 -11
  460. package/dist/daemon/broker-signals/bus.js +1 -1
  461. package/dist/daemon/cron/armed-context.js +1 -1
  462. package/dist/daemon/cron/lease-recovery.js +1 -1
  463. package/dist/daemon/cron/live-runs.d.ts +6 -0
  464. package/dist/daemon/cron/live-runs.js +1 -1
  465. package/dist/daemon/cron/run-socket.js +1 -1
  466. package/dist/daemon/cron-run.js +2 -2
  467. package/dist/daemon/crtrd-cli.js +1 -1
  468. package/dist/daemon/crtrd.d.ts +10 -0
  469. package/dist/daemon/crtrd.js +18 -17
  470. package/dist/daemon/delivery/run-delivery.d.ts +1 -1
  471. package/dist/daemon/delivery/run-delivery.js +1 -1
  472. package/dist/daemon/due-sources.d.ts +1 -1
  473. package/dist/daemon/due-sources.js +1 -1
  474. package/dist/daemon/due.js +7 -6
  475. package/dist/daemon/fleet.d.ts +6 -6
  476. package/dist/daemon/fleet.js +9 -9
  477. package/dist/daemon/park-activity.d.ts +2 -1
  478. package/dist/daemon/park-activity.js +1 -1
  479. package/dist/daemon/park-pending.d.ts +20 -13
  480. package/dist/daemon/park-pending.js +3 -1
  481. package/dist/daemon/profile-delete.js +1 -1
  482. package/dist/daemon/reconcilers/bash-deadline.d.ts +15 -10
  483. package/dist/daemon/reconcilers/bash-deadline.js +1 -3
  484. package/dist/daemon/reconcilers/broker-supervision.d.ts +13 -3
  485. package/dist/daemon/reconcilers/broker-supervision.js +4 -4
  486. package/dist/daemon/reconcilers/cron-lane.d.ts +0 -1
  487. package/dist/daemon/reconcilers/cron-lane.js +1 -1
  488. package/dist/daemon/reconcilers/fault-retry-backstop.d.ts +2 -9
  489. package/dist/daemon/reconcilers/fault-retry-backstop.js +1 -1
  490. package/dist/daemon/reconcilers/grant-effects.d.ts +8 -0
  491. package/dist/daemon/reconcilers/grant-effects.js +1 -0
  492. package/dist/daemon/reconcilers/grove-instance-sweep.d.ts +1 -3
  493. package/dist/daemon/reconcilers/grove-instance-sweep.js +1 -1
  494. package/dist/daemon/reconcilers/ledger-r2.d.ts +27 -0
  495. package/dist/daemon/reconcilers/ledger-r2.js +7 -0
  496. package/dist/daemon/reconcilers/ledger-sandbox.d.ts +55 -0
  497. package/dist/daemon/reconcilers/ledger-sandbox.js +9 -0
  498. package/dist/daemon/reconcilers/ledger-storage.d.ts +22 -0
  499. package/dist/daemon/reconcilers/ledger-storage.js +5 -0
  500. package/dist/daemon/reconcilers/live-obligation.d.ts +3 -2
  501. package/dist/daemon/reconcilers/live-obligation.js +1 -1
  502. package/dist/daemon/reconcilers/managed-worktree-sweep.d.ts +0 -5
  503. package/dist/daemon/reconcilers/managed-worktree-sweep.js +1 -1
  504. package/dist/daemon/reconcilers/node-deadline.d.ts +2 -1
  505. package/dist/daemon/reconcilers/node-deadline.js +1 -1
  506. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.d.ts +1 -2
  507. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +1 -1
  508. package/dist/daemon/reconcilers/node-lifecycle/remote-wake.d.ts +31 -0
  509. package/dist/daemon/reconcilers/node-lifecycle/remote-wake.js +1 -0
  510. package/dist/daemon/reconcilers/node-lifecycle/requested-stops.d.ts +5 -0
  511. package/dist/daemon/reconcilers/node-lifecycle/requested-stops.js +2 -0
  512. package/dist/daemon/reconcilers/node-lifecycle/tick.js +1 -1
  513. package/dist/daemon/reconcilers/pending-review-submit.js +1 -1
  514. package/dist/daemon/reconcilers/recipe-image.d.ts +49 -0
  515. package/dist/daemon/reconcilers/recipe-image.js +7 -0
  516. package/dist/daemon/reconcilers/row-claim.d.ts +19 -0
  517. package/dist/daemon/reconcilers/row-claim.js +2 -0
  518. package/dist/daemon/reconcilers/storage-due-work.d.ts +18 -3
  519. package/dist/daemon/reconcilers/storage-due-work.js +2 -1
  520. package/dist/daemon/reconcilers/storage-maintenance.d.ts +5 -13
  521. package/dist/daemon/reconcilers/storage-maintenance.js +1 -1
  522. package/dist/daemon/reconcilers/structured-request-lane.js +1 -1
  523. package/dist/daemon/reconcilers/throttled-lane.d.ts +11 -0
  524. package/dist/daemon/reconcilers/throttled-lane.js +1 -0
  525. package/dist/daemon/review/finish.js +1 -1
  526. package/dist/daemon/run-notices.d.ts +3 -0
  527. package/dist/daemon/run-notices.js +1 -1
  528. package/dist/daemon/runs/append-section.d.ts +4 -1
  529. package/dist/daemon/runs/append-section.js +2 -2
  530. package/dist/daemon/runs/observer.d.ts +3 -2
  531. package/dist/daemon/runs/observer.js +3 -2
  532. package/dist/daemon/runs/view-transport.d.ts +19 -0
  533. package/dist/daemon/runs/view-transport.js +1 -0
  534. package/dist/daemon/structured-requests/boot.d.ts +2 -1
  535. package/dist/daemon/structured-requests/boot.js +1 -1
  536. package/dist/daemon/structured-requests/effects.d.ts +4 -3
  537. package/dist/daemon/structured-requests/effects.js +1 -1
  538. package/dist/daemon/structured-requests/notifier.d.ts +2 -0
  539. package/dist/daemon/structured-requests/notifier.js +1 -1
  540. package/dist/daemon/structured-requests/sidecar.d.ts +4 -3
  541. package/dist/daemon/structured-requests/sidecar.js +1 -1
  542. package/dist/launcher/validate.js +1 -1
  543. package/dist/migrations/004-canvas-documents/index.js +2 -2
  544. package/dist/pi-extensions/broker-local.d.ts +3 -3
  545. package/dist/pi-extensions/broker-local.js +1 -2
  546. package/dist/pi-extensions/canvas-bash-valve.js +6 -6
  547. package/dist/pi-extensions/canvas-context-intro.js +2 -2
  548. package/dist/pi-extensions/canvas-elapsed.js +2 -2
  549. package/dist/pi-extensions/canvas-goal-capture.js +1 -1
  550. package/dist/pi-extensions/canvas-inbox-watcher.js +2 -2
  551. package/dist/pi-extensions/canvas-review-boundary.d.ts +11 -1
  552. package/dist/pi-extensions/canvas-review-boundary.js +3 -3
  553. package/dist/pi-extensions/canvas-stophook.js +1 -1
  554. package/dist/pi-extensions/canvas-structured-output.d.ts +5 -3
  555. package/dist/pi-extensions/canvas-structured-output.js +6 -11
  556. package/dist/pi-extensions/canvas-tool-guide.d.ts +9 -2
  557. package/dist/pi-extensions/canvas-tool-guide.js +6 -6
  558. package/dist/shared/inbox-entry-body.js +7 -7
  559. package/dist/types.d.ts +12 -0
  560. package/docs/sdk/release-notes.md +4 -0
  561. package/package.json +7 -4
  562. package/packages/crouter-identity/package.json +1 -1
  563. package/runtime.lock.json +13 -13
  564. package/scripts/git-credential-crtr +33 -0
  565. package/scripts/sandbox-bootstrap.sh +54 -0
  566. package/scripts/sandbox-start.mjs +320 -0
  567. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +0 -78
  568. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +0 -1179
  569. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +0 -1406
  570. package/dist/core/conversation-store/file-store.d.ts +0 -37
  571. package/dist/core/conversation-store/file-store.js +0 -9
  572. package/dist/core/conversation-store/listener.d.ts +0 -4
  573. package/dist/core/conversation-store/listener.js +0 -8
  574. package/dist/core/scoped-state/profiles.d.ts +0 -27
  575. package/dist/core/scoped-state/profiles.js +0 -1
  576. package/dist/core/scoped-state/providers.d.ts +0 -24
  577. package/dist/core/scoped-state/providers.js +0 -1
  578. package/dist/core/scoped-state/settings.d.ts +0 -28
  579. package/dist/core/scoped-state/settings.js +0 -1
@@ -1,12 +1,18 @@
1
- import { type GrantRecord } from './mirror.js';
2
- /** Resolve when grant `grantId` is notified, or after `seconds`. Register it in the
3
- * same synchronous block that read the mirror, so no notify falls between the read
4
- * and the registration. The timer is unref'd: a held request never keeps the
5
- * process alive. */
6
- export declare function waitGrantChange(grantId: string, seconds: number): Promise<void>;
7
- /** Wake the long-polls of every grant whose `grant.get` answer this sync changed:
8
- * it left (or reached) `active`, or its version differs. Never triggers a sync. */
9
- export declare function notifyGrantChanges(before: readonly GrantRecord[], after: readonly GrantRecord[]): void;
1
+ /** A `grant.get` hold on one grant. It is registered before the caller reads the mirror and stays registered until `release`, so a
2
+ * wake that lands at any moment of the hold (including while a mirror read is outstanding) is remembered: the next `changed()`
3
+ * answers at once. */
4
+ export interface GrantHold {
5
+ /** Resolve when the grant was notified since the last `changed()` returned, or after `seconds`. */
6
+ changed(seconds: number): Promise<void>;
7
+ release(): void;
8
+ }
9
+ /** Register a hold on grant `grantId`. The timer of a pending `changed()` is unref'd: a held request never keeps the process alive. */
10
+ export declare function holdGrant(grantId: string): GrantHold;
11
+ /** Wake the long-polls held on grant `grantId`: its row changed. Each woken `grant.get` re-reads the grant and waits again unless
12
+ * its version moved, so a change that leaves the version alone never ends a hold early. Never triggers a sync. */
13
+ export declare function wakeGrantWaiters(grantId: string): void;
14
+ /** Wake every held long-poll (the listen connection restarted: changes may have gone unheard). */
15
+ export declare function wakeAllGrantWaiters(): void;
10
16
  /** The app listener is closing: drop every held long-poll and its timer. Its
11
17
  * socket is being destroyed, so nothing is answered. */
12
18
  export declare function releaseGrantWaiters(): void;
@@ -1 +1 @@
1
- var u=Object.defineProperty;var s=(e,t)=>u(e,"name",{value:t,configurable:!0});import{grantVersion as c}from"./mirror.js";const o=new Map;function f(e,t){const i=o.get(e);i&&(i.delete(t),i.size||o.delete(e))}s(f,"release");function m(e,t){return new Promise(i=>{const r={wake:s(()=>{clearTimeout(n),f(e,r),i()},"wake"),cancel:s(()=>{clearTimeout(n),f(e,r)},"cancel")},n=setTimeout(r.wake,t*1e3);n.unref();let a=o.get(e);a||o.set(e,a=new Set),a.add(r)})}s(m,"waitGrantChange");function l(e,t){const i=e?.status==="active",r=t?.status==="active";return i!==r?!0:r?c(e)!==c(t):!1}s(l,"changed");function v(e,t){if(!o.size)return;const i=new Map(e.map(n=>[n.id,n])),r=new Map(t.map(n=>[n.id,n]));for(const n of[...o.keys()])if(l(i.get(n),r.get(n)))for(const a of[...o.get(n)??[]])a.wake()}s(v,"notifyGrantChanges");function d(){for(const e of[...o.values()])for(const t of[...e])t.cancel();o.clear()}s(d,"releaseGrantWaiters");function g(){let e=0;for(const t of o.values())e+=t.size;return e}s(g,"grantWaiterCount");export{g as grantWaiterCount,v as notifyGrantChanges,d as releaseGrantWaiters,m as waitGrantChange};
1
+ var u=Object.defineProperty;var n=(e,t)=>u(e,"name",{value:t,configurable:!0});const o=new Map;function w(e,t){const r=o.get(e);r&&(r.delete(t),r.size||o.delete(e))}n(w,"release");function k(e){let t=!1,r,s;const a=n(()=>{clearTimeout(s);const f=r;r=void 0,f?.()},"settle"),c={wake:n(()=>{t=!0,a()},"wake"),cancel:n(()=>{clearTimeout(s)},"cancel")};let i=o.get(e);return i||o.set(e,i=new Set),i.add(c),{changed:n(f=>new Promise(l=>{if(t){t=!1,l();return}r=n(()=>{t=!1,l()},"resolveWait"),s=setTimeout(a,f*1e3),s.unref()}),"changed"),release:n(()=>{w(e,c),a()},"release")}}n(k,"holdGrant");function d(e){for(const t of[...o.get(e)??[]])t.wake()}n(d,"wakeGrantWaiters");function m(){for(const e of[...o.keys()])d(e)}n(m,"wakeAllGrantWaiters");function x(){for(const e of[...o.values()])for(const t of[...e])t.cancel();o.clear()}n(x,"releaseGrantWaiters");function W(){let e=0;for(const t of o.values())e+=t.size;return e}n(W,"grantWaiterCount");export{W as grantWaiterCount,k as holdGrant,x as releaseGrantWaiters,m as wakeAllGrantWaiters,d as wakeGrantWaiters};
@@ -1,12 +1,12 @@
1
- var C=Object.defineProperty;var l=(e,t)=>C(e,"name",{value:t,configurable:!0});import{createHash as x,randomBytes as P,timingSafeEqual as $}from"node:crypto";import{CrtrError as j,usage as f}from"../errors.js";import{ExitCode as S}from"../../types.js";import{createObject as J,liveDocumentNamed as q}from"./objects.js";import{invalidateCustomKind as A}from"./kinds/registry.js";import{publish as O,HOP_LIMIT as F,HopLimitError as M}from"./events.js";import{checkoutsForCwd as N}from"./repo-sync/repos.js";import{onQueueSignal as B,signalQueue as b}from"./receivers.js";import{receiverEndpoint as K,isPublicReceiverAddress as V}from"./receiver-url.js";import{isIP as G}from"node:net";import{KIND as k,LOCAL as D,MAX_KIND_EVENTS as Q,RESERVED_KIND_ROOTS as Y,eventCatalogReasons as z}from"../plugin-objects-manifest.js";import{check as L}from"../permissions/evaluator.js";import{userOf as X}from"../permissions/evaluator.js";import{nodePlace as Z}from"../permissions/ceiling.js";import{PermissionError as ee}from"../permissions/errors.js";import{writeActsFor as te}from"../permissions/facts.js";async function ne(e,t,i){return t.class!=="person"&&t.app.id===i.id?!0:await L(e,t,"edit",i.id)}l(ne,"mayActAs");async function ie(e,t,i){return await L(e,t,"read",i.id)}l(ie,"customReachable");const se=["public","private"];function h(e,t){throw new j(e,t,S.GENERAL)}l(h,"conflict");function R(e){return x("sha256").update(e).digest("hex")}l(R,"hash");function H(){return`crtr_pub_${P(32).toString("base64url")}`}l(H,"issueToken");function ae(e){if(e==null)return null;if(e==="queue")return JSON.stringify(e);if(typeof e!="object"||Array.isArray(e))throw f("invalid custom receiver; expected queue, an https url, or argv and absolute cwd");if("url"in e&&typeof e.url=="string")try{const i=K(e.url).hostname.replace(/^\[|\]$/g,"");if(G(i)&&!V(i))throw new Error("non-public IP address");return JSON.stringify({url:e.url})}catch(t){throw f(`invalid custom receiver: ${String(t)}`)}if("argv"in e&&Array.isArray(e.argv)&&e.argv.length>0&&e.argv.every(t=>typeof t=="string")&&typeof e.cwd=="string"&&e.cwd.startsWith("/"))return JSON.stringify(e);throw f("invalid custom receiver; url must be https and argv requires a command and absolute cwd")}l(ae,"receiverJson");async function re(e,t){return t.class==="node"?(await Z(e,t.node.id))?.isolated===!0:t.class==="app"||t.class==="object"?t.place?.isolated===!0:!1}l(re,"inIsolatedRun");async function Ne(e,t,i){const{kind:n,name:a,owner:s,description:o,events:u,receiver:c,rotateToken:w,privacy:p}=i;if(p!==void 0&&!se.includes(p))throw f(`invalid privacy ${JSON.stringify(p)}`);if(await ne(e,t,s)||h("forbidden",`cannot act as ${s.id}`),await re(e,t))throw new ee(403,"isolated_run","a custom object cannot be registered from an isolated run");if(!k.test(n)||Y.includes(n.split(".")[0]))throw f(`invalid or reserved kind ${n}`);if(!a.trim()||a!==a.trim()||a.includes("\0"))throw f(`invalid object name ${JSON.stringify(a)}`);const m=ae(c);if(c!=="queue"&&c!==null&&c!==void 0&&"argv"in c&&t.class!=="person"&&t.class!=="node"&&h("argv_forbidden","argv receiver requires a person or node principal"),u===void 0){const d=await e.prepare("SELECT source FROM object_kinds WHERE name = ?").get(n);if(d?.source!=="manifest"&&d?.source!=="peer")throw f("events is required unless the kind is declared by an installed plugin")}else if(Object.keys(u).length>Q||Object.entries(u).some(([d,_])=>!D.test(d)||!Number.isInteger(_?.level)||_.level<1||_.level>10||_.supersedes!==void 0&&typeof _.supersedes!="boolean"||_.ends_wait!==void 0&&typeof _.ends_wait!="boolean"))throw f("invalid custom event catalog");const r=await q(e,s.id,a);if(r&&!await ie(e,t,r)&&h("forbidden",`cannot act as ${s.id}`),r&&t.class!=="person"&&t.class!=="node"){const d=(await e.prepare("SELECT receiver_json FROM custom_objects WHERE object = ?").get(r.id))?.receiver_json;d&&typeof JSON.parse(d)=="object"&&"argv"in JSON.parse(d)&&h("argv_forbidden","argv receiver requires a person or node principal")}r&&r.type!==n&&h("name_taken",`${s.name}/${a} is already held by ${r.id}`);const g=u===void 0?null:await oe(e,{name:n,owner:s,description:o,events:u,read:null,source:"register"});if(!r){const d=await J(e,{type:n,kind:null,owner:s.id,privacy:p??"public",name:a,preview:""});s.type==="app"&&await te(e,d.id,await X(e,t));const _=H();return await e.prepare(`INSERT INTO custom_objects (object, description, receiver_json, token_hash)
2
- VALUES (?, ?, ?, ?)`).run(d.id,o,m,R(_)),t.class==="node"&&await e.prepare("UPDATE custom_objects SET registered_by = ? WHERE object = ?").run(t.node.id,d.id),m!==null&&await e.prepare("UPDATE objects SET reception = 2 WHERE id = ?").run(d.id),await O(e,{event:"created",object:d,actor:t.class==="object"?t.object:t.class==="person"?t.person:t.class==="app"?t.app:t.node,attrs:{}},async U=>await N(e,U)),{object:d,token:_,created:!0,changed:[]}}const y=await e.prepare("SELECT description, receiver_json FROM custom_objects WHERE object = ?").get(r.id);y||h("name_taken",`${s.name}/${a} is not a custom object`);const E=[];if(y.description!==o&&E.push("description"),y.receiver_json!==m&&E.push("receiver"),g?.changed.includes("events")&&E.push("events"),g?.changed.includes("description")&&!E.includes("description")&&E.push("description"),p!==void 0&&r.privacy!==p&&E.push("privacy"),w&&E.push("token"),E.length===0)return{object:r,created:!1,changed:E};const v=w?H():void 0,T=E.includes("privacy")?{...r,privacy:p}:r;if(T!==r&&await e.prepare("UPDATE objects SET privacy = ? WHERE id = ?").run(p,r.id),await e.prepare("UPDATE custom_objects SET description = ?, receiver_json = ?, token_hash = COALESCE(?, token_hash) WHERE object = ?").run(o,m,v===void 0?null:R(v),r.id),await e.prepare("UPDATE objects SET reception = ? WHERE id = ?").run(m===null?0:2,r.id),y.receiver_json!==m){const d=Date.now();if(m===null){const _=await e.prepare(`UPDATE deliveries SET canceled_at = ?, updated_at = ?
1
+ var x=Object.defineProperty;var l=(e,t)=>x(e,"name",{value:t,configurable:!0});import{createHash as C,randomBytes as $,timingSafeEqual as P}from"node:crypto";import{CrtrError as j,usage as f}from"../errors.js";import{ExitCode as S}from"../../types.js";import{createObject as J,liveDocumentNamed as q}from"./objects.js";import{invalidateCustomKind as A}from"./kinds/registry.js";import{publish as O,HOP_LIMIT as F,HopLimitError as M}from"./events.js";import{checkoutsForCwd as N}from"./repo-sync/repos.js";import{onQueueSignal as B,signalQueue as R}from"./receivers.js";import{receiverEndpoint as K,isPublicReceiverAddress as V}from"./receiver-url.js";import{isIP as G}from"node:net";import{KIND as k,LOCAL as D,MAX_KIND_EVENTS as Q,RESERVED_KIND_ROOTS as Y,eventCatalogReasons as z}from"../plugin-objects-manifest.js";import{check as L}from"../permissions/evaluator.js";import{userOf as X}from"../permissions/evaluator.js";import{nodePlace as Z}from"../permissions/ceiling.js";import{PermissionError as ee}from"../permissions/errors.js";import{writeActsFor as te}from"../permissions/facts.js";async function ne(e,t,n){return t.class!=="person"&&t.app.id===n.id?!0:await L(e,t,"edit",n.id)}l(ne,"mayActAs");async function ie(e,t,n){return await L(e,t,"read",n.id)}l(ie,"customReachable");const se=["public","private"];function h(e,t){throw new j(e,t,S.GENERAL)}l(h,"conflict");function b(e){return C("sha256").update(e).digest("hex")}l(b,"hash");function H(){return`crtr_pub_${$(32).toString("base64url")}`}l(H,"issueToken");function ae(e){if(e==null)return null;if(e==="queue")return JSON.stringify(e);if(typeof e!="object"||Array.isArray(e))throw f("invalid custom receiver; expected queue, an https url, or argv and absolute cwd");if("url"in e&&typeof e.url=="string")try{const n=K(e.url).hostname.replace(/^\[|\]$/g,"");if(G(n)&&!V(n))throw new Error("non-public IP address");return JSON.stringify({url:e.url})}catch(t){throw f(`invalid custom receiver: ${String(t)}`)}if("argv"in e&&Array.isArray(e.argv)&&e.argv.length>0&&e.argv.every(t=>typeof t=="string")&&typeof e.cwd=="string"&&e.cwd.startsWith("/"))return JSON.stringify(e);throw f("invalid custom receiver; url must be https and argv requires a command and absolute cwd")}l(ae,"receiverJson");async function re(e,t){return t.class==="node"?(await Z(e,t.node.id))?.isolated===!0:t.class==="app"||t.class==="object"?t.place?.isolated===!0:!1}l(re,"inIsolatedRun");async function Ne(e,t,n){const{kind:i,name:a,owner:s,description:o,events:u,receiver:c,rotateToken:w,privacy:p}=n;if(p!==void 0&&!se.includes(p))throw f(`invalid privacy ${JSON.stringify(p)}`);if(await ne(e,t,s)||h("forbidden",`cannot act as ${s.id}`),await re(e,t))throw new ee(403,"isolated_run","a custom object cannot be registered from an isolated run");if(!k.test(i)||Y.includes(i.split(".")[0]))throw f(`invalid or reserved kind ${i}`);if(!a.trim()||a!==a.trim()||a.includes("\0"))throw f(`invalid object name ${JSON.stringify(a)}`);const m=ae(c);if(c!=="queue"&&c!==null&&c!==void 0&&"argv"in c&&t.class!=="person"&&t.class!=="node"&&h("argv_forbidden","argv receiver requires a person or node principal"),u===void 0){const d=await e.prepare("SELECT source FROM object_kinds WHERE name = ?").get(i);if(d?.source!=="manifest"&&d?.source!=="peer")throw f("events is required unless the kind is declared by an installed plugin")}else if(Object.keys(u).length>Q||Object.entries(u).some(([d,_])=>!D.test(d)||!Number.isInteger(_?.level)||_.level<1||_.level>10||_.supersedes!==void 0&&typeof _.supersedes!="boolean"||_.ends_wait!==void 0&&typeof _.ends_wait!="boolean"))throw f("invalid custom event catalog");const r=await q(e,s.id,a);if(r&&!await ie(e,t,r)&&h("forbidden",`cannot act as ${s.id}`),r&&t.class!=="person"&&t.class!=="node"){const d=(await e.prepare("SELECT receiver_json FROM custom_objects WHERE object = ?").get(r.id))?.receiver_json;d&&typeof JSON.parse(d)=="object"&&"argv"in JSON.parse(d)&&h("argv_forbidden","argv receiver requires a person or node principal")}r&&r.type!==i&&h("name_taken",`${s.name}/${a} is already held by ${r.id}`);const g=u===void 0?null:await oe(e,{name:i,owner:s,description:o,events:u,read:null,source:"register"});if(!r){const d=await J(e,{type:i,kind:null,owner:s.id,privacy:p??"public",name:a,preview:""});s.type==="app"&&await te(e,d.id,await X(e,t));const _=H();return await e.prepare(`INSERT INTO custom_objects (object, description, receiver_json, token_hash)
2
+ VALUES (?, ?, ?, ?)`).run(d.id,o,m,b(_)),t.class==="node"&&await e.prepare("UPDATE custom_objects SET registered_by = ? WHERE object = ?").run(t.node.id,d.id),m!==null&&await e.prepare("UPDATE objects SET reception = 2 WHERE id = ?").run(d.id),await O(e,{event:"created",object:d,actor:t.class==="object"?t.object:t.class==="person"?t.person:t.class==="app"?t.app:t.node,attrs:{}},async U=>await N(e,U)),{object:d,token:_,created:!0,changed:[]}}const y=await e.prepare("SELECT description, receiver_json FROM custom_objects WHERE object = ?").get(r.id);y||h("name_taken",`${s.name}/${a} is not a custom object`);const E=[];if(y.description!==o&&E.push("description"),y.receiver_json!==m&&E.push("receiver"),g?.changed.includes("events")&&E.push("events"),g?.changed.includes("description")&&!E.includes("description")&&E.push("description"),p!==void 0&&r.privacy!==p&&E.push("privacy"),w&&E.push("token"),E.length===0)return{object:r,created:!1,changed:E};const v=w?H():void 0,T=E.includes("privacy")?{...r,privacy:p}:r;if(T!==r&&await e.prepare("UPDATE objects SET privacy = ? WHERE id = ?").run(p,r.id),await e.prepare("UPDATE custom_objects SET description = ?, receiver_json = ?, token_hash = COALESCE(?, token_hash) WHERE object = ?").run(o,m,v===void 0?null:b(v),r.id),await e.prepare("UPDATE objects SET reception = ? WHERE id = ?").run(m===null?0:2,r.id),y.receiver_json!==m){const d=Date.now();if(m===null){const _=await e.prepare(`UPDATE deliveries SET canceled_at = ?, updated_at = ?
3
3
  WHERE destination_id = ? AND state = 'pending' AND canceled_at IS NULL`).run(d,d,r.id);await e.prepare("UPDATE custom_objects SET dropped = dropped + ? WHERE object = ?").run(_.changes,r.id)}else await e.prepare(`UPDATE deliveries SET next_attempt_at = ?, updated_at = ?
4
- WHERE destination_id = ? AND state = 'pending' AND canceled_at IS NULL`).run(m==='"queue"'?null:d,d,r.id);b(r.id)}return w&&b(r.id),await O(e,{event:"configured",object:T,actor:t.class==="object"?t.object:t.class==="person"?t.person:t.class==="app"?t.app:t.node,attrs:{changed:E.join(",")}},async d=>await N(e,d)),{object:T,...v===void 0?{}:{token:v},created:!1,changed:E}}l(Ne,"registerCustomObject");async function oe(e,t){const{name:i,owner:n,description:a,events:s,read:o,source:u}=t;if(!k.test(i))throw f(`invalid kind ${i}`);if(z(s,i).length>0)throw f("invalid custom event catalog");const c=await e.prepare("SELECT owner, description, events_json, read_json, source, declared FROM object_kinds WHERE name = ?").get(i),w=I(s),p=u==="manifest"&&o!==null?JSON.stringify({command:o.command}):null,m=new Date().toISOString();if(!c)return await e.prepare(`INSERT INTO object_kinds (name, owner, description, events_json, created_at, updated_at, read_json, source, declared)
5
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1)`).run(i,n.id,a,w,m,m,p,u),A(e,i),{outcome:"inserted",changed:[]};const r=c.owner===n.id;if(u==="peer"){if(c.source!=="peer")return{outcome:"skipped",changed:[]}}else if(c.source!=="peer"&&(r||h("kind_owned",`${i} belongs to another owner`),u==="register"&&c.source==="manifest"))return{outcome:"skipped",changed:[]};const g=[];return I(JSON.parse(c.events_json))!==w&&g.push("events"),c.description!==a&&g.push("description"),(c.read_json??null)!==p&&g.push("read"),g.length===0&&r&&c.source===u&&c.declared===1?{outcome:"unchanged",changed:g}:(await e.prepare("UPDATE object_kinds SET owner = ?, description = ?, events_json = ?, read_json = ?, source = ?, declared = 1, updated_at = ? WHERE name = ?").run(n.id,a,w,p,u,m,i),A(e,i),{outcome:"updated",changed:g})}l(oe,"upsertKind");async function Te(e,t,i,n){const a=await e.prepare("SELECT events_json FROM object_kinds WHERE name = ?").get(t.type);if(!a||t.deleted_at!==null)throw f(`${t.id} is not a live custom object`);const s=JSON.parse(a.events_json);if(!D.test(n.event)||!Object.hasOwn(s,n.event))throw new j("unknown_event",`${t.type} declares ${Object.keys(s).join(", ")}, not ${n.event}`,S.USAGE,{declared:Object.keys(s),event:n.event});if(typeof n.summary!="string")throw f("summary must be text");const o=n.state===void 0?null:JSON.stringify(n.state);if(n.state!==void 0&&o===void 0)throw f("state must be JSON-serializable");if(Buffer.byteLength(JSON.stringify({summary:n.summary,...n.state===void 0?{}:{state:n.state}}))>64*1024)throw new j("body_too_large","custom event summary and state exceed 64 KiB",S.USAGE);if(o!==null&&Buffer.byteLength(o)>64*1024)throw f("state exceeds 64 KiB");const u=n.cause??null,c=u===null?null:await e.prepare(`SELECT depth FROM messages WHERE message_id = ?
6
- UNION ALL SELECT depth FROM message_causes WHERE message_id = ? LIMIT 1`).get(u,u);if(u!==null&&c===void 0)throw new j("unknown_cause",`no message ${u}`,S.USAGE);if(c&&c.depth>=F)throw new M(c.depth+1);const w=new Date().toISOString();return await e.prepare("UPDATE custom_objects SET summary = ?, state_json = ?, published_at = ? WHERE object = ?").run(n.summary,o,w,t.id),await e.prepare("UPDATE objects SET preview = ? WHERE id = ?").run(n.summary.split(`
7
- `)[0],t.id),await O(e,{event:`${t.type}.${n.event}`,object:t,actor:i,attrs:{},messageBody:{summary:n.summary,...n.state===void 0?{}:{state:n.state}},...n.cause===void 0?{}:{cause:n.cause}},async p=>await N(e,p))}l(Te,"publishCustom");async function be(e,t,i){if(t.deleted_at!==null)return;await O(e,{event:"deleted",object:t,actor:i,attrs:{}},async a=>await N(e,a));const n=new Date().toISOString();await e.prepare("UPDATE objects SET deleted_at = ?, reception = 0 WHERE id = ?").run(n,t.id),await e.prepare("UPDATE deliveries SET canceled_at = ?, updated_at = ? WHERE destination_id = ? AND state = 'pending' AND canceled_at IS NULL").run(Date.now(),Date.now(),t.id),b(t.id)}l(be,"unregisterCustomObject");function I(e){return JSON.stringify(Object.fromEntries(Object.entries(e).sort(([t],[i])=>t.localeCompare(i)).map(([t,i])=>[t,{level:i.level,...i.supersedes===void 0?{}:{supersedes:i.supersedes},...i.ends_wait===void 0?{}:{ends_wait:i.ends_wait}}])))}l(I,"canonicalEvents");async function Re(e,t,i=0){return await W(e,t.id,i,"pending")}l(Re,"pendingFor");async function Ae(e,t){const i=await e.prepare("SELECT destination_id FROM deliveries WHERE delivery_id = ?").get(t);return i?(await W(e,i.destination_id,-1,"handed_off")).find(n=>n.delivery_id===t)??null:null}l(Ae,"pushLineFor");async function W(e,t,i,n){return(await e.prepare(`SELECT d.delivery_id, m.message_id, m.seq, m.event, m.level, m.object_id, m.actor_id, m.body_json, m.body_ref, m.depth, m.created_at,
4
+ WHERE destination_id = ? AND state = 'pending' AND canceled_at IS NULL`).run(m==='"queue"'?null:d,d,r.id);R(r.id)}return w&&R(r.id),await O(e,{event:"configured",object:T,actor:t.class==="object"?t.object:t.class==="person"?t.person:t.class==="app"?t.app:t.node,attrs:{changed:E.join(",")}},async d=>await N(e,d)),{object:T,...v===void 0?{}:{token:v},created:!1,changed:E}}l(Ne,"registerCustomObject");async function oe(e,t){const{name:n,owner:i,description:a,events:s,read:o,source:u}=t;if(!k.test(n))throw f(`invalid kind ${n}`);if(z(s,n).length>0)throw f("invalid custom event catalog");await e.prepare("SELECT pg_advisory_xact_lock(hashtext(?))").get(`kind:${n}`);const c=await e.prepare("SELECT owner, description, events_json, read_json, source, declared FROM object_kinds WHERE name = ?").get(n),w=I(s),p=u==="manifest"&&o!==null?JSON.stringify({command:o.command}):null,m=new Date().toISOString();if(!c)return await e.prepare(`INSERT INTO object_kinds (name, owner, description, events_json, created_at, updated_at, read_json, source, declared)
5
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1)`).run(n,i.id,a,w,m,m,p,u),A(e,n),{outcome:"inserted",changed:[]};const r=c.owner===i.id;if(u==="peer"){if(c.source!=="peer")return{outcome:"skipped",changed:[]}}else if(c.source!=="peer"&&(r||h("kind_owned",`${n} belongs to another owner`),u==="register"&&c.source==="manifest"))return{outcome:"skipped",changed:[]};const g=[];return I(JSON.parse(c.events_json))!==w&&g.push("events"),c.description!==a&&g.push("description"),(c.read_json??null)!==p&&g.push("read"),g.length===0&&r&&c.source===u&&c.declared===1?{outcome:"unchanged",changed:g}:(await e.prepare("UPDATE object_kinds SET owner = ?, description = ?, events_json = ?, read_json = ?, source = ?, declared = 1, updated_at = ? WHERE name = ?").run(i.id,a,w,p,u,m,n),A(e,n),{outcome:"updated",changed:g})}l(oe,"upsertKind");async function Te(e,t,n,i){const a=await e.prepare("SELECT events_json FROM object_kinds WHERE name = ?").get(t.type);if(!a||t.deleted_at!==null)throw f(`${t.id} is not a live custom object`);const s=JSON.parse(a.events_json);if(!D.test(i.event)||!Object.hasOwn(s,i.event))throw new j("unknown_event",`${t.type} declares ${Object.keys(s).join(", ")}, not ${i.event}`,S.USAGE,{declared:Object.keys(s),event:i.event});if(typeof i.summary!="string")throw f("summary must be text");const o=i.state===void 0?null:JSON.stringify(i.state);if(i.state!==void 0&&o===void 0)throw f("state must be JSON-serializable");if(Buffer.byteLength(JSON.stringify({summary:i.summary,...i.state===void 0?{}:{state:i.state}}))>64*1024)throw new j("body_too_large","custom event summary and state exceed 64 KiB",S.USAGE);if(o!==null&&Buffer.byteLength(o)>64*1024)throw f("state exceeds 64 KiB");const u=i.cause??null,c=u===null?null:await e.prepare(`SELECT depth FROM messages WHERE message_id = ?
6
+ UNION ALL SELECT depth FROM message_causes WHERE message_id = ? LIMIT 1`).get(u,u);if(u!==null&&c===void 0)throw new j("unknown_cause",`no message ${u}`,S.USAGE);if(c&&c.depth>=F)throw new M(c.depth+1);const w=new Date().toISOString();return await e.prepare("UPDATE custom_objects SET summary = ?, state_json = ?, published_at = ? WHERE object = ?").run(i.summary,o,w,t.id),await e.prepare("UPDATE objects SET preview = ? WHERE id = ?").run(i.summary.split(`
7
+ `)[0],t.id),await O(e,{event:`${t.type}.${i.event}`,object:t,actor:n,attrs:{},messageBody:{summary:i.summary,...i.state===void 0?{}:{state:i.state}},...i.cause===void 0?{}:{cause:i.cause}},async p=>await N(e,p))}l(Te,"publishCustom");async function Re(e,t,n){if(t.deleted_at!==null)return;await O(e,{event:"deleted",object:t,actor:n,attrs:{}},async a=>await N(e,a));const i=new Date().toISOString();await e.prepare("UPDATE objects SET deleted_at = ?, reception = 0 WHERE id = ?").run(i,t.id),await e.prepare("UPDATE deliveries SET canceled_at = ?, updated_at = ? WHERE destination_id = ? AND state = 'pending' AND canceled_at IS NULL").run(Date.now(),Date.now(),t.id),R(t.id)}l(Re,"unregisterCustomObject");function I(e){return JSON.stringify(Object.fromEntries(Object.entries(e).sort(([t],[n])=>t.localeCompare(n)).map(([t,n])=>[t,{level:n.level,...n.supersedes===void 0?{}:{supersedes:n.supersedes},...n.ends_wait===void 0?{}:{ends_wait:n.ends_wait}}])))}l(I,"canonicalEvents");async function be(e,t,n=0){return await W(e,t.id,n,"pending")}l(be,"pendingFor");async function Ae(e,t){const n=await e.prepare("SELECT destination_id FROM deliveries WHERE delivery_id = ?").get(t);return n?(await W(e,n.destination_id,-1,"handed_off")).find(i=>i.delivery_id===t)??null:null}l(Ae,"pushLineFor");async function W(e,t,n,i){return(await e.prepare(`SELECT d.delivery_id, m.message_id, m.seq, m.event, m.level, m.object_id, m.actor_id, m.body_json, m.body_ref, m.depth, m.created_at,
8
8
  o.type, o.name, owner.id AS owner_id
9
9
  FROM deliveries d JOIN messages m ON m.message_id = d.message_id JOIN objects o ON o.id = m.object_id
10
10
  LEFT JOIN objects owner ON owner.id = o.owner
11
- WHERE d.destination_id = ? AND d.state = ? AND d.canceled_at IS NULL AND m.seq > ? ORDER BY m.seq`).all(t,n,i)).map(s=>{const o=s.body_json?JSON.parse(s.body_json):{};return{delivery_id:s.delivery_id,message_id:s.message_id,seq:s.seq,event:s.event,level:s.level,object:{id:s.object_id,type:s.type,name:s.name,owner:s.owner_id},actor:s.actor_id,attrs:o.push?.attrs??{},...o.summary===void 0?{}:{summary:o.summary},...o.state===void 0?{}:{state:o.state},...o.push?.pointer===void 0?{}:{pointer:o.push.pointer},depth:s.depth,created_at:s.created_at}})}l(W,"queueLines");async function ke(e,t,i){const n=Date.now(),a=[];let s=0;for(const o of i){const u=await e.prepare(`UPDATE deliveries SET state = 'acknowledged', acknowledged_at = ?, updated_at = ?
12
- WHERE delivery_id = ? AND destination_id = ? AND state = 'pending' AND canceled_at IS NULL`).run(n,n,o,t.id);Number(u.changes)?s++:a.push(o)}return{acknowledged:s,...a.length?{unknown:a}:{}}}l(ke,"acknowledge");async function De(e,t,i){const n=await e.prepare("SELECT token_hash FROM custom_objects WHERE object = ?").get(t.id),a=Buffer.from(n?.token_hash??"".padEnd(64,"0"),"hex"),s=Buffer.from(R(i),"hex");return i.startsWith("crtr_pub_")&&t.deleted_at===null&&a.length===s.length&&$(a,s)&&n?.token_hash!=null}l(De,"verifyPublishToken");export{ke as acknowledge,ie as customReachable,ne as mayActAs,B as onQueueSignal,Re as pendingFor,Te as publishCustom,Ae as pushLineFor,Ne as registerCustomObject,be as unregisterCustomObject,oe as upsertKind,De as verifyPublishToken};
11
+ WHERE d.destination_id = ? AND d.state = ? AND d.canceled_at IS NULL AND m.seq > ? ORDER BY m.seq`).all(t,i,n)).map(s=>{const o=s.body_json?JSON.parse(s.body_json):{};return{delivery_id:s.delivery_id,message_id:s.message_id,seq:s.seq,event:s.event,level:s.level,object:{id:s.object_id,type:s.type,name:s.name,owner:s.owner_id},actor:s.actor_id,attrs:o.push?.attrs??{},...o.summary===void 0?{}:{summary:o.summary},...o.state===void 0?{}:{state:o.state},...o.push?.pointer===void 0?{}:{pointer:o.push.pointer},depth:s.depth,created_at:s.created_at}})}l(W,"queueLines");async function ke(e,t,n){const i=Date.now(),a=[];let s=0;for(const o of n){const u=await e.prepare(`UPDATE deliveries SET state = 'acknowledged', acknowledged_at = ?, updated_at = ?
12
+ WHERE delivery_id = ? AND destination_id = ? AND state = 'pending' AND canceled_at IS NULL`).run(i,i,o,t.id);Number(u.changes)?s++:a.push(o)}return{acknowledged:s,...a.length?{unknown:a}:{}}}l(ke,"acknowledge");async function De(e,t,n){const i=await e.prepare("SELECT token_hash FROM custom_objects WHERE object = ?").get(t.id),a=Buffer.from(i?.token_hash??"".padEnd(64,"0"),"hex"),s=Buffer.from(b(n),"hex");return n.startsWith("crtr_pub_")&&t.deleted_at===null&&a.length===s.length&&P(a,s)&&i?.token_hash!=null}l(De,"verifyPublishToken");export{ke as acknowledge,ie as customReachable,ne as mayActAs,B as onQueueSignal,be as pendingFor,Te as publishCustom,Ae as pushLineFor,Ne as registerCustomObject,Re as unregisterCustomObject,oe as upsertKind,De as verifyPublishToken};
@@ -6,8 +6,55 @@ export interface JobRow {
6
6
  command: string;
7
7
  status: JobStatus;
8
8
  exit_code: number | null;
9
- job_dir: string;
10
- log_path: string;
9
+ /** Blob key of the job's output (`jobs/<node>/<job>/output/<seq>`). */
10
+ output_key: string;
11
+ /** ISO time the node must claim the job by, or null for a job that runs to completion. */
12
+ deadline_at: string | null;
13
+ purpose: string | null;
14
+ quiet_ok: boolean;
15
+ pgid: number | null;
16
+ pgid_identity: string | null;
17
+ activity_last_ms: number | null;
18
+ activity_sampled_ms: number | null;
19
+ activity_files: string | null;
20
+ silence_activity_ms: number | null;
21
+ silence_threshold_ms: number | null;
22
+ exit_reported_at: string | null;
23
+ }
24
+ /** A live job as the node, the roster and the stop guard see it. */
25
+ export interface RunningJob {
26
+ jobId: string;
27
+ command: string;
28
+ purpose: string | null;
29
+ startedAtMs: number;
30
+ outputKey: string;
31
+ deadlineAtMs: number | null;
32
+ pgid: number | null;
33
+ pgidIdentity: string | null;
34
+ quietOk: boolean;
35
+ activity: {
36
+ lastActivityMs: number;
37
+ sampledAtMs: number;
38
+ files: string[];
39
+ } | null;
40
+ silence: {
41
+ lastActivityMs: number;
42
+ thresholdMs: number;
43
+ } | null;
44
+ }
45
+ /** The blob prefix a job's output chunks are written under, one object per chunk (`<prefix>/<seq, 8 digits>`). */
46
+ export declare function jobOutputKey(nodeId: string, jobId: string): string;
47
+ /** What the broker's valve reports about a job it runs: any subset, applied together. */
48
+ export interface JobReport {
49
+ pgid?: number;
50
+ pgidIdentity?: string;
51
+ exitCode?: number;
52
+ vanished?: boolean;
53
+ activity?: {
54
+ lastActivityMs: number;
55
+ sampledAtMs: number;
56
+ files: string[];
57
+ };
11
58
  }
12
59
  export declare function jobRow(db: CanvasDb, job: ObjectRow): Promise<JobRow | null>;
13
60
  /** Create the job object (`id` keeps the job id the runner already uses), its `running` row,
@@ -15,15 +62,46 @@ export declare function jobRow(db: CanvasDb, job: ObjectRow): Promise<JobRow | n
15
62
  export declare function startJob(db: CanvasDb, node: ObjectRow, spec: {
16
63
  id?: string;
17
64
  command: string;
18
- jobDir: string;
19
- logPath: string;
65
+ purpose?: string | null;
66
+ deadlineAtMs?: number | null;
20
67
  }): Promise<ObjectRow>;
21
68
  /** End a running job: `exited` with its exit code, or `killed` with the killer as actor (null
22
69
  * when the runtime killed it, e.g. at its deadline; `reason` then names why). The event carries
23
- * the exit code and log path, never output. A job already ended is left as is. */
70
+ * the exit code and output key, never output. A job already ended is left as is. */
24
71
  export declare function finishJob(db: CanvasDb, job: ObjectRow, end: {
25
72
  exitCode: number;
26
73
  } | {
27
74
  killedBy: ObjectRow | null;
28
75
  reason?: string;
29
76
  }): Promise<ObjectRow>;
77
+ /** Arm (epoch ms) or drop (null) a running job's cancel deadline. */
78
+ export declare function setJobDeadline(db: CanvasDb, jobId: string, deadlineAtMs: number | null): Promise<void>;
79
+ /** Declare a running job an expected wait: it gets no silence messages. */
80
+ export declare function markJobQuietOk(db: CanvasDb, jobId: string): Promise<void>;
81
+ /** Record the silence message last sent for a job. */
82
+ export declare function recordJobSilence(db: CanvasDb, jobId: string, lastActivityMs: number, thresholdMs: number): Promise<void>;
83
+ /** Apply a broker report to a running job's row: the process identity, the activity sample, and the exit
84
+ * (`exit_reported_at` set; the reconciler's next pass ends the job through `finishJob`). */
85
+ export declare function applyJobReport(db: CanvasDb, jobId: string, report: JobReport): Promise<void>;
86
+ /** Every `running` job a node owns, oldest first. */
87
+ export declare function runningJobs(db: CanvasDb, nodeId: string): Promise<RunningJob[]>;
88
+ /** Every `running` job in the canvas, with its owner. */
89
+ export declare function allRunningJobs(db: CanvasDb): Promise<Array<RunningJob & {
90
+ owner: string;
91
+ }>>;
92
+ /** Every `running` job in the canvas whose cancel deadline has passed, with its owner. */
93
+ export declare function expiredJobs(db: CanvasDb, nowMs: number): Promise<Array<RunningJob & {
94
+ owner: string;
95
+ }>>;
96
+ /** Credit every armed deadline of a node's running jobs for an interval the node was not permitted to run. */
97
+ export declare function shiftJobDeadlines(db: CanvasDb, nodeId: string, byMs: number): Promise<number>;
98
+ /** End every running job whose exit the broker reported: `exited` with its code, or `killed` when the process
99
+ * vanished without one. Returns how many ended. */
100
+ export declare function endReportedJobs(db: CanvasDb): Promise<number>;
101
+ /** True when the node's newest execution runs on a runner other than the process runner (a sandbox): its jobs run
102
+ * there, so their pids, supervisor files and output are not on this host and only the broker's reports describe them. */
103
+ export declare function ownerRunsRemotely(db: CanvasDb, nodeId: string): Promise<boolean>;
104
+ /** End, as killed, every running job whose owner's newest execution is a sandbox execution that has exited: the
105
+ * sandbox the job ran in is gone with its broker, so nothing is left to report an exit. Run after `endReportedJobs`,
106
+ * so an exit the broker did report wins. Returns how many ended. */
107
+ export declare function endJobsOfExitedSandboxes(db: CanvasDb): Promise<number>;
@@ -1,2 +1,11 @@
1
- var m=Object.defineProperty;var e=(t,i)=>m(t,"name",{value:i,configurable:!0});import{recordActedOn as s}from"./edges.js";import{publish as u}from"./events.js";import{createObject as f,getById as E}from"./objects.js";import{recordWatch as h}from"./watches.js";function d(t){return t.split(`
2
- `,1)[0].trim()}e(d,"firstLine");function p(t,i){return`${d(t)} (${i})`}e(p,"previewOf");async function b(t,i){return await t.prepare("SELECT * FROM jobs WHERE object = ?").get(i.id)??null}e(b,"jobRow");async function T(t,i,r){if(i.type!=="node")throw new Error(`startJob: ${i.id} is a ${i.type}, not a node`);const a=await f(t,{...r.id===void 0?{}:{id:r.id},type:"job",kind:"bash",owner:i.id,privacy:"public",name:d(r.command)||"bash",preview:p(r.command,"running")});return await t.prepare("INSERT INTO jobs (object, command, status, exit_code, job_dir, log_path) VALUES (?, ?, 'running', NULL, ?, ?)").run(a.id,r.command,r.jobDir,r.logPath),await s(t,i,a),await h(t,i,a,"job-start"),await u(t,{event:"created",object:a,actor:i,attrs:{}}),a}e(T,"startJob");async function v(t,i,r){const a=await b(t,i);if(a===null)throw new Error(`finishJob: ${i.id} has no jobs row`);if(a.status!=="running")return i;const o="killedBy"in r,c=o?"killed":"exited",n=o?null:r.exitCode;await t.prepare("UPDATE jobs SET status = ?, exit_code = ? WHERE object = ?").run(c,n,i.id);const w=o?"killed":`exited ${n}`;await t.prepare("UPDATE objects SET preview = ? WHERE id = ?").run(p(a.command,w),i.id);const l=await E(t,i.id);return o&&r.killedBy!==null&&await s(t,r.killedBy,l),await u(t,{event:c,object:l,actor:o?r.killedBy:null,attrs:o?{"log-path":a.log_path,...r.reason===void 0?{}:{reason:r.reason}}:{"exit-code":n,"log-path":a.log_path}}),l}e(v,"finishJob");export{v as finishJob,b as jobRow,T as startJob};
1
+ var y=Object.defineProperty;var o=(t,e)=>y(t,"name",{value:e,configurable:!0});import{recordActedOn as d}from"./edges.js";import{publish as p}from"./events.js";import{createObject as b,getById as u}from"./objects.js";import{recordWatch as m}from"./watches.js";function w(t,e){return`jobs/${t}/${e}/output`}o(w,"jobOutputKey");function j(t){return t.split(`
2
+ `,1)[0].trim()}o(j,"firstLine");function E(t,e){return`${j(t)} (${e})`}o(E,"previewOf");async function h(t,e){return await t.prepare("SELECT * FROM jobs WHERE object = ?").get(e.id)??null}o(h,"jobRow");async function I(t,e,n){if(e.type!=="node")throw new Error(`startJob: ${e.id} is a ${e.type}, not a node`);const i=await b(t,{...n.id===void 0?{}:{id:n.id},type:"job",kind:"bash",owner:e.id,privacy:"public",name:j(n.command)||"bash",preview:E(n.command,"running")});return await t.prepare(`INSERT INTO jobs (object, command, status, exit_code, output_key, deadline_at, purpose)
3
+ VALUES (?, ?, 'running', NULL, ?, ?, ?)`).run(i.id,n.command,w(e.id,i.id),n.deadlineAtMs==null?null:new Date(n.deadlineAtMs).toISOString(),n.purpose??null),await d(t,e,i),await m(t,e,i,"job-start"),await p(t,{event:"created",object:i,actor:e,attrs:{}}),i}o(I,"startJob");async function f(t,e,n){const i=await h(t,e);if(i===null)throw new Error(`finishJob: ${e.id} has no jobs row`);if(i.status!=="running")return e;const a="killedBy"in n,l=a?"killed":"exited",s=a?null:n.exitCode;await t.prepare("UPDATE jobs SET status = ?, exit_code = ? WHERE object = ?").run(l,s,e.id);const _=a?"killed":`exited ${s}`;await t.prepare("UPDATE objects SET preview = ? WHERE id = ?").run(E(i.command,_),e.id);const r=await u(t,e.id);return a&&n.killedBy!==null&&await d(t,n.killedBy,r),await p(t,{event:l,object:r,actor:a?n.killedBy:null,attrs:a?{"output-key":i.output_key,...n.reason===void 0?{}:{reason:n.reason}}:{"exit-code":s,"output-key":i.output_key}}),r}o(f,"finishJob");async function x(t,e,n){await t.prepare("UPDATE jobs SET deadline_at = ? WHERE object = ? AND status = 'running'").run(n===null?null:new Date(n).toISOString(),e)}o(x,"setJobDeadline");async function L(t,e){await t.prepare("UPDATE jobs SET quiet_ok = true WHERE object = ?").run(e)}o(L,"markJobQuietOk");async function M(t,e,n,i){await t.prepare("UPDATE jobs SET silence_activity_ms = ?, silence_threshold_ms = ? WHERE object = ?").run(Math.trunc(n),Math.trunc(i),e)}o(M,"recordJobSilence");async function v(t,e,n){const i=[],a=[];n.pgid!==void 0&&(i.push("pgid = ?"),a.push(n.pgid)),n.pgidIdentity!==void 0&&(i.push("pgid_identity = ?"),a.push(n.pgidIdentity)),n.activity!==void 0&&(i.push("activity_last_ms = ?","activity_sampled_ms = ?","activity_files = ?"),a.push(Math.trunc(n.activity.lastActivityMs),Math.trunc(n.activity.sampledAtMs),n.activity.files.join(`
4
+ `))),n.exitCode!==void 0?(i.push("exit_code = ?","exit_reported_at = ?"),a.push(n.exitCode,new Date().toISOString())):n.vanished===!0&&(i.push("exit_reported_at = ?"),a.push(new Date().toISOString())),i.length!==0&&await t.prepare(`UPDATE jobs SET ${i.join(", ")} WHERE object = ? AND status = 'running'`).run(...a,e)}o(v,"applyJobReport");function S(t){const e=Number(t.slice(0,Math.max(0,t.indexOf("-"))));return Number.isFinite(e)&&e>0?e:0}o(S,"startedAtMs");function c(t){return{jobId:t.object,command:t.command,purpose:t.purpose,startedAtMs:S(t.object),outputKey:t.output_key,deadlineAtMs:t.deadline_at===null?null:Date.parse(t.deadline_at),pgid:t.pgid,pgidIdentity:t.pgid_identity,quietOk:t.quiet_ok,activity:t.activity_last_ms===null||t.activity_sampled_ms===null?null:{lastActivityMs:t.activity_last_ms,sampledAtMs:t.activity_sampled_ms,files:(t.activity_files??"").split(`
5
+ `).filter(e=>e!=="")},silence:t.silence_activity_ms===null||t.silence_threshold_ms===null?null:{lastActivityMs:t.silence_activity_ms,thresholdMs:t.silence_threshold_ms}}}o(c,"toRunning");async function N(t,e){return(await t.prepare(`SELECT j.* FROM jobs j JOIN objects o ON o.id = j.object
6
+ WHERE o.owner = ? AND o.deleted_at IS NULL AND j.status = 'running'`).all(e)).map(c).sort((i,a)=>i.startedAtMs-a.startedAtMs)}o(N,"runningJobs");async function T(t){return(await t.prepare(`SELECT j.*, o.owner AS owner FROM jobs j JOIN objects o ON o.id = j.object
7
+ WHERE o.deleted_at IS NULL AND j.status = 'running'`).all()).map(n=>({...c(n),owner:n.owner}))}o(T,"allRunningJobs");async function k(t,e){return(await t.prepare(`SELECT j.*, o.owner AS owner FROM jobs j JOIN objects o ON o.id = j.object
8
+ WHERE o.deleted_at IS NULL AND j.status = 'running' AND j.deadline_at IS NOT NULL AND j.deadline_at <= ?`).all(new Date(e).toISOString())).map(i=>({...c(i),owner:i.owner}))}o(k,"expiredJobs");async function J(t,e,n){if(!Number.isFinite(n)||n<=0)return 0;let i=0;for(const a of await N(t,e))a.deadlineAtMs!==null&&(await x(t,a.jobId,a.deadlineAtMs+n),i+=1);return i}o(J,"shiftJobDeadlines");async function C(t){const e=await t.prepare(`SELECT j.object, j.exit_code FROM jobs j JOIN objects o ON o.id = j.object
9
+ WHERE o.deleted_at IS NULL AND j.status = 'running' AND j.exit_reported_at IS NOT NULL`).all();for(const{object:n,exit_code:i}of e){const a=await u(t,n);await f(t,a,i===null?{killedBy:null,reason:"process disappeared without an exit record"}:{exitCode:i})}return e.length}o(C,"endReportedJobs");async function U(t,e){const n=await t.prepare("SELECT runner FROM executions WHERE node_id = ? ORDER BY started_at DESC, attempt DESC LIMIT 1").get(e);return n!==void 0&&n.runner!=="process"}o(U,"ownerRunsRemotely");async function W(t){const e=await t.prepare(`SELECT j.object FROM jobs j JOIN objects o ON o.id = j.object
10
+ WHERE o.deleted_at IS NULL AND j.status = 'running'
11
+ AND (SELECT e.runner || ':' || e.state FROM executions e WHERE e.node_id = o.owner ORDER BY e.started_at DESC, e.attempt DESC LIMIT 1) IN ('blaxel:exited')`).all();for(const{object:n}of e)await f(t,await u(t,n),{killedBy:null,reason:"the sandbox running the job ended with its broker"});return e.length}o(W,"endJobsOfExitedSandboxes");export{T as allRunningJobs,v as applyJobReport,W as endJobsOfExitedSandboxes,C as endReportedJobs,k as expiredJobs,f as finishJob,w as jobOutputKey,h as jobRow,L as markJobQuietOk,U as ownerRunsRemotely,M as recordJobSilence,N as runningJobs,x as setJobDeadline,J as shiftJobDeadlines,I as startJob};
@@ -1,2 +1,2 @@
1
- import{jobRow as d}from"../jobs.js";import{objectDTO as c}from"./dto.js";import{withBase as l}from"./kind.js";import{recordLiveRead as p}from"./live-read.js";import{attr as r,header as w}from"./render.js";const u={name:"job",addressable:"id",events:l({exited:{level:7},killed:{level:7},silent:{level:7}}),async read(a,i,e,s){const o=await d(a,e);if(!o)throw new Error(`job object ${e.id} has no jobs row`);await p(a,i,e,s.reposFn);const n=await c(a,e),t={command:o.command,status:o.status,exit_code:o.exit_code,log_path:o.log_path},m=[w("object",n,r("status",t.status)+r("exit_code",t.exit_code)),`Command: ${t.command}`,`Log: ${t.log_path}`].join(`
2
- `);return{data:t,text:m}}};export{u as jobKind};
1
+ import{jobRow as d}from"../jobs.js";import{objectDTO as c}from"./dto.js";import{withBase as u}from"./kind.js";import{recordLiveRead as p}from"./live-read.js";import{attr as r,header as l}from"./render.js";const v={name:"job",addressable:"id",events:u({exited:{level:7},killed:{level:7},silent:{level:7}}),async read(a,i,e,s){const t=await d(a,e);if(!t)throw new Error(`job object ${e.id} has no jobs row`);await p(a,i,e,s.reposFn);const n=await c(a,e),o={command:t.command,status:t.status,exit_code:t.exit_code,output_key:t.output_key},m=[l("object",n,r("status",o.status)+r("exit_code",o.exit_code)),`Command: ${o.command}`,`Output: ${o.output_key}`].join(`
2
+ `);return{data:o,text:m}}};export{v as jobKind};
@@ -1,11 +1,11 @@
1
- var F=Object.defineProperty;var p=(t,e)=>F(t,"name",{value:e,configurable:!0});import{isSafeNodeId as H}from"../canvas/paths.js";import{OBJECT_TYPE as x}from"../permissions/builtin.js";import{ceilingFrag as W,resolveCeiling as v}from"../permissions/ceiling.js";import{compilePermission as Y}from"../permissions/compile.js";import{permits as k,resolveReader as A,userOf as q}from"../permissions/evaluator.js";import{loadDefinitions as B}from"../permissions/schemas.js";import{and as P,or as b,raw as I}from"../permissions/sql.js";import{getById as J}from"./objects.js";import{flatMapAsync as O}from"../async-array.js";async function g(t,e){const o=await t.prepare(`SELECT o.* FROM objects o JOIN identities i ON i.object = o.id AND i.scheme = 'user'
2
- WHERE o.type = 'person' AND o.home_machine IS NULL AND regexp_replace(i.external_id, '^user:', '') = ?`).get(await q(t,e)),i=await t.prepare(`SELECT a.* FROM objects a WHERE a.type = 'app' AND a.deleted_at IS NULL AND EXISTS (
1
+ var F=Object.defineProperty;var p=(t,e)=>F(t,"name",{value:e,configurable:!0});import{isSafeNodeId as H}from"../canvas/paths.js";import{OBJECT_TYPE as x}from"../permissions/builtin.js";import{ceilingFrag as W,resolveCeiling as v}from"../permissions/ceiling.js";import{compilePermission as Y}from"../permissions/compile.js";import{permits as k,readerUser as q,resolveReader as A}from"../permissions/evaluator.js";import{loadDefinitions as B}from"../permissions/schemas.js";import{and as P,or as b,raw as I}from"../permissions/sql.js";import{getById as J}from"./objects.js";import{flatMapAsync as O}from"../async-array.js";async function g(t,e){const o=await q(t,e),s=o===null?void 0:await t.prepare(`SELECT o.* FROM objects o JOIN identities i ON i.object = o.id AND i.scheme = 'user'
2
+ WHERE o.type = 'person' AND o.home_machine IS NULL AND regexp_replace(i.external_id, '^user:', '') = ?`).get(o),a=await t.prepare(`SELECT a.* FROM objects a WHERE a.type = 'app' AND a.deleted_at IS NULL AND EXISTS (
3
3
  SELECT 1 FROM objects o JOIN documents d ON d.object = o.id
4
4
  WHERE o.owner = a.id AND o.type = 'document' AND o.deleted_at IS NULL AND d.source_path IS NOT NULL)
5
- ORDER BY a.name = 'crtr', a.name`).all(),a=e.class==="node"?[e.node,...e.repos,...e.projectRepos,e.profile,e.app]:e.class==="app"?[e.app]:e.class==="object"?[e.owner,...e.repos,e.app]:[],s=new Set;return[...a,o,...i].filter(r=>!!r&&!s.has(r.id)&&!!s.add(r.id))}p(g,"viewOf");async function V(t,e,o){const i=await k(t,e,"read",o),a=await v(t,e.reader,"read"),s=Y(await B(t),e.subjects,x,"read",I("gr.id")),r={sql:`EXISTS (WITH RECURSIVE up(id, depth) AS (
5
+ ORDER BY a.name = 'crtr', a.name`).all(),c=e.class==="node"?[e.node,...e.repos,...e.projectRepos,e.profile,e.app]:e.class==="app"?[e.app]:e.class==="object"?[e.owner,...e.repos,e.app]:[],r=new Set;return[...c,s,...a].filter(i=>!!i&&!r.has(i.id)&&!!r.add(i.id))}p(g,"viewOf");async function V(t,e,o){const s=await k(t,e,"read",o),a=await v(t,e.reader,"read"),c=Y(await B(t),e.subjects,x,"read",I("gr.id")),r={sql:`EXISTS (WITH RECURSIVE up(id, depth) AS (
6
6
  SELECT ${o}.id, 0
7
7
  UNION ALL SELECT p.owner, up.depth + 1 FROM up JOIN objects p ON p.id = up.id WHERE p.owner IS NOT NULL AND up.depth < 12)
8
- SELECT 1 FROM up gr WHERE ${s.sql})`,params:s.params},c=P([I(`${o}.deleted_at IS NOT NULL AND ${o}.schema_type IS NULL`),...a===null?[]:[W(a,o)],r]);return b([i,c])}p(V,"readableOrDeleted");const S=new WeakMap;function D(t,e){let o=S.get(e);return o===void 0&&(o=V(t,e,"o"),o.catch(()=>S.delete(e)),S.set(e,o)),o}p(D,"readableFrag");async function X(t,e,o){const i=await D(t,e);return(await t.prepareUncached(`SELECT ${i.sql} AS ok FROM objects o WHERE o.id = ?`).get(...i.params,o))?.ok===!0}p(X,"canReadOrDeleted");async function E(t,e,o,i,a,s=!1){const r=await D(t,e);return await t.prepareUncached(`SELECT o.* FROM objects o WHERE (o.type = 'document' OR strpos(o.type, '.') > 0) AND o.owner = ? AND o.name = ? AND o.home_machine IS ${s?"NOT NULL":"NULL"}
9
- AND o.deleted_at IS ${a?"NOT NULL":"NULL"} AND ${r.sql}${a?" ORDER BY o.deleted_at DESC":""}`).all(o.id,i,...r.params)}p(E,"namedObjects");const re=["person","app","repo","profile"],T=/^(person|app|repo|profile|node):([^/]+)\/(.+)$/,h=p(t=>t.trim().replace(/^\[\[(.*)\]\]$/,"$1").trim(),"unwrapRef");function j(t){const e=T.exec(t);if(e)return{type:e[1],handle:e[2],rest:e[3]};const o=t.indexOf("/");return o>0?{handle:t.slice(0,o),rest:t.slice(o+1)}:null}p(j,"splitOwnerRef");async function U(t,e,o){return o==="node"?await t.prepare("SELECT * FROM objects WHERE type = 'node' AND id = ?").all(e):o?await t.prepare("SELECT * FROM objects WHERE type = ? AND (name = ? OR id = ?) AND home_machine IS NULL").all(o,e,e):await t.prepare(`SELECT * FROM objects WHERE (type = 'node' AND id = ?)
10
- OR (type IN ('person','app','repo','profile') AND (name = ? OR id = ?) AND home_machine IS NULL)`).all(e,e,e)}p(U,"ownersByHandle");const _=/^([^:/]+):reports\/(.+?)(?:\.md)?$/;async function ie(t,e,o){const i=[...new Set(o.filter(n=>h(n)===n&&!_.test(n)))],a=new Map;if(i.length===0||e.view.length===0)return a;const s=new Set((await t.prepare("SELECT id FROM objects WHERE id = ANY(?::text[])").all(i)).map(n=>n.id)),r=i.filter(n=>!s.has(n));for(const n of r)a.set(n,null);const c=await D(t,e.ev),w=await t.prepareUncached(`SELECT o.id, o.owner, o.name FROM objects o WHERE (o.type = 'document' OR strpos(o.type, '.') > 0)
11
- AND o.owner = ANY(?::text[]) AND o.name = ANY(?::text[]) AND o.home_machine IS NULL AND o.deleted_at IS NULL AND ${c.sql}`).all(e.view.map(n=>n.id),r,...c.params),m=new Map(e.view.map((n,d)=>[n.id,d])),l=new Map;for(const n of w){const d=m.get(n.owner);d<(l.get(n.name)??1/0)&&(l.set(n.name,d),a.set(n.name,n.id))}return a}p(ie,"viewResolutions");async function se(t,e,o,i){let a=h(o);const s=_.exec(a);s&&!T.test(a)&&(a=`${s[1]}/reports/${s[2]}`);const r=i?.ev??await A(t,e),c=await J(t,a);if(c&&await X(t,r,c.id))return c.deleted_at?{deleted:c}:{object:c};const w=i?.view??await g(t,e);for(const l of w){const n=(await E(t,r,l,a,!1))[0];if(n)return{object:n}}const m=j(a);if(m){const{type:l,handle:n,rest:d}=m,C=l!==void 0,N=await U(t,n,l),R=p(f=>{const u=w.findIndex($=>$.id===f.id);return u<0?1/0:u},"rank"),M=N.filter(f=>R(f)<1/0).sort((f,u)=>R(f)-R(u));for(const f of M){const u=(await E(t,r,f,d,!1))[0];if(u)return{object:u}}const L=await O(N,async f=>await E(t,r,f,d,!1));if(L.length===1)return{object:L[0]};if(L.length>1)return{ambiguous:L};const y=(await O(N,async f=>await E(t,r,f,d,!0)))[0];return y?{deleted:y}:!C&&N.length===0&&H(n)?{remote:a}:{missing:!0}}for(const l of w){const n=(await E(t,r,l,a,!0))[0];if(n)return{deleted:n}}return{missing:!0}}p(se,"resolveName");async function ce(t,e,o){const i=h(o),a=await A(t,e),s=p(async(w,m)=>await O(w,async l=>await E(t,a,l,m,!1,!0)),"found"),r=await s(await g(t,e),i);if(r.length)return r;const c=j(i);return c?await s(await U(t,c.handle,c.type),c.rest):[]}p(ce,"stubsNamed");export{re as HANDLE_TYPES,X as canReadOrDeleted,U as ownersByHandle,V as readableOrDeleted,se as resolveName,ce as stubsNamed,h as unwrapRef,g as viewOf,ie as viewResolutions};
8
+ SELECT 1 FROM up gr WHERE ${c.sql})`,params:c.params},i=P([I(`${o}.deleted_at IS NOT NULL AND ${o}.schema_type IS NULL`),...a===null?[]:[W(a,o)],r]);return b([s,i])}p(V,"readableOrDeleted");const S=new WeakMap;function D(t,e){let o=S.get(e);return o===void 0&&(o=V(t,e,"o"),o.catch(()=>S.delete(e)),S.set(e,o)),o}p(D,"readableFrag");async function X(t,e,o){const s=await D(t,e);return(await t.prepareUncached(`SELECT ${s.sql} AS ok FROM objects o WHERE o.id = ?`).get(...s.params,o))?.ok===!0}p(X,"canReadOrDeleted");async function E(t,e,o,s,a,c=!1){const r=await D(t,e);return await t.prepareUncached(`SELECT o.* FROM objects o WHERE (o.type = 'document' OR strpos(o.type, '.') > 0) AND o.owner = ? AND o.name = ? AND o.home_machine IS ${c?"NOT NULL":"NULL"}
9
+ AND o.deleted_at IS ${a?"NOT NULL":"NULL"} AND ${r.sql}${a?" ORDER BY o.deleted_at DESC":""}`).all(o.id,s,...r.params)}p(E,"namedObjects");const re=["person","app","repo","profile"],T=/^(person|app|repo|profile|node):([^/]+)\/(.+)$/,h=p(t=>t.trim().replace(/^\[\[(.*)\]\]$/,"$1").trim(),"unwrapRef");function j(t){const e=T.exec(t);if(e)return{type:e[1],handle:e[2],rest:e[3]};const o=t.indexOf("/");return o>0?{handle:t.slice(0,o),rest:t.slice(o+1)}:null}p(j,"splitOwnerRef");async function U(t,e,o){return o==="node"?await t.prepare("SELECT * FROM objects WHERE type = 'node' AND id = ?").all(e):o?await t.prepare("SELECT * FROM objects WHERE type = ? AND (name = ? OR id = ?) AND home_machine IS NULL").all(o,e,e):await t.prepare(`SELECT * FROM objects WHERE (type = 'node' AND id = ?)
10
+ OR (type IN ('person','app','repo','profile') AND (name = ? OR id = ?) AND home_machine IS NULL)`).all(e,e,e)}p(U,"ownersByHandle");const _=/^([^:/]+):reports\/(.+?)(?:\.md)?$/;async function ie(t,e,o){const s=[...new Set(o.filter(n=>h(n)===n&&!_.test(n)))],a=new Map;if(s.length===0||e.view.length===0)return a;const c=new Set((await t.prepare("SELECT id FROM objects WHERE id = ANY(?::text[])").all(s)).map(n=>n.id)),r=s.filter(n=>!c.has(n));for(const n of r)a.set(n,null);const i=await D(t,e.ev),w=await t.prepareUncached(`SELECT o.id, o.owner, o.name FROM objects o WHERE (o.type = 'document' OR strpos(o.type, '.') > 0)
11
+ AND o.owner = ANY(?::text[]) AND o.name = ANY(?::text[]) AND o.home_machine IS NULL AND o.deleted_at IS NULL AND ${i.sql}`).all(e.view.map(n=>n.id),r,...i.params),m=new Map(e.view.map((n,d)=>[n.id,d])),l=new Map;for(const n of w){const d=m.get(n.owner);d<(l.get(n.name)??1/0)&&(l.set(n.name,d),a.set(n.name,n.id))}return a}p(ie,"viewResolutions");async function se(t,e,o,s){let a=h(o);const c=_.exec(a);c&&!T.test(a)&&(a=`${c[1]}/reports/${c[2]}`);const r=s?.ev??await A(t,e),i=await J(t,a);if(i&&await X(t,r,i.id))return i.deleted_at?{deleted:i}:{object:i};const w=s?.view??await g(t,e);for(const l of w){const n=(await E(t,r,l,a,!1))[0];if(n)return{object:n}}const m=j(a);if(m){const{type:l,handle:n,rest:d}=m,C=l!==void 0,N=await U(t,n,l),R=p(f=>{const u=w.findIndex($=>$.id===f.id);return u<0?1/0:u},"rank"),M=N.filter(f=>R(f)<1/0).sort((f,u)=>R(f)-R(u));for(const f of M){const u=(await E(t,r,f,d,!1))[0];if(u)return{object:u}}const L=await O(N,async f=>await E(t,r,f,d,!1));if(L.length===1)return{object:L[0]};if(L.length>1)return{ambiguous:L};const y=(await O(N,async f=>await E(t,r,f,d,!0)))[0];return y?{deleted:y}:!C&&N.length===0&&H(n)?{remote:a}:{missing:!0}}for(const l of w){const n=(await E(t,r,l,a,!0))[0];if(n)return{deleted:n}}return{missing:!0}}p(se,"resolveName");async function ce(t,e,o){const s=h(o),a=await A(t,e),c=p(async(w,m)=>await O(w,async l=>await E(t,a,l,m,!1,!0)),"found"),r=await c(await g(t,e),s);if(r.length)return r;const i=j(s);return i?await c(await U(t,i.handle,i.type),i.rest):[]}p(ce,"stubsNamed");export{re as HANDLE_TYPES,X as canReadOrDeleted,U as ownersByHandle,V as readableOrDeleted,se as resolveName,ce as stubsNamed,h as unwrapRef,g as viewOf,ie as viewResolutions};
@@ -15,6 +15,8 @@ export declare function createObject(db: CanvasDb, spec: {
15
15
  /** The permission schema type the object is evaluated under (`firstlight/memory`); omitted = `crtr/object`. */
16
16
  schema_type?: string;
17
17
  }): Promise<ObjectRow>;
18
+ /** The advisory lock key `ensureOwner` takes for a first sighting of an identity. */
19
+ export declare const ownerLockKey: (scheme: string, externalId: string) => string;
18
20
  /** The owner object for an identity, created on first sight. Idempotent on
19
21
  * (scheme, externalId). Handles are unique per owner type: a handle already held
20
22
  * by a different identity of the same type throws `conflict` naming both; it is
@@ -26,7 +28,9 @@ export declare function ensureOwner(db: CanvasDb, type: OwnerType, scheme: strin
26
28
  * A profile with no object yet has nothing to rename (its object is created from the manifest name
27
29
  * on first use). Returns whether an object was renamed. Links are edges to ids, so they keep resolving. */
28
30
  export declare function renameProfileObject(db: CanvasDb, profileId: string, handle: string): Promise<boolean>;
29
- /** The owner object of a principal: `app:<x>` → app `<x>`; `user:<id>` → person (handle `user` for this install's person, else the id). */
31
+ /** The owner object of a principal: `app:<x>` → app `<x>`; `user:<id>` → person (handle `user` for this install's person, else the id).
32
+ * A cloud role serves many people and `personId()` is the request's person, so every person there gets the id as its handle: the one
33
+ * `user` handle would otherwise go to whichever person was created first and refuse every other person with a `conflict`. */
30
34
  export declare function ownerForGrantee(db: CanvasDb, grantee: string): Promise<ObjectRow>;
31
35
  /** The owner's live document named `name`, or null. */
32
36
  export declare function liveDocumentNamed(db: CanvasDb, owner: string, name: string): Promise<ObjectRow | null>;
@@ -1,17 +1,17 @@
1
- var F=Object.defineProperty;var i=(e,t)=>F(e,"name",{value:t,configurable:!0});import{CrtrError as _,notFound as M}from"../errors.js";import{ExitCode as j}from"../../types.js";import{newNodeId as H}from"../runtime/nodes.js";import{kindOf as W}from"./kinds/registry.js";import{bodyOf as v}from"./bodies.js";import{personId as k,personIdAsync as J}from"../canvas/person.js";import{check as P,permits as l,resolveReader as I}from"../permissions/evaluator.js";import{userSubject as y,writeActsFor as b}from"../permissions/facts.js";const Y=new Set(["person","app","repo","profile"]);function D(e){return e||null}i(D,"rowFrom");async function K(e,t){return D(await e.prepare("SELECT * FROM objects WHERE id = ?").get(t))}i(K,"getById");async function X(e,t){return await W(e,t.type),await e.prepare(`INSERT INTO objects (id, type, kind, owner, privacy, name, preview, created_at, home_machine, schema_type)
2
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(t.id??await H(),t.type,t.kind,t.owner,t.privacy,t.name,t.preview,new Date().toISOString(),t.home_machine??null,t.schema_type??null)}i(X,"createObject");async function T(e,t,o,s,n,c){if(!Y.has(t))throw new Error(`ensureOwner: ${t} is not an owner type`);const p=await e.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
3
- WHERE i.scheme = ? AND i.external_id = ?`).get(o,s);if(p)return p;const d=await e.prepare(`SELECT o.id, o.type, i.scheme, i.external_id FROM objects o
1
+ var F=Object.defineProperty;var s=(e,t)=>F(e,"name",{value:t,configurable:!0});import{CrtrError as j,notFound as M}from"../errors.js";import{ExitCode as y}from"../../types.js";import{newNodeId as H}from"../runtime/nodes.js";import{kindOf as W}from"./kinds/registry.js";import{bodyOf as v}from"./bodies.js";import{isCloudRole as k,personId as J,personIdAsync as P}from"../canvas/person.js";import{check as K,permits as L,resolveReader as I}from"../permissions/evaluator.js";import{userSubject as b,writeActsFor as D}from"../permissions/facts.js";const Y=new Set(["person","app","repo","profile"]);function T(e){return e||null}s(T,"rowFrom");async function X(e,t){return T(await e.prepare("SELECT * FROM objects WHERE id = ?").get(t))}s(X,"getById");async function z(e,t){return await W(e,t.type),await e.prepare(`INSERT INTO objects (id, type, kind, owner, privacy, name, preview, created_at, home_machine, schema_type)
2
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(t.id??await H(),t.type,t.kind,t.owner,t.privacy,t.name,t.preview,new Date().toISOString(),t.home_machine??null,t.schema_type??null)}s(z,"createObject");const B=s((e,t)=>`owner:${e}:${t}`,"ownerLockKey");async function x(e,t,o,i,n,c){if(!Y.has(t))throw new Error(`ensureOwner: ${t} is not an owner type`);const d=s(async()=>await e.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
3
+ WHERE i.scheme = ? AND i.external_id = ?`).get(o,i),"find"),E=await d();if(E)return E;await e.prepare("SELECT pg_advisory_xact_lock(hashtext(?))").get(B(o,i));const w=await d();if(w)return w;const p=await e.prepare(`SELECT o.id, o.type, i.scheme, i.external_id FROM objects o
4
4
  LEFT JOIN identities i ON i.object = o.id
5
- WHERE o.name = ? AND o.type = ? AND o.home_machine IS NULL`).get(n,t);if(d)throw new _("conflict",`handle "${n}" is held by ${d.type} ${d.id} (${d.scheme}:${d.external_id}); cannot give it to ${t} ${o}:${s}`,j.GENERAL,{handle:n,holder:d.id});const E=await X(e,{type:t,kind:null,owner:t==="profile"?c?.id??null:null,privacy:"public",name:n,preview:""});return await e.prepare("INSERT INTO identities (object, scheme, external_id) VALUES (?, ?, ?)").run(E.id,o,s),t==="person"&&o==="user"?await b(e,E.id,y(s).id):(t==="profile"||t==="repo")&&await b(e,E.id,y(await J()).id),E}i(T,"ensureOwner");async function re(e,t,o){const s=await e.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
5
+ WHERE o.name = ? AND o.type = ? AND o.home_machine IS NULL`).get(n,t);if(p)throw new j("conflict",`handle "${n}" is held by ${p.type} ${p.id} (${p.scheme}:${p.external_id}); cannot give it to ${t} ${o}:${i}`,y.GENERAL,{handle:n,holder:p.id});const N=await z(e,{type:t,kind:null,owner:t==="profile"?c?.id??null:null,privacy:"public",name:n,preview:""});return await e.prepare("INSERT INTO identities (object, scheme, external_id) VALUES (?, ?, ?)").run(N.id,o,i),t==="person"&&o==="user"?await D(e,N.id,b(i).id):(t==="profile"||t==="repo")&&await D(e,N.id,b(await P()).id),N}s(x,"ensureOwner");async function se(e,t,o){const i=await e.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
6
6
  WHERE i.scheme = 'profile' AND i.external_id = ? AND o.type = 'profile' AND o.deleted_at IS NULL`).get(t),n=await e.prepare(`SELECT o.id FROM objects o
7
- WHERE o.name = ? AND o.type = 'profile' AND o.deleted_at IS NULL AND o.home_machine IS NULL AND o.id <> ?`).get(o,s?s.id:"");if(n)throw new _("conflict",`handle "${o}" is already held by profile object ${n.id}; cannot rename profile ${t} to it`,j.GENERAL,{handle:o,holder:n.id});if(!s)return!1;const c=s;return c.name===o?!1:(await z(e,c,o),!0)}i(re,"renameProfileObject");async function ie(e,t){if(/^app:[a-z0-9][a-z0-9._-]*$/.test(t))return await T(e,"app","app",t,t.slice(4));if(/^user:[a-z0-9][a-z0-9._-]*$/.test(t))return await T(e,"person","user",t,t===k()?"user":t.slice(5));throw new Error(`principal ${JSON.stringify(t)} is neither app:<name> nor user:<id>`)}i(ie,"ownerForGrantee");async function se(e,t,o){return D(await e.prepare("SELECT * FROM objects WHERE (type = 'document' OR strpos(type, '.') > 0) AND owner = ? AND name = ? AND deleted_at IS NULL AND home_machine IS NULL").get(t,o))}i(se,"liveDocumentNamed");async function ae(e,t){const o=await K(e,t);if(!o||o.type!=="node")throw M(`no node object ${t}`);return o}i(ae,"nodeObject");async function z(e,t,o){await e.prepare("UPDATE objects SET name = ? WHERE id = ?").run(o,t.id)}i(z,"setObjectName");async function ce(e,t,o){await e.prepare("UPDATE objects SET kind = ? WHERE id = ?").run(o,t.id)}i(ce,"setObjectKind");function S(e,t){const o=[];return e.type!==void 0&&(o.push("o.type = ?"),t.push(e.type)),e.kind!==void 0&&(o.push("o.kind = ?"),t.push(e.kind)),e.owner!==void 0&&(o.push("o.owner = ?"),t.push(e.owner.id)),e.namePrefix!==void 0&&(o.push("o.name LIKE ? ESCAPE '\\'"),t.push(`${e.namePrefix.replace(/[\\%_]/g,s=>`\\${s}`)}%`)),e.schemaType!==void 0&&(o.push("o.schema_type = ?"),t.push(e.schemaType)),e.extensions!==void 0&&(o.push("EXISTS (SELECT 1 FROM documents x WHERE x.object = o.id AND x.extensions_json::jsonb @> ?::jsonb)"),t.push(JSON.stringify(e.extensions))),o}i(S,"narrowing");function B(e,t){const o="WITH RECURSIVE sa(id, owner, depth) AS (SELECT o.id, o.owner, 0 UNION ALL SELECT p.id, p.owner, sa.depth + 1 FROM sa JOIN objects p ON p.id = sa.owner WHERE sa.depth < 12)";return t.push(e.subjects.userIds,e.subjects.groupIds,e.userId),["o.schema_type IS NULL",`EXISTS (${o} SELECT 1 FROM sa JOIN relations r ON r.resource_type = 'crtr/object' AND r.resource_id = sa.id
7
+ WHERE o.name = ? AND o.type = 'profile' AND o.deleted_at IS NULL AND o.home_machine IS NULL AND o.id <> ?`).get(o,i?i.id:"");if(n)throw new j("conflict",`handle "${o}" is already held by profile object ${n.id}; cannot rename profile ${t} to it`,y.GENERAL,{handle:o,holder:n.id});if(!i)return!1;const c=i;return c.name===o?!1:(await G(e,c,o),!0)}s(se,"renameProfileObject");async function ae(e,t){if(/^app:[a-z0-9][a-z0-9._-]*$/.test(t))return await x(e,"app","app",t,t.slice(4));if(/^user:[a-z0-9][a-z0-9._-]*$/.test(t))return await x(e,"person","user",t,!k()&&t===J()?"user":t.slice(5));throw new Error(`principal ${JSON.stringify(t)} is neither app:<name> nor user:<id>`)}s(ae,"ownerForGrantee");async function ce(e,t,o){return T(await e.prepare("SELECT * FROM objects WHERE (type = 'document' OR strpos(type, '.') > 0) AND owner = ? AND name = ? AND deleted_at IS NULL AND home_machine IS NULL").get(t,o))}s(ce,"liveDocumentNamed");async function de(e,t){const o=await X(e,t);if(!o||o.type!=="node")throw M(`no node object ${t}`);return o}s(de,"nodeObject");async function G(e,t,o){await e.prepare("UPDATE objects SET name = ? WHERE id = ?").run(o,t.id)}s(G,"setObjectName");async function pe(e,t,o){await e.prepare("UPDATE objects SET kind = ? WHERE id = ?").run(o,t.id)}s(pe,"setObjectKind");function O(e,t){const o=[];return e.type!==void 0&&(o.push("o.type = ?"),t.push(e.type)),e.kind!==void 0&&(o.push("o.kind = ?"),t.push(e.kind)),e.owner!==void 0&&(o.push("o.owner = ?"),t.push(e.owner.id)),e.namePrefix!==void 0&&(o.push("o.name LIKE ? ESCAPE '\\'"),t.push(`${e.namePrefix.replace(/[\\%_]/g,i=>`\\${i}`)}%`)),e.schemaType!==void 0&&(o.push("o.schema_type = ?"),t.push(e.schemaType)),e.extensions!==void 0&&(o.push("EXISTS (SELECT 1 FROM documents x WHERE x.object = o.id AND x.extensions_json::jsonb @> ?::jsonb)"),t.push(JSON.stringify(e.extensions))),o}s(O,"narrowing");function V(e,t){const o="WITH RECURSIVE sa(id, owner, depth) AS (SELECT o.id, o.owner, 0 UNION ALL SELECT p.id, p.owner, sa.depth + 1 FROM sa JOIN objects p ON p.id = sa.owner WHERE sa.depth < 12)";t.push(e.subjects.userIds,e.subjects.groupIds);const i=["o.schema_type IS NULL",`EXISTS (${o} SELECT 1 FROM sa JOIN relations r ON r.resource_type = 'crtr/object' AND r.resource_id = sa.id
8
8
  AND r.relation IN ('reader', 'editor', 'messenger', 'user_of') AND r.written_by <> 'package'
9
- AND ((r.subject_type = 'user' AND r.subject_id = ANY(?::text[])) OR (r.subject_type = 'crtr/group' AND r.subject_relation = 'member' AND r.subject_id = ANY(?::text[]))))`,`NOT EXISTS (${o} SELECT 1 FROM sa JOIN relations r ON r.resource_type = 'crtr/object' AND r.resource_id = sa.id
10
- AND r.relation = 'acts_for' AND r.subject_type = 'user' AND r.subject_id = ?)`]}i(B,"sharedOnly");async function de(e,t,o,s={}){const n=["o.seq > ?"],c=[s.after_seq??0];o.privacy!==void 0&&(n.push("o.privacy = ?"),c.push(o.privacy)),o.name!==void 0&&(n.push("o.name = ?"),c.push(o.name)),n.push("o.home_machine IS NULL"),n.push(o.deleted?"o.deleted_at IS NOT NULL":"o.deleted_at IS NULL"),n.push(...S(o,c));const p=await I(e,t);o.shared&&n.push(...B(p,c));const d=await l(e,p,"read","o"),E=Math.min(s.limit??100,100);return await e.prepareUncached(`SELECT o.* FROM objects o WHERE ${n.join(" AND ")} AND (${d.sql}) ORDER BY o.seq LIMIT ${E}`).all(...c,...d.params)}i(de,"listObjects");async function pe(e,t,o){const n=[],c=`%${o.text.replace(/[\\%_]/g,r=>`\\${r}`)}%`,p=await I(e,t);if(!o.grep){const r=[c],u=S(o,r).map(w=>` AND ${w}`).join(""),a=await l(e,p,"read","o"),m=await e.prepareUncached(`SELECT o.*, (SELECT d.summary FROM documents d WHERE d.object = o.id) AS _summary FROM objects o
9
+ AND ((r.subject_type = 'user' AND r.subject_id = ANY(?::text[])) OR (r.subject_type = 'crtr/group' AND r.subject_relation = 'member' AND r.subject_id = ANY(?::text[]))))`];return e.userId===null?i:(t.push(e.userId),[...i,`NOT EXISTS (${o} SELECT 1 FROM sa JOIN relations r ON r.resource_type = 'crtr/object' AND r.resource_id = sa.id
10
+ AND r.relation = 'acts_for' AND r.subject_type = 'user' AND r.subject_id = ?)`])}s(V,"sharedOnly");async function ue(e,t,o,i={}){const n=["o.seq > ?"],c=[i.after_seq??0];o.privacy!==void 0&&(n.push("o.privacy = ?"),c.push(o.privacy)),o.name!==void 0&&(n.push("o.name = ?"),c.push(o.name)),n.push("o.home_machine IS NULL"),n.push(o.deleted?"o.deleted_at IS NOT NULL":"o.deleted_at IS NULL"),n.push(...O(o,c));const d=await I(e,t);o.shared&&n.push(...V(d,c));const E=await L(e,d,"read","o"),w=Math.min(i.limit??100,100);return await e.prepareUncached(`SELECT o.* FROM objects o WHERE ${n.join(" AND ")} AND (${E.sql}) ORDER BY o.seq LIMIT ${w}`).all(...c,...E.params)}s(ue,"listObjects");async function Ee(e,t,o){const n=[],c=`%${o.text.replace(/[\\%_]/g,r=>`\\${r}`)}%`,d=await I(e,t);if(!o.grep){const r=[c],u=O(o,r).map(l=>` AND ${l}`).join(""),a=await L(e,d,"read","o"),m=await e.prepareUncached(`SELECT o.*, (SELECT d.summary FROM documents d WHERE d.object = o.id) AS _summary FROM objects o
11
11
  WHERE o.deleted_at IS NULL AND o.home_machine IS NULL AND (o.name ILIKE ?1 ESCAPE '\\' OR o.preview ILIKE ?1 ESCAPE '\\'
12
12
  OR EXISTS (SELECT 1 FROM documents d WHERE d.object = o.id AND d.summary ILIKE ?1 ESCAPE '\\'))${u}
13
- AND (${a.sql}) ORDER BY o.seq LIMIT 100`).all(...r,...a.params),h=o.text.toLowerCase();for(const w of m){const{_summary:U,...g}=w,f=g,R=f.name.toLowerCase().includes(h)?"name":f.preview.toLowerCase().includes(h)?"preview":"summary";n.push({object:f,field:R,...R==="summary"?{text:String(U)}:{}})}if(n.length>=100)return n}if(!o.body&&!o.grep)return n;const d=new Set(n.map(r=>r.object.id)),E=o.grep?(r=>u=>r.test(u))(new RegExp(o.text)):r=>r.includes(o.text),O=[],x=S(o,O).map(r=>` AND ${r}`).join(""),$=await e.prepareUncached(`SELECT o.id, r.body_hash AS body_hash, d.source_path AS source_path FROM objects o
13
+ AND (${a.sql}) ORDER BY o.seq LIMIT 100`).all(...r,...a.params),f=o.text.toLowerCase();for(const l of m){const{_summary:U,...g}=l,S=g,R=S.name.toLowerCase().includes(f)?"name":S.preview.toLowerCase().includes(f)?"preview":"summary";n.push({object:S,field:R,...R==="summary"?{text:String(U)}:{}})}if(n.length>=100)return n}if(!o.body&&!o.grep)return n;const E=new Set(n.map(r=>r.object.id)),w=o.grep?(r=>u=>r.test(u))(new RegExp(o.text)):r=>r.includes(o.text),p=[],N=O(o,p).map(r=>` AND ${r}`).join(""),$=await e.prepareUncached(`SELECT o.id, r.body_hash AS body_hash, d.source_path AS source_path FROM objects o
14
14
  JOIN documents d ON d.object = o.id JOIN revisions r ON r.document = o.id
15
- WHERE o.type = 'document' AND o.deleted_at IS NULL AND NOT EXISTS (SELECT 1 FROM revision_parents p WHERE p.parent = r.rev_id)${x}
16
- ORDER BY o.seq, r.number`).all(...O),L=await l(e,p,"read","o"),C=200;let N=[];const A=i(async()=>{if(N.length===0)return!1;const r=await e.prepareUncached(`SELECT o.* FROM objects o WHERE o.id = ANY(?::text[]) AND (${L.sql})`).all(N.map(a=>a.id),...L.params),u=new Map(r.map(a=>[a.id,a]));for(const a of N){const m=u.get(a.id);if(m!==void 0&&(n.push({object:m,field:"body",line:a.line,text:a.text}),n.length>=100))return!0}return N=[],!1},"flush");for(const r of $){if(d.has(r.id))continue;let u;try{u=v(r.body_hash,r.source_path)}catch(h){if(await P(e,p,"read",r.id))throw h;continue}if(!o.grep&&!u.includes(o.text))continue;const a=u.split(`
17
- `),m=a.findIndex(E);if(!(m<0)&&(d.add(r.id),N.push({id:r.id,line:m+1,text:a[m]}),N.length>=C&&await A()))return n}return await A(),n}i(pe,"searchObjects");export{X as createObject,T as ensureOwner,K as getById,de as listObjects,se as liveDocumentNamed,ae as nodeObject,ie as ownerForGrantee,re as renameProfileObject,pe as searchObjects,ce as setObjectKind,z as setObjectName};
15
+ WHERE o.type = 'document' AND o.deleted_at IS NULL AND NOT EXISTS (SELECT 1 FROM revision_parents p WHERE p.parent = r.rev_id)${N}
16
+ ORDER BY o.seq, r.number`).all(...p),A=await L(e,d,"read","o"),C=200;let h=[];const _=s(async()=>{if(h.length===0)return!1;const r=await e.prepareUncached(`SELECT o.* FROM objects o WHERE o.id = ANY(?::text[]) AND (${A.sql})`).all(h.map(a=>a.id),...A.params),u=new Map(r.map(a=>[a.id,a]));for(const a of h){const m=u.get(a.id);if(m!==void 0&&(n.push({object:m,field:"body",line:a.line,text:a.text}),n.length>=100))return!0}return h=[],!1},"flush");for(const r of $){if(E.has(r.id))continue;let u;try{u=v(r.body_hash,r.source_path)}catch(f){if(await K(e,d,"read",r.id))throw f;continue}if(!o.grep&&!u.includes(o.text))continue;const a=u.split(`
17
+ `),m=a.findIndex(w);if(!(m<0)&&(E.add(r.id),h.push({id:r.id,line:m+1,text:a[m]}),h.length>=C&&await _()))return n}return await _(),n}s(Ee,"searchObjects");export{z as createObject,x as ensureOwner,X as getById,ue as listObjects,ce as liveDocumentNamed,de as nodeObject,ae as ownerForGrantee,B as ownerLockKey,se as renameProfileObject,Ee as searchObjects,pe as setObjectKind,G as setObjectName};
@@ -10,6 +10,9 @@ export type ReposFn = (cwd: string) => Promise<Checkout[]>;
10
10
  export declare function viewRepos(cwd: string, profileId: string | null, reposFn: ReposFn, lookups: Pick<ReaderLookups, 'projectPaths'>): Promise<Pick<Extract<Reader, {
11
11
  class: 'node';
12
12
  }>, 'repos' | 'checkouts' | 'projectRepos' | 'projectCheckouts'>>;
13
+ /** Run the git lookups a node's reader needs (its cwd and its profile's project paths) now, before a canvas write opens, so
14
+ * `viewRepos` inside the write hits the identity cache instead of spawning git while the write holds a pooled connection. */
15
+ export declare function warmNodeRepos(db: CanvasDb, nodeId: string): Promise<void>;
13
16
  export declare function readerFor(db: CanvasDb, principal: RequestPrincipal, reposFn: ReposFn): Promise<Exclude<Reader, {
14
17
  class: 'object';
15
18
  }>>;
@@ -1,3 +1,3 @@
1
- var N=Object.defineProperty;var p=(o,e)=>N(o,"name",{value:e,configurable:!0});import{coveredByAny as f,isSandboxedNode as b}from"../../shared/env.js";import{existsSync as x}from"node:fs";import{CrtrError as L}from"../errors.js";import{getStoredProfile as $}from"../scoped-state/profiles.js";import{userScopeRoot as A}from"../scope.js";import{parseScope as S}from"@north-light/identity";import{activeGrant as R,grantFor as O}from"../grants/mirror.js";import{ApiError as C}from"../../api/errors.js";import{pluginAppToken as M}from"../secrets.js";import{loadExactProfileManifest as T}from"../profiles/manifest.js";import{ensureOwner as v,getById as H,nodeObject as W,ownerForGrantee as D}from"./objects.js";import{personId as P}from"../canvas/person.js";async function y(o,e,r,a){const t=e===null?[]:a.projectPaths(e),n=await r(o),l=new Set(n.map(s=>s.repo.id)),c=[];for(const s of t){if(!x(s))continue;let i;try{i=await r(s)}catch(u){if(u instanceof L&&u.code==="git-lookup-failed")continue;throw u}for(const u of i)l.has(u.repo.id)||(l.add(u.repo.id),c.push(u))}return{repos:n.map(s=>s.repo),checkouts:n,projectRepos:c.map(s=>s.repo),projectCheckouts:c}}p(y,"viewRepos");async function te(o,e,r){if(e.class==="anonymous")throw new Error("a publish token cannot read as a principal");if(e.class==="person")return{class:"person",person:await j(o)};if(e.class==="app")return{class:"app",app:await E(o,e.app),scopes:e.scopes,...e.sub===void 0?{}:{sub:e.sub}};if(e.class==="machine")throw new Error("a machine token cannot read as a principal");if(e.class==="forwarded"){if(e.actor.kind==="person")return{class:"person",person:await j(o)};const c=await O(e.actor.app,e.sub);if(!c)throw new C(403,"app_not_granted",`${e.actor.app} holds no active grant for ${e.sub} on this machine`);const s=e.actor.node_scopes;return{class:"app",app:await E(o,e.actor.app),place:e.actor.place,launched:s,scopes:s===null?c.resolved:s.filter(i=>f(c.resolved,i))}}if(e.class==="cron"){const c=e.grant?(await R(e.grant))?.resolved??[]:[],s=e.scopes;return{class:"app",app:await E(o,e.app),scopes:s===null?c:s.filter(i=>f(c,i)),launched:s,place:{run:e.origin_run??"",isolated:e.isolated}}}const a=d(o),t=await W(o,e.node),n=await o.prepare("SELECT cwd, profile_id, app FROM nodes WHERE node_id = ?").get(e.node),l=n.app===null?null:await a.object(n.app);if(!l)throw new Error(`node ${e.node} has no app`);return{class:"node",node:t,app:l,scopes:await F(e.app,e.sub,e.grant,e.scopes,a),launched:e.scopes,cwd:n.cwd,...await y(n.cwd,n.profile_id,r,a),profile:n.profile_id===null?null:await a.profile(n.profile_id,l)}}p(te,"readerFor");async function B(o,e,r,a=d(o),t=!1){const n=await o.prepare("SELECT o.* FROM objects o WHERE o.id = ? AND o.type = 'node'").get(e),l=await o.prepare(`SELECT (SELECT external_id FROM identities WHERE object = nodes.app) AS grantee,
2
- sub, grant_id, scopes_json, cwd, profile_id, app FROM nodes WHERE node_id = ?`).get(e);if(!n||!l)throw new Error(`no node ${e}`);const{grantee:c,sub:s,grant_id:i,scopes_json:u,cwd:w,profile_id:g,app:h}=l,m=h===null?null:await a.object(h);if(!m)throw new Error(`node ${e} has no app`);const _=u===null?null:JSON.parse(u);return{class:"node",node:n,app:m,scopes:await F(c,s??"",i,_,a,t),launched:_,cwd:w,...await y(w,g,r,a),profile:g===null?null:await a.profile(g,m)}}p(B,"readerForNode");async function ne(o,e,r=async()=>[],a=d(o)){const t=await o.prepare("SELECT envelope_json FROM peer_stubs WHERE object = ?").get(e.id);if(t!==void 0)return await J(o,e,t.envelope_json===null?null:JSON.parse(t.envelope_json),a);if(e.type==="node")return await B(o,e.id,r,a,!0);const n=e.owner===null?null:await a.object(e.owner);if(!n)throw new Error(`watcher ${e.id} has no owner`);const c=(n.type==="app"?n:n.type==="profile"&&n.owner!==null?await a.object(n.owner):null)??await o.prepare("SELECT o.* FROM identities i JOIN objects o ON o.id = i.object WHERE i.external_id = 'app:terminal' AND i.scheme = 'app'").get()??await o.prepare("SELECT * FROM objects WHERE type = 'person' AND name = 'user' AND home_machine IS NULL").get();if(!c)throw new Error("no terminal app or person object");const s=(await o.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(c.id))?.external_id??"app:terminal",i=await o.prepare("SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(s),u=n.type==="person"?null:i.length===0?a.pluginScopes(s):i.length===1?JSON.parse(i[0].resolved_scopes_json).filter(w=>w.mode==="always").map(w=>S(w.scope)):null;return{class:"object",object:e,node:e,owner:n,app:c,scopes:u,repos:n.type==="repo"?[n]:[]}}p(ne,"readerForObject");async function re(o,e,r){const a=(await o.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(e.id))?.external_id;if(a===void 0)return null;const t=await o.prepare("SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(a);return t.length===0?r.pluginScopes(a):t.length>1?null:JSON.parse(t[0].resolved_scopes_json).filter(n=>n.mode==="always").map(n=>S(n.scope))}p(re,"appScopesNow");async function J(o,e,r,a){const t=await j(o);if(r!==null&&r.app===null)return{class:"object",object:e,node:e,owner:t,app:t,scopes:null,repos:[]};const n=r?.app==null?null:await E(o,r.app),l=r?.app==null?null:await a.grantFor(r.app,r.sub),c=r===null||l===null?null:r.node_scopes===null?l.resolved:r.node_scopes.filter(i=>f(l.resolved,i)),s=n??(e.owner===null?t:await a.object(e.owner)??t);return{class:"object",object:e,node:e,owner:s,app:s,scopes:c,launched:r?.node_scopes??null,repos:[],...r?.place==null?{}:{place:r.place}}}p(J,"readerForStub");function d(o){return{grantById:R,grantFor:O,pluginScopes:p(e=>M(e.replace(/^app:/,""))?.scopes??null,"pluginScopes"),object:p(async e=>await H(o,e),"object"),profile:p(async(e,r)=>await G(o,e,r),"profile"),projectPaths:p(e=>b()?[]:$(A(),e)?.manifest.projects.map(r=>r.path)??[],"projectPaths")}}p(d,"liveLookups");function ae(o){const e=d(o),r=new Map,a=p((t,n)=>(r.has(t)||r.set(t,n()),r.get(t)),"memo");return{grantById:p(t=>a(`g\0${t}`,()=>e.grantById(t)),"grantById"),grantFor:p((t,n)=>a(`a\0${t}\0${n}`,()=>e.grantFor(t,n)),"grantFor"),pluginScopes:p(t=>a(`t\0${t}`,()=>e.pluginScopes(t)),"pluginScopes"),object:p(async t=>await a(`o\0${t}`,async()=>await e.object(t)),"object"),profile:p(async(t,n)=>await a(`p\0${t}\0${n.id}`,async()=>await e.profile(t,n)),"profile"),projectPaths:p(t=>a(`j\0${t}`,()=>e.projectPaths(t)),"projectPaths")}}p(ae,"publishLookups");async function F(o,e,r,a,t,n=!1){const l=r?await t.grantById(r):await t.grantFor(o,e),c=l&&l.grantee===o?l.resolved:null;if(c===null){if(n)return null;throw new Error(`the grant of ${o} is not active`)}return a===null?c:a.filter(s=>f(c,s))}p(F,"nodeScopes");async function j(o){return await D(o,P())}p(j,"personObject");async function E(o,e){return await v(o,"app","app",e,e.replace(/^app:/,""))}p(E,"appObject");async function G(o,e,r){return await o.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
3
- WHERE i.scheme = 'profile' AND i.external_id = ?`).get(e)??await v(o,"profile","profile",e,T(e).manifest.name,r)}p(G,"profileObject");export{re as appScopesNow,d as liveLookups,G as profileObject,ae as publishLookups,te as readerFor,B as readerForNode,ne as readerForObject,y as viewRepos};
1
+ var b=Object.defineProperty;var p=(o,e)=>b(o,"name",{value:e,configurable:!0});import{coveredByAny as d,isSandboxedNode as C}from"../../shared/env.js";import{existsSync as S}from"node:fs";import{CrtrError as L}from"../errors.js";import{getCachedProfile as M}from"../scoped-state/person-store.js";import{parseScope as R}from"@north-light/identity";import{activeGrant as O,grantFor as v}from"../grants/mirror.js";import{ApiError as $}from"../../api/errors.js";import{pluginAppToken as A}from"../secrets.js";import{loadExactProfileManifest as T}from"../profiles/manifest.js";import{ensureOwner as y,getById as H,nodeObject as W,ownerForGrantee as D}from"./objects.js";import{personId as P}from"../canvas/person.js";import{warmCheckoutIdentities as F}from"./repo-sync/repos.js";async function N(o,e,n,a){const t=e===null?[]:a.projectPaths(e),r=await n(o),l=new Set(r.map(s=>s.repo.id)),c=[];for(const s of t){if(!S(s))continue;let i;try{i=await n(s)}catch(u){if(u instanceof L&&u.code==="git-lookup-failed")continue;throw u}for(const u of i)l.has(u.repo.id)||(l.add(u.repo.id),c.push(u))}return{repos:r.map(s=>s.repo),checkouts:r,projectRepos:c.map(s=>s.repo),projectCheckouts:c}}p(N,"viewRepos");async function te(o,e){const n=await o.prepare("SELECT cwd, profile_id FROM nodes WHERE node_id = ?").get(e);if(n!==void 0&&(await F(n.cwd),n.profile_id))for(const a of w(o).projectPaths(n.profile_id))S(a)&&await F(a)}p(te,"warmNodeRepos");async function ne(o,e,n){if(e.class==="anonymous")throw new Error("a publish token cannot read as a principal");if(e.class==="person")return{class:"person",person:await j(o)};if(e.class==="app")return{class:"app",app:await E(o,e.app),scopes:e.scopes,...e.sub===void 0?{}:{sub:e.sub}};if(e.class==="machine")throw new Error("a machine token cannot read as a principal");if(e.class==="forwarded"){if(e.actor.kind==="person")return{class:"person",person:await j(o)};const c=await v(e.actor.app,e.sub);if(!c)throw new $(403,"app_not_granted",`${e.actor.app} holds no active grant for ${e.sub} on this machine`);const s=e.actor.node_scopes;return{class:"app",app:await E(o,e.actor.app),place:e.actor.place,launched:s,scopes:s===null?c.resolved:s.filter(i=>d(c.resolved,i))}}if(e.class==="cron"){const c=e.grant?(await O(e.grant))?.resolved??[]:[],s=e.scopes;return{class:"app",app:await E(o,e.app),scopes:s===null?c:s.filter(i=>d(c,i)),launched:s,place:{run:e.origin_run??"",isolated:e.isolated}}}const a=w(o),t=await W(o,e.node),r=await o.prepare("SELECT cwd, profile_id, app FROM nodes WHERE node_id = ?").get(e.node),l=r.app===null?null:await a.object(r.app);if(!l)throw new Error(`node ${e.node} has no app`);return{class:"node",node:t,app:l,scopes:await x(e.app,e.sub,e.grant,e.scopes,a),launched:e.scopes,cwd:r.cwd,...await N(r.cwd,r.profile_id,n,a),profile:r.profile_id===null?null:await a.profile(r.profile_id,l)}}p(ne,"readerFor");async function B(o,e,n,a=w(o),t=!1){const r=await o.prepare("SELECT o.* FROM objects o WHERE o.id = ? AND o.type = 'node'").get(e),l=await o.prepare(`SELECT (SELECT external_id FROM identities WHERE object = nodes.app) AS grantee,
2
+ sub, grant_id, scopes_json, cwd, profile_id, app FROM nodes WHERE node_id = ?`).get(e);if(!r||!l)throw new Error(`no node ${e}`);const{grantee:c,sub:s,grant_id:i,scopes_json:u,cwd:f,profile_id:g,app:h}=l,m=h===null?null:await a.object(h);if(!m)throw new Error(`node ${e} has no app`);const _=u===null?null:JSON.parse(u);return{class:"node",node:r,app:m,scopes:await x(c,s??"",i,_,a,t),launched:_,cwd:f,...await N(f,g,n,a),profile:g===null?null:await a.profile(g,m)}}p(B,"readerForNode");async function re(o,e,n=async()=>[],a=w(o)){const t=await o.prepare("SELECT envelope_json FROM peer_stubs WHERE object = ?").get(e.id);if(t!==void 0)return await J(o,e,t.envelope_json===null?null:JSON.parse(t.envelope_json),a);if(e.type==="node")return await B(o,e.id,n,a,!0);const r=e.owner===null?null:await a.object(e.owner);if(!r)throw new Error(`watcher ${e.id} has no owner`);const c=(r.type==="app"?r:r.type==="profile"&&r.owner!==null?await a.object(r.owner):null)??await o.prepare("SELECT o.* FROM identities i JOIN objects o ON o.id = i.object WHERE i.external_id = 'app:terminal' AND i.scheme = 'app'").get()??await o.prepare("SELECT * FROM objects WHERE type = 'person' AND name = 'user' AND home_machine IS NULL").get();if(!c)throw new Error("no terminal app or person object");const s=(await o.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(c.id))?.external_id??"app:terminal",i=await o.prepare("SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(s),u=r.type==="person"?null:i.length===0?a.pluginScopes(s):i.length===1?JSON.parse(i[0].resolved_scopes_json).filter(f=>f.mode==="always").map(f=>R(f.scope)):null;return{class:"object",object:e,node:e,owner:r,app:c,scopes:u,repos:r.type==="repo"?[r]:[]}}p(re,"readerForObject");async function ae(o,e,n){const a=(await o.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(e.id))?.external_id;if(a===void 0)return null;const t=await o.prepare("SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(a);return t.length===0?n.pluginScopes(a):t.length>1?null:JSON.parse(t[0].resolved_scopes_json).filter(r=>r.mode==="always").map(r=>R(r.scope))}p(ae,"appScopesNow");async function J(o,e,n,a){const t=await j(o);if(n!==null&&n.app===null)return{class:"object",object:e,node:e,owner:t,app:t,scopes:null,repos:[]};const r=n?.app==null?null:await E(o,n.app),l=n?.app==null?null:await a.grantFor(n.app,n.sub),c=n===null||l===null?null:n.node_scopes===null?l.resolved:n.node_scopes.filter(i=>d(l.resolved,i)),s=r??(e.owner===null?t:await a.object(e.owner)??t);return{class:"object",object:e,node:e,owner:s,app:s,scopes:c,launched:n?.node_scopes??null,repos:[],...n?.place==null?{}:{place:n.place}}}p(J,"readerForStub");function w(o){return{grantById:O,grantFor:v,pluginScopes:p(e=>A(e.replace(/^app:/,""))?.scopes??null,"pluginScopes"),object:p(async e=>await H(o,e),"object"),profile:p(async(e,n)=>await I(o,e,n),"profile"),projectPaths:p(e=>C()?[]:M(e)?.manifest.projects.map(n=>n.path)??[],"projectPaths")}}p(w,"liveLookups");function se(o){const e=w(o),n=new Map,a=p((t,r)=>(n.has(t)||n.set(t,r()),n.get(t)),"memo");return{grantById:p(t=>a(`g\0${t}`,()=>e.grantById(t)),"grantById"),grantFor:p((t,r)=>a(`a\0${t}\0${r}`,()=>e.grantFor(t,r)),"grantFor"),pluginScopes:p(t=>a(`t\0${t}`,()=>e.pluginScopes(t)),"pluginScopes"),object:p(async t=>await a(`o\0${t}`,async()=>await e.object(t)),"object"),profile:p(async(t,r)=>await a(`p\0${t}\0${r.id}`,async()=>await e.profile(t,r)),"profile"),projectPaths:p(t=>a(`j\0${t}`,()=>e.projectPaths(t)),"projectPaths")}}p(se,"publishLookups");async function x(o,e,n,a,t,r=!1){const l=n?await t.grantById(n):await t.grantFor(o,e),c=l&&l.grantee===o?l.resolved:null;if(c===null){if(r)return null;throw new Error(`the grant of ${o} is not active`)}return a===null?c:a.filter(s=>d(c,s))}p(x,"nodeScopes");async function j(o){return await D(o,P())}p(j,"personObject");async function E(o,e){return await y(o,"app","app",e,e.replace(/^app:/,""))}p(E,"appObject");async function I(o,e,n){return await o.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
3
+ WHERE i.scheme = 'profile' AND i.external_id = ?`).get(e)??await y(o,"profile","profile",e,T(e).manifest.name,n)}p(I,"profileObject");export{ae as appScopesNow,w as liveLookups,I as profileObject,se as publishLookups,ne as readerFor,B as readerForNode,re as readerForObject,N as viewRepos,te as warmNodeRepos};
@@ -25,6 +25,8 @@ export declare function normalizeRemoteUrl(url: string, base?: string): string;
25
25
  /** Owner handle a new repo object takes: the last segment of its external id (the normalized remote path, or the folder of a local-path repo), without `.git`. */
26
26
  export declare function handleHint(externalId: string): string;
27
27
  export declare function identifyCheckout(dir: string): CheckoutIdentity;
28
+ /** `identifyCheckout` without blocking the event loop: the same parsing and cache, git run through `gitAsync`. */
29
+ export declare function identifyCheckoutAsync(dir: string): Promise<CheckoutIdentity>;
28
30
  /** Checkouts enclosing `dir`, innermost first; stops at the first directory
29
31
  * that is not in a repo. A failed lookup ends the chain and is returned last. */
30
- export declare function checkoutChain(dir: string): CheckoutIdentity[];
32
+ export declare function checkoutChain(dir: string): Promise<CheckoutIdentity[]>;
@@ -1 +1 @@
1
- var C=Object.defineProperty;var u=(o,e)=>C(o,"name",{value:e,configurable:!0});import{existsSync as O}from"node:fs";import{basename as g,dirname as k,isAbsolute as v,resolve as $}from"node:path";import{gitSync as x}from"../../git.js";import{realpathOrSelf as f}from"../../fs-utils.js";const p=5e3,M=6e4,h=new Map;function b(o,e){const r=o.trim();let t,s;const l=/^([a-z][a-z0-9+.-]*):\/\//i.exec(r);if(l){if(l[1].toLowerCase()==="file")return`file:${w(decodeURIComponent(r.slice(7)),e)}`;const i=r.slice(l[0].length),a=i.indexOf("/"),c=a<0?i:i.slice(0,a);s=a<0?"":i.slice(a),t=c.replace(/^.*@/,"").replace(/:\d*$/,"")}else{const i=/^(?:[^@/]+@)?([^:/]+):(.*)$/.exec(r);if(!i)return`file:${w(r,e)}`;t=i[1],s=i[2]}return s=s.replace(/^\/*~?/,"").replace(/\/+$/,"").replace(/\.git$/i,"").replace(/\/+$/,""),`${t}/${s}`.toLowerCase()}u(b,"normalizeRemoteUrl");function w(o,e){const r=v(o)?o:$(e??process.cwd(),o);return f(r).replace(/\/+$/,"")||"/"}u(w,"localPath");function y(o){const e=o.startsWith("file:")?o.slice(5):o;return g(e).replace(/\.git$/i,"")||"repo"}u(y,"handleHint");function L(o){const e=f($(o)),r=h.get(e);if(r&&Date.now()-r.at<M)return r.id;if(!O(e))return{ok:!1,code:"not-a-repo",dir:e,reason:"directory does not exist"};const t=x(["rev-parse","--path-format=absolute","--show-toplevel","--git-common-dir"],e,void 0,{timeoutMs:p});if(t.timedOut)return{ok:!1,code:"git-lookup-failed",dir:e,reason:`git lookup timed out after ${p}ms`};if(t.status!==0){if(/not a git repository/i.test(t.stderr)){const n={ok:!1,code:"not-a-repo",dir:e,reason:t.stderr.trim()};return h.set(e,{at:Date.now(),id:n}),n}return{ok:!1,code:"git-lookup-failed",dir:e,reason:t.stderr.trim()||t.processError||`git rev-parse exited ${t.status}`}}const[s,l]=t.stdout.split(/\r?\n/).map(n=>n.trim());if(!s||!l)return{ok:!1,code:"git-lookup-failed",dir:e,reason:"git rev-parse returned no work tree (bare repository?)"};const i=f(s),a=f(l),c=x(["remote","get-url","origin"],i,void 0,{timeoutMs:p});if(c.timedOut)return{ok:!1,code:"git-lookup-failed",dir:e,reason:`git remote lookup timed out after ${p}ms`};let d;const m=c.stdout.trim();if(c.status===0&&m!==""){const n=b(m,i);d={ok:!0,scheme:"git",externalId:n,remoteUrl:m,root:i,commonDir:a,handleHint:y(n)}}else if(/no such remote/i.test(c.stderr)){const n=g(a)===".git"?k(a):a;d={ok:!0,scheme:"local-path",externalId:n,root:i,handleHint:y(n)}}else return{ok:!1,code:"git-lookup-failed",dir:e,reason:c.stderr.trim()||`git remote get-url exited ${c.status}`};return h.set(e,{at:Date.now(),id:d}),d}u(L,"identifyCheckout");function z(o){const e=[];let r=o;for(;;){const t=L(r);if(!t.ok)return t.code==="git-lookup-failed"&&e.push(t),e;e.push(t);const s=k(t.root);if(s===t.root)return e;r=s}}u(z,"checkoutChain");export{z as checkoutChain,y as handleHint,L as identifyCheckout,b as normalizeRemoteUrl};
1
+ var L=Object.defineProperty;var a=(t,e)=>L(t,"name",{value:e,configurable:!0});import{existsSync as S}from"node:fs";import{basename as m,dirname as h,isAbsolute as U,resolve as d}from"node:path";import{gitAsync as g,gitSync as k}from"../../git.js";import{realpathOrSelf as u}from"../../fs-utils.js";const c=5e3,_=6e4,f=new Map;function A(t,e){const r=t.trim();let o,n;const s=/^([a-z][a-z0-9+.-]*):\/\//i.exec(r);if(s){if(s[1].toLowerCase()==="file")return`file:${y(decodeURIComponent(r.slice(7)),e)}`;const i=r.slice(s[0].length),l=i.indexOf("/"),E=l<0?i:i.slice(0,l);n=l<0?"":i.slice(l),o=E.replace(/^.*@/,"").replace(/:\d*$/,"")}else{const i=/^(?:[^@/]+@)?([^:/]+):(.*)$/.exec(r);if(!i)return`file:${y(r,e)}`;o=i[1],n=i[2]}return n=n.replace(/^\/*~?/,"").replace(/\/+$/,"").replace(/\.git$/i,"").replace(/\/+$/,""),`${o}/${n}`.toLowerCase()}a(A,"normalizeRemoteUrl");function y(t,e){const r=U(t)?t:d(e??process.cwd(),t);return u(r).replace(/\/+$/,"")||"/"}a(y,"localPath");function w(t){const e=t.startsWith("file:")?t.slice(5):t;return m(e).replace(/\.git$/i,"")||"repo"}a(w,"handleHint");const $=["rev-parse","--path-format=absolute","--show-toplevel","--git-common-dir"],x=["remote","get-url","origin"];function M(t){const e=f.get(t);return e&&Date.now()-e.at<_?e.id:void 0}a(M,"cached");function O(t){return S(t)?void 0:{ok:!1,code:"not-a-repo",dir:t,reason:"directory does not exist"}}a(O,"missingDir");function C(t,e){if(t.timedOut)return{done:{ok:!1,code:"git-lookup-failed",dir:e,reason:`git lookup timed out after ${c}ms`}};if(t.status!==0){if(/not a git repository/i.test(t.stderr)){const n={ok:!1,code:"not-a-repo",dir:e,reason:t.stderr.trim()};return f.set(e,{at:Date.now(),id:n}),{done:n}}return{done:{ok:!1,code:"git-lookup-failed",dir:e,reason:t.stderr.trim()||t.processError||`git rev-parse exited ${t.status}`}}}const[r,o]=t.stdout.split(/\r?\n/).map(n=>n.trim());return!r||!o?{done:{ok:!1,code:"git-lookup-failed",dir:e,reason:"git rev-parse returned no work tree (bare repository?)"}}:{root:u(r),commonDir:u(o)}}a(C,"parseRevParse");function R(t,e,r,o){if(t.timedOut)return{ok:!1,code:"git-lookup-failed",dir:e,reason:`git remote lookup timed out after ${c}ms`};let n;const s=t.stdout.trim();if(t.status===0&&s!==""){const i=A(s,r);n={ok:!0,scheme:"git",externalId:i,remoteUrl:s,root:r,commonDir:o,handleHint:w(i)}}else if(/no such remote/i.test(t.stderr)){const i=m(o)===".git"?h(o):o;n={ok:!0,scheme:"local-path",externalId:i,root:r,handleHint:w(i)}}else return{ok:!1,code:"git-lookup-failed",dir:e,reason:t.stderr.trim()||`git remote get-url exited ${t.status}`};return f.set(e,{at:Date.now(),id:n}),n}a(R,"parseOrigin");function v(t){const e=u(d(t)),r=M(e)??O(e);if(r)return r;const o=C(k($,e,void 0,{timeoutMs:c}),e);return"done"in o?o.done:R(k(x,o.root,void 0,{timeoutMs:c}),e,o.root,o.commonDir)}a(v,"identifyCheckout");const p=new Map;async function D(t){const e=u(d(t)),r=M(e)??O(e);if(r)return r;const o=p.get(e);if(o)return await o;const n=(async()=>{const s=C(await g($,e,void 0,{timeoutMs:c}),e);return"done"in s?s.done:R(await g(x,s.root,void 0,{timeoutMs:c}),e,s.root,s.commonDir)})().finally(()=>p.delete(e));return p.set(e,n),await n}a(D,"identifyCheckoutAsync");async function K(t){const e=[];let r=t;for(;;){const o=await D(r);if(!o.ok)return o.code==="git-lookup-failed"&&e.push(o),e;e.push(o);const n=h(o.root);if(n===o.root)return e;r=n}}a(K,"checkoutChain");export{K as checkoutChain,w as handleHint,v as identifyCheckout,D as identifyCheckoutAsync,A as normalizeRemoteUrl};
@@ -16,6 +16,6 @@ export declare function ensureRepo(db: CanvasDb, id: RepoIdentity, handle?: stri
16
16
  export declare function reposForCwd(db: CanvasDb, cwd: string): Promise<ObjectRow[]>;
17
17
  /** Run the git lookups for `cwd`'s checkouts now, so a later `checkoutsForCwd` inside a canvas write hits the identity
18
18
  * cache instead of spawning git while every other db caller waits on the write lock. */
19
- export declare function warmCheckoutIdentities(cwd: string): void;
19
+ export declare function warmCheckoutIdentities(cwd: string): Promise<void>;
20
20
  /** `reposForCwd` with the directory each repo is checked out at. */
21
21
  export declare function checkoutsForCwd(db: CanvasDb, cwd: string): Promise<Checkout[]>;
@@ -1,3 +1,3 @@
1
1
  var l=Object.defineProperty;var a=(t,o)=>l(t,"name",{value:o,configurable:!0});import{resolve as c,sep as f}from"node:path";import{CrtrError as u}from"../../errors.js";import{realpathOrSelf as h}from"../../fs-utils.js";import{ExitCode as m}from"../../../types.js";import{ensureOwner as w}from"../objects.js";import{checkoutChain as d,handleHint as E}from"./identity.js";async function x(t,o,r){return await t.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
2
- WHERE o.type = 'repo' AND i.scheme = ? AND i.external_id = ?`).get(o,r)??null}a(x,"existingRepo");async function N(t,o,r){const n=await x(t,o.scheme,o.externalId);if(n)return n;const s=r??o.handleHint,p=t.prepare("SELECT 1 FROM objects WHERE name = ? AND type = 'repo' AND home_machine IS NULL");let e=s;for(let i=2;await p.get(e);i++)e=`${s}-${i}`;return await w(t,"repo",o.scheme,o.externalId,e)}a(N,"ensureRepo");async function I(t,o){return(await C(t,o)).map(r=>r.repo)}a(I,"reposForCwd");function L(t){d(h(c(t)))}a(L,"warmCheckoutIdentities");async function C(t,o){const r=h(c(o)),n=[];for(const e of d(r)){if(!e.ok)throw new u("git-lookup-failed",`cannot identify the repo at ${e.dir}: ${e.reason}`,m.GENERAL,{dir:e.dir});n.push({root:e.root,repo:await N(t,e)})}const s=await t.prepare(`SELECT o.*, i.external_id AS path FROM identities i JOIN objects o ON o.id = i.object
3
- WHERE o.type = 'repo' AND i.scheme = 'local-path'`).all();for(const{path:e,...i}of s)(r===e||r.startsWith(e.endsWith(f)?e:e+f))&&n.push({root:e,repo:i});n.sort((e,i)=>i.root.length-e.root.length);const p=new Set;return n.filter(({repo:e})=>!p.has(e.id)&&(p.add(e.id),!0))}a(C,"checkoutsForCwd");export{C as checkoutsForCwd,N as ensureRepo,E as handleHint,I as reposForCwd,L as warmCheckoutIdentities};
2
+ WHERE o.type = 'repo' AND i.scheme = ? AND i.external_id = ?`).get(o,r)??null}a(x,"existingRepo");async function N(t,o,r){const n=await x(t,o.scheme,o.externalId);if(n)return n;const s=r??o.handleHint,p=t.prepare("SELECT 1 FROM objects WHERE name = ? AND type = 'repo' AND home_machine IS NULL");let e=s;for(let i=2;await p.get(e);i++)e=`${s}-${i}`;return await w(t,"repo",o.scheme,o.externalId,e)}a(N,"ensureRepo");async function I(t,o){return(await y(t,o)).map(r=>r.repo)}a(I,"reposForCwd");async function L(t){await d(h(c(t)))}a(L,"warmCheckoutIdentities");async function y(t,o){const r=h(c(o)),n=[];for(const e of await d(r)){if(!e.ok)throw new u("git-lookup-failed",`cannot identify the repo at ${e.dir}: ${e.reason}`,m.GENERAL,{dir:e.dir});n.push({root:e.root,repo:await N(t,e)})}const s=await t.prepare(`SELECT o.*, i.external_id AS path FROM identities i JOIN objects o ON o.id = i.object
3
+ WHERE o.type = 'repo' AND i.scheme = 'local-path'`).all();for(const{path:e,...i}of s)(r===e||r.startsWith(e.endsWith(f)?e:e+f))&&n.push({root:e,repo:i});n.sort((e,i)=>i.root.length-e.root.length);const p=new Set;return n.filter(({repo:e})=>!p.has(e.id)&&(p.add(e.id),!0))}a(y,"checkoutsForCwd");export{y as checkoutsForCwd,N as ensureRepo,E as handleHint,I as reposForCwd,L as warmCheckoutIdentities};