@nylorun/runtime 0.13.0-beta → 0.15.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 (309) hide show
  1. package/CHANGELOG.md +434 -0
  2. package/README.md +137 -41
  3. package/dist/adapters/execution/restate.d.ts +7 -2
  4. package/dist/adapters/execution/restate.js +40 -10
  5. package/dist/adapters/replication/pgoutput.d.ts +21 -0
  6. package/dist/adapters/replication/pgoutput.js +170 -0
  7. package/dist/adapters/streams/s2.js +37 -20
  8. package/dist/admin-openapi.json +570 -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 +8065 -0
  32. package/dist/api/components.js +82 -0
  33. package/dist/api/gate/keys.d.ts +14 -0
  34. package/dist/api/gate/keys.js +59 -0
  35. package/dist/api/gate/routes.d.ts +34 -0
  36. package/dist/api/gate/routes.js +235 -0
  37. package/dist/api/http/app.d.ts +18 -0
  38. package/dist/api/http/app.js +58 -0
  39. package/dist/api/http/body.d.ts +4 -0
  40. package/dist/api/http/body.js +37 -0
  41. package/dist/api/http/define.d.ts +39 -0
  42. package/dist/api/http/define.js +122 -0
  43. package/dist/api/http/respond.d.ts +9 -0
  44. package/dist/api/http/respond.js +44 -0
  45. package/dist/api/http/routes/access.d.ts +24 -0
  46. package/dist/api/http/routes/access.js +266 -0
  47. package/dist/api/http/routes/actions.d.ts +8 -0
  48. package/dist/api/http/routes/actions.js +76 -0
  49. package/dist/api/http/routes/endpoints.d.ts +7 -0
  50. package/dist/api/http/routes/endpoints.js +100 -0
  51. package/dist/api/http/routes/sessions.d.ts +3 -0
  52. package/dist/api/http/routes/sessions.js +205 -0
  53. package/dist/api/http/routes/tenant.d.ts +11 -0
  54. package/dist/api/http/routes/tenant.js +244 -0
  55. package/dist/api/http/routes/vaults.d.ts +8 -0
  56. package/dist/api/http/routes/vaults.js +167 -0
  57. package/dist/api/openapi.d.ts +19 -0
  58. package/dist/api/openapi.js +137 -0
  59. package/dist/api/route.d.ts +12 -0
  60. package/dist/api/route.js +4 -0
  61. package/dist/core/bearer.d.ts +8 -0
  62. package/dist/core/bearer.js +11 -0
  63. package/dist/core/flow-host.d.ts +7 -15
  64. package/dist/core/flow-host.js +16 -41
  65. package/dist/core/sandbox-routes.d.ts +12 -7
  66. package/dist/core/sandbox-routes.js +24 -25
  67. package/dist/execution/memory.d.ts +8 -5
  68. package/dist/execution/memory.js +33 -13
  69. package/dist/execution/types.d.ts +38 -6
  70. package/dist/execution/types.js +12 -3
  71. package/dist/gates/contract.d.ts +51 -0
  72. package/dist/gates/contract.js +37 -0
  73. package/dist/gates/handler.d.ts +21 -0
  74. package/dist/gates/handler.js +55 -0
  75. package/dist/gates/http-client.d.ts +12 -0
  76. package/dist/gates/http-client.js +160 -0
  77. package/dist/gates/in-process.d.ts +32 -0
  78. package/dist/gates/in-process.js +42 -0
  79. package/dist/gates/inflight.d.ts +39 -0
  80. package/dist/gates/inflight.js +70 -0
  81. package/dist/gates/mcp-handler.d.ts +35 -0
  82. package/dist/gates/mcp-handler.js +142 -0
  83. package/dist/gates/meter.d.ts +24 -0
  84. package/dist/gates/meter.js +165 -0
  85. package/dist/gates/model-gate.d.ts +45 -0
  86. package/dist/gates/model-gate.js +1 -0
  87. package/dist/gates/tenant-vaults.d.ts +64 -0
  88. package/dist/gates/tenant-vaults.js +94 -0
  89. package/dist/gates/tool-calls.d.ts +51 -0
  90. package/dist/gates/tool-calls.js +64 -0
  91. package/dist/gates/tool-client.d.ts +12 -0
  92. package/dist/gates/tool-client.js +201 -0
  93. package/dist/gates/tool-contract.d.ts +116 -0
  94. package/dist/gates/tool-contract.js +64 -0
  95. package/dist/gates/tool-gate.d.ts +45 -0
  96. package/dist/gates/tool-gate.js +10 -0
  97. package/dist/host/admin-api.d.ts +23 -0
  98. package/dist/host/admin-api.js +74 -0
  99. package/dist/host/app.d.ts +34 -0
  100. package/dist/host/app.js +181 -0
  101. package/dist/host/config-for.d.ts +3 -1
  102. package/dist/host/config-for.js +3 -2
  103. package/dist/host/config.d.ts +11 -0
  104. package/dist/host/cors.d.ts +27 -0
  105. package/dist/host/cors.js +64 -0
  106. package/dist/host/create-host.d.ts +36 -8
  107. package/dist/host/create-host.js +161 -330
  108. package/dist/host/execution.d.ts +20 -18
  109. package/dist/host/execution.js +2 -2
  110. package/dist/host/gates.d.ts +58 -0
  111. package/dist/host/gates.js +127 -0
  112. package/dist/host/http.d.ts +20 -4
  113. package/dist/host/http.js +81 -23
  114. package/dist/host/index.d.ts +1 -1
  115. package/dist/host/main.js +189 -58
  116. package/dist/host/stack-config.d.ts +91 -19
  117. package/dist/host/stack-config.js +246 -17
  118. package/dist/infra/database.d.ts +2 -2
  119. package/dist/infra/execution.d.ts +5 -5
  120. package/dist/infra/execution.js +8 -6
  121. package/dist/infra/index.d.ts +1 -1
  122. package/dist/infra/workers.d.ts +17 -17
  123. package/dist/infra/workers.js +9 -9
  124. package/dist/keys/client.d.ts +9 -0
  125. package/dist/keys/client.js +70 -0
  126. package/dist/keys/contract.d.ts +24 -0
  127. package/dist/keys/contract.js +12 -0
  128. package/dist/keys/keys.d.ts +41 -0
  129. package/dist/keys/keys.js +50 -0
  130. package/dist/mcp/connect.d.ts +43 -3
  131. package/dist/mcp/connect.js +12 -5
  132. package/dist/mcp/pool.d.ts +39 -3
  133. package/dist/mcp/pool.js +74 -11
  134. package/dist/model/catalog.d.ts +2 -10
  135. package/dist/model/classify.d.ts +13 -0
  136. package/dist/model/classify.js +61 -0
  137. package/dist/model/models.d.ts +10 -1
  138. package/dist/model/models.js +14 -6
  139. package/dist/model/pi-model.d.ts +17 -2
  140. package/dist/model/pi-model.js +366 -222
  141. package/dist/openapi.json +25889 -0
  142. package/dist/record/append.d.ts +51 -0
  143. package/dist/record/append.js +20 -0
  144. package/dist/record/envelope.d.ts +19 -0
  145. package/dist/record/envelope.js +49 -0
  146. package/dist/record/index.d.ts +11 -0
  147. package/dist/record/index.js +11 -0
  148. package/dist/sandbox/manager.d.ts +13 -1
  149. package/dist/sandbox/manager.js +8 -11
  150. package/dist/sandbox/resolve.d.ts +33 -0
  151. package/dist/sandbox/resolve.js +81 -0
  152. package/dist/sandbox/session-sandbox.d.ts +11 -0
  153. package/dist/sandbox/session-sandbox.js +51 -0
  154. package/dist/sandbox/share.d.ts +14 -2
  155. package/dist/sandbox/share.js +33 -3
  156. package/dist/sandbox/tenant-config.d.ts +37 -0
  157. package/dist/sandbox/tenant-config.js +51 -0
  158. package/dist/sandbox/tools.d.ts +7 -9
  159. package/dist/sandbox/tools.js +11 -3
  160. package/dist/sandbox/types.d.ts +8 -2
  161. package/dist/store/postgres/connect.d.ts +25 -9
  162. package/dist/store/postgres/connect.js +27 -10
  163. package/dist/store/postgres/db.d.ts +16 -0
  164. package/dist/store/postgres/db.js +28 -0
  165. package/dist/store/postgres/drizzle/0000_foundation.sql +22 -0
  166. package/dist/store/postgres/drizzle/0001_baseline.sql +255 -0
  167. package/dist/store/postgres/drizzle/0002_stream_relay.sql +4 -0
  168. package/dist/store/postgres/drizzle/0003_tool_crossings.sql +9 -0
  169. package/dist/store/postgres/drizzle/meta/_journal.json +34 -0
  170. package/dist/store/postgres/migrate.d.ts +68 -0
  171. package/dist/store/postgres/migrate.js +183 -0
  172. package/dist/store/postgres/record-writer.d.ts +4 -0
  173. package/dist/store/postgres/record-writer.js +34 -0
  174. package/dist/store/postgres/record.d.ts +5 -0
  175. package/dist/store/postgres/record.js +60 -0
  176. package/dist/store/postgres/schema.d.ts +3096 -0
  177. package/dist/store/postgres/schema.js +363 -0
  178. package/dist/store/postgres/store.d.ts +2 -4
  179. package/dist/store/postgres/store.js +590 -434
  180. package/dist/store/postgres/tenant.d.ts +56 -0
  181. package/dist/store/postgres/tenant.js +154 -0
  182. package/dist/store/types.d.ts +183 -132
  183. package/dist/store/types.js +0 -1
  184. package/dist/streams/basin.d.ts +18 -1
  185. package/dist/streams/basin.js +34 -2
  186. package/dist/streams/control.d.ts +18 -0
  187. package/dist/streams/control.js +33 -0
  188. package/dist/streams/memory.d.ts +3 -1
  189. package/dist/streams/memory.js +14 -8
  190. package/dist/streams/relay/core.d.ts +35 -0
  191. package/dist/streams/relay/core.js +366 -0
  192. package/dist/streams/relay/types.d.ts +78 -0
  193. package/dist/streams/relay/types.js +8 -0
  194. package/dist/streams/types.d.ts +71 -36
  195. package/dist/streams/types.js +18 -53
  196. package/dist/tenant/access-policy.d.ts +30 -0
  197. package/dist/tenant/access-policy.js +52 -0
  198. package/dist/tenant/actions.d.ts +10 -149
  199. package/dist/tenant/actions.js +5 -167
  200. package/dist/tenant/advance.d.ts +7 -2
  201. package/dist/tenant/advance.js +125 -14
  202. package/dist/tenant/auth.d.ts +19 -17
  203. package/dist/tenant/auth.js +90 -93
  204. package/dist/tenant/browser.d.ts +15 -0
  205. package/dist/tenant/browser.js +39 -0
  206. package/dist/tenant/cause.d.ts +25 -0
  207. package/dist/tenant/cause.js +45 -0
  208. package/dist/tenant/commands.d.ts +18 -2
  209. package/dist/tenant/commands.js +111 -115
  210. package/dist/tenant/context.d.ts +101 -21
  211. package/dist/tenant/context.js +14 -10
  212. package/dist/tenant/delivery-token.d.ts +41 -0
  213. package/dist/tenant/delivery-token.js +104 -0
  214. package/dist/tenant/delivery.d.ts +62 -0
  215. package/dist/tenant/delivery.js +481 -0
  216. package/dist/tenant/effects.d.ts +20 -1
  217. package/dist/tenant/effects.js +159 -42
  218. package/dist/tenant/endpoints.d.ts +67 -0
  219. package/dist/tenant/endpoints.js +90 -0
  220. package/dist/tenant/ephemeral.d.ts +22 -7
  221. package/dist/tenant/ephemeral.js +83 -125
  222. package/dist/tenant/history.d.ts +64 -0
  223. package/dist/tenant/history.js +120 -0
  224. package/dist/tenant/http.d.ts +8 -9
  225. package/dist/tenant/http.js +13 -27
  226. package/dist/tenant/jwt.d.ts +16 -0
  227. package/dist/tenant/jwt.js +50 -0
  228. package/dist/tenant/model-setting.d.ts +2 -2
  229. package/dist/tenant/module.d.ts +17 -28
  230. package/dist/tenant/module.js +133 -279
  231. package/dist/tenant/outbound.d.ts +44 -0
  232. package/dist/tenant/outbound.js +127 -0
  233. package/dist/tenant/paths.d.ts +11 -16
  234. package/dist/tenant/paths.js +24 -68
  235. package/dist/tenant/pool.d.ts +1 -3
  236. package/dist/tenant/pool.js +1 -19
  237. package/dist/tenant/principals.d.ts +23 -11
  238. package/dist/tenant/principals.js +61 -49
  239. package/dist/tenant/reset.d.ts +0 -4
  240. package/dist/tenant/reset.js +0 -5
  241. package/dist/tenant/runtime.d.ts +28 -20
  242. package/dist/tenant/runtime.js +95 -64
  243. package/dist/tenant/scheduler.d.ts +10 -3
  244. package/dist/tenant/scheduler.js +9 -5
  245. package/dist/tenant/session-streams.d.ts +127 -0
  246. package/dist/tenant/session-streams.js +404 -0
  247. package/dist/tenant/sessions.d.ts +28 -4
  248. package/dist/tenant/sessions.js +118 -24
  249. package/dist/tenant/signing-keys.d.ts +55 -0
  250. package/dist/tenant/signing-keys.js +172 -0
  251. package/dist/tenant/slim.d.ts +8 -0
  252. package/dist/tenant/slim.js +21 -0
  253. package/dist/tenant/status.d.ts +3 -3
  254. package/dist/tenant/status.js +20 -13
  255. package/dist/tenant/store-pg.d.ts +9 -6
  256. package/dist/tenant/store-pg.js +60 -125
  257. package/dist/tenant/streams.d.ts +62 -85
  258. package/dist/tenant/streams.js +143 -226
  259. package/dist/tenant/subject-limits.d.ts +16 -0
  260. package/dist/tenant/subject-limits.js +45 -0
  261. package/dist/tenant/sweep.d.ts +1 -10
  262. package/dist/tenant/sweep.js +29 -69
  263. package/dist/tenant/tokens.d.ts +14 -0
  264. package/dist/tenant/tokens.js +180 -0
  265. package/dist/tenant/transcript.d.ts +48 -5
  266. package/dist/tenant/transcript.js +58 -1
  267. package/dist/tenant/types.d.ts +69 -98
  268. package/dist/tenant/types.js +4 -15
  269. package/dist/tenant/worker.d.ts +3 -1
  270. package/dist/tenant/worker.js +13 -7
  271. package/dist/vault/host-model.d.ts +31 -0
  272. package/dist/vault/host-model.js +57 -0
  273. package/dist/vault/service.d.ts +44 -17
  274. package/dist/vault/service.js +126 -121
  275. package/dist/version.d.ts +1 -1
  276. package/dist/version.js +1 -1
  277. package/package.json +25 -9
  278. package/dist/core/executors.d.ts +0 -44
  279. package/dist/core/executors.js +0 -95
  280. package/dist/store/memory.d.ts +0 -57
  281. package/dist/store/memory.js +0 -645
  282. package/dist/store/postgres/migrations/001_initial.d.ts +0 -27
  283. package/dist/store/postgres/migrations/001_initial.js +0 -196
  284. package/dist/store/postgres/migrations/002_session_owner.d.ts +0 -6
  285. package/dist/store/postgres/migrations/002_session_owner.js +0 -13
  286. package/dist/store/postgres/migrations/index.d.ts +0 -49
  287. package/dist/store/postgres/migrations/index.js +0 -76
  288. package/dist/store/postgres/names.d.ts +0 -11
  289. package/dist/store/postgres/names.js +0 -42
  290. package/dist/store/postgres/tenants.d.ts +0 -90
  291. package/dist/store/postgres/tenants.js +0 -214
  292. package/dist/streams/relay.d.ts +0 -37
  293. package/dist/streams/relay.js +0 -254
  294. package/dist/tenant/envelope.d.ts +0 -7
  295. package/dist/tenant/envelope.js +0 -22
  296. package/dist/tenant/live.d.ts +0 -92
  297. package/dist/tenant/live.js +0 -290
  298. package/dist/tenant/quarantine-error.d.ts +0 -6
  299. package/dist/tenant/quarantine-error.js +0 -9
  300. package/dist/tenant/quarantine.d.ts +0 -27
  301. package/dist/tenant/quarantine.js +0 -72
  302. package/dist/tenant/routes-tenant.d.ts +0 -12
  303. package/dist/tenant/routes-tenant.js +0 -137
  304. package/dist/tenant/routes.d.ts +0 -9
  305. package/dist/tenant/routes.js +0 -151
  306. package/dist/tenant/store-memory.d.ts +0 -16
  307. package/dist/tenant/store-memory.js +0 -111
  308. /package/dist/{store → record}/cursor.d.ts +0 -0
  309. /package/dist/{store → record}/cursor.js +0 -0
