@nylorun/runtime 0.13.0-beta → 0.14.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 (223) hide show
  1. package/CHANGELOG.md +330 -0
  2. package/README.md +69 -15
  3. package/dist/adapters/execution/restate.d.ts +7 -2
  4. package/dist/adapters/execution/restate.js +38 -8
  5. package/dist/adapters/replication/pgoutput.d.ts +16 -0
  6. package/dist/adapters/replication/pgoutput.js +169 -0
  7. package/dist/adapters/streams/s2.js +37 -20
  8. package/dist/admin-openapi.json +1071 -0
  9. package/dist/api/a2a/card.d.ts +15 -0
  10. package/dist/api/a2a/card.js +24 -0
  11. package/dist/api/a2a/endpoint.d.ts +8 -0
  12. package/dist/api/a2a/endpoint.js +81 -0
  13. package/dist/api/a2a/protocol.d.ts +144 -0
  14. package/dist/api/a2a/protocol.js +188 -0
  15. package/dist/api/a2a/routes.d.ts +19 -0
  16. package/dist/api/a2a/routes.js +289 -0
  17. package/dist/api/a2a/tasks.d.ts +50 -0
  18. package/dist/api/a2a/tasks.js +186 -0
  19. package/dist/api/ag-ui/endpoint.d.ts +8 -0
  20. package/dist/api/ag-ui/endpoint.js +126 -0
  21. package/dist/api/ag-ui/history.d.ts +7 -0
  22. package/dist/api/ag-ui/history.js +60 -0
  23. package/dist/api/ag-ui/routes.d.ts +32 -0
  24. package/dist/api/ag-ui/routes.js +305 -0
  25. package/dist/api/ag-ui/session-id.d.ts +1 -0
  26. package/dist/api/ag-ui/session-id.js +12 -0
  27. package/dist/api/ag-ui/sse.d.ts +9 -0
  28. package/dist/api/ag-ui/sse.js +7 -0
  29. package/dist/api/ag-ui/translate.d.ts +38 -0
  30. package/dist/api/ag-ui/translate.js +222 -0
  31. package/dist/api/components.d.ts +7825 -0
  32. package/dist/api/components.js +83 -0
  33. package/dist/api/http/app.d.ts +18 -0
  34. package/dist/api/http/app.js +58 -0
  35. package/dist/api/http/body.d.ts +4 -0
  36. package/dist/api/http/body.js +37 -0
  37. package/dist/api/http/define.d.ts +39 -0
  38. package/dist/api/http/define.js +130 -0
  39. package/dist/api/http/respond.d.ts +9 -0
  40. package/dist/api/http/respond.js +44 -0
  41. package/dist/api/http/routes/access.d.ts +24 -0
  42. package/dist/api/http/routes/access.js +259 -0
  43. package/dist/api/http/routes/actions.d.ts +8 -0
  44. package/dist/api/http/routes/actions.js +76 -0
  45. package/dist/api/http/routes/endpoints.d.ts +7 -0
  46. package/dist/api/http/routes/endpoints.js +100 -0
  47. package/dist/api/http/routes/sessions.d.ts +3 -0
  48. package/dist/api/http/routes/sessions.js +205 -0
  49. package/dist/api/http/routes/tenant.d.ts +10 -0
  50. package/dist/api/http/routes/tenant.js +172 -0
  51. package/dist/api/http/routes/vaults.d.ts +8 -0
  52. package/dist/api/http/routes/vaults.js +167 -0
  53. package/dist/api/openapi.d.ts +19 -0
  54. package/dist/api/openapi.js +138 -0
  55. package/dist/api/route.d.ts +12 -0
  56. package/dist/api/route.js +4 -0
  57. package/dist/core/bearer.d.ts +8 -0
  58. package/dist/core/bearer.js +11 -0
  59. package/dist/core/flow-host.d.ts +7 -15
  60. package/dist/core/flow-host.js +16 -41
  61. package/dist/core/sandbox-routes.d.ts +12 -7
  62. package/dist/core/sandbox-routes.js +24 -25
  63. package/dist/execution/memory.d.ts +8 -5
  64. package/dist/execution/memory.js +33 -13
  65. package/dist/execution/types.d.ts +38 -6
  66. package/dist/execution/types.js +12 -3
  67. package/dist/host/admin-api.d.ts +22 -0
  68. package/dist/host/admin-api.js +178 -0
  69. package/dist/host/app.d.ts +34 -0
  70. package/dist/host/app.js +185 -0
  71. package/dist/host/config-for.d.ts +2 -0
  72. package/dist/host/config-for.js +1 -0
  73. package/dist/host/config.d.ts +11 -0
  74. package/dist/host/cors.d.ts +27 -0
  75. package/dist/host/cors.js +64 -0
  76. package/dist/host/create-host.d.ts +31 -3
  77. package/dist/host/create-host.js +168 -327
  78. package/dist/host/http.d.ts +14 -3
  79. package/dist/host/http.js +64 -23
  80. package/dist/host/main.js +55 -4
  81. package/dist/host/stack-config.d.ts +20 -0
  82. package/dist/host/stack-config.js +55 -0
  83. package/dist/mcp/pool.d.ts +19 -1
  84. package/dist/mcp/pool.js +60 -10
  85. package/dist/model/catalog.d.ts +2 -10
  86. package/dist/model/classify.d.ts +13 -0
  87. package/dist/model/classify.js +61 -0
  88. package/dist/model/models.d.ts +10 -1
  89. package/dist/model/models.js +14 -6
  90. package/dist/model/pi-model.d.ts +17 -2
  91. package/dist/model/pi-model.js +366 -222
  92. package/dist/openapi.json +25696 -0
  93. package/dist/sandbox/manager.d.ts +13 -1
  94. package/dist/sandbox/manager.js +8 -11
  95. package/dist/sandbox/resolve.d.ts +33 -0
  96. package/dist/sandbox/resolve.js +81 -0
  97. package/dist/sandbox/session-sandbox.d.ts +11 -0
  98. package/dist/sandbox/session-sandbox.js +51 -0
  99. package/dist/sandbox/share.d.ts +14 -2
  100. package/dist/sandbox/share.js +33 -3
  101. package/dist/sandbox/tenant-config.d.ts +37 -0
  102. package/dist/sandbox/tenant-config.js +51 -0
  103. package/dist/sandbox/tools.d.ts +7 -9
  104. package/dist/sandbox/tools.js +11 -3
  105. package/dist/sandbox/types.d.ts +8 -2
  106. package/dist/store/event.d.ts +19 -0
  107. package/dist/store/event.js +49 -0
  108. package/dist/store/memory.d.ts +20 -8
  109. package/dist/store/memory.js +265 -100
  110. package/dist/store/postgres/connect.d.ts +13 -0
  111. package/dist/store/postgres/connect.js +5 -0
  112. package/dist/store/postgres/migrations/003_subject_tokens.d.ts +8 -0
  113. package/dist/store/postgres/migrations/003_subject_tokens.js +42 -0
  114. package/dist/store/postgres/migrations/004_publishable_keys.d.ts +6 -0
  115. package/dist/store/postgres/migrations/004_publishable_keys.js +18 -0
  116. package/dist/store/postgres/migrations/005_action_endpoints.d.ts +6 -0
  117. package/dist/store/postgres/migrations/005_action_endpoints.js +32 -0
  118. package/dist/store/postgres/migrations/006_drop_executors.d.ts +11 -0
  119. package/dist/store/postgres/migrations/006_drop_executors.js +32 -0
  120. package/dist/store/postgres/migrations/007_event_time.d.ts +7 -0
  121. package/dist/store/postgres/migrations/007_event_time.js +14 -0
  122. package/dist/store/postgres/migrations/008_durable_streams.d.ts +13 -0
  123. package/dist/store/postgres/migrations/008_durable_streams.js +35 -0
  124. package/dist/store/postgres/migrations/index.d.ts +5 -1
  125. package/dist/store/postgres/migrations/index.js +22 -2
  126. package/dist/store/postgres/migrations/shared/index.d.ts +31 -0
  127. package/dist/store/postgres/migrations/shared/index.js +92 -0
  128. package/dist/store/postgres/record.d.ts +11 -0
  129. package/dist/store/postgres/record.js +64 -0
  130. package/dist/store/postgres/store.js +337 -95
  131. package/dist/store/postgres/tenants.js +14 -0
  132. package/dist/store/types.d.ts +176 -81
  133. package/dist/streams/basin.d.ts +18 -1
  134. package/dist/streams/basin.js +34 -2
  135. package/dist/streams/control.d.ts +18 -0
  136. package/dist/streams/control.js +29 -0
  137. package/dist/streams/memory.d.ts +3 -1
  138. package/dist/streams/memory.js +14 -8
  139. package/dist/streams/relay/core.d.ts +35 -0
  140. package/dist/streams/relay/core.js +366 -0
  141. package/dist/streams/relay/memory.d.ts +69 -0
  142. package/dist/streams/relay/memory.js +176 -0
  143. package/dist/streams/relay/types.d.ts +77 -0
  144. package/dist/streams/relay/types.js +7 -0
  145. package/dist/streams/types.d.ts +66 -36
  146. package/dist/streams/types.js +18 -53
  147. package/dist/tenant/access-policy.d.ts +30 -0
  148. package/dist/tenant/access-policy.js +52 -0
  149. package/dist/tenant/actions.d.ts +10 -149
  150. package/dist/tenant/actions.js +5 -167
  151. package/dist/tenant/advance.js +32 -0
  152. package/dist/tenant/auth.d.ts +19 -17
  153. package/dist/tenant/auth.js +90 -93
  154. package/dist/tenant/browser.d.ts +15 -0
  155. package/dist/tenant/browser.js +39 -0
  156. package/dist/tenant/commands.d.ts +18 -2
  157. package/dist/tenant/commands.js +107 -113
  158. package/dist/tenant/context.d.ts +75 -18
  159. package/dist/tenant/context.js +14 -10
  160. package/dist/tenant/delivery-token.d.ts +41 -0
  161. package/dist/tenant/delivery-token.js +105 -0
  162. package/dist/tenant/delivery.d.ts +62 -0
  163. package/dist/tenant/delivery.js +476 -0
  164. package/dist/tenant/effects.d.ts +5 -0
  165. package/dist/tenant/effects.js +78 -37
  166. package/dist/tenant/endpoints.d.ts +67 -0
  167. package/dist/tenant/endpoints.js +90 -0
  168. package/dist/tenant/ephemeral.d.ts +6 -0
  169. package/dist/tenant/ephemeral.js +60 -36
  170. package/dist/tenant/http.d.ts +8 -9
  171. package/dist/tenant/http.js +13 -27
  172. package/dist/tenant/jwt.d.ts +16 -0
  173. package/dist/tenant/jwt.js +50 -0
  174. package/dist/tenant/module.d.ts +3 -0
  175. package/dist/tenant/module.js +6 -12
  176. package/dist/tenant/outbound.d.ts +44 -0
  177. package/dist/tenant/outbound.js +127 -0
  178. package/dist/tenant/pool.d.ts +0 -2
  179. package/dist/tenant/pool.js +0 -18
  180. package/dist/tenant/reset.d.ts +0 -4
  181. package/dist/tenant/reset.js +0 -5
  182. package/dist/tenant/runtime.d.ts +10 -20
  183. package/dist/tenant/runtime.js +59 -42
  184. package/dist/tenant/scheduler.d.ts +7 -2
  185. package/dist/tenant/scheduler.js +7 -3
  186. package/dist/tenant/session-streams.d.ts +125 -0
  187. package/dist/tenant/session-streams.js +399 -0
  188. package/dist/tenant/sessions.d.ts +28 -4
  189. package/dist/tenant/sessions.js +118 -24
  190. package/dist/tenant/signing-keys.d.ts +55 -0
  191. package/dist/tenant/signing-keys.js +172 -0
  192. package/dist/tenant/slim.d.ts +8 -0
  193. package/dist/tenant/slim.js +21 -0
  194. package/dist/tenant/status.d.ts +0 -3
  195. package/dist/tenant/status.js +19 -12
  196. package/dist/tenant/streams.d.ts +60 -67
  197. package/dist/tenant/streams.js +140 -208
  198. package/dist/tenant/subject-limits.d.ts +16 -0
  199. package/dist/tenant/subject-limits.js +45 -0
  200. package/dist/tenant/sweep.d.ts +1 -10
  201. package/dist/tenant/sweep.js +29 -69
  202. package/dist/tenant/tokens.d.ts +14 -0
  203. package/dist/tenant/tokens.js +178 -0
  204. package/dist/tenant/transcript.d.ts +48 -5
  205. package/dist/tenant/transcript.js +58 -1
  206. package/dist/tenant/types.d.ts +29 -7
  207. package/dist/tenant/worker.d.ts +3 -1
  208. package/dist/tenant/worker.js +9 -3
  209. package/dist/vault/service.d.ts +18 -2
  210. package/dist/vault/service.js +57 -13
  211. package/dist/version.d.ts +1 -1
  212. package/dist/version.js +1 -1
  213. package/package.json +20 -8
  214. package/dist/core/executors.d.ts +0 -44
  215. package/dist/core/executors.js +0 -95
  216. package/dist/streams/relay.d.ts +0 -37
  217. package/dist/streams/relay.js +0 -254
  218. package/dist/tenant/live.d.ts +0 -92
  219. package/dist/tenant/live.js +0 -290
  220. package/dist/tenant/routes-tenant.d.ts +0 -12
  221. package/dist/tenant/routes-tenant.js +0 -137
  222. package/dist/tenant/routes.d.ts +0 -9
  223. package/dist/tenant/routes.js +0 -151
