@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
package/dist/host/main.js CHANGED
@@ -2,33 +2,48 @@
2
2
  * Runtime Host process entry (`@nylorun/runtime/server`).
3
3
  *
4
4
  * Reads `NYLORUN_HOME` (falls back to `~/.nylorun`), and hands an environment
5
- * snapshot and argv to `parseStackConfig` (container listen mode, `--role`,
5
+ * snapshot and argv to `parseStackConfig` (container listen mode, `--service`,
6
6
  * stack endpoints). Absolute Host root is resolved once and passed down.
7
7
  *
8
8
  * Composition: `createInfra` builds the Postgres pool, Durable Session
9
9
  * Execution and Durable Streams from the endpoints; `createHostExecution`
10
- * shares one execution across the Tenants; the Tenant store is Postgres
11
- * (`NYLORUN_DATABASE_URL`, required); `/ready` reports the infrastructure
12
- * checks. See the startup order in `main()`. Tests compose a Host without this
10
+ * builds the process's execution; the Host serves the one Tenant its Postgres
11
+ * database holds (`NYLORUN_DATABASE_URL`, required), and creates it there on
12
+ * first start (`NYLORUN_TENANT_ID`, `NYLORUN_TENANT_NAME`,
13
+ * `NYLORUN_DERIVED_PRINCIPALS`; tenancy.md §4); with S2, a process running core
14
+ * runs the stream relay, which feeds the Tenant's streams from the record over
15
+ * logical replication (one process at a time holds the slot); `/ready` reports
16
+ * the Tenant and the infrastructure checks. A process running the gates service (the local
17
+ * stack's `gateway` container) starts only the gate (`runGates`): it needs
18
+ * neither host.json nor host-credentials.json. See the startup order in `main()`. Tests compose a Host without this
13
19
  * entry, with `createHost` and an injected Tenant module.
14
20
  */
15
21
  import { readFileSync, mkdirSync, existsSync } from "node:fs";
16
22
  import { createRequire } from "node:module";
17
23
  import { homedir } from "node:os";
18
24
  import { resolve } from "node:path";
19
- import { hostPaths, trashSqliteTenants } from "../tenant/paths.js";
25
+ import { hostPaths } from "../tenant/paths.js";
20
26
  import { createTenantModule } from "../tenant/module.js";
21
- import { createPostgresTenantStore } from "../tenant/store-pg.js";
27
+ import { hostPrincipals } from "../tenant/principals.js";
28
+ import { createPostgresTenantOpener } from "../tenant/store-pg.js";
22
29
  import { openTenantRuntime } from "../tenant/runtime.js";
23
- import { createTenantStreams, deleteTenantStreams } from "../tenant/streams.js";
30
+ import { createPgoutputSource } from "../adapters/replication/pgoutput.js";
31
+ import { assertLogicalReplication } from "../store/postgres/connect.js";
32
+ import { createPostgresRecordReader } from "../store/postgres/record.js";
33
+ import { createStreamRelay } from "../streams/relay/core.js";
24
34
  import { configForFactory } from "./config-for.js";
25
35
  import { createHost } from "./create-host.js";
26
36
  import { createHostExecution } from "./execution.js";
27
37
  import { baselineEnvironment } from "./environment.js";
28
38
  import { HostListenError, EXIT_PORT_IN_USE, EXIT_NON_LOOPBACK, } from "./http.js";
29
39
  import { createHostLogger } from "./logger.js";
30
- import { describeEndpoints, parseStackConfig } from "./stack-config.js";
40
+ import { describeEndpoints, describeServices, parseStackConfig, } from "./stack-config.js";
31
41
  import { createExecution, createInfra } from "../infra/index.js";
42
+ import { createDatabase } from "../infra/database.js";
43
+ import { startGates } from "./gates.js";
44
+ import { httpToolGate } from "../gates/tool-client.js";
45
+ import { httpKeys } from "../keys/client.js";
46
+ import { httpModelGate } from "../gates/http-client.js";
32
47
  const nodeRequire = createRequire(import.meta.url);
33
48
  function coreVersion() {
34
49
  try {
@@ -46,20 +61,74 @@ function resolveHostRoot() {
46
61
  const root = fromEnv && fromEnv.length > 0 ? fromEnv : resolve(homedir(), ".nylorun");
47
62
  return resolve(root);
48
63
  }
64
+ /** The slot's lag in WAL bytes, when the source can tell. */
65
+ async function lagOf(source) {
66
+ const lagBytes = await source.lag?.().catch(() => undefined);
67
+ return lagBytes === undefined ? {} : { lagBytes };
68
+ }
69
+ /**
70
+ * The gates service: the Model Gate's listener over the Postgres pool and the Host's tenant
71
+ * directory. Writes nothing to the Host root (the local stack mounts it read-only).
72
+ */
73
+ async function runGates(stack) {
74
+ const gates = stack.gates;
75
+ if (!stack.endpoints.databaseUrl)
76
+ throw new Error("NYLORUN_DATABASE_URL is required for --service gates: the gate reads Tenant vaults from Postgres (`nylorun start` sets it)");
77
+ const logger = createHostLogger();
78
+ logger.info("host_stack_config", {
79
+ services: [...stack.services],
80
+ ...(stack.packing ? { packing: stack.packing } : {}),
81
+ endpoints: describeEndpoints(stack.endpoints),
82
+ });
83
+ const database = createDatabase(stack);
84
+ let server;
85
+ try {
86
+ server = await startGates({
87
+ gates,
88
+ database,
89
+ hostRoot: resolveHostRoot(),
90
+ logger,
91
+ ...(stack.delivery ? { delivery: stack.delivery } : {}),
92
+ ...(stack.services.has("keys") ? { keys: true } : {}),
93
+ });
94
+ }
95
+ catch (error) {
96
+ await database.end({ timeout: 5 });
97
+ if (error instanceof HostListenError) {
98
+ logger.error("listen_failed", { message: error.message, exitCode: error.exitCode });
99
+ process.exitCode = error.exitCode;
100
+ return;
101
+ }
102
+ throw error;
103
+ }
104
+ logger.info("gates_ready", { url: server.url });
105
+ let stopping = false;
106
+ for (const signal of ["SIGINT", "SIGTERM"])
107
+ process.on(signal, () => {
108
+ if (stopping)
109
+ return;
110
+ stopping = true;
111
+ void server
112
+ .close()
113
+ .then(() => database.end({ timeout: 5 }))
114
+ .finally(() => process.exit(0));
115
+ });
116
+ }
49
117
  export async function main() {
50
118
  const stack = parseStackConfig(process.env, process.argv.slice(2));
119
+ if (stack.services.has("gates") || stack.services.has("keys"))
120
+ return runGates(stack);
51
121
  const hostRoot = resolveHostRoot();
52
122
  const paths = hostPaths(hostRoot);
53
123
  mkdirSync(paths.home, { recursive: true });
54
124
  mkdirSync(paths.tmp, { recursive: true });
55
- mkdirSync(paths.tenants, { recursive: true });
56
125
  if (!existsSync(paths.config)) {
57
126
  throw new Error(`Missing host.json at ${paths.config}; run \`nylorun start\` first`);
58
127
  }
59
128
  if (!existsSync(paths.credentials)) {
60
129
  throw new Error(`Missing host-credentials.json at ${paths.credentials}; run \`nylorun start\` first`);
61
130
  }
62
- // Tenants are Postgres schemas. Only a Host outside a container (a local
131
+ // The Tenant is a Postgres database. Only a Host outside a container (a local
63
132
  // development Host) may run without S2, on in-process streams.
64
133
  if (!stack.endpoints.databaseUrl) {
65
134
  throw new Error("NYLORUN_DATABASE_URL is required: the Postgres URL of the Session Store (`nylorun start` sets it)");
@@ -70,13 +139,20 @@ export async function main() {
70
139
  const config = loadJson(paths.config);
71
140
  const credentials = loadJson(paths.credentials);
72
141
  const logger = createHostLogger();
142
+ if (stack.deprecatedRole)
143
+ logger.warn("deprecated_flag", {
144
+ flag: "--role",
145
+ use: `--service ${describeServices(stack.services)}`,
146
+ });
73
147
  logger.info("host_stack_config", {
74
- role: stack.role,
148
+ services: [...stack.services],
149
+ ...(stack.packing ? { packing: stack.packing } : {}),
150
+ ...(stack.services.has("loop")
151
+ ? { modelGate: stack.modelGate ? stack.modelGate.url : "in-process" }
152
+ : {}),
75
153
  mode: stack.listen ? "container" : "local",
76
154
  endpoints: describeEndpoints(stack.endpoints),
77
155
  });
78
- // Tenants from the SQLite Runtime are not migrated: move them out of the way.
79
- trashSqliteTenants(hostRoot, logger);
80
156
  const infra = createInfra(stack, { logger });
81
157
  const database = infra.database;
82
158
  if (!database)
@@ -89,46 +165,86 @@ export async function main() {
89
165
  hostConfig: config,
90
166
  logger,
91
167
  baseline,
168
+ ...(stack.delivery ? { delivery: stack.delivery } : {}),
92
169
  });
93
- // One Durable Session Execution for every Tenant this process opens: Restate
94
- // when its endpoints are set, else the in-process memory execution. An
95
- // invocation for a Tenant that is not open here opens it on demand.
170
+ // The process's Durable Session Execution: Restate when its endpoints are set, else the
171
+ // in-process memory execution. An invocation that arrives while the Tenant opens waits for it.
96
172
  const hostExecution = createHostExecution({
97
173
  execution: infra.execution ?? createExecution(stack),
98
- role: stack.role,
174
+ services: stack.services,
99
175
  resolve: (tenantId) => module.worker(tenantId),
100
176
  logger,
101
177
  });
102
178
  // Durable Streams: S2 when configured (always in container mode). Without
103
- // them (a local Host only) each Tenant keeps in-process streams, whose history
179
+ // them (a local Host only) the Tenant keeps in-process streams, whose history
104
180
  // does not survive a restart.
105
181
  const streams = infra.streams;
106
- const store = createPostgresTenantStore({
107
- hostRoot,
108
- sql: database,
109
- configFor,
110
- logger,
111
- openRuntime: (tenantConfig, opened) => openTenantRuntime(tenantConfig, {
112
- execution: hostExecution.tenantExecution,
113
- ...(streams ? { streams } : {}),
114
- ...opened,
115
- }),
116
- });
182
+ // With S2, the stream relay feeds the Tenant's streams from the record. Every process
183
+ // running core runs one, once the Tenant is open (the relay reads its id); the replication
184
+ // slot lets exactly one be active. Without S2, the Tenant relays its own commits.
185
+ let relay;
186
+ let relayLag;
187
+ if (streams && stack.services.has("core"))
188
+ await assertLogicalReplication(database);
189
+ const startRelay = (tenantId) => {
190
+ if (!streams || !stack.services.has("core") || relay)
191
+ return;
192
+ const source = createPgoutputSource({
193
+ connectionString: stack.endpoints.databaseUrl,
194
+ tenantId,
195
+ log: (message, fields) => logger.info(message, fields),
196
+ });
197
+ relay = createStreamRelay({
198
+ source,
199
+ record: createPostgresRecordReader(database, { tenantId }),
200
+ streams,
201
+ log: (message, fields) => logger.info(message, fields),
202
+ });
203
+ const status = relay.status;
204
+ relayLag = async () => ({ ...status(), ...(await lagOf(source)) });
205
+ relay.start();
206
+ };
207
+ // With the gates service the Tenant's vault-backed model calls, remote MCP calls and Action
208
+ // deliveries cross it, and this process never reads a model or MCP credential.
209
+ const modelGate = stack.modelGate
210
+ ? httpModelGate({ url: stack.modelGate.url, token: stack.modelGate.token })
211
+ : undefined;
212
+ const toolGate = stack.modelGate
213
+ ? httpToolGate({ url: stack.modelGate.url, token: stack.modelGate.token })
214
+ : undefined;
215
+ // With the keys service, vault writes and token signing cross it, and this process never
216
+ // reads the vault key (F4.2).
217
+ const keys = stack.keys ? httpKeys({ url: stack.keys.url, token: stack.keys.token }) : undefined;
218
+ const tenantSettings = stack.tenant ?? { name: "default", derivedPrincipals: ["project"] };
117
219
  const module = createTenantModule({
118
- store,
220
+ open: createPostgresTenantOpener({
221
+ hostRoot,
222
+ sql: database,
223
+ create: {
224
+ ...(tenantSettings.id ? { tenantId: tenantSettings.id } : {}),
225
+ name: tenantSettings.name,
226
+ principals: hostPrincipals({
227
+ adminKey: credentials.adminKey,
228
+ derived: tenantSettings.derivedPrincipals,
229
+ }),
230
+ },
231
+ configFor,
232
+ logger,
233
+ openRuntime: (tenantConfig, opened) => openTenantRuntime(tenantConfig, {
234
+ execution: hostExecution.tenantExecution,
235
+ ...(modelGate ? { modelGate } : {}),
236
+ ...(toolGate ? { toolGate } : {}),
237
+ ...(keys ? { keys } : {}),
238
+ ...(streams ? { streams, hostRelay: true } : {}),
239
+ ...opened,
240
+ }),
241
+ }),
119
242
  logger,
120
- // A new Tenant's basin is created with it (opening it repairs a failure);
121
- // a deleted Tenant's sweep stops re-arming and its basin goes.
122
- ...(streams
123
- ? { onCreated: (tenantId) => createTenantStreams(streams, tenantId) }
124
- : {}),
125
- onDeleted: async (tenantId) => {
126
- const failed = (await Promise.allSettled([
127
- hostExecution.disarm(tenantId),
128
- ...(streams ? [deleteTenantStreams(streams, tenantId)] : []),
129
- ])).flatMap((result) => (result.status === "rejected" ? [result.reason] : []));
130
- if (failed.length > 0)
131
- throw new AggregateError(failed, "Deleted Tenant cleanup failed");
243
+ onOpen: (handle) => startRelay(handle.envelope.id),
244
+ relayStatus: async () => {
245
+ if (!relayLag)
246
+ throw new Error("no relay");
247
+ return relayLag();
132
248
  },
133
249
  });
134
250
  const options = {
@@ -140,29 +256,47 @@ export async function main() {
140
256
  coreVersion: coreVersion(),
141
257
  ...(stack.listen ? { listen: stack.listen } : {}),
142
258
  ...(stack.publicUrl ? { publicUrl: stack.publicUrl } : {}),
259
+ // Container mode (the local stack) allows browsers unless told not to; a Host started
260
+ // from host.json only when it says so. Without publishable keys nothing is reachable.
261
+ browserAccess: stack.browserAccess ?? (stack.listen ? true : config.browserAccess === true),
262
+ // The Admin API on its own listener: from the container environment, or from host.json
263
+ // (loopback, on the same host) when the Host runs outside a container.
264
+ ...(stack.operator
265
+ ? { operator: stack.operator }
266
+ : !stack.listen && typeof config.adminPort === "number"
267
+ ? { operator: { host: config.host, port: config.adminPort } }
268
+ : {}),
143
269
  ...(infra.readiness ? { readiness: infra.readiness } : {}),
144
270
  // SIGTERM and POST /v1/admin/host/shutdown both close the Host this way:
145
- // stop the Worker, close the Tenants, then end the infrastructure clients.
271
+ // stop the Worker and the relay, close the Tenant, then end the infrastructure clients.
146
272
  shutdown: {
147
- beforeTenants: () => hostExecution.stop(),
273
+ beforeTenants: async () => {
274
+ await hostExecution.stop();
275
+ await relay?.stop();
276
+ },
148
277
  afterTenants: () => infra.close(),
149
278
  },
150
279
  };
151
280
  const host = createHost(options);
152
- // Startup order. The Worker starts first: opening a Tenant arms its sweep
281
+ // Startup order. The Worker starts first: opening the Tenant arms its sweep
153
282
  // through Restate's ingress, which answers 404 until a Worker has registered
154
- // the services, so no Tenant may open before `start` (the listener opens
155
- // Tenants on demand). An api-role process serves no Worker endpoint, so it
156
- // can open Tenants only once some Worker process has registered. Container
157
- // mode runs one `--role all` process, which registers here. Then the
158
- // listener starts (and marks discovery done), and every listed Tenant's
159
- // sweep is re-armed, which recovers wakes lost with Restate's state (§14.8).
283
+ // the services, so the Tenant may not open before `start`. A core-only
284
+ // process serves no Worker endpoint, so it can open the Tenant only once some
285
+ // Worker process has registered (until then the open is retried). Container
286
+ // mode runs one `--service core,loop` process, which registers here. Then the
287
+ // listener starts and opens the Tenant (migrating its database, creating it on
288
+ // first start); opening arms its sweep, which recovers wakes lost with
289
+ // Restate's state (§14.8), and starts the stream relay. A Tenant that cannot
290
+ // be opened leaves the Host listening but not ready, with the cause in
291
+ // `/v1/admin/status`.
160
292
  try {
161
293
  await hostExecution.start();
162
294
  await host.listen();
163
295
  }
164
296
  catch (error) {
165
297
  await hostExecution.stop().catch(() => undefined);
298
+ await module.close().catch(() => undefined);
299
+ await relay?.stop().catch(() => undefined);
166
300
  await infra.close();
167
301
  if (error instanceof HostListenError) {
168
302
  logger.error("listen_failed", {
@@ -174,16 +308,13 @@ export async function main() {
174
308
  }
175
309
  throw error;
176
310
  }
177
- const tenants = await module.list();
178
- await hostExecution
179
- .armAll(tenants.filter((t) => t.state === "open").map((t) => t.id))
180
- .catch((error) => logger.warn("tenant_sweeps_not_armed", {
181
- message: error instanceof Error ? error.message : String(error),
182
- }));
311
+ const tenant = module.tenant();
183
312
  logger.info("host_ready", {
184
313
  url: host.url,
185
314
  hostId: config.hostId,
186
- tenants: tenants.length,
315
+ tenantId: tenant.id,
316
+ tenant: tenant.state,
317
+ ...(tenant.cause ? { cause: tenant.cause.code } : {}),
187
318
  });
188
319
  void host.closed.then(() => process.exit(0));
189
320
  for (const signal of ["SIGINT", "SIGTERM"]) {
@@ -1,21 +1,20 @@
1
- /**
2
- * Stack configuration for the Runtime process, parsed from an environment
3
- * snapshot and argv. Pure: `host/main.ts` passes the process environment and
4
- * arguments; nothing here reads ambient state.
5
- *
6
- * Two listen modes:
7
- * - **local** (no `NYLORUN_LISTEN_*` / `NYLORUN_ALLOWED_HOSTS`): the Host binds
8
- * what `host.json` names, loopback only unless `allowNonLoopback`.
9
- * - **container**: the Host binds `NYLORUN_LISTEN_HOST:NYLORUN_LISTEN_PORT`
10
- * (default `0.0.0.0:4000`) and accepts only the `Host` headers in
11
- * `NYLORUN_ALLOWED_HOSTS`, plus the loopback forms of the listen port for
12
- * probes from inside the container.
13
- *
14
- * The Postgres, Restate and S2 endpoints are parsed and validated here;
15
- * `infra/*` builds the clients from them.
16
- */
1
+ /** A Runtime service this release has. */
2
+ export type RuntimeService = "core" | "loop" | "gates" | "keys";
3
+ export type RuntimeServices = ReadonlySet<RuntimeService>;
4
+ export declare const RUNTIME_SERVICES: readonly RuntimeService[];
5
+ /** Where the gates service listens by default. */
6
+ export declare const DEFAULT_GATES_LISTEN_PORT = 4100;
7
+ /** What a process runs without `--service`: core and loop, as `--role all` did. */
8
+ export declare const DEFAULT_SERVICES: RuntimeServices;
9
+ /** The deprecated `--role` values, and the services each stands for. */
17
10
  export type RuntimeRole = "api" | "worker" | "all";
18
- export declare const RUNTIME_ROLES: readonly RuntimeRole[];
11
+ /** `services` as the `--service` value that selects them, e.g. `core,loop`. */
12
+ export declare function describeServices(services: RuntimeServices): string;
13
+ export interface ServiceSelection {
14
+ services: RuntimeServices;
15
+ /** Set when the process was started with the deprecated `--role`. */
16
+ deprecatedRole?: RuntimeRole;
17
+ }
19
18
  export declare const DEFAULT_CONTAINER_LISTEN_HOST = "0.0.0.0";
20
19
  export declare const DEFAULT_CONTAINER_LISTEN_PORT = 4000;
21
20
  export interface ContainerListen {
@@ -43,8 +42,43 @@ export interface StackEndpoints {
43
42
  */
44
43
  restateIdentityKeys?: string[];
45
44
  }
45
+ /** The gates service's listener and the token callers must present (`NYLORUN_GATES_*`). */
46
+ export interface GatesConfig {
47
+ listen: ContainerListen;
48
+ /** `NYLORUN_GATES_TOKEN`: the bearer the loop presents; at least 32 bytes as hex. */
49
+ token: string;
50
+ }
51
+ /** Where the loop reaches the gates service (`NYLORUN_GATES_URL`, `NYLORUN_GATES_TOKEN`). */
52
+ export interface ModelGateEndpoint {
53
+ url: string;
54
+ token: string;
55
+ }
46
56
  export interface StackConfig {
47
- role: RuntimeRole;
57
+ /** The Runtime services this process runs. */
58
+ services: RuntimeServices;
59
+ /** Present when the process runs gates. */
60
+ gates?: GatesConfig;
61
+ /**
62
+ * Present when the process runs core or loop and `NYLORUN_GATES_URL` is set: its model calls,
63
+ * remote MCP calls and Action deliveries cross the gates service. Required for loop in
64
+ * container mode; outside a container (a development Host, tests) the loop may make them in
65
+ * its own process.
66
+ */
67
+ modelGate?: ModelGateEndpoint;
68
+ /**
69
+ * Present when the process runs core or loop and `NYLORUN_KEYS_URL`, or else
70
+ * `NYLORUN_GATES_URL`, is set (F4.2): its vault writes and token signing cross the gateway's
71
+ * keys service, with `NYLORUN_GATES_TOKEN`, and it never reads the vault key. A loop in a
72
+ * container always has it, since it requires `NYLORUN_GATES_URL`.
73
+ */
74
+ keys?: ModelGateEndpoint;
75
+ /**
76
+ * How the stack packs services into containers (`NYLORUN_PACKING`), for the startup log:
77
+ * `combined` (the local stack: runtime and gateway) or `split` (one container per service).
78
+ */
79
+ packing?: "combined" | "split";
80
+ /** Set when the process was started with the deprecated `--role` (logged at startup). */
81
+ deprecatedRole?: RuntimeRole;
48
82
  /** Present in container mode; absent means bind what host.json names. */
49
83
  listen?: ContainerListen;
50
84
  endpoints: StackEndpoints;
@@ -54,12 +88,50 @@ export interface StackConfig {
54
88
  * means nothing outside the container.
55
89
  */
56
90
  publicUrl?: string;
91
+ /**
92
+ * `NYLORUN_BROWSER_ACCESS` (`on` or `off`): whether browser requests may reach Tenant
93
+ * routes. Absent means the Host's default (on in container mode).
94
+ */
95
+ browserAccess?: boolean;
96
+ /**
97
+ * The operator listener in container mode (`NYLORUN_ADMIN_LISTEN_PORT`, `…_HOST`,
98
+ * `…_ALLOWED_HOSTS`). Absent: one listener serves the Admin API and the Tenant API.
99
+ */
100
+ operator?: ContainerListen;
101
+ /**
102
+ * How the Runtime may call Action endpoints: `NYLORUN_ENDPOINT_LOOPBACK=docker-host` (the local
103
+ * stack: `localhost` means the machine that runs Docker), `NYLORUN_ENDPOINT_PRIVATE`
104
+ * (`allow` or `refuse`) and `NYLORUN_ENDPOINT_HTTP` (`allow` or `refuse`).
105
+ */
106
+ delivery?: {
107
+ loopback?: "docker-host";
108
+ privateAddresses?: "allow" | "refuse";
109
+ allowHttp?: boolean;
110
+ };
111
+ /** Present when the process runs core or loop: who its Tenant is on first start. */
112
+ tenant?: TenantSettings;
113
+ }
114
+ /**
115
+ * The Tenant a Host creates when its database holds none (tenancy.md §4). Later starts open the
116
+ * Tenant the database holds; only principals missing from it are added.
117
+ */
118
+ export interface TenantSettings {
119
+ /** `NYLORUN_TENANT_ID`: the new Tenant's id. Default: a new id. */
120
+ id?: string;
121
+ /** `NYLORUN_TENANT_NAME`. Default `default`. */
122
+ name: string;
123
+ /**
124
+ * `NYLORUN_DERIVED_PRINCIPALS`: comma-separated application principals whose keys the admin
125
+ * key derives (`deriveTenantKey`). Default `project`.
126
+ */
127
+ derivedPrincipals: readonly string[];
57
128
  }
58
129
  export declare class StackConfigError extends Error {
59
130
  constructor(message: string);
60
131
  }
61
132
  type EnvSnapshot = Readonly<Record<string, string | undefined>>;
62
- export declare function parseRole(argv: readonly string[]): RuntimeRole;
133
+ /** Parses `--service a,b` (or the deprecated `--role`); throws `StackConfigError`. */
134
+ export declare function parseServices(argv: readonly string[]): ServiceSelection;
63
135
  /**
64
136
  * Normalize one `Host` header allowlist entry to lowercase `name:port`.
65
137
  * IPv6 literals must be bracketed (`[::1]:4000`).