@@ -2,16 +2,16 @@
2
2
  * The Session Store seam (architecture §12.2).
3
3
  *
4
4
  * The Session Store records what happened. It is async, transactional and
5
- * tenant-scoped: one `SessionStore` per Tenant. Postgres is the supported
6
- * implementation; `store/memory.ts` is the in-memory fake for unit tests.
5
+ * tenant-scoped: one `SessionStore` per Tenant. Postgres is the
6
+ * implementation (`store/postgres/store.ts`); tests run on it too.
7
7
  *
8
8
  * ## Invariants every implementation keeps
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 `record/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,17 +45,21 @@
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";
60
+ import type { ModelBudgetRow, ModelUsageRow, PrincipalRow, ToolCrossingRow, PublishableKeyRow, SigningKeyRow, SubjectUsageRow, VaultAuditRow, VaultCredentialRow, VaultIdempotencyRow, VaultRow } from "./postgres/schema.js";
58
61
  /** Tables holding JSON documents keyed by `id`. */
59
- export type DocTable = "definitions" | "sessions" | "commands" | "checkpoints" | "effects" | "actions" | "sandboxes" | "links";
62
+ export type DocTable = "definitions" | "sessions" | "commands" | "effects" | "actions" | "sandboxes" | "links";
60
63
  export declare const DOC_TABLES: readonly DocTable[];
61
64
  /** Session lifecycle (architecture §10.2). */
62
65
  export type SessionStatus = "idle" | "runnable" | "running" | "waiting" | "paused" | "uncertain" | "completed" | "failed" | "cancelled";
@@ -87,18 +90,13 @@ export interface SessionDoc {
87
90
  agentId: string;
88
91
  status: SessionStatus | (string & {});
89
92
  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
93
  }
96
94
  /** The fields of an effect document the store indexes (`request.sessionId`, `request.turnId`, `request.kind`, `status`). */
97
95
  export interface EffectDoc {
98
96
  request: HostEffect;
99
97
  status: EffectStatus | (string & {});
100
98
  }
101
- /** Actions are stored as the wire `Action`. Indexed: `sessionId`, `turnId`, `agentId`, `status`, `kind`, `leaseExpiresAt`. */
99
+ /** Actions are stored as the wire `Action`. Indexed: `sessionId`, `turnId`, `agentId`, `status`, `kind`, `deadlineAt`. */
102
100
  export type ActionDoc = Action;
103
101
  /** A workflow → agent session link, keyed by the linked agent session id. Indexed: `workflowSessionId`. */
104
102
  export interface LinkDoc {
@@ -125,95 +123,93 @@ export interface LinkedSession<S extends SessionDoc = SessionDoc> {
125
123
  link: LinkDoc;
126
124
  session: StoredSession<S>;
127
125
  }
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
126
  /** What one commit produced, delivered to commit listeners after commit. */
141
127
  export interface Commit {
142
128
  /** Events written by the transaction, in allocation order. */
143
129
  readonly events: readonly LiveEvent[];
144
130
  /**
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.
131
+ * The basin generation of each event, aligned with `events`: the stream relay appends it to
132
+ * `sessions/<id>` in that generation's basin.
148
133
  */
149
- readonly incarnations: readonly (string | null)[];
150
- /** True when the transaction called `signalWork()`. */
151
- readonly workAvailable: boolean;
134
+ readonly generations: readonly number[];
135
+ }
136
+ /** A Tenant's basin generations (Durable Streams §8.1). */
137
+ export interface BasinGenerations {
138
+ /** The basin session streams are written to and read from. */
139
+ current: number;
140
+ /** Earlier generations whose basins are still to be deleted. */
141
+ retired: number[];
152
142
  }
153
143
  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 {
144
+ /**
145
+ * An Action endpoint: the URL the Runtime delivers one agent's Actions to, and what recent
146
+ * deliveries and the last ping say about it.
147
+ */
148
+ export interface EndpointRow {
156
149
  agentId: string;
157
- tokenHash: string;
150
+ url: string;
158
151
  implementationVersion: string;
159
152
  manifestHash?: string;
153
+ timeoutMs: number;
154
+ maxConcurrent: number;
155
+ /** Application principal that registered it, when known. */
160
156
  principalId?: string;
157
+ lastDeliveryAt?: string;
158
+ lastSuccessAt?: string;
159
+ lastErrorCode?: string;
160
+ lastErrorMessage?: string;
161
+ consecutiveFailures: number;
162
+ /** What the endpoint reported serving on the last ping. */
163
+ servedImplementationVersion?: string;
164
+ servedManifestHash?: string;
161
165
  createdAt: string;
162
166
  updatedAt: string;
163
167
  }
164
- export interface PrincipalRow {
165
- id: string;
166
- role: "application" | (string & {});
167
- tokenHash: string;
168
- idempotencyKey: string | null;
169
- createdAt: string;
168
+ /** The registration part of an endpoint, as `putEndpoint` writes it. */
169
+ export type EndpointRegistrationRow = Pick<EndpointRow, "agentId" | "url" | "implementationVersion" | "manifestHash" | "timeoutMs" | "maxConcurrent" | "principalId" | "updatedAt">;
170
+ /** One observation about an endpoint (`recordEndpointHealth`). */
171
+ export type EndpointHealthUpdate =
172
+ /** A delivery was answered. */
173
+ {
174
+ kind: "success";
175
+ at: string;
170
176
  }
171
- export interface VaultRow {
172
- id: string;
173
- name: string;
174
- ownerUserId: string;
175
- /** JSON text, or null. */
176
- metadataJson: string | null;
177
- createdAt: string;
178
- scope: "user" | "host";
177
+ /** A delivery failed: not reached, refused, or lost. */
178
+ | {
179
+ kind: "failure";
180
+ at: string;
181
+ code: string;
182
+ message: string;
179
183
  }
180
- /** Envelope-encrypted secret material. Always in columns, never inside a JSON body. */
181
- export interface SealedSecret {
182
- kekId: string;
183
- nonce: Uint8Array;
184
- ciphertext: Uint8Array;
185
- wrappedDek: Uint8Array;
184
+ /** A ping was answered with what the endpoint serves. */
185
+ | {
186
+ kind: "served";
187
+ implementationVersion: string;
188
+ manifestHash?: string;
189
+ };
190
+ /**
191
+ * The rows of the typed tables, inferred from the tables Drizzle defines
192
+ * (`store/postgres/schema.ts`): a principal, a vault and its credentials (secrets sealed in
193
+ * `bytea` columns, never inside a JSON body), the vault's audit and idempotency records, the
194
+ * Tenant's signing keys, a subject's turn bucket, a publishable key, a model call in the usage
195
+ * ledger and a model budget.
196
+ */
197
+ export type { ModelBudgetRow, ModelUsageRow, ToolCrossingRow, PrincipalRow, PublishableKeyRow, SigningKeyRow, SubjectUsageRow, VaultAuditRow, VaultCredentialRow, VaultIdempotencyRow, VaultRow, };
198
+ /** Which rows of the usage ledger a total covers. */
199
+ export interface ModelUsageQuery {
200
+ scope: "tenant" | "agent" | "turn";
201
+ /** The agent or turn id; ignored for `tenant`. */
202
+ id?: string;
203
+ /** Only rows created at or after this ISO time. */
204
+ since?: string;
186
205
  }
187
- export interface VaultCredentialRow extends SealedSecret {
188
- id: string;
189
- vaultId: string;
190
- name: string;
191
- type: "bearer" | "oauth" | "model";
192
- /** JSON text of the non-secret binding (url, or provider and model). */
193
- bindingJson: string;
194
- expiresAt: string | null;
195
- createdAt: string;
196
- rotatedAt: string | null;
206
+ export interface ModelUsageTotals {
207
+ calls: number;
208
+ tokens: number;
209
+ costUsd: number;
197
210
  }
198
211
  /** Fields `updateCredential` may change. */
199
212
  export type VaultCredentialPatch = Partial<Omit<VaultCredentialRow, "id" | "vaultId" | "createdAt">>;
200
- export interface VaultAuditRow {
201
- id: string;
202
- at: string;
203
- actor: string;
204
- action: string;
205
- vaultId: string | null;
206
- credentialId: string | null;
207
- sessionId: string | null;
208
- target: string | null;
209
- outcome: string;
210
- }
211
- export interface VaultIdempotencyRow {
212
- id: string;
213
- bodyHash: string;
214
- /** JSON text of the original response. */
215
- response: string;
216
- }
217
213
  export interface SessionEffectFilter {
218
214
  /** Only this turn. Absent means every turn. */
219
215
  turnId?: string;
@@ -229,7 +225,7 @@ export interface StoreCounts {
229
225
  sessions: number;
230
226
  /** Sessions `running` or `runnable`. */
231
227
  runningSessions: number;
232
- /** Actions `pending` or `claimed`. */
228
+ /** Actions `pending` or `delivering`. */
233
229
  pendingActions: number;
234
230
  /** Effects `uncertain`. */
235
231
  uncertainEffects: number;
@@ -263,10 +259,12 @@ export interface SessionStore {
263
259
  tx<T>(fn: (t: Tx) => Promise<T>): Promise<T>;
264
260
  /**
265
261
  * 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.
262
+ * events. Returns an unsubscribe function. A Tenant without the Host's stream
263
+ * relay relays its own commits from here (`tenant/streams.ts`).
268
264
  */
269
265
  onCommit(listener: CommitListener): () => void;
266
+ /** Reads the record back, for the stream relay's refills and reconciliation. */
267
+ record(): RecordReader;
270
268
  /** Reachability and schema check for Tenant status and `/ready`. Never throws. */
271
269
  health(): Promise<StoreHealth>;
272
270
  close(): Promise<void>;
@@ -279,7 +277,7 @@ export interface StoreHealth {
279
277
  /** Options every implementation accepts. */
280
278
  export interface SessionStoreOptions {
281
279
  tenantId: string;
282
- /** Clock for event `createdAt`. Defaults to `() => new Date()`. */
280
+ /** Clock for event `time`. Defaults to `() => new Date()`. */
283
281
  now?: () => Date;
284
282
  /** Receives listener and `afterCommit` failures, which never reject `tx`. */
285
283
  onError?: (error: unknown) => void;
@@ -290,9 +288,9 @@ export interface Tx {
290
288
  /** Insert or replace. For `sessions`, ownership fields in `body` are ignored. */
291
289
  put(table: DocTable, id: string, body: unknown): Promise<void>;
292
290
  /**
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).
291
+ * Deletes a document. Deleting a session keeps its record rows and log head
292
+ * (per-session record deletion is deferred, Durable Streams §15), so a session
293
+ * created again with the same id continues its log and stream.
296
294
  */
297
295
  delete(table: DocTable, id: string): Promise<void>;
298
296
  /**
@@ -301,7 +299,7 @@ export interface Tx {
301
299
  * Locking twice in one transaction is a no-op.
302
300
  *
303
301
  * Lock order, so concurrent transactions cannot deadlock (Postgres takes row
304
- * locks in statement order; the in-memory fake serializes whole transactions):
302
+ * locks in statement order):
305
303
  * - a linked agent (child) session is locked before its workflow (parent)
306
304
  * session, never after it. `t.event` on a session takes its lock, so an
307
305
  * event on a child after the parent is locked breaks the rule too. Work
@@ -313,14 +311,15 @@ export interface Tx {
313
311
  lockSession<T extends SessionDoc = SessionDoc>(id: string): Promise<StoredSession<T> | undefined>;
314
312
  /**
315
313
  * 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.
314
+ * builds the event on the `nylorun.event/2` envelope, checks it against the
315
+ * event catalog (`InvalidEventError` when it does not match), writes it to
316
+ * the record in the Tenant's current basin generation and buffers it for
317
+ * commit listeners. Rejects when the session does not exist. The returned
318
+ * cursor is final.
318
319
  */
319
- event(sessionId: string, turnId: string | null, type: string, payload: unknown): Promise<LiveEvent>;
320
+ event<T extends EventType>(sessionId: string, turnId: string | null, type: T, payload: EventPayload<T>): Promise<SessionEventOf<T>>;
320
321
  /** Runs `fn` after a successful commit (never on rollback). Used for wakes. */
321
322
  afterCommit(fn: () => void | Promise<void>): void;
322
- /** Signals `work_available` to executors after a successful commit. */
323
- signalWork(): void;
324
323
  /**
325
324
  * Locks the session and takes ownership when it has no owner or the owner's
326
325
  * lease ended at or before `now`; then increments `epoch` and sets the lease
@@ -354,10 +353,14 @@ export interface Tx {
354
353
  }): Promise<StoredSession<T>[]>;
355
354
  listDefinitions<T extends DefinitionDoc = DefinitionDoc>(): Promise<T[]>;
356
355
  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. */
356
+ /** One agent's `pending` actions, delivered when its endpoint is registered. */
360
357
  pendingActions(agentId: string): Promise<ActionDoc[]>;
358
+ /** How many of one agent's actions are `delivering` (Action endpoints). */
359
+ deliveringCount(agentId: string): Promise<number>;
360
+ /** `pending` actions of agents that have an Action endpoint, by id. */
361
+ pendingActionsWithEndpoint(limit: number): Promise<ActionDoc[]>;
362
+ /** `delivering` actions whose deadline is at or before `now`, earliest first. */
363
+ expiredDeliveries(now: Date, limit: number): Promise<ActionDoc[]>;
361
364
  actionsForSession(sessionId: string, filter?: SessionActionFilter): Promise<ActionDoc[]>;
362
365
  actionsWithStatus(statuses: readonly ActionStatus[], filter?: {
363
366
  kinds?: readonly ActionKind[];
@@ -372,27 +375,19 @@ export interface Tx {
372
375
  /** Links of one workflow session joined to their existing agent sessions, ordered by agent session id. */
373
376
  linkedSessions<S extends SessionDoc = SessionDoc>(workflowSessionId: string): Promise<LinkedSession<S>[]>;
374
377
  counts(): Promise<StoreCounts>;
375
- /** Unrelayed events, ordered by session id then sequence. */
376
- outbox(limit: number, filter?: {
377
- sessionId?: string;
378
- }): Promise<OutboxRow[]>;
378
+ basinGenerations(): Promise<BasinGenerations>;
379
+ /** Forgets a retired generation once its basin is deleted. */
380
+ forgetRetiredGeneration(generation: number): Promise<void>;
381
+ listEndpoints(): Promise<EndpointRow[]>;
382
+ getEndpoint(agentId: string): Promise<EndpointRow | undefined>;
379
383
  /**
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.
384
+ * Inserts or updates by `agentId`, keeping `createdAt`. Health is kept, except that a new
385
+ * `url` starts with none.
384
386
  */
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>;
387
+ putEndpoint(row: EndpointRegistrationRow): Promise<void>;
388
+ deleteEndpoint(agentId: string): Promise<void>;
389
+ /** Records one observation. Nothing happens when the endpoint does not exist. */
390
+ recordEndpointHealth(agentId: string, update: EndpointHealthUpdate): Promise<void>;
396
391
  /** Rejects when the id or token hash already exists. */
397
392
  insertPrincipal(row: PrincipalRow): Promise<void>;
398
393
  principalByTokenHash(tokenHash: string): Promise<PrincipalRow | undefined>;
@@ -428,15 +423,71 @@ export interface Tx {
428
423
  getVaultIdempotency(id: string): Promise<VaultIdempotencyRow | undefined>;
429
424
  /** Rejects when the id exists. */
430
425
  insertVaultIdempotency(row: VaultIdempotencyRow): Promise<void>;
426
+ /**
427
+ * Serializes signing key changes: held until the transaction ends. Take it before reading
428
+ * the keys a change depends on, so concurrent first uses or rotations do not collide.
429
+ */
430
+ lockSigningKeys(): Promise<void>;
431
+ /** Rejects on a duplicate id, or a second key in `standby`, `current` or `previous`. */
432
+ insertSigningKey(row: SigningKeyRow): Promise<void>;
433
+ signingKey(id: string): Promise<SigningKeyRow | undefined>;
434
+ /** Keys in the given states (all when omitted), ordered by `createdAt`, then id. */
435
+ signingKeys(states?: readonly SigningKeyRow["state"][]): Promise<SigningKeyRow[]>;
436
+ /**
437
+ * Moves a key from `from` to `to` and stamps the matching time (`activatedAt` for
438
+ * `current`, `retiredAt` for `previous`, `revokedAt` for `revoked`). Returns false when the
439
+ * key is not in `from`.
440
+ */
441
+ setSigningKeyState(id: string, from: SigningKeyRow["state"], to: SigningKeyRow["state"], at: string): Promise<boolean>;
442
+ countSigningKeys(): Promise<number>;
443
+ /** The subject's revocation epoch; 0 when it was never revoked. */
444
+ subjectEpoch(subject: string): Promise<number>;
445
+ /** The epochs of several subjects; subjects never revoked are absent. */
446
+ subjectEpochs(subjects: readonly string[]): Promise<Map<string, number>>;
447
+ /** Adds one to the subject's epoch and returns the new value. */
448
+ bumpSubjectEpoch(subject: string, at: string): Promise<number>;
449
+ /**
450
+ * The subject's turn bucket, created from `initial` when missing, locked until the
451
+ * transaction ends so concurrent commands of one subject serialize.
452
+ */
453
+ lockSubjectUsage(initial: SubjectUsageRow): Promise<SubjectUsageRow>;
454
+ putSubjectUsage(row: SubjectUsageRow): Promise<void>;
455
+ /** How many sessions of `ownerUserId` are in one of `statuses`. */
456
+ countOwnerSessions(ownerUserId: string, statuses: readonly string[]): Promise<number>;
457
+ /** Rejects on a duplicate id, key or name. */
458
+ insertPublishableKey(row: PublishableKeyRow): Promise<void>;
459
+ publishableKeyByKey(key: string): Promise<PublishableKeyRow | undefined>;
460
+ publishableKey(id: string): Promise<PublishableKeyRow | undefined>;
461
+ /** All keys, revoked ones included, ordered by `createdAt`, then id. */
462
+ publishableKeys(): Promise<PublishableKeyRow[]>;
463
+ /** Returns false when the key does not exist. */
464
+ updatePublishableKey(id: string, patch: Partial<Pick<PublishableKeyRow, "originsJson" | "revokedAt">>): Promise<boolean>;
465
+ /** Appends a row, setting `duplicate` when one with the same `effectKey` exists; returns it. */
466
+ recordModelUsage(row: Omit<ModelUsageRow, "duplicate">): Promise<ModelUsageRow>;
467
+ modelUsageTotals(query: ModelUsageQuery): Promise<ModelUsageTotals>;
468
+ /** Every budget, ordered by scope, then scope id. */
469
+ listModelBudgets(): Promise<ModelBudgetRow[]>;
470
+ /** Replaces every budget with `rows`. */
471
+ putModelBudgets(rows: readonly ModelBudgetRow[]): Promise<void>;
472
+ toolCrossing(key: string): Promise<ToolCrossingRow | undefined>;
473
+ /** Inserts the running call's row; false when a row with `key` already exists. */
474
+ startToolCrossing(row: Pick<ToolCrossingRow, "key" | "hash" | "startedAt">): Promise<boolean>;
475
+ /** Records the call's answer. */
476
+ settleToolCrossing(key: string, answer: unknown, settledAt: string): Promise<void>;
477
+ /** Deletes rows that settled before `before`; returns how many. */
478
+ pruneToolCrossings(before: string): Promise<number>;
431
479
  getSetting(key: string): Promise<string | undefined>;
432
480
  putSetting(key: string, value: string): Promise<void>;
433
481
  /**
434
482
  * Deletes Tenant state by scope, in this transaction:
435
- * - `sessions`: sessions, commands, checkpoints, effects, actions, links and the outbox;
483
+ * - `sessions`: sessions, commands, effects, actions, links, subject turn
484
+ * buckets and the Tenant's record rows and log heads. The Tenant moves to the next basin
485
+ * generation and the current one is retired, so session ids it frees start again in an
486
+ * empty basin;
436
487
  * - `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.
