@nylorun/runtime 0.15.2-beta → 0.17.0-beta

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 (291) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/README.md +68 -29
  3. package/dist/adapters/execution/restate.d.ts +9 -1
  4. package/dist/adapters/execution/restate.js +64 -5
  5. package/dist/adapters/sandbox/local.d.ts +10 -0
  6. package/dist/adapters/sandbox/local.js +195 -0
  7. package/dist/adapters/sandbox/virtual.js +36 -1
  8. package/dist/admin-openapi.json +341 -7
  9. package/dist/api/a2a/tasks.js +6 -1
  10. package/dist/api/ag-ui/endpoint.js +1 -2
  11. package/dist/api/ag-ui/history.js +6 -1
  12. package/dist/api/ag-ui/routes.d.ts +1 -1
  13. package/dist/api/ag-ui/routes.js +15 -13
  14. package/dist/api/components.d.ts +2667 -711
  15. package/dist/api/components.js +24 -11
  16. package/dist/api/gate/keys.d.ts +4 -3
  17. package/dist/api/gate/routes.d.ts +19 -3
  18. package/dist/api/gate/routes.js +162 -53
  19. package/dist/api/http/app.js +6 -0
  20. package/dist/api/http/define.d.ts +19 -12
  21. package/dist/api/http/define.js +13 -27
  22. package/dist/api/http/routes/access.d.ts +3 -19
  23. package/dist/api/http/routes/access.js +12 -172
  24. package/dist/api/http/routes/actions.js +0 -1
  25. package/dist/api/http/routes/artifacts.d.ts +11 -0
  26. package/dist/api/http/routes/artifacts.js +516 -0
  27. package/dist/api/http/routes/me.d.ts +13 -0
  28. package/dist/api/http/routes/me.js +45 -0
  29. package/dist/api/http/routes/sandboxes.d.ts +9 -0
  30. package/dist/api/http/routes/sandboxes.js +150 -0
  31. package/dist/api/http/routes/sessions.js +9 -13
  32. package/dist/api/http/routes/tenant.js +36 -2
  33. package/dist/api/http/routes/vaults.d.ts +12 -3
  34. package/dist/api/http/routes/vaults.js +122 -54
  35. package/dist/api/openapi.js +4 -10
  36. package/dist/artifacts/config.d.ts +23 -0
  37. package/dist/artifacts/config.js +27 -0
  38. package/dist/artifacts/export.d.ts +8 -0
  39. package/dist/artifacts/export.js +205 -0
  40. package/dist/artifacts/files.d.ts +36 -0
  41. package/dist/artifacts/files.js +45 -0
  42. package/dist/artifacts/folders.d.ts +60 -0
  43. package/dist/artifacts/folders.js +209 -0
  44. package/dist/artifacts/links.d.ts +28 -0
  45. package/dist/artifacts/links.js +92 -0
  46. package/dist/artifacts/media-types.d.ts +13 -0
  47. package/dist/artifacts/media-types.js +80 -0
  48. package/dist/artifacts/parts.d.ts +34 -0
  49. package/dist/artifacts/parts.js +38 -0
  50. package/dist/artifacts/service.d.ts +74 -0
  51. package/dist/artifacts/service.js +301 -0
  52. package/dist/artifacts/workspace.d.ts +60 -0
  53. package/dist/artifacts/workspace.js +57 -0
  54. package/dist/artifacts/zip.d.ts +10 -0
  55. package/dist/artifacts/zip.js +126 -0
  56. package/dist/blob/fs.d.ts +13 -0
  57. package/dist/blob/fs.js +194 -0
  58. package/dist/blob/index.d.ts +9 -0
  59. package/dist/blob/index.js +9 -0
  60. package/dist/blob/s3.d.ts +42 -0
  61. package/dist/blob/s3.js +366 -0
  62. package/dist/blob/types.d.ts +108 -0
  63. package/dist/blob/types.js +85 -0
  64. package/dist/core/sandbox-routes.d.ts +2 -2
  65. package/dist/core/sandbox-routes.js +4 -3
  66. package/dist/execution/memory.d.ts +11 -1
  67. package/dist/execution/memory.js +77 -3
  68. package/dist/execution/types.d.ts +54 -1
  69. package/dist/execution/types.js +31 -0
  70. package/dist/gates/contract.d.ts +14 -13
  71. package/dist/gates/contract.js +13 -12
  72. package/dist/gates/egress.d.ts +72 -0
  73. package/dist/gates/egress.js +319 -0
  74. package/dist/gates/handler.d.ts +3 -0
  75. package/dist/gates/handler.js +3 -2
  76. package/dist/gates/http-client.d.ts +3 -2
  77. package/dist/gates/http-client.js +17 -6
  78. package/dist/gates/in-process.d.ts +13 -1
  79. package/dist/gates/in-process.js +11 -0
  80. package/dist/gates/inflight.d.ts +23 -4
  81. package/dist/gates/inflight.js +18 -4
  82. package/dist/gates/mcp-handler.js +5 -1
  83. package/dist/gates/model-gate.d.ts +6 -2
  84. package/dist/gates/tenant-vaults.d.ts +18 -9
  85. package/dist/gates/tenant-vaults.js +17 -12
  86. package/dist/gates/tool-calls.d.ts +9 -5
  87. package/dist/gates/tool-calls.js +6 -6
  88. package/dist/gates/tool-client.d.ts +9 -2
  89. package/dist/gates/tool-client.js +56 -22
  90. package/dist/gates/tool-contract.d.ts +12 -7
  91. package/dist/gates/tool-contract.js +10 -4
  92. package/dist/gates/tool-gate.d.ts +1 -0
  93. package/dist/harness/calls.d.ts +52 -0
  94. package/dist/harness/calls.js +113 -0
  95. package/dist/harness/executors.d.ts +22 -0
  96. package/dist/harness/executors.js +96 -0
  97. package/dist/harness/main.d.ts +6 -0
  98. package/dist/harness/main.js +103 -0
  99. package/dist/harness/pod.d.ts +21 -0
  100. package/dist/harness/pod.js +119 -0
  101. package/dist/harness/run-tokens.d.ts +18 -0
  102. package/dist/harness/run-tokens.js +15 -0
  103. package/dist/harness/service.d.ts +46 -0
  104. package/dist/harness/service.js +179 -0
  105. package/dist/harness/ws-client.d.ts +43 -0
  106. package/dist/harness/ws-client.js +122 -0
  107. package/dist/harness/ws-port.d.ts +22 -0
  108. package/dist/harness/ws-port.js +78 -0
  109. package/dist/harness-api/in-process.d.ts +24 -0
  110. package/dist/harness-api/in-process.js +119 -0
  111. package/dist/harness-api/record.d.ts +44 -0
  112. package/dist/harness-api/record.js +250 -0
  113. package/dist/harness-api/renew.d.ts +37 -0
  114. package/dist/harness-api/renew.js +77 -0
  115. package/dist/harness-api/server.d.ts +104 -0
  116. package/dist/harness-api/server.js +408 -0
  117. package/dist/harness-api/start.d.ts +36 -0
  118. package/dist/harness-api/start.js +118 -0
  119. package/dist/harness-api/workspace.d.ts +53 -0
  120. package/dist/harness-api/workspace.js +201 -0
  121. package/dist/harness-api/ws-server.d.ts +34 -0
  122. package/dist/harness-api/ws-server.js +227 -0
  123. package/dist/host/admin-api.d.ts +12 -5
  124. package/dist/host/admin-api.js +72 -6
  125. package/dist/host/app.d.ts +0 -1
  126. package/dist/host/app.js +35 -24
  127. package/dist/host/config-for.d.ts +4 -0
  128. package/dist/host/config-for.js +2 -0
  129. package/dist/host/config.d.ts +0 -6
  130. package/dist/host/create-host.d.ts +3 -10
  131. package/dist/host/create-host.js +4 -1
  132. package/dist/host/gates.d.ts +27 -2
  133. package/dist/host/gates.js +52 -2
  134. package/dist/host/http.d.ts +4 -1
  135. package/dist/host/http.js +6 -1
  136. package/dist/host/main.js +167 -37
  137. package/dist/host/stack-config.d.ts +112 -9
  138. package/dist/host/stack-config.js +222 -20
  139. package/dist/index.d.ts +3 -0
  140. package/dist/index.js +2 -0
  141. package/dist/keys/client.js +9 -1
  142. package/dist/keys/keys.d.ts +12 -2
  143. package/dist/keys/keys.js +8 -0
  144. package/dist/mcp/connect.js +2 -1
  145. package/dist/mcp/pool.d.ts +1 -0
  146. package/dist/mcp/pool.js +1 -0
  147. package/dist/mcp/snapshot.d.ts +8 -1
  148. package/dist/mcp/snapshot.js +28 -0
  149. package/dist/media.d.ts +5 -24
  150. package/dist/media.js +5 -0
  151. package/dist/model/pi-model.d.ts +6 -2
  152. package/dist/model/pi-model.js +39 -22
  153. package/dist/node/index.d.ts +1 -1
  154. package/dist/node/index.js +1 -1
  155. package/dist/openapi.json +14084 -9426
  156. package/dist/record/index.d.ts +1 -0
  157. package/dist/record/index.js +1 -0
  158. package/dist/record/sandbox.d.ts +25 -0
  159. package/dist/record/sandbox.js +43 -0
  160. package/dist/sandbox/capability.d.ts +3 -0
  161. package/dist/sandbox/capability.js +10 -0
  162. package/dist/sandbox/egress-token.d.ts +26 -0
  163. package/dist/sandbox/egress-token.js +8 -0
  164. package/dist/sandbox/join.d.ts +37 -0
  165. package/dist/sandbox/join.js +165 -0
  166. package/dist/sandbox/manager.d.ts +99 -34
  167. package/dist/sandbox/manager.js +216 -84
  168. package/dist/sandbox/placement.d.ts +27 -0
  169. package/dist/sandbox/placement.js +44 -0
  170. package/dist/sandbox/pods/client.d.ts +90 -0
  171. package/dist/sandbox/pods/client.js +79 -0
  172. package/dist/sandbox/pods/lifecycle.d.ts +91 -0
  173. package/dist/sandbox/pods/lifecycle.js +178 -0
  174. package/dist/sandbox/pods/name.d.ts +1 -0
  175. package/dist/sandbox/pods/name.js +28 -0
  176. package/dist/sandbox/pods/network-gate.d.ts +25 -0
  177. package/dist/sandbox/pods/network-gate.js +71 -0
  178. package/dist/sandbox/pods/reconcile.d.ts +11 -0
  179. package/dist/sandbox/pods/reconcile.js +152 -0
  180. package/dist/sandbox/pods/spec.d.ts +45 -0
  181. package/dist/sandbox/pods/spec.js +62 -0
  182. package/dist/sandbox/records.d.ts +29 -0
  183. package/dist/sandbox/records.js +79 -0
  184. package/dist/sandbox/resolve.d.ts +5 -0
  185. package/dist/sandbox/resolve.js +3 -2
  186. package/dist/sandbox/session-sandbox.js +7 -5
  187. package/dist/sandbox/share.d.ts +11 -0
  188. package/dist/sandbox/share.js +10 -0
  189. package/dist/sandbox/tenant-config.d.ts +16 -1
  190. package/dist/sandbox/tenant-config.js +9 -0
  191. package/dist/sandbox/types.d.ts +29 -1
  192. package/dist/sandbox/types.js +13 -1
  193. package/dist/store/postgres/drizzle/0004_sandbox_resources.sql +20 -0
  194. package/dist/store/postgres/drizzle/0005_artifacts.sql +27 -0
  195. package/dist/store/postgres/drizzle/0006_folder_artifacts.sql +12 -0
  196. package/dist/store/postgres/drizzle/0007_sandbox_pods.sql +14 -0
  197. package/dist/store/postgres/drizzle/0008_installation_vaults.sql +2 -0
  198. package/dist/store/postgres/drizzle/0009_oauth_pending.sql +19 -0
  199. package/dist/store/postgres/drizzle/0010_oss_auth_removals.sql +3 -0
  200. package/dist/store/postgres/drizzle/meta/_journal.json +49 -0
  201. package/dist/store/postgres/record-writer.d.ts +6 -1
  202. package/dist/store/postgres/record-writer.js +20 -2
  203. package/dist/store/postgres/schema.d.ts +1168 -250
  204. package/dist/store/postgres/schema.js +174 -28
  205. package/dist/store/postgres/store.js +339 -103
  206. package/dist/store/postgres/tenant.d.ts +1 -1
  207. package/dist/store/postgres/tenant.js +2 -2
  208. package/dist/store/types.d.ts +163 -32
  209. package/dist/streams/control.d.ts +2 -5
  210. package/dist/streams/control.js +5 -8
  211. package/dist/streams/types.d.ts +8 -8
  212. package/dist/streams/types.js +1 -1
  213. package/dist/tenant/advance.js +218 -160
  214. package/dist/tenant/artifact-tool.d.ts +5 -0
  215. package/dist/tenant/artifact-tool.js +119 -0
  216. package/dist/tenant/auth.d.ts +13 -9
  217. package/dist/tenant/auth.js +25 -32
  218. package/dist/tenant/commands.js +31 -10
  219. package/dist/tenant/context.d.ts +83 -26
  220. package/dist/tenant/delivery-token.js +4 -4
  221. package/dist/tenant/effects.d.ts +24 -14
  222. package/dist/tenant/effects.js +10 -398
  223. package/dist/tenant/ephemeral.d.ts +14 -2
  224. package/dist/tenant/ephemeral.js +8 -1
  225. package/dist/tenant/history.d.ts +13 -23
  226. package/dist/tenant/history.js +14 -62
  227. package/dist/tenant/host-token.d.ts +92 -0
  228. package/dist/tenant/host-token.js +179 -0
  229. package/dist/tenant/identity-file.d.ts +75 -0
  230. package/dist/tenant/identity-file.js +202 -0
  231. package/dist/tenant/issuers.d.ts +48 -0
  232. package/dist/tenant/issuers.js +311 -0
  233. package/dist/tenant/jwt.js +3 -3
  234. package/dist/tenant/module.js +1 -0
  235. package/dist/tenant/operator-keys.d.ts +18 -0
  236. package/dist/tenant/operator-keys.js +55 -0
  237. package/dist/tenant/outbound.d.ts +30 -0
  238. package/dist/tenant/outbound.js +142 -10
  239. package/dist/tenant/paths.d.ts +2 -1
  240. package/dist/tenant/paths.js +3 -1
  241. package/dist/tenant/principals.d.ts +2 -11
  242. package/dist/tenant/principals.js +10 -30
  243. package/dist/tenant/reset.d.ts +5 -2
  244. package/dist/tenant/reset.js +22 -2
  245. package/dist/tenant/run-grants.d.ts +49 -0
  246. package/dist/tenant/run-grants.js +62 -0
  247. package/dist/tenant/run-token.d.ts +70 -0
  248. package/dist/tenant/run-token.js +138 -0
  249. package/dist/tenant/runtime.d.ts +42 -0
  250. package/dist/tenant/runtime.js +134 -33
  251. package/dist/tenant/sandboxes.d.ts +119 -0
  252. package/dist/tenant/sandboxes.js +551 -0
  253. package/dist/tenant/session-streams.d.ts +6 -10
  254. package/dist/tenant/session-streams.js +9 -27
  255. package/dist/tenant/session.d.ts +2 -13
  256. package/dist/tenant/session.js +4 -28
  257. package/dist/tenant/sessions.d.ts +6 -1
  258. package/dist/tenant/sessions.js +29 -2
  259. package/dist/tenant/signing-keys.d.ts +2 -1
  260. package/dist/tenant/signing-keys.js +2 -2
  261. package/dist/tenant/status.d.ts +8 -3
  262. package/dist/tenant/status.js +5 -0
  263. package/dist/tenant/streams.d.ts +8 -2
  264. package/dist/tenant/streams.js +25 -6
  265. package/dist/tenant/sweep.js +37 -4
  266. package/dist/tenant/types.d.ts +39 -1
  267. package/dist/tenant/worker.d.ts +11 -5
  268. package/dist/tenant/worker.js +12 -6
  269. package/dist/vault/crypto.d.ts +7 -0
  270. package/dist/vault/crypto.js +16 -0
  271. package/dist/vault/oauth.d.ts +55 -0
  272. package/dist/vault/oauth.js +186 -0
  273. package/dist/vault/service.d.ts +54 -7
  274. package/dist/vault/service.js +261 -17
  275. package/dist/vault/sources.d.ts +54 -0
  276. package/dist/vault/sources.js +185 -0
  277. package/dist/version.d.ts +1 -1
  278. package/dist/version.js +1 -1
  279. package/package.json +14 -10
  280. package/dist/adapters/media.d.ts +0 -24
  281. package/dist/adapters/media.js +0 -111
  282. package/dist/host/cors.d.ts +0 -27
  283. package/dist/host/cors.js +0 -64
  284. package/dist/tenant/access-policy.d.ts +0 -30
  285. package/dist/tenant/access-policy.js +0 -52
  286. package/dist/tenant/browser.d.ts +0 -15
  287. package/dist/tenant/browser.js +0 -39
  288. package/dist/tenant/subject-limits.d.ts +0 -16
  289. package/dist/tenant/subject-limits.js +0 -45
  290. package/dist/tenant/tokens.d.ts +0 -14
  291. package/dist/tenant/tokens.js +0 -180
