@nylorun/runtime 0.15.2-beta → 0.17.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 (291) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/README.md +68 -29
  3. package/dist/adapters/execution/restate.d.ts +9 -1
  4. package/dist/adapters/execution/restate.js +64 -5
  5. package/dist/adapters/sandbox/local.d.ts +10 -0
  6. package/dist/adapters/sandbox/local.js +195 -0
  7. package/dist/adapters/sandbox/virtual.js +36 -1
  8. package/dist/admin-openapi.json +341 -7
  9. package/dist/api/a2a/tasks.js +6 -1
  10. package/dist/api/ag-ui/endpoint.js +1 -2
  11. package/dist/api/ag-ui/history.js +6 -1
  12. package/dist/api/ag-ui/routes.d.ts +1 -1
  13. package/dist/api/ag-ui/routes.js +15 -13
  14. package/dist/api/components.d.ts +2667 -711
  15. package/dist/api/components.js +24 -11
  16. package/dist/api/gate/keys.d.ts +4 -3
  17. package/dist/api/gate/routes.d.ts +19 -3
  18. package/dist/api/gate/routes.js +162 -53
  19. package/dist/api/http/app.js +6 -0
  20. package/dist/api/http/define.d.ts +19 -12
  21. package/dist/api/http/define.js +13 -27
  22. package/dist/api/http/routes/access.d.ts +3 -19
  23. package/dist/api/http/routes/access.js +12 -172
  24. package/dist/api/http/routes/actions.js +0 -1
  25. package/dist/api/http/routes/artifacts.d.ts +11 -0
  26. package/dist/api/http/routes/artifacts.js +516 -0
  27. package/dist/api/http/routes/me.d.ts +13 -0
  28. package/dist/api/http/routes/me.js +45 -0
  29. package/dist/api/http/routes/sandboxes.d.ts +9 -0
  30. package/dist/api/http/routes/sandboxes.js +150 -0
  31. package/dist/api/http/routes/sessions.js +9 -13
  32. package/dist/api/http/routes/tenant.js +36 -2
  33. package/dist/api/http/routes/vaults.d.ts +12 -3
  34. package/dist/api/http/routes/vaults.js +122 -54
  35. package/dist/api/openapi.js +4 -10
  36. package/dist/artifacts/config.d.ts +23 -0
  37. package/dist/artifacts/config.js +27 -0
  38. package/dist/artifacts/export.d.ts +8 -0
  39. package/dist/artifacts/export.js +205 -0
  40. package/dist/artifacts/files.d.ts +36 -0
  41. package/dist/artifacts/files.js +45 -0
  42. package/dist/artifacts/folders.d.ts +60 -0
  43. package/dist/artifacts/folders.js +209 -0
  44. package/dist/artifacts/links.d.ts +28 -0
  45. package/dist/artifacts/links.js +92 -0
  46. package/dist/artifacts/media-types.d.ts +13 -0
  47. package/dist/artifacts/media-types.js +80 -0
  48. package/dist/artifacts/parts.d.ts +34 -0
  49. package/dist/artifacts/parts.js +38 -0
  50. package/dist/artifacts/service.d.ts +74 -0
  51. package/dist/artifacts/service.js +301 -0
  52. package/dist/artifacts/workspace.d.ts +60 -0
  53. package/dist/artifacts/workspace.js +57 -0
  54. package/dist/artifacts/zip.d.ts +10 -0
  55. package/dist/artifacts/zip.js +126 -0
  56. package/dist/blob/fs.d.ts +13 -0
  57. package/dist/blob/fs.js +194 -0
  58. package/dist/blob/index.d.ts +9 -0
  59. package/dist/blob/index.js +9 -0
  60. package/dist/blob/s3.d.ts +42 -0
  61. package/dist/blob/s3.js +366 -0
  62. package/dist/blob/types.d.ts +108 -0
  63. package/dist/blob/types.js +85 -0
  64. package/dist/core/sandbox-routes.d.ts +2 -2
  65. package/dist/core/sandbox-routes.js +4 -3
  66. package/dist/execution/memory.d.ts +11 -1
  67. package/dist/execution/memory.js +77 -3
  68. package/dist/execution/types.d.ts +54 -1
  69. package/dist/execution/types.js +31 -0
  70. package/dist/gates/contract.d.ts +14 -13
  71. package/dist/gates/contract.js +13 -12
  72. package/dist/gates/egress.d.ts +72 -0
  73. package/dist/gates/egress.js +319 -0
  74. package/dist/gates/handler.d.ts +3 -0
  75. package/dist/gates/handler.js +3 -2
  76. package/dist/gates/http-client.d.ts +3 -2
  77. package/dist/gates/http-client.js +17 -6
  78. package/dist/gates/in-process.d.ts +13 -1
  79. package/dist/gates/in-process.js +11 -0
  80. package/dist/gates/inflight.d.ts +23 -4
  81. package/dist/gates/inflight.js +18 -4
  82. package/dist/gates/mcp-handler.js +5 -1
  83. package/dist/gates/model-gate.d.ts +6 -2
  84. package/dist/gates/tenant-vaults.d.ts +18 -9
  85. package/dist/gates/tenant-vaults.js +17 -12
  86. package/dist/gates/tool-calls.d.ts +9 -5
  87. package/dist/gates/tool-calls.js +6 -6
  88. package/dist/gates/tool-client.d.ts +9 -2
  89. package/dist/gates/tool-client.js +56 -22
  90. package/dist/gates/tool-contract.d.ts +12 -7
  91. package/dist/gates/tool-contract.js +10 -4
  92. package/dist/gates/tool-gate.d.ts +1 -0
  93. package/dist/harness/calls.d.ts +52 -0
  94. package/dist/harness/calls.js +113 -0
  95. package/dist/harness/executors.d.ts +22 -0
  96. package/dist/harness/executors.js +96 -0
  97. package/dist/harness/main.d.ts +6 -0
  98. package/dist/harness/main.js +103 -0
  99. package/dist/harness/pod.d.ts +21 -0
  100. package/dist/harness/pod.js +119 -0
  101. package/dist/harness/run-tokens.d.ts +18 -0
  102. package/dist/harness/run-tokens.js +15 -0
  103. package/dist/harness/service.d.ts +46 -0
  104. package/dist/harness/service.js +179 -0
  105. package/dist/harness/ws-client.d.ts +43 -0
  106. package/dist/harness/ws-client.js +122 -0
  107. package/dist/harness/ws-port.d.ts +22 -0
  108. package/dist/harness/ws-port.js +78 -0
  109. package/dist/harness-api/in-process.d.ts +24 -0
  110. package/dist/harness-api/in-process.js +119 -0
  111. package/dist/harness-api/record.d.ts +44 -0
  112. package/dist/harness-api/record.js +250 -0
  113. package/dist/harness-api/renew.d.ts +37 -0
  114. package/dist/harness-api/renew.js +77 -0
  115. package/dist/harness-api/server.d.ts +104 -0
  116. package/dist/harness-api/server.js +408 -0
  117. package/dist/harness-api/start.d.ts +36 -0
  118. package/dist/harness-api/start.js +118 -0
  119. package/dist/harness-api/workspace.d.ts +53 -0
  120. package/dist/harness-api/workspace.js +201 -0
  121. package/dist/harness-api/ws-server.d.ts +34 -0
  122. package/dist/harness-api/ws-server.js +227 -0
  123. package/dist/host/admin-api.d.ts +12 -5
  124. package/dist/host/admin-api.js +72 -6
  125. package/dist/host/app.d.ts +0 -1
  126. package/dist/host/app.js +35 -24
  127. package/dist/host/config-for.d.ts +4 -0
  128. package/dist/host/config-for.js +2 -0
  129. package/dist/host/config.d.ts +0 -6
  130. package/dist/host/create-host.d.ts +3 -10
  131. package/dist/host/create-host.js +4 -1
  132. package/dist/host/gates.d.ts +27 -2
  133. package/dist/host/gates.js +52 -2
  134. package/dist/host/http.d.ts +4 -1
  135. package/dist/host/http.js +6 -1
  136. package/dist/host/main.js +167 -37
  137. package/dist/host/stack-config.d.ts +112 -9
  138. package/dist/host/stack-config.js +222 -20
  139. package/dist/index.d.ts +3 -0
  140. package/dist/index.js +2 -0
  141. package/dist/keys/client.js +9 -1
  142. package/dist/keys/keys.d.ts +12 -2
  143. package/dist/keys/keys.js +8 -0
  144. package/dist/mcp/connect.js +2 -1
  145. package/dist/mcp/pool.d.ts +1 -0
  146. package/dist/mcp/pool.js +1 -0
  147. package/dist/mcp/snapshot.d.ts +8 -1
  148. package/dist/mcp/snapshot.js +28 -0
  149. package/dist/media.d.ts +5 -24
  150. package/dist/media.js +5 -0
  151. package/dist/model/pi-model.d.ts +6 -2
  152. package/dist/model/pi-model.js +39 -22
  153. package/dist/node/index.d.ts +1 -1
  154. package/dist/node/index.js +1 -1
  155. package/dist/openapi.json +14084 -9426
  156. package/dist/record/index.d.ts +1 -0
  157. package/dist/record/index.js +1 -0
  158. package/dist/record/sandbox.d.ts +25 -0
  159. package/dist/record/sandbox.js +43 -0
  160. package/dist/sandbox/capability.d.ts +3 -0
  161. package/dist/sandbox/capability.js +10 -0
  162. package/dist/sandbox/egress-token.d.ts +26 -0
  163. package/dist/sandbox/egress-token.js +8 -0
  164. package/dist/sandbox/join.d.ts +37 -0
  165. package/dist/sandbox/join.js +165 -0
  166. package/dist/sandbox/manager.d.ts +99 -34
  167. package/dist/sandbox/manager.js +216 -84
  168. package/dist/sandbox/placement.d.ts +27 -0
  169. package/dist/sandbox/placement.js +44 -0
  170. package/dist/sandbox/pods/client.d.ts +90 -0
  171. package/dist/sandbox/pods/client.js +79 -0
  172. package/dist/sandbox/pods/lifecycle.d.ts +91 -0
  173. package/dist/sandbox/pods/lifecycle.js +178 -0
  174. package/dist/sandbox/pods/name.d.ts +1 -0
  175. package/dist/sandbox/pods/name.js +28 -0
  176. package/dist/sandbox/pods/network-gate.d.ts +25 -0
  177. package/dist/sandbox/pods/network-gate.js +71 -0
  178. package/dist/sandbox/pods/reconcile.d.ts +11 -0
  179. package/dist/sandbox/pods/reconcile.js +152 -0
  180. package/dist/sandbox/pods/spec.d.ts +45 -0
  181. package/dist/sandbox/pods/spec.js +62 -0
  182. package/dist/sandbox/records.d.ts +29 -0
  183. package/dist/sandbox/records.js +79 -0
  184. package/dist/sandbox/resolve.d.ts +5 -0
  185. package/dist/sandbox/resolve.js +3 -2
  186. package/dist/sandbox/session-sandbox.js +7 -5
  187. package/dist/sandbox/share.d.ts +11 -0
  188. package/dist/sandbox/share.js +10 -0
  189. package/dist/sandbox/tenant-config.d.ts +16 -1
  190. package/dist/sandbox/tenant-config.js +9 -0
  191. package/dist/sandbox/types.d.ts +29 -1
  192. package/dist/sandbox/types.js +13 -1
  193. package/dist/store/postgres/drizzle/0004_sandbox_resources.sql +20 -0
  194. package/dist/store/postgres/drizzle/0005_artifacts.sql +27 -0
  195. package/dist/store/postgres/drizzle/0006_folder_artifacts.sql +12 -0
  196. package/dist/store/postgres/drizzle/0007_sandbox_pods.sql +14 -0
  197. package/dist/store/postgres/drizzle/0008_installation_vaults.sql +2 -0
  198. package/dist/store/postgres/drizzle/0009_oauth_pending.sql +19 -0
  199. package/dist/store/postgres/drizzle/0010_oss_auth_removals.sql +3 -0
  200. package/dist/store/postgres/drizzle/meta/_journal.json +49 -0
  201. package/dist/store/postgres/record-writer.d.ts +6 -1
  202. package/dist/store/postgres/record-writer.js +20 -2
  203. package/dist/store/postgres/schema.d.ts +1168 -250
  204. package/dist/store/postgres/schema.js +174 -28
  205. package/dist/store/postgres/store.js +339 -103
  206. package/dist/store/postgres/tenant.d.ts +1 -1
  207. package/dist/store/postgres/tenant.js +2 -2
  208. package/dist/store/types.d.ts +163 -32
  209. package/dist/streams/control.d.ts +2 -5
  210. package/dist/streams/control.js +5 -8
  211. package/dist/streams/types.d.ts +8 -8
  212. package/dist/streams/types.js +1 -1
  213. package/dist/tenant/advance.js +218 -160
  214. package/dist/tenant/artifact-tool.d.ts +5 -0
  215. package/dist/tenant/artifact-tool.js +119 -0
  216. package/dist/tenant/auth.d.ts +13 -9
  217. package/dist/tenant/auth.js +25 -32
  218. package/dist/tenant/commands.js +31 -10
  219. package/dist/tenant/context.d.ts +83 -26
  220. package/dist/tenant/delivery-token.js +4 -4
  221. package/dist/tenant/effects.d.ts +24 -14
  222. package/dist/tenant/effects.js +10 -398
  223. package/dist/tenant/ephemeral.d.ts +14 -2
  224. package/dist/tenant/ephemeral.js +8 -1
  225. package/dist/tenant/history.d.ts +13 -23
  226. package/dist/tenant/history.js +14 -62
  227. package/dist/tenant/host-token.d.ts +92 -0
  228. package/dist/tenant/host-token.js +179 -0
  229. package/dist/tenant/identity-file.d.ts +75 -0
  230. package/dist/tenant/identity-file.js +202 -0
  231. package/dist/tenant/issuers.d.ts +48 -0
  232. package/dist/tenant/issuers.js +311 -0
  233. package/dist/tenant/jwt.js +3 -3
  234. package/dist/tenant/module.js +1 -0
  235. package/dist/tenant/operator-keys.d.ts +18 -0
  236. package/dist/tenant/operator-keys.js +55 -0
  237. package/dist/tenant/outbound.d.ts +30 -0
  238. package/dist/tenant/outbound.js +142 -10
  239. package/dist/tenant/paths.d.ts +2 -1
  240. package/dist/tenant/paths.js +3 -1
  241. package/dist/tenant/principals.d.ts +2 -11
  242. package/dist/tenant/principals.js +10 -30
  243. package/dist/tenant/reset.d.ts +5 -2
  244. package/dist/tenant/reset.js +22 -2
  245. package/dist/tenant/run-grants.d.ts +49 -0
  246. package/dist/tenant/run-grants.js +62 -0
  247. package/dist/tenant/run-token.d.ts +70 -0
  248. package/dist/tenant/run-token.js +138 -0
  249. package/dist/tenant/runtime.d.ts +42 -0
  250. package/dist/tenant/runtime.js +134 -33
  251. package/dist/tenant/sandboxes.d.ts +119 -0
  252. package/dist/tenant/sandboxes.js +551 -0
  253. package/dist/tenant/session-streams.d.ts +6 -10
  254. package/dist/tenant/session-streams.js +9 -27
  255. package/dist/tenant/session.d.ts +2 -13
  256. package/dist/tenant/session.js +4 -28
  257. package/dist/tenant/sessions.d.ts +6 -1
  258. package/dist/tenant/sessions.js +29 -2
  259. package/dist/tenant/signing-keys.d.ts +2 -1
  260. package/dist/tenant/signing-keys.js +2 -2
  261. package/dist/tenant/status.d.ts +8 -3
  262. package/dist/tenant/status.js +5 -0
  263. package/dist/tenant/streams.d.ts +8 -2
  264. package/dist/tenant/streams.js +25 -6
  265. package/dist/tenant/sweep.js +37 -4
  266. package/dist/tenant/types.d.ts +39 -1
  267. package/dist/tenant/worker.d.ts +11 -5
  268. package/dist/tenant/worker.js +12 -6
  269. package/dist/vault/crypto.d.ts +7 -0
  270. package/dist/vault/crypto.js +16 -0
  271. package/dist/vault/oauth.d.ts +55 -0
  272. package/dist/vault/oauth.js +186 -0
  273. package/dist/vault/service.d.ts +54 -7
  274. package/dist/vault/service.js +261 -17
  275. package/dist/vault/sources.d.ts +54 -0
  276. package/dist/vault/sources.js +185 -0
  277. package/dist/version.d.ts +1 -1
  278. package/dist/version.js +1 -1
  279. package/package.json +14 -10
  280. package/dist/adapters/media.d.ts +0 -24
  281. package/dist/adapters/media.js +0 -111
  282. package/dist/host/cors.d.ts +0 -27
  283. package/dist/host/cors.js +0 -64
  284. package/dist/tenant/access-policy.d.ts +0 -30
  285. package/dist/tenant/access-policy.js +0 -52
  286. package/dist/tenant/browser.d.ts +0 -15
  287. package/dist/tenant/browser.js +0 -39
  288. package/dist/tenant/subject-limits.d.ts +0 -16
  289. package/dist/tenant/subject-limits.js +0 -45
  290. package/dist/tenant/tokens.d.ts +0 -14
  291. package/dist/tenant/tokens.js +0 -180
