@nylorun/runtime 0.16.0-beta → 0.19.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 (256) hide show
  1. package/CHANGELOG.md +207 -0
  2. package/README.md +105 -63
  3. package/dist/adapters/execution/restate.d.ts +1 -3
  4. package/dist/adapters/execution/restate.js +4 -31
  5. package/dist/adapters/replication/pgoutput.js +1 -1
  6. package/dist/adapters/sandbox/local.js +1 -1
  7. package/dist/adapters/sandbox/virtual.js +1 -1
  8. package/dist/api/ag-ui/endpoint.js +1 -2
  9. package/dist/api/ag-ui/routes.d.ts +1 -1
  10. package/dist/api/ag-ui/routes.js +11 -13
  11. package/dist/api/ag-ui/translate.d.ts +1 -3
  12. package/dist/api/ag-ui/translate.js +2 -17
  13. package/dist/api/components.d.ts +4783 -2588
  14. package/dist/api/components.js +19 -21
  15. package/dist/api/gate/routes.d.ts +7 -8
  16. package/dist/api/gate/routes.js +58 -52
  17. package/dist/api/http/app.d.ts +6 -0
  18. package/dist/api/http/app.js +20 -14
  19. package/dist/api/http/define.d.ts +22 -13
  20. package/dist/api/http/define.js +43 -39
  21. package/dist/api/http/routes/access.d.ts +3 -19
  22. package/dist/api/http/routes/access.js +25 -184
  23. package/dist/api/http/routes/artifacts.js +3 -7
  24. package/dist/api/http/routes/files.d.ts +8 -0
  25. package/dist/api/http/routes/files.js +77 -0
  26. package/dist/api/http/routes/keys.d.ts +10 -0
  27. package/dist/api/http/routes/keys.js +64 -0
  28. package/dist/api/http/routes/me.d.ts +13 -0
  29. package/dist/api/http/routes/me.js +48 -0
  30. package/dist/api/http/routes/reads.d.ts +66 -0
  31. package/dist/api/http/routes/reads.js +127 -0
  32. package/dist/api/http/routes/sandboxes.d.ts +2 -2
  33. package/dist/api/http/routes/sandboxes.js +8 -7
  34. package/dist/api/http/routes/sessions.js +31 -20
  35. package/dist/api/http/routes/tenant.js +15 -18
  36. package/dist/api/http/routes/vaults.d.ts +13 -3
  37. package/dist/api/http/routes/vaults.js +136 -64
  38. package/dist/api/openapi.d.ts +10 -11
  39. package/dist/api/openapi.js +292 -68
  40. package/dist/artifacts/links.js +6 -6
  41. package/dist/artifacts/service.js +1 -1
  42. package/dist/core/flow-host.d.ts +44 -23
  43. package/dist/core/flow-host.js +88 -106
  44. package/dist/core/provider.d.ts +4 -1
  45. package/dist/core/provider.js +6 -1
  46. package/dist/core/sandbox-routes.d.ts +0 -9
  47. package/dist/core/sandbox-routes.js +2 -21
  48. package/dist/execution/memory.d.ts +4 -5
  49. package/dist/execution/memory.js +11 -25
  50. package/dist/execution/types.d.ts +3 -33
  51. package/dist/execution/types.js +2 -12
  52. package/dist/gates/http-tool.d.ts +86 -0
  53. package/dist/gates/http-tool.js +197 -0
  54. package/dist/gates/mcp-handler.d.ts +3 -0
  55. package/dist/gates/mcp-handler.js +6 -4
  56. package/dist/gates/meter.d.ts +2 -2
  57. package/dist/gates/meter.js +6 -0
  58. package/dist/gates/tenant-vaults.d.ts +19 -14
  59. package/dist/gates/tenant-vaults.js +17 -12
  60. package/dist/gates/tool-calls.d.ts +7 -15
  61. package/dist/gates/tool-calls.js +4 -4
  62. package/dist/gates/tool-client.d.ts +1 -1
  63. package/dist/gates/tool-client.js +23 -47
  64. package/dist/gates/tool-contract.d.ts +27 -39
  65. package/dist/gates/tool-contract.js +30 -21
  66. package/dist/gates/tool-gate.d.ts +25 -24
  67. package/dist/gates/tool-gate.js +11 -7
  68. package/dist/harness/calls.d.ts +10 -4
  69. package/dist/harness/calls.js +15 -6
  70. package/dist/harness/executors.d.ts +1 -1
  71. package/dist/harness/executors.js +17 -12
  72. package/dist/harness/main.d.ts +2 -2
  73. package/dist/harness/main.js +5 -13
  74. package/dist/harness/service.d.ts +4 -5
  75. package/dist/harness/service.js +9 -2
  76. package/dist/harness-api/in-process.js +2 -2
  77. package/dist/harness-api/record.d.ts +6 -3
  78. package/dist/harness-api/record.js +65 -72
  79. package/dist/harness-api/server.d.ts +1 -9
  80. package/dist/harness-api/server.js +19 -17
  81. package/dist/harness-api/start.js +1 -5
  82. package/dist/harness-api/workspace.js +2 -3
  83. package/dist/harness-api/ws-server.js +1 -1
  84. package/dist/host/app.d.ts +2 -10
  85. package/dist/host/app.js +24 -44
  86. package/dist/host/config-for.d.ts +5 -5
  87. package/dist/host/config-for.js +2 -3
  88. package/dist/host/config.d.ts +0 -11
  89. package/dist/host/create-host.d.ts +3 -40
  90. package/dist/host/create-host.js +18 -91
  91. package/dist/host/environment.d.ts +0 -3
  92. package/dist/host/environment.js +0 -9
  93. package/dist/host/gates.d.ts +4 -1
  94. package/dist/host/gates.js +9 -3
  95. package/dist/host/http.d.ts +1 -2
  96. package/dist/host/http.js +1 -7
  97. package/dist/host/index.d.ts +2 -2
  98. package/dist/host/index.js +2 -2
  99. package/dist/host/main.js +55 -40
  100. package/dist/host/operate.d.ts +29 -0
  101. package/dist/host/operate.js +223 -0
  102. package/dist/host/stack-config.d.ts +23 -20
  103. package/dist/host/stack-config.js +28 -44
  104. package/dist/index.d.ts +4 -1
  105. package/dist/index.js +3 -1
  106. package/dist/infra/workers.d.ts +1 -1
  107. package/dist/keys/client.js +9 -1
  108. package/dist/keys/keys.d.ts +12 -2
  109. package/dist/keys/keys.js +10 -2
  110. package/dist/management-openapi.json +6491 -0
  111. package/dist/mcp/connect.d.ts +8 -4
  112. package/dist/mcp/connect.js +8 -27
  113. package/dist/mcp/pool.d.ts +6 -9
  114. package/dist/mcp/pool.js +8 -18
  115. package/dist/mcp/snapshot.d.ts +8 -2
  116. package/dist/mcp/snapshot.js +29 -2
  117. package/dist/media.d.ts +1 -1
  118. package/dist/media.js +1 -1
  119. package/dist/model/pi-model.js +3 -0
  120. package/dist/openapi.json +6228 -15615
  121. package/dist/reads/access.d.ts +4 -0
  122. package/dist/reads/access.js +12 -0
  123. package/dist/reads/cursor.d.ts +11 -0
  124. package/dist/reads/cursor.js +80 -0
  125. package/dist/reads/types.d.ts +25 -0
  126. package/dist/reads/types.js +1 -0
  127. package/dist/sandbox/manager.d.ts +16 -0
  128. package/dist/sandbox/manager.js +40 -0
  129. package/dist/sandbox/session-sandbox.js +13 -3
  130. package/dist/sandbox/skills.d.ts +13 -0
  131. package/dist/sandbox/skills.js +46 -0
  132. package/dist/sandbox/types.d.ts +2 -2
  133. package/dist/store/ownership.d.ts +9 -0
  134. package/dist/store/ownership.js +13 -0
  135. package/dist/store/postgres/connect.d.ts +2 -0
  136. package/dist/store/postgres/connect.js +29 -0
  137. package/dist/store/postgres/drizzle/0008_installation_vaults.sql +2 -0
  138. package/dist/store/postgres/drizzle/0009_oauth_pending.sql +19 -0
  139. package/dist/store/postgres/drizzle/0010_oss_auth_removals.sql +3 -0
  140. package/dist/store/postgres/drizzle/0011_key_roles.sql +3 -0
  141. package/dist/store/postgres/drizzle/0012_session_reads.sql +8 -0
  142. package/dist/store/postgres/drizzle/0013_definition_files.sql +16 -0
  143. package/dist/store/postgres/drizzle/0014_action_endpoints_removed.sql +2 -0
  144. package/dist/store/postgres/drizzle/meta/_journal.json +49 -0
  145. package/dist/store/postgres/reads.d.ts +3 -0
  146. package/dist/store/postgres/reads.js +272 -0
  147. package/dist/store/postgres/schema.d.ts +423 -594
  148. package/dist/store/postgres/schema.js +84 -101
  149. package/dist/store/postgres/store.js +96 -248
  150. package/dist/store/postgres/tenant.d.ts +34 -2
  151. package/dist/store/postgres/tenant.js +16 -32
  152. package/dist/store/types.d.ts +44 -119
  153. package/dist/store/types.js +0 -1
  154. package/dist/streams/control.d.ts +0 -7
  155. package/dist/streams/control.js +0 -13
  156. package/dist/streams/types.d.ts +2 -20
  157. package/dist/streams/types.js +1 -1
  158. package/dist/tenant/advance.d.ts +3 -3
  159. package/dist/tenant/advance.js +10 -7
  160. package/dist/tenant/auth.d.ts +24 -15
  161. package/dist/tenant/auth.js +44 -55
  162. package/dist/tenant/cause.d.ts +3 -3
  163. package/dist/tenant/cause.js +1 -1
  164. package/dist/tenant/commands.d.ts +2 -18
  165. package/dist/tenant/commands.js +38 -179
  166. package/dist/tenant/context.d.ts +33 -45
  167. package/dist/tenant/definition-files.d.ts +24 -0
  168. package/dist/tenant/definition-files.js +103 -0
  169. package/dist/tenant/effects.d.ts +20 -18
  170. package/dist/tenant/effects.js +59 -133
  171. package/dist/tenant/ephemeral.d.ts +21 -8
  172. package/dist/tenant/ephemeral.js +11 -12
  173. package/dist/tenant/host-token.js +5 -5
  174. package/dist/tenant/identity-file.d.ts +75 -0
  175. package/dist/tenant/identity-file.js +202 -0
  176. package/dist/tenant/issuers.d.ts +48 -0
  177. package/dist/tenant/issuers.js +311 -0
  178. package/dist/tenant/jwt.js +3 -3
  179. package/dist/tenant/module.d.ts +1 -4
  180. package/dist/tenant/module.js +3 -12
  181. package/dist/tenant/operator-keys.d.ts +44 -0
  182. package/dist/tenant/operator-keys.js +87 -0
  183. package/dist/tenant/outbound.d.ts +40 -1
  184. package/dist/tenant/outbound.js +167 -18
  185. package/dist/tenant/paths.d.ts +1 -1
  186. package/dist/tenant/paths.js +1 -3
  187. package/dist/tenant/principals.d.ts +13 -13
  188. package/dist/tenant/principals.js +35 -36
  189. package/dist/tenant/run-token.js +6 -6
  190. package/dist/tenant/runtime.d.ts +5 -3
  191. package/dist/tenant/runtime.js +29 -15
  192. package/dist/tenant/sandboxes.d.ts +9 -6
  193. package/dist/tenant/sandboxes.js +16 -5
  194. package/dist/tenant/scheduler.d.ts +1 -5
  195. package/dist/tenant/scheduler.js +2 -5
  196. package/dist/tenant/session-streams.d.ts +8 -11
  197. package/dist/tenant/session-streams.js +48 -32
  198. package/dist/tenant/session.d.ts +8 -41
  199. package/dist/tenant/session.js +0 -37
  200. package/dist/tenant/sessions.d.ts +2 -2
  201. package/dist/tenant/sessions.js +17 -9
  202. package/dist/tenant/signing-keys.d.ts +3 -2
  203. package/dist/tenant/signing-keys.js +5 -5
  204. package/dist/tenant/skill-tool.d.ts +20 -0
  205. package/dist/tenant/skill-tool.js +72 -0
  206. package/dist/tenant/slim.d.ts +1 -1
  207. package/dist/tenant/slim.js +1 -1
  208. package/dist/tenant/status.js +7 -12
  209. package/dist/tenant/store-pg.d.ts +15 -0
  210. package/dist/tenant/store-pg.js +7 -3
  211. package/dist/tenant/streams.d.ts +3 -11
  212. package/dist/tenant/streams.js +2 -29
  213. package/dist/tenant/sweep.js +0 -5
  214. package/dist/tenant/transcript.d.ts +2 -2
  215. package/dist/tenant/transcript.js +2 -2
  216. package/dist/tenant/types.d.ts +33 -19
  217. package/dist/tenant/worker.d.ts +1 -3
  218. package/dist/tenant/worker.js +3 -8
  219. package/dist/vault/crypto.d.ts +7 -0
  220. package/dist/vault/crypto.js +16 -0
  221. package/dist/vault/oauth.d.ts +55 -0
  222. package/dist/vault/oauth.js +186 -0
  223. package/dist/vault/service.d.ts +54 -7
  224. package/dist/vault/service.js +261 -17
  225. package/dist/vault/sources.d.ts +60 -0
  226. package/dist/vault/sources.js +196 -0
  227. package/dist/version.d.ts +1 -1
  228. package/dist/version.js +1 -1
  229. package/package.json +5 -4
  230. package/dist/admin-openapi.json +0 -604
  231. package/dist/api/http/routes/actions.d.ts +0 -8
  232. package/dist/api/http/routes/actions.js +0 -76
  233. package/dist/api/http/routes/endpoints.d.ts +0 -7
  234. package/dist/api/http/routes/endpoints.js +0 -100
  235. package/dist/host/admin-api.d.ts +0 -23
  236. package/dist/host/admin-api.js +0 -74
  237. package/dist/host/cors.d.ts +0 -27
  238. package/dist/host/cors.js +0 -64
  239. package/dist/mcp/stdio.d.ts +0 -17
  240. package/dist/mcp/stdio.js +0 -82
  241. package/dist/tenant/access-policy.d.ts +0 -30
  242. package/dist/tenant/access-policy.js +0 -52
  243. package/dist/tenant/actions.d.ts +0 -14
  244. package/dist/tenant/actions.js +0 -29
  245. package/dist/tenant/browser.d.ts +0 -15
  246. package/dist/tenant/browser.js +0 -39
  247. package/dist/tenant/delivery-token.d.ts +0 -41
  248. package/dist/tenant/delivery-token.js +0 -104
  249. package/dist/tenant/delivery.d.ts +0 -62
  250. package/dist/tenant/delivery.js +0 -481
  251. package/dist/tenant/endpoints.d.ts +0 -67
  252. package/dist/tenant/endpoints.js +0 -90
  253. package/dist/tenant/subject-limits.d.ts +0 -16
  254. package/dist/tenant/subject-limits.js +0 -45
  255. package/dist/tenant/tokens.d.ts +0 -14
  256. package/dist/tenant/tokens.js +0 -187
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * Plain reads and writes use Drizzle's query builder: `.set(patch)` and `and(...)` skip
10
10
  * `undefined`, rows come back in the camelCase of `schema.ts`. Statements whose logic is SQL
