@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
@@ -0,0 +1,127 @@
1
+ /**
2
+ * The Runtime's requests to Action endpoints (design: Action endpoints §8.3): one POST, no
3
+ * redirects, a bounded answer, and the Host's address policy checked on the address actually
4
+ * connected to, so a DNS answer cannot steer a delivery somewhere the Host forbids.
5
+ *
6
+ * The result says whether anything reached the endpoint. A request that failed before its body
7
+ * was flushed (refused connection, unknown host, TLS failure, a refused address) was `not_sent`
8
+ * and is safe to send again, even for a tool; one that failed after (reset, timeout) is `lost`.
9
+ */
10
+ import { lookup as dnsLookup } from "node:dns";
11
+ import { request as httpRequest } from "node:http";
12
+ import { request as httpsRequest } from "node:https";
13
+ import { BlockList, isIP } from "node:net";
14
+ /** The largest answer read from an endpoint. */
15
+ export const MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
16
+ const PRIVATE = new BlockList();
17
+ for (const [network, prefix] of [
18
+ ["0.0.0.0", 8],
19
+ ["10.0.0.0", 8],
20
+ ["100.64.0.0", 10],
21
+ ["127.0.0.0", 8],
22
+ ["169.254.0.0", 16],
23
+ ["172.16.0.0", 12],
24
+ ["192.168.0.0", 16],
25
+ ])
26
+ PRIVATE.addSubnet(network, prefix, "ipv4");
27
+ for (const [network, prefix] of [
28
+ ["::", 128],
29
+ ["::1", 128],
30
+ ["fc00::", 7],
31
+ ["fe80::", 10],
32
+ ])
33
+ PRIVATE.addSubnet(network, prefix, "ipv6");
34
+ /** True when `address` is private, loopback, link-local or unspecified (IPv4-mapped included). */
35
+ export function isPrivateAddress(address) {
36
+ const mapped = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/i.exec(address);
37
+ if (mapped)
38
+ return PRIVATE.check(mapped[1], "ipv4");
39
+ const family = isIP(address);
40
+ if (family === 0)
41
+ return false;
42
+ return PRIVATE.check(address, family === 4 ? "ipv4" : "ipv6");
43
+ }
44
+ class RefusedAddress extends Error {
45
+ code = "ENDPOINT_ADDRESS_REFUSED";
46
+ }
47
+ /** Why the Host refuses `url`, or `undefined` when it may be called. */
48
+ export function refusal(url, policy) {
49
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && policy.allowHttp !== false))
50
+ return `This Runtime calls only ${policy.allowHttp === false ? "https" : "http and https"} endpoints`;
51
+ const host = url.hostname.replace(/^\[|\]$/g, "");
52
+ if (policy.privateAddresses === "refuse" && isIP(host) && isPrivateAddress(host))
53
+ return "This Runtime does not call private addresses; expose the endpoint through a public URL (a tunnel, for a local app)";
54
+ return undefined;
55
+ }
56
+ /** POSTs `body` to `url` under `policy`. Never throws; `signal` aborts the request. */
57
+ export async function post(target, body, headers, options) {
58
+ const { policy, signal } = options;
59
+ const limit = options.maxResponseBytes ?? MAX_RESPONSE_BYTES;
60
+ const url = new URL(target);
61
+ const refused = refusal(url, policy);
62
+ if (refused)
63
+ return { kind: "not_sent", code: "ENDPOINT_ADDRESS_REFUSED", message: refused };
64
+ const originalHost = url.host;
65
+ if (policy.loopback === "docker-host" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))
66
+ url.hostname = "host.docker.internal";
67
+ const lookup = (hostname, lookupOptions, callback) => dnsLookup(hostname, { ...lookupOptions, all: true }, (error, addresses) => {
68
+ if (error)
69
+ return callback(error, []);
70
+ const allowed = policy.privateAddresses === "refuse"
71
+ ? addresses.filter((a) => !isPrivateAddress(a.address))
72
+ : addresses;
73
+ if (allowed.length === 0)
74
+ return callback(new RefusedAddress(`${hostname} resolves only to private addresses, which this Runtime does not call`), []);
75
+ if (lookupOptions.all)
76
+ return callback(null, allowed);
77
+ return callback(null, allowed[0].address, allowed[0].family);
78
+ });
79
+ return new Promise((resolve) => {
80
+ let sent = false;
81
+ let settled = false;
82
+ const finish = (result) => {
83
+ if (settled)
84
+ return;
85
+ settled = true;
86
+ resolve(result);
87
+ };
88
+ const failed = (error) => finish({
89
+ kind: sent ? "lost" : "not_sent",
90
+ code: signal.aborted ? "ABORTED" : (error.code ?? error.name),
91
+ message: signal.aborted ? "The delivery was aborted" : error.message,
92
+ });
93
+ const request = (url.protocol === "https:" ? httpsRequest : httpRequest)(url, {
94
+ method: "POST",
95
+ headers: {
96
+ ...headers,
97
+ host: originalHost,
98
+ "content-type": "application/json",
99
+ "content-length": String(Buffer.byteLength(body)),
100
+ },
101
+ lookup: lookup,
102
+ signal,
103
+ }, (response) => {
104
+ const status = response.statusCode ?? 0;
105
+ const chunks = [];
106
+ let size = 0;
107
+ response.on("data", (chunk) => {
108
+ size += chunk.length;
109
+ if (size > limit) {
110
+ // Settle first: destroying the answer reports an error of its own.
111
+ finish({ kind: "too_large", status });
112
+ response.destroy();
113
+ return;
114
+ }
115
+ chunks.push(chunk);
116
+ });
117
+ response.on("end", () => finish({ kind: "response", status, headers: response.headers, body: Buffer.concat(chunks) }));
118
+ response.on("error", failed);
119
+ response.on("aborted", () => failed(Object.assign(new Error("The answer was cut off"), { code: "ECONNRESET" })));
120
+ });
121
+ request.on("finish", () => {
122
+ sent = true;
123
+ });
124
+ request.on("error", failed);
125
+ request.end(body);
126
+ });
127
+ }
@@ -1,28 +1,23 @@
1
- import type { Logger, TenantPaths } from "./types.js";
1
+ import type { TenantPaths } from "./types.js";
2
2
  export interface HostPaths {
3
3
  root: string;
4
4
  config: string;
5
5
  credentials: string;
6
6
  home: string;
7
7
  tmp: string;
8
- tenants: string;
9
- trash: string;
8
+ /** The Tenant directory (`tenantPaths`). */
9
+ tenant: string;
10
10
  }