package/dist/host/main.js CHANGED
@@ -9,14 +9,19 @@
9
9
  * Execution and Durable Streams from the endpoints; `createHostExecution`
10
10
  * builds the process's execution; the Host serves the one Tenant its Postgres
11
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
12
+ * first start (`NYLORUN_TENANT_ID`, `NYLORUN_TENANT_NAME`; tenancy.md §4), trusting the issuers of the identity file
13
+ * `NYLORUN_IDENTITY_FILE` names (Host feature `trusted-issuers`); with S2, a process running core
14
14
  * runs the stream relay, which feeds the Tenant's streams from the record over
15
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
19
- * entry, with `createHost` and an injected Tenant module.
16
+ * the Tenant and the infrastructure checks. With an Object store
17
+ * (`NYLORUN_OBJECT_STORE_*`) the Tenant's blobs go to it through the `s3` BlobStore, and a
18
+ * process running core creates its bucket; without one they stay on disk (`fs`). A process
19
+ * running the gates service (the local stack's `gateway` container) starts only the gate, and egress-gate with `egress` (`runGates`): it needs
20
+ * neither host.json nor host-credentials.json, and reads the files a prompt names from the Object store. A process running the harness service (`--service harness`, F6.2)
21
+ * starts only the harness (`harness/main.ts`), which connects to core's Harness API. With `NYLORUN_HARNESS=remote`
22
+ * the Tenant runs no harness of its own: core starts the Harness API listener (`NYLORUN_HARNESS_LISTEN_*`) and
23
+ * harnesses that present `NYLORUN_HARNESS_TOKEN` run its segments and hold its workspaces. See the startup order
24
+ * in `main()`. Tests compose a Host without this entry, with `createHost` and an injected Tenant module.
20
25
  */
21
26
  import { readFileSync, mkdirSync, existsSync } from "node:fs";
22
27
  import { createRequire } from "node:module";
@@ -24,6 +29,8 @@ import { homedir } from "node:os";
24
29
  import { resolve } from "node:path";
25
30
  import { hostPaths } from "../tenant/paths.js";
26
31
  import { createTenantModule } from "../tenant/module.js";
32
+ import { parseIdentityFile } from "../tenant/identity-file.js";
33
+ import { createTrustedIssuers } from "../tenant/issuers.js";
27
34
  import { hostPrincipals } from "../tenant/principals.js";
28
35
  import { createPostgresTenantOpener } from "../tenant/store-pg.js";
29
36
  import { openTenantRuntime } from "../tenant/runtime.js";
@@ -40,10 +47,15 @@ import { createHostLogger } from "./logger.js";
40
47
  import { describeEndpoints, describeServices, parseStackConfig, } from "./stack-config.js";
41
48
  import { createExecution, createInfra } from "../infra/index.js";
42
49
  import { createDatabase } from "../infra/database.js";
43
- import { startGates } from "./gates.js";
50
+ import { startEgress, startGates } from "./gates.js";
44
51
  import { httpToolGate } from "../gates/tool-client.js";
45
52
  import { httpKeys } from "../keys/client.js";
46
53
  import { httpModelGate } from "../gates/http-client.js";
54
+ import { createS3BlobStore } from "../blob/index.js";
55
+ import { createRunGrants } from "../tenant/run-grants.js";
56
+ import { runHarness } from "../harness/main.js";
57
+ import { startHarnessListener } from "../harness-api/ws-server.js";
58
+ import { httpSandboxesClient } from "../sandbox/pods/client.js";
47
59
  const nodeRequire = createRequire(import.meta.url);
48
60
  function coreVersion() {
49
61
  try {
@@ -56,43 +68,107 @@ function coreVersion() {
56
68
  function loadJson(path) {
57
69
  return JSON.parse(readFileSync(path, "utf8"));
58
70
  }
71
+ /**
72
+ * The trusted issuers of the identity file (`NYLORUN_IDENTITY_FILE`, Host feature
73
+ * `trusted-issuers`). A missing or malformed file stops the boot, naming the issuer and field;
74
+ * an issuer's JWKS is fetched only when a token needs it, so one that is down does not.
75
+ */
76
+ function loadIssuers(path) {
77
+ let text;
78
+ try {
79
+ text = readFileSync(path, "utf8");
80
+ }
81
+ catch (error) {
82
+ throw new Error(`NYLORUN_IDENTITY_FILE names ${path}, which cannot be read: ${error instanceof Error ? error.message : String(error)}`);
83
+ }
84
+ return createTrustedIssuers(parseIdentityFile(text, path));
85
+ }
59
86
  function resolveHostRoot() {
60
87
  const fromEnv = process.env.NYLORUN_HOME;
61
88
  const root = fromEnv && fromEnv.length > 0 ? fromEnv : resolve(homedir(), ".nylorun");
62
89
  return resolve(root);
63
90
  }
91
+ /** The Object store's `s3` BlobStore (D35), from `StackConfig.objectStore`. */
92
+ function objectStore(config) {
93
+ return createS3BlobStore({
94
+ endpoint: config.endpoint,
95
+ bucket: config.bucket,
96
+ region: config.region,
97
+ accessKeyId: config.accessKeyId,
98
+ secretAccessKey: config.secretAccessKey,
99
+ });
100
+ }
101
+ /**
102
+ * Creates the Object store's bucket unless it exists, retrying while the store starts. In the
103
+ * background: only artifacts use the store, so a store that stays down is logged (artifact
104
+ * uploads and downloads fail meanwhile) and does not hold up the Host.
105
+ */
106
+ async function ensureBucket(store, bucket, logger) {
107
+ for (let attempt = 1;; attempt += 1) {
108
+ try {
109
+ await store.ensureBucket();
110
+ logger.info("object_store_ready", { bucket });
111
+ return;
112
+ }
113
+ catch (error) {
114
+ if (attempt >= 10) {
115
+ logger.error("object_store_unavailable", {
116
+ bucket,
117
+ message: error instanceof Error ? error.message : String(error),
118
+ });
119
+ return;
120
+ }
121
+ await new Promise((resolve) => setTimeout(resolve, Math.min(1000 * attempt, 5000)));
122
+ }
123
+ }
124
+ }
64
125
  /** The slot's lag in WAL bytes, when the source can tell. */
65
126
  async function lagOf(source) {
66
127
  const lagBytes = await source.lag?.().catch(() => undefined);
67
128
  return lagBytes === undefined ? {} : { lagBytes };
68
129
  }
69
130
  /**
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).
131
+ * The gateway's services: the Model Gate's listener (gates, keys) and egress-gate's (egress), over
132
+ * the Postgres pool and the Host's tenant directory. Writes nothing to the Host root (the local
133
+ * stack mounts it read-only).
72
134
  */
73
135
  async function runGates(stack) {
74
- const gates = stack.gates;
75
136
  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)");
137
+ throw new Error(`NYLORUN_DATABASE_URL is required for --service ${describeServices(stack.services)}: the gateway reads the Tenant from Postgres (\`nylorun start\` sets it)`);
77
138
  const logger = createHostLogger();
78
139
  logger.info("host_stack_config", {
79
140
  services: [...stack.services],
80
141
  ...(stack.packing ? { packing: stack.packing } : {}),
81
142
  endpoints: describeEndpoints(stack.endpoints),
143
+ objectStore: stack.objectStore ? "s3" : "none",
144
+ // Never the resolver's URL or token: only whether one is set (F9 C1).
145
+ resolver: stack.gates?.resolver ? "configured" : "none",
82
146
  });
83
147
  const database = createDatabase(stack);
84
- let server;
148
+ const servers = [];
85
149
  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
- });
150
+ if (stack.gates) {
151
+ const server = await startGates({
152
+ gates: stack.gates,
153
+ database,
154
+ hostRoot: resolveHostRoot(),
155
+ logger,
156
+ ...(stack.delivery ? { delivery: stack.delivery } : {}),
157
+ ...(stack.services.has("keys") ? { keys: true } : {}),
158
+ // Model-gate reads the files a prompt names from the Object store (protocol 6).
159
+ ...(stack.objectStore ? { blobs: objectStore(stack.objectStore) } : {}),
160
+ });
161
+ servers.push(server);
162
+ logger.info("gates_ready", { url: server.url });
163
+ }
164
+ if (stack.egress) {
165
+ const egress = await startEgress({ egress: stack.egress, database, hostRoot: resolveHostRoot(), logger });
166
+ servers.push(egress);
167
+ logger.info("egress_ready", { url: egress.url });
168
+ }
94
169
  }
95
170
  catch (error) {
171
+ await Promise.all(servers.map((server) => server.close()));
96
172
  await database.end({ timeout: 5 });
97
173
  if (error instanceof HostListenError) {
98
174
  logger.error("listen_failed", { message: error.message, exitCode: error.exitCode });
@@ -101,23 +177,23 @@ async function runGates(stack) {
101
177
  }
102
178
  throw error;
103
179
  }
104
- logger.info("gates_ready", { url: server.url });
105
180
  let stopping = false;
106
181
  for (const signal of ["SIGINT", "SIGTERM"])
107
182
  process.on(signal, () => {
108
183
  if (stopping)
109
184
  return;
110
185
  stopping = true;
111
- void server
112
- .close()
186
+ void Promise.all(servers.map((server) => server.close()))
113
187
  .then(() => database.end({ timeout: 5 }))
114
188
  .finally(() => process.exit(0));
115
189
  });
116
190
  }
117
191
  export async function main() {
118
192
  const stack = parseStackConfig(process.env, process.argv.slice(2));
119
- if (stack.services.has("gates") || stack.services.has("keys"))
193
+ if (stack.services.has("gates") || stack.services.has("keys") || stack.services.has("egress"))
120
194
  return runGates(stack);
195
+ if (stack.services.has("harness"))
196
+ return runHarness(stack, baselineEnvironment(process.env));
121
197
  const hostRoot = resolveHostRoot();
122
198
  const paths = hostPaths(hostRoot);
123
199
  mkdirSync(paths.home, { recursive: true });
@@ -138,7 +214,14 @@ export async function main() {
138
214
  }
139
215
  const config = loadJson(paths.config);
140
216
  const credentials = loadJson(paths.credentials);
217
+ const identityFile = stack.tenant?.identityFile;
218
+ const issuers = identityFile ? loadIssuers(identityFile) : undefined;
141
219
  const logger = createHostLogger();
220
+ if (issuers)
221
+ logger.info("trusted_issuers", {
222
+ file: identityFile,
223
+ issuers: issuers.issuers.map((issuer) => issuer.config.name),
224
+ });
142
225
  if (stack.deprecatedRole)
143
226
  logger.warn("deprecated_flag", {
144
227
  flag: "--role",
@@ -150,8 +233,10 @@ export async function main() {
150
233
  ...(stack.services.has("loop")
151
234
  ? { modelGate: stack.modelGate ? stack.modelGate.url : "in-process" }
152
235
  : {}),
236
+ harness: stack.harnessMode ?? "in-process",
153
237
  mode: stack.listen ? "container" : "local",
154
238
  endpoints: describeEndpoints(stack.endpoints),
239
+ objectStore: stack.objectStore ? "s3" : "fs",
155
240
  });
156
241
  const infra = createInfra(stack, { logger });
157
242
  const database = infra.database;
@@ -166,6 +251,8 @@ export async function main() {
166
251
  logger,
167
252
  baseline,
168
253
  ...(stack.delivery ? { delivery: stack.delivery } : {}),
254
+ ...(issuers ? { issuers } : {}),
255
+ ...(stack.publicUrl ? { publicUrl: stack.publicUrl } : {}),
169
256
  });
170
257
  // The process's Durable Session Execution: Restate when its endpoints are set, else the
171
258
  // in-process memory execution. An invocation that arrives while the Tenant opens waits for it.
@@ -206,16 +293,32 @@ export async function main() {
206
293
  };
207
294
  // With the gates service the Tenant's vault-backed model calls, remote MCP calls and Action
208
295
  // 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 })
296
+ // Their credentials (F5): each advance's run token for its session's model and MCP calls,
297
+ // core's credential (NYLORUN_GATES_TOKEN) for deliveries and MCP requests outside a run.
298
+ const runGrants = stack.modelGate ? createRunGrants() : undefined;
299
+ const modelGate = stack.modelGate && runGrants
300
+ ? httpModelGate({ url: stack.modelGate.url, runTokens: runGrants })
211
301
  : undefined;
212
- const toolGate = stack.modelGate
213
- ? httpToolGate({ url: stack.modelGate.url, token: stack.modelGate.token })
302
+ const toolGate = stack.modelGate && runGrants
303
+ ? httpToolGate({ url: stack.modelGate.url, token: stack.modelGate.token, runTokens: runGrants })
214
304
  : undefined;
215
305
  // With the keys service, vault writes and token signing cross it, and this process never
216
306
  // reads the vault key (F4.2).
217
307
  const keys = stack.keys ? httpKeys({ url: stack.keys.url, token: stack.keys.token }) : undefined;
218
- const tenantSettings = stack.tenant ?? { name: "default", derivedPrincipals: ["project"] };
308
+ // The Object store (D35): the stack's S3 server through the BlobStore seam, handed to the
309
+ // Tenant (`TenantContext.blobs`). Without one the Tenant keeps blobs on disk. core creates
310
+ // the bucket.
311
+ const blobs = stack.objectStore ? objectStore(stack.objectStore) : undefined;
312
+ if (blobs && stack.services.has("core"))
313
+ void ensureBucket(blobs, stack.objectStore.bucket, logger);
314
+ // Sandbox pods (F7.2): the sandboxes service, which alone holds the cluster's credentials.
315
+ const pods = stack.sandboxes
316
+ ? {
317
+ client: httpSandboxesClient({ url: stack.sandboxes.url, token: stack.sandboxes.token }),
318
+ harnessImage: stack.sandboxes.harnessImage,
319
+ }
320
+ : undefined;
321
+ const tenantSettings = stack.tenant ?? { name: "default" };
219
322
  const module = createTenantModule({
220
323
  open: createPostgresTenantOpener({
221
324
  hostRoot,
@@ -223,10 +326,7 @@ export async function main() {
223
326
  create: {
224
327
  ...(tenantSettings.id ? { tenantId: tenantSettings.id } : {}),
225
328
  name: tenantSettings.name,
226
- principals: hostPrincipals({
227
- adminKey: credentials.adminKey,
228
- derived: tenantSettings.derivedPrincipals,
229
- }),
329
+ principals: hostPrincipals({ adminKey: credentials.adminKey }),
230
330
  },
231
331
  configFor,
232
332
  logger,
@@ -234,8 +334,12 @@ export async function main() {
234
334
  execution: hostExecution.tenantExecution,
235
335
  ...(modelGate ? { modelGate } : {}),
236
336
  ...(toolGate ? { toolGate } : {}),
337
+ ...(runGrants ? { runGrants } : {}),
237
338
  ...(keys ? { keys } : {}),
238
339
  ...(streams ? { streams, hostRelay: true } : {}),
340
+ ...(blobs ? { blobs } : {}),
341
+ ...(stack.harnessMode === "remote" ? { harness: "remote" } : {}),
342
+ ...(pods ? { pods } : {}),
239
343
  ...opened,
240
344
  }),
241
345
  }),
@@ -256,9 +360,6 @@ export async function main() {
256
360
  coreVersion: coreVersion(),
257
361
  ...(stack.listen ? { listen: stack.listen } : {}),
258
362
  ...(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
363
  // The Admin API on its own listener: from the container environment, or from host.json
263
364
  // (loopback, on the same host) when the Host runs outside a container.
264
365
  ...(stack.operator
@@ -273,8 +374,12 @@ export async function main() {
273
374
  beforeTenants: async () => {
274
375
  await hostExecution.stop();
275
376
  await relay?.stop();
377
+ // The harnesses' runs stop with the Tenant's advances; their connections close after.
378
+ },
379
+ afterTenants: async () => {
380
+ await harnessListener?.close();
381
+ await infra.close();
276
382
  },
277
- afterTenants: () => infra.close(),
278
383
  },
279
384
  };
280
385
  const host = createHost(options);
@@ -289,11 +394,35 @@ export async function main() {
289
394
  // Restate's state (§14.8), and starts the stream relay. A Tenant that cannot
290
395
  // be opened leaves the Host listening but not ready, with the cause in
291
396
  // `/v1/admin/status`.
397
+ // The Harness API listener (NYLORUN_HARNESS=remote): only harnesses connect here, with the
398
+ // harness credential, and only while the Tenant is open.
399
+ let harnessListener;
292
400
  try {
293
401
  await hostExecution.start();
402
+ if (stack.harnessListener)
403
+ harnessListener = await startHarnessListener({
404
+ ...stack.harnessListener.listen,
405
+ ...(stack.harnessListener.token ? { token: stack.harnessListener.token } : {}),
406
+ // Sandbox pods join here and connect with host tokens (F7.2).
407
+ ...(pods
408
+ ? {
409
+ hosts: async () => {
410
+ const resolved = await module.resolve();
411
+ return resolved.kind === "open" ? resolved.handle.hostAuthority?.() : undefined;
412
+ },
413
+ }
414
+ : {}),
415
+ attach: async () => {
416
+ const resolved = await module.resolve();
417
+ const handle = resolved.kind === "open" ? resolved.handle : undefined;
418
+ return handle?.attachHarness ? (channel, peer) => handle.attachHarness(channel, peer) : undefined;
419
+ },
420
+ logger,
421
+ });
294
422
  await host.listen();
295
423
  }
296
424
  catch (error) {
425
+ await harnessListener?.close().catch(() => undefined);
297
426
  await hostExecution.stop().catch(() => undefined);
298
427
  await module.close().catch(() => undefined);
299
428
  await relay?.stop().catch(() => undefined);
@@ -311,6 +440,7 @@ export async function main() {
311
440
  const tenant = module.tenant();
312
441
  logger.info("host_ready", {
313
442
  url: host.url,
443
+ ...(harnessListener ? { harness: harnessListener.url } : {}),
314
444
  hostId: config.hostId,
315
445
  tenantId: tenant.id,
316
446
  tenant: tenant.state,
@@ -1,9 +1,16 @@
1
+ import { type BlockedAddress } from "../sandbox/pods/network-gate.js";
1
2
  /** A Runtime service this release has. */
2
- export type RuntimeService = "core" | "loop" | "gates" | "keys";
3
+ export type RuntimeService = "core" | "loop" | "gates" | "keys" | "egress" | "harness";
3
4
  export type RuntimeServices = ReadonlySet<RuntimeService>;
4
5
  export declare const RUNTIME_SERVICES: readonly RuntimeService[];
5
6
  /** Where the gates service listens by default. */
6
7
  export declare const DEFAULT_GATES_LISTEN_PORT = 4100;
8
+ /** Where egress-gate listens by default. */
9
+ export declare const DEFAULT_EGRESS_LISTEN_PORT = 4200;
10
+ /** Where core's Harness API listener listens by default. */
11
+ export declare const DEFAULT_HARNESS_LISTEN_PORT = 4200;
12
+ /** Where a harness process answers `/health` (loopback only). */
13
+ export declare const DEFAULT_HARNESS_HEALTH_PORT = 4300;
7
14
  /** What a process runs without `--service`: core and loop, as `--role all` did. */
8
15
  export declare const DEFAULT_SERVICES: RuntimeServices;
9
16
  /** The deprecated `--role` values, and the services each stands for. */
@@ -47,17 +54,99 @@ export interface GatesConfig {
47
54
  listen: ContainerListen;
48
55
  /** `NYLORUN_GATES_TOKEN`: the bearer the loop presents; at least 32 bytes as hex. */
49
56
  token: string;
57
+ /**
58
+ * The operator's credential resolver (`NYLORUN_RESOLVER_URL`, `NYLORUN_RESOLVER_TOKEN`), asked
59
+ * for a person's MCP credential when the session's vaults hold none (F9 C1). Absent: vaults only.
60
+ */
61
+ resolver?: {
62
+ url: string;
63
+ token: string;
64
+ };
65
+ }
66
+ /** egress-gate's listener (`NYLORUN_EGRESS_LISTEN_HOST`, `NYLORUN_EGRESS_LISTEN_PORT`). */
67
+ export interface EgressConfig {
68
+ listen: {
69
+ host: string;
70
+ port: number;
71
+ };
50
72
  }
51
73
  /** Where the loop reaches the gates service (`NYLORUN_GATES_URL`, `NYLORUN_GATES_TOKEN`). */
52
74
  export interface ModelGateEndpoint {
53
75
  url: string;
54
76
  token: string;
55
77
  }
78
+ /**
79
+ * Core's Harness API listener (`NYLORUN_HARNESS=remote`, or sandbox pods): where harnesses
80
+ * connect, and the credential they present (`NYLORUN_HARNESS_TOKEN`), which nothing else
81
+ * accepts. With sandbox pods and no harness container there is no token: only pods' host tokens
82
+ * are accepted (F7.2).
83
+ */
84
+ export interface HarnessListenConfig {
85
+ listen: ContainerListen;
86
+ token?: string;
87
+ }
88
+ /**
89
+ * Sandbox pods (`NYLORUN_SANDBOXES_URL`, F7.2): the sandboxes service core drives them with, its
90
+ * token, and the Runtime image pods copy the engine from.
91
+ */
92
+ export interface SandboxesConfig {
93
+ url: string;
94
+ token: string;
95
+ /** `NYLORUN_SANDBOX_HARNESS_IMAGE` (the stack sets it to the Runtime image). */
96
+ harnessImage: string;
97
+ }
98
+ /**
99
+ * The engine in a pod sandbox (`--service harness` with `NYLORUN_SANDBOX_KIND=pod`, F7.2): no
100
+ * harness token; it joins with the join token mounted in the pod.
101
+ */
102
+ export interface PodHostConfig {
103
+ /** `NYLORUN_SANDBOX_ID`. */
104
+ sandboxId: string;
105
+ /** `NYLORUN_POD_UID` (the downward API's `metadata.uid`). */
106
+ podUid: string;
107
+ /** `NYLORUN_SANDBOX_JOIN_FILE`. */
108
+ joinFile: string;
109
+ /** The Harness API listener's HTTP base, for `host/join` and `host/renew`. */
110
+ httpUrl: string;
111
+ /** `NYLORUN_EGRESS_PROXY`: egress-gate's address, or undefined while it is not published. */
112
+ egressProxy?: string;
113
+ /** Addresses the pod's NetworkPolicy must block, probed before the engine starts. */
114
+ blocked: BlockedAddress[];
115
+ }
116
+ /** A harness process (`--service harness`). */
117
+ export interface HarnessServiceConfig {
118
+ /** `NYLORUN_HARNESS_URL`: core's Harness API, e.g. `ws://runtime:4200/nylorun/harness/v1`. */
119
+ url: string;
120
+ /** `NYLORUN_HARNESS_TOKEN`; absent for a pod's engine, which joins (`pod`). */
121
+ token?: string;
122
+ /** Present for the engine in a pod sandbox. */
123
+ pod?: PodHostConfig;
124
+ /** `NYLORUN_GATES_URL`: the gates service its model and MCP calls cross, with run tokens. */
125
+ gatesUrl: string;
126
+ /** `NYLORUN_HARNESS_ROOT`: its sandboxes, plugin data, home and tmp. Default `/harness`. */
127
+ root: string;
128
+ /** `/health` on `127.0.0.1:<NYLORUN_HARNESS_HEALTH_PORT>` (default 4300). */
129
+ healthPort: number;
130
+ }
56
131
  export interface StackConfig {
57
132
  /** The Runtime services this process runs. */
58
133
  services: RuntimeServices;
59
134
  /** Present when the process runs gates. */
60
135
  gates?: GatesConfig;
136
+ /** Present when the process runs egress. */
137
+ egress?: EgressConfig;
138
+ /** Present when the process runs the harness service (`--service harness`). */
139
+ harness?: HarnessServiceConfig;
140
+ /**
141
+ * `NYLORUN_HARNESS` (`in-process` or `remote`; absent means `in-process`), for a process running
142
+ * core or loop: whether the Tenant runs its own harness, or harnesses connect to
143
+ * `harnessListener`.
144
+ */
145
+ harnessMode?: "in-process" | "remote";
146
+ /** Present when `harnessMode` is `remote`, or with `sandboxes`: core's Harness API listener. */
147
+ harnessListener?: HarnessListenConfig;
148
+ /** Present when the process runs core or loop and `NYLORUN_SANDBOXES_URL` is set (F7.2). */
149
+ sandboxes?: SandboxesConfig;
61
150
  /**
62
151
  * Present when the process runs core or loop and `NYLORUN_GATES_URL` is set: its model calls,
63
152
  * remote MCP calls and Action deliveries cross the gates service. Required for loop in
@@ -88,11 +177,6 @@ export interface StackConfig {
88
177
  * means nothing outside the container.
89
178
  */
90
179
  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
180
  /**
97
181
  * The operator listener in container mode (`NYLORUN_ADMIN_LISTEN_PORT`, `…_HOST`,
98
182
  * `…_ALLOWED_HOSTS`). Absent: one listener serves the Admin API and the Tenant API.
@@ -110,6 +194,25 @@ export interface StackConfig {
110
194
  };
111
195
  /** Present when the process runs core or loop: who its Tenant is on first start. */
112
196
  tenant?: TenantSettings;
197
+ /**
198
+ * The Object store (blueprint D35) behind the `BlobStore` seam's `s3` adapter, from
199
+ * `NYLORUN_OBJECT_STORE_*`; the local stack's RustFS. Absent: the Tenant keeps blobs on disk
200
+ * (the `fs` adapter, `TenantPaths.blobs`).
201
+ */
202
+ objectStore?: ObjectStoreConfig;
203
+ }
204
+ /** Where the Object store is and the credential for it (`NYLORUN_OBJECT_STORE_*`). */
205
+ export interface ObjectStoreConfig {
206
+ /** `NYLORUN_OBJECT_STORE_ENDPOINT`: the S3 endpoint, e.g. `http://rustfs:9000`. */
207
+ endpoint: string;
208
+ /** `NYLORUN_OBJECT_STORE_BUCKET`. Default `nylorun`. */
209
+ bucket: string;
210
+ /** `NYLORUN_OBJECT_STORE_REGION`. Default `us-east-1`. */
211
+ region: string;
212
+ /** `NYLORUN_OBJECT_STORE_ACCESS_KEY`. */
213
+ accessKeyId: string;
214
+ /** `NYLORUN_OBJECT_STORE_SECRET_KEY`. */
215
+ secretAccessKey: string;
113
216
  }
114
217
  /**
115
218
  * The Tenant a Host creates when its database holds none (tenancy.md §4). Later starts open the
@@ -121,10 +224,10 @@ export interface TenantSettings {
121
224
  /** `NYLORUN_TENANT_NAME`. Default `default`. */
122
225
  name: string;
123
226
  /**
124
- * `NYLORUN_DERIVED_PRINCIPALS`: comma-separated application principals whose keys the admin
125
- * key derives (`deriveTenantKey`). Default `project`.
227
+ * `NYLORUN_IDENTITY_FILE`: the identity file listing the trusted issuers (Host feature
228
+ * `trusted-issuers`), read once at boot (`host/main.ts`). Absent: no issuer is trusted.
126
229
  */
127
- derivedPrincipals: readonly string[];
230
+ identityFile?: string;
128
231
  }
129
232
  export declare class StackConfigError extends Error {
130
233
  constructor(message: string);