@nylorun/runtime 0.16.0-beta → 0.19.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 (256) hide show
  1. package/CHANGELOG.md +207 -0
  2. package/README.md +105 -63
  3. package/dist/adapters/execution/restate.d.ts +1 -3
  4. package/dist/adapters/execution/restate.js +4 -31
  5. package/dist/adapters/replication/pgoutput.js +1 -1
  6. package/dist/adapters/sandbox/local.js +1 -1
  7. package/dist/adapters/sandbox/virtual.js +1 -1
  8. package/dist/api/ag-ui/endpoint.js +1 -2
  9. package/dist/api/ag-ui/routes.d.ts +1 -1
  10. package/dist/api/ag-ui/routes.js +11 -13
  11. package/dist/api/ag-ui/translate.d.ts +1 -3
  12. package/dist/api/ag-ui/translate.js +2 -17
  13. package/dist/api/components.d.ts +4783 -2588
  14. package/dist/api/components.js +19 -21
  15. package/dist/api/gate/routes.d.ts +7 -8
  16. package/dist/api/gate/routes.js +58 -52
  17. package/dist/api/http/app.d.ts +6 -0
  18. package/dist/api/http/app.js +20 -14
  19. package/dist/api/http/define.d.ts +22 -13
  20. package/dist/api/http/define.js +43 -39
  21. package/dist/api/http/routes/access.d.ts +3 -19
  22. package/dist/api/http/routes/access.js +25 -184
  23. package/dist/api/http/routes/artifacts.js +3 -7
  24. package/dist/api/http/routes/files.d.ts +8 -0
  25. package/dist/api/http/routes/files.js +77 -0
  26. package/dist/api/http/routes/keys.d.ts +10 -0
  27. package/dist/api/http/routes/keys.js +64 -0
  28. package/dist/api/http/routes/me.d.ts +13 -0
  29. package/dist/api/http/routes/me.js +48 -0
  30. package/dist/api/http/routes/reads.d.ts +66 -0
  31. package/dist/api/http/routes/reads.js +127 -0
  32. package/dist/api/http/routes/sandboxes.d.ts +2 -2
  33. package/dist/api/http/routes/sandboxes.js +8 -7
  34. package/dist/api/http/routes/sessions.js +31 -20
  35. package/dist/api/http/routes/tenant.js +15 -18
  36. package/dist/api/http/routes/vaults.d.ts +13 -3
  37. package/dist/api/http/routes/vaults.js +136 -64
  38. package/dist/api/openapi.d.ts +10 -11
  39. package/dist/api/openapi.js +292 -68
  40. package/dist/artifacts/links.js +6 -6
  41. package/dist/artifacts/service.js +1 -1
  42. package/dist/core/flow-host.d.ts +44 -23
  43. package/dist/core/flow-host.js +88 -106
  44. package/dist/core/provider.d.ts +4 -1
  45. package/dist/core/provider.js +6 -1
  46. package/dist/core/sandbox-routes.d.ts +0 -9
  47. package/dist/core/sandbox-routes.js +2 -21
  48. package/dist/execution/memory.d.ts +4 -5
  49. package/dist/execution/memory.js +11 -25
  50. package/dist/execution/types.d.ts +3 -33
  51. package/dist/execution/types.js +2 -12
  52. package/dist/gates/http-tool.d.ts +86 -0
  53. package/dist/gates/http-tool.js +197 -0
  54. package/dist/gates/mcp-handler.d.ts +3 -0
  55. package/dist/gates/mcp-handler.js +6 -4
  56. package/dist/gates/meter.d.ts +2 -2
  57. package/dist/gates/meter.js +6 -0
  58. package/dist/gates/tenant-vaults.d.ts +19 -14
  59. package/dist/gates/tenant-vaults.js +17 -12
  60. package/dist/gates/tool-calls.d.ts +7 -15
  61. package/dist/gates/tool-calls.js +4 -4
  62. package/dist/gates/tool-client.d.ts +1 -1
  63. package/dist/gates/tool-client.js +23 -47
  64. package/dist/gates/tool-contract.d.ts +27 -39
  65. package/dist/gates/tool-contract.js +30 -21
  66. package/dist/gates/tool-gate.d.ts +25 -24
  67. package/dist/gates/tool-gate.js +11 -7
  68. package/dist/harness/calls.d.ts +10 -4
  69. package/dist/harness/calls.js +15 -6
  70. package/dist/harness/executors.d.ts +1 -1
  71. package/dist/harness/executors.js +17 -12
  72. package/dist/harness/main.d.ts +2 -2
  73. package/dist/harness/main.js +5 -13
  74. package/dist/harness/service.d.ts +4 -5
  75. package/dist/harness/service.js +9 -2
  76. package/dist/harness-api/in-process.js +2 -2
  77. package/dist/harness-api/record.d.ts +6 -3
  78. package/dist/harness-api/record.js +65 -72
  79. package/dist/harness-api/server.d.ts +1 -9
  80. package/dist/harness-api/server.js +19 -17
  81. package/dist/harness-api/start.js +1 -5
  82. package/dist/harness-api/workspace.js +2 -3
  83. package/dist/harness-api/ws-server.js +1 -1
  84. package/dist/host/app.d.ts +2 -10
  85. package/dist/host/app.js +24 -44
  86. package/dist/host/config-for.d.ts +5 -5
  87. package/dist/host/config-for.js +2 -3
  88. package/dist/host/config.d.ts +0 -11
  89. package/dist/host/create-host.d.ts +3 -40
  90. package/dist/host/create-host.js +18 -91
  91. package/dist/host/environment.d.ts +0 -3
  92. package/dist/host/environment.js +0 -9
  93. package/dist/host/gates.d.ts +4 -1
  94. package/dist/host/gates.js +9 -3
  95. package/dist/host/http.d.ts +1 -2
  96. package/dist/host/http.js +1 -7
  97. package/dist/host/index.d.ts +2 -2
  98. package/dist/host/index.js +2 -2
  99. package/dist/host/main.js +55 -40
  100. package/dist/host/operate.d.ts +29 -0
  101. package/dist/host/operate.js +223 -0
  102. package/dist/host/stack-config.d.ts +23 -20
  103. package/dist/host/stack-config.js +28 -44
  104. package/dist/index.d.ts +4 -1
  105. package/dist/index.js +3 -1
  106. package/dist/infra/workers.d.ts +1 -1
  107. package/dist/keys/client.js +9 -1
  108. package/dist/keys/keys.d.ts +12 -2
  109. package/dist/keys/keys.js +10 -2
  110. package/dist/management-openapi.json +6491 -0
  111. package/dist/mcp/connect.d.ts +8 -4
  112. package/dist/mcp/connect.js +8 -27
  113. package/dist/mcp/pool.d.ts +6 -9
  114. package/dist/mcp/pool.js +8 -18
  115. package/dist/mcp/snapshot.d.ts +8 -2
  116. package/dist/mcp/snapshot.js +29 -2
  117. package/dist/media.d.ts +1 -1
  118. package/dist/media.js +1 -1
  119. package/dist/model/pi-model.js +3 -0
  120. package/dist/openapi.json +6228 -15615
  121. package/dist/reads/access.d.ts +4 -0
  122. package/dist/reads/access.js +12 -0
  123. package/dist/reads/cursor.d.ts +11 -0
  124. package/dist/reads/cursor.js +80 -0
  125. package/dist/reads/types.d.ts +25 -0
  126. package/dist/reads/types.js +1 -0
  127. package/dist/sandbox/manager.d.ts +16 -0
  128. package/dist/sandbox/manager.js +40 -0
  129. package/dist/sandbox/session-sandbox.js +13 -3
  130. package/dist/sandbox/skills.d.ts +13 -0
  131. package/dist/sandbox/skills.js +46 -0
  132. package/dist/sandbox/types.d.ts +2 -2
  133. package/dist/store/ownership.d.ts +9 -0
  134. package/dist/store/ownership.js +13 -0
  135. package/dist/store/postgres/connect.d.ts +2 -0
  136. package/dist/store/postgres/connect.js +29 -0
  137. package/dist/store/postgres/drizzle/0008_installation_vaults.sql +2 -0
  138. package/dist/store/postgres/drizzle/0009_oauth_pending.sql +19 -0
  139. package/dist/store/postgres/drizzle/0010_oss_auth_removals.sql +3 -0
  140. package/dist/store/postgres/drizzle/0011_key_roles.sql +3 -0
  141. package/dist/store/postgres/drizzle/0012_session_reads.sql +8 -0
  142. package/dist/store/postgres/drizzle/0013_definition_files.sql +16 -0
  143. package/dist/store/postgres/drizzle/0014_action_endpoints_removed.sql +2 -0
  144. package/dist/store/postgres/drizzle/meta/_journal.json +49 -0
  145. package/dist/store/postgres/reads.d.ts +3 -0
  146. package/dist/store/postgres/reads.js +272 -0
  147. package/dist/store/postgres/schema.d.ts +423 -594
  148. package/dist/store/postgres/schema.js +84 -101
  149. package/dist/store/postgres/store.js +96 -248
  150. package/dist/store/postgres/tenant.d.ts +34 -2
  151. package/dist/store/postgres/tenant.js +16 -32
  152. package/dist/store/types.d.ts +44 -119
  153. package/dist/store/types.js +0 -1
  154. package/dist/streams/control.d.ts +0 -7
  155. package/dist/streams/control.js +0 -13
  156. package/dist/streams/types.d.ts +2 -20
  157. package/dist/streams/types.js +1 -1
  158. package/dist/tenant/advance.d.ts +3 -3
  159. package/dist/tenant/advance.js +10 -7
  160. package/dist/tenant/auth.d.ts +24 -15
  161. package/dist/tenant/auth.js +44 -55
  162. package/dist/tenant/cause.d.ts +3 -3
  163. package/dist/tenant/cause.js +1 -1
  164. package/dist/tenant/commands.d.ts +2 -18
  165. package/dist/tenant/commands.js +38 -179
  166. package/dist/tenant/context.d.ts +33 -45
  167. package/dist/tenant/definition-files.d.ts +24 -0
  168. package/dist/tenant/definition-files.js +103 -0
  169. package/dist/tenant/effects.d.ts +20 -18
  170. package/dist/tenant/effects.js +59 -133
  171. package/dist/tenant/ephemeral.d.ts +21 -8
  172. package/dist/tenant/ephemeral.js +11 -12
  173. package/dist/tenant/host-token.js +5 -5
  174. package/dist/tenant/identity-file.d.ts +75 -0
  175. package/dist/tenant/identity-file.js +202 -0
  176. package/dist/tenant/issuers.d.ts +48 -0
  177. package/dist/tenant/issuers.js +311 -0
  178. package/dist/tenant/jwt.js +3 -3
  179. package/dist/tenant/module.d.ts +1 -4
  180. package/dist/tenant/module.js +3 -12
  181. package/dist/tenant/operator-keys.d.ts +44 -0
  182. package/dist/tenant/operator-keys.js +87 -0
  183. package/dist/tenant/outbound.d.ts +40 -1
  184. package/dist/tenant/outbound.js +167 -18
  185. package/dist/tenant/paths.d.ts +1 -1
  186. package/dist/tenant/paths.js +1 -3
  187. package/dist/tenant/principals.d.ts +13 -13
  188. package/dist/tenant/principals.js +35 -36
  189. package/dist/tenant/run-token.js +6 -6
  190. package/dist/tenant/runtime.d.ts +5 -3
  191. package/dist/tenant/runtime.js +29 -15
  192. package/dist/tenant/sandboxes.d.ts +9 -6
  193. package/dist/tenant/sandboxes.js +16 -5
  194. package/dist/tenant/scheduler.d.ts +1 -5
  195. package/dist/tenant/scheduler.js +2 -5
  196. package/dist/tenant/session-streams.d.ts +8 -11
  197. package/dist/tenant/session-streams.js +48 -32
  198. package/dist/tenant/session.d.ts +8 -41
  199. package/dist/tenant/session.js +0 -37
  200. package/dist/tenant/sessions.d.ts +2 -2
  201. package/dist/tenant/sessions.js +17 -9
  202. package/dist/tenant/signing-keys.d.ts +3 -2
  203. package/dist/tenant/signing-keys.js +5 -5
  204. package/dist/tenant/skill-tool.d.ts +20 -0
  205. package/dist/tenant/skill-tool.js +72 -0
  206. package/dist/tenant/slim.d.ts +1 -1
  207. package/dist/tenant/slim.js +1 -1
  208. package/dist/tenant/status.js +7 -12
  209. package/dist/tenant/store-pg.d.ts +15 -0
  210. package/dist/tenant/store-pg.js +7 -3
  211. package/dist/tenant/streams.d.ts +3 -11
  212. package/dist/tenant/streams.js +2 -29
  213. package/dist/tenant/sweep.js +0 -5
  214. package/dist/tenant/transcript.d.ts +2 -2
  215. package/dist/tenant/transcript.js +2 -2
  216. package/dist/tenant/types.d.ts +33 -19
  217. package/dist/tenant/worker.d.ts +1 -3
  218. package/dist/tenant/worker.js +3 -8
  219. package/dist/vault/crypto.d.ts +7 -0
  220. package/dist/vault/crypto.js +16 -0
  221. package/dist/vault/oauth.d.ts +55 -0
  222. package/dist/vault/oauth.js +186 -0
  223. package/dist/vault/service.d.ts +54 -7
  224. package/dist/vault/service.js +261 -17
  225. package/dist/vault/sources.d.ts +60 -0
  226. package/dist/vault/sources.js +196 -0
  227. package/dist/version.d.ts +1 -1
  228. package/dist/version.js +1 -1
  229. package/package.json +5 -4
  230. package/dist/admin-openapi.json +0 -604
  231. package/dist/api/http/routes/actions.d.ts +0 -8
  232. package/dist/api/http/routes/actions.js +0 -76
  233. package/dist/api/http/routes/endpoints.d.ts +0 -7
  234. package/dist/api/http/routes/endpoints.js +0 -100
  235. package/dist/host/admin-api.d.ts +0 -23
  236. package/dist/host/admin-api.js +0 -74
  237. package/dist/host/cors.d.ts +0 -27
  238. package/dist/host/cors.js +0 -64
  239. package/dist/mcp/stdio.d.ts +0 -17
  240. package/dist/mcp/stdio.js +0 -82
  241. package/dist/tenant/access-policy.d.ts +0 -30
  242. package/dist/tenant/access-policy.js +0 -52
  243. package/dist/tenant/actions.d.ts +0 -14
  244. package/dist/tenant/actions.js +0 -29
  245. package/dist/tenant/browser.d.ts +0 -15
  246. package/dist/tenant/browser.js +0 -39
  247. package/dist/tenant/delivery-token.d.ts +0 -41
  248. package/dist/tenant/delivery-token.js +0 -104
  249. package/dist/tenant/delivery.d.ts +0 -62
  250. package/dist/tenant/delivery.js +0 -481
  251. package/dist/tenant/endpoints.d.ts +0 -67
  252. package/dist/tenant/endpoints.js +0 -90
  253. package/dist/tenant/subject-limits.d.ts +0 -16
  254. package/dist/tenant/subject-limits.js +0 -45
  255. package/dist/tenant/tokens.d.ts +0 -14
  256. package/dist/tenant/tokens.js +0 -187