11
11
  /** Host layout under NYLORUN_HOME / ~/.nylorun. */
12
12
  export declare function hostPaths(hostRoot: string): HostPaths;
13
13
  /**
14
- * Absolute Tenant directory paths under `<hostRoot>/tenants/<tenantId>/`.
15
- * Validates the id and asserts containment after realpath.
14
+ * Absolute paths of the Tenant directory, `<hostRoot>/tenant/`: a Host serves one Tenant
15
+ * (tenancy.md §1), so the directory has no id segment. Asserts that it stays inside the Host
16
+ * root after realpath.
16
17
  *
17
- * The directory holds what stays on the Host (the Tenant's data is its Postgres schema):
18
- * `vault-kek`, `home/`, `tmp/`, `sandboxes/`, `plugin-data/`, `logs/tenant.log`.
18
+ * The directory holds what stays on the Host (the Tenant's data is its database): `home/`,
19
+ * `tmp/`, `sandboxes/`, `plugin-data/`, `logs/tenant.log`. The vault key is the one exception:
20
+ * it lives beside the Tenant directory, in `<hostRoot>/keys/vault-kek`, which the local stack
21
+ * mounts only into the gateway (F4.2), never into the runtime container.
19
22
  */
20
- export declare function tenantPaths(hostRoot: string, tenantId: string): TenantPaths;
21
- /**
22
- * Moves every Tenant directory that holds a `tenant.sqlite` to `trash/`, logging each.
23
- *
24
- * Those are beta Tenants from the SQLite Runtime. They are not migrated (recreate them); a
25
- * Postgres Tenant's directory never holds that file. Moving them at Host start keeps a new
26
- * Postgres Tenant with the same id from sharing the old directory. Returns the moved ids.
27
- */
28
- export declare function trashSqliteTenants(hostRoot: string, logger: Logger): string[];
23
+ export declare function tenantPaths(hostRoot: string): TenantPaths;
@@ -1,14 +1,9 @@
1
- import { existsSync, mkdirSync, readdirSync, realpathSync, renameSync, } from "node:fs";
1
+ import { existsSync, mkdirSync, realpathSync } from "node:fs";
2
2
  import { join, resolve, sep } from "node:path";