@@ -1,5 +1,7 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { randomUUID } from "node:crypto";
3
+ import { MemoryRecord } from "../streams/relay/memory.js";
4
+ import { buildEvent } from "./event.js";
3
5
  import { encodeCursor } from "./cursor.js";
4
6
  import { OwnershipLostError } from "./ownership.js";
5
7
  import { DOC_TABLES, } from "./types.js";
@@ -9,14 +11,19 @@ function emptyState() {
9
11
  return {
10
12
  docs: Object.fromEntries(DOC_TABLES.map((table) => [table, new Map()])),
11
13
  sessionMeta: new Map(),
12
- outbox: new Map(),
13
- executors: new Map(),
14
+ heads: new Map(),
15
+ basin: { current: 0, retired: [] },
16
+ endpoints: new Map(),
14
17
  principals: new Map(),
15
18
  vaults: new Map(),
16
19
  credentials: new Map(),
17
20
  audit: [],
18
21
  idempotency: new Map(),
19
22
  settings: new Map(),
23
+ signingKeys: new Map(),
24
+ subjectEpochs: new Map(),
25
+ subjectUsage: new Map(),
26
+ publishableKeys: new Map(),
20
27
  };
21
28
  }
22
29
  const OWNERSHIP_KEYS = new Set(["owner", "epoch", "ownerExpiresAt"]);
