@north-light/cli 0.3.579 → 0.3.581

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 (228) hide show
  1. package/dist/api/client.d.ts +6 -1
  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/execution-start.d.ts +14 -0
  6. package/dist/api/dto/health.d.ts +20 -0
  7. package/dist/api/dto/imports.d.ts +142 -0
  8. package/dist/api/dto/imports.js +0 -0
  9. package/dist/api/index.d.ts +1 -0
  10. package/dist/api/index.js +1 -1
  11. package/dist/api/routes.d.ts +2 -0
  12. package/dist/api/routes.js +1 -1
  13. package/dist/builtin-memory/internal/storage-tiers.md +2 -2
  14. package/dist/clients/attach/session/connection.d.ts +3 -1
  15. package/dist/clients/attach/session/connection.js +1 -1
  16. package/dist/clients/attach/viewer.js +510 -510
  17. package/dist/commands/api-client.d.ts +7 -4
  18. package/dist/commands/api-client.js +3 -3
  19. package/dist/commands/sys/daemon.js +1 -1
  20. package/dist/commands/sys/import/resume.d.ts +1 -0
  21. package/dist/commands/sys/import/resume.js +1 -0
  22. package/dist/commands/sys/import/start.d.ts +5 -0
  23. package/dist/commands/sys/import/start.js +4 -0
  24. package/dist/commands/sys/join.d.ts +2 -0
  25. package/dist/commands/sys/join.js +1 -1
  26. package/dist/commands/sys/machine/rotate.d.ts +1 -0
  27. package/dist/commands/sys/machine/rotate.js +1 -0
  28. package/dist/commands/sys/signin.d.ts +7 -0
  29. package/dist/commands/sys/signin.js +2 -0
  30. package/dist/commands/sys.js +1 -1
  31. package/dist/core/blobs/r2.d.ts +4 -0
  32. package/dist/core/blobs/r2.js +7 -4
  33. package/dist/core/canvas/daemon-owner.d.ts +1 -1
  34. package/dist/core/canvas/executions.d.ts +1 -1
  35. package/dist/core/canvas/export-sqlite.js +1 -1
  36. package/dist/core/canvas/node-tokens.d.ts +2 -0
  37. package/dist/core/canvas/node-tokens.js +3 -3
  38. package/dist/core/canvas/schema-pg/009-bodies.sql +10 -0
  39. package/dist/core/canvas/schema-version.d.ts +1 -1
  40. package/dist/core/canvas/schema-version.js +1 -1
  41. package/dist/core/directory/person-credential.d.ts +36 -0
  42. package/dist/core/directory/person-credential.js +1 -0
  43. package/dist/core/feed/feed.js +3 -3
  44. package/dist/core/feed/inbox.js +1 -1
  45. package/dist/core/feed/reports.js +2 -2
  46. package/dist/core/graph/access.d.ts +1 -1
  47. package/dist/core/graph/bodies-boot-copy.d.ts +14 -0
  48. package/dist/core/graph/bodies-boot-copy.js +1 -0
  49. package/dist/core/graph/bodies.d.ts +13 -6
  50. package/dist/core/graph/bodies.js +1 -1
  51. package/dist/core/graph/deletion.d.ts +4 -1
  52. package/dist/core/graph/deletion.js +3 -3
  53. package/dist/core/graph/documents.d.ts +4 -0
  54. package/dist/core/graph/documents.js +10 -10
  55. package/dist/core/graph/objects.js +11 -11
  56. package/dist/core/graph/package-docs.js +5 -5
  57. package/dist/core/graph/reader.js +2 -2
  58. package/dist/core/graph/repo-sync/exchange.js +2 -2
  59. package/dist/core/graph/repo-sync/ref-format.d.ts +0 -1
  60. package/dist/core/graph/repo-sync/ref-format.js +2 -2
  61. package/dist/core/import/bundle/blob-keys.d.ts +10 -0
  62. package/dist/core/import/bundle/blob-keys.js +1 -0
  63. package/dist/core/import/bundle/layout.d.ts +89 -0
  64. package/dist/core/import/bundle/layout.js +5 -0
  65. package/dist/core/import/export/archive.d.ts +11 -0
  66. package/dist/core/import/export/archive.js +1 -0
  67. package/dist/core/import/export/cloud.d.ts +21 -0
  68. package/dist/core/import/export/cloud.js +1 -0
  69. package/dist/core/import/export/dump.d.ts +34 -0
  70. package/dist/core/import/export/dump.js +3 -0
  71. package/dist/core/import/export/files.d.ts +22 -0
  72. package/dist/core/import/export/files.js +1 -0
  73. package/dist/core/import/export/inline.d.ts +3 -0
  74. package/dist/core/import/export/inline.js +1 -0
  75. package/dist/core/import/export/source.d.ts +2 -0
  76. package/dist/core/import/export/source.js +1 -0
  77. package/dist/core/import/export/stores.d.ts +9 -0
  78. package/dist/core/import/export/stores.js +1 -0
  79. package/dist/core/import/load/bind.d.ts +19 -0
  80. package/dist/core/import/load/bind.js +15 -0
  81. package/dist/core/import/load/blobs.d.ts +10 -0
  82. package/dist/core/import/load/blobs.js +1 -0
  83. package/dist/core/import/load/catalog.d.ts +32 -0
  84. package/dist/core/import/load/catalog.js +9 -0
  85. package/dist/core/import/load/copy.d.ts +26 -0
  86. package/dist/core/import/load/copy.js +6 -0
  87. package/dist/core/import/load/path-map.d.ts +7 -0
  88. package/dist/core/import/load/path-map.js +1 -0
  89. package/dist/core/import/load/prepare.d.ts +37 -0
  90. package/dist/core/import/load/prepare.js +10 -0
  91. package/dist/core/import/load/rewrite.d.ts +39 -0
  92. package/dist/core/import/load/rewrite.js +9 -0
  93. package/dist/core/import/load/rollback.d.ts +3 -0
  94. package/dist/core/import/load/rollback.js +6 -0
  95. package/dist/core/import/load/run.d.ts +22 -0
  96. package/dist/core/import/load/run.js +3 -0
  97. package/dist/core/import/load/stage.d.ts +15 -0
  98. package/dist/core/import/load/stage.js +1 -0
  99. package/dist/core/import/load/stores.d.ts +24 -0
  100. package/dist/core/import/load/stores.js +2 -0
  101. package/dist/core/import/load/verify.d.ts +4 -0
  102. package/dist/core/import/load/verify.js +11 -0
  103. package/dist/core/peer/receiver.js +1 -1
  104. package/dist/core/runtime/broker.js +1 -1
  105. package/dist/core/runtime/revive.d.ts +5 -2
  106. package/dist/core/runtime/revive.js +2 -2
  107. package/dist/core/runtime/roadmap.js +3 -3
  108. package/dist/core/runtime/runner/blaxel/runner.js +3 -3
  109. package/dist/core/runtime/runner/index.d.ts +1 -0
  110. package/dist/core/runtime/runner/index.js +1 -1
  111. package/dist/core/runtime/runner/machine/call.d.ts +8 -0
  112. package/dist/core/runtime/runner/machine/call.js +3 -0
  113. package/dist/core/runtime/runner/machine/environment.d.ts +1 -0
  114. package/dist/core/runtime/runner/machine/environment.js +1 -0
  115. package/dist/core/runtime/runner/machine/executor.d.ts +23 -0
  116. package/dist/core/runtime/runner/machine/executor.js +1 -0
  117. package/dist/core/runtime/runner/machine/machine-executor.test.d.ts +1 -0
  118. package/dist/core/runtime/runner/machine/machine-executor.test.js +4 -0
  119. package/dist/core/runtime/runner/machine/ops.d.ts +127 -0
  120. package/dist/core/runtime/runner/machine/ops.js +1 -0
  121. package/dist/core/runtime/runner/machine/runner.d.ts +12 -0
  122. package/dist/core/runtime/runner/machine/runner.js +1 -0
  123. package/dist/core/runtime/runner/types.d.ts +7 -1
  124. package/dist/core/runtime/warm-pool.d.ts +2 -0
  125. package/dist/core/runtime/warm-pool.js +1 -1
  126. package/dist/core/secrets/env-rows.d.ts +1 -0
  127. package/dist/core/secrets/env-rows.js +1 -1
  128. package/dist/core/storage/purge.d.ts +6 -0
  129. package/dist/core/storage/purge.js +4 -0
  130. package/dist/core/subscription-state.d.ts +1 -0
  131. package/dist/core/subscription-state.js +1 -1
  132. package/dist/core/substrate/delivery/corpus.d.ts +2 -0
  133. package/dist/core/substrate/delivery/corpus.js +3 -3
  134. package/dist/core/substrate/delivery/deliver.js +3 -3
  135. package/dist/core/substrate/delivery/render-boot.js +25 -25
  136. package/dist/core/substrate/delivery/render-event.d.ts +1 -1
  137. package/dist/core/substrate/delivery/render-event.js +4 -4
  138. package/dist/core/substrate/delivery/types.d.ts +2 -2
  139. package/dist/daemon/api/bridge.js +3 -3
  140. package/dist/daemon/api/document-listener.js +4 -4
  141. package/dist/daemon/api/handlers/attach.js +1 -1
  142. package/dist/daemon/api/handlers/crons.js +1 -1
  143. package/dist/daemon/api/handlers/daemon.js +1 -1
  144. package/dist/daemon/api/handlers/docs.js +1 -1
  145. package/dist/daemon/api/handlers/execution-start.js +2 -2
  146. package/dist/daemon/api/handlers/files.js +1 -1
  147. package/dist/daemon/api/handlers/imports.d.ts +2 -0
  148. package/dist/daemon/api/handlers/imports.js +1 -0
  149. package/dist/daemon/api/handlers/machine-credential.d.ts +2 -0
  150. package/dist/daemon/api/handlers/machine-credential.js +1 -0
  151. package/dist/daemon/api/handlers/memory.js +4 -4
  152. package/dist/daemon/api/handlers/profiles.js +1 -1
  153. package/dist/daemon/api/handlers/providers.js +1 -1
  154. package/dist/daemon/api/handlers/reports.js +4 -4
  155. package/dist/daemon/api/handlers/runs.js +1 -1
  156. package/dist/daemon/api/import-auth.d.ts +6 -0
  157. package/dist/daemon/api/import-auth.js +1 -0
  158. package/dist/daemon/api/operations.d.ts +2 -2
  159. package/dist/daemon/api/operations.js +1 -1
  160. package/dist/daemon/api/router.d.ts +7 -1
  161. package/dist/daemon/api/router.js +1 -1
  162. package/dist/daemon/api/server.js +1 -1
  163. package/dist/daemon/boot-migrations.js +1 -1
  164. package/dist/daemon/broker-signals/bus.d.ts +3 -2
  165. package/dist/daemon/broker-signals/bus.js +1 -1
  166. package/dist/daemon/cron/armed-context.d.ts +1 -0
  167. package/dist/daemon/cron/armed-context.js +1 -1
  168. package/dist/daemon/cron/closed.d.ts +31 -0
  169. package/dist/daemon/cron/closed.js +1 -0
  170. package/dist/daemon/cron/ended.js +2 -2
  171. package/dist/daemon/cron/lease-recovery.js +1 -1
  172. package/dist/daemon/cron/machine-exec.d.ts +5 -0
  173. package/dist/daemon/cron/machine-exec.js +1 -0
  174. package/dist/daemon/cron/machine-fire.d.ts +26 -0
  175. package/dist/daemon/cron/machine-fire.js +1 -0
  176. package/dist/daemon/cron/recurrence.d.ts +14 -0
  177. package/dist/daemon/cron/recurrence.js +1 -0
  178. package/dist/daemon/cron/run-token.d.ts +7 -0
  179. package/dist/daemon/cron/run-token.js +1 -0
  180. package/dist/daemon/cron-run.js +2 -2
  181. package/dist/daemon/crtrd-cli.js +1 -1
  182. package/dist/daemon/crtrd.d.ts +2 -2
  183. package/dist/daemon/crtrd.js +10 -10
  184. package/dist/daemon/drain.d.ts +25 -0
  185. package/dist/daemon/drain.js +9 -0
  186. package/dist/daemon/due-sources.d.ts +1 -1
  187. package/dist/daemon/due-sources.js +1 -1
  188. package/dist/daemon/due.js +8 -8
  189. package/dist/daemon/fleet.js +5 -5
  190. package/dist/daemon/import-dump-run.d.ts +1 -0
  191. package/dist/daemon/import-dump-run.js +5 -0
  192. package/dist/daemon/machine-daemon.d.ts +7 -0
  193. package/dist/daemon/machine-daemon.js +1 -0
  194. package/dist/daemon/machine-link/client.d.ts +29 -0
  195. package/dist/daemon/machine-link/client.js +1 -0
  196. package/dist/daemon/machine-link/config.d.ts +14 -0
  197. package/dist/daemon/machine-link/config.js +3 -0
  198. package/dist/daemon/machine-link/credential.d.ts +7 -0
  199. package/dist/daemon/machine-link/credential.js +2 -0
  200. package/dist/daemon/machine-link/forward.d.ts +16 -0
  201. package/dist/daemon/machine-link/forward.js +1 -0
  202. package/dist/daemon/machine-link/frames.d.ts +49 -0
  203. package/dist/daemon/machine-link/frames.js +1 -0
  204. package/dist/daemon/machine-link/holder.d.ts +73 -0
  205. package/dist/daemon/machine-link/holder.js +9 -0
  206. package/dist/daemon/manage.d.ts +19 -3
  207. package/dist/daemon/manage.js +3 -3
  208. package/dist/daemon/reconcilers/cron-lane.js +1 -1
  209. package/dist/daemon/reconcilers/delivery-lane.js +2 -2
  210. package/dist/daemon/reconcilers/import-lane.d.ts +9 -0
  211. package/dist/daemon/reconcilers/import-lane.js +1 -0
  212. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +1 -1
  213. package/dist/daemon/reconcilers/node-lifecycle/remote-wake.d.ts +3 -0
  214. package/dist/daemon/reconcilers/node-lifecycle/remote-wake.js +1 -1
  215. package/dist/daemon/reconcilers/node-lifecycle/tick.js +1 -1
  216. package/dist/daemon/reconcilers/purge-due-work.d.ts +13 -0
  217. package/dist/daemon/reconcilers/purge-due-work.js +1 -0
  218. package/dist/daemon/reconcilers/row-claim.d.ts +1 -0
  219. package/dist/daemon/reconcilers/row-claim.js +2 -2
  220. package/dist/daemon/review/finish.js +1 -1
  221. package/dist/daemon/runs/observer.js +3 -3
  222. package/dist/migrations/004-canvas-documents/apply.d.ts +2 -4
  223. package/dist/migrations/004-canvas-documents/apply.js +1 -1
  224. package/dist/migrations/004-canvas-documents/index.js +2 -2
  225. package/package.json +2 -2
  226. package/packages/crouter-identity/package.json +1 -1
  227. package/runtime.lock.json +13 -13
  228. package/scripts/sandbox-start.mjs +185 -2