488
+ * - `all`: both, plus definitions, Action endpoints, user vaults with their credentials,
489
+ * the model usage ledger and the model budgets. The host vault, principals, signing keys, subject epochs,
490
+ * publishable keys, settings, audit and vault idempotency rows stay.
440
491
  */
441
492
  reset(scope: ResetScope): Promise<void>;
442
493
  }
@@ -2,7 +2,6 @@ export const DOC_TABLES = [
2
2
  "definitions",
3
3
  "sessions",
4
4
  "commands",
5
- "checkpoints",
6
5
  "effects",
7
6
  "actions",
8
7
  "sandboxes",
@@ -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, turnId?: 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,33 @@
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, turnId) {
9
+ if (!sessionId)
10
+ throw new Error("sessionId is required");
11
+ const signal = {
12
+ type: "session.cancel",
13
+ sessionId,
14
+ ...(turnId ? { turnId } : {}),
15
+ };
16
+ await streams.append(basin, CONTROL_STREAM, [signal]);
17
+ }
18
+ /**
19
+ * Appends a `sessions.reset` signal to the old generation's basin: every process with the
20
+ * Tenant open moves its readers to `generation` and ends the streams of deleted sessions.
21
+ */
22
+ export async function signalSessionsReset(streams, basin, generation) {
23
+ const signal = { type: "sessions.reset", generation };
24
+ await streams.append(basin, CONTROL_STREAM, [signal]);
25
+ }
26
+ /**
27
+ * Appends a `subject.revoked` signal: every process with the Tenant open ends the subject's
28
+ * streams opened with a token older than `epoch`.
29
+ */
30
+ export async function signalSubjectRevoked(streams, basin, subject, epoch) {
31
+ const signal = { type: "subject.revoked", subject, epoch };
32
+ await streams.append(basin, CONTROL_STREAM, [signal]);
33
+ }
@@ -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>;