package/dist/host/main.js CHANGED
@@ -9,8 +9,8 @@
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
16
  * the Tenant and the infrastructure checks. With an Object store
@@ -29,6 +29,8 @@ import { homedir } from "node:os";
29
29
  import { resolve } from "node:path";
30
30
  import { hostPaths } from "../tenant/paths.js";
31
31
  import { createTenantModule } from "../tenant/module.js";
32
+ import { parseIdentityFile } from "../tenant/identity-file.js";
33
+ import { createTrustedIssuers } from "../tenant/issuers.js";
32
34
  import { hostPrincipals } from "../tenant/principals.js";
33
35
  import { createPostgresTenantOpener } from "../tenant/store-pg.js";
34
36
  import { openTenantRuntime } from "../tenant/runtime.js";
@@ -66,6 +68,35 @@ function coreVersion() {
66
68
  function loadJson(path) {
67
69
  return JSON.parse(readFileSync(path, "utf8"));
68
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
+ }
86
+ /** The bootstrap management key: 64 hex characters, surrounding whitespace ignored. */
87
+ function loadManagementKey(path) {
88
+ let text;
89
+ try {
90
+ text = readFileSync(path, "utf8");
91
+ }
92
+ catch (error) {
93
+ throw new Error(`NYLORUN_MANAGEMENT_KEY_FILE names ${path}, which cannot be read: ${error instanceof Error ? error.message : String(error)}`);
94
+ }
95
+ const key = text.trim();
96
+ if (!/^[0-9a-f]{64}$/.test(key))
97
+ throw new Error(`NYLORUN_MANAGEMENT_KEY_FILE (${path}) must hold one key of 64 lowercase hex characters, such as \`openssl rand -hex 32\``);
98
+ return key;
99
+ }
69
100
  function resolveHostRoot() {
70
101
  const fromEnv = process.env.NYLORUN_HOME;
71
102
  const root = fromEnv && fromEnv.length > 0 ? fromEnv : resolve(homedir(), ".nylorun");
@@ -105,11 +136,6 @@ async function ensureBucket(store, bucket, logger) {
105
136
  }
106
137
  }
107
138
  }
