@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
@@ -1,13 +1,15 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
- import type { AdminTenant, AdminTenantStatus, HostAggregate, TenantEnvelope } from "@nylorun/core/contracts";
2
+ import type { HostAggregate, HostTenant, TenantEnvelope } from "@nylorun/core/contracts";
3
3
  import type { FlowLimits } from "../core/limits.js";
4
4
  import type { SessionStore } from "../store/types.js";
5
5
  import type { SandboxBackend } from "../sandbox/types.js";
6
+ import type { TenantCause } from "./cause.js";
7
+ import type { OutboundPolicy } from "./outbound.js";
6
8
  import type { TenantWorker } from "./worker.js";
7
9
  export type TenantMode = "shared" | "ephemeral" | "test";
8
10
  /**
9
- * The Tenant directory on the Host root. The Tenant's data lives in its store (a Postgres
10
- * schema); this holds what stays on the Host: the vault key, plugin data, logs, and the
11
+ * The Tenant directory on the Host root (`<Host root>/tenant/`). The Tenant's data lives in
12
+ * its database; this holds what stays on the Host: the vault key, plugin data, logs, and the
11
13
  * private home, tmp and sandbox directories.
12
14
  */
13
15
  export interface TenantPaths {
@@ -22,10 +24,6 @@ export interface TenantPaths {
22
24
  }
23
25
  export type TenantModelConfig = {
24
26
  kind: "vault";
25
- } | {
26
- kind: "gateway";
27
- url: string;
28
- model: string;
29
27
  } | {
30
28
  kind: "fixture";
31
29
  } | {
@@ -41,8 +39,21 @@ export interface TenantConfig {
41
39
  backends?: readonly SandboxBackend[];
42
40
  };
43
41
  model: TenantModelConfig;
42
+ /**
43
+ * Segment rollover (Model Calls §10): a turn ends its segment at a step boundary after this
44
+ * many steps or milliseconds and continues in a new one, so no advance reaches its
45
+ * deadline. Default 50 steps or 20 minutes.
46
+ */
47
+ rollover?: {
48
+ steps?: number;
49
+ ms?: number;
50
+ };
51
+ /** Retries and timeouts for model calls (Model Calls §5, §6). Defaults in `piModel`. */
52
+ modelCall?: import("../gates/model-gate.js").ModelCallSettings;
44
53
  childEnv: Readonly<Record<string, string>>;
45
54
  /** Action claim lease. Default 30 s. */
55
+ /** How the Runtime may call Action endpoints (Host settings). Default: http and private addresses allowed. */
56
+ delivery?: OutboundPolicy;
46
57
  leaseMs?: number;
47
58
  /** Ownership lease of an advance (§10.6); renewed every third while it runs. Default 30 s. */
48
59
  ownerLeaseMs?: number;
@@ -69,135 +80,95 @@ export interface Logger {
69
80
  export interface TenantSummary {
70
81
  ready: boolean;
71
82
  runningSessions: number;
72
- connectedExecutors: number;
83
+ /** Deliveries to Action endpoints in flight on this process. */
84
+ inFlightDeliveries: number;
73
85
  pendingActions: number;
74
86
  uncertainEffects: number;
75
- /** Events committed but not yet relayed to Durable Streams. */
76
- outboxDepth?: number;
77
- /** The oldest unrelayed event's age; 0 when none waits. */
78
- relayLagMs?: number;
87
+ }
88
+ /**
89
+ * The Node request and response behind a `Request`, for what still writes to Node directly
90
+ * (event streams). The shape of `@hono/node-server`'s `HttpBindings`.
91
+ */
92
+ export interface NodeBindings {
93
+ readonly incoming: IncomingMessage;
94
+ readonly outgoing: ServerResponse;
79
95
  }
80
96
  /** An open Tenant Runtime. Created only by the Tenant module. */
81
97
  export interface TenantHandle {
82
98
  readonly envelope: TenantEnvelope;
83
99
  /** The handlers Durable Session Execution calls for this Tenant (`worker.ts`). */
84
100
  readonly worker?: TenantWorker;
85
- /** Headers already validated by the Host. Authenticates, authorizes, dispatches. */
86
- handle(request: IncomingMessage, response: ServerResponse, url: URL): Promise<void>;
101
+ /**
102
+ * A Tenant API request whose headers the Host has validated: authenticates, authorizes,
103
+ * dispatches. A response already written to `node.outgoing` is `RESPONSE_ALREADY_SENT`.
104
+ */
105
+ fetch(request: Request, node: NodeBindings): Promise<Response>;
87
106
  summary(): Promise<TenantSummary>;
88
107
  /** Stop scheduling; wait for or cancel active turns. */
89
108
  drain(activeWork: "drain" | "cancel", timeoutMs?: number): Promise<void>;
90
109
  close(): Promise<void>;
91
110
  }
111
+ /**
112
+ * The Host's Tenant for a request: open, or unavailable. `cause` says why when opening it
113
+ * failed; without one the Host is starting or closing. Either way the request gets the opaque
114
+ * 404.
115
+ */
92
116
  export type TenantResolution = {
93
117
  kind: "open";
94
118
  handle: TenantHandle;
95
119
  } | {
96
- kind: "not-found";
97
- } | {
98
- kind: "quarantined";
99
- quarantine: Quarantine;
120
+ kind: "unavailable";
121
+ cause?: TenantCause;
100
122
  };
101
- export interface Quarantine {
102
- code: "kek-missing" | "corrupt" | "schema-too-new" | "migration-failed" | "envelope-invalid" | "open-timeout" | "open-failed";
103
- message: string;
104
- repair: string;
105
- }
106
- export interface BootstrapPrincipal {
107
- principalId: string;
108
- credentialHash: string;
109
- idempotencyKey: string;
110
- /** When set, also registers application principal `studio` with this hash. */
111
- studioCredentialHash?: string;
112
- /** Application principals whose keys the admin key derives (feature `derived-principals`). */
113
- derivedPrincipals?: readonly DerivedPrincipal[];
114
- }
115
- export interface DerivedPrincipal {
116
- id: string;
117
- credentialHash: string;
118
- }
119
123
  /**
120
- * The deep module (§8). HTTP, CLI and tests use only this.
124
+ * The deep module (§8): the one Tenant a Host serves (tenancy.md §5). HTTP, the Worker and
125
+ * tests use only this.
121
126
  *
122
- * Tenants open on demand (architecture §8.2): the first `resolve`, `status`, `worker` or
123
- * `delete` naming a Tenant opens it (bounded by the open timeout) and caches the handle or
124
- * the quarantine; `start` only marks discovery done, so a Host with many Tenants opens none
125
- * at startup.
127
+ * `start` opens the Tenant; the Host has it open from readiness to shutdown. There is no
128
+ * Tenant list, no opening on demand of other Tenants and no quarantine cache: when opening
129
+ * fails for a reason in the Tenant (a `TenantCause`), the Host fails readiness and reports
130
+ * the cause until it is restarted. A failure outside the Tenant (`TenantUnavailableError`:
131
+ * Postgres unreachable) is retried in the background and by the next request.
126
132
  */
127
133
  export interface TenantModule {
128
- /** Marks discovery done (`/ready`). Opens nothing. */
134
+ /** Opens the Tenant. Never rejects: a failure is recorded and reported by `tenant()`. */
129
135
  start(): Promise<void>;
130
- readonly started: boolean;
131
- /** Opens the Tenant on first use; a missing Tenant is `not-found`. */
132
- resolve(id: string): Promise<TenantResolution>;
136
+ /** The Tenant is open (`/ready`). */
137
+ readonly ready: boolean;
133
138
  /**
134
- * The Tenant's Worker handlers, opening it on demand, for `TenantWorkers.resolve`.
135
- * Undefined when the Tenant does not exist, is quarantined or is being deleted, or the
136
- * module is closed.
139
+ * The open Tenant, waiting for an open in progress. Rejects with `TenantUnavailableError`
140
+ * when it cannot be reached for a reason outside it.
137
141
  */
138
- worker(id: string): Promise<TenantWorker | undefined>;
139
- create(input: {
140
- tenantId: string;
141
- name: string;
142
- } & BootstrapPrincipal): Promise<{
143
- envelope: TenantEnvelope;
144
- created: boolean;
145
- }>;
142
+ resolve(): Promise<TenantResolution>;
146
143
  /**
147
- * Every Tenant the store holds, without opening any. A Tenant not opened yet is listed
148
- * `open` when its envelope reads; opening it may still quarantine it.
144
+ * The Tenant's Worker handlers when `id` is the open Tenant's, for `TenantWorkers.resolve`.
145
+ * Undefined otherwise, or once the module is closed.
149
146
  */
150
- list(): Promise<readonly AdminTenant[]>;
151
- status(id: string): Promise<AdminTenantStatus | undefined>;
152
- delete(id: string, activeWork: "refuse" | "drain" | "cancel"): Promise<void>;
147
+ worker(id: string): Promise<TenantWorker | undefined>;
148
+ /** The Tenant as `/v1/admin/status` reports it. */
149
+ tenant(): HostTenant;
153
150
  summarize(): Promise<HostAggregate>;
154
151
  close(): Promise<void>;
155
152
  }
156
153
  /**
157
- * Storage adapter behind the module (§8): a Postgres schema per Tenant (`store-pg.ts`), and
158
- * in-memory for tests (`store-memory.ts`).
154
+ * Opens the Host's Tenant (`store-pg.ts`): migrates its database, creates the Tenant when it
155
+ * has none, and opens the Tenant Runtime. Rejects with a `TenantOpenError` (`cause.ts`) when
156
+ * the Tenant itself cannot be opened, and with `TenantUnavailableError` when something
157
+ * outside it failed.
159
158
  */
160
- export interface TenantStore {
161
- /** Ids of every Tenant the store holds. */
162
- enumerate(): Promise<readonly string[]>;
163
- /** Throws a quarantine error when unreadable, `TenantNotFoundError` when gone. */
164
- readEnvelope(id: string): Promise<TenantEnvelope>;
165
- /**
166
- * Creates the Tenant, or returns `exists` when it already exists (whatever its bootstrap
167
- * material; the module compares it with `bootstrapMatches`). The stored envelope's
168
- * `schemaVersion` is the store's own; read it back with `readEnvelope`.
169
- */
170
- create(envelope: TenantEnvelope, bootstrap: BootstrapPrincipal): Promise<"created" | "exists">;
171
- bootstrapMatches(id: string, bootstrap: BootstrapPrincipal): Promise<boolean>;
172
- /**
173
- * Opens the Tenant Runtime; runs migration. Throws `TenantNotFoundError` when the Tenant
174
- * does not exist, `TenantUnavailableError` when something outside the Tenant failed, and
175
- * any other error (a quarantine error, ideally) when the Tenant itself cannot be opened.
176
- */
177
- open(id: string): Promise<TenantHandle>;
178
- /** Removes the Tenant: its data and its Tenant directory. */
179
- trash(id: string, now: Date): Promise<void>;
180
- /** Removes what a failed `create` left, never an existing Tenant. */
181
- removePartial(id: string): Promise<void>;
182
- }
159
+ export type TenantOpener = () => Promise<TenantHandle>;
183
160
  /**
184
- * Thrown by a store when a Tenant cannot be reached for a reason outside it (the database
185
- * or Durable Session Execution is unavailable). The module does not quarantine it: the next
186
- * use tries again. The Host answers 503.
161
+ * Thrown when the Tenant cannot be reached for a reason outside it (the database or Durable
162
+ * Session Execution is unavailable). Not a cause: the next use tries again. The Host answers
163
+ * 503.
187
164
  */
188
165
  export declare class TenantUnavailableError extends Error {
189
- readonly tenantId: string;
190
166
  readonly status = 503;
191
- constructor(tenantId: string, options?: {
167
+ constructor(options?: {
192
168
  cause?: unknown;
193
169
  });
194
170
  }
195
- /** Thrown by `TenantStore.open` for a Tenant that does not exist. */
196
- export declare class TenantNotFoundError extends Error {
197
- readonly tenantId: string;
198
- constructor(tenantId: string);
199
- }
200
- /** What a Tenant store hands the Tenant Runtime it opens. */
171
+ /** What the Tenant opener hands the Tenant Runtime it opens. */
201
172
  export interface OpenedTenant {
202
173
  /**
203
174
  * The Tenant's opened Session Store. The Tenant Runtime owns it from here on and closes it
@@ -207,5 +178,5 @@ export interface OpenedTenant {
207
178
  /** The Tenant envelope as its store reports it. */
208
179
  envelope: TenantEnvelope;
209
180
  }
210
- /** Opens the Tenant Runtime on a store the Tenant store opened (injected, so tests can fake it). */
181
+ /** Opens the Tenant Runtime on an opened store (injected, so tests can fake it). */
211
182
  export type OpenTenantRuntime = (config: TenantConfig, opened: OpenedTenant) => Promise<TenantHandle>;
@@ -1,23 +1,12 @@
1
1
  /**
2
- * Thrown by a store when a Tenant cannot be reached for a reason outside it (the database
3
- * or Durable Session Execution is unavailable). The module does not quarantine it: the next
4
- * use tries again. The Host answers 503.
2
+ * Thrown when the Tenant cannot be reached for a reason outside it (the database or Durable
3
+ * Session Execution is unavailable). Not a cause: the next use tries again. The Host answers
4
+ * 503.
5
5
  */
6
6
  export class TenantUnavailableError extends Error {
7
- tenantId;
8
7
  status = 503;
9
- constructor(tenantId, options) {
8
+ constructor(options) {
10
9
  super("Tenant is temporarily unavailable", options);
11
- this.tenantId = tenantId;
12
10
  this.name = "TenantUnavailableError";
13
11
  }
14
12
  }
15
- /** Thrown by `TenantStore.open` for a Tenant that does not exist. */
16
- export class TenantNotFoundError extends Error {
17
- tenantId;
18
- constructor(tenantId) {
19
- super(`Tenant ${tenantId} not found`);
20
- this.tenantId = tenantId;
21
- this.name = "TenantNotFoundError";
22
- }
23
- }
@@ -1,4 +1,4 @@
1
- import type { AdvanceResult, DurableExecution, StuckInvocation, WorkerHandlers } from "../execution/types.js";
1
+ import type { AdvanceResult, DeliverResult, DurableExecution, StuckInvocation, WorkerHandlers } from "../execution/types.js";
2
2
  import type { Logger } from "./types.js";
3
3
  /** This process's Worker id: the `owner` it writes on the sessions it advances (§10.6). */
4
4
  export declare const WORKER_ID: string;
@@ -38,6 +38,8 @@ export interface TenantWorker {
38
38
  advance(sessionId: string, signal: AbortSignal): Promise<AdvanceResult>;
39
39
  /** One pass of the Tenant sweep. */
40
40
  sweep(): Promise<void>;
41
+ /** Delivers one Action to its endpoint. Only infrastructure errors throw. */
42
+ deliver?(actionId: string, signal: AbortSignal): Promise<DeliverResult>;
41
43
  }
42
44
  export interface TenantWorkersOptions {
43
45
  /** Finds or opens a Tenant that is not registered on this process. */
@@ -1,18 +1,20 @@
1
1
  /**
2
2
  * The Worker side of Durable Session Execution (architecture §12.3): what a `DurableExecution`
3
- * calls for one Tenant, and the registry that lets one execution serve every Tenant open on
4
- * this process.
3
+ * calls for the Tenant, and the registry its handlers dispatch through by Tenant id.
5
4
  *
6
5
  * - `TenantWorker` is one open Tenant's handlers: `advance(sessionId, signal)` (§10.5, in
7
- * `advance.ts`) and `sweep()` (the Tenant sweep, in `sweep.ts`).
6
+ * `advance.ts`), `sweep()` (the Tenant sweep, in `sweep.ts`) and, optionally,
7
+ * `deliver(actionId, signal)` (one Action to its Action endpoint).
8
8
  * - `TenantWorkers` is the registry. Its `handlers` are the `WorkerHandlers` an execution is
9
9
  * started with; they dispatch by `tenantId` to the registered `TenantWorker`. A Tenant
10
10
  * registers when it opens and unregisters when it closes. A call for a Tenant that is not
11
- * open here asks the optional `resolve` hook (the Host can open it on demand); without one,
12
- * the advance returns `done` and the sweep does nothing. Nothing is lost: the Tenant's sweep
13
- * re-wakes its orphaned sessions once it is open again.
11
+ * open here asks the optional `resolve` hook (the Host's, which waits for its Tenant to
12
+ * open, and knows no other); without one,
13
+ * the advance and the delivery return `done` and the sweep does nothing. Nothing is lost:
14
+ * the Tenant's sweep re-wakes its orphaned sessions once it is open again, and an Action
15
+ * that was not delivered stays pending.
14
16
  * - `TenantExecution` pairs an execution with its registry. The Host creates one per process
15
- * (`host/execution.ts`) and passes it to every Tenant it opens; a Tenant opened without one
17
+ * (`host/execution.ts`) and passes it to its Tenant; a Tenant opened without one
16
18
  * (tests, ephemeral) gets its own in-process `MemoryExecution`.
17
19
  *
18
20
  * ## Advance deadline
@@ -132,6 +134,10 @@ export class TenantWorkers {
132
134
  sweep: async (tenantId) => {
133
135
  await (await this.find(tenantId))?.sweep();
134
136
  },
137
+ deliver: async (tenantId, actionId, signal) => {
138
+ const worker = await this.find(tenantId);
139
+ return worker?.deliver ? worker.deliver(actionId, signal) : DONE;
140
+ },
135
141
  };
136
142
  /** Runs one advance under the deadline and the grace period (see the module comment). */
137
143
  async bounded(tenantId, sessionId, worker, signal) {
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The Tenant's host model credential in plaintext: reading it to call the provider, and
3
+ * writing back a credential the provider adapter refreshed (OAuth). Only the Model Gate holds
4
+ * one (`gates/`; `scripts/check-boundaries.mjs` enforces it), so the loop process never reads a
5
+ * model credential. `VaultService` seals the credential but never reads it back.
6
+ *
7
+ * Decryption runs after the read commits; the key is resolved before the write's transaction.
8
+ */
9
+ import type { SessionStore } from "../store/types.js";
10
+ import { type HostModelSecret } from "./service.js";
11
+ export interface HostModelVaultOptions {
12
+ readonly store: SessionStore;
13
+ /** The Tenant's key-encryption key; may throw when it is missing. */
14
+ readonly kek: () => Buffer;
15
+ }
16
+ export declare class HostModelVault {
17
+ private readonly store;
18
+ private readonly kek;
19
+ constructor(options: HostModelVaultOptions);
20
+ /** Reads the active host model secret. Decrypts after the read commits. */
21
+ readHostModel(): Promise<HostModelSecret | undefined>;
22
+ /** Replaces the active host model's secret, keeping its binding. */
23
+ updateHostCredential(credential: {
24
+ type: "api_key" | "oauth";
25
+ key?: string;
26
+ env?: Record<string, string>;
27
+ refresh?: string;
28
+ access?: string;
29
+ expires?: number;
30
+ }): Promise<void>;
31
+ }
@@ -0,0 +1,57 @@
1
+ import { encryptSecret } from "./crypto.js";
2
+ import { VaultError } from "./error.js";
3
+ import { activeHostCredentialRow, HOST_VAULT_ID, hostModelAad, modelBinding, readHostPayload, } from "./service.js";
4
+ export class HostModelVault {
5
+ store;
6
+ kek;
7
+ constructor(options) {
8
+ this.store = options.store;
9
+ this.kek = options.kek;
10
+ }
11
+ /** Reads the active host model secret. Decrypts after the read commits. */
12
+ async readHostModel() {
13
+ const row = await this.store.tx((t) => activeHostCredentialRow(t));
14
+ if (!row)
15
+ return undefined;
16
+ const binding = modelBinding(row);
17
+ const payload = readHostPayload(this.kek(), row, binding);
18
+ const credential = binding.authType === "oauth"
19
+ ? { type: "oauth", ...payload.oauth }
20
+ : {
21
+ type: "api_key",
22
+ key: payload.apiKey,
23
+ ...(payload.env ? { env: payload.env } : {}),
24
+ };
25
+ return {
26
+ provider: binding.provider,
27
+ model: binding.model,
28
+ ...(binding.baseUrl ? { baseUrl: binding.baseUrl } : {}),
29
+ ...(binding.settings ? { settings: binding.settings } : {}),
30
+ authType: binding.authType,
31
+ credential,
32
+ };
33
+ }
34
+ /** Replaces the active host model's secret, keeping its binding. */
35
+ async updateHostCredential(credential) {
36
+ const kek = this.kek();
37
+ await this.store.tx(async (t) => {
38
+ const row = await activeHostCredentialRow(t);
39
+ if (!row)
40
+ throw new VaultError(404, "Model provider is not configured");
41
+ const binding = modelBinding(row);
42
+ const payload = credential.type === "oauth"
43
+ ? { oauth: credential }
44
+ : {
45
+ apiKey: credential.key,
46
+ ...(credential.env ? { env: credential.env } : {}),
47
+ };
48
+ const sealed = encryptSecret(kek, hostModelAad(binding.provider, binding.model), Buffer.from(JSON.stringify(payload), "utf8"));
49
+ await t.updateCredential(HOST_VAULT_ID, row.id, {
50
+ type: "model",
51
+ bindingJson: JSON.stringify({ ...binding, authType: credential.type }),
52
+ ...sealed,
53
+ rotatedAt: new Date().toISOString(),
54
+ });
55
+ });
56
+ }
57
+ }
@@ -1,9 +1,12 @@
1
- import type { CreateCredentialRequest, CreateVaultRequest, CredentialInfo, CredentialSelection, HostModelProviderInfo, HostModelView, PutHostModelRequest, RotateCredentialRequest, SelectHostModelRequest, VaultInfo } from "@nylorun/core/contracts";
2
- import type { SessionStore, Tx } from "../store/types.js";
1
+ import type { CreateCredentialRequest, CreateVaultRequest, CredentialInfo, CredentialSelection, HostModelProviderInfo, HostModelView, PutHostModelRequest, CustomModelSettings, RotateCredentialRequest, SelectHostModelRequest, VaultInfo } from "@nylorun/core/contracts";
2
+ import type { SessionStore, Tx, VaultCredentialRow } from "../store/types.js";
3
+ export declare const HOST_VAULT_ID = "host";
3
4
  export type HostModelSecret = {
4
5
  provider: string;
5
6
  model: string;
6
7
  baseUrl?: string;
8
+ /** A custom endpoint's window, output limit, reasoning and pi-ai compat (Model Calls §7). */
9
+ settings?: CustomModelSettings;
7
10
  authType: "api_key" | "oauth";
8
11
  credential: {
9
12
  type: "api_key" | "oauth";
@@ -37,11 +40,16 @@ export interface VaultServiceOptions {
37
40
  kek: () => Buffer;
38
41
  /** Used only for OAuth refresh, always outside a transaction. */
39
42
  fetch: typeof fetch;
43
+ /** How long a token endpoint may take to answer a refresh. Default 30 s. */
44
+ refreshTimeoutMs?: number;
40
45
  }
41
46
  export declare class VaultService {
42
47
  private readonly store;
43
48
  private readonly kek;
44
49
  private readonly fetchImpl;
50
+ private readonly refreshTimeoutMs;
51
+ /** Refreshes in progress by credential id: concurrent uses share one. */
52
+ private readonly refreshing;
45
53
  constructor(options: VaultServiceOptions);
46
54
  createVault(body: CreateVaultRequest): Promise<VaultInfo>;
47
55
  listVaults(ownerUserId: string): Promise<VaultInfo[]>;
@@ -80,18 +88,17 @@ export declare class VaultService {
80
88
  }>;
81
89
  putHostModel(body: PutHostModelRequest): Promise<HostModelView>;
82
90
  selectHostModel(body: SelectHostModelRequest): Promise<HostModelView>;
83
- /** Reads the active host model secret. Decrypts after the read commits. */
84
- readHostModel(): Promise<HostModelSecret | undefined>;
85
- updateHostCredential(credential: {
86
- type: "api_key" | "oauth";
87
- key?: string;
88
- env?: Record<string, string>;
89
- refresh?: string;
90
- access?: string;
91
- expires?: number;
92
- }): Promise<void>;
93
- reject(route: string): Promise<void>;
94
91
  /** Calls the token endpoint outside any transaction, then stores the result. */
92
+ /**
93
+ * Refreshes an OAuth grant once for every concurrent use in this process. A rotating refresh
94
+ * token is valid once, so two refreshes with it would fail the second, or make the provider
95
+ * revoke the grant.
96
+ */
97
+ private refreshShared;
98
+ /** Uses a token another process refreshed instead of refreshing again, before and after. */
99
+ private refreshLatest;
100
+ /** The credential's payload when it was rotated after `row` was read and is not due. */
101
+ private rotatedSince;
95
102
  private refresh;
96
103
  private replay;
97
104
  /** Re-seals a user credential's payload and returns the updated row. */
@@ -101,12 +108,32 @@ export declare class VaultService {
101
108
  private validateHostModel;
102
109
  private insertHostCredential;
103
110
  private hostModelView;
104
- private hostModelRows;
105
- private hostCredentialRowFor;
106
- private activeHostCredentialRow;
107
- private activeProviderId;
108
111
  private setActiveProvider;
109
112
  private deleteHostProviderRows;
110
113
  private credentialRow;
111
114
  private audit;
112
115
  }
116
+ /** The host vault's model credential rows that parse. */
117
+ export declare function hostModelRows(t: Tx): Promise<VaultCredentialRow[]>;
118
+ /** The host model credential of `provider`. */
119
+ export declare function hostCredentialRowFor(t: Tx, provider: string): Promise<VaultCredentialRow | undefined>;
120
+ /** The provider the host model selection names, or the only (or legacy) one configured. */
121
+ export declare function activeProviderId(t: Tx): Promise<string | undefined>;
122
+ /** The active host model credential row (ciphertext), in `t`. */
123
+ export declare function activeHostCredentialRow(t: Tx): Promise<VaultCredentialRow | undefined>;
124
+ export declare function readHostPayload(kek: Buffer, row: VaultCredentialRow, binding: ModelBinding): HostSecretPayload;
125
+ type ModelBinding = {
126
+ provider: string;
127
+ model: string;
128
+ baseUrl?: string;
129
+ settings?: CustomModelSettings;
130
+ authType: "api_key" | "oauth";
131
+ };
132
+ type HostSecretPayload = {
133
+ apiKey?: string;
134
+ env?: Record<string, string>;
135
+ oauth?: HostModelSecret["credential"];
136
+ };
137
+ export declare function modelBinding(row: VaultCredentialRow): ModelBinding;
138
+ export declare function hostModelAad(provider: string, model: string): Buffer;
139
+ export {};