@@ -32,6 +32,9 @@
32
32
  * head).
33
33
  * 2. A transaction that touches several sessions locks them in ascending id
34
34
  * order, with `lockSessions` (`./locking.ts`), before writing any of them.
35
+ * 3. A sandbox resource's row (`sandboxResource(id, { lock: true })`, which orders its
36
+ * lifecycle stream) is locked after every session row the transaction locks, never
37
+ * before one.
35
38
  *
36
39
  * Serialization failures and deadlocks are not retried here: with the order
37
40
  * above, READ COMMITTED transactions do not raise them. A `40P01` means a
@@ -47,17 +50,19 @@
47
50
  */
48
51
  import { AsyncLocalStorage } from "node:async_hooks";
49
52
  import { and, count, eq, getTableColumns, gte, inArray, isNotNull, isNull, lt, lte, ne, or, sql, } from "drizzle-orm";
50
- import { appendEvent } from "../../record/index.js";
53
+ import { appendEvent, appendSandboxEvent } from "../../record/index.js";
51
54
  import { OwnershipLostError } from "../ownership.js";
52
55
  import { database, driverError } from "./db.js";
53
56
  import { expectedSchemaVersion, readSchemaVersion } from "./migrate.js";
54
57
  import { createPostgresRecordReader } from "./record.js";