@@ -32,13 +39,15 @@ const copy = (value) => structuredClone(value);
32
39
  export class MemoryStoreData {
33
40
  /** @internal */ state = emptyState();
34
41
  /** @internal */ queue = Promise.resolve();
42
+ /** The record of committed events, read back by the stream relay. */
43
+ record = new MemoryRecord();
35
44
  }
36
45
  /**
37
46
  * In-memory `SessionStore` for unit tests. Not a supported profile.
38
47
  *
39
48
  * Transactions are serialized (one at a time), each works on a copy of the
40
49
  * state and replaces it on commit, so a throw rolls back everything: documents,
41
- * sequences, outbox rows, `afterCommit` callbacks and `signalWork`. Commit listeners see
50
+ * sequences, record rows and `afterCommit` callbacks. Commit listeners see
42
51
  * only this store's commits, as with a Postgres connection.
43
52
  */
44
53
  export class MemorySessionStore {
@@ -79,11 +88,17 @@ export class MemorySessionStore {
79
88
  t.closed = true;
80
89
  }
81
90
  this.data.state = working;
82
- if (t.events.length > 0 || t.workAvailable) {
91
+ for (const change of t.recordChanges)
92
+ if (change.kind === "rows")
93
+ this.data.record.commit(change.rows);
94
+ else {
95
+ this.data.record.deleteRows(this.tenantId);
96
+ this.data.record.setGeneration(this.tenantId, change.generation);
97
+ }
98
+ if (t.events.length > 0) {
83
99
  const commit = {
84
100
  events: t.events,
85
- incarnations: t.incarnations,
86
- workAvailable: t.workAvailable,
101
+ generations: t.generations,
87
102
  };
88
103
  for (const listener of this.listeners) {
89
104
  try {
@@ -112,6 +127,9 @@ export class MemorySessionStore {
112
127
  this.listeners.add(listener);
113
128
  return () => this.listeners.delete(listener);
114
129
  }
130
+ record() {
131
+ return this.data.record;
132
+ }
115
133
  async health() {
116
134
  return {
117
135
  ok: !this.closed,
@@ -137,9 +155,9 @@ class MemoryTx {
137
155
  now;
138
156
  closed = false;
139
157
  events = [];
140
- incarnations = [];
158
+ generations = [];
159
+ recordChanges = [];
141
160
  callbacks = [];
142
- workAvailable = false;
143
161
  constructor(s, tenantId, now) {
144
162
  this.s = s;
145
163
  this.tenantId = tenantId;
@@ -167,7 +185,6 @@ class MemoryTx {
167
185
  owner: null,
168
186
  epoch: 0,
169
187
  ownerExpiresAt: null,
170
- nextSeq: 0,
171
188
  });
172
189
  }
173
190
  const json = JSON.stringify(value);
@@ -178,10 +195,9 @@ class MemoryTx {
178
195
  async delete(table, id) {
179
196
  this.check();
180
197
  this.s.docs[table].delete(id);
181
- if (table === "sessions") {
198
+ // The session's log head stays: a session created again with this id continues its log.
199
+ if (table === "sessions")
182
200
  this.s.sessionMeta.delete(id);
183
- this.s.outbox.delete(id);
184
- }
185
201
  }
186
202
  session(id) {
187
203
  const raw = this.s.docs.sessions.get(id);
@@ -215,34 +231,32 @@ class MemoryTx {
215
231
  const meta = this.s.sessionMeta.get(sessionId);
216
232
  if (!meta)
217
233
  throw new Error(`Session ${sessionId} not found`);
218
- const seq = meta.nextSeq++;
219
- const event = JSON.parse(JSON.stringify({
220
- eventId: randomUUID(),
221
- sessionId,
234
+ let head = this.s.heads.get(sessionId);
235
+ if (!head)
236
+ this.s.heads.set(sessionId, (head = { head: 0, generation: this.s.basin.current }));
237
+ const event = buildEvent({
222
238
  tenantId: this.tenantId,
239
+ sessionId,
223
240
  turnId,
224
- cursor: encodeCursor(sessionId, seq),
225
- createdAt: this.now().toISOString(),
241
+ seq: head.head,
242
+ epoch: meta.epoch,
243
+ time: this.now(),
226
244
  type,
227
245
  payload,
228
- }));
229
- let rows = this.s.outbox.get(sessionId);
230
- if (!rows)
231
- this.s.outbox.set(sessionId, (rows = new Map()));
232
- rows.set(seq, JSON.stringify(event));
246
+ });
247
+ const seq = head.head++;
248
+ this.recordChanges.push({
249
+ kind: "rows",
250
+ rows: [{ tenantId: this.tenantId, sessionId, seq, generation: head.generation, body: event }],
251
+ });
233
252
  this.events.push(event);
234
- const doc = JSON.parse(this.s.docs.sessions.get(sessionId));
235
- this.incarnations.push(doc.streamIncarnation ?? null);
253
+ this.generations.push(head.generation);
236
254
  return copy(event);
237
255
  }
238
256
  afterCommit(fn) {
239
257
  this.check();
240
258
  this.callbacks.push(fn);
241
259
  }
242
- signalWork() {
243
- this.check();
244
- this.workAvailable = true;
245
- }
246
260
  // --- ownership -----------------------------------------------------------
247
261
  async takeOwnership(sessionId, claim) {
248
262
  this.check();
@@ -330,19 +344,29 @@ class MemoryTx {
330
344
  actions() {
331
345
  return this.docs("actions").map(([, doc]) => doc);
332
346
  }
333
- async expiredClaims(now, limit) {
347
+ async pendingActions(agentId) {
348
+ this.check();
349
+ return this.actions().filter((a) => a.status === "pending" && a.agentId === agentId);
350
+ }
351
+ async deliveringCount(agentId) {
352
+ this.check();
353
+ return this.actions().filter((a) => a.status === "delivering" && a.agentId === agentId).length;
354
+ }
355
+ async pendingActionsWithEndpoint(limit) {
334
356
  this.check();
335
357
  return this.actions()
336
- .filter((a) => a.status === "claimed" &&
337
- a.leaseExpiresAt !== null &&
338
- Date.parse(a.leaseExpiresAt) <= now.getTime())
339
- .sort((a, b) => Date.parse(a.leaseExpiresAt) - Date.parse(b.leaseExpiresAt) ||
340
- (a.actionId < b.actionId ? -1 : 1))
358
+ .filter((a) => a.status === "pending" && this.s.endpoints.has(a.agentId))
341
359
  .slice(0, limit);
342
360
  }
343
- async pendingActions(agentId) {
361
+ async expiredDeliveries(now, limit) {
344
362
  this.check();
345
- return this.actions().filter((a) => a.status === "pending" && a.agentId === agentId);
363
+ return this.actions()
364
+ .filter((a) => a.status === "delivering" &&
365
+ typeof a.deadlineAt === "string" &&
366
+ Date.parse(a.deadlineAt) <= now.getTime())
367
+ .sort((a, b) => Date.parse(a.deadlineAt) - Date.parse(b.deadlineAt) ||
368
+ (a.actionId < b.actionId ? -1 : 1))
369
+ .slice(0, limit);
346
370
  }
347
371
  async actionsForSession(sessionId, filter = {}) {
348
372
  this.check();
@@ -394,86 +418,51 @@ class MemoryTx {
394
418
  sessions: sessions.length,
395
419
  runningSessions: sessions.filter((s) => OPEN_SESSION.has(s.status))
396
420
  .length,
397
- pendingActions: this.actions().filter((a) => a.status === "pending" || a.status === "claimed").length,
421
+ pendingActions: this.actions().filter((a) => a.status === "pending" || a.status === "delivering").length,
398
422
  uncertainEffects: this.effects().filter((e) => e.status === "uncertain")
399
423
  .length,
400
424
  sandboxes: this.s.docs.sandboxes.size,
401
425
  definitions: this.s.docs.definitions.size,
402
426
  };
403
427
  }
404
- // --- outbox --------------------------------------------------------------
405
- async outbox(limit, filter = {}) {
428
+ // --- basin generations --------------------------------------------------
429
+ async basinGenerations() {
406
430
  this.check();
407
- const rows = [];
408
- for (const [sessionId, bySeq] of byId(this.s.outbox)) {
409
- if (filter.sessionId !== undefined && filter.sessionId !== sessionId)
410
- continue;
411
- for (const seq of [...bySeq.keys()].sort((a, b) => a - b)) {
412
- if (rows.length >= limit)
413
- return rows;
414
- rows.push({ sessionId, seq, event: JSON.parse(bySeq.get(seq)) });
415
- }
416
- }
417
- return rows;
418
- }
419
- async deleteOutbox(sessionId, throughSeq, incarnation) {
420
- this.check();
421
- const rows = this.s.outbox.get(sessionId);
422
- if (!rows)
423
- return 0;
424
- if (incarnation !== undefined) {
425
- const doc = this.s.docs.sessions.get(sessionId);
426
- if (doc === undefined ||
427
- (JSON.parse(doc).streamIncarnation ?? null) !== incarnation)
428
- return 0;
429
- }
430
- let n = 0;
431
- for (const seq of [...rows.keys()])
432
- if (seq <= throughSeq) {
433
- rows.delete(seq);
434
- n += 1;
435
- }
436
- if (rows.size === 0)
437
- this.s.outbox.delete(sessionId);
438
- return n;
439
- }
440
- async outboxStats() {
441
- this.check();
442
- let depth = 0;
443
- let oldestCreatedAt = null;
444
- for (const rows of this.s.outbox.values())
445
- for (const body of rows.values()) {
446
- depth += 1;
447
- const createdAt = JSON.parse(body).createdAt;
448
- if (oldestCreatedAt === null || createdAt < oldestCreatedAt)
449
- oldestCreatedAt = createdAt;
450
- }
451
- return { depth, oldestCreatedAt };
431
+ return copy(this.s.basin);
432
+ }
433
+ async forgetRetiredGeneration(generation) {
434
+ this.check();
435
+ this.s.basin.retired = this.s.basin.retired.filter((g) => g !== generation);
452
436
  }
453
- // --- executors -----------------------------------------------------------
454
- async listExecutors() {
437
+ // --- Action endpoints -----------------------------------------------------
438
+ async listEndpoints() {
455
439
  this.check();
456
- return byId(this.s.executors).map(([, row]) => copy(row));
440
+ return byId(this.s.endpoints).map(([, row]) => copy(row));
457
441
  }
458
- async getExecutor(agentId) {
442
+ async getEndpoint(agentId) {
459
443
  this.check();
460
- const row = this.s.executors.get(agentId);
444
+ const row = this.s.endpoints.get(agentId);
461
445
  return row && copy(row);
462
446
  }
463
- async putExecutor(row) {
447
+ async putEndpoint(row) {
464
448
  this.check();
465
- for (const other of this.s.executors.values())
466
- if (other.tokenHash === row.tokenHash && other.agentId !== row.agentId)
467
- throw new Error("executors.token_hash must be unique");
468
- const existing = this.s.executors.get(row.agentId);
469
- this.s.executors.set(row.agentId, {
449
+ const existing = this.s.endpoints.get(row.agentId);
450
+ const health = existing && existing.url === row.url ? healthOf(existing) : { consecutiveFailures: 0 };
451
+ this.s.endpoints.set(row.agentId, {
470
452
  ...copy(row),
453
+ ...health,
471
454
  createdAt: existing?.createdAt ?? row.updatedAt,
472
455
  });
473
456
  }
474
- async deleteExecutor(agentId) {
457
+ async deleteEndpoint(agentId) {
475
458
  this.check();
476
- this.s.executors.delete(agentId);
459
+ this.s.endpoints.delete(agentId);
460
+ }
461
+ async recordEndpointHealth(agentId, update) {
462
+ this.check();
463
+ const row = this.s.endpoints.get(agentId);
464
+ if (row)
465
+ this.s.endpoints.set(agentId, withHealth(row, update));
477
466
  }
478
467
  // --- principals ----------------------------------------------------------
479
468
  async insertPrincipal(row) {
@@ -602,6 +591,119 @@ class MemoryTx {
602
591
  throw new Error("vault_idempotency.id must be unique");
603
592
  this.s.idempotency.set(row.id, copy(row));
604
593
  }
594
+ // --- subject tokens ------------------------------------------------------
595
+ /** Transactions are serialized already. */
596
+ async lockSigningKeys() {
597
+ this.check();
598
+ }
599
+ async insertSigningKey(row) {
600
+ this.check();
601
+ if (this.s.signingKeys.has(row.id))
602
+ throw new Error("signing_keys.id must be unique");
603
+ if (row.state !== "revoked")
604
+ for (const other of this.s.signingKeys.values())
605
+ if (other.state === row.state)
606
+ throw new Error(`signing_keys allows one ${row.state} key`);
607
+ this.s.signingKeys.set(row.id, copy(row));
608
+ }
609
+ async signingKey(id) {
610
+ this.check();
611
+ const row = this.s.signingKeys.get(id);
612
+ return row && copy(row);
613
+ }
614
+ async signingKeys(states) {
615
+ this.check();
616
+ return [...this.s.signingKeys.values()]
617
+ .filter((row) => states === undefined || states.includes(row.state))
618
+ .sort(byCreated)
619
+ .map(copy);
620
+ }
621
+ async setSigningKeyState(id, from, to, at) {
622
+ this.check();
623
+ const row = this.s.signingKeys.get(id);
624
+ if (!row || row.state !== from)
625
+ return false;
626
+ if (to !== "revoked")
627
+ for (const other of this.s.signingKeys.values())
628
+ if (other.id !== id && other.state === to)
629
+ throw new Error(`signing_keys allows one ${to} key`);
630
+ this.s.signingKeys.set(id, { ...row, ...signingKeyStamp(to, at), state: to });
631
+ return true;
632
+ }
633
+ async countSigningKeys() {
634
+ this.check();
635
+ return this.s.signingKeys.size;
636
+ }
637
+ async subjectEpoch(subject) {
638
+ this.check();
639
+ return this.s.subjectEpochs.get(subject) ?? 0;
640
+ }
641
+ async subjectEpochs(subjects) {
642
+ this.check();
643
+ const out = new Map();
644
+ for (const subject of subjects) {
645
+ const epoch = this.s.subjectEpochs.get(subject);
646
+ if (epoch !== undefined)
647
+ out.set(subject, epoch);
648
+ }
649
+ return out;
650
+ }
651
+ async bumpSubjectEpoch(subject, _at) {
652
+ this.check();
653
+ const epoch = (this.s.subjectEpochs.get(subject) ?? 0) + 1;
654
+ this.s.subjectEpochs.set(subject, epoch);
655
+ return epoch;
656
+ }
657
+ async lockSubjectUsage(initial) {
658
+ this.check();
659
+ // Transactions are serialized, so the row is as good as locked.
660
+ const row = this.s.subjectUsage.get(initial.subject);
661
+ if (row)
662
+ return copy(row);
663
+ this.s.subjectUsage.set(initial.subject, copy(initial));
664
+ return copy(initial);
665
+ }
666
+ async putSubjectUsage(row) {
667
+ this.check();
668
+ this.s.subjectUsage.set(row.subject, copy(row));
669
+ }
670
+ async countOwnerSessions(ownerUserId, statuses) {
671
+ this.check();
672
+ return this.sessions().filter((s) => s.ownerUserId === ownerUserId &&
673
+ statuses.includes(s.status ?? "")).length;
674
+ }
675
+ // --- publishable keys ----------------------------------------------------
676
+ async insertPublishableKey(row) {
677
+ this.check();
678
+ for (const other of this.s.publishableKeys.values())
679
+ if (other.id === row.id || other.key === row.key || other.name === row.name)
680
+ throw new Error("publishable_keys id, key and name must be unique");
681
+ this.s.publishableKeys.set(row.id, copy(row));
682
+ }
683
+ async publishableKeyByKey(key) {
684
+ this.check();
685
+ for (const row of this.s.publishableKeys.values())
686
+ if (row.key === key)
687
+ return copy(row);
688
+ return undefined;
689
+ }
690
+ async publishableKey(id) {
691
+ this.check();
692
+ const row = this.s.publishableKeys.get(id);
693
+ return row && copy(row);
694
+ }
695
+ async publishableKeys() {
696
+ this.check();
697
+ return [...this.s.publishableKeys.values()].sort(byCreated).map(copy);
698
+ }
699
+ async updatePublishableKey(id, patch) {
700
+ this.check();
701
+ const row = this.s.publishableKeys.get(id);
702
+ if (!row)
703
+ return false;
704
+ this.s.publishableKeys.set(id, { ...row, ...copy(patch) });
705
+ return true;
706
+ }
605
707
  // --- settings ------------------------------------------------------------
606
708
  async getSetting(key) {
607
709
  this.check();
@@ -625,21 +727,84 @@ class MemoryTx {
625
727
  ])
626
728
  this.s.docs[table].clear();
627
729
  this.s.sessionMeta.clear();
628
- this.s.outbox.clear();
730
+ this.s.heads.clear();
731
+ this.s.subjectUsage.clear();
732
+ this.s.basin = {
733
+ current: this.s.basin.current + 1,
734
+ retired: [...this.s.basin.retired, this.s.basin.current],
735
+ };
736
+ this.recordChanges.push({ kind: "reset", generation: this.s.basin.current });
629
737
  }
630
738
  if (scope === "sandboxes" || scope === "all")
631
739
  this.s.docs.sandboxes.clear();
632
740
  if (scope === "all") {
633
741
  this.s.docs.definitions.clear();
634
- this.s.executors.clear();
742
+ this.s.endpoints.clear();
635
743
  for (const vault of [...this.s.vaults.values()])
636
744
  if (vault.scope !== "host")
637
745
  await this.deleteVault(vault.id);
638
746
  }
639
747
  }
640
748
  }
749
+ /** The time column a signing key's new state stamps. */
750
+ export function signingKeyStamp(state, at) {
751
+ if (state === "current")
752
+ return { activatedAt: at };
753
+ if (state === "previous")
754
+ return { retiredAt: at };
755
+ if (state === "revoked")
756
+ return { revokedAt: at };
757
+ return {};
758
+ }
641
759
  function byCreated(a, b) {
642
760
  if (a.createdAt !== b.createdAt)
643
761
  return a.createdAt < b.createdAt ? -1 : 1;
644
762
  return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
645
763
  }
764
+ const HEALTH_KEYS = [
765
+ "lastDeliveryAt",
766
+ "lastSuccessAt",
767
+ "lastErrorCode",
768
+ "lastErrorMessage",
769
+ "consecutiveFailures",
770
+ "servedImplementationVersion",
771
+ "servedManifestHash",
772
+ ];
773
+ /** The health fields of an endpoint row. */
774
+ function healthOf(row) {
775
+ const health = {};
776
+ for (const key of HEALTH_KEYS)
777
+ if (row[key] !== undefined)
778
+ health[key] = row[key];
779
+ return { ...health, consecutiveFailures: row.consecutiveFailures };
780
+ }
781
+ /** `row` after one health observation (the same rules as the Postgres store). */
782
+ function withHealth(row, update) {
783
+ switch (update.kind) {
784
+ case "success": {
785
+ const { lastErrorCode: _c, lastErrorMessage: _m, ...rest } = row;
786
+ return {
787
+ ...rest,
788
+ lastDeliveryAt: update.at,
789
+ lastSuccessAt: update.at,
790
+ consecutiveFailures: 0,
791
+ };
792
+ }
793
+ case "failure":
794
+ return {
795
+ ...row,
796
+ lastDeliveryAt: update.at,
797
+ lastErrorCode: update.code,
798
+ lastErrorMessage: update.message,
799
+ consecutiveFailures: row.consecutiveFailures + 1,
800
+ };
801
+ case "served": {
802
+ const { servedManifestHash: _h, ...rest } = row;
803
+ return {
804
+ ...rest,
805
+ servedImplementationVersion: update.implementationVersion,
806
+ ...(update.manifestHash === undefined ? {} : { servedManifestHash: update.manifestHash }),
807
+ };
808
+ }
809
+ }
810
+ }
@@ -9,6 +9,16 @@ export interface PostgresClientOptions {
9
9
  connectTimeoutSeconds?: number;
10
10
  /** `application_name` on every connection. Default `nylorun-runtime`. */
11
11
  applicationName?: string;
12
+ /**
13
+ * `statement_timeout`: a statement running or waiting for a lock longer fails. Default
14
+ * 60 s; 0 turns it off. Schema migrations lift it (`lockSchema`).
15
+ */
16
+ statementTimeoutMs?: number;
17
+ /**
18
+ * `idle_in_transaction_session_timeout`: a transaction left idle this long is ended and its
19
+ * connection closed, releasing its locks. Default 60 s; 0 turns it off.
20
+ */
21
+ idleInTransactionTimeoutMs?: number;
12
22
  }
13
23
  /**
14
24
  * Opens a connection pool for Session Stores and the Tenant catalog. One pool
@@ -21,5 +31,8 @@ export interface PostgresClientOptions {
21
31
  * named prepared statements would pile up on every pooled connection with the
22
32
  * number of Tenants a Host serves. Unnamed statements still go in one round
23
33
  * trip; the queries are simple enough that re-planning them costs little.
34
+ *
35
+ * The pool is shared by every Tenant, so a statement or transaction that hangs must not hold
36
+ * a connection forever: both are bounded by default.
24
37
  */
25
38
  export declare function createPostgresClient(url: string, options?: PostgresClientOptions): PostgresClient;
@@ -10,6 +10,9 @@ import postgres, {} from "postgres";
10
10
  * named prepared statements would pile up on every pooled connection with the
11
11
  * number of Tenants a Host serves. Unnamed statements still go in one round
12
12
  * trip; the queries are simple enough that re-planning them costs little.
13
+ *
14
+ * The pool is shared by every Tenant, so a statement or transaction that hangs must not hold
15
+ * a connection forever: both are bounded by default.
13
16
  */
14
17
  export function createPostgresClient(url, options = {}) {
15
18
  return postgres(url, {
@@ -20,6 +23,8 @@ export function createPostgresClient(url, options = {}) {
20
23
  onnotice: () => { },
21
24
  connection: {
22
25
  application_name: options.applicationName ?? "nylorun-runtime",
26
+ statement_timeout: options.statementTimeoutMs ?? 60_000,
27
+ idle_in_transaction_session_timeout: options.idleInTransactionTimeoutMs ?? 60_000,
23
28
  },
24
29
  });
25
30
  }
@@ -0,0 +1,8 @@
1
+ import type { Migration } from "./index.js";
2
+ /**
3
+ * Subject tokens (Host feature `subject-tokens`): the Tenant's signing keys (private halves
4
+ * sealed with the vault KEK, like credentials), each subject's revocation epoch, each
5
+ * subject's turn bucket. Counting a subject's sessions by status uses the owner index of
6
+ * migration 2; a second index on status would be written on every session transition.
7
+ */
8
+ export declare const subjectTokens: Migration;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Subject tokens (Host feature `subject-tokens`): the Tenant's signing keys (private halves
3
+ * sealed with the vault KEK, like credentials), each subject's revocation epoch, each
4
+ * subject's turn bucket. Counting a subject's sessions by status uses the owner index of
5
+ * migration 2; a second index on status would be written on every session transition.
6
+ */
7
+ export const subjectTokens = {
8
+ version: 3,
9
+ name: "subject_tokens",
10
+ up: (s) => `
11
+ CREATE TABLE ${s}.signing_keys (
12
+ id text COLLATE "C" PRIMARY KEY,
13
+ state text NOT NULL CHECK (state IN ('standby', 'current', 'previous', 'revoked')),
14
+ alg text NOT NULL CHECK (alg = 'ES256'),
15
+ public_jwk text NOT NULL,
16
+ kek_id text NOT NULL,
17
+ nonce bytea NOT NULL,
18
+ ciphertext bytea NOT NULL,
19
+ wrapped_dek bytea NOT NULL,
20
+ created_at text NOT NULL,
21
+ activated_at text,
22
+ retired_at text,
23
+ revoked_at text
24
+ );
25
+ CREATE UNIQUE INDEX signing_keys_one_standby ON ${s}.signing_keys (state) WHERE state = 'standby';
26
+ CREATE UNIQUE INDEX signing_keys_one_current ON ${s}.signing_keys (state) WHERE state = 'current';
27
+ CREATE UNIQUE INDEX signing_keys_one_previous ON ${s}.signing_keys (state) WHERE state = 'previous';
28
+
29
+ CREATE TABLE ${s}.subject_epochs (
30
+ subject text COLLATE "C" PRIMARY KEY,
31
+ epoch bigint NOT NULL,
32
+ revoked_at text NOT NULL
33
+ );
34
+
35
+ CREATE TABLE ${s}.subject_usage (
36
+ subject text COLLATE "C" PRIMARY KEY,
37
+ turn_tokens double precision NOT NULL,
38
+ refilled_at text NOT NULL
39
+ );
40
+
41
+ `,
42
+ };
@@ -0,0 +1,6 @@
1
+ import type { Migration } from "./index.js";
2
+ /**
3
+ * Publishable keys (Host feature `browser-access`): public by design, so stored as they are,
4
+ * with the origins a browser may use each from.
5
+ */
6
+ export declare const publishableKeys: Migration;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Publishable keys (Host feature `browser-access`): public by design, so stored as they are,
3
+ * with the origins a browser may use each from.
4
+ */
5
+ export const publishableKeys = {
6
+ version: 4,
7
+ name: "publishable_keys",
8
+ up: (s) => `
9
+ CREATE TABLE ${s}.publishable_keys (
10
+ id text COLLATE "C" PRIMARY KEY,
11
+ key text COLLATE "C" NOT NULL UNIQUE,
12
+ name text NOT NULL UNIQUE,
13
+ origins_json text NOT NULL,
14
+ created_at text NOT NULL,
15
+ revoked_at text
16
+ );
17
+ `,
18
+ };
@@ -0,0 +1,6 @@
1
+ import type { Migration } from "./index.js";
2
+ /**
3
+ * Action endpoints: where the Runtime delivers each agent's Actions over HTTP, with the health
4
+ * recent deliveries report, and the deadline of an Action being delivered.
5
+ */
6
+ export declare const actionEndpoints: Migration;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Action endpoints: where the Runtime delivers each agent's Actions over HTTP, with the health
3
+ * recent deliveries report, and the deadline of an Action being delivered.
4
+ */
5
+ export const actionEndpoints = {
6
+ version: 5,
7
+ name: "action_endpoints",
8
+ up: (s) => `
9
+ CREATE TABLE ${s}.endpoints (
10
+ agent_id text COLLATE "C" PRIMARY KEY,
11
+ url text NOT NULL,
12
+ implementation_version text NOT NULL,
13
+ manifest_hash text,
14
+ timeout_ms integer NOT NULL,
15
+ max_concurrent integer NOT NULL,
16
+ principal_id text,
17
+ last_delivery_at text,
18
+ last_success_at text,
19
+ last_error_code text,
20
+ last_error_message text,
21
+ consecutive_failures integer NOT NULL DEFAULT 0,
22
+ served_implementation_version text,
23
+ served_manifest_hash text,
24
+ created_at text NOT NULL,
25
+ updated_at text NOT NULL
26
+ );
27
+
28
+ ALTER TABLE ${s}.actions
29
+ ADD COLUMN deadline_at text GENERATED ALWAYS AS (${s}.doc(body)->>'deadlineAt') STORED;
30
+ CREATE INDEX actions_deadline ON ${s}.actions (status, deadline_at);
31
+ `,
32
+ };