@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
@@ -9,9 +9,9 @@
9
9
  *
10
10
  * 1. **One transaction per `tx` call, READ COMMITTED or stronger.** Nothing a
11
11
  * transaction wrote is visible to others before it commits, and nothing is
12
- * kept when `fn` throws: document writes, event sequences, outbox rows,
13
- * `afterCommit` callbacks and `signalWork` are all discarded, and `tx`
14
- * rejects with the error `fn` threw.
12
+ * kept when `fn` throws: document writes, event sequences, record rows,
13
+ * and `afterCommit` callbacks are all discarded, and `tx` rejects with the
14
+ * error `fn` threw.
15
15
  * 2. **Session-scoped writes lock the session row first.** `lockSession` takes
16
16
  * a row lock (`SELECT … FOR UPDATE`) held until the transaction ends. Effect
17
17
  * intent and outcome, Action transitions, checkpoint settlement and event
@@ -21,18 +21,17 @@
21
21
  * 3. **Per-session event sequence.** `event` allocates the session's next
22
22
  * sequence under the session lock, starting at 0, without gaps across
23
23
  * committed transactions. The sequence is also the S2 sequence number of the
24
- * event in the session's stream (`sessions/<id>/<incarnation>`,
25
- * `streams/types.ts`), and the cursor is `base64url("<sessionId>:<seq>")`
26
- * (see `store/cursor.ts`).
24
+ * event in the session's stream (`sessions/<id>` in the Tenant's basin
25
+ * generation, `streams/basin.ts`), and the cursor is
26
+ * `base64url("<sessionId>:<seq>")` (see `store/cursor.ts`).
27
27
  * 4. **No external I/O inside `fn`.** No model, tool, MCP, sandbox, Restate or
28
- * S2 call, and no `fetch`, runs inside a transaction. Wakes go through
29
- * `afterCommit`, executor notifications through `signalWork`, and events are
30
- * delivered to commit listeners after commit (seam rule 1 and 2).
28
+ * S2 call, and no `fetch`, runs inside a transaction. Wakes and deliveries go
29
+ * through `afterCommit`, and events are delivered to commit listeners after
30
+ * commit (seam rule 1 and 2).
31
31
  * 5. **No nested transactions.** Calling `store.tx` from inside `fn` rejects.
32
32
  * A `Tx` must not be used after its `tx` call settles.
33
33
  * 6. **Post-commit order.** After a commit, the store first calls every commit
34
- * listener once with the transaction's events (in allocation order) and its
35
- * `signalWork` flag, then runs the `afterCommit` callbacks in registration
34
+ * listener once with the transaction's events (in allocation order), then runs the `afterCommit` callbacks in registration
36
35
  * order and awaits them. Listener and callback failures are reported to the
37
36
  * store's error hook; they never reject `tx`, because the commit stands.
38
37
  * Recovery from a lost post-commit step is the Tenant sweep's job.
@@ -46,14 +45,17 @@
46
45
  * ## Typed queries, no scans
47
46
  *
48
47
  * There is no generic table scan. Every read the Runtime needs is a typed