11
- * stay `sql` fragments inside it: the endpoint upsert's `CASE` arms, the log-head upsert,
11
+ * stay `sql` fragments inside it: the log-head upsert,
12
12
  * `orphanedSessions`' ordering, `counts()`, advisory locks and array updates.
13
13
  *
14
14
  * ## Transactions
@@ -28,7 +28,7 @@
28
28
  * ends. To stay deadlock-free:
29
29
  *
30
30
  * 1. A session-scoped transaction locks its session row before writing any
31
- * other row of that session (effects, actions, its record rows and log
31
+ * other row of that session (effects, its record rows and log
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.
@@ -51,12 +51,12 @@
51
51
  import { AsyncLocalStorage } from "node:async_hooks";
52
52
  import { and, count, eq, getTableColumns, gte, inArray, isNotNull, isNull, lt, lte, ne, or, sql, } from "drizzle-orm";
53
53
  import { appendEvent, appendSandboxEvent } from "../../record/index.js";
54
- import { OwnershipLostError } from "../ownership.js";
54
+ import { OwnershipLostError, PrincipalRoleConflict } from "../ownership.js";
55
55
  import { database, driverError } from "./db.js";
56
56
  import { expectedSchemaVersion, readSchemaVersion } from "./migrate.js";
57
57
  import { createPostgresRecordReader } from "./record.js";
58
58
  import { postgresRecordWriter, postgresSandboxRecordWriter } from "./record-writer.js";
59
- import { actions, artifacts, artifactVersions, artifactContent, commands, definitions, effects, endpoints, links, modelBudgets, modelUsage, principals, publishableKeys, sandboxes, sandboxEvents, sandboxResources, sessionEvents, sessionLogHeads, sessions, toJson, toolCrossings, signingKeys, subjectEpochs, subjectUsage, tenant, tenantSettings, vaultAudit, vaultCredentials, vaultIdempotency, vaults, } from "./schema.js";
59
+ import { artifacts, artifactVersions, artifactContent, definitionFiles, definitionFileUses, commands, definitions, effects, links, modelBudgets, modelUsage, principals, sandboxes, sandboxEvents, sandboxResources, sessionEvents, sessionLogHeads, sessions, toJson, toolCrossings, signingKeys, tenant, tenantSettings, vaultAudit, vaultCredentials, vaultIdempotency, vaults, oauthPending, } from "./schema.js";
60
60
  export function createPostgresSessionStore(options) {
61
61
  return new PostgresSessionStore(options);
62
62
  }
@@ -68,7 +68,6 @@ const DOCUMENTS = {
68
68
  sessions: sessions,
69
69
  commands,
70
70
  effects: effects,
71
- actions: actions,
72
71
  sandboxes,
73
72
  links: links,
74
73
  };
@@ -243,10 +242,6 @@ function podColumns(patch) {
243
242
  };
244
243
  return Object.fromEntries(Object.entries(columns).filter(([, value]) => value !== undefined));
245
244
  }
246
- /** An endpoint row with its unset (null) columns left out, as `EndpointRow` has them. */
247
- function endpointRow(row) {
248
- return Object.fromEntries(Object.entries(row).filter(([, value]) => value !== null));
249
- }
250
245
  /** The time column a signing key's new state stamps. */
251
246
  const SIGNING_KEY_STAMP = {
252
247
  current: "activatedAt",
@@ -255,11 +250,9 @@ const SIGNING_KEY_STAMP = {
255
250
  };
256
251
  /** The audit columns a row has (`ord` only orders them). */
257
252
  const { ord: _ord, ...AUDIT } = getTableColumns(vaultAudit);
258
- const SESSION_TABLES = [sessions, commands, effects, actions, links];
253
+ const SESSION_TABLES = [sessions, commands, effects, links];
259
254
  /** The inserted row's `column` (`ON CONFLICT … DO UPDATE`). */
260
255
  const excluded = (column) => sql.raw(`excluded.${column}`);
261
- /** Keeps an endpoint's health `column` when its URL stays; a new URL starts with `fresh`. */
262
- const sameUrl = (column, fresh = sql `NULL`) => sql `CASE WHEN ${endpoints.url} = excluded.url THEN ${column} ELSE ${fresh} END`;
263
256
  class PostgresTx {
264
257
  db;
265
258
  tenantId;
@@ -536,56 +529,6 @@ class PostgresTx {
536
529
  .limit(options.limit ?? 1000);
537
530
  return rows.map((row) => row.body);
538
531
  }
539
- async actionBodies(where) {
540
- const rows = await this.db
541
- .select({ body: actions.body })
542
- .from(actions)
543
- .where(where)
544
- .orderBy(actions.id);
545
- return rows.map((row) => row.body);
546
- }
547
- async pendingActions(agentId) {
548
- this.check();
549
- return this.actionBodies(and(eq(actions.agentId, agentId), eq(actions.status, "pending")));
550
- }
551
- async deliveringCount(agentId) {
552
- this.check();
553
- const [row] = await this.db
554
- .select({ n: count() })
555
- .from(actions)
556
- .where(and(eq(actions.agentId, agentId), eq(actions.status, "delivering")));
557
- return row.n;
558
- }
559
- async pendingActionsWithEndpoint(limit) {
560
- this.check();
561
- const rows = await this.db
562
- .select({ body: actions.body })
563
- .from(actions)
564
- .innerJoin(endpoints, eq(endpoints.agentId, actions.agentId))
565
- .where(eq(actions.status, "pending"))
566
- .orderBy(actions.id)
567
- .limit(limit);
568
- return rows.map((row) => row.body);
569
- }
570
- async expiredDeliveries(now, limit) {
571
- this.check();
572
- const deadline = sql `${actions.deadlineAt}::timestamptz`;
573
- const rows = await this.db
574
- .select({ body: actions.body })
575
- .from(actions)
576
- .where(and(eq(actions.status, "delivering"), isNotNull(actions.deadlineAt), sql `${deadline} <= ${now.toISOString()}::timestamptz`))
577
- .orderBy(deadline, actions.id)
578
- .limit(limit);
579
- return rows.map((row) => row.body);
580
- }
581
- async actionsForSession(sessionId, filter = {}) {
582
- this.check();
583
- return this.actionBodies(and(eq(actions.sessionId, sessionId), filter.turnId === undefined ? undefined : eq(actions.turnId, filter.turnId), filter.statuses === undefined ? undefined : inArray(actions.status, filter.statuses)));
584
- }
585
- async actionsWithStatus(statuses, filter = {}) {
586
- this.check();
587
- return this.actionBodies(and(inArray(actions.status, statuses), filter.kinds === undefined ? undefined : inArray(actions.kind, filter.kinds)));
588
- }
589
532
  async effectBodies(where) {
590
533
  const rows = await this.db
591
534
  .select({ body: effects.body })
@@ -629,15 +572,12 @@ class PostgresTx {
629
572
  (SELECT count(*) FROM ${sessions})::int AS sessions,
630
573
  (SELECT count(*) FROM ${sessions}
631
574
  WHERE ${sessions.status} IN ('running', 'runnable'))::int AS running,
632
- (SELECT count(*) FROM ${actions}
633
- WHERE ${actions.status} IN ('pending', 'delivering'))::int AS actions,
634
575
  (SELECT count(*) FROM ${effects} WHERE ${effects.status} = 'uncertain')::int AS uncertain,
635
576
  (SELECT count(*) FROM ${sandboxes})::int AS sandboxes,
636
577
  (SELECT count(*) FROM ${definitions})::int AS definitions`);
637
578
  return {
638
579
  sessions: row.sessions,
639
580
  runningSessions: row.running,
640
- pendingActions: row.actions,
641
581
  uncertainEffects: row.uncertain,
642
582
  sandboxes: row.sandboxes,
643
583
  definitions: row.definitions,
@@ -658,81 +598,6 @@ class PostgresTx {
658
598
  retiredGenerations: sql `array_remove(${tenant.retiredGenerations}, ${generation}::int)`,
659
599
  });
660
600
  }
661
- // --- Action endpoints -----------------------------------------------------
662
- async listEndpoints() {
663
- this.check();
664
- const rows = await this.db.select().from(endpoints).orderBy(endpoints.agentId);
665
- return rows.map(endpointRow);
666
- }
667
- async getEndpoint(agentId) {
668
- this.check();
669
- const [row] = await this.db.select().from(endpoints).where(eq(endpoints.agentId, agentId));
670
- return row && endpointRow(row);
671
- }
672
- async putEndpoint(row) {
673
- this.check();
674
- // Health belongs to a URL: a new URL starts with none.
675
- await this.db
676
- .insert(endpoints)
677
- .values({
678
- agentId: row.agentId,
679
- url: row.url,
680
- implementationVersion: row.implementationVersion,
681
- manifestHash: row.manifestHash ?? null,
682
- timeoutMs: row.timeoutMs,
683
- maxConcurrent: row.maxConcurrent,
684
- principalId: row.principalId ?? null,
685
- consecutiveFailures: 0,
686
- createdAt: row.updatedAt,
687
- updatedAt: row.updatedAt,
688
- })
689
- .onConflictDoUpdate({
690
- target: endpoints.agentId,
691
- set: {
692
- url: excluded("url"),
693
- implementationVersion: excluded("implementation_version"),
694
- manifestHash: excluded("manifest_hash"),
695
- timeoutMs: excluded("timeout_ms"),
696
- maxConcurrent: excluded("max_concurrent"),
697
- principalId: excluded("principal_id"),
698
- updatedAt: excluded("updated_at"),
699
- lastDeliveryAt: sameUrl(sql `${endpoints.lastDeliveryAt}`),
700
- lastSuccessAt: sameUrl(sql `${endpoints.lastSuccessAt}`),
701
- lastErrorCode: sameUrl(sql `${endpoints.lastErrorCode}`),
702
- lastErrorMessage: sameUrl(sql `${endpoints.lastErrorMessage}`),
703
- consecutiveFailures: sameUrl(sql `${endpoints.consecutiveFailures}`, sql `0`),
704
- servedImplementationVersion: sameUrl(sql `${endpoints.servedImplementationVersion}`),
705
- servedManifestHash: sameUrl(sql `${endpoints.servedManifestHash}`),
706
- },
707
- });
708
- }
709
- async deleteEndpoint(agentId) {
710
- this.check();
711
- await this.db.delete(endpoints).where(eq(endpoints.agentId, agentId));
712
- }
713
- async recordEndpointHealth(agentId, update) {
714
- this.check();
715
- const set = update.kind === "success"
716
- ? {
717
- lastDeliveryAt: update.at,
718
- lastSuccessAt: update.at,
719
- consecutiveFailures: 0,
720
- lastErrorCode: null,
721
- lastErrorMessage: null,
722
- }
723
- : update.kind === "failure"
724
- ? {
725
- lastDeliveryAt: update.at,
726
- lastErrorCode: update.code,
727
- lastErrorMessage: update.message,
728
- consecutiveFailures: sql `${endpoints.consecutiveFailures} + 1`,
729
- }
730
- : {
731
- servedImplementationVersion: update.implementationVersion,
732
- servedManifestHash: update.manifestHash ?? null,
733
- };
734
- await this.db.update(endpoints).set(set).where(eq(endpoints.agentId, agentId));
735
- }
736
601
  // --- principals ----------------------------------------------------------
737
602
  async insertPrincipal(row) {
738
603
  this.check();
@@ -751,14 +616,33 @@ class PostgresTx {
751
616
  const [row] = await this.db.select().from(principals).where(eq(principals.id, id));
752
617
  return row;
753
618
  }
754
- async applicationTokenHashes() {
619
+ async listPrincipals() {
620
+ this.check();
621
+ return await this.db.select().from(principals).orderBy(principals.id);
622
+ }
623
+ async putPrincipal(id, tokenHash, createdAt, role = "application") {
624
+ this.check();
625
+ const [row] = await this.db
626
+ .insert(principals)
627
+ .values({ id, role, tokenHash, idempotencyKey: null, createdAt })
628
+ .onConflictDoUpdate({
629
+ target: principals.id,
630
+ set: { tokenHash, createdAt, idempotencyKey: null },
631
+ // A key keeps its role: rotating never turns one kind of key into another.
632
+ setWhere: eq(principals.role, role),
633
+ })
634
+ .returning();
635
+ if (!row)
636
+ throw new PrincipalRoleConflict(id);
637
+ return row;
638
+ }
639
+ async deletePrincipal(id) {
755
640
  this.check();
756
641
  const rows = await this.db
757
- .select({ tokenHash: principals.tokenHash })
758
- .from(principals)
759
- .where(eq(principals.role, "application"))
760
- .orderBy(principals.id);
761
- return rows.map((row) => row.tokenHash);
642
+ .delete(principals)
643
+ .where(eq(principals.id, id))
644
+ .returning({ id: principals.id });
645
+ return rows.length > 0;
762
646
  }
763
647
  // --- vault ---------------------------------------------------------------
764
648
  async insertVault(row) {
@@ -778,6 +662,14 @@ class PostgresTx {
778
662
  .where(and(eq(vaults.ownerUserId, ownerUserId), eq(vaults.scope, "user")))
779
663
  .orderBy(vaults.createdAt, vaults.id);
780
664
  }
665
+ async installationVaults() {
666
+ this.check();
667
+ return this.db
668
+ .select()
669
+ .from(vaults)
670
+ .where(eq(vaults.scope, "installation"))
671
+ .orderBy(vaults.createdAt, vaults.id);
672
+ }
781
673
  async updateVaultMetadata(id, metadataJson) {
782
674
  this.check();
783
675
  await this.db.update(vaults).set({ metadataJson }).where(eq(vaults.id, id));
@@ -858,7 +750,24 @@ class PostgresTx {
858
750
  this.check();
859
751
  await this.db.insert(vaultIdempotency).values(row);
860
752
  }
861
- // --- subject tokens ------------------------------------------------------
753
+ async insertOAuthPending(row) {
754
+ this.check();
755
+ await this.db.insert(oauthPending).values(row);
756
+ }
757
+ async takeOAuthPending(stateHash) {
758
+ this.check();
759
+ const [row] = await this.db
760
+ .delete(oauthPending)
761
+ .where(eq(oauthPending.stateHash, stateHash))
762
+ .returning();
763
+ return row;
764
+ }
765
+ async deleteExpiredOAuthPending(now) {
766
+ this.check();
767
+ const result = await this.db.delete(oauthPending).where(lt(oauthPending.expiresAt, now));
768
+ return result.count;
769
+ }
770
+ // --- signing keys --------------------------------------------------------
862
771
  async lockSigningKeys() {
863
772
  this.check();
864
773
  await this.db.execute(sql `SELECT pg_advisory_xact_lock(hashtext('nylorun.signing_keys'))`);
@@ -895,103 +804,6 @@ class PostgresTx {
895
804
  const [row] = await this.db.select({ n: count() }).from(signingKeys);
896
805
  return row.n;
897
806
  }
898
- async subjectEpoch(subject) {
899
- this.check();
900
- const [row] = await this.db
901
- .select({ epoch: subjectEpochs.epoch })
902
- .from(subjectEpochs)
903
- .where(eq(subjectEpochs.subject, subject));
904
- return row?.epoch ?? 0;
905
- }
906
- async subjectEpochs(subjects) {
907
- this.check();
908
- if (subjects.length === 0)
909
- return new Map();
910
- const rows = await this.db
911
- .select({ subject: subjectEpochs.subject, epoch: subjectEpochs.epoch })
912
- .from(subjectEpochs)
913
- .where(inArray(subjectEpochs.subject, subjects));
914
- return new Map(rows.map((row) => [row.subject, row.epoch]));
915
- }
916
- async bumpSubjectEpoch(subject, at) {
917
- this.check();
918
- const [row] = await this.db
919
- .insert(subjectEpochs)
920
- .values({ subject, epoch: 1, revokedAt: at })
921
- .onConflictDoUpdate({
922
- target: subjectEpochs.subject,
923
- set: { epoch: sql `${subjectEpochs.epoch} + 1`, revokedAt: excluded("revoked_at") },
924
- })
925
- .returning({ epoch: subjectEpochs.epoch });
926
- return row.epoch;
927
- }
928
- async lockSubjectUsage(initial) {
929
- this.check();
930
- await this.db.insert(subjectUsage).values(initial).onConflictDoNothing();
931
- const [row] = await this.db
932
- .select()
933
- .from(subjectUsage)
934
- .where(eq(subjectUsage.subject, initial.subject))
935
- .for("update");
936
- return row;
937
- }
938
- async putSubjectUsage(row) {
939
- this.check();
940
- await this.db
941
- .insert(subjectUsage)
942
- .values(row)
943
- .onConflictDoUpdate({
944
- target: subjectUsage.subject,
945
- set: { turnTokens: excluded("turn_tokens"), refilledAt: excluded("refilled_at") },
946
- });
947
- }
948
- async countOwnerSessions(ownerUserId, statuses) {
949
- this.check();
950
- const [row] = await this.db
951
- .select({ n: count() })
952
- .from(sessions)
953
- .where(and(eq(sessions.ownerUserId, ownerUserId), inArray(sessions.status, statuses)));
954
- return row.n;
955
- }
956
- // --- publishable keys ----------------------------------------------------
957
- async insertPublishableKey(row) {
958
- this.check();
959
- await this.db.insert(publishableKeys).values(row);
960
- }
961
- async publishableKeyByKey(key) {
962
- this.check();
963
- const [row] = await this.db
964
- .select()
965
- .from(publishableKeys)
966
- .where(eq(publishableKeys.key, key));
967
- return row;
968
- }
969
- async publishableKey(id) {
970
- this.check();
971
- const [row] = await this.db
972
- .select()
973
- .from(publishableKeys)
974
- .where(eq(publishableKeys.id, id));
975
- return row;
976
- }
977
- async publishableKeys() {
978
- this.check();
979
- return this.db
980
- .select()
981
- .from(publishableKeys)
982
- .orderBy(publishableKeys.createdAt, publishableKeys.id);
983
- }
984
- async updatePublishableKey(id, patch) {
985
- this.check();
986
- if (patch.originsJson === undefined && patch.revokedAt === undefined)
987
- return (await this.publishableKey(id)) !== undefined;
988
- const rows = await this.db
989
- .update(publishableKeys)
990
- .set(patch)
991
- .where(eq(publishableKeys.id, id))
992
- .returning({ id: publishableKeys.id });
993
- return rows.length === 1;
994
- }
995
807
  // --- model usage ---------------------------------------------------------
996
808
  async recordModelUsage(row) {
997
809
  this.check();
@@ -1001,8 +813,8 @@ class PostgresTx {
1001
813
  ...row,
1002
814
  duplicate: sql `EXISTS (SELECT 1 FROM ${modelUsage} WHERE ${modelUsage.effectKey} = ${row.effectKey})`,
1003
815
  })
1004
- .returning({ duplicate: modelUsage.duplicate });
1005
- return { ...row, duplicate: inserted.duplicate };
816
+ .returning();
817
+ return inserted;
1006
818
  }
1007
819
  async modelUsageTotals(query) {
1008
820
  this.check();
@@ -1185,6 +997,42 @@ class PostgresTx {
1185
997
  .where(scope === "sessions" ? isNotNull(artifacts.sessionId) : undefined);
1186
998
  return rows.map((row) => row.blobKey);
1187
999
  }
1000
+ // --- definition files -----------------------------------------------------
1001
+ async definitionFile(sha256) {
1002
+ this.check();
1003
+ const [row] = await this.db.select().from(definitionFiles).where(eq(definitionFiles.sha256, sha256));
1004
+ return row;
1005
+ }
1006
+ async insertDefinitionFile(row) {
1007
+ this.check();
1008
+ const inserted = await this.db
1009
+ .insert(definitionFiles)
1010
+ .values(row)
1011
+ .onConflictDoNothing()
1012
+ .returning({ sha256: definitionFiles.sha256 });
1013
+ return inserted.length === 1;
1014
+ }
1015
+ async heldDefinitionFiles(shas) {
1016
+ this.check();
1017
+ const found = new Set();
1018
+ for (let at = 0; at < shas.length; at += CONTENT_BATCH) {
1019
+ const rows = await this.db
1020
+ .select({ sha256: definitionFiles.sha256 })
1021
+ .from(definitionFiles)
1022
+ .where(inArray(definitionFiles.sha256, shas.slice(at, at + CONTENT_BATCH)));
1023
+ for (const row of rows)
1024
+ found.add(row.sha256);
1025
+ }
1026
+ return found;
1027
+ }
1028
+ async insertDefinitionFileUses(agentId, manifestHash, shas) {
1029
+ this.check();
1030
+ for (let at = 0; at < shas.length; at += CONTENT_BATCH)
1031
+ await this.db
1032
+ .insert(definitionFileUses)
1033
+ .values(shas.slice(at, at + CONTENT_BATCH).map((sha256) => ({ agentId, manifestHash, sha256 })))
1034
+ .onConflictDoNothing();
1035
+ }
1188
1036
  // --- settings ------------------------------------------------------------
1189
1037
  async getSetting(key) {
1190
1038
  this.check();
@@ -1208,7 +1056,6 @@ class PostgresTx {
1208
1056
  if (scope === "sessions" || scope === "all") {
1209
1057
  for (const table of SESSION_TABLES)
1210
1058
  await db.delete(table);
1211
- await db.delete(subjectUsage);
1212
1059
  await db.delete(toolCrossings);
1213
1060
  // A session's artifacts go with it (their versions by cascade).
1214
1061
  await db.delete(artifacts).where(isNotNull(artifacts.sessionId));
@@ -1228,7 +1075,8 @@ class PostgresTx {
1228
1075
  }
1229
1076
  if (scope === "all") {
1230
1077
  await db.delete(definitions);
1231
- await db.delete(endpoints);
1078
+ // The files stay, as their bytes do; only what used them goes.
1079
+ await db.delete(definitionFileUses);
1232
1080
  await db.delete(modelUsage);
1233
1081
  await db.delete(modelBudgets);
1234
1082
  await db.delete(artifacts);
@@ -1,3 +1,27 @@
1
+ /**
2
+ * The Tenant a database holds (tenancy.md §1, §4; session-store.md §2). One database is one
3
+ * Tenant: its state in the schema `nylorun`, its record in `nylorun_streams`, and its
4
+ * envelope in the one row of `nylorun.tenant`. There is no catalog.
5
+ *
6
+ * `openTenantDatabase` runs in one transaction, under the database's migration lock
7
+ * (`migrate.ts`), so several processes starting at once migrate and create once:
8
+ *
9
+ * 1. Refuse an old layout (`database-layout-old`): `tenant_<id>` schemas or a record keyed by
10
+ * Tenant (a Runtime from before one Tenant per installation), or the `schema_version`
11
+ * tables of a pre-release build of it. It is never touched: this release starts fresh.
12
+ * 2. Apply the missing migrations (`migrate.ts`). A database with migrations this Runtime
13
+ * does not know is `schema-too-new`; a migration Postgres rejects is `migration-failed`.
14
+ * 3. When `nylorun.tenant` is empty, create the Tenant: its row (id, name, created time) and
15
+ * its first principals. A database whose row is gone but that holds a Tenant's data
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 (the
18
+ * ephemeral Runtime's application key); existing ones are kept, as ordinary keys.
19
+ * 4. Read the envelope back (`envelope-invalid` when it does not parse), and open the store.
20
+ *
21
+ * A lost connection or an unavailable server says nothing about the Tenant: it is thrown as
22
+ * it is, never as a cause.
23
+ */
24
+ import type { KeyRole } from "@nylorun/core/compatibility";
1
25
  import type { Sql } from "postgres";
2
26
  import { type TenantEnvelope } from "@nylorun/core/contracts";
3
27
  import type { SessionStore } from "../types.js";
@@ -8,6 +32,14 @@ export interface InitialPrincipal {
8
32
  id: string;
9
33
  /** SHA-256 hex of the principal's key. */
10
34
  credentialHash: string;
35
+ /** What the key reaches; `application` when absent. */
36
+ role?: KeyRole;
37
+ /**
38
+ * Its key comes from outside the database (the bootstrap file, or Studio's key derived from
39
+ * the admin key): on later opens a principal of the same role takes this hash. Otherwise an
40
+ * existing principal is kept as it is.
41
+ */
42
+ replace?: boolean;
11
43
  }
12
44
  /** Who the Tenant is when the database holds none yet. */
13
45
  export interface TenantCreation {
@@ -16,8 +48,8 @@ export interface TenantCreation {
16
48
  /** Ignored when the Tenant exists. */
17
49
  name: string;
18
50
  /**
19
- * The principals the Tenant must have, given its id (derived keys depend on it). Created
20
- * with the Tenant; on later opens, the missing ones are added.
51
+ * The principals the Tenant must have, given its id. Created with the Tenant; on later
52
+ * opens, the missing ones are added.
21
53
  */
22
54
  principals?(tenantId: string): readonly InitialPrincipal[];
23
55
  }
@@ -1,27 +1,4 @@
1
- /**
2
- * The Tenant a database holds (tenancy.md §1, §4; session-store.md §2). One database is one
3
- * Tenant: its state in the schema `nylorun`, its record in `nylorun_streams`, and its
4
- * envelope in the one row of `nylorun.tenant`. There is no catalog.
5
- *
6
- * `openTenantDatabase` runs in one transaction, under the database's migration lock
7
- * (`migrate.ts`), so several processes starting at once migrate and create once:
8
- *
9
- * 1. Refuse an old layout (`database-layout-old`): `tenant_<id>` schemas or a record keyed by
10
- * Tenant (a Runtime from before one Tenant per installation), or the `schema_version`
11
- * tables of a pre-release build of it. It is never touched: this release starts fresh.
12
- * 2. Apply the missing migrations (`migrate.ts`). A database with migrations this Runtime
13
- * does not know is `schema-too-new`; a migration Postgres rejects is `migration-failed`.
14
- * 3. When `nylorun.tenant` is empty, create the Tenant: its row (id, name, created time) and
15
- * its first principals. A database whose row is gone but that holds a Tenant's data
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.
19
- * 4. Read the envelope back (`envelope-invalid` when it does not parse), and open the store.
20
- *
21
- * A lost connection or an unavailable server says nothing about the Tenant: it is thrown as
22
- * it is, never as a cause.
23
- */
24
- import { sql } from "drizzle-orm";
1
+ import { and, eq, ne, sql } from "drizzle-orm";
25
2
  import { isTenantId, newTenantId } from "@nylorun/core/compatibility";
26
3
  import { TenantEnvelopeSchema, } from "@nylorun/core/contracts";
27
4
  import { openError, TenantOpenError } from "../../tenant/cause.js";
@@ -138,17 +115,24 @@ async function ensureTenant(tx, create, migrated, now) {
138
115
  else if (migrated.from !== migrated.to) {
139
116
  await tx.update(tenant).set({ schemaVersion: migrated.to });
140
117
  }
141
- for (const principal of create.principals?.(tenantId) ?? [])
142
- // A principal with this id or this key already exists: it is kept as it is.
143
- await tx
144
- .insert(principals)
145
- .values({
118
+ for (const principal of create.principals?.(tenantId) ?? []) {
119
+ const insert = tx.insert(principals).values({
146
120
  id: principal.id,
147
- role: "application",
121
+ role: principal.role ?? "application",
148
122
  tokenHash: principal.credentialHash,
149
123
  idempotencyKey: null,
150
124
  createdAt,
151
- })
152
- .onConflictDoNothing();
125
+ });
126
+ if (principal.replace)
127
+ // The bootstrap key or Studio's: a principal of the same role takes the current key.
128
+ await insert.onConflictDoUpdate({
129
+ target: principals.id,
130
+ set: { tokenHash: principal.credentialHash, createdAt },
131
+ setWhere: and(eq(principals.role, principal.role ?? "application"), ne(principals.tokenHash, principal.credentialHash)),
132
+ });
133
+ // A principal with this id or this key already exists: it is kept as it is.
134
+ else
135
+ await insert.onConflictDoNothing();
136
+ }
153
137
  return existing === undefined;
154
138
  }