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