49
- * method on `Tx` (`sessionsWithStatus`, `expiredClaims`, `pendingActions`,
48
+ * method on `Tx` (`sessionsWithStatus`, `expiredDeliveries`, `pendingActions`,
50
49
  * `effectsForTurn`, `linkedSessions`, `counts`, …) that an implementation can
51
- * back with an index, and principals, executors, vaults and Tenant settings
50
+ * back with an index, and principals, endpoints, vaults and Tenant settings
52
51
  * have their own methods rather than raw SQL outside the store. Session history
53
- * is not read from the store: the relay moves events from the outbox to
54
- * Durable Streams, and history and SSE read them there (`tenant/streams.ts`).
52
+ * is not read from the store: every event is written to the record (Postgres
53
+ * `nylorun_streams.session_events`) in its transaction, the stream relay
54
+ * (`streams/relay/`) feeds Durable Streams from it, and history and SSE read
55
+ * them there (`tenant/session-streams.ts`).
55
56
  */
56
- import type { Action, LiveEvent } from "@nylorun/core/contracts";
57
+ import type { RecordReader } from "../streams/relay/types.js";
58
+ import type { Action, EventPayload, EventType, LiveEvent, SessionEventOf } from "@nylorun/core/contracts";
57
59
  import type { HostEffect } from "@nylorun/harness/run";
58
60
  /** Tables holding JSON documents keyed by `id`. */
59
61
  export type DocTable = "definitions" | "sessions" | "commands" | "checkpoints" | "effects" | "actions" | "sandboxes" | "links";
@@ -87,18 +89,13 @@ export interface SessionDoc {
87
89
  agentId: string;
88
90
  status: SessionStatus | (string & {});
89
91
  activeTurnId: string | null;
90
- /**
91
- * Names the session's event stream (`sessions/<id>/<incarnation>`, `streams/types.ts`).
92
- * Set when the session is created; `event` reports it with each event (`Commit`).
93
- */
94
- streamIncarnation?: string;
95
92
  }
96
93
  /** The fields of an effect document the store indexes (`request.sessionId`, `request.turnId`, `request.kind`, `status`). */
97
94
  export interface EffectDoc {
98
95
  request: HostEffect;
99
96
  status: EffectStatus | (string & {});
100
97
  }
101
- /** Actions are stored as the wire `Action`. Indexed: `sessionId`, `turnId`, `agentId`, `status`, `kind`, `leaseExpiresAt`. */
98
+ /** Actions are stored as the wire `Action`. Indexed: `sessionId`, `turnId`, `agentId`, `status`, `kind`, `deadlineAt`. */
102
99
  export type ActionDoc = Action;
103
100
  /** A workflow → agent session link, keyed by the linked agent session id. Indexed: `workflowSessionId`. */
104
101
  export interface LinkDoc {
@@ -125,42 +122,70 @@ export interface LinkedSession<S extends SessionDoc = SessionDoc> {
125
122
  link: LinkDoc;
126
123
  session: StoredSession<S>;
127
124
  }
128
- /** One committed-but-not-yet-relayed event (§12.4). Deleted once S2 has it. */
129
- export interface OutboxRow {
130
- sessionId: string;
131
- seq: number;
132
- event: LiveEvent;
133
- }
134
- export interface OutboxStats {
135
- /** Unrelayed events. */
136
- depth: number;
137
- /** ISO `createdAt` of the oldest unrelayed event, or null when the outbox is empty. */
138
- oldestCreatedAt: string | null;
139
- }
140
125
  /** What one commit produced, delivered to commit listeners after commit. */
141
126
  export interface Commit {
142
127
  /** Events written by the transaction, in allocation order. */
143
128
  readonly events: readonly LiveEvent[];
144
129
  /**
145
- * The `streamIncarnation` of each event's session when the event was allocated (under the
146
- * session lock), aligned with `events`; null for a session without one. The relay appends
147
- * each event to that incarnation's stream.
130
+ * The basin generation of each event, aligned with `events`: the stream relay appends it to
131
+ * `sessions/<id>` in that generation's basin.
148
132
  */
149
- readonly incarnations: readonly (string | null)[];
150
- /** True when the transaction called `signalWork()`. */
151
- readonly workAvailable: boolean;
133
+ readonly generations: readonly number[];
134
+ }
135
+ /** A Tenant's basin generations (Durable Streams §8.1). */
136
+ export interface BasinGenerations {
137
+ /** The basin session streams are written to and read from. */
138
+ current: number;
139
+ /** Earlier generations whose basins are still to be deleted. */
140
+ retired: number[];
152
141
  }
153
142
  export type CommitListener = (commit: Commit) => void;
154
- /** Executors carry a bearer secret hash, so they use typed columns with a unique token hash. */
155
- export interface ExecutorRow {
143
+ /**
144
+ * An Action endpoint: the URL the Runtime delivers one agent's Actions to, and what recent
145
+ * deliveries and the last ping say about it.
146
+ */
147
+ export interface EndpointRow {
156
148
  agentId: string;
157
- tokenHash: string;
149
+ url: string;
158
150
  implementationVersion: string;
159
151
  manifestHash?: string;
152
+ timeoutMs: number;
153
+ maxConcurrent: number;
154
+ /** Application principal that registered it, when known. */
160
155
  principalId?: string;
156
+ lastDeliveryAt?: string;
157
+ lastSuccessAt?: string;
158
+ lastErrorCode?: string;
159
+ lastErrorMessage?: string;
160
+ consecutiveFailures: number;
161
+ /** What the endpoint reported serving on the last ping. */
162
+ servedImplementationVersion?: string;
163
+ servedManifestHash?: string;
161
164
  createdAt: string;
162
165
  updatedAt: string;
163
166
  }
167
+ /** The registration part of an endpoint, as `putEndpoint` writes it. */
168
+ export type EndpointRegistrationRow = Pick<EndpointRow, "agentId" | "url" | "implementationVersion" | "manifestHash" | "timeoutMs" | "maxConcurrent" | "principalId" | "updatedAt">;
169
+ /** One observation about an endpoint (`recordEndpointHealth`). */
170
+ export type EndpointHealthUpdate =
171
+ /** A delivery was answered. */
172
+ {
173
+ kind: "success";
174
+ at: string;
175
+ }
176
+ /** A delivery failed: not reached, refused, or lost. */
177
+ | {
178
+ kind: "failure";
179
+ at: string;
180
+ code: string;
181
+ message: string;
182
+ }
183
+ /** A ping was answered with what the endpoint serves. */
184
+ | {
185
+ kind: "served";
186
+ implementationVersion: string;
187
+ manifestHash?: string;
188
+ };
164
189
  export interface PrincipalRow {
165
190
  id: string;
166
191
  role: "application" | (string & {});
@@ -184,6 +209,34 @@ export interface SealedSecret {
184
209
  ciphertext: Uint8Array;
185
210
  wrappedDek: Uint8Array;
186
211
  }
212
+ /** A Tenant signing key (subject tokens). The private key is sealed; the public JWK is not. */
213
+ export interface SigningKeyRow extends SealedSecret {
214
+ id: string;
215
+ state: "standby" | "current" | "previous" | "revoked";
216
+ alg: "ES256";
217
+ /** JSON text of the public JWK. */
218
+ publicJwk: string;
219
+ createdAt: string;
220
+ activatedAt: string | null;
221
+ retiredAt: string | null;
222
+ revokedAt: string | null;
223
+ }
224
+ /** A publishable key (Host feature `browser-access`). Public by design. */
225
+ export interface PublishableKeyRow {
226
+ id: string;
227
+ key: string;
228
+ name: string;
229
+ /** JSON array of allowed origins. */
230
+ originsJson: string;
231
+ createdAt: string;
232
+ revokedAt: string | null;
233
+ }
234
+ /** A subject's turn bucket (subject limits): tokens left and when they were last refilled. */
235
+ export interface SubjectUsageRow {
236
+ subject: string;
237
+ turnTokens: number;
238
+ refilledAt: string;
239
+ }
187
240
  export interface VaultCredentialRow extends SealedSecret {
188
241
  id: string;
189
242
  vaultId: string;
@@ -229,7 +282,7 @@ export interface StoreCounts {
229
282
  sessions: number;
230
283
  /** Sessions `running` or `runnable`. */
231
284
  runningSessions: number;
232
- /** Actions `pending` or `claimed`. */
285
+ /** Actions `pending` or `delivering`. */
233
286
  pendingActions: number;
234
287
  /** Effects `uncertain`. */
235
288
  uncertainEffects: number;
@@ -263,10 +316,12 @@ export interface SessionStore {
263
316
  tx<T>(fn: (t: Tx) => Promise<T>): Promise<T>;
264
317
  /**
265
318
  * Registers a listener called once per committed transaction that wrote
266
- * events or signalled work. Returns an unsubscribe function. The outbox
267
- * relay (`streams/relay.ts`) subscribes here.
319
+ * events. Returns an unsubscribe function. A Tenant without the Host's stream
320
+ * relay relays its own commits from here (`tenant/streams.ts`).
268
321
  */
269
322
  onCommit(listener: CommitListener): () => void;
323
+ /** Reads the record back, for the stream relay's refills and reconciliation. */
324
+ record(): RecordReader;
270
325
  /** Reachability and schema check for Tenant status and `/ready`. Never throws. */
271
326
  health(): Promise<StoreHealth>;
272
327
  close(): Promise<void>;
@@ -279,7 +334,7 @@ export interface StoreHealth {
279
334
  /** Options every implementation accepts. */
280
335
  export interface SessionStoreOptions {
281
336
  tenantId: string;
282
- /** Clock for event `createdAt`. Defaults to `() => new Date()`. */
337
+ /** Clock for event `time`. Defaults to `() => new Date()`. */
283
338
  now?: () => Date;
284
339
  /** Receives listener and `afterCommit` failures, which never reject `tx`. */
285
340
  onError?: (error: unknown) => void;
@@ -290,9 +345,9 @@ export interface Tx {
290
345
  /** Insert or replace. For `sessions`, ownership fields in `body` are ignored. */
291
346
  put(table: DocTable, id: string, body: unknown): Promise<void>;
292
347
  /**
293
- * Deletes a document. Deleting a session also deletes its outbox rows, so a
294
- * session created again with the same id starts at sequence 0 without
295
- * colliding with rows the relay never took (its stream is a new incarnation).
348
+ * Deletes a document. Deleting a session keeps its record rows and log head
349
+ * (per-session record deletion is deferred, Durable Streams §15), so a session
350
+ * created again with the same id continues its log and stream.
296
351
  */
297
352
  delete(table: DocTable, id: string): Promise<void>;
298
353
  /**
@@ -313,14 +368,15 @@ export interface Tx {
313
368
  lockSession<T extends SessionDoc = SessionDoc>(id: string): Promise<StoredSession<T> | undefined>;
314
369
  /**
315
370
  * Allocates the session's next sequence (from 0) under the session lock,
316
- * writes the event to the outbox and buffers it for commit listeners.
317
- * Rejects when the session does not exist. The returned cursor is final.
371
+ * builds the event on the `nylorun.event/2` envelope, checks it against the
372
+ * event catalog (`InvalidEventError` when it does not match), writes it to
373
+ * the record in the Tenant's current basin generation and buffers it for
374
+ * commit listeners. Rejects when the session does not exist. The returned
375
+ * cursor is final.
318
376
  */
319
- event(sessionId: string, turnId: string | null, type: string, payload: unknown): Promise<LiveEvent>;
377
+ event<T extends EventType>(sessionId: string, turnId: string | null, type: T, payload: EventPayload<T>): Promise<SessionEventOf<T>>;
320
378
  /** Runs `fn` after a successful commit (never on rollback). Used for wakes. */
321
379
  afterCommit(fn: () => void | Promise<void>): void;
322
- /** Signals `work_available` to executors after a successful commit. */
323
- signalWork(): void;
324
380
  /**
325
381
  * Locks the session and takes ownership when it has no owner or the owner's
326
382
  * lease ended at or before `now`; then increments `epoch` and sets the lease
@@ -354,10 +410,14 @@ export interface Tx {
354
410
  }): Promise<StoredSession<T>[]>;
355
411
  listDefinitions<T extends DefinitionDoc = DefinitionDoc>(): Promise<T[]>;
356
412
  listSandboxes<T extends SandboxDoc = SandboxDoc>(): Promise<T[]>;
357
- /** Claimed actions whose lease ended at or before `now`, earliest lease first. */
358
- expiredClaims(now: Date, limit: number): Promise<ActionDoc[]>;
359
- /** `pending` actions offered to one agent's executor. */
413
+ /** One agent's `pending` actions, delivered when its endpoint is registered. */
360
414
  pendingActions(agentId: string): Promise<ActionDoc[]>;
415
+ /** How many of one agent's actions are `delivering` (Action endpoints). */
416
+ deliveringCount(agentId: string): Promise<number>;
417
+ /** `pending` actions of agents that have an Action endpoint, by id. */
418
+ pendingActionsWithEndpoint(limit: number): Promise<ActionDoc[]>;
419
+ /** `delivering` actions whose deadline is at or before `now`, earliest first. */
420
+ expiredDeliveries(now: Date, limit: number): Promise<ActionDoc[]>;
361
421
  actionsForSession(sessionId: string, filter?: SessionActionFilter): Promise<ActionDoc[]>;
362
422
  actionsWithStatus(statuses: readonly ActionStatus[], filter?: {
363
423
  kinds?: readonly ActionKind[];
@@ -372,27 +432,19 @@ export interface Tx {
372
432
  /** Links of one workflow session joined to their existing agent sessions, ordered by agent session id. */
373
433
  linkedSessions<S extends SessionDoc = SessionDoc>(workflowSessionId: string): Promise<LinkedSession<S>[]>;
374
434
  counts(): Promise<StoreCounts>;
375
- /** Unrelayed events, ordered by session id then sequence. */
376
- outbox(limit: number, filter?: {
377
- sessionId?: string;
378
- }): Promise<OutboxRow[]>;
435
+ basinGenerations(): Promise<BasinGenerations>;
436
+ /** Forgets a retired generation once its basin is deleted. */
437
+ forgetRetiredGeneration(generation: number): Promise<void>;
438
+ listEndpoints(): Promise<EndpointRow[]>;
439
+ getEndpoint(agentId: string): Promise<EndpointRow | undefined>;
379
440
  /**
380
- * Deletes a session's outbox rows with `seq <= throughSeq`. Returns the number deleted.
381
- * With `incarnation`, deletes only while the session exists with that `streamIncarnation`
382
- * (null: none), in one statement: a relay that appended to an abandoned incarnation's
383
- * stream never deletes the rows of a session created again with the same id.
441
+ * Inserts or updates by `agentId`, keeping `createdAt`. Health is kept, except that a new
442
+ * `url` starts with none.
384
443
  */
385
- deleteOutbox(sessionId: string, throughSeq: number, incarnation?: string | null): Promise<number>;
386
- /** How many events are unrelayed, and the `createdAt` of the oldest (Tenant status). */
387
- outboxStats(): Promise<OutboxStats>;
388
- listExecutors(): Promise<ExecutorRow[]>;
389
- getExecutor(agentId: string): Promise<ExecutorRow | undefined>;
390
- /**
391
- * Inserts or updates by `agentId`, keeping `createdAt` on update. Rejects
392
- * when another agent already uses `tokenHash`.
393
- */
394
- putExecutor(row: Omit<ExecutorRow, "createdAt">): Promise<void>;
395
- deleteExecutor(agentId: string): Promise<void>;
444
+ putEndpoint(row: EndpointRegistrationRow): Promise<void>;
445
+ deleteEndpoint(agentId: string): Promise<void>;
446
+ /** Records one observation. Nothing happens when the endpoint does not exist. */
447
+ recordEndpointHealth(agentId: string, update: EndpointHealthUpdate): Promise<void>;
396
448
  /** Rejects when the id or token hash already exists. */
397
449
  insertPrincipal(row: PrincipalRow): Promise<void>;
398
450
  principalByTokenHash(tokenHash: string): Promise<PrincipalRow | undefined>;
@@ -428,15 +480,58 @@ export interface Tx {
428
480
  getVaultIdempotency(id: string): Promise<VaultIdempotencyRow | undefined>;
429
481
  /** Rejects when the id exists. */
430
482
  insertVaultIdempotency(row: VaultIdempotencyRow): Promise<void>;
483
+ /**
484
+ * Serializes signing key changes: held until the transaction ends. Take it before reading
485
+ * the keys a change depends on, so concurrent first uses or rotations do not collide.
486
+ */
487
+ lockSigningKeys(): Promise<void>;
488
+ /** Rejects on a duplicate id, or a second key in `standby`, `current` or `previous`. */
489
+ insertSigningKey(row: SigningKeyRow): Promise<void>;
490
+ signingKey(id: string): Promise<SigningKeyRow | undefined>;
491
+ /** Keys in the given states (all when omitted), ordered by `createdAt`, then id. */
492
+ signingKeys(states?: readonly SigningKeyRow["state"][]): Promise<SigningKeyRow[]>;
493
+ /**
494
+ * Moves a key from `from` to `to` and stamps the matching time (`activatedAt` for
495
+ * `current`, `retiredAt` for `previous`, `revokedAt` for `revoked`). Returns false when the
496
+ * key is not in `from`.
497
+ */
498
+ setSigningKeyState(id: string, from: SigningKeyRow["state"], to: SigningKeyRow["state"], at: string): Promise<boolean>;
499
+ countSigningKeys(): Promise<number>;
500
+ /** The subject's revocation epoch; 0 when it was never revoked. */
501
+ subjectEpoch(subject: string): Promise<number>;
502
+ /** The epochs of several subjects; subjects never revoked are absent. */
503
+ subjectEpochs(subjects: readonly string[]): Promise<Map<string, number>>;
504
+ /** Adds one to the subject's epoch and returns the new value. */
505
+ bumpSubjectEpoch(subject: string, at: string): Promise<number>;
506
+ /**
507
+ * The subject's turn bucket, created from `initial` when missing, locked until the
508
+ * transaction ends so concurrent commands of one subject serialize.
509
+ */
510
+ lockSubjectUsage(initial: SubjectUsageRow): Promise<SubjectUsageRow>;
511
+ putSubjectUsage(row: SubjectUsageRow): Promise<void>;
512
+ /** How many sessions of `ownerUserId` are in one of `statuses`. */
513
+ countOwnerSessions(ownerUserId: string, statuses: readonly string[]): Promise<number>;
514
+ /** Rejects on a duplicate id, key or name. */
515
+ insertPublishableKey(row: PublishableKeyRow): Promise<void>;
516
+ publishableKeyByKey(key: string): Promise<PublishableKeyRow | undefined>;
517
+ publishableKey(id: string): Promise<PublishableKeyRow | undefined>;
518
+ /** All keys, revoked ones included, ordered by `createdAt`, then id. */
519
+ publishableKeys(): Promise<PublishableKeyRow[]>;
520
+ /** Returns false when the key does not exist. */
521
+ updatePublishableKey(id: string, patch: Partial<Pick<PublishableKeyRow, "originsJson" | "revokedAt">>): Promise<boolean>;
431
522
  getSetting(key: string): Promise<string | undefined>;
432
523
  putSetting(key: string, value: string): Promise<void>;
433
524
  /**
434
525
  * Deletes Tenant state by scope, in this transaction:
435
- * - `sessions`: sessions, commands, checkpoints, effects, actions, links and the outbox;
526
+ * - `sessions`: sessions, commands, checkpoints, effects, actions, links, subject turn
527
+ * buckets and the Tenant's record rows and log heads. The Tenant moves to the next basin
528
+ * generation and the current one is retired, so session ids it frees start again in an
529
+ * empty basin;
436
530
  * - `sandboxes`: sandbox records;
437
- * - `all`: both, plus definitions, executors and user vaults with their
438
- * credentials. The host vault, principals, settings, audit and vault
439
- * idempotency rows stay.
531
+ * - `all`: both, plus definitions, Action endpoints and user vaults with their
532
+ * credentials. The host vault, principals, signing keys, subject epochs, publishable
533
+ * keys, settings,
534
+ * audit and vault idempotency rows stay.
440
535
  */
441
536
  reset(scope: ResetScope): Promise<void>;
442
537
  }
@@ -20,4 +20,21 @@
20
20
  */
21
21
  export declare const MAX_BASIN_PREFIX_LENGTH = 16;
22
22
  export declare function validateBasinPrefix(prefix: string): string;
23
- export declare function tenantBasinName(tenantId: string, prefix?: string): string;
23
+ /**
24
+ * The basin of one generation of a Tenant (Durable Streams §8.1). A Tenant reset moves the
25
+ * Tenant to a new generation, so a session id it frees starts again in an empty basin.
26
+ * `DurableStreams` methods take this key where they take a Tenant id; generation 0 is the
27
+ * Tenant id itself, so existing basins keep their names.
28
+ */
29
+ export declare function basinOf(tenantId: string, generation: number): string;
30
+ /** The Tenant id and generation of a basin key from `basinOf`. */
31
+ export declare function parseBasin(basin: string): {
32
+ tenantId: string;
33
+ generation: number;
34
+ };
35
+ /**
36
+ * The S2 basin of a Tenant, or of one of its generations (`basinOf`). Generation `g` > 0 of
37
+ * a canonical Tenant appends `-<g mod 1296 in base36>`, at most 48 characters with a
38
+ * 16-character prefix; other ids fold the generation into their hash.
39
+ */
40
+ export declare function tenantBasinName(basin: string, prefix?: string): string;
@@ -28,13 +28,42 @@ export function validateBasinPrefix(prefix) {
28
28
  throw new Error(`S2 basin prefix must be at most ${MAX_BASIN_PREFIX_LENGTH} lowercase letters, digits or hyphens, starting with a letter or digit`);
29
29
  return prefix;
30
30
  }
31
- export function tenantBasinName(tenantId, prefix = "") {
31
+ /**
32
+ * The basin of one generation of a Tenant (Durable Streams §8.1). A Tenant reset moves the
33
+ * Tenant to a new generation, so a session id it frees starts again in an empty basin.
34
+ * `DurableStreams` methods take this key where they take a Tenant id; generation 0 is the
35
+ * Tenant id itself, so existing basins keep their names.
36
+ */
37
+ export function basinOf(tenantId, generation) {
38
+ if (!Number.isInteger(generation) || generation < 0)
39
+ throw new Error("A basin generation is a non-negative integer");
40
+ return generation === 0 ? tenantId : `${tenantId}${GENERATION_SEPARATOR}${generation}`;
41
+ }
42
+ /** The Tenant id and generation of a basin key from `basinOf`. */
43
+ export function parseBasin(basin) {
44
+ const at = basin.lastIndexOf(GENERATION_SEPARATOR);
45
+ const generation = at < 0 ? NaN : Number(basin.slice(at + 1));
46
+ return Number.isInteger(generation) && generation > 0
47
+ ? { tenantId: basin.slice(0, at), generation }
48
+ : { tenantId: basin, generation: 0 };
49
+ }
50
+ /** Never in a Tenant id (`TENANT_ID_PATTERN`). */
51
+ const GENERATION_SEPARATOR = "#";
52
+ /**
53
+ * The S2 basin of a Tenant, or of one of its generations (`basinOf`). Generation `g` > 0 of
54
+ * a canonical Tenant appends `-<g mod 1296 in base36>`, at most 48 characters with a
55
+ * 16-character prefix; other ids fold the generation into their hash.
56
+ */
57
+ export function tenantBasinName(basin, prefix = "") {
32
58
  validateBasinPrefix(prefix);
59
+ const { tenantId, generation } = parseBasin(basin);
33
60
  if (!tenantId)
34
61
  throw new Error("tenantId is required");
35
62
  let name;
36
63
  if (TENANT_ID_PATTERN.test(tenantId)) {
37
64
  name = `${prefix}tn-${tenantId.slice(3)}`;
65
+ if (generation > 0)
66
+ name += `-${(generation % 1296).toString(36)}`;
38
67
  }
39
68
  else {
40
69
  const slug = tenantId
@@ -42,7 +71,10 @@ export function tenantBasinName(tenantId, prefix = "") {
42
71
  .replace(/[^a-z0-9]+/g, "-")
43
72
  .slice(0, 12)
44
73
  .replace(/^-+|-+$/g, "");
45
- const hash = createHash("sha256").update(tenantId).digest("hex").slice(0, 16);
74
+ const hash = createHash("sha256")
75
+ .update(generation > 0 ? basin : tenantId)
76
+ .digest("hex")
77
+ .slice(0, 16);
46
78
  name = `${prefix}x-${slug ? `${slug}-` : ""}${hash}`;
47
79
  }
48
80
  if (!BASIN_NAME.test(name))
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Signals on a basin's `tenant/control` stream (Durable Streams §8). They are appended
3
+ * directly, never recorded: a lost signal costs latency, never correctness (the epoch fence
4
+ * stops a cancelled turn's effects; readers also check their sessions periodically).
5
+ */
6
+ import { type DurableStreams } from "./types.js";
7
+ /** Appends a `session.cancel` signal: the process running the session's advance aborts it. */
8
+ export declare function signalCancel(streams: DurableStreams, basin: string, sessionId: string): Promise<void>;
9
+ /**
10
+ * Appends a `sessions.reset` signal to the old generation's basin: every process with the
11
+ * Tenant open moves its readers to `generation` and ends the streams of deleted sessions.
12
+ */
13
+ export declare function signalSessionsReset(streams: DurableStreams, basin: string, generation: number): Promise<void>;
14
+ /**
15
+ * Appends a `subject.revoked` signal: every process with the Tenant open ends the subject's
16
+ * streams opened with a token older than `epoch`.
17
+ */
18
+ export declare function signalSubjectRevoked(streams: DurableStreams, basin: string, subject: string, epoch: number): Promise<void>;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Signals on a basin's `tenant/control` stream (Durable Streams §8). They are appended
3
+ * directly, never recorded: a lost signal costs latency, never correctness (the epoch fence
4
+ * stops a cancelled turn's effects; readers also check their sessions periodically).
5
+ */
6
+ import { CONTROL_STREAM } from "./types.js";
7
+ /** Appends a `session.cancel` signal: the process running the session's advance aborts it. */
8
+ export async function signalCancel(streams, basin, sessionId) {
9
+ if (!sessionId)
10
+ throw new Error("sessionId is required");
11
+ const signal = { type: "session.cancel", sessionId };
12
+ await streams.append(basin, CONTROL_STREAM, [signal]);
13
+ }
14
+ /**
15
+ * Appends a `sessions.reset` signal to the old generation's basin: every process with the
16
+ * Tenant open moves its readers to `generation` and ends the streams of deleted sessions.
17
+ */
18
+ export async function signalSessionsReset(streams, basin, generation) {
19
+ const signal = { type: "sessions.reset", generation };
20
+ await streams.append(basin, CONTROL_STREAM, [signal]);
21
+ }
22
+ /**
23
+ * Appends a `subject.revoked` signal: every process with the Tenant open ends the subject's
24
+ * streams opened with a token older than `epoch`.
25
+ */
26
+ export async function signalSubjectRevoked(streams, basin, subject, epoch) {
27
+ const signal = { type: "subject.revoked", subject, epoch };
28
+ await streams.append(basin, CONTROL_STREAM, [signal]);
29
+ }
@@ -14,7 +14,9 @@ export declare class MemoryStreams implements DurableStreams {
14
14
  read<T = unknown>(tenantId: string, stream: string, fromSeq: number, options?: ReadOptions): AsyncIterable<StreamRecord<T>>;
15
15
  tail(tenantId: string, stream: string): Promise<number>;
16
16
  ensureTenant(tenantId: string): Promise<void>;
17
- deleteTenant(tenantId: string): Promise<void>;
17
+ deleteTenant(tenantId: string, options?: {
18
+ allGenerations?: boolean;
19
+ }): Promise<void>;
18
20
  deleteStream(tenantId: string, stream: string): Promise<void>;
19
21
  listStreams(tenantId: string, prefix: string): Promise<string[]>;
20
22
  close(): Promise<void>;
@@ -1,3 +1,4 @@
1
+ import { parseBasin } from "./basin.js";
1
2
  /**
2
3
  * In-memory `DurableStreams` for unit tests and a local development Host.
3
4
  * Not durable: history does not survive a restart. Keeps the seam's
@@ -71,14 +72,19 @@ export class MemoryStreams {
71
72
  if (!this.tenants.has(tenantId))
72
73
  this.tenants.set(tenantId, new Map());
73
74
  }
74
- async deleteTenant(tenantId) {
75
- const streams = this.tenants.get(tenantId);
76
- this.tenants.delete(tenantId);
77
- for (const stream of streams?.keys() ?? [])
78
- this.notify(tenantId, stream);
79
- for (const key of this.waiters.keys())
80
- if (key.startsWith(`${tenantId}\u0000`))
81
- this.notifyKey(key);
75
+ async deleteTenant(tenantId, options = {}) {
76
+ const basins = options.allGenerations
77
+ ? [...this.tenants.keys()].filter((key) => parseBasin(key).tenantId === tenantId)
78
+ : [tenantId];
79
+ for (const basin of basins) {
80
+ const streams = this.tenants.get(basin);
81
+ this.tenants.delete(basin);
82
+ for (const stream of streams?.keys() ?? [])
83
+ this.notify(basin, stream);
84
+ for (const key of this.waiters.keys())
85
+ if (key.startsWith(`${basin}\u0000`))
86
+ this.notifyKey(key);
87
+ }
82
88
  }
83
89
  async deleteStream(tenantId, stream) {
84
90
  this.tenants.get(tenantId)?.delete(stream);
@@ -0,0 +1,35 @@
1
+ import { type DurableStreams } from "../types.js";
2
+ import type { ChangeSource, RecordReader } from "./types.js";
3
+ export interface StreamRelayOptions {
4
+ source: ChangeSource;
5
+ record: RecordReader;
6
+ streams: DurableStreams;
7
+ /** Relay events: lag, errors, reconciliations. */
8
+ log?: (message: string, fields?: Record<string, unknown>) => void;
9
+ /** Records per append (S2 allows 1000). */
10
+ maxBatchRecords?: number;
11
+ /** Bytes per append (S2 allows 1 MiB). */
12
+ maxBatchBytes?: number;
13
+ /** Sessions reconciled at once. */
14
+ reconcileConcurrency?: number;
15
+ }
16
+ export interface StreamRelayStatus {
17
+ /** This process holds the source (the replication slot). */
18
+ active: boolean;
19
+ /** Committed transactions not yet fully in S2. */
20
+ pendingTxs: number;
21
+ /** Rows queued for S2. */
22
+ pendingRows: number;
23
+ /** The last position acknowledged to the source. */
24
+ confirmed: string | null;
25
+ reconciliations: number;
26
+ lastError: string | null;
27
+ }
28
+ export interface StreamRelay {
29
+ start(): void;
30
+ stop(): Promise<void>;
31
+ status(): StreamRelayStatus;
32
+ /** Resolves once nothing is pending (tests and shutdown). */
33
+ idle(): Promise<void>;
34
+ }
35
+ export declare function createStreamRelay(options: StreamRelayOptions): StreamRelay;