3
- import { isTenantId } from "@nylorun/core/compatibility";
4
- function ensureDir(path) {
5
- mkdirSync(path, { recursive: true });
6
- return realpathSync(path);
7
- }
8
3
  function assertContained(parent, child) {
9
4
  const root = parent.endsWith(sep) ? parent : parent + sep;
10
5
  if (child !== parent && !child.startsWith(root)) {
11
- throw new Error(`Path ${child} escapes host tenants root ${parent}`);
6
+ throw new Error(`Path ${child} escapes the Host root ${parent}`);
12
7
  }
13
8
  }
14
9
  /** Host layout under NYLORUN_HOME / ~/.nylorun. */
@@ -20,72 +15,33 @@ export function hostPaths(hostRoot) {
20
15
  credentials: join(root, "host-credentials.json"),
21
16
  home: join(root, "home"),
22
17
  tmp: join(root, "tmp"),
23
- tenants: join(root, "tenants"),
24
- trash: join(root, "trash"),
18
+ tenant: join(root, "tenant"),
25
19
  };
26
20
  }
27
21
  /**
28
- * Absolute Tenant directory paths under `<hostRoot>/tenants/<tenantId>/`.
29
- * Validates the id and asserts containment after realpath.
22
+ * Absolute paths of the Tenant directory, `<hostRoot>/tenant/`: a Host serves one Tenant
23
+ * (tenancy.md §1), so the directory has no id segment. Asserts that it stays inside the Host
24
+ * root after realpath.
30
25
  *
31
- * The directory holds what stays on the Host (the Tenant's data is its Postgres schema):
32
- * `vault-kek`, `home/`, `tmp/`, `sandboxes/`, `plugin-data/`, `logs/tenant.log`.
26
+ * The directory holds what stays on the Host (the Tenant's data is its database): `home/`,
27
+ * `tmp/`, `sandboxes/`, `plugin-data/`, `logs/tenant.log`. The vault key is the one exception:
28
+ * it lives beside the Tenant directory, in `<hostRoot>/keys/vault-kek`, which the local stack
29
+ * mounts only into the gateway (F4.2), never into the runtime container.
33
30
  */