55
- import { postgresRecordWriter } from "./record-writer.js";
56
- import { actions, commands, definitions, effects, endpoints, links, modelBudgets, modelUsage, principals, publishableKeys, sandboxes, sessionEvents, sessionLogHeads, sessions, toolCrossings, signingKeys, subjectEpochs, subjectUsage, tenant, tenantSettings, vaultAudit, vaultCredentials, vaultIdempotency, vaults, } from "./schema.js";
58
+ import { postgresRecordWriter, postgresSandboxRecordWriter } from "./record-writer.js";
59
+ import { actions, artifacts, artifactVersions, artifactContent, commands, definitions, effects, endpoints, links, modelBudgets, modelUsage, principals, sandboxes, sandboxEvents, sandboxResources, sessionEvents, sessionLogHeads, sessions, toJson, toolCrossings, signingKeys, tenant, tenantSettings, vaultAudit, vaultCredentials, vaultIdempotency, vaults, oauthPending, } from "./schema.js";
57
60
  export function createPostgresSessionStore(options) {
58
61
  return new PostgresSessionStore(options);
59
62
  }
60
63
  const OPEN_SESSION = ["running", "runnable"];
64
+ /** Rows per statement when a folder version names many files. */
65
+ const CONTENT_BATCH = 1000;
61
66
  const DOCUMENTS = {
62
67
  definitions,
63
68
  sessions: sessions,
@@ -187,6 +192,57 @@ function ownership(row) {
187
192
  ownerExpiresAt: row.ownerExpiresAt?.toISOString() ?? null,
188
193
  };
189
194
  }