@@ -0,0 +1,10 @@
1
+ export declare const runBlobPrefix: (run: string) => string;
2
+ export declare const runBlobKey: (run: string, key: string) => string;
3
+ /** `node`: the owner is a node id of the bundle; `request`: a human request id of the bundle. */
4
+ export interface ParsedBlobKey {
5
+ kind: 'conversation' | 'attachment' | 'job' | 'event' | 'request';
6
+ ownerTable: 'nodes' | 'human_requests';
7
+ owner: string;
8
+ }
9
+ /** The key's kind and owner, or null when it is none of the keys cycle 5 reads (or has an empty, `.` or `..` segment). */
10
+ export declare function parseBlobKey(key: string): ParsedBlobKey | null;
@@ -0,0 +1 @@
1
+ var r=Object.defineProperty;var n=(t,e)=>r(t,"name",{value:e,configurable:!0});const u=n(t=>`imports/${t}/blobs/`,"runBlobPrefix"),c=n((t,e)=>`${u(t)}${e}`,"runBlobKey"),a=[["conversation",/^conversations\/([^/]+)\/[^/]+\/[^/]+$/],["attachment",/^attachments\/([^/]+)\/[0-9a-f]{64}$/],["job",/^jobs\/([^/]+)\/[^/]+\/output(?:\/\d{8})?$/],["event",/^events\/([^/]+)\/\d{16}-\d{16}-[0-9a-f]{32}\.jsonl\.zst$/],["request",/^requests\/([^/]+)\/.+$/]];function i(t){if(t.split("/").some(e=>e===""||e==="."||e===".."))return null;for(const[e,s]of a){const o=s.exec(t);if(o!==null)return{kind:e,ownerTable:e==="request"?"human_requests":"nodes",owner:o[1]}}return null}n(i,"parseBlobKey");export{i as parseBlobKey,c as runBlobKey,u as runBlobPrefix};
@@ -0,0 +1,89 @@
1
+ /** `bodies` (revision text, design-fleet D106) is copied like any table but dumped apart: its 667 MB do not fit one row file (`encodeRows` builds one string), so its rows
2
+ * travel as the part files named in `source.bodyParts` (see `rowKeys`), each written and read a line at a time (`RowFileWriter`, `rowLines`). */
3
+ export declare const BODIES_TABLE = "bodies";
4
+ /** Tables whose rows are dumped, staged and (after rewrites) copied, in no particular order: the lane computes the copy
5
+ * order from the target's foreign-key catalog. Each row file holds the table's rows in primary-key order (identity-ordered
6
+ * tables in source `seq` order). */
7
+ export declare const COPIED_TABLES: readonly ["objects", "documents", "revisions", "revision_parents", "identities", "jobs", "custom_objects", "context_sessions", "environments", "object_kinds", "edges", "watches", "watch_subscriptions", "exposures", "rules", "unresolved_links", "nodes", "sessions", "worktrees", "node_faults", "waits", "structured_requests", "memory_reads", "deleted_isolated_nodes", "node_outcome_deliveries", "run_events", "messages", "message_causes", "deliveries", "crons", "cron_runs", "daemon_actions", "human_requests", "human_action_deliveries", "review_comments", "review_comment_events", "audit_rows", "app_users", "idempotency_keys", "uploads", "upload_uses", "shares", "provider_approvals", "relations", "charges", "limits", "conversation_parts", "conversation_entries", "profiles", "bodies"];
8
+ /** Dumped and staged so rewrite 2 can map them, never inserted into the target (`grants`: the directory writes them). */
9
+ export declare const STAGED_ONLY_TABLES: readonly ["grants"];
10
+ /** Every table the exporter dumps. */
11
+ export declare const DUMPED_TABLES: readonly string[];
12
+ /** Tables with an identity primary key, tracked in `import_key_map` (source key → target key). */
13
+ export declare const KEY_MAPPED_TABLES: {
14
+ readonly audit_rows: "id";
15
+ readonly review_comment_events: "seq";
16
+ readonly app_users: "uid";
17
+ };
18
+ /** Tables the target numbers by `seq`/identity in source order (the staged `seq` is not carried over). */
19
+ export declare const SEQ_TABLES: readonly ["objects", "revisions", "watches", "watch_subscriptions", "sessions", "waits", "structured_requests", "deliveries", "run_events"];
20
+ export declare const bundleKeys: {
21
+ readonly prefix: (run: string) => string;
22
+ readonly rows: (run: string, table: string) => string;
23
+ readonly source: (run: string) => string;
24
+ readonly manifest: (run: string) => string;
25
+ readonly nodeFiles: (run: string, node: string) => string;
26
+ readonly bodyRows: (run: string, part: string) => string;
27
+ readonly request: (requestId: string, relative: string) => string;
28
+ };
29
+ /** The blob keys of a table's row files: one file, or for `bodies` its parts. */
30
+ export declare function rowKeys(run: string, table: string, source: Pick<SourceJson, 'bodyParts'>): string[];
31
+ /** `imports/<run>/source.json`. */
32
+ export interface SourceJson {
33
+ installId: string;
34
+ kind: 'machine' | 'hosted';
35
+ /** The source's person (`user:<id>`, or the local person on a runtime that never joined). */
36
+ person: string;
37
+ release: string;
38
+ schemaVersion: number;
39
+ /** Rows per dumped table. The lane refuses a row file whose line count differs. */
40
+ counts: Record<string, number>;
41
+ /** The source's machine environment object id, when it has one (`environments.object`). */
42
+ environmentObject?: string;
43
+ /** Hosted sources: every directory the exporter archived for a running node (`nodeFiles` holds its entries as `<from minus leading slash>/<relative path>`).
44
+ * The lane picks the sandbox directory each one lands in (`sandboxPathMap`); the exporter does not. */
45
+ pathPrefixes?: PathPrefix[];
46
+ /** The part files of the `bodies` rows (`imports/<run>/rows/bodies/<part>.jsonl.zst`): the pre-pause dump's parts, then the pause dump's. `counts.bodies` is their total. */
47
+ bodyParts?: string[];
48
+ /** Shipped documents (`documents.source_path` set): document object id → hash of the body text the exporter read from its file; that text is a `bodies` row in the bundle.
49
+ * The lane uses it for a shipped document the target has no copy of, which becomes an ordinary document (I23). */
50
+ shippedBodies?: Record<string, string>;
51
+ }
52
+ /** One archived directory of a running node: `app` is its grantee (`app:<slug>`), `role` says which directory it is. */
53
+ export interface PathPrefix {
54
+ node: string;
55
+ app: string;
56
+ role: 'context' | 'worktree' | 'cwd';
57
+ from: string;
58
+ }
59
+ /** Rewrite 4: source path prefix → target path prefix, applied to the rows of running nodes of a hosted source. */
60
+ export interface PathMapEntry {
61
+ from: string;
62
+ to: string;
63
+ }
64
+ export type PathMap = readonly PathMapEntry[];
65
+ /** Rewrite one path through the map: the longest matching prefix wins; a path under no prefix is returned unchanged. */
66
+ export declare function mapPath(map: PathMap, path: string): string;
67
+ export type RowObject = Record<string, unknown>;
68
+ /** Row-file codec: one JSON object per line, newline-terminated, zstd-compressed. */
69
+ export declare function encodeRows(rows: Iterable<RowObject>): Buffer;
70
+ export declare function decodeRows(data: Buffer | Uint8Array): RowObject[];
71
+ /** One JSON object per line, newline-terminated: the line `RowFileWriter.write` takes. */
72
+ export declare function rowLine(row: RowObject): string;
73
+ /** Writes a row file as a zstd stream, a line at a time, so no string larger than one line exists (`encodeRows` builds the whole file as one string, past V8's
74
+ * ~512 MiB string limit for a large file). `bytes` is the size of the JSON written so far, before compression. */
75
+ export declare class RowFileWriter {
76
+ private readonly zst;
77
+ private readonly finished;
78
+ private written;
79
+ constructor(path: string);
80
+ get bytes(): number;
81
+ /** Write one line (`rowLine`), waiting for the compressor when it is full. */
82
+ write(line: string, size?: number): Promise<void>;
83
+ /** Finish the file; resolves when it is on disk. */
84
+ close(): Promise<void>;
85
+ destroy(): void;
86
+ }
87
+ /** The lines of a zstd row file, decompressed as a stream: each yielded string is one line (without its newline), so memory is one line plus a chunk. A file whose last
88
+ * line is not newline-terminated, or that is not valid zstd, throws when the reader reaches it. */
89
+ export declare function rowLines(data: Buffer | Uint8Array): AsyncGenerator<string>;
@@ -0,0 +1,5 @@
1
+ var a=Object.defineProperty;var r=(s,e)=>a(s,"name",{value:e,configurable:!0});import{once as d}from"node:events";import{createWriteStream as u}from"node:fs";import{pipeline as f}from"node:stream/promises";import{createZstdCompress as m,createZstdDecompress as p,zstdCompressSync as l,zstdDecompressSync as _}from"node:zlib";const h="bodies",w=["objects","documents","revisions","revision_parents","identities","jobs","custom_objects","context_sessions","environments","object_kinds","edges","watches","watch_subscriptions","exposures","rules","unresolved_links","nodes","sessions","worktrees","node_faults","waits","structured_requests","memory_reads","deleted_isolated_nodes","node_outcome_deliveries","run_events","messages","message_causes","deliveries","crons","cron_runs","daemon_actions","human_requests","human_action_deliveries","review_comments","review_comment_events","audit_rows","app_users","idempotency_keys","uploads","upload_uses","shares","provider_approvals","relations","charges","limits","conversation_parts","conversation_entries","profiles","bodies"],x=["grants"],E=[...w,...x],z={audit_rows:"id",review_comment_events:"seq",app_users:"uid"},$=["objects","revisions","watches","watch_subscriptions","sessions","waits","structured_requests","deliveries","run_events"],c={prefix:r(s=>`imports/${s}/`,"prefix"),rows:r((s,e)=>`imports/${s}/rows/${e}.jsonl.zst`,"rows"),source:r(s=>`imports/${s}/source.json`,"source"),manifest:r(s=>`imports/${s}/manifest.jsonl.zst`,"manifest"),nodeFiles:r((s,e)=>`imports/${s}/node-files/${e}.tar.zst`,"nodeFiles"),bodyRows:r((s,e)=>`imports/${s}/rows/bodies/${e}.jsonl.zst`,"bodyRows"),request:r((s,e)=>`requests/${s}/${e}`,"request")};function B(s,e,t){return e===h?(t.bodyParts??[]).map(o=>c.bodyRows(s,o)):[c.rows(s,e)]}r(B,"rowKeys");function L(s,e){let t;for(const o of s)(e===o.from||e.startsWith(o.from.endsWith("/")?o.from:`${o.from}/`))&&(t===void 0||o.from.length>t.from.length)&&(t=o);return t===void 0?e:t.to+e.slice(t.from.length)}r(L,"mapPath");function j(s){let e="";for(const t of s)e+=`${JSON.stringify(t)}
2
+ `;return l(Buffer.from(e,"utf8"))}r(j,"encodeRows");function A(s){const e=_(s).toString("utf8");if(e==="")return[];if(!e.endsWith(`
3
+ `))throw new Error("row file is truncated: the last line is not newline-terminated");return e.slice(0,-1).split(`
4
+ `).map(t=>JSON.parse(t))}r(A,"decodeRows");function D(s){return`${JSON.stringify(s)}
5
+ `}r(D,"rowLine");class O{static{r(this,"RowFileWriter")}zst=m();finished;written=0;constructor(e){this.finished=f(this.zst,u(e)),this.finished.catch(()=>{})}get bytes(){return this.written}async write(e,t=Buffer.byteLength(e,"utf8")){this.written+=t,this.zst.write(e,"utf8")||await Promise.race([d(this.zst,"drain"),this.finished])}async close(){this.zst.end(),await this.finished}destroy(){this.zst.destroy()}}async function*P(s){const e=p({chunkSize:1048576});e.end(s);let t=[];for await(const o of e){let n=0;for(let i=o.indexOf(10,n);i!==-1;i=o.indexOf(10,n))t.push(o.subarray(n,i)),yield(t.length===1?t[0]:Buffer.concat(t)).toString("utf8"),t=[],n=i+1;n<o.length&&t.push(o.subarray(n))}if(t.length>0)throw new Error("row file is truncated: the last line is not newline-terminated")}r(P,"rowLines");export{h as BODIES_TABLE,w as COPIED_TABLES,E as DUMPED_TABLES,z as KEY_MAPPED_TABLES,O as RowFileWriter,$ as SEQ_TABLES,x as STAGED_ONLY_TABLES,c as bundleKeys,A as decodeRows,j as encodeRows,L as mapPath,B as rowKeys,D as rowLine,P as rowLines};
@@ -0,0 +1,11 @@
1
+ import type { PathPrefix } from '../bundle/layout.js';
2
+ /** One node's archive and the directories in it: context dir, worktree, and a cwd outside both. The lane picks each directory's sandbox target. */
3
+ export declare function nodeArchive(node: {
4
+ node: string;
5
+ app: string;
6
+ cwd: string;
7
+ worktree: string | null;
8
+ }, contextDir?: string): Promise<{
9
+ data: Buffer;
10
+ prefixes: PathPrefix[];
11
+ }>;
@@ -0,0 +1 @@
1
+ var a=Object.defineProperty;var s=(r,c)=>a(r,"name",{value:c,configurable:!0});import{existsSync as n}from"node:fs";import{join as l}from"node:path";import{zstdCompressSync as u}from"node:zlib";import*as w from"tar";import{nodeAgentDir as h}from"../../canvas/paths.js";async function S(r,c=l(h(r.node,r.app),"context")){const e={node:r.node,app:r.app},t=[{...e,role:"context",from:c}];r.worktree!==null&&t.push({...e,role:"worktree",from:r.worktree});const f=t.some(o=>r.cwd===o.from||r.cwd.startsWith(`${o.from}/`));r.cwd!==""&&!f&&t.push({...e,role:"cwd",from:r.cwd});const i=[],p=w.create({cwd:"/",portable:!0,filter:s((o,m)=>!("isSymbolicLink"in m&&m.isSymbolicLink()),"filter")},t.filter(o=>n(o.from)).map(o=>o.from.replace(/^\//,"")));for await(const o of p)i.push(o);return{data:u(Buffer.concat(i)),prefixes:t}}s(S,"nodeArchive");export{S as nodeArchive};
@@ -0,0 +1,21 @@
1
+ import type { ImportRunDTO, ImportStartRequest, ImportStartedDTO, ImportStoreNameDTO, ImportUploadUrlsDTO } from '../../../api/dto/imports.js';
2
+ /** The dump directory's file holding the opened file stores, and the order they are uploaded in. */
3
+ export declare const STORE_FILE = "stores.json";
4
+ export declare const STORE_ORDER: readonly ImportStoreNameDTO[];
5
+ export declare class ImportCloud {
6
+ private readonly apiUrl;
7
+ private readonly token;
8
+ constructor(apiUrl: string, token: string);
9
+ private call;
10
+ start(request: ImportStartRequest): Promise<ImportStartedDTO>;
11
+ uploadUrls(run: string, keys: string[]): Promise<ImportUploadUrlsDTO>;
12
+ putStore(run: string, name: ImportStoreNameDTO, body: unknown): Promise<void>;
13
+ load(run: string): Promise<ImportRunDTO>;
14
+ get(run: string): Promise<ImportRunDTO>;
15
+ machineCredential(run: string): Promise<{
16
+ environment_id: string;
17
+ credential: string;
18
+ }>;
19
+ /** PUT bytes to a signed URL. */
20
+ put(url: string, data: Buffer): Promise<void>;
21
+ }
@@ -0,0 +1 @@
1
+ var l=Object.defineProperty;var i=(a,t)=>l(a,"name",{value:t,configurable:!0});import{general as o}from"../../errors.js";const u="stores.json",d=["auth","credentials","env","settings","plugins"];class h{static{i(this,"ImportCloud")}apiUrl;token;constructor(t,e){this.apiUrl=t,this.token=e}async call(t,e,r){const s=await fetch(new URL(e,this.apiUrl),{method:t,headers:{authorization:`Bearer ${this.token}`,...r===void 0?{}:{"content-type":"application/json"}},...r===void 0?{}:{body:JSON.stringify(r)}}),n=await s.text();if(!s.ok)throw o(`${t} ${e} answered ${s.status}: ${n.slice(0,500)}`);return n===""?void 0:JSON.parse(n)}start(t){return this.call("POST","/v1/imports",t)}uploadUrls(t,e){return this.call("POST",`/v1/imports/${t}/upload-urls`,{keys:e})}putStore(t,e,r){return this.call("PUT",`/v1/imports/${t}/stores/${e}`,r)}load(t){return this.call("POST",`/v1/imports/${t}/load`,{})}get(t){return this.call("GET",`/v1/imports/${t}`)}machineCredential(t){return this.call("POST",`/v1/imports/${t}/machine-credential`,{})}async put(t,e){const r=await fetch(t,{method:"PUT",body:new Uint8Array(e)});if(!r.ok)throw o(`upload answered ${r.status}: ${(await r.text()).slice(0,300)}`)}}export{h as ImportCloud,u as STORE_FILE,d as STORE_ORDER};
@@ -0,0 +1,34 @@
1
+ import { type SourceJson } from '../bundle/layout.js';
2
+ export interface DumpRequestRef {
3
+ id: string;
4
+ dir: string;
5
+ file: string | null;
6
+ }
7
+ export interface DumpRunningNode {
8
+ node: string;
9
+ app: string;
10
+ cwd: string;
11
+ worktree: string | null;
12
+ }
13
+ export interface DumpResult {
14
+ source: SourceJson;
15
+ /** Human requests (page and review) with the folder and reviewed file their rows name. */
16
+ requests: DumpRequestRef[];
17
+ /** Nodes with status active or idle, for the hosted node-file archives. */
18
+ runningNodes: DumpRunningNode[];
19
+ /** `body_ref` files that could not be read and stayed as references. */
20
+ bodiesMissing: number;
21
+ bodiesInlined: number;
22
+ }
23
+ /** A `bodies` part file holds about this much JSON (the encoded lines, before compression); a row that alone exceeds it is a part of its own. */
24
+ export declare const PART_JSON_BYTES: number;
25
+ /** A body longer than this many UTF-8 bytes fails the export. The largest body on Silas's Mac is 5.2 MB. JSON escapes one character into at most six (`\u0001`), so a body at
26
+ * the ceiling is a line of at most 192 MiB, well inside V8's ~512 MiB string limit. */
27
+ export declare const BODY_MAX_BYTES: number;
28
+ /** The pre-pause dump of `bodies` (design-import cutover step 2): every row, while nodes still run. Rows are never updated or deleted, so the pause dump
29
+ * needs only the rows whose hash is not in the list this leaves in `<out>/bodies-pre.json`. */
30
+ export declare function dumpBodiesPre(out: string): Promise<{
31
+ parts: string[];
32
+ count: number;
33
+ }>;
34
+ export declare function dumpRows(out: string, kind: SourceJson['kind']): Promise<DumpResult>;
@@ -0,0 +1,3 @@
1
+ var G=Object.defineProperty;var s=(t,n)=>G(t,"name",{value:n,configurable:!0});import{mkdirSync as v,readFileSync as M,writeFileSync as k}from"node:fs";import{join as f}from"node:path";import{openDb as X,withCanvasWrite as K}from"../../canvas/db.js";import{hashOf as Q,storable as Z}from"../../graph/bodies.js";import{personIdAsync as ee}from"../../canvas/person.js";import{CANVAS_SCHEMA_VERSION as te}from"../../canvas/schema-version.js";import{crtrVersion as ne}from"../../../shared/crtr-version.js";import{BODIES_TABLE as x,DUMPED_TABLES as H,RowFileWriter as ae,SEQ_TABLES as re,bundleKeys as se,encodeRows as oe,rowLine as ie}from"../bundle/layout.js";import{parseFrontmatterGeneric as ce}from"../../frontmatter.js";import{inlineMessageBody as le}from"./inline.js";const he=16*1024*1024,pe=8*1024*1024,D=32*1024*1024,W=5e3,$="bodies-pre.json",F=s(t=>`"${t.replace(/"/g,'""')}"`,"q");function de(t){try{const n=M(t,"utf8");return n.trim()===""?null:n}catch(n){if(n.code==="ENOENT")return null;throw n}}s(de,"readText");function ue(t){try{return M(t,"utf8")}catch(n){if(n.code==="ENOENT")return null;throw n}}s(ue,"readFileOrNull");function P(t,n){return new Error(`revision text ${t} is ${n} bytes; the import carries at most ${D} bytes of text per body`)}s(P,"tooLarge");async function q(t,n,p,r,m){const w=f(n,"rows",x);v(w,{recursive:!0});const _=[];let o=null;const S=s(async()=>{const a=o;o=null,a!==null&&await a.close()},"close"),g=s(async a=>{const l=ie(a),i=Buffer.byteLength(l,"utf8");if(o!==null&&o.bytes>0&&o.bytes+i>he&&await S(),o===null){const h=`${p}-${_.length}`;o=new ae(f(w,`${h}.jsonl.zst`)),_.push(h)}await o.write(l,i)},"add");try{for(let a=0;a<r.length;){const l=await t.prepare("SELECT hash, octet_length(text) AS n FROM bodies WHERE hash = ANY(?::text[]) ORDER BY hash").all(r.slice(a,a+W));let i=[],h=0;const R=s(async()=>{if(i.length===0)return;const d=await t.prepare("SELECT hash, text FROM bodies WHERE hash = ANY(?::text[]) ORDER BY hash").all(i);for(const O of d)await g(O);i=[],h=0},"fetchPage");for(const{hash:d,n:O}of l){const E=Number(O);if(E>D)throw P(d,E);i.length>0&&h+E>pe&&await R(),i.push(d),h+=E}await R(),a+=W}for(const a of m){const l=Buffer.byteLength(a.text,"utf8");if(l>D)throw P(a.hash,l);await g(a)}await S()}catch(a){throw o?.destroy(),a}return _}s(q,"writeBodyParts");const J=s(async t=>(await t.prepare("SELECT hash FROM bodies ORDER BY hash").all()).map(n=>n.hash),"allHashes");async function Ne(t){const n=await X(),p=await J(n),r=await q(n,t,"pre",p,[]),m={hashes:p,parts:r,count:p.length};return k(f(t,$),JSON.stringify(m)),{parts:r,count:p.length}}s(Ne,"dumpBodiesPre");function Ee(t){try{return JSON.parse(M(f(t,$),"utf8"))}catch(n){if(n.code==="ENOENT")return{hashes:[],parts:[],count:0};throw n}}s(Ee,"readPre");async function Le(t,n){const p=await ee();return v(f(t,"rows"),{recursive:!0}),K(async r=>{await r.exec("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ");const m=new Set((await r.prepareUncached("SELECT table_name FROM information_schema.tables WHERE table_schema = current_schema()").all()).map(e=>e.table_name)),w=H.filter(e=>!m.has(e));if(w.length>0)throw new Error(`the canvas database lacks tables the bundle needs: ${w.join(", ")}`);const _=await r.prepareUncached(`SELECT c.conrelid::regclass::text AS tbl, (SELECT array_agg(a.attname::text ORDER BY k.ord) FROM unnest(c.conkey) WITH ORDINALITY k(attnum, ord)
2
+ JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = k.attnum) AS cols FROM pg_constraint c WHERE c.contype = 'p' AND c.connamespace = current_schema()::regnamespace`).all(),o=new Map(_.map(e=>[e.tbl.replace(/"/g,"").replace(/^.*\./,""),e.cols])),S=await r.prepareUncached("SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = current_schema() ORDER BY table_name, ordinal_position").all(),g=new Set(re),a={};let l=0,i=0;for(const e of H.filter(u=>u!==x)){const u=g.has(e)?["seq"]:o.get(e),y=u!==void 0&&u.length>0?u.map(c=>`t.${F(c)}`):S.filter(c=>c.table_name===e).map(c=>`t.${F(c.column_name)}::text`);if(y.length===0)throw new Error(`table ${e} has no columns to order its dump by`);let T=(await r.prepareUncached(`SELECT row_to_json(t)::text AS j FROM ${F(e)} t ORDER BY ${y.join(", ")}`).all()).map(c=>JSON.parse(c.j));e==="messages"&&(T=T.map(c=>{const Y=c.body_ref,j=le(c,de);return typeof Y=="string"&&Y.startsWith("/")&&(j===c?i+=1:l+=1),j})),a[e]=T.length,k(f(t,se.rows("",e).replace(/^imports\/\//,"")),oe(T))}const h=(await r.prepare("SELECT value FROM canvas_meta WHERE key = 'install_id'").get())?.value;if(h===void 0)throw new Error("this canvas database has no install id");const R=await r.prepare("SELECT object FROM environments WHERE kind = 'machine' ORDER BY object LIMIT 1").get(),d={installId:h,kind:n,person:p,release:ne(),schemaVersion:te,counts:a,...R===void 0?{}:{environmentObject:R.object}},O=await r.prepare("SELECT request_id, request_dir, file FROM human_requests ORDER BY request_id").all(),E=await r.prepare("SELECT object, source_path FROM documents WHERE source_path IS NOT NULL ORDER BY object").all(),I={},N=new Map;for(const e of E){const u=ue(e.source_path);if(u===null)continue;const y=Z(ce(u).body),b=Q(y);I[e.object]=b,N.set(b,{hash:b,text:y})}const L=Ee(t),A=new Set(L.hashes),B=(await J(r)).filter(e=>!A.has(e)),C=new Set(B),U=await q(r,t,"pause",B,[...N.values()].filter(e=>!A.has(e.hash)&&!C.has(e.hash))),V=L.count+B.length+[...N.values()].filter(e=>!A.has(e.hash)&&!C.has(e.hash)).length;a[x]=V,d.bodyParts=[...L.parts,...U];const z=await r.prepare(`SELECT n.node_id, r.grantee, n.cwd, w.path AS worktree FROM nodes n JOIN node_rows r ON r.node_id = n.node_id LEFT JOIN worktrees w ON w.node_id = n.node_id AND w.state = 'open'
3
+ WHERE n.status IN ('active', 'idle') ORDER BY n.node_id`).all();return d.shippedBodies=I,{source:d,requests:O.map(e=>({id:e.request_id,dir:e.request_dir,file:e.file})),runningNodes:z.map(e=>({node:e.node_id,app:e.grantee,cwd:e.cwd,worktree:e.worktree})),bodiesInlined:l,bodiesMissing:i}})}s(Le,"dumpRows");export{D as BODY_MAX_BYTES,he as PART_JSON_BYTES,Ne as dumpBodiesPre,Le as dumpRows};
@@ -0,0 +1,22 @@
1
+ import type { ImportCloud } from './cloud.js';
2
+ export interface LocalObject {
3
+ key: string;
4
+ path: string;
5
+ }
6
+ /** Every file the runtime holds that the cloud serves from a blob key. */
7
+ export declare function localObjects(): LocalObject[];
8
+ /** The reviewed file of a review that lies outside the review's own directory, at the key the lane rewrites `file` to. */
9
+ export declare function reviewSourceObject(requestId: string, requestDir: string, file: string | null): LocalObject | null;
10
+ export declare class Uploader {
11
+ private readonly cloud;
12
+ private readonly run;
13
+ private readonly progress;
14
+ private readonly sent;
15
+ uploaded: number;
16
+ missing: string[];
17
+ constructor(cloud: ImportCloud, run: string, progress: (line: string) => void);
18
+ /** Upload source blobs, each to `imports/<run>/blobs/<its key>`: the upload route signs only under the run's own prefix, and the lane moves each blob to its key. */
19
+ upload(objects: readonly LocalObject[]): Promise<void>;
20
+ /** Upload bytes held in memory (row files, source.json, node archives). */
21
+ uploadBytes(key: string, data: Buffer): Promise<void>;
22
+ }
@@ -0,0 +1 @@
1
+ var f=Object.defineProperty;var a=(s,o)=>f(s,"name",{value:o,configurable:!0});import{readFileSync as y,readdirSync as w,statSync as g}from"node:fs";import{basename as b,join as l,relative as k,sep as $}from"node:path";import{runtimeRoot as j}from"../../layout.js";import{bundleKeys as p}from"../bundle/layout.js";import{runBlobKey as d}from"../bundle/blob-keys.js";function c(s,o,t=s){let r;try{r=w(s,{withFileTypes:!0})}catch(e){if(e.code==="ENOENT")return;throw e}for(const e of r){const n=l(s,e.name);e.isDirectory()?c(n,o,t):e.isFile()&&!e.name.endsWith(".tmp")&&!e.name.includes(".tmp-")&&o(n,k(t,n).split($).join("/"))}}a(c,"walk");function S(){const s=j(),o=[];c(l(s,"blobs"),(t,r)=>o.push({key:r,path:t}));for(const t of["requests","reviews"])c(l(s,t),(r,e)=>{const[n,...u]=e.split("/");u.length>0&&o.push({key:p.request(n,u.join("/")),path:r})});return o}a(S,"localObjects");function W(s,o,t){return t===null||!t.startsWith("/")||t.startsWith(o.endsWith("/")?o:`${o}/`)?null:{key:p.request(s,`source/${b(t)}`),path:t}}a(W,"reviewSourceObject");class F{static{a(this,"Uploader")}cloud;run;progress;sent=new Map;uploaded=0;missing=[];constructor(o,t,r){this.cloud=o,this.run=t,this.progress=r}async upload(o){const t=[];for(const r of o){let e;try{e=g(r.path)}catch(u){if(u.code==="ENOENT"){this.missing.push(r.path);continue}throw u}const n=`${e.size}:${e.mtimeMs}`;this.sent.get(r.key)!==n&&t.push({...r,stamp:n})}for(let r=0;r<t.length;r+=100){const e=t.slice(r,r+100),{urls:n}=await this.cloud.uploadUrls(this.run,e.map(i=>d(this.run,i.key)));let u=0;const m=a(async()=>{for(let i=e[u++];i!==void 0;i=e[u++]){const h=n[d(this.run,i.key)];if(h===void 0)throw new Error(`no upload URL returned for ${i.key}`);await this.cloud.put(h,y(i.path)),this.sent.set(i.key,i.stamp),this.uploaded+=1}},"worker");await Promise.all(Array.from({length:Math.min(8,e.length)},m)),this.progress(`uploaded ${Math.min(r+100,t.length)}/${t.length}`)}}async uploadBytes(o,t){const{urls:r}=await this.cloud.uploadUrls(this.run,[o]),e=r[o];if(e===void 0)throw new Error(`no upload URL returned for ${o}`);await this.cloud.put(e,t)}}export{F as Uploader,S as localObjects,W as reviewSourceObject};
@@ -0,0 +1,3 @@
1
+ import type { RowObject } from '../bundle/layout.js';
2
+ /** `read` returns the file's text, or null when the file is absent or empty. */
3
+ export declare function inlineMessageBody(row: RowObject, read: (path: string) => string | null): RowObject;
@@ -0,0 +1 @@
1
+ var f=Object.defineProperty;var r=(t,e)=>f(t,"name",{value:e,configurable:!0});function i(t,e){const o=t.body_ref;if(typeof o!="string"||!o.startsWith("/"))return t;const s=e(o);if(s===null)return t;const n=t.body_json,y=n!==null&&typeof n=="object"&&!Array.isArray(n)?n:{};return{...t,body_ref:null,body_json:{...y,body_full:s}}}r(i,"inlineMessageBody");export{i as inlineMessageBody};
@@ -0,0 +1,2 @@
1
+ import type { PathPrefix, SourceJson } from '../bundle/layout.js';
2
+ export declare function buildSourceJson(dump: SourceJson, prefixes: readonly PathPrefix[]): SourceJson;
@@ -0,0 +1 @@
1
+ var o=Object.defineProperty;var e=(t,n)=>o(t,"name",{value:n,configurable:!0});function u(t,n){return{...t,...n.length>0?{pathPrefixes:[...n]}:{}}}e(u,"buildSourceJson");export{u as buildSourceJson};
@@ -0,0 +1,9 @@
1
+ import type { ImportAuthStoreDTO, ImportCredentialsStoreDTO, ImportEnvStoreDTO, ImportPluginsStoreDTO, ImportSettingsStoreDTO } from '../../../api/dto/imports.js';
2
+ export interface ExportedStores {
3
+ auth: ImportAuthStoreDTO;
4
+ credentials: ImportCredentialsStoreDTO;
5
+ env: ImportEnvStoreDTO;
6
+ settings: ImportSettingsStoreDTO;
7
+ plugins: ImportPluginsStoreDTO;
8
+ }
9
+ export declare function readStores(): Promise<ExportedStores>;
@@ -0,0 +1 @@
1
+ var k=Object.defineProperty;var _=(a,t)=>k(a,"name",{value:t,configurable:!0});import{openDb as R}from"../../canvas/db.js";import{personIdAsync as h}from"../../canvas/person.js";import{loadSecretsKey as y,open as c}from"../../secrets/index.js";import{aadOf as v}from"../../subscription-state.js";import{aadOf as O}from"../../secrets/env-rows.js";const r=_(a=>a==null?null:String(a),"str"),o=_(a=>Number(a??0),"num");async function M(){const a=await h(),t=await R(),d=y(),u={};for(const e of await t.prepare("SELECT provider_id, credential_sealed, key_id FROM model_api_keys WHERE person = ? ORDER BY provider_id").all(a)){const n=String(e.provider_id);u[n]=JSON.parse(c(e.credential_sealed,String(e.key_id),d,`model_api_keys|${a}|${n}`))}const w=await t.prepare("SELECT provider_id, account_id, family, rate_limited_until, last_rate_limited_at FROM model_account_cooldowns WHERE person = ? ORDER BY family").all(a),E=[];for(const e of await t.prepare("SELECT * FROM model_accounts WHERE person = ? ORDER BY provider_id, position").all(a)){const n=String(e.provider_id),l=String(e.id);if(e.refresh_sealed===null||e.access_sealed===null||e.key_id===null)throw new Error(`managed ${n} account ${l} has no credential record`);const g=String(e.key_id);E.push({provider:n,id:l,label:String(e.label),labelKey:String(e.label_key),position:o(e.position),lastAttemptAt:o(e.last_attempt_at),lastRateLimitedAt:o(e.last_rate_limited_at),rateLimitedUntil:o(e.rate_limited_until),...e.auth_failure==="invalid_grant"?{authFailure:"invalid_grant"}:{},refresh:c(e.refresh_sealed,g,d,v(a,n,l,"refresh")),access:c(e.access_sealed,g,d,v(a,n,l,"access")),expires:o(e.expires),...r(e.vendor_account_id)===null?{}:{accountId:r(e.vendor_account_id)},...r(e.client_id)===null?{}:{clientId:r(e.client_id)},cooldowns:w.filter(s=>s.provider_id===n&&s.account_id===l).map(s=>({family:String(s.family),until:o(s.rate_limited_until),at:o(s.last_rate_limited_at)}))})}const i=await t.prepare("SELECT default_fallback_strength, revert_when_available, openai_device_id FROM model_settings WHERE person = ?").get(a),S={accounts:E,...i===void 0||i.default_fallback_strength===null&&i.revert_when_available===null?{}:{rotation:{defaultFallbackStrength:r(i.default_fallback_strength),revertWhenAvailable:i.revert_when_available===null?null:o(i.revert_when_available)===1}},...i===void 0||r(i.openai_device_id)===null?{}:{openaiDeviceId:r(i.openai_device_id)}},m={};for(const e of await t.prepare("SELECT owner_id, name, value_sealed, key_id FROM env_vars WHERE person = ? AND owner_kind = 'profile' ORDER BY owner_id, name").all(a)){const n=String(e.owner_id),l=String(e.name);(m[n]??={})[l]=c(e.value_sealed,String(e.key_id),d,O({person:a,kind:"profile",id:n},l))}const p={settings:{},state:{},profileProjects:{},defaults:{}};for(const e of await t.prepare("SELECT owner_key, raw_json FROM scope_settings WHERE person = ?").all(a))p.settings[String(e.owner_key)]=JSON.parse(String(e.raw_json));for(const e of await t.prepare("SELECT owner_key, last_self_check, active_canvas FROM scope_bookkeeping WHERE person = ?").all(a))p.state[String(e.owner_key)]={last_self_check:r(e.last_self_check),activeCanvas:r(e.active_canvas)};for(const e of await t.prepare("SELECT pp.profile_id, pp.path FROM profile_projects pp JOIN profiles p ON p.id = pp.profile_id WHERE p.person = ? ORDER BY pp.profile_id, pp.position").all(a))(p.profileProjects[String(e.profile_id)]??=[]).push(String(e.path));for(const e of await t.prepare("SELECT directory, profile_id FROM profile_defaults WHERE person = ?").all(a))p.defaults[String(e.directory)]=String(e.profile_id);const f={marketplaces:[],plugins:[]};for(const e of await t.prepare("SELECT * FROM scope_marketplaces WHERE person = ? ORDER BY owner_key, name").all(a))f.marketplaces.push({owner:String(e.owner_key),name:String(e.name),url:r(e.url),ref:r(e.ref),installed_at:r(e.installed_at),last_updated:r(e.last_updated)});for(const e of await t.prepare("SELECT * FROM scope_plugins WHERE person = ? ORDER BY owner_key, name").all(a))f.plugins.push({owner:String(e.owner_key),name:String(e.name),enabled:o(e.enabled)===1,source_marketplace:r(e.source_marketplace),version:r(e.version),source_path:r(e.source_path),last_updated:r(e.last_updated),etag:r(e.etag),checked_at:r(e.checked_at)});return{auth:{apiKeys:u},credentials:S,env:{profiles:m},settings:p,plugins:f}}_(M,"readStores");export{M as readStores};
@@ -0,0 +1,19 @@
1
+ import type { CanvasDb } from '../../canvas/db.js';
2
+ import { type RewriteInput } from './rewrite.js';
3
+ /** A grant's or row's `sub` as the run sees it: the source's person is the run's, no other person is allowed. */
4
+ export declare function boundSub(sub: string, source: string, run: string, where: string): string;
5
+ /** Before anything is staged: the bundle's person has the shape of a person, and when it differs from the run's it is not already someone else in the target. */
6
+ export declare function checkSourcePerson(db: CanvasDb, target: string, source: string, run: string): Promise<void>;
7
+ /** After staging, before rewrite 1: every person the staged rows name is the bundle's or the run's. Drops the directory's own team facts
8
+ * (`crtr/group` relations). Returns rows dropped per table. */
9
+ export declare function checkStagedPersons(input: RewriteInput): Promise<Record<string, number>>;
10
+ /** After rewrite 2 and the drop: every reference from a staged row to a target row reaches a row the run's person owns or acts for (an app, a shipped
11
+ * document, the person's own object, an object with an `acts_for` fact for the person, or anything below those that is not directly under an app), and no profile or grant the
12
+ * rows name belongs to another person. */
13
+ export declare function checkStagedReferences(input: RewriteInput): Promise<void>;
14
+ /** After staging, before rewrite 1: every blob key a staged row names has the shape its row would produce and an owner (node) that is in this bundle.
15
+ * A key naming another person's blob is `person_mismatch`; a key of no known shape is `bundle_invalid`. Without this, the importer's own node would read, and
16
+ * compaction would delete, a blob of someone else's conversation. (Human requests are checked by `checkStagedRequestKeys`, after their dirs are rewritten to blob keys.) */
17
+ export declare function checkStagedBlobKeys(input: RewriteInput): Promise<void>;
18
+ /** After rewrite 4b: a human request's `request_dir`, `presentation_dir` and `file` that are blob keys (`requests/…`) are under the request's own `requests/<its id>`. */
19
+ export declare function checkStagedRequestKeys(input: RewriteInput): Promise<void>;
@@ -0,0 +1,15 @@
1
+ var g=Object.defineProperty;var u=(s,t)=>g(s,"name",{value:t,configurable:!0});import{parseBlobKey as L}from"../bundle/blob-keys.js";import{q as E}from"./catalog.js";import{objectIdColumns as R}from"./rewrite.js";import{ImportLaneError as m}from"./stage.js";const w=/^user:[A-Za-z0-9._-]+$/,S=u(s=>s.replace(/^user:/,""),"bare"),I=u((s,t)=>`${E(s)}.${E(t)}`,"sq"),$=u(async(s,t,...r)=>await s.prepareUncached(t).all(...r),"all");function _(s,t){return new m("bind","person_mismatch",s,t)}u(_,"mismatch");function D(s,t,r,a){if(s===t||s===r)return r;throw _(`${a} names ${s}, which is neither the bundle's person nor the importing person`,{where:a,value:s})}u(D,"boundSub");async function k(s,t,r,a){if(!w.test(r))throw _(`the bundle's person ${JSON.stringify(r)} is not a user:<id> principal`,{where:"source.json person",value:r});if(r===a)return;const i=u(e=>I(t,e),"T"),n=await s.prepareUncached(`SELECT 'identities' AS tbl FROM ${i("identities")} WHERE scheme IN ('user', 'local-user') AND external_id = ?
2
+ UNION ALL SELECT 'grants' FROM ${i("grants")} WHERE sub = ? UNION ALL SELECT 'profiles' FROM ${i("profiles")} WHERE person = ?
3
+ UNION ALL SELECT 'nodes' FROM ${i("nodes")} WHERE sub = ? LIMIT 1`).get(r,r,r,r);if(n!==void 0)throw _(`the bundle's person ${r} is already a person in the target (${n.tbl}), not the importing person ${a}`,{where:`${n.tbl}`,value:r})}u(k,"checkSourcePerson");const v=[["nodes","sub"],["crons","sub"],["grants","sub"],["profiles","person"],["charges","sub"],["limits","sub"],["audit_rows","sub"],["provider_approvals","sub"]],C=[["charges","payer"],["limits","payer"],["audit_rows","payer"],["audit_rows","grantee"],["charges","grantee"],["run_events","grantee"],["idempotency_keys","grantee"],["uploads","grantee"],["provider_approvals","grantee"],["profiles","grantee"],["crons","grantee"]];async function H(s){const{db:t,schema:r,sourcePerson:a,targetPerson:i}=s,n=u(p=>I(r,p),"S"),e=u((p,d)=>{throw _(`${p} names ${d.join(", ")}, who is neither the bundle's person ${a} nor the importing person ${i}`,{where:p,values:d})},"refuse");for(const[p,d]of v){const o=await $(t,`SELECT DISTINCT ${E(d)} AS v FROM ${n(p)} WHERE ${E(d)} IS NOT NULL AND ${E(d)} <> '' AND ${E(d)} NOT IN (?, ?) LIMIT 5`,a,i);o.length>0&&e(`${p}.${d}`,o.map(l=>l.v))}for(const[p,d]of C){const o=await $(t,`SELECT DISTINCT ${E(d)} AS v FROM ${n(p)} WHERE ${E(d)} LIKE 'user:%' AND ${E(d)} NOT IN (?, ?) LIMIT 5`,a,i);o.length>0&&e(`${p}.${d}`,o.map(l=>l.v))}const c=await $(t,`SELECT DISTINCT external_id AS v FROM ${n("identities")} WHERE scheme IN ('user', 'local-user') AND external_id NOT IN (?, ?) LIMIT 5`,a,i);c.length>0&&e("identities.external_id",c.map(p=>p.v));const f=await $(t,`SELECT DISTINCT adapter_json::jsonb #>> '{principal,sub}' AS v FROM ${n("daemon_actions")}
4
+ WHERE adapter_json::jsonb #>> '{principal,sub}' IS NOT NULL AND adapter_json::jsonb #>> '{principal,sub}' NOT IN (?, ?) LIMIT 5`,a,i);f.length>0&&e("daemon_actions principal.sub",f.map(p=>p.v));const h=await $(t,`SELECT DISTINCT subject_id AS v FROM ${n("relations")} WHERE relation = 'acts_for' AND subject_type = 'user' AND subject_id NOT IN (?, ?) LIMIT 5`,S(a),S(i));h.length>0&&e("relations acts_for subject",h.map(p=>p.v));const N=Number((await t.prepareUncached(`DELETE FROM ${n("relations")} WHERE resource_type = 'crtr/group'`).run()).changes);return N>0?{relations:N}:{}}u(H,"checkStagedPersons");async function W(s){const{db:t,schema:r,target:a,catalog:i,targetPerson:n}=s,e=u(o=>I(r,o),"S"),c=u(o=>I(a,o),"T"),f=S(n);await t.exec(`CREATE TABLE ${e("_refs")} (tbl text NOT NULL, col text NOT NULL, value text NOT NULL)`);const h=[];for(const[o,l]of R(i))if(!(!i.tables.has(o)||o==="relations"))for(const b of l)!(o==="nodes"&&b==="profile_id")&&!(o==="crons"&&b==="profile")&&h.push([o,b,""]);h.push(["relations","resource_id","WHERE resource_type = 'crtr/object'"]);for(const[o,l,b]of h)await t.prepareUncached(`INSERT INTO ${e("_refs")} SELECT ?, ?, ${E(l)}::text FROM ${e(o)} ${b===""?"WHERE":`${b} AND`} ${E(l)} IS NOT NULL
5
+ AND ${E(l)}::text NOT IN (SELECT id FROM ${e("objects")}) AND ${E(l)}::text IN (SELECT id FROM ${c("objects")})`).run(o,l);const N=await $(t,`WITH RECURSIVE chain(start, id) AS (
6
+ SELECT DISTINCT value, value FROM ${e("_refs")}
7
+ UNION SELECT c.start, o.owner FROM chain c JOIN ${c("objects")} o ON o.id = c.id JOIN ${c("objects")} up ON up.id = o.owner AND up.type <> 'app'),
8
+ ok(id) AS (
9
+ SELECT o.id FROM ${c("objects")} o WHERE o.type = 'app'
10
+ UNION SELECT d.object FROM ${c("documents")} d WHERE d.source_path IS NOT NULL
11
+ UNION SELECT i.object FROM ${c("identities")} i WHERE i.scheme IN ('user', 'local-user') AND i.external_id = ?
12
+ UNION SELECT r.resource_id FROM ${c("relations")} r WHERE r.resource_type = 'crtr/object' AND r.relation = 'acts_for' AND r.subject_type = 'user' AND r.subject_id = ?)
13
+ SELECT r.tbl, r.col, r.value FROM ${e("_refs")} r
14
+ WHERE NOT EXISTS (SELECT 1 FROM chain c JOIN ok ON ok.id = c.id WHERE c.start = r.value) LIMIT 10`,n,f);if(N.length>0)throw _(`the bundle refers to objects that are not ${n}'s: ${N.map(o=>`${o.tbl}.${o.col}=${o.value}`).join(", ")}`,{references:N});const p=[["nodes","profile_id"],["crons","profile"],["profiles","id"]];for(const[o,l]of p){const b=await $(t,`SELECT DISTINCT s.${E(l)} AS v FROM ${e(o)} s JOIN ${c("profiles")} p ON p.id = s.${E(l)} WHERE p.person <> ? LIMIT 5`,n);if(b.length>0)throw _(`${o}.${l} names profiles that belong to another person: ${b.map(T=>T.v).join(", ")}`,{where:`${o}.${l}`,values:b.map(T=>T.v)})}for(const[o,l]of[["nodes","grant_id"],["crons","grant_id"]]){const b=await $(t,`SELECT DISTINCT s.${E(l)} AS v FROM ${e(o)} s JOIN ${c("grants")} g ON g.id = s.${E(l)} WHERE g.sub <> ? LIMIT 5`,n);if(b.length>0)throw _(`${o}.${l} names grants that belong to another person: ${b.map(T=>T.v).join(", ")}`,{where:`${o}.${l}`,values:b.map(T=>T.v)})}const d=await $(t,`SELECT DISTINCT d.adapter_json::jsonb #>> '{principal,grant_id}' AS v FROM ${e("daemon_actions")} d
15
+ JOIN ${c("grants")} g ON g.id = d.adapter_json::jsonb #>> '{principal,grant_id}' WHERE g.sub <> ? LIMIT 5`,n);if(d.length>0)throw _(`a queued action names grants that belong to another person: ${d.map(o=>o.v).join(", ")}`,{where:"daemon_actions principal.grant_id",values:d.map(o=>o.v)})}u(W,"checkStagedReferences");const O=u((s,t)=>new m("bind","bundle_invalid",s,t),"bundleInvalid");async function U(s){const{db:t,schema:r}=s,a=u(e=>I(r,e),"S"),i=new Set((await $(t,`SELECT node_id AS v FROM ${a("nodes")}`)).map(e=>e.v)),n=u((e,c,f,h)=>{const N=L(c);if(N===null||N.kind!==f||h!==void 0&&c!==h)throw O(`${e} holds ${JSON.stringify(c)}, which is not a ${f} blob key${h===void 0?"":` of the shape ${h}`}`,{where:e,value:c});if(!i.has(N.owner))throw _(`${e} holds ${c}, which belongs to node ${N.owner}, who is not in the bundle`,{where:e,value:c})},"check");for(const e of await $(t,`SELECT conversation_id, seq::text AS seq, blob_key FROM ${a("conversation_parts")}`))if(n("conversation_parts.blob_key",e.blob_key,"conversation"),e.blob_key.split("/")[2]!==e.conversation_id)throw O(`conversation_parts.blob_key ${e.blob_key} is not under its conversation ${e.conversation_id}`,{where:"conversation_parts.blob_key",value:e.blob_key});for(const e of await $(t,`SELECT blob_key FROM ${a("upload_uses")} WHERE blob_key IS NOT NULL`))n("upload_uses.blob_key",e.blob_key,"attachment");for(const e of await $(t,`SELECT object, output_key FROM ${a("jobs")} WHERE output_key IS NOT NULL`)){const c=e.output_key.split("/")[1]??"";n("jobs.output_key",e.output_key,"job",`jobs/${c}/${e.object}/output`)}}u(U,"checkStagedBlobKeys");async function q(s){const{db:t,schema:r}=s;for(const a of await $(t,`SELECT request_id, request_dir, presentation_dir, file FROM ${I(r,"human_requests")}`))for(const i of["request_dir","presentation_dir","file"]){const n=a[i];if(n===null||!n.startsWith("requests/"))continue;const e=L(n.includes("/",9)?n:`${n}/x`);if(e===null||e.kind!=="request")throw O(`human_requests.${i} holds ${JSON.stringify(n)}, which is not a request blob key`,{where:`human_requests.${i}`,value:n});if(e.owner!==a.request_id)throw _(`human_requests.${i} of ${a.request_id} holds ${n}, which belongs to request ${e.owner}`,{where:`human_requests.${i}`,value:n})}}u(q,"checkStagedRequestKeys");export{D as boundSub,k as checkSourcePerson,U as checkStagedBlobKeys,H as checkStagedPersons,W as checkStagedReferences,q as checkStagedRequestKeys};
@@ -0,0 +1,10 @@
1
+ import type { BlobStore } from '../../blobs/index.js';
2
+ import type { CanvasDb } from '../../canvas/db.js';
3
+ export interface MovedBlobs {
4
+ moved: number;
5
+ left: number;
6
+ }
7
+ /** `created` collects the keys this call wrote, so the caller can delete them if the run fails before its copy commits. */
8
+ export declare function moveRunBlobs(db: CanvasDb, schema: string, blobs: BlobStore, runId: string, created: string[]): Promise<MovedBlobs>;
9
+ /** Delete the run's uploaded copies (after the run is done). */
10
+ export declare function deleteRunBlobs(blobs: BlobStore, runId: string): Promise<void>;
@@ -0,0 +1 @@
1
+ var S=Object.defineProperty;var r=(e,n)=>S(e,"name",{value:n,configurable:!0});import{parseBlobKey as q,runBlobPrefix as f}from"../bundle/blob-keys.js";import{q as m}from"./catalog.js";import{ImportLaneError as h}from"./stage.js";const E=8;async function k(e,n,o,g,y){const v={nodes:new Set((await e.prepareUncached(`SELECT node_id AS v FROM ${m(n)}.nodes`).all()).map(t=>t.v)),human_requests:new Set((await e.prepareUncached(`SELECT request_id AS v FROM ${m(n)}.human_requests`).all()).map(t=>t.v))},w=f(g),s=await o.list(w),i={moved:0,left:0};let p=0;const x=r(async()=>{for(let t=p++;t<s.length;t=p++){const l=s[t],a=l.slice(w.length),c=q(a);if(c===null||!v[c.ownerTable].has(c.owner)){i.left+=1;continue}const u=await o.get(l);if(u===null)throw new h("blobs","bundle_invalid",`${l} was listed and is gone`);const d=await o.get(a);if(d===null)await o.put(a,u),y.push(a);else if(!d.equals(u))throw new h("blobs","unique_conflict",`${a} already exists with other contents; the import does not replace it`,{key:a});i.moved+=1}},"worker");return await Promise.all(Array.from({length:Math.min(E,s.length)},x)),i}r(k,"moveRunBlobs");async function A(e,n){for(const o of await e.list(f(n)))await e.delete(o)}r(A,"deleteRunBlobs");export{A as deleteRunBlobs,k as moveRunBlobs};
@@ -0,0 +1,32 @@
1
+ import type { CanvasDb } from '../../canvas/db.js';
2
+ export interface ForeignKey {
3
+ table: string;
4
+ columns: string[];
5
+ parent: string;
6
+ parentColumns: string[];
7
+ }
8
+ export interface UniqueKey {
9
+ table: string;
10
+ name: string;
11
+ columns: string[];
12
+ predicate: string | null;
13
+ }
14
+ /** The columns that identify a row of `info`: its primary key, or every column in order for a table with none (`memory_reads`). */
15
+ export declare const rowKey: (info: TableInfo) => string[];
16
+ export interface TableInfo {
17
+ name: string;
18
+ columns: string[];
19
+ primaryKey: string[];
20
+ identity: string[];
21
+ }
22
+ export interface Catalog {
23
+ schema: string;
24
+ tables: Map<string, TableInfo>;
25
+ foreignKeys: ForeignKey[];
26
+ uniqueKeys: UniqueKey[];
27
+ }
28
+ export declare const q: (name: string) => string;
29
+ /** Read the catalog of `schema` for `tables`. */
30
+ export declare function readCatalog(db: CanvasDb, schema: string, tables: readonly string[]): Promise<Catalog>;
31
+ /** Parents before children. Cycles (none in the schema today) are broken by the table list's order. */
32
+ export declare function topologicalOrder(catalog: Catalog, tables: readonly string[]): string[];
@@ -0,0 +1,9 @@
1
+ var g=Object.defineProperty;var r=(a,n)=>g(a,"name",{value:n,configurable:!0});const x=r(a=>a.primaryKey.length>0?a.primaryKey:a.columns,"rowKey"),y=r(a=>`"${a.replace(/"/g,'""')}"`,"q");async function O(a,n,p){const i=[...p],d=await a.prepareUncached(`SELECT table_name, column_name, is_identity FROM information_schema.columns
2
+ WHERE table_schema = ? AND table_name = ANY(?::text[]) ORDER BY table_name, ordinal_position`).all(n,`{${i.map(e=>`"${e}"`).join(",")}}`),o=new Map;for(const e of i)o.set(e,{name:e,columns:[],primaryKey:[],identity:[]});for(const e of d){const s=o.get(e.table_name);s.columns.push(e.column_name),e.is_identity==="YES"&&s.identity.push(e.column_name)}const l=await a.prepareUncached(`SELECT c.conrelid::regclass::text AS tbl, c.contype, c.conname,
3
+ (SELECT array_agg(a.attname::text ORDER BY k.ord) FROM unnest(c.conkey) WITH ORDINALITY k(attnum, ord) JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = k.attnum) AS cols,
4
+ c.confrelid::regclass::text AS ptbl,
5
+ (SELECT array_agg(a.attname::text ORDER BY k.ord) FROM unnest(c.confkey) WITH ORDINALITY k(attnum, ord) JOIN pg_attribute a ON a.attrelid = c.confrelid AND a.attnum = k.attnum) AS pcols
6
+ FROM pg_constraint c WHERE c.connamespace = ?::regnamespace AND c.contype IN ('p', 'f')`).all(n),c=r(e=>e.replace(/^.*\./,"").replace(/"/g,""),"bare"),t=[];for(const e of l){const s=c(e.tbl);o.has(s)&&(e.contype==="p"?o.get(s).primaryKey=e.cols:o.has(c(e.ptbl))&&t.push({table:s,columns:e.cols,parent:c(e.ptbl),parentColumns:e.pcols}))}const u=(await a.prepareUncached(`SELECT t.relname AS tbl, i.relname AS name, pg_get_expr(x.indpred, x.indrelid) AS predicate, x.indexprs IS NOT NULL AS has_expr,
7
+ (SELECT array_agg(a.attname::text ORDER BY k.ord) FROM unnest(x.indkey::int2[]) WITH ORDINALITY k(attnum, ord) JOIN pg_attribute a ON a.attrelid = x.indrelid AND a.attnum = k.attnum) AS cols
8
+ FROM pg_index x JOIN pg_class t ON t.oid = x.indrelid JOIN pg_class i ON i.oid = x.indexrelid
9
+ WHERE t.relnamespace = ?::regnamespace AND x.indisunique AND NOT x.indisprimary`).all(n)).filter(e=>o.has(e.tbl)&&!e.has_expr).map(e=>({table:e.tbl,name:e.name,columns:e.cols,predicate:e.predicate}));return{schema:n,tables:o,foreignKeys:t,uniqueKeys:u}}r(O,"readCatalog");function _(a,n){const p=new Set(n),i=new Map(n.map(t=>[t,new Set]));for(const t of a.foreignKeys)p.has(t.table)&&p.has(t.parent)&&t.table!==t.parent&&i.get(t.table).add(t.parent);const d=[],o=new Set,l=new Set,c=r(t=>{if(!(o.has(t)||l.has(t))){l.add(t);for(const m of[...i.get(t)].sort())c(m);l.delete(t),o.add(t),d.push(t)}},"visit");for(const t of n)c(t);return d}r(_,"topologicalOrder");export{y as q,O as readCatalog,x as rowKey,_ as topologicalOrder};
@@ -0,0 +1,26 @@
1
+ import type { CanvasDb } from '../../canvas/db.js';
2
+ import { type PathMap } from '../bundle/layout.js';
3
+ import { type Catalog } from './catalog.js';
4
+ import type { IdMap } from './rewrite.js';
5
+ import type { SourceJson } from '../bundle/layout.js';
6
+ export interface CopyResult {
7
+ inserted: Record<string, number>;
8
+ mapped: Record<string, number>;
9
+ }
10
+ /** `import_manifest.inserted_keys`: a zstd-compressed JSON array of primary-key tuples (each value as text). */
11
+ export declare const encodeKeys: (keys: string[][]) => Buffer;
12
+ export declare const decodeKeys: (data: Buffer) => string[][];
13
+ export interface CopyInput {
14
+ db: CanvasDb;
15
+ runId: string;
16
+ schema: string;
17
+ target: string;
18
+ catalog: Catalog;
19
+ source: SourceJson;
20
+ person: string;
21
+ idMap: IdMap;
22
+ pathMap: PathMap;
23
+ }
24
+ export declare const copyOrder: (catalog: Catalog) => string[];
25
+ /** Must run inside `withCanvasWrite`. */
26
+ export declare function copyRows(input: CopyInput): Promise<CopyResult>;
@@ -0,0 +1,6 @@
1
+ var A=Object.defineProperty;var i=(a,t)=>A(a,"name",{value:t,configurable:!0});import{zstdCompressSync as D,zstdDecompressSync as b}from"node:zlib";import{BODIES_TABLE as U,COPIED_TABLES as k,KEY_MAPPED_TABLES as C}from"../bundle/layout.js";import{q as s,rowKey as M,topologicalOrder as F}from"./catalog.js";import{fillEnvironments as H}from"./prepare.js";const h=i((a,t)=>`${s(a)}.${s(t)}`,"sq"),j=i(a=>D(Buffer.from(JSON.stringify(a))),"encodeKeys"),P=i(a=>JSON.parse(b(a).toString("utf8")),"decodeKeys"),v=i(a=>F(a,k),"copyOrder");async function K(a){const{db:t,runId:O,schema:f,target:I,catalog:T,source:l}=a,c=i(e=>h(f,e),"S"),E=i(e=>h(I,e),"T"),m={inserted:{},mapped:{}},N=i(async(e,n)=>{m.inserted[e]=n.length,await t.prepare("INSERT INTO import_manifest (run_id, table_name, inserted_keys) VALUES (?, ?, ?)").run(O,e,j(n))},"manifest");await H(t,f,I,l,a.person);for(const[e,n]of Object.entries(C)){const o=T.tables.get(e).columns.filter(p=>p!==n).map(s).join(", "),d=[],_=await t.prepareUncached(`SELECT ${s(n)}::text AS key FROM ${c(e)} ORDER BY ${s(n)}::bigint`).all();for(const{key:p}of _){const S=await t.prepareUncached(`INSERT INTO ${E(e)} (${o}) SELECT ${o} FROM ${c(e)} WHERE ${s(n)}::text = ? RETURNING ${s(n)}::text AS key`).get(p);await t.prepare("INSERT INTO import_key_map (source_install_id, table_name, source_key, target_key) VALUES (?, ?, ?, ?)").run(l.installId,e,p,S.key),d.push([S.key])}await N(e,d)}await t.prepareUncached(`UPDATE ${c("review_comments")} c SET created_seq = m.target_key::bigint FROM import_key_map m
2
+ WHERE m.source_install_id = ? AND m.table_name = 'review_comment_events' AND m.source_key = c.created_seq::text`).run(l.installId);for(const e of v(T)){if(e in C)continue;const n=T.tables.get(e),u=Number((await t.prepareUncached(`SELECT count(*) AS n FROM ${c(e)}`).get()).n);if(u===0){m.inserted[e]=0;continue}const o=M(n),d=n.columns.filter(r=>!n.identity.includes(r)),_=n.identity.length>0?n.identity.map(r=>`s.${s(r)}`).join(", "):o.map(r=>`s.${s(r)}`).join(", ");let p=d.map(r=>`s.${s(r)}`),S="";if(e==="messages"){const r=`NOT EXISTS (SELECT 1 FROM ${E("messages")} t WHERE t.message_id = s.message_id)`,g=Number((await t.prepareUncached(`SELECT count(*) AS n FROM ${c("messages")} s WHERE ${r}`).get()).n);if(S=` WHERE ${r}`,g===0){m.inserted[e]=0,await N(e,[]);continue}const w=Number((await t.prepare("UPDATE message_sequence SET seq = seq + ? WHERE id = 1 RETURNING seq").get(g)).seq);p=d.map(y=>y==="seq"?`${w-g} + row_number() OVER (ORDER BY s.seq)`:`s.${s(y)}`)}const L=o.map(r=>`${s(r)}::text`).join(", "),$=n.primaryKey.length===0;$&&(S=` WHERE NOT EXISTS (SELECT 1 FROM ${E(e)} t WHERE ${o.map(r=>`t.${s(r)} IS NOT DISTINCT FROM s.${s(r)}`).join(" AND ")})`);const R=await t.prepareUncached(`INSERT INTO ${E(e)} (${d.map(s).join(", ")}) SELECT ${p.join(", ")} FROM ${c(e)} s${S}
3
+ ORDER BY ${_}${$?"":` ON CONFLICT (${o.map(s).join(", ")}) DO NOTHING`} RETURNING ${L}`).all();e===U?m.inserted[e]=R.length:await N(e,R.map(r=>Object.values(r))),m.mapped[e]=u-R.length}await t.prepareUncached(`DELETE FROM ${E("charge_totals")} t WHERE (t.month, t.payer) IN (SELECT DISTINCT month, payer FROM ${c("charges")})`).run(),await t.prepareUncached(`INSERT INTO ${E("charge_totals")} (month, payer, funding, sub, grantee, provider, usd, billed_usd)
4
+ SELECT month, payer, funding, COALESCE(sub, ''), grantee, COALESCE(provider, ''), COALESCE(SUM(usd), 0), COALESCE(SUM(billed_usd), 0)
5
+ FROM ${E("charges")} WHERE refused IS NULL AND kind <> 'take' AND (month, payer) IN (SELECT DISTINCT month, payer FROM ${c("charges")})
6
+ GROUP BY month, payer, funding, COALESCE(sub, ''), grantee, COALESCE(provider, '')`).run();for(const[e,n]of[["object",a.idMap.object],["grant",a.idMap.grant],["kind",a.idMap.kind]])for(const[u,o]of n)await t.prepare("INSERT INTO import_id_map (run_id, kind, source_value, target_value) VALUES (?, ?, ?, ?) ON CONFLICT DO NOTHING").run(O,e,u,o);for(const e of a.pathMap)await t.prepare("INSERT INTO import_id_map (run_id, kind, source_value, target_value) VALUES (?, 'path', ?, ?) ON CONFLICT DO NOTHING").run(O,e.from,e.to);return await t.prepare("UPDATE import_runs SET state = 'committed', committed_at = ?, step = 'copy' WHERE run_id = ?").run(new Date().toISOString(),O),m}i(K,"copyRows");export{v as copyOrder,K as copyRows,P as decodeKeys,j as encodeKeys};
@@ -0,0 +1,7 @@
1
+ import type { CanvasDb } from '../../canvas/db.js';
2
+ import { type PathMap, type PathPrefix, type SourceJson } from '../bundle/layout.js';
3
+ /** A directory two nodes share (a cwd outside both worktrees) maps once, to the first node's folder: both nodes' rows read the same target and each
4
+ * sandbox unpacks its own archive there. */
5
+ export declare function sandboxPathMap(prefixes: readonly PathPrefix[]): PathMap;
6
+ /** One `import_node_files` row per hosted running node the exporter archived. Runs in the copy transaction, after the nodes are copied. */
7
+ export declare function writeNodeFiles(tx: CanvasDb, schema: string, runId: string, source: SourceJson): Promise<number>;
@@ -0,0 +1 @@
1
+ var f=Object.defineProperty;var d=(o,t)=>f(o,"name",{value:t,configurable:!0});import{bundleKeys as l}from"../bundle/layout.js";import{q as c}from"./catalog.js";import{ImportLaneError as s}from"./stage.js";const p="/var/lib/crtr",u="/work/imported";function b(o){const t=o.map(e=>{if(!e.from.startsWith("/")||e.from.split("/").includes(".."))throw new s("stage","bundle_invalid",`path prefix ${e.from} of node ${e.node} is not an absolute path`);const n=e.app.replace(/^app:/,"");if(!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(n)||!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(e.node))throw new s("stage","bundle_invalid",`path prefix names app ${e.app} and node ${e.node}`);const r=`${p}/apps/${n}/nodes/${e.node}`,i=e.role==="context"?`${r}/context`:e.role==="worktree"?`${r}/worktree`:`${u}/${e.node}`;return{from:e.from,to:i}});return t.filter((e,n)=>t.findIndex(r=>r.from===e.from)===n)}d(b,"sandboxPathMap");async function _(o,t,e,n){if(n.kind!=="hosted")return 0;const r=[...new Set((n.pathPrefixes??[]).map(a=>a.node))];let i=0;for(const a of r){if(await o.prepareUncached(`SELECT 1 AS found FROM ${c(t)}.nodes WHERE node_id = ? AND status IN ('active', 'idle')`).get(a)===void 0)throw new s("copy","bundle_invalid",`an archive was uploaded for node ${a}, which is not a running node of the bundle`);i+=Number((await o.prepare("INSERT INTO import_node_files (node, blob_key) VALUES (?, ?) ON CONFLICT DO NOTHING").run(a,l.nodeFiles(e,a))).changes)}return i}d(_,"writeNodeFiles");export{b as sandboxPathMap,_ as writeNodeFiles};
@@ -0,0 +1,37 @@
1
+ import type { CanvasDb } from '../../canvas/db.js';
2
+ import { type PathMap, type SourceJson } from '../bundle/layout.js';
3
+ import { type Catalog } from './catalog.js';
4
+ /** A revision of a document the target already holds, to be added through repo sync's own arrival path. */
5
+ export interface ArrivingRevision {
6
+ document: string;
7
+ rev_id: string;
8
+ parents: string[];
9
+ body_hash: string;
10
+ rationale: string | null;
11
+ created_at: string;
12
+ }
13
+ /** Rewrite 4: for rows of running nodes of a hosted source, path columns go through the run's map. Ended nodes' rows stay as historical text. */
14
+ export declare function rewritePaths(db: CanvasDb, schema: string, map: PathMap): Promise<number>;
15
+ /** Rewrite 4b: a human request's files live in the blob store under `requests/<id>/` (the exporter uploaded the request dir there). `request_dir` becomes that
16
+ * prefix; a `file` or `presentation_dir` inside the old dir keeps its relative path, one outside it was uploaded to `source/<basename>`. A path that is not
17
+ * absolute is already a blob path and stays. */
18
+ export declare function rewriteRequestPaths(db: CanvasDb, schema: string): Promise<number>;
19
+ /** A profile whose name the person already uses for another profile is renamed `<name>-<install id>` (staging only; runs before the pre-check). */
20
+ export declare function renameClashingProfiles(db: CanvasDb, schema: string, target: string, installId: string, person: string): Promise<number>;
21
+ /** Fill: a staged profile with no environment gets the source's machine environment (a Mac) or the person's Blaxel environment (hosted, created if
22
+ * absent). Runs in the copy transaction: it writes target objects. */
23
+ export declare function fillEnvironments(tx: CanvasDb, schema: string, target: string, source: SourceJson, person: string): Promise<number>;
24
+ /** Staged edits that need nothing from the target but its key map: limits the directory or an operator wrote stay out, a hosted source's own
25
+ * machine environment is marked for the drop step, claims of the old runtime's lanes are cleared, and rows of the identity-keyed tables already in
26
+ * `import_key_map` are not staged again. Returns the rows removed per table. */
27
+ export declare function pruneStaged(db: CanvasDb, schema: string, catalog: Catalog, source: SourceJson): Promise<Record<string, number>>;
28
+ /** A staged document whose id the target already holds (repo sync keeps the id across runtimes) is not inserted: its revisions arrive through
29
+ * `insertImportedRevisions`. Removes its staged object, document, revision and parent rows and returns the revisions. Runs before the pre-check. */
30
+ export declare function takeRepoSyncedDocuments(db: CanvasDb, schema: string, target: string): Promise<ArrivingRevision[]>;
31
+ export interface Collision {
32
+ table: string;
33
+ key: string;
34
+ values: string[];
35
+ }
36
+ /** Pre-check (I9): every non-primary unique key and every text primary key the target already holds. Throws `unique_conflict` naming the first 10 values. */
37
+ export declare function precheck(db: CanvasDb, schema: string, target: string, catalog: Catalog, tables: readonly string[], skip: ReadonlySet<string>, seqLike: ReadonlyMap<string, string[]>): Promise<void>;
@@ -0,0 +1,10 @@
1
+ var y=Object.defineProperty;var d=(n,o)=>y(n,"name",{value:o,configurable:!0});import{createProfileBlaxelEnvironment as v}from"../../environments.js";import{KEY_MAPPED_TABLES as U,mapPath as S}from"../bundle/layout.js";import{ImportLaneError as O}from"./stage.js";import{q as l}from"./catalog.js";const $=d((n,o)=>`${l(n)}.${l(o)}`,"sq");async function F(n,o,c){if(c.length===0)return 0;const e=d(s=>$(o,s),"S"),t=`(SELECT node_id FROM ${e("nodes")} WHERE status IN ('active', 'idle'))`;let a=0;const E=d(async(s,r,i,u=!1)=>{const _=await n.prepareUncached(`SELECT ${l(r)}::text AS value FROM ${e(s)} WHERE ${i} AND ${l(r)} IS NOT NULL`).all();for(const{value:R}of _){const T=u?H(R,c):S(c,R);T!==R&&(await n.prepareUncached(`UPDATE ${e(s)} SET ${l(r)} = ? WHERE ${l(r)}::text = ? AND ${i}`).run(T,R),a+=1)}},"apply");return await E("nodes","cwd",`node_id IN ${t}`),await E("worktrees","path",`node_id IN ${t}`),await E("worktrees","repo_root",`node_id IN ${t}`),await E("crons","cwd",`anchor_node IN ${t}`),await E("provider_approvals","prepared_files","TRUE",!0),await E("upload_uses","path",`run_id IN ${t}`),a}d(F,"rewritePaths");async function g(n,o){const c=$(o,"human_requests"),e=d(a=>`CASE WHEN ${a} IS NULL OR ${a} NOT LIKE '/%' THEN ${a}
2
+ WHEN rtrim(${a}, '/') = rtrim(request_dir, '/') THEN 'requests/' || request_id
3
+ WHEN ${a} LIKE rtrim(request_dir, '/') || '/%' THEN 'requests/' || request_id || '/' || substr(${a}, length(rtrim(request_dir, '/')) + 2)
4
+ ELSE 'requests/' || request_id || '/source/' || regexp_replace(${a}, '^.*/', '') END`,"moved"),t=await n.prepareUncached(`UPDATE ${c} SET file = ${e("file")}, presentation_dir = ${e("presentation_dir")},
5
+ request_dir = CASE WHEN request_dir LIKE '/%' THEN 'requests/' || request_id ELSE request_dir END`).run();return Number(t.changes)}d(g,"rewriteRequestPaths");function H(n,o){const c=d(e=>typeof e=="string"?S(o,e):Array.isArray(e)?e.map(c):e!==null&&typeof e=="object"?Object.fromEntries(Object.entries(e).map(([t,a])=>[t,c(a)])):e,"walk");try{return JSON.stringify(c(JSON.parse(n)))}catch{return S(o,n)}}d(H,"rewriteJsonPaths");async function j(n,o,c,e,t){const a=d(r=>$(o,r),"S"),E=await n.prepareUncached(`SELECT id, name FROM ${a("profiles")} ORDER BY id`).all();let s=0;for(const r of E)await n.prepareUncached(`SELECT 1 AS found FROM ${$(c,"profiles")} WHERE person = ? AND name = ? AND id <> ?`).get(t,r.name,r.id)!==void 0&&(await n.prepareUncached(`UPDATE ${a("profiles")} SET name = ? WHERE id = ?`).run(`${r.name}-${e}`,r.id),s+=1);return s}d(j,"renameClashingProfiles");async function q(n,o,c,e,t){const a=d(r=>$(o,r),"S");let E=0;const s=await n.prepareUncached(`SELECT id, grantee FROM ${a("profiles")} WHERE environment_id IS NULL ORDER BY id`).all();for(const r of s){let i=e.kind==="machine"?e.environmentObject??null:null;if(e.kind==="hosted"&&(i=(await n.prepareUncached(`SELECT environment_id FROM ${$(c,"profiles")} WHERE id = ?`).get(r.id))?.environment_id??await v(n,{profileId:r.id,grantee:r.grantee??"app:terminal",person:t})),i===null)throw new O("fill","bundle_invalid",`profile ${r.id} has no environment and the source names none`);await n.prepareUncached(`UPDATE ${a("profiles")} SET environment_id = ? WHERE id = ?`).run(i,r.id),E+=1}return E}d(q,"fillEnvironments");async function x(n,o,c,e){const t=d(s=>$(o,s),"S"),a={},E=d(async(s,r,...i)=>{const u=Number((await n.prepareUncached(r).run(...i)).changes);u>0&&(a[s]=(a[s]??0)+u)},"del");await E("limits",`DELETE FROM ${t("limits")} WHERE source <> 'person'`),e.kind==="hosted"&&(await n.prepareUncached(`UPDATE ${t("profiles")} SET environment_id = NULL WHERE environment_id IN (SELECT object FROM ${t("environments")})`).run(),await n.prepareUncached(`UPDATE ${t("objects")} SET home_machine = 'import-hosted-source' WHERE id IN (SELECT object FROM ${t("environments")})`).run());for(const[s,r]of Object.entries(U))await E(s,`DELETE FROM ${t(s)} WHERE ${l(r)}::text IN (SELECT source_key FROM import_key_map WHERE source_install_id = ? AND table_name = ?)`,e.installId,s);for(const s of c.tables.values())s.columns.includes("claimed_by")&&s.columns.includes("lease_expires_at")&&await n.prepareUncached(`UPDATE ${t(s.name)} SET claimed_by = NULL, lease_expires_at = NULL WHERE claimed_by IS NOT NULL OR lease_expires_at IS NOT NULL`).run();return a}d(x,"pruneStaged");async function b(n,o,c){const e=d(E=>$(o,E),"S"),t=`(SELECT d.object FROM ${e("documents")} d WHERE EXISTS (SELECT 1 FROM ${$(c,"documents")} t WHERE t.object = d.object))`;await n.exec(`CREATE TABLE ${e("_arr_revisions")} AS SELECT r.* FROM ${e("revisions")} r WHERE r.document IN ${t};
6
+ CREATE TABLE ${e("_arr_parents")} AS SELECT p.* FROM ${e("revision_parents")} p WHERE p.child IN (SELECT rev_id FROM ${e("revisions")} WHERE document IN ${t})`);const a=await n.prepareUncached(`SELECT r.document, r.rev_id, r.body_hash, r.rationale, r.created_at,
7
+ COALESCE((SELECT array_agg(p.parent) FROM ${e("revision_parents")} p WHERE p.child = r.rev_id), ARRAY[]::text[]) AS parents
8
+ FROM ${e("revisions")} r WHERE r.document IN ${t} ORDER BY r.document, r.number`).all();return await n.prepareUncached(`DELETE FROM ${e("revision_parents")} WHERE child IN (SELECT rev_id FROM ${e("revisions")} WHERE document IN ${t})`).run(),await n.prepareUncached(`DELETE FROM ${e("revisions")} WHERE document IN ${t}`).run(),await n.prepareUncached(`DELETE FROM ${e("objects")} WHERE id IN ${t}`).run(),await n.prepareUncached(`DELETE FROM ${e("documents")} WHERE object IN ${t}`).run(),a.map(E=>({...E}))}d(b,"takeRepoSyncedDocuments");async function k(n,o,c,e,t,a,E){const s=d(i=>[...E.get(i)??[],...i==="profiles"?["environment_id"]:[],"claimed_by","lease_expires_at"],"ignored"),r=[];for(const i of t){const u=e.tables.get(i);if(u===void 0||a.has(i)||u.primaryKey.length===0)continue;const _=u.primaryKey,R=_.some(p=>u.identity.includes(p)),T=_.map(p=>`s.${l(p)} = t.${l(p)}`).join(" AND "),w=R?"TRUE":_.map(p=>`s.${l(p)} IS DISTINCT FROM t.${l(p)}`).join(" OR "),I=new Set([...u.identity,...E.get(i)??[]]);for(const p of e.uniqueKeys.filter(f=>f.table===i&&!f.columns.every(m=>I.has(m)))){const f=p.predicate===null?"":` WHERE ${p.predicate}`,m=p.columns.map(h=>`s.${l(h)} = t.${l(h)}`).join(" AND "),L=await n.prepareUncached(`SELECT ${p.columns.map(h=>`s.${l(h)}::text`).join(" || ',' || ")} AS value
9
+ FROM (SELECT * FROM ${$(o,i)}${f}) s JOIN (SELECT * FROM ${$(c,i)}${f}) t ON ${m} AND (${w}) LIMIT 10`).all();L.length>0&&r.push({table:i,key:p.name,values:L.map(h=>h.value)})}const N=u.columns.filter(p=>!u.identity.includes(p)&&!s(i).includes(p));if(N.length>0&&!R){const p=N.map(m=>`s.${l(m)} IS DISTINCT FROM t.${l(m)}`).join(" OR "),f=await n.prepareUncached(`SELECT ${_.map(m=>`s.${l(m)}::text`).join(" || ',' || ")} AS value
10
+ FROM ${$(o,i)} s JOIN ${$(c,i)} t ON ${T} WHERE ${p} LIMIT 10`).all();f.length>0&&r.push({table:i,key:"primary key payload",values:f.map(m=>m.value)})}}if(r.length>0){const i=r[0];throw new O("pre-check","unique_conflict",`${i.table} ${i.key} collides with the target: ${i.values.join(", ")}`,{collisions:r})}}d(k,"precheck");export{q as fillEnvironments,k as precheck,x as pruneStaged,j as renameClashingProfiles,F as rewritePaths,g as rewriteRequestPaths,b as takeRepoSyncedDocuments};
@@ -0,0 +1,39 @@
1
+ import { type CanvasDb } from '../../canvas/db.js';
2
+ import { ImportLaneError } from './stage.js';
3
+ import { type Catalog } from './catalog.js';
4
+ export interface RewriteInput {
5
+ db: CanvasDb;
6
+ /** The staging schema. */
7
+ schema: string;
8
+ /** The crouter schema the rows are copied into. */
9
+ target: string;
10
+ catalog: Catalog;
11
+ sourcePerson: string;
12
+ targetPerson: string;
13
+ /** The control plane's fleet runtime principal (`CRTR_RUNTIME`), the key of the directory's grants. */
14
+ fleetRuntime: string | null;
15
+ /** `source.shippedBodies`: shipped document id → hash of its body, which is a `bodies` row of the bundle (copied with the table). */
16
+ shippedBodies: Readonly<Record<string, string>>;
17
+ }
18
+ /** Source id → target id, per kind. Persisted into `import_id_map` in the copy transaction. */
19
+ export interface IdMap {
20
+ object: Map<string, string>;
21
+ grant: Map<string, string>;
22
+ kind: Map<string, string>;
23
+ }
24
+ /** Object-id columns that carry no foreign key (design-import, "Id columns"). */
25
+ export declare const OBJECT_ID_COLUMNS: ReadonlyArray<[string, string]>;
26
+ /** Every column that holds an object id, per table: the foreign keys onto `objects` in the target's catalog, then the ones without a foreign key. */
27
+ export declare function objectIdColumns(catalog: Catalog): Map<string, Set<string>>;
28
+ /** Rewrite 1: `substitutePerson` against the staging schema (every table it touches is staged, the ones the bundle does not dump empty). */
29
+ export declare function rewritePerson(input: RewriteInput): Promise<void>;
30
+ /** The target's grant for (fleet runtime, sub, grantee), the key the directory writes an importing person's grants under. The lane's wait for those
31
+ * grants (`run.ts`) and rewrite 2 both look a grant up here. A null fleet runtime (`CRTR_RUNTIME` unset) holds none. */
32
+ export declare function findFleetGrant(db: CanvasDb, target: string, fleetRuntime: string | null, sub: string, grantee: string): Promise<string | undefined>;
33
+ /** The `grant_missing` failure, for the grantees the target holds no grant for. */
34
+ export declare function grantMissing(grantees: readonly string[]): ImportLaneError;
35
+ /** Rewrite 2 (id map): build the map, drop the staged rows of mapped objects, rewrite every id column through it. */
36
+ export declare function rewriteIds(input: RewriteInput): Promise<IdMap>;
37
+ /** The drop step: federation stubs' objects and every staged row that points at a dropped row, then the assertion that no staged foreign
38
+ * key points at a row that is neither staged nor in the target. Returns rows dropped per table. */
39
+ export declare function dropStubRows(input: RewriteInput): Promise<Record<string, number>>;