34
- export function tenantPaths(hostRoot, tenantId) {
35
- if (!isTenantId(tenantId)) {
36
- throw new Error(`Invalid tenant id: ${String(tenantId)}`);
37
- }
38
- const host = hostPaths(hostRoot);
39
- const tenantsRoot = ensureDir(host.tenants);
40
- const root = join(tenantsRoot, tenantId);
41
- // Containment before the Tenant directory exists: resolve under real tenants root.
42
- const resolvedRoot = resolve(tenantsRoot, tenantId);
43
- assertContained(tenantsRoot, resolvedRoot);
44
- if (existsSync(root)) {
45
- assertContained(tenantsRoot, realpathSync(root));
46
- }
31
+ export function tenantPaths(hostRoot) {
32
+ mkdirSync(hostRoot, { recursive: true });
33
+ const host = realpathSync(hostRoot);
34
+ const root = join(host, "tenant");
35
+ if (existsSync(root))
36
+ assertContained(host, realpathSync(root));
47
37
  return {
48
- root: resolvedRoot,
49
- kek: join(resolvedRoot, "vault-kek"),
50
- home: join(resolvedRoot, "home"),
51
- tmp: join(resolvedRoot, "tmp"),
52
- sandboxes: join(resolvedRoot, "sandboxes"),
53
- pluginData: join(resolvedRoot, "plugin-data"),
54
- logs: join(resolvedRoot, "logs"),
55
- log: join(resolvedRoot, "logs", "tenant.log"),
38
+ root,
39
+ kek: join(host, "keys", "vault-kek"),
40
+ home: join(root, "home"),
41
+ tmp: join(root, "tmp"),
42
+ sandboxes: join(root, "sandboxes"),
43
+ pluginData: join(root, "plugin-data"),
44
+ logs: join(root, "logs"),
45
+ log: join(root, "logs", "tenant.log"),
56
46
  };
57
47
  }
58
- /** The database file of a Tenant from before the Postgres Session Store. */
59
- const SQLITE_DATABASE = "tenant.sqlite";
60
- /**
61
- * Moves every Tenant directory that holds a `tenant.sqlite` to `trash/`, logging each.
62
- *
63
- * Those are beta Tenants from the SQLite Runtime. They are not migrated (recreate them); a
64
- * Postgres Tenant's directory never holds that file. Moving them at Host start keeps a new
65
- * Postgres Tenant with the same id from sharing the old directory. Returns the moved ids.
66
- */
67
- export function trashSqliteTenants(hostRoot, logger) {
68
- const host = hostPaths(hostRoot);
69
- if (!existsSync(host.tenants))
70
- return [];
71
- const moved = [];
72
- const stamp = new Date().toISOString().replaceAll(":", "-");
73
- for (const entry of readdirSync(host.tenants, { withFileTypes: true })) {
74
- if (!entry.isDirectory())
75
- continue;
76
- const directory = join(host.tenants, entry.name);
77
- if (!existsSync(join(directory, SQLITE_DATABASE)))
78
- continue;
79
- mkdirSync(host.trash, { recursive: true });
80
- const destination = join(host.trash, `${entry.name}-sqlite-${stamp}`);
81
- renameSync(directory, destination);
82
- moved.push(entry.name);
83
- logger.warn("sqlite_tenant_moved_to_trash", {
84
- tenant: entry.name,
85
- from: directory,
86
- to: destination,
87
- reason: "SQLite Tenants are not migrated to the Postgres Session Store; recreate the Tenant",
88
- });
89
- }
90
- return moved;
91
- }
@@ -1,10 +1,8 @@
1
- /** Run async work over items with a fixed concurrency limit. */
2
- export declare function mapPool<T, R>(items: readonly T[], concurrency: number, worker: (item: T, index: number) => Promise<R>): Promise<R[]>;
3
1
  export declare class TimeoutError extends Error {
4
2
  constructor(message?: string);
5
3
  }
6
4
  /**
7
5
  * Race a promise against a timeout. On timeout the late result is handed to
8
- * `onLate` (Risk R6: mark quarantined and close a late handle).
6
+ * `onLate` (Risk R6: the module closes a handle that opened too late).
9
7
  */
10
8
  export declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number, onLate?: (late: Promise<T>) => void): Promise<T>;