195
+ function sandboxResourceOf(row) {
196
+ return {
197
+ id: row.id,
198
+ kind: row.kind,
199
+ spec: row.spec,
200
+ labels: row.labels,
201
+ createdAt: row.createdAt,
202
+ updatedAt: row.updatedAt,
203
+ ...(row.k8sName !== null && row.desired !== null && row.observed !== null
204
+ ? {
205
+ pod: {
206
+ k8sName: row.k8sName,
207
+ volumeGen: row.volumeGen,
208
+ desired: row.desired,
209
+ observed: row.observed,
210
+ ...(row.podUid !== null ? { podUid: row.podUid } : {}),
211
+ hostEpoch: row.hostEpoch,
212
+ ...(row.joinTokenHash !== null ? { joinTokenHash: row.joinTokenHash } : {}),
213
+ rev: row.rev,
214
+ ...(row.lastActiveAt ? { lastActiveAt: row.lastActiveAt.toISOString() } : {}),
215
+ ...(row.expiresAt ? { expiresAt: row.expiresAt.toISOString() } : {}),
216
+ ...(row.startedAt ? { startedAt: row.startedAt.toISOString() } : {}),
217
+ ...(row.reason !== null ? { reason: row.reason } : {}),
218
+ ...(row.retiring !== null ? { retiring: row.retiring } : {}),
219
+ ...(row.volumeId !== null ? { volumeId: row.volumeId } : {}),
220
+ },
221
+ }
222
+ : {}),
223
+ };
224
+ }
225
+ /** A pod patch as column values: ISO times become dates, `null` clears. */
226
+ function podColumns(patch) {
227
+ const date = (value) => value === undefined ? undefined : value === null ? null : new Date(value);
228
+ const columns = {
229
+ k8sName: patch.k8sName,
230
+ volumeGen: patch.volumeGen,
231
+ desired: patch.desired,
232
+ observed: patch.observed,
233
+ podUid: patch.podUid,
234
+ hostEpoch: patch.hostEpoch,
235
+ joinTokenHash: patch.joinTokenHash,
236
+ rev: patch.rev,
237
+ lastActiveAt: date(patch.lastActiveAt),
238
+ expiresAt: date(patch.expiresAt),
239
+ startedAt: date(patch.startedAt),
240
+ reason: patch.reason,
241
+ retiring: patch.retiring,
242
+ volumeId: patch.volumeId,
243
+ };
244
+ return Object.fromEntries(Object.entries(columns).filter(([, value]) => value !== undefined));
245
+ }
190
246
  /** An endpoint row with its unset (null) columns left out, as `EndpointRow` has them. */