108
- /** The slot's lag in WAL bytes, when the source can tell. */
109
- async function lagOf(source) {
110
- const lagBytes = await source.lag?.().catch(() => undefined);
111
- return lagBytes === undefined ? {} : { lagBytes };
112
- }
113
139
  /**
114
140
  * The gateway's services: the Model Gate's listener (gates, keys) and egress-gate's (egress), over
115
141
  * the Postgres pool and the Host's tenant directory. Writes nothing to the Host root (the local
@@ -124,6 +150,8 @@ async function runGates(stack) {
124
150
  ...(stack.packing ? { packing: stack.packing } : {}),
125
151
  endpoints: describeEndpoints(stack.endpoints),
126
152
  objectStore: stack.objectStore ? "s3" : "none",
153
+ // Never the resolver's URL or token: only whether one is set (F9 C1).
154
+ resolver: stack.gates?.resolver ? "configured" : "none",
127
155
  });
128
156
  const database = createDatabase(stack);
129
157
  const servers = [];
@@ -195,7 +223,14 @@ export async function main() {
195
223
  }
196
224
  const config = loadJson(paths.config);
197
225
  const credentials = loadJson(paths.credentials);
226
+ const identityFile = stack.tenant?.identityFile;
227
+ const issuers = identityFile ? loadIssuers(identityFile) : undefined;
198
228
  const logger = createHostLogger();
229
+ if (issuers)
230
+ logger.info("trusted_issuers", {
231
+ file: identityFile,
232
+ issuers: issuers.issuers.map((issuer) => issuer.config.name),
233
+ });
199
234
  if (stack.deprecatedRole)
200
235
  logger.warn("deprecated_flag", {
201
236
  flag: "--role",
@@ -216,15 +251,14 @@ export async function main() {
216
251
  const database = infra.database;
217
252
  if (!database)
218
253
  throw new Error("NYLORUN_DATABASE_URL did not yield a Postgres pool");
219
- const baseline = baselineEnvironment(process.env);
220
254
  // A Tenant that should answer with the fixture model carries the Tenant
221
255
  // setting (`tenant/model-setting.ts`); the Host has no fixture mode.
222
256
  const configFor = configForFactory({
223
257
  hostRoot,
224
- hostConfig: config,
225
258
  logger,
226
- baseline,
227
259
  ...(stack.delivery ? { delivery: stack.delivery } : {}),
260
+ ...(issuers ? { issuers } : {}),
261
+ ...(stack.publicUrl ? { publicUrl: stack.publicUrl } : {}),
228
262
  });
229
263
  // The process's Durable Session Execution: Restate when its endpoints are set, else the
230
264
  // in-process memory execution. An invocation that arrives while the Tenant opens waits for it.
@@ -242,7 +276,6 @@ export async function main() {
242
276
  // running core runs one, once the Tenant is open (the relay reads its id); the replication
243
277
  // slot lets exactly one be active. Without S2, the Tenant relays its own commits.
244
278
  let relay;
245
- let relayLag;
246
279
  if (streams && stack.services.has("core"))
247
280
  await assertLogicalReplication(database);
248
281
  const startRelay = (tenantId) => {
@@ -259,14 +292,12 @@ export async function main() {
259
292
  streams,
260
293
  log: (message, fields) => logger.info(message, fields),
261
294
  });
262
- const status = relay.status;
263
- relayLag = async () => ({ ...status(), ...(await lagOf(source)) });
264
295
  relay.start();
265
296
  };
266
- // With the gates service the Tenant's vault-backed model calls, remote MCP calls and Action
267
- // deliveries cross it, and this process never reads a model or MCP credential.
268
- // Their credentials (F5): each advance's run token for its session's model and MCP calls,
269
- // core's credential (NYLORUN_GATES_TOKEN) for deliveries and MCP requests outside a run.
297
+ // With the gates service the Tenant's vault-backed model calls, remote MCP calls and HTTP
298
+ // tool calls cross it, and this process never reads a model or MCP credential.
299
+ // Their credentials (F5): each advance's run token for its session's model and tool calls,
300
+ // core's credential (NYLORUN_GATES_TOKEN) for MCP requests outside a run.
270
301
  const runGrants = stack.modelGate ? createRunGrants() : undefined;
271
302
  const modelGate = stack.modelGate && runGrants
272
303
  ? httpModelGate({ url: stack.modelGate.url, runTokens: runGrants })
@@ -290,7 +321,7 @@ export async function main() {
290
321
  harnessImage: stack.sandboxes.harnessImage,
291
322
  }
292
323
  : undefined;
293
- const tenantSettings = stack.tenant ?? { name: "default", derivedPrincipals: ["project"] };
324
+ const tenantSettings = stack.tenant ?? { name: "default" };
294
325
  const module = createTenantModule({
295
326
  open: createPostgresTenantOpener({
296
327
  hostRoot,
@@ -300,7 +331,9 @@ export async function main() {
300
331
  name: tenantSettings.name,
301
332
  principals: hostPrincipals({
302
333
  adminKey: credentials.adminKey,
303
- derived: tenantSettings.derivedPrincipals,
334
+ ...(stack.tenant?.managementKeyFile
335
+ ? { bootstrapKey: loadManagementKey(stack.tenant.managementKeyFile) }
336
+ : {}),
304
337
  }),
305
338
  },
306
339
  configFor,
@@ -320,34 +353,16 @@ export async function main() {
320
353
  }),
321
354
  logger,
322
355
  onOpen: (handle) => startRelay(handle.envelope.id),
323
- relayStatus: async () => {
324
- if (!relayLag)
325
- throw new Error("no relay");
326
- return relayLag();
327
- },
328
356
  });
329
357
  const options = {
330
358
  hostRoot,
331
359
  module,
332
360
  config,
333
- credentials,
334
361
  logger,
335
362
  coreVersion: coreVersion(),
336
363
  ...(stack.listen ? { listen: stack.listen } : {}),
337
- ...(stack.publicUrl ? { publicUrl: stack.publicUrl } : {}),
338
- // Container mode (the local stack) allows browsers unless told not to; a Host started
339
- // from host.json only when it says so. Without publishable keys nothing is reachable.
340
- browserAccess: stack.browserAccess ?? (stack.listen ? true : config.browserAccess === true),
341
- // The Admin API on its own listener: from the container environment, or from host.json
342
- // (loopback, on the same host) when the Host runs outside a container.
343
- ...(stack.operator
344
- ? { operator: stack.operator }
345
- : !stack.listen && typeof config.adminPort === "number"
346
- ? { operator: { host: config.host, port: config.adminPort } }
347
- : {}),
348
364
  ...(infra.readiness ? { readiness: infra.readiness } : {}),
349
- // SIGTERM and POST /v1/admin/host/shutdown both close the Host this way:
350
- // stop the Worker and the relay, close the Tenant, then end the infrastructure clients.
365
+ // SIGTERM closes the Host this way: stop the Worker and the relay, close the Tenant, then end the infrastructure clients.
351
366
  shutdown: {
352
367
  beforeTenants: async () => {
353
368
  await hostExecution.stop();
@@ -370,8 +385,8 @@ export async function main() {
370
385
  // listener starts and opens the Tenant (migrating its database, creating it on
371
386
  // first start); opening arms its sweep, which recovers wakes lost with
372
387
  // Restate's state (§14.8), and starts the stream relay. A Tenant that cannot
373
- // be opened leaves the Host listening but not ready, with the cause in
374
- // `/v1/admin/status`.
388
+ // be opened leaves the Host listening but not ready; `nylorun-operate status` names the
389
+ // cause.
375
390
  // The Harness API listener (NYLORUN_HARNESS=remote): only harnesses connect here, with the
376
391
  // harness credential, and only while the Tenant is open.
377
392
  let harnessListener;
@@ -0,0 +1,29 @@
1
+ import type { TenantCause } from "@nylorun/core/contracts";
2
+ export declare const EXIT_REFUSED = 1;
3
+ export declare const EXIT_TENANT = 2;
4
+ export declare const EXIT_USAGE = 64;
5
+ export interface OperateIo {
6
+ env: Readonly<Record<string, string | undefined>>;
7
+ out(line: string): void;
8
+ err(line: string): void;
9
+ /** For the Host's `/ready`. Default: the global `fetch`. */
10
+ fetch?: typeof fetch;
11
+ }
12
+ /** `nylorun-operate status --json`. */
13
+ export interface OperateStatus {
14
+ version: string;
15
+ protocol: {
16
+ min: number;
17
+ max: number;
18
+ features: string[];
19
+ };
20
+ tenant: {
21
+ id: string | null;
22
+ name: string | null;
23
+ state: "open" | "unavailable";
24
+ cause?: TenantCause["code"];
25
+ message?: string;
26
+ };
27
+ }
28
+ /** Runs one command; resolves to the exit code. */
29
+ export declare function runOperate(argv: readonly string[], io: OperateIo): Promise<number>;
@@ -0,0 +1,223 @@
1
+ /**
2
+ * `nylorun-operate`: the Host's one-shot operator command, run inside the runtime container
3
+ * (`docker compose exec runtime nylorun-operate …`, `kubectl exec … -- nylorun-operate …`). It
4
+ * reads the Tenant's database from `NYLORUN_DATABASE_URL`, as the Host does, does one job and
5
+ * exits. Being able to run it is the authorization: whoever can exec into the container holds
6
+ * the installation. It never migrates or creates the Tenant: the Host does that.
7
+ *
8
+ * nylorun-operate status [--json]
9
+ * nylorun-operate keys list [--json]
10
+ * nylorun-operate keys put <id> [--role application|management] [--json]
11
+ * nylorun-operate keys rm <id> [--json]
12
+ *
13
+ * `status` says whether the Tenant is open and, when it is not, why: from the database (no
14
+ * Tenant yet, an old layout, a schema newer or older than this Runtime) and from the running
15
+ * Host's `/ready` on this container's listen port. It needs no key, so it works when the Tenant
16
+ * cannot open. `keys put` prints the new key once on stdout (with `--json`, the whole
17
+ * response). Exit codes: 0 done (`status`: the Tenant is open), 1 refused (an id, a role),
18
+ * 2 the Tenant cannot be opened (`status` still prints it), 64 usage.
19
+ */
20
+ import { pathToFileURL } from "node:url";
21
+ import { HOST_PROTOCOL } from "@nylorun/core/compatibility";
22
+ import { createPostgresClient } from "../store/postgres/connect.js";
23
+ import { database } from "../store/postgres/db.js";
24
+ import { assertCurrentLayout, expectedSchemaVersion, readSchemaVersion, } from "../store/postgres/migrate.js";
25
+ import { createPostgresSessionStore } from "../store/postgres/store.js";
26
+ import { readTenantEnvelope } from "../store/postgres/tenant.js";
27
+ import { openError, TenantOpenError } from "../tenant/cause.js";
28
+ import { operatorKeys } from "../tenant/operator-keys.js";
29
+ import { RUNTIME_VERSION } from "../version.js";
30
+ import { DEFAULT_CONTAINER_LISTEN_PORT } from "./stack-config.js";
31
+ export const EXIT_REFUSED = 1;
32
+ export const EXIT_TENANT = 2;
33
+ export const EXIT_USAGE = 64;
34
+ const USAGE = `Usage:
35
+ nylorun-operate status [--json]
36
+ nylorun-operate keys list [--json]
37
+ nylorun-operate keys put <id> [--role application|management] [--json]
38
+ nylorun-operate keys rm <id> [--json]`;
39
+ class Exit extends Error {
40
+ code;
41
+ constructor(code, message) {
42
+ super(message);
43
+ this.code = code;
44
+ }
45
+ }
46
+ /** Runs one command; resolves to the exit code. */
47
+ export async function runOperate(argv, io) {
48
+ try {
49
+ if (argv[0] === "status") {
50
+ const json = argv[1] === "--json";
51
+ if (argv.length > (json ? 2 : 1))
52
+ throw new Exit(EXIT_USAGE, USAGE);
53
+ return await status(io, json);
54
+ }
55
+ const command = parse(argv);
56
+ const url = io.env.NYLORUN_DATABASE_URL;
57
+ if (!url)
58
+ throw new Exit(EXIT_TENANT, "NYLORUN_DATABASE_URL is not set: run this inside the runtime container");
59
+ const sql = createPostgresClient(url, { max: 1 });
60
+ try {
61
+ const keys = operatorKeys(await openStore(sql));
62
+ return await command(keys, io);
63
+ }
64
+ finally {
65
+ await sql.end({ timeout: 5 });
66
+ }
67
+ }
68
+ catch (error) {
69
+ if (error instanceof Exit) {
70
+ io.err(error.message);
71
+ return error.code;
72
+ }
73
+ io.err(`nylorun-operate failed: ${error instanceof Error ? error.message : String(error)}`);
74
+ return EXIT_TENANT;
75
+ }
76
+ }
77
+ function parse(argv) {
78
+ const [group, verb, ...rest] = argv;
79
+ if (group !== "keys")
80
+ throw new Exit(EXIT_USAGE, USAGE);
81
+ const json = rest.includes("--json");
82
+ const args = rest.filter((arg) => arg !== "--json");
83
+ switch (verb) {
84
+ case "list":
85
+ if (args.length > 0)
86
+ throw new Exit(EXIT_USAGE, USAGE);
87
+ return async (keys, io) => {
88
+ const list = await keys.list();
89
+ if (json)
90
+ io.out(JSON.stringify({ keys: list }));
91
+ else
92
+ for (const key of list)
93
+ io.out(`${key.id}\t${key.role}\t${key.createdAt}`);
94
+ return 0;
95
+ };
96
+ case "put": {
97
+ const [id, ...flags] = args;
98
+ if (!id || id.startsWith("-"))
99
+ throw new Exit(EXIT_USAGE, USAGE);
100
+ let role = "application";
101
+ if (flags.length > 0) {
102
+ const value = flags[0] === "--role" ? flags[1] : undefined;
103
+ if (flags.length !== 2 || (value !== "application" && value !== "management"))
104
+ throw new Exit(EXIT_USAGE, USAGE);
105
+ role = value;
106
+ }
107
+ return async (keys, io) => {
108
+ const put = await keys.put(id, role);
109
+ if ("reason" in put)
110
+ throw new Exit(EXIT_REFUSED, put.message);
111
+ if (json)
112
+ io.out(JSON.stringify(put));
113
+ else {
114
+ io.out(put.key);
115
+ io.err(`${put.rotated ? "Rotated" : "Created"} ${put.role} key ${put.id}. It is shown this once.`);
116
+ }
117
+ return 0;
118
+ };
119
+ }
120
+ case "rm": {
121
+ const [id] = args;
122
+ if (!id || args.length !== 1)
123
+ throw new Exit(EXIT_USAGE, USAGE);
124
+ return async (keys, io) => {
125
+ const deleted = await keys.delete(id);
126
+ if (typeof deleted !== "boolean")
127
+ throw new Exit(EXIT_REFUSED, deleted.message);
128
+ if (json)
129
+ io.out(JSON.stringify({ id, deleted }));
130
+ else
131
+ io.err(deleted ? `Deleted key ${id}.` : `There is no key ${id}.`);
132
+ return 0;
133
+ };
134
+ }
135
+ default:
136
+ throw new Exit(EXIT_USAGE, USAGE);
137
+ }
138
+ }
139
+ async function status(io, json) {
140
+ const tenant = await tenantStatus(io);
141
+ const report = {
142
+ version: RUNTIME_VERSION,
143
+ protocol: { min: HOST_PROTOCOL.min, max: HOST_PROTOCOL.max, features: [...HOST_PROTOCOL.features] },
144
+ tenant,
145
+ };
146
+ if (json)
147
+ io.out(JSON.stringify(report));
148
+ else {
149
+ io.out(`Runtime ${report.version}, protocol ${report.protocol.min}–${report.protocol.max}`);
150
+ const named = tenant.name ? `${tenant.name} (${tenant.id})` : "none";
151
+ io.out(`Tenant ${named}: ${tenant.state}${tenant.cause ? ` (${tenant.cause})` : ""}`);
152
+ if (tenant.message)
153
+ io.out(tenant.message);
154
+ }
155
+ return tenant.state === "open" ? 0 : EXIT_TENANT;
156
+ }
157
+ /** The Tenant from its database, then from the running Host's `/ready`. */
158
+ async function tenantStatus(io) {
159
+ const url = io.env.NYLORUN_DATABASE_URL;
160
+ if (!url)
161
+ return unavailable(null, "NYLORUN_DATABASE_URL is not set: run this inside the runtime container");
162
+ const sql = createPostgresClient(url, { max: 1 });
163
+ let envelope;
164
+ try {
165
+ const db = database(sql);
166
+ await assertCurrentLayout(db);
167
+ envelope = await readTenantEnvelope(db);
168
+ if (!envelope)
169
+ return unavailable(null, "The database holds no Tenant yet: the Runtime creates it when it starts");
170
+ const version = (await readSchemaVersion(db)) ?? 0;
171
+ const expected = expectedSchemaVersion();
172
+ if (version > expected)
173
+ throw openError("schema-too-new", `The Tenant's schema is at version ${version}, this Runtime's is ${expected}: it was migrated by a newer Runtime`);
174
+ if (version < expected)
175
+ return unavailable(envelope, `The Tenant's schema is at version ${version}, this Runtime's is ${expected}: the Runtime migrates it when it starts; if it is running, its log says why it did not`);
176
+ }
177
+ catch (error) {
178
+ if (error instanceof TenantOpenError)
179
+ return { ...unavailable(envelope ?? null, `${error.message}. Repair: ${error.repair}`), cause: error.code };
180
+ return unavailable(envelope ?? null, `The database cannot be read: ${error instanceof Error ? error.message : String(error)}`);
181
+ }
182
+ finally {
183
+ await sql.end({ timeout: 5 });
184
+ }
185
+ const port = io.env.NYLORUN_LISTEN_PORT ?? String(DEFAULT_CONTAINER_LISTEN_PORT);
186
+ const ready = `http://127.0.0.1:${port}/ready`;
187
+ let answer;
188
+ try {
189
+ const response = await (io.fetch ?? fetch)(ready, { signal: AbortSignal.timeout(5000) });
190
+ answer = (await response.json());
191
+ }
192
+ catch {
193
+ return unavailable(envelope, `The Runtime is not answering at ${ready}: it is stopped or still starting`);
194
+ }
195
+ if (answer.checks?.tenant !== true)
196
+ return unavailable(envelope, "The Runtime is running but has not opened the Tenant: its log names the cause");
197
+ return { id: envelope.id, name: envelope.name, state: "open" };
198
+ }
199
+ function unavailable(envelope, message) {
200
+ return { id: envelope?.id ?? null, name: envelope?.name ?? null, state: "unavailable", message };
201
+ }
202
+ /** The Tenant's store, when the Host has created and migrated it to this Runtime's schema. */
203
+ async function openStore(sql) {
204
+ const db = database(sql);
205
+ const envelope = await readTenantEnvelope(db).catch((error) => {
206
+ throw new Exit(EXIT_TENANT, `The Tenant's database cannot be read: ${error instanceof Error ? error.message : String(error)}`);
207
+ });
208
+ if (!envelope)
209
+ throw new Exit(EXIT_TENANT, "The database holds no Tenant yet: start the Runtime first");
210
+ const version = await readSchemaVersion(db);
211
+ const expected = expectedSchemaVersion();
212
+ if (version !== expected)
213
+ throw new Exit(EXIT_TENANT, `The Tenant's schema is at version ${version ?? "none"}, this Runtime's is ${expected}: start the Runtime to migrate it, or run the Runtime's own image`);
214
+ return createPostgresSessionStore({ sql, tenantId: envelope.id, schemaVersion: expected });
215
+ }
216
+ if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
217
+ const code = await runOperate(process.argv.slice(2), {
218
+ env: process.env,
219
+ out: (line) => process.stdout.write(`${line}\n`),
220
+ err: (line) => process.stderr.write(`${line}\n`),
221
+ });
222
+ process.exitCode = code;
223
+ }
@@ -54,6 +54,14 @@ export interface GatesConfig {
54
54
  listen: ContainerListen;
55
55
  /** `NYLORUN_GATES_TOKEN`: the bearer the loop presents; at least 32 bytes as hex. */
56
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
+ };
57
65
  }
