@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.
- package/CHANGELOG.md +207 -0
- package/README.md +105 -63
- package/dist/adapters/execution/restate.d.ts +1 -3
- package/dist/adapters/execution/restate.js +4 -31
- package/dist/adapters/replication/pgoutput.js +1 -1
- package/dist/adapters/sandbox/local.js +1 -1
- package/dist/adapters/sandbox/virtual.js +1 -1
- package/dist/api/ag-ui/endpoint.js +1 -2
- package/dist/api/ag-ui/routes.d.ts +1 -1
- package/dist/api/ag-ui/routes.js +11 -13
- package/dist/api/ag-ui/translate.d.ts +1 -3
- package/dist/api/ag-ui/translate.js +2 -17
- package/dist/api/components.d.ts +4783 -2588
- package/dist/api/components.js +19 -21
- package/dist/api/gate/routes.d.ts +7 -8
- package/dist/api/gate/routes.js +58 -52
- package/dist/api/http/app.d.ts +6 -0
- package/dist/api/http/app.js +20 -14
- package/dist/api/http/define.d.ts +22 -13
- package/dist/api/http/define.js +43 -39
- package/dist/api/http/routes/access.d.ts +3 -19
- package/dist/api/http/routes/access.js +25 -184
- package/dist/api/http/routes/artifacts.js +3 -7
- package/dist/api/http/routes/files.d.ts +8 -0
- package/dist/api/http/routes/files.js +77 -0
- package/dist/api/http/routes/keys.d.ts +10 -0
- package/dist/api/http/routes/keys.js +64 -0
- package/dist/api/http/routes/me.d.ts +13 -0
- package/dist/api/http/routes/me.js +48 -0
- package/dist/api/http/routes/reads.d.ts +66 -0
- package/dist/api/http/routes/reads.js +127 -0
- package/dist/api/http/routes/sandboxes.d.ts +2 -2
- package/dist/api/http/routes/sandboxes.js +8 -7
- package/dist/api/http/routes/sessions.js +31 -20
- package/dist/api/http/routes/tenant.js +15 -18
- package/dist/api/http/routes/vaults.d.ts +13 -3
- package/dist/api/http/routes/vaults.js +136 -64
- package/dist/api/openapi.d.ts +10 -11
- package/dist/api/openapi.js +292 -68
- package/dist/artifacts/links.js +6 -6
- package/dist/artifacts/service.js +1 -1
- package/dist/core/flow-host.d.ts +44 -23
- package/dist/core/flow-host.js +88 -106
- package/dist/core/provider.d.ts +4 -1
- package/dist/core/provider.js +6 -1
- package/dist/core/sandbox-routes.d.ts +0 -9
- package/dist/core/sandbox-routes.js +2 -21
- package/dist/execution/memory.d.ts +4 -5
- package/dist/execution/memory.js +11 -25
- package/dist/execution/types.d.ts +3 -33
- package/dist/execution/types.js +2 -12
- package/dist/gates/http-tool.d.ts +86 -0
- package/dist/gates/http-tool.js +197 -0
- package/dist/gates/mcp-handler.d.ts +3 -0
- package/dist/gates/mcp-handler.js +6 -4
- package/dist/gates/meter.d.ts +2 -2
- package/dist/gates/meter.js +6 -0
- package/dist/gates/tenant-vaults.d.ts +19 -14
- package/dist/gates/tenant-vaults.js +17 -12
- package/dist/gates/tool-calls.d.ts +7 -15
- package/dist/gates/tool-calls.js +4 -4
- package/dist/gates/tool-client.d.ts +1 -1
- package/dist/gates/tool-client.js +23 -47
- package/dist/gates/tool-contract.d.ts +27 -39
- package/dist/gates/tool-contract.js +30 -21
- package/dist/gates/tool-gate.d.ts +25 -24
- package/dist/gates/tool-gate.js +11 -7
- package/dist/harness/calls.d.ts +10 -4
- package/dist/harness/calls.js +15 -6
- package/dist/harness/executors.d.ts +1 -1
- package/dist/harness/executors.js +17 -12
- package/dist/harness/main.d.ts +2 -2
- package/dist/harness/main.js +5 -13
- package/dist/harness/service.d.ts +4 -5
- package/dist/harness/service.js +9 -2
- package/dist/harness-api/in-process.js +2 -2
- package/dist/harness-api/record.d.ts +6 -3
- package/dist/harness-api/record.js +65 -72
- package/dist/harness-api/server.d.ts +1 -9
- package/dist/harness-api/server.js +19 -17
- package/dist/harness-api/start.js +1 -5
- package/dist/harness-api/workspace.js +2 -3
- package/dist/harness-api/ws-server.js +1 -1
- package/dist/host/app.d.ts +2 -10
- package/dist/host/app.js +24 -44
- package/dist/host/config-for.d.ts +5 -5
- package/dist/host/config-for.js +2 -3
- package/dist/host/config.d.ts +0 -11
- package/dist/host/create-host.d.ts +3 -40
- package/dist/host/create-host.js +18 -91
- package/dist/host/environment.d.ts +0 -3
- package/dist/host/environment.js +0 -9
- package/dist/host/gates.d.ts +4 -1
- package/dist/host/gates.js +9 -3
- package/dist/host/http.d.ts +1 -2
- package/dist/host/http.js +1 -7
- package/dist/host/index.d.ts +2 -2
- package/dist/host/index.js +2 -2
- package/dist/host/main.js +55 -40
- package/dist/host/operate.d.ts +29 -0
- package/dist/host/operate.js +223 -0
- package/dist/host/stack-config.d.ts +23 -20
- package/dist/host/stack-config.js +28 -44
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/infra/workers.d.ts +1 -1
- package/dist/keys/client.js +9 -1
- package/dist/keys/keys.d.ts +12 -2
- package/dist/keys/keys.js +10 -2
- package/dist/management-openapi.json +6491 -0
- package/dist/mcp/connect.d.ts +8 -4
- package/dist/mcp/connect.js +8 -27
- package/dist/mcp/pool.d.ts +6 -9
- package/dist/mcp/pool.js +8 -18
- package/dist/mcp/snapshot.d.ts +8 -2
- package/dist/mcp/snapshot.js +29 -2
- package/dist/media.d.ts +1 -1
- package/dist/media.js +1 -1
- package/dist/model/pi-model.js +3 -0
- package/dist/openapi.json +6228 -15615
- package/dist/reads/access.d.ts +4 -0
- package/dist/reads/access.js +12 -0
- package/dist/reads/cursor.d.ts +11 -0
- package/dist/reads/cursor.js +80 -0
- package/dist/reads/types.d.ts +25 -0
- package/dist/reads/types.js +1 -0
- package/dist/sandbox/manager.d.ts +16 -0
- package/dist/sandbox/manager.js +40 -0
- package/dist/sandbox/session-sandbox.js +13 -3
- package/dist/sandbox/skills.d.ts +13 -0
- package/dist/sandbox/skills.js +46 -0
- package/dist/sandbox/types.d.ts +2 -2
- package/dist/store/ownership.d.ts +9 -0
- package/dist/store/ownership.js +13 -0
- package/dist/store/postgres/connect.d.ts +2 -0
- package/dist/store/postgres/connect.js +29 -0
- package/dist/store/postgres/drizzle/0008_installation_vaults.sql +2 -0
- package/dist/store/postgres/drizzle/0009_oauth_pending.sql +19 -0
- package/dist/store/postgres/drizzle/0010_oss_auth_removals.sql +3 -0
- package/dist/store/postgres/drizzle/0011_key_roles.sql +3 -0
- package/dist/store/postgres/drizzle/0012_session_reads.sql +8 -0
- package/dist/store/postgres/drizzle/0013_definition_files.sql +16 -0
- package/dist/store/postgres/drizzle/0014_action_endpoints_removed.sql +2 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +49 -0
- package/dist/store/postgres/reads.d.ts +3 -0
- package/dist/store/postgres/reads.js +272 -0
- package/dist/store/postgres/schema.d.ts +423 -594
- package/dist/store/postgres/schema.js +84 -101
- package/dist/store/postgres/store.js +96 -248
- package/dist/store/postgres/tenant.d.ts +34 -2
- package/dist/store/postgres/tenant.js +16 -32
- package/dist/store/types.d.ts +44 -119
- package/dist/store/types.js +0 -1
- package/dist/streams/control.d.ts +0 -7
- package/dist/streams/control.js +0 -13
- package/dist/streams/types.d.ts +2 -20
- package/dist/streams/types.js +1 -1
- package/dist/tenant/advance.d.ts +3 -3
- package/dist/tenant/advance.js +10 -7
- package/dist/tenant/auth.d.ts +24 -15
- package/dist/tenant/auth.js +44 -55
- package/dist/tenant/cause.d.ts +3 -3
- package/dist/tenant/cause.js +1 -1
- package/dist/tenant/commands.d.ts +2 -18
- package/dist/tenant/commands.js +38 -179
- package/dist/tenant/context.d.ts +33 -45
- package/dist/tenant/definition-files.d.ts +24 -0
- package/dist/tenant/definition-files.js +103 -0
- package/dist/tenant/effects.d.ts +20 -18
- package/dist/tenant/effects.js +59 -133
- package/dist/tenant/ephemeral.d.ts +21 -8
- package/dist/tenant/ephemeral.js +11 -12
- package/dist/tenant/host-token.js +5 -5
- package/dist/tenant/identity-file.d.ts +75 -0
- package/dist/tenant/identity-file.js +202 -0
- package/dist/tenant/issuers.d.ts +48 -0
- package/dist/tenant/issuers.js +311 -0
- package/dist/tenant/jwt.js +3 -3
- package/dist/tenant/module.d.ts +1 -4
- package/dist/tenant/module.js +3 -12
- package/dist/tenant/operator-keys.d.ts +44 -0
- package/dist/tenant/operator-keys.js +87 -0
- package/dist/tenant/outbound.d.ts +40 -1
- package/dist/tenant/outbound.js +167 -18
- package/dist/tenant/paths.d.ts +1 -1
- package/dist/tenant/paths.js +1 -3
- package/dist/tenant/principals.d.ts +13 -13
- package/dist/tenant/principals.js +35 -36
- package/dist/tenant/run-token.js +6 -6
- package/dist/tenant/runtime.d.ts +5 -3
- package/dist/tenant/runtime.js +29 -15
- package/dist/tenant/sandboxes.d.ts +9 -6
- package/dist/tenant/sandboxes.js +16 -5
- package/dist/tenant/scheduler.d.ts +1 -5
- package/dist/tenant/scheduler.js +2 -5
- package/dist/tenant/session-streams.d.ts +8 -11
- package/dist/tenant/session-streams.js +48 -32
- package/dist/tenant/session.d.ts +8 -41
- package/dist/tenant/session.js +0 -37
- package/dist/tenant/sessions.d.ts +2 -2
- package/dist/tenant/sessions.js +17 -9
- package/dist/tenant/signing-keys.d.ts +3 -2
- package/dist/tenant/signing-keys.js +5 -5
- package/dist/tenant/skill-tool.d.ts +20 -0
- package/dist/tenant/skill-tool.js +72 -0
- package/dist/tenant/slim.d.ts +1 -1
- package/dist/tenant/slim.js +1 -1
- package/dist/tenant/status.js +7 -12
- package/dist/tenant/store-pg.d.ts +15 -0
- package/dist/tenant/store-pg.js +7 -3
- package/dist/tenant/streams.d.ts +3 -11
- package/dist/tenant/streams.js +2 -29
- package/dist/tenant/sweep.js +0 -5
- package/dist/tenant/transcript.d.ts +2 -2
- package/dist/tenant/transcript.js +2 -2
- package/dist/tenant/types.d.ts +33 -19
- package/dist/tenant/worker.d.ts +1 -3
- package/dist/tenant/worker.js +3 -8
- package/dist/vault/crypto.d.ts +7 -0
- package/dist/vault/crypto.js +16 -0
- package/dist/vault/oauth.d.ts +55 -0
- package/dist/vault/oauth.js +186 -0
- package/dist/vault/service.d.ts +54 -7
- package/dist/vault/service.js +261 -17
- package/dist/vault/sources.d.ts +60 -0
- package/dist/vault/sources.js +196 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -4
- package/dist/admin-openapi.json +0 -604
- package/dist/api/http/routes/actions.d.ts +0 -8
- package/dist/api/http/routes/actions.js +0 -76
- package/dist/api/http/routes/endpoints.d.ts +0 -7
- package/dist/api/http/routes/endpoints.js +0 -100
- package/dist/host/admin-api.d.ts +0 -23
- package/dist/host/admin-api.js +0 -74
- package/dist/host/cors.d.ts +0 -27
- package/dist/host/cors.js +0 -64
- package/dist/mcp/stdio.d.ts +0 -17
- package/dist/mcp/stdio.js +0 -82
- package/dist/tenant/access-policy.d.ts +0 -30
- package/dist/tenant/access-policy.js +0 -52
- package/dist/tenant/actions.d.ts +0 -14
- package/dist/tenant/actions.js +0 -29
- package/dist/tenant/browser.d.ts +0 -15
- package/dist/tenant/browser.js +0 -39
- package/dist/tenant/delivery-token.d.ts +0 -41
- package/dist/tenant/delivery-token.js +0 -104
- package/dist/tenant/delivery.d.ts +0 -62
- package/dist/tenant/delivery.js +0 -481
- package/dist/tenant/endpoints.d.ts +0 -67
- package/dist/tenant/endpoints.js +0 -90
- package/dist/tenant/subject-limits.d.ts +0 -16
- package/dist/tenant/subject-limits.js +0 -45
- package/dist/tenant/tokens.d.ts +0 -14
- 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
|
-
* `
|
|
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
|
|
267
|
-
//
|
|
268
|
-
// Their credentials (F5): each advance's run token for its session's model and
|
|
269
|
-
// core's credential (NYLORUN_GATES_TOKEN) for
|
|
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"
|
|
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
|
-
|
|
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
|
|
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
|
|
374
|
-
//
|
|
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
|
|
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
|
|
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`)
|
|
168
|
-
*
|
|
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
|
-
*
|
|
174
|
-
*
|
|
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
|
-
* `
|
|
225
|
-
*
|
|
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
|
-
|
|
230
|
+
managementKeyFile?: string;
|
|
228
231
|
}
|
|
229
232
|
export declare class StackConfigError extends Error {
|
|
230
233
|
constructor(message: string);
|