191
247
  function endpointRow(row) {
192
248
  return Object.fromEntries(Object.entries(row).filter(([, value]) => value !== null));
@@ -345,6 +401,18 @@ class PostgresTx {
345
401
  throw new OwnershipLostError(sessionId, epoch, session.epoch);
346
402
  return session;
347
403
  }
404
+ async runState(sessionId) {
405
+ this.check();
406
+ const [row] = await this.db
407
+ .select({
408
+ epoch: sessions.epoch,
409
+ status: sessions.status,
410
+ activeTurnId: sql `nylorun.doc(${sessions.body})->>'activeTurnId'`,
411
+ })
412
+ .from(sessions)
413
+ .where(eq(sessions.id, sessionId));
414
+ return row && { epoch: row.epoch, status: row.status ?? "", activeTurnId: row.activeTurnId ?? null };
415
+ }
348
416
  // --- typed queries -------------------------------------------------------
349
417
  async sessionsWithStatus(statuses) {
350
418
  this.check();
@@ -383,6 +451,91 @@ class PostgresTx {
383
451
  this.check();
384
452
  return this.bodies(sandboxes);
385
453
  }
454
+ // --- sandbox resources ---------------------------------------------------
455
+ async sandboxResource(id, options = {}) {
456
+ this.check();
457
+ const query = this.db.select().from(sandboxResources).where(eq(sandboxResources.id, id));
458
+ const [row] = options.lock ? await query.for("update") : await query;
459
+ return row && sandboxResourceOf(row);
460
+ }
461
+ async createSandboxResource(row, limit) {
462
+ this.check();
463
+ // Every create takes this lock, so two never both see room for one more.
464
+ await this.db.execute(sql `SELECT pg_advisory_xact_lock(hashtext('nylorun.sandbox_resources'))`);
465
+ const [found] = await this.db
466
+ .select({ id: sandboxResources.id })
467
+ .from(sandboxResources)
468
+ .where(eq(sandboxResources.id, row.id));
469
+ if (found)
470
+ return "exists";
471
+ const [counted] = await this.db.select({ n: count() }).from(sandboxResources);
472
+ if ((counted?.n ?? 0) >= limit)
473
+ return "limit";
474
+ const { pod, ...resource } = row;
475
+ await this.db.insert(sandboxResources).values({ ...resource, ...(pod ? podColumns(pod) : {}) });
476
+ return "created";
477
+ }
478
+ async updateSandboxSpec(id, spec, updatedAt) {
479
+ this.check();
480
+ await this.db.update(sandboxResources).set({ spec, updatedAt }).where(eq(sandboxResources.id, id));
481
+ }
482
+ async updateSandboxPod(id, patch, updatedAt) {
483
+ this.check();
484
+ await this.db
485
+ .update(sandboxResources)
486
+ .set({ ...podColumns(patch), updatedAt })
487
+ .where(eq(sandboxResources.id, id));
488
+ }
489
+ async updateSandboxLabels(id, labels, updatedAt) {
490
+ this.check();
491
+ await this.db
492
+ .update(sandboxResources)
493
+ .set({ labels, updatedAt })
494
+ .where(eq(sandboxResources.id, id));
495
+ }
496
+ async deleteSandboxResource(id) {
497
+ this.check();
498
+ await this.db.delete(sandboxResources).where(eq(sandboxResources.id, id));
499
+ }
500
+ async listSandboxResources(filter = {}) {
501
+ this.check();
502
+ const labels = filter.labels;
503
+ const rows = await this.db
504
+ .select()
505
+ .from(sandboxResources)
506
+ .where(labels === undefined || Object.keys(labels).length === 0
507
+ ? undefined
508
+ : sql `nylorun.doc(${sandboxResources.labels}) @> ${toJson(labels)}::jsonb`)
509
+ .orderBy(sandboxResources.id);
510
+ return rows.map(sandboxResourceOf);
511
+ }
512
+ async sessionsOnSandbox(sandboxId) {
513
+ this.check();
514
+ const rows = await this.sessions()
515
+ .where(eq(sessions.sandboxId, sandboxId))
516
+ .orderBy(sessions.id);
517
+ return rows.map((row) => storedSession(row));
518
+ }
519
+ async sandboxEvent(sandboxId, type, payload) {
520
+ this.check();
521
+ return appendSandboxEvent(postgresSandboxRecordWriter(this.db), {
522
+ tenantId: this.tenantId,
523
+ sandboxId,
524
+ time: this.now(),
525
+ type,
526
+ payload,
527
+ });
528
+ }
529
+ async sandboxEvents(sandboxId, options = {}) {
530
+ this.check();
531
+ const rows = await this.db
532
+ .select({ body: sandboxEvents.body })
533
+ .from(sandboxEvents)
534
+ .where(and(eq(sandboxEvents.sandboxId, sandboxId), options.fromSeq === undefined ? undefined : gte(sandboxEvents.seq, options.fromSeq)))
535
+ .orderBy(sandboxEvents.seq)
536
+ .limit(options.limit ?? 1000);
537
+ return rows.map((row) => row.body);
538
+ }
386
539
  async actionBodies(where) {
387
540
  const rows = await this.db
388
541
  .select({ body: actions.body })
@@ -607,6 +760,30 @@ class PostgresTx {
607
760
  .orderBy(principals.id);
608
761
  return rows.map((row) => row.tokenHash);
609
762
  }
763
+ async listPrincipals() {
764
+ this.check();
765
+ return await this.db.select().from(principals).orderBy(principals.id);
766
+ }
767
+ async putPrincipal(id, tokenHash, createdAt) {
768
+ this.check();
769
+ const [row] = await this.db
770
+ .insert(principals)
771
+ .values({ id, role: "application", tokenHash, idempotencyKey: null, createdAt })
772
+ .onConflictDoUpdate({
773
+ target: principals.id,
774
+ set: { tokenHash, createdAt, idempotencyKey: null },
775
+ })
776
+ .returning();
777
+ return row;
778
+ }
779
+ async deletePrincipal(id) {
780
+ this.check();
781
+ const rows = await this.db
782
+ .delete(principals)
783
+ .where(eq(principals.id, id))
784
+ .returning({ id: principals.id });
785
+ return rows.length > 0;
786
+ }
610
787
  // --- vault ---------------------------------------------------------------
611
788
  async insertVault(row) {
612
789
  this.check();
@@ -625,6 +802,14 @@ class PostgresTx {
625
802
  .where(and(eq(vaults.ownerUserId, ownerUserId), eq(vaults.scope, "user")))
626
803
  .orderBy(vaults.createdAt, vaults.id);
627
804
  }
805
+ async installationVaults() {
806
+ this.check();
807
+ return this.db
808
+ .select()
809
+ .from(vaults)
810
+ .where(eq(vaults.scope, "installation"))
811
+ .orderBy(vaults.createdAt, vaults.id);
812
+ }
628
813
  async updateVaultMetadata(id, metadataJson) {
629
814
  this.check();
630
815
  await this.db.update(vaults).set({ metadataJson }).where(eq(vaults.id, id));
@@ -705,7 +890,24 @@ class PostgresTx {
705
890
  this.check();
706
891
  await this.db.insert(vaultIdempotency).values(row);
707
892
  }
708
- // --- subject tokens ------------------------------------------------------
893
+ async insertOAuthPending(row) {
894
+ this.check();
895
+ await this.db.insert(oauthPending).values(row);
896
+ }
897
+ async takeOAuthPending(stateHash) {
898
+ this.check();
899
+ const [row] = await this.db
900
+ .delete(oauthPending)
901
+ .where(eq(oauthPending.stateHash, stateHash))
902
+ .returning();
903
+ return row;
904
+ }
905
+ async deleteExpiredOAuthPending(now) {
906
+ this.check();
907
+ const result = await this.db.delete(oauthPending).where(lt(oauthPending.expiresAt, now));
908
+ return result.count;
909
+ }
910
+ // --- signing keys --------------------------------------------------------
709
911
  async lockSigningKeys() {
710
912
  this.check();
711
913
  await this.db.execute(sql `SELECT pg_advisory_xact_lock(hashtext('nylorun.signing_keys'))`);
@@ -742,103 +944,6 @@ class PostgresTx {
742
944
  const [row] = await this.db.select({ n: count() }).from(signingKeys);
743
945
  return row.n;
744
946
  }
745
- async subjectEpoch(subject) {
746
- this.check();
747
- const [row] = await this.db
748
- .select({ epoch: subjectEpochs.epoch })
749
- .from(subjectEpochs)
750
- .where(eq(subjectEpochs.subject, subject));
751
- return row?.epoch ?? 0;
752
- }
753
- async subjectEpochs(subjects) {
754
- this.check();
755
- if (subjects.length === 0)
756
- return new Map();
757
- const rows = await this.db
758
- .select({ subject: subjectEpochs.subject, epoch: subjectEpochs.epoch })
759
- .from(subjectEpochs)
760
- .where(inArray(subjectEpochs.subject, subjects));
761
- return new Map(rows.map((row) => [row.subject, row.epoch]));
762
- }
763
- async bumpSubjectEpoch(subject, at) {
764
- this.check();
765
- const [row] = await this.db
766
- .insert(subjectEpochs)
767
- .values({ subject, epoch: 1, revokedAt: at })
768
- .onConflictDoUpdate({
769
- target: subjectEpochs.subject,
770
- set: { epoch: sql `${subjectEpochs.epoch} + 1`, revokedAt: excluded("revoked_at") },
771
- })
772
- .returning({ epoch: subjectEpochs.epoch });
773
- return row.epoch;
774
- }
775
- async lockSubjectUsage(initial) {
776
- this.check();
777
- await this.db.insert(subjectUsage).values(initial).onConflictDoNothing();
778
- const [row] = await this.db
779
- .select()
780
- .from(subjectUsage)
781
- .where(eq(subjectUsage.subject, initial.subject))
782
- .for("update");
783
- return row;
784
- }
785
- async putSubjectUsage(row) {
786
- this.check();
787
- await this.db
788
- .insert(subjectUsage)
789
- .values(row)
790
- .onConflictDoUpdate({
791
- target: subjectUsage.subject,
792
- set: { turnTokens: excluded("turn_tokens"), refilledAt: excluded("refilled_at") },
793
- });
794
- }
795
- async countOwnerSessions(ownerUserId, statuses) {
796
- this.check();
797
- const [row] = await this.db
798
- .select({ n: count() })
799
- .from(sessions)
800
- .where(and(eq(sessions.ownerUserId, ownerUserId), inArray(sessions.status, statuses)));
801
- return row.n;
802
- }
803
- // --- publishable keys ----------------------------------------------------
804
- async insertPublishableKey(row) {
805
- this.check();
806
- await this.db.insert(publishableKeys).values(row);
807
- }
808
- async publishableKeyByKey(key) {
809
- this.check();
810
- const [row] = await this.db
811
- .select()
812
- .from(publishableKeys)
813
- .where(eq(publishableKeys.key, key));
814
- return row;
815
- }
816
- async publishableKey(id) {
817
- this.check();
818
- const [row] = await this.db
819
- .select()
820
- .from(publishableKeys)
821
- .where(eq(publishableKeys.id, id));
822
- return row;
823
- }
824
- async publishableKeys() {
825
- this.check();
826
- return this.db
827
- .select()
828
- .from(publishableKeys)
829
- .orderBy(publishableKeys.createdAt, publishableKeys.id);
830
- }
831
- async updatePublishableKey(id, patch) {
832
- this.check();
833
- if (patch.originsJson === undefined && patch.revokedAt === undefined)
834
- return (await this.publishableKey(id)) !== undefined;
835
- const rows = await this.db
836
- .update(publishableKeys)
837
- .set(patch)
838
- .where(eq(publishableKeys.id, id))
839
- .returning({ id: publishableKeys.id });
840
- return rows.length === 1;
841
- }
842
947
  // --- model usage ---------------------------------------------------------
843
948
  async recordModelUsage(row) {
844
949
  this.check();
@@ -906,6 +1011,132 @@ class PostgresTx {
906
1011
  .returning({ key: toolCrossings.key });
907
1012
  return deleted.length;
908
1013
  }
1014
+ // --- file artifacts --------------------------------------------------------
1015
+ async artifact(id, options = {}) {
1016
+ this.check();
1017
+ const query = this.db.select().from(artifacts).where(eq(artifacts.id, id));
1018
+ const [row] = options.lock ? await query.for("update") : await query;
1019
+ return row;
1020
+ }
1021
+ async listArtifacts(filter = {}) {
1022
+ this.check();
1023
+ return this.db
1024
+ .select()
1025
+ .from(artifacts)
1026
+ .where(filter.sessionId === undefined ? undefined : eq(artifacts.sessionId, filter.sessionId))
1027
+ .orderBy(artifacts.createdAt, artifacts.id);
1028
+ }
1029
+ async insertArtifact(row, version) {
1030
+ this.check();
1031
+ await this.db.insert(artifacts).values(row);
1032
+ await this.db.insert(artifactVersions).values(version);
1033
+ }
1034
+ async insertArtifactVersion(version) {
1035
+ this.check();
1036
+ await this.db.insert(artifactVersions).values(version);
1037
+ await this.db
1038
+ .update(artifacts)
1039
+ .set({
1040
+ latestVersion: version.version,
1041
+ contentType: version.contentType,
1042
+ updatedAt: version.createdAt,
1043
+ })
1044
+ .where(eq(artifacts.id, version.artifactId));
1045
+ }
1046
+ async artifactVersion(artifactId, version) {
1047
+ this.check();
1048
+ const [row] = await this.db
1049
+ .select()
1050
+ .from(artifactVersions)
1051
+ .where(and(eq(artifactVersions.artifactId, artifactId), eq(artifactVersions.version, version)));
1052
+ return row;
1053
+ }
1054
+ async artifactVersions(artifactId) {
1055
+ this.check();
1056
+ return this.db
1057
+ .select()
1058
+ .from(artifactVersions)
1059
+ .where(eq(artifactVersions.artifactId, artifactId))
1060
+ .orderBy(artifactVersions.version);
1061
+ }
1062
+ async deleteArtifact(id) {
1063
+ this.check();
1064
+ const deleted = await this.db
1065
+ .delete(artifactVersions)
1066
+ .where(eq(artifactVersions.artifactId, id))
1067
+ .returning({ blobKey: artifactVersions.blobKey });
1068
+ await this.db.delete(artifacts).where(eq(artifacts.id, id));
1069
+ return deleted.map((row) => row.blobKey);
1070
+ }
1071
+ async lockArtifactQuota() {
1072
+ this.check();
1073
+ await this.db.execute(sql `SELECT pg_advisory_xact_lock(hashtext('nylorun.artifacts'))`);
1074
+ }
1075
+ async artifactBytes() {
1076
+ this.check();
1077
+ const [files] = await this.db
1078
+ .select({
1079
+ bytes: sql `coalesce(sum(${artifactVersions.size}), 0)::bigint`.mapWith(Number),
1080
+ })
1081
+ .from(artifactVersions)
1082
+ .innerJoin(artifacts, eq(artifacts.id, artifactVersions.artifactId))
1083
+ .where(eq(artifacts.kind, "file"));
1084
+ // A folder's files are stored once per distinct hash, however many versions name them.
1085
+ const distinct = this.db
1086
+ .select({ size: sql `max(${artifactContent.size})`.as("size") })
1087
+ .from(artifactContent)
1088
+ .groupBy(artifactContent.sha256)
1089
+ .as("distinct_content");
1090
+ const [content] = await this.db
1091
+ .select({ bytes: sql `coalesce(sum(${distinct.size}), 0)::bigint`.mapWith(Number) })
1092
+ .from(distinct);
1093
+ return files.bytes + content.bytes;
1094
+ }
1095
+ async insertArtifactContent(artifactId, version, files) {
1096
+ this.check();
1097
+ for (let at = 0; at < files.length; at += CONTENT_BATCH)
1098
+ await this.db
1099
+ .insert(artifactContent)
1100
+ .values(files
1101
+ .slice(at, at + CONTENT_BATCH)
1102
+ .map((file) => ({ artifactId, version, sha256: file.sha256, size: file.size })))
1103
+ .onConflictDoNothing();
1104
+ }
1105
+ async referencedArtifactContent(shas) {
1106
+ this.check();
1107
+ const found = new Set();
1108
+ for (let at = 0; at < shas.length; at += CONTENT_BATCH) {
1109
+ const rows = await this.db
1110
+ .selectDistinct({ sha256: artifactContent.sha256 })
1111
+ .from(artifactContent)
1112
+ .where(inArray(artifactContent.sha256, shas.slice(at, at + CONTENT_BATCH)));
1113
+ for (const row of rows)
1114
+ found.add(row.sha256);
1115
+ }
1116
+ return found;
1117
+ }
1118
+ async artifactContentShas(scope) {
1119
+ this.check();
1120
+ const rows = await this.db
1121
+ .selectDistinct({ sha256: artifactContent.sha256 })
1122
+ .from(artifactContent)
1123
+ .innerJoin(artifacts, eq(artifacts.id, artifactContent.artifactId))
1124
+ .where(scope === "all"
1125
+ ? undefined
1126
+ : scope === "sessions"
1127
+ ? isNotNull(artifacts.sessionId)
1128
+ : eq(artifacts.id, scope.artifactId));
1129
+ return rows.map((row) => row.sha256);
1130
+ }
1131
+ async artifactBlobKeys(scope) {
1132
+ this.check();
1133
+ const rows = await this.db
1134
+ .select({ blobKey: artifactVersions.blobKey })
1135
+ .from(artifactVersions)
1136
+ .innerJoin(artifacts, eq(artifacts.id, artifactVersions.artifactId))
1137
+ .where(scope === "sessions" ? isNotNull(artifacts.sessionId) : undefined);
1138
+ return rows.map((row) => row.blobKey);
1139
+ }
909
1140
  // --- settings ------------------------------------------------------------
910
1141
  async getSetting(key) {
911
1142
  this.check();
@@ -929,8 +1160,9 @@ class PostgresTx {
929
1160
  if (scope === "sessions" || scope === "all") {
930
1161
  for (const table of SESSION_TABLES)
931
1162
  await db.delete(table);
932
- await db.delete(subjectUsage);
933
1163
  await db.delete(toolCrossings);
1164
+ // A session's artifacts go with it (their versions by cascade).
1165
+ await db.delete(artifacts).where(isNotNull(artifacts.sessionId));
934
1166
  // The record goes with the sessions, and the Tenant moves to a new basin: the ids it
935
1167
  // frees start again in an empty one (Durable Streams §8.1).
936
1168
  await db.delete(sessionEvents);
@@ -940,13 +1172,17 @@ class PostgresTx {
940
1172
  basinGeneration: sql `${tenant.basinGeneration} + 1`,
941
1173
  });
942
1174
  }
943
- if (scope === "sandboxes" || scope === "all")
1175
+ if (scope === "sandboxes" || scope === "all") {
944
1176
  await db.delete(sandboxes);
1177
+ await db.delete(sandboxResources);
1178
+ await db.delete(sandboxEvents);
1179
+ }
945
1180
  if (scope === "all") {
946
1181
  await db.delete(definitions);
947
1182
  await db.delete(endpoints);
948
1183
  await db.delete(modelUsage);
949
1184
  await db.delete(modelBudgets);
1185
+ await db.delete(artifacts);
950
1186
  await db.delete(vaults).where(ne(vaults.scope, "host"));
951
1187
  }
952
1188
  }
@@ -16,7 +16,7 @@ export interface TenantCreation {
16
16
  /** Ignored when the Tenant exists. */
17
17
  name: string;
18
18
  /**
19
- * The principals the Tenant must have, given its id (derived keys depend on it). Created
19
+ * The principals the Tenant must have, given its id (Studio's derived key depends on it). Created
20
20
  * with the Tenant; on later opens, the missing ones are added.
21
21
  */
22
22
  principals?(tenantId: string): readonly InitialPrincipal[];
@@ -14,8 +14,8 @@
14
14
  * 3. When `nylorun.tenant` is empty, create the Tenant: its row (id, name, created time) and
15
15
  * its first principals. A database whose row is gone but that holds a Tenant's data
16
16
  * (principals, sessions, agents, vaults, keys) is `envelope-invalid`, never given a new
17
- * Tenant. Principals given later that the Tenant does not have yet are added (a derived
18
- * principal configured after the first start); existing ones are kept.
17
+ * Tenant. Principals given later that the Tenant does not have yet are added (the
18
+ * ephemeral Runtime's application key); existing ones are kept, as ordinary keys.
19
19
  * 4. Read the envelope back (`envelope-invalid` when it does not parse), and open the store.
20
20
  *
21
21
  * A lost connection or an unavailable server says nothing about the Tenant: it is thrown as