58
66
  /** egress-gate's listener (`NYLORUN_EGRESS_LISTEN_HOST`, `NYLORUN_EGRESS_LISTEN_PORT`). */
59
67
  export interface EgressConfig {
@@ -115,7 +123,7 @@ export interface HarnessServiceConfig {
115
123
  pod?: PodHostConfig;
116
124
  /** `NYLORUN_GATES_URL`: the gates service its model and MCP calls cross, with run tokens. */
117
125
  gatesUrl: string;
118
- /** `NYLORUN_HARNESS_ROOT`: its sandboxes, plugin data, home and tmp. Default `/harness`. */
126
+ /** `NYLORUN_HARNESS_ROOT`: its sandboxes. Default `/harness`. */
119
127
  root: string;
120
128
  /** `/health` on `127.0.0.1:<NYLORUN_HARNESS_HEALTH_PORT>` (default 4300). */
121
129
  healthPort: number;
@@ -141,7 +149,7 @@ export interface StackConfig {
141
149
  sandboxes?: SandboxesConfig;
142
150
  /**
143
151
  * Present when the process runs core or loop and `NYLORUN_GATES_URL` is set: its model calls,
144
- * remote MCP calls and Action deliveries cross the gates service. Required for loop in
152
+ * remote MCP calls and HTTP tool calls cross the gates service. Required for loop in
145
153
  * container mode; outside a container (a development Host, tests) the loop may make them in
146
154
  * its own process.
147
155
  */
@@ -164,24 +172,13 @@ export interface StackConfig {
164
172
  listen?: ContainerListen;
165
173
  endpoints: StackEndpoints;
166
174
  /**
167
- * The URL clients use to reach this Host (`NYLORUN_PUBLIC_URL`), reported by
168
- * `/v1/admin/status`. In container mode the bind address (`0.0.0.0:4000`)
169
- * means nothing outside the container.
175
+ * The URL clients use to reach this Host (`NYLORUN_PUBLIC_URL`). In container mode the bind
176
+ * address (`0.0.0.0:4000`) means nothing outside the container.
170
177
  */
171
178
  publicUrl?: string;
172
179
  /**
173
- * `NYLORUN_BROWSER_ACCESS` (`on` or `off`): whether browser requests may reach Tenant
174
- * routes. Absent means the Host's default (on in container mode).
175
- */
176
- browserAccess?: boolean;
177
- /**
178
- * The operator listener in container mode (`NYLORUN_ADMIN_LISTEN_PORT`, `…_HOST`,
179
- * `…_ALLOWED_HOSTS`). Absent: one listener serves the Admin API and the Tenant API.
180
- */
181
- operator?: ContainerListen;
182
- /**
183
- * How the Runtime may call Action endpoints: `NYLORUN_ENDPOINT_LOOPBACK=docker-host` (the local
184
- * stack: `localhost` means the machine that runs Docker), `NYLORUN_ENDPOINT_PRIVATE`
180
+ * How the Runtime may call MCP servers and HTTP tools: `NYLORUN_ENDPOINT_LOOPBACK=docker-host`
181
+ * (the local stack: `localhost` means the machine that runs Docker), `NYLORUN_ENDPOINT_PRIVATE`
185
182
  * (`allow` or `refuse`) and `NYLORUN_ENDPOINT_HTTP` (`allow` or `refuse`).
186
183
  */
187
184
  delivery?: {
@@ -221,10 +218,16 @@ export interface TenantSettings {
221
218
  /** `NYLORUN_TENANT_NAME`. Default `default`. */
222
219
  name: string;
223
220
  /**
224
- * `NYLORUN_DERIVED_PRINCIPALS`: comma-separated application principals whose keys the admin
225
- * key derives (`deriveTenantKey`). Default `project`.
221
+ * `NYLORUN_IDENTITY_FILE`: the identity file listing the trusted issuers (Host feature
222
+ * `trusted-issuers`), read once at boot (`host/main.ts`). Absent: no issuer is trusted.
223
+ */
224
+ identityFile?: string;
225
+ /**
226
+ * `NYLORUN_MANAGEMENT_KEY_FILE`: a file holding a management key (64 hex characters), which
227
+ * the Host registers as the principal `bootstrap` at every start, replacing its key when the
228
+ * file changed (protocol 8). For installations where no one can run `nylorun-operate`.
226
229
  */
227
- derivedPrincipals: readonly string[];
230
+ managementKeyFile?: string;
228
231
  }
229
232
  export declare class StackConfigError extends Error {
230
233
  constructor(message: string);