@@ -1,21 +1,3 @@
1
- /** Run async work over items with a fixed concurrency limit. */
2
- export async function mapPool(items, concurrency, worker) {
3
- if (items.length === 0)
4
- return [];
5
- const limit = Math.max(1, Math.min(concurrency, items.length));
6
- const results = new Array(items.length);
7
- let next = 0;
8
- async function run() {
9
- for (;;) {
10
- const index = next++;
11
- if (index >= items.length)
12
- return;
13
- results[index] = await worker(items[index], index);
14
- }
15
- }
16
- await Promise.all(Array.from({ length: limit }, () => run()));
17
- return results;
18
- }
19
1
  export class TimeoutError extends Error {
20
2
  constructor(message = "operation timed out") {
21
3
  super(message);
@@ -24,7 +6,7 @@ export class TimeoutError extends Error {
24
6
  }
25
7
  /**
26
8
  * Race a promise against a timeout. On timeout the late result is handed to
27
- * `onLate` (Risk R6: mark quarantined and close a late handle).
9
+ * `onLate` (Risk R6: the module closes a handle that opened too late).
28
10
  */
29
11
  export async function withTimeout(promise, timeoutMs, onLate) {
30
12
  let timer;
@@ -1,16 +1,28 @@
1
- import type { Tx } from "../store/types.js";
2
- import type { BootstrapPrincipal } from "./types.js";
1
+ import type { InitialPrincipal } from "../store/postgres/tenant.js";
3
2
  export type { PrincipalRow } from "../store/types.js";
4
3
  /** Principal id of the Studio key derived from the admin key. */
5
4
  export declare const STUDIO_PRINCIPAL_ID = "studio";
5
+ /** The derived principal Projects use (`PROJECT_PRINCIPAL_ID` in `@nylorun/admin`). */
6
+ export declare const PROJECT_PRINCIPAL_ID = "project";
7
+ /** The derived principals a Host registers when not told otherwise. */
8
+ export declare const DEFAULT_DERIVED_PRINCIPALS: readonly string[];
9
+ /** The Studio key of `tenantId`: HMAC-SHA256 of the admin key. */
10
+ export declare function deriveStudioKey(adminKey: string, tenantId: string): string;
11
+ /** The key of derived principal `principalId` on `tenantId`: HMAC-SHA256 of the admin key. */
12
+ export declare function deriveTenantKey(adminKey: string, tenantId: string, principalId: string): string;
13
+ /** Whether `id` may name a derived principal: the client's name, never `studio`. */
14
+ export declare function isDerivedPrincipalId(id: string): boolean;
6
15
  /**
7
- * Writes the application principal and idempotency key in the caller's transaction, plus
8
- * principal `studio` when the bootstrap carries a Studio credential hash.
9
- * Exported for WS-B's `TenantStore.create` (A3).
16
+ * The principals a Host's Tenant is created with: `studio`, each derived principal, and an
17
+ * application principal when a key is given (the ephemeral Runtime). Keys are hashed here.
10
18
  */
11
- export declare function bootstrapPrincipal(t: Tx, bootstrap: BootstrapPrincipal, now?: Date): Promise<void>;
12
- /**
13
- * Whether the stored bootstrap, Studio and derived principals equal `bootstrap`. A retried
14
- * create names the same derived principals, so each must be stored with its hash.
15
- */
16
- export declare function bootstrapPrincipalMatches(t: Tx, bootstrap: BootstrapPrincipal): Promise<boolean>;
19
+ export declare function hostPrincipals(options: {
20
+ adminKey: string;
21
+ derived?: readonly string[];
22
+ application?: {
23
+ principalId: string;
24
+ key: string;
25
+ };
26
+ /** Registers `studio` with this hash instead of the admin key's derivation. */
27
+ studioCredentialHash?: string;
28
+ }): (tenantId: string) => InitialPrincipal[];
@@ -1,56 +1,68 @@
1
+ /**
2
+ * The principals the Host gives its Tenant when it creates it (tenancy.md §4, plan P9): the
3
+ * Studio principal and the derived principals, whose keys the admin key derives, so the
4
+ * clients holding the admin key (Studio, `nylorun start`, Babai) store none. The Tenant keeps
5
+ * only their hashes.
6
+ *
7
+ * The derivations are the ones `@nylorun/admin` computes on the client side
8
+ * (`admin/src/derived-credentials.ts`: `deriveStudioToken`, `deriveTenantKey`); both sides
9
+ * must stay byte for byte the same.
10
+ */
11
+ import { createHmac } from "node:crypto";
12
+ import { DERIVED_PRINCIPAL_ID_PATTERN } from "@nylorun/core/compatibility";
13
+ import { hashToken } from "../core/bearer.js";
1
14
  /** Principal id of the Studio key derived from the admin key. */
2
15
  export const STUDIO_PRINCIPAL_ID = "studio";
16
+ /** The derived principal Projects use (`PROJECT_PRINCIPAL_ID` in `@nylorun/admin`). */
17
+ export const PROJECT_PRINCIPAL_ID = "project";
18
+ /** The derived principals a Host registers when not told otherwise. */
19
+ export const DEFAULT_DERIVED_PRINCIPALS = [PROJECT_PRINCIPAL_ID];
20
+ /** The Studio key of `tenantId`: HMAC-SHA256 of the admin key. */
21
+ export function deriveStudioKey(adminKey, tenantId) {
22
+ return hmac(adminKey, ["nylorun/studio/v1", tenantId]);
23
+ }
24
+ /** The key of derived principal `principalId` on `tenantId`: HMAC-SHA256 of the admin key. */
25
+ export function deriveTenantKey(adminKey, tenantId, principalId) {
26
+ return hmac(adminKey, ["nylorun/principal/v1", principalId, tenantId]);
27
+ }
28
+ /** Whether `id` may name a derived principal: the client's name, never `studio`. */
29
+ export function isDerivedPrincipalId(id) {
30
+ return DERIVED_PRINCIPAL_ID_PATTERN.test(id) && id !== STUDIO_PRINCIPAL_ID;
31
+ }
3
32
  /**
4
- * Writes the application principal and idempotency key in the caller's transaction, plus
5
- * principal `studio` when the bootstrap carries a Studio credential hash.
6
- * Exported for WS-B's `TenantStore.create` (A3).
33
+ * The principals a Host's Tenant is created with: `studio`, each derived principal, and an
34
+ * application principal when a key is given (the ephemeral Runtime). Keys are hashed here.
7
35
  */
8
- export async function bootstrapPrincipal(t, bootstrap, now = new Date()) {
9
- const createdAt = now.toISOString();
10
- await t.insertPrincipal({
11
- id: bootstrap.principalId,
12
- role: "application",
13
- tokenHash: bootstrap.credentialHash,
14
- idempotencyKey: bootstrap.idempotencyKey,
15
- createdAt,
16
- });
17
- if (bootstrap.studioCredentialHash)
18
- await t.insertPrincipal({
36
+ export function hostPrincipals(options) {
37
+ const derived = options.derived ?? DEFAULT_DERIVED_PRINCIPALS;
38
+ for (const id of derived)
39
+ if (!isDerivedPrincipalId(id))
40
+ throw new Error(`Derived principal id '${id}' must match ${DERIVED_PRINCIPAL_ID_PATTERN} and not be '${STUDIO_PRINCIPAL_ID}'`);
41
+ return (tenantId) => [
42
+ ...(options.application
43
+ ? [
44
+ {
45
+ id: options.application.principalId,
46
+ credentialHash: hashToken(options.application.key),
47
+ },
48
+ ]
49
+ : []),
50
+ {
19
51
  id: STUDIO_PRINCIPAL_ID,
20
- role: "application",
21
- tokenHash: bootstrap.studioCredentialHash,
22
- idempotencyKey: null,
23
- createdAt,
24
- });
25
- for (const principal of bootstrap.derivedPrincipals ?? [])
26
- await t.insertPrincipal({
27
- id: principal.id,
28
- role: "application",
29
- tokenHash: principal.credentialHash,
30
- idempotencyKey: null,
31
- createdAt,
32
- });
52
+ credentialHash: options.studioCredentialHash ??
53
+ hashToken(deriveStudioKey(options.adminKey, tenantId)),
54
+ },
55
+ ...[...new Set(derived)].map((id) => ({
56
+ id,
57
+ credentialHash: hashToken(deriveTenantKey(options.adminKey, tenantId, id)),
58
+ })),
59
+ ];
33
60
  }
34
- /**
35
- * Whether the stored bootstrap, Studio and derived principals equal `bootstrap`. A retried
36
- * create names the same derived principals, so each must be stored with its hash.
37
- */
38
- export async function bootstrapPrincipalMatches(t, bootstrap) {
39
- const row = await t.principalById(bootstrap.principalId);
40
- if (!row ||
41
- row.role !== "application" ||
42
- row.tokenHash !== bootstrap.credentialHash ||
43
- row.idempotencyKey !== bootstrap.idempotencyKey)
44
- return false;
45
- const studio = await t.principalById(STUDIO_PRINCIPAL_ID);
46
- if ((studio?.role === "application" ? studio.tokenHash : undefined) !==
47
- bootstrap.studioCredentialHash)
48
- return false;
49
- for (const principal of bootstrap.derivedPrincipals ?? []) {
50
- const stored = await t.principalById(principal.id);
51
- if (stored?.role !== "application" ||
52
- stored.tokenHash !== principal.credentialHash)
53
- return false;
54
- }
55
- return true;
61
+ /** HMAC-SHA256 (hex) of `parts` joined by NUL bytes, keyed with the admin key. */
62
+ function hmac(adminKey, parts) {
63
+ const message = Buffer.concat(parts.flatMap((part, index) => [
64
+ ...(index > 0 ? [Buffer.from([0])] : []),
65
+ Buffer.from(part, "utf8"),
66
+ ]));
67
+ return createHmac("sha256", Buffer.from(adminKey, "utf8")).update(message).digest("hex");
56
68
  }
@@ -1,17 +1,13 @@
1
1
  import type { ResetScope, SessionStore } from "../store/types.js";
2
- import type { ExecutorRegistry } from "../core/executors.js";
3
2
  import type { SandboxManager } from "../sandbox/manager.js";
4
3
  import type { TenantPaths } from "./types.js";
5
4
  export type { ResetScope } from "../store/types.js";
6
5
  export interface ResetTenantContext {
7
6
  store: SessionStore;
8
- registry: ExecutorRegistry;
9
7
  sandbox: SandboxManager;
10
8
  paths: TenantPaths;
11
9
  /** Clear in-memory session observers after the store wipe. */
12
10
  clearSessionState: () => void;
13
- /** Clear executor SSE streams when registrations are removed. */
14
- clearExecutorStreams: () => void;
15
11
  }
16
12
  /**
17
13
  * Reset Tenant durable state for `scope` (A17).
@@ -42,13 +42,8 @@ export async function resetTenant(ctx, scope) {
42
42
  await ctx.sandbox.reconcile(() => false);
43
43
  }
44
44
  await ctx.store.tx((t) => t.reset(scope));
45
- if (clearAll)
46
- for (const row of ctx.registry.list())
47
- ctx.registry.remove(row.agentId);
48
45
  if (clearSessions)
49
46
  ctx.clearSessionState();
50
- if (clearAll)
51
- ctx.clearExecutorStreams();
52
47
  if (clearSandboxes)
53
48
  replaceDirectory(ctx.paths.sandboxes);
54
49
  if (clearAll) {
@@ -1,30 +1,31 @@
1
- /**
2
- * Composition root of an open Tenant: validates the config, opens the Session Store and the
3
- * vault key, builds the services and the `TenantContext`, wires the seams, registers the
4
- * Tenant's Worker handlers and arms its sweep, and implements `TenantHandle` (handle,
5
- * summary, drain, close) by delegating to the Tenant modules.
6
- *
7
- * Seams wired here: `wireStreams()` connects the store's commits to Durable Streams (the
8
- * relay, and the history, SSE, work and control readers); `wake` goes to the
9
- * `DurableExecution`, whose handlers (`worker.ts`) call `advance` and `sweep`; `abortLocal`
10
- * aborts an advance running on this process. The sweep also drains the outbox.
11
- *
12
- * Execution: the Host passes one `TenantExecution` for every Tenant it opens
13
- * (`TenantOpenHooks.execution`); without one, the Tenant runs its own in-process
14
- * `MemoryExecution`. No lock file: several processes may open a Tenant, and ownership of each
15
- * session (§10.6) keeps its advances apart.
16
- */
17
- import type { ServerResponse, IncomingMessage } from "node:http";
18
1
  import type { TenantEnvelope } from "@nylorun/core/contracts";
19
2
  import { type ModelProvider } from "../core/provider.js";
3
+ import type { ModelGate } from "../gates/model-gate.js";
20
4
  import { type AuthorizeResult } from "../vault/service.js";
21
5
  import type { DurableStreams } from "../streams/types.js";
22
- import type { OpenedTenant, TenantConfig, TenantHandle, TenantSummary } from "./types.js";
6
+ import type { NodeBindings, OpenedTenant, TenantConfig, TenantHandle, TenantSummary } from "./types.js";
23
7
  import { type StreamsStatus } from "./streams.js";
24
8
  import { type TenantExecution, type TenantWorker } from "./worker.js";
9
+ import { type ToolGate } from "../gates/tool-gate.js";
10
+ import { type Keys } from "../keys/keys.js";
25
11
  /** TENANTS-CCR: test/injection hooks until TenantConfig gains them. */
26
12
  export type TenantOpenHooks = {
27
13
  modelProvider?: ModelProvider;
14
+ /**
15
+ * Serves the Tenant's vault-backed model calls (the gates service's client). Without one,
16
+ * the Tenant calls the model in this process.
17
+ */
18
+ modelGate?: ModelGate;
19
+ /**
20
+ * Serves the Tenant's remote MCP servers and Action deliveries (the gates service's client).
21
+ * Without one, the Tenant opens them and POSTs deliveries in this process.
22
+ */
23
+ toolGate?: ToolGate;
24
+ /**
25
+ * Runs vault writes and token signing (the `keys` service's client, F4.2). With one, this
26
+ * process never reads, creates or holds the vault key. Without one, it does them here.
27
+ */
28
+ keys?: Keys;
28
29
  vaultKek?: Buffer | string | null;
29
30
  /** When true, create the KEK file on first vault write (tests / new Tenants). */
30
31
  createKekIfMissing?: boolean;
@@ -42,6 +43,13 @@ export type TenantOpenHooks = {
42
43
  * survive a restart (tests, and a Host without S2).
43
44
  */
44
45
  streams?: DurableStreams;
46
+ /**
47
+ * The Host's stream relay feeds `streams` from the record. Without it the
48
+ * Tenant relays its own commits.
49
+ */
50
+ hostRelay?: boolean;
51
+ /** How long a retired stream basin is kept after a reset (tests). Default 60 s. */
52
+ retireGraceMs?: number;
45
53
  } & OpenedTenant;
46
54
  export declare class TenantRuntime implements TenantHandle {
47
55
  private readonly ctx;
@@ -53,7 +61,7 @@ export declare class TenantRuntime implements TenantHandle {
53
61
  private readonly closeGraceMs;
54
62
  private constructor();
55
63
  static open(config: TenantConfig, hooks: TenantOpenHooks): Promise<TenantRuntime>;
56
- handle(request: IncomingMessage, response: ServerResponse, url?: URL): Promise<void>;
64
+ fetch(request: Request, node: NodeBindings): Promise<Response>;
57
65
  authorize(sessionId: string, request: {
58
66
  url: string;
59
67
  serverName?: string;
@@ -67,7 +75,7 @@ export declare class TenantRuntime implements TenantHandle {
67
75
  abortLocal(sessionId: string): void;
68
76
  /**
69
77
  * The Durable Streams seam's status for Tenant status and readiness: S2 reachability, the
70
- * basin, outbox depth and relay lag (`streamsStatus` in `tenant/streams.ts`).
78
+ * basin, its generation and the Tenant's relay (`streamsStatus` in `tenant/streams.ts`).
71
79
  */
72
80
  streamsStatus(): Promise<StreamsStatus>;
73
81
  /** Adds a callback to the Tenant sweep. Returns a function that removes it. */