@nylorun/runtime 0.22.0-beta → 0.24.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 +161 -0
- package/README.md +95 -30
- package/dist/adapters/execution/restate.d.ts +15 -3
- package/dist/adapters/execution/restate.js +78 -84
- package/dist/adapters/replication/pgoutput.d.ts +2 -0
- package/dist/adapters/replication/pgoutput.js +6 -3
- package/dist/adapters/sandbox/local.d.ts +9 -1
- package/dist/adapters/sandbox/local.js +16 -4
- package/dist/adapters/sandbox/virtual.js +4 -1
- package/dist/adapters/streams/s2.js +1 -13
- package/dist/api/a2a/endpoint.js +5 -1
- package/dist/api/a2a/routes.d.ts +2 -0
- package/dist/api/a2a/routes.js +2 -2
- package/dist/api/a2a/tasks.js +2 -1
- package/dist/api/ag-ui/endpoint.js +4 -2
- package/dist/api/ag-ui/history.js +4 -1
- package/dist/api/ag-ui/routes.d.ts +4 -1
- package/dist/api/ag-ui/routes.js +11 -10
- package/dist/api/ag-ui/translate.js +3 -2
- package/dist/api/components.d.ts +4953 -1204
- package/dist/api/components.js +25 -16
- package/dist/api/gate/keys.js +7 -1
- package/dist/api/gate/routes.d.ts +6 -0
- package/dist/api/gate/routes.js +39 -3
- package/dist/api/http/app.js +4 -4
- package/dist/api/http/define.d.ts +6 -0
- package/dist/api/http/define.js +13 -1
- package/dist/api/http/respond.js +1 -1
- package/dist/api/http/routes/artifacts.js +35 -8
- package/dist/api/http/routes/coverage.d.ts +9 -0
- package/dist/api/http/routes/coverage.js +34 -0
- package/dist/api/http/routes/environments.d.ts +11 -0
- package/dist/api/http/routes/environments.js +283 -0
- package/dist/api/http/routes/mcp.d.ts +1 -1
- package/dist/api/http/routes/mcp.js +2 -2
- package/dist/api/http/routes/me.js +1 -1
- package/dist/api/http/routes/reads.d.ts +45 -9
- package/dist/api/http/routes/reads.js +54 -5
- package/dist/api/http/routes/sessions.js +11 -6
- package/dist/api/http/routes/tenant.js +32 -10
- package/dist/api/openapi.js +31 -26
- package/dist/artifacts/disk.d.ts +59 -0
- package/dist/artifacts/disk.js +563 -0
- package/dist/artifacts/export.d.ts +1 -1
- package/dist/artifacts/export.js +2 -3
- package/dist/artifacts/folders.d.ts +6 -3
- package/dist/artifacts/folders.js +17 -12
- package/dist/artifacts/service.d.ts +2 -1
- package/dist/artifacts/service.js +6 -1
- package/dist/artifacts/workspace.d.ts +1 -1
- package/dist/artifacts/workspace.js +1 -1
- package/dist/core/flow-host.d.ts +3 -22
- package/dist/core/flow-host.js +11 -37
- package/dist/core/provider.d.ts +3 -2
- package/dist/core/provider.js +5 -37
- package/dist/core/sandbox-routes.d.ts +2 -21
- package/dist/core/sandbox-routes.js +10 -43
- package/dist/execution/types.d.ts +11 -9
- package/dist/execution/types.js +5 -7
- package/dist/gates/contract.d.ts +3 -24
- package/dist/gates/contract.js +1 -36
- package/dist/gates/egress-credentials.d.ts +9 -0
- package/dist/gates/egress-credentials.js +11 -4
- package/dist/gates/egress.d.ts +28 -1
- package/dist/gates/egress.js +115 -10
- package/dist/gates/fixture-model.d.ts +28 -0
- package/dist/gates/fixture-model.js +92 -0
- package/dist/gates/handler.js +4 -4
- package/dist/gates/http-tool.d.ts +6 -2
- package/dist/gates/http-tool.js +3 -5
- package/dist/gates/in-process.d.ts +5 -0
- package/dist/gates/in-process.js +12 -1
- package/dist/gates/journal.d.ts +30 -0
- package/dist/gates/journal.js +52 -0
- package/dist/gates/mcp-handler.d.ts +4 -0
- package/dist/gates/mcp-handler.js +5 -1
- package/dist/gates/meter.d.ts +10 -2
- package/dist/gates/meter.js +20 -5
- package/dist/gates/policy.d.ts +49 -0
- package/dist/gates/policy.js +134 -0
- package/dist/gates/tenant-vaults.d.ts +7 -7
- package/dist/gates/tenant-vaults.js +5 -5
- package/dist/gates/tool-calls.d.ts +10 -5
- package/dist/gates/tool-calls.js +28 -10
- package/dist/gates/tool-client.d.ts +8 -0
- package/dist/gates/tool-client.js +27 -11
- package/dist/gates/tool-contract.d.ts +10 -68
- package/dist/gates/tool-contract.js +1 -85
- package/dist/gates/tool-gate.d.ts +1 -1
- package/dist/harness/calls.d.ts +12 -5
- package/dist/harness/calls.js +21 -12
- package/dist/harness/executors.js +12 -9
- package/dist/harness/main.js +18 -5
- package/dist/harness/pod.d.ts +34 -0
- package/dist/harness/pod.js +41 -0
- package/dist/harness/service.d.ts +0 -1
- package/dist/harness/service.js +13 -5
- package/dist/harness/setup.d.ts +27 -0
- package/dist/harness/setup.js +150 -0
- package/dist/harness/ws-client.js +3 -2
- package/dist/harness/ws-port.d.ts +1 -5
- package/dist/harness/ws-port.js +1 -5
- package/dist/harness-api/in-process.js +3 -3
- package/dist/harness-api/record.d.ts +15 -1
- package/dist/harness-api/record.js +174 -44
- package/dist/harness-api/redact.d.ts +3 -0
- package/dist/harness-api/redact.js +45 -0
- package/dist/harness-api/server.d.ts +8 -0
- package/dist/harness-api/server.js +93 -13
- package/dist/harness-api/start.d.ts +10 -3
- package/dist/harness-api/start.js +40 -10
- package/dist/harness-api/tool-journal.d.ts +96 -0
- package/dist/harness-api/tool-journal.js +102 -0
- package/dist/harness-api/tool-status.d.ts +13 -0
- package/dist/harness-api/tool-status.js +26 -0
- package/dist/harness-api/workspace.d.ts +6 -3
- package/dist/harness-api/workspace.js +27 -11
- package/dist/harness-api/ws-server.d.ts +11 -3
- package/dist/harness-api/ws-server.js +94 -15
- package/dist/host/execution.d.ts +8 -6
- package/dist/host/gates.d.ts +2 -1
- package/dist/host/gates.js +10 -2
- package/dist/host/main.js +64 -21
- package/dist/host/operate.js +67 -5
- package/dist/host/stack-config.d.ts +70 -0
- package/dist/host/stack-config.js +67 -1
- package/dist/infra/execution.d.ts +18 -1
- package/dist/infra/execution.js +28 -4
- package/dist/infra/index.d.ts +1 -1
- package/dist/keys/client.js +4 -1
- package/dist/keys/keys.d.ts +5 -5
- package/dist/keys/keys.js +5 -7
- package/dist/management-openapi.json +999 -2211
- package/dist/mcp/connect.d.ts +25 -20
- package/dist/mcp/connect.js +29 -30
- package/dist/mcp/pool.d.ts +2 -2
- package/dist/mcp/pool.js +1 -1
- package/dist/mcp/preview.d.ts +3 -3
- package/dist/mcp/preview.js +10 -20
- package/dist/mcp/snapshot.d.ts +5 -11
- package/dist/mcp/snapshot.js +6 -7
- package/dist/model/pi-model.js +8 -0
- package/dist/openapi.json +6955 -2526
- package/dist/reads/types.d.ts +16 -3
- package/dist/record/append.d.ts +3 -1
- package/dist/record/append.js +1 -0
- package/dist/record/envelope.d.ts +5 -1
- package/dist/record/envelope.js +7 -3
- package/dist/record/environment.d.ts +25 -0
- package/dist/record/environment.js +44 -0
- package/dist/record/index.d.ts +1 -1
- package/dist/record/index.js +1 -1
- package/dist/retry.d.ts +21 -0
- package/dist/retry.js +34 -0
- package/dist/sandbox/disk-fs.d.ts +95 -0
- package/dist/sandbox/disk-fs.js +206 -0
- package/dist/sandbox/join.d.ts +17 -18
- package/dist/sandbox/join.js +46 -20
- package/dist/sandbox/manager.d.ts +16 -8
- package/dist/sandbox/manager.js +33 -17
- package/dist/sandbox/placement.d.ts +13 -12
- package/dist/sandbox/placement.js +29 -12
- package/dist/sandbox/pods/client.d.ts +6 -1
- package/dist/sandbox/pods/lifecycle.d.ts +28 -27
- package/dist/sandbox/pods/lifecycle.js +14 -26
- package/dist/sandbox/pods/reconcile.js +21 -15
- package/dist/sandbox/pods/spec.d.ts +15 -13
- package/dist/sandbox/pods/spec.js +16 -20
- package/dist/sandbox/records.d.ts +2 -2
- package/dist/sandbox/records.js +3 -3
- package/dist/sandbox/resolve.d.ts +35 -27
- package/dist/sandbox/resolve.js +66 -41
- package/dist/sandbox/session-sandbox.js +1 -1
- package/dist/sandbox/setup.d.ts +61 -0
- package/dist/sandbox/setup.js +123 -0
- package/dist/sandbox/share.d.ts +11 -21
- package/dist/sandbox/share.js +12 -41
- package/dist/sandbox/tenant-config.d.ts +12 -14
- package/dist/sandbox/tenant-config.js +12 -17
- package/dist/sandbox/types.d.ts +6 -0
- package/dist/store/postgres/control.js +8 -1
- package/dist/store/postgres/drizzle/0018_wake_outbox_retries.sql +3 -0
- package/dist/store/postgres/drizzle/0019_sandbox_signal_outbox.sql +18 -0
- package/dist/store/postgres/drizzle/0020_environments.sql +63 -0
- package/dist/store/postgres/drizzle/0021_disk_versions.sql +7 -0
- package/dist/store/postgres/drizzle/0022_model_usage_timing.sql +5 -0
- package/dist/store/postgres/drizzle/0023_artifact_pages.sql +1 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +42 -0
- package/dist/store/postgres/reads.js +117 -17
- package/dist/store/postgres/record-writer.d.ts +4 -4
- package/dist/store/postgres/record-writer.js +9 -9
- package/dist/store/postgres/record.d.ts +7 -0
- package/dist/store/postgres/record.js +23 -2
- package/dist/store/postgres/schema.d.ts +1213 -779
- package/dist/store/postgres/schema.js +136 -76
- package/dist/store/postgres/store.js +283 -117
- package/dist/store/types.d.ts +206 -88
- package/dist/streams/relay/core.js +1 -13
- package/dist/tenant/advance.d.ts +7 -6
- package/dist/tenant/advance.js +121 -49
- package/dist/tenant/artifact-tool.js +3 -4
- package/dist/tenant/commands.d.ts +15 -2
- package/dist/tenant/commands.js +94 -14
- package/dist/tenant/context.d.ts +67 -33
- package/dist/tenant/context.js +4 -15
- package/dist/tenant/control.d.ts +8 -1
- package/dist/tenant/control.js +2 -0
- package/dist/tenant/effects.d.ts +1 -1
- package/dist/tenant/effects.js +37 -20
- package/dist/tenant/environments.d.ts +167 -0
- package/dist/tenant/environments.js +823 -0
- package/dist/tenant/harness-tokens.d.ts +21 -0
- package/dist/tenant/harness-tokens.js +61 -0
- package/dist/tenant/host-token.d.ts +1 -1
- package/dist/tenant/host-token.js +2 -4
- package/dist/tenant/identity-file.d.ts +4 -4
- package/dist/tenant/identity-file.js +17 -8
- package/dist/tenant/issuers.js +3 -3
- package/dist/tenant/principals.js +1 -1
- package/dist/tenant/reset.js +3 -3
- package/dist/tenant/runtime.d.ts +6 -1
- package/dist/tenant/runtime.js +30 -4
- package/dist/tenant/scheduler.d.ts +7 -0
- package/dist/tenant/scheduler.js +38 -7
- package/dist/tenant/session-streams.d.ts +0 -1
- package/dist/tenant/session-streams.js +1 -13
- package/dist/tenant/session.d.ts +3 -3
- package/dist/tenant/session.js +0 -14
- package/dist/tenant/sessions.d.ts +13 -13
- package/dist/tenant/sessions.js +61 -138
- package/dist/tenant/signing-keys.d.ts +5 -1
- package/dist/tenant/status.js +3 -3
- package/dist/tenant/streams.js +14 -29
- package/dist/tenant/sweep.d.ts +39 -12
- package/dist/tenant/sweep.js +168 -63
- package/dist/tenant/transcript.d.ts +210 -20
- package/dist/tenant/transcript.js +62 -11
- package/dist/tenant/turn-timing.d.ts +43 -0
- package/dist/tenant/turn-timing.js +39 -0
- package/dist/tenant/types.d.ts +2 -0
- package/dist/traces/ids.d.ts +42 -0
- package/dist/traces/ids.js +85 -0
- package/dist/traces/key.d.ts +6 -0
- package/dist/traces/key.js +37 -0
- package/dist/traces/map.d.ts +113 -0
- package/dist/traces/map.js +317 -0
- package/dist/traces/options.d.ts +7 -0
- package/dist/traces/options.js +1 -0
- package/dist/traces/otlp.d.ts +42 -0
- package/dist/traces/otlp.js +84 -0
- package/dist/traces/projector.d.ts +58 -0
- package/dist/traces/projector.js +200 -0
- package/dist/traces/read.d.ts +23 -0
- package/dist/traces/read.js +16 -0
- package/dist/vault/coverage.d.ts +26 -0
- package/dist/vault/coverage.js +64 -0
- package/dist/vault/error.d.ts +11 -1
- package/dist/vault/error.js +5 -1
- package/dist/vault/service.d.ts +47 -48
- package/dist/vault/service.js +321 -382
- package/dist/vault/sources.d.ts +5 -16
- package/dist/vault/sources.js +6 -8
- package/dist/version.d.ts +1 -1
- package/dist/version.js +2 -2
- package/package.json +14 -13
- package/dist/api/http/routes/sandboxes.d.ts +0 -9
- package/dist/api/http/routes/sandboxes.js +0 -153
- package/dist/api/http/routes/vaults.d.ts +0 -10
- package/dist/api/http/routes/vaults.js +0 -139
- package/dist/record/sandbox.d.ts +0 -25
- package/dist/record/sandbox.js +0 -43
- package/dist/tenant/sandboxes.d.ts +0 -122
- package/dist/tenant/sandboxes.js +0 -562
|
@@ -14,22 +14,31 @@
|
|
|
14
14
|
* /nylorun/harness/v1/host/join` (`{ sandboxId, podUid, joinToken }`), renews it with `POST
|
|
15
15
|
* .../host/renew` (`Authorization: Bearer <host token>`), and upgrades with the host token as
|
|
16
16
|
* its bearer: that connection hosts its sandbox alone (`HarnessPeer.host`). Both answer
|
|
17
|
-
* `{ hostToken, egressToken, sandboxId, epoch, expiresAt, caCertificate }` (the egress CA, R2c)
|
|
18
|
-
*
|
|
17
|
+
* `{ hostToken, egressToken, sandboxId, epoch, expiresAt, caCertificate }` (the egress CA, R2c),
|
|
18
|
+
* and a join adds the disk's `setup` (R2d step 2). With its host token the engine also reads and
|
|
19
|
+
* reports its setup (`GET|POST .../host/setup`) and reads its input files (`GET
|
|
20
|
+
* .../host/files/{index}[/{path}]`). Every refusal is the same 401. The harness token is never
|
|
21
|
+
* accepted as a host token, nor a host token anywhere but here.
|
|
19
22
|
*/
|
|
20
23
|
import { createHash, timingSafeEqual } from "node:crypto";
|
|
21
24
|
import { createServer } from "node:http";
|
|
22
|
-
import { createChannel } from "@nylorun/core/harness-api";
|
|
25
|
+
import { DEFAULT_HARNESS, createChannel } from "@nylorun/core/harness-api";
|
|
23
26
|
import { WebSocketServer } from "ws";
|
|
24
|
-
import { HARNESS_API_HEADER, HARNESS_API_PATH, HARNESS_MAX_PAYLOAD,
|
|
27
|
+
import { HARNESS_API_HEADER, HARNESS_API_PATH, HARNESS_API_TRANSPORT, HARNESS_MAX_PAYLOAD, HOST_JOIN_PATH, HOST_RENEW_PATH, } from "@nylorun/core/harness-api";
|
|
28
|
+
import { wsPort } from "../harness/ws-port.js";
|
|
25
29
|
import { tokenType } from "../tenant/jwt.js";
|
|
26
30
|
import { HOST_TOKEN_TYP } from "../tenant/host-token.js";
|
|
27
31
|
import { HostAuthError } from "../sandbox/join.js";
|
|
28
|
-
|
|
29
|
-
export
|
|
30
|
-
|
|
32
|
+
import { parseSetupReport } from "../sandbox/setup.js";
|
|
33
|
+
export { HOST_JOIN_PATH, HOST_RENEW_PATH };
|
|
34
|
+
/** Where a pod's engine reads its setup and reports it (R2d step 2, `sandbox/setup.ts`). */
|
|
35
|
+
export const HOST_SETUP_PATH = `${HARNESS_API_PATH}/host/setup`;
|
|
36
|
+
/** Where it reads its input files: `/{index}`, and a folder's file `/{index}/{path}`. */
|
|
37
|
+
export const HOST_FILES_PATH = `${HARNESS_API_PATH}/host/files`;
|
|
31
38
|
/** A join's body is small. */
|
|
32
39
|
const MAX_JOIN_BODY = 8 * 1024;
|
|
40
|
+
/** A setup report carries the end of a command's output. */
|
|
41
|
+
const MAX_REPORT_BODY = 64 * 1024;
|
|
33
42
|
function refuse(socket, status, text, headers = {}) {
|
|
34
43
|
const lines = [
|
|
35
44
|
`HTTP/1.1 ${status} ${text}`,
|
|
@@ -76,12 +85,12 @@ export async function startHarnessListener(options) {
|
|
|
76
85
|
});
|
|
77
86
|
response.end(text);
|
|
78
87
|
};
|
|
79
|
-
const readBody = (request) => new Promise((resolve, reject) => {
|
|
88
|
+
const readBody = (request, max = MAX_JOIN_BODY) => new Promise((resolve, reject) => {
|
|
80
89
|
let size = 0;
|
|
81
90
|
const chunks = [];
|
|
82
91
|
request.on("data", (chunk) => {
|
|
83
92
|
size += chunk.length;
|
|
84
|
-
if (size >
|
|
93
|
+
if (size > max) {
|
|
85
94
|
reject(new HostAuthError(400, "The body is too large"));
|
|
86
95
|
request.destroy();
|
|
87
96
|
}
|
|
@@ -116,7 +125,61 @@ export async function startHarnessListener(options) {
|
|
|
116
125
|
return reply(response, 503, { error: { code: "unavailable", message: "Try again" } });
|
|
117
126
|
}
|
|
118
127
|
};
|
|
119
|
-
|
|
128
|
+
/** A pod's setup and input files (R2d step 2), with its host token. */
|
|
129
|
+
const serveSetup = async (request, response, path) => {
|
|
130
|
+
if (!allowed.has(String(request.headers.host ?? "").toLowerCase()))
|
|
131
|
+
return reply(response, 421);
|
|
132
|
+
const authority = await options.hosts?.().catch(() => undefined);
|
|
133
|
+
if (!authority || closing)
|
|
134
|
+
return reply(response, 503, { error: { code: "unavailable", message: "Try again" } });
|
|
135
|
+
const token = bearerOf(request.headers.authorization) ?? "";
|
|
136
|
+
try {
|
|
137
|
+
if (path === HOST_SETUP_PATH) {
|
|
138
|
+
if (request.method === "GET")
|
|
139
|
+
return reply(response, 200, await authority.setup(token));
|
|
140
|
+
if (request.method !== "POST")
|
|
141
|
+
return reply(response, 405, undefined, { allow: "GET, POST" });
|
|
142
|
+
const report = parseSetupReport(JSON.parse(await readBody(request, MAX_REPORT_BODY)));
|
|
143
|
+
if (!report)
|
|
144
|
+
throw new HostAuthError(400, "The report is malformed");
|
|
145
|
+
await authority.reportSetup(token, report);
|
|
146
|
+
return reply(response, 200, {});
|
|
147
|
+
}
|
|
148
|
+
if (request.method !== "GET")
|
|
149
|
+
return reply(response, 405, undefined, { allow: "GET" });
|
|
150
|
+
const [index, ...rest] = path.slice(HOST_FILES_PATH.length + 1).split("/");
|
|
151
|
+
if (!/^\d{1,2}$/.test(index ?? ""))
|
|
152
|
+
return reply(response, 404);
|
|
153
|
+
const file = await authority.setupFile(token, Number(index), rest.length === 0 ? undefined : rest.map((part) => decodeURIComponent(part)).join("/"));
|
|
154
|
+
if (!file)
|
|
155
|
+
return reply(response, 404);
|
|
156
|
+
if (file.kind === "folder")
|
|
157
|
+
return reply(response, 200, { entries: file.entries }, { "x-nylorun-artifact-kind": "folder" });
|
|
158
|
+
response.writeHead(200, {
|
|
159
|
+
connection: "close",
|
|
160
|
+
"cache-control": "no-store",
|
|
161
|
+
"content-type": file.contentType,
|
|
162
|
+
"x-nylorun-artifact-kind": "file",
|
|
163
|
+
});
|
|
164
|
+
for await (const chunk of file.body)
|
|
165
|
+
response.write(chunk);
|
|
166
|
+
response.end();
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (error instanceof HostAuthError) {
|
|
170
|
+
if (error.reason)
|
|
171
|
+
logger.warn("sandbox_host_refused", { path, reason: error.reason });
|
|
172
|
+
return reply(response, error.status, { error: { code: error.status === 401 ? "unauthorized" : "invalid", message: error.message } }, error.status === 401 ? { "www-authenticate": "Bearer" } : {});
|
|
173
|
+
}
|
|
174
|
+
if (error instanceof SyntaxError)
|
|
175
|
+
return reply(response, 400, { error: { code: "invalid", message: "Not JSON" } });
|
|
176
|
+
logger.warn("sandbox_host_failed", { path, message: error instanceof Error ? error.message : String(error) });
|
|
177
|
+
if (response.headersSent)
|
|
178
|
+
return void response.destroy();
|
|
179
|
+
return reply(response, 503, { error: { code: "unavailable", message: "Try again" } });
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
// Plain requests get nothing but the pods' join, renewal and setup: the listener speaks the Harness API.
|
|
120
183
|
const server = createServer((request, response) => {
|
|
121
184
|
const path = new URL(request.url ?? "/", "http://harness.local").pathname;
|
|
122
185
|
if (closing)
|
|
@@ -125,6 +188,10 @@ export async function startHarnessListener(options) {
|
|
|
125
188
|
void serveHost(request, response, path);
|
|
126
189
|
return;
|
|
127
190
|
}
|
|
191
|
+
if ((path === HOST_SETUP_PATH || path.startsWith(`${HOST_FILES_PATH}/`)) && options.hosts) {
|
|
192
|
+
void serveSetup(request, response, path);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
128
195
|
response.writeHead(426, { connection: "close", upgrade: "websocket" });
|
|
129
196
|
response.end();
|
|
130
197
|
});
|
|
@@ -140,10 +207,12 @@ export async function startHarnessListener(options) {
|
|
|
140
207
|
const bearer = bearerOf(request.headers.authorization);
|
|
141
208
|
// A pod's host token (F7.2): checked against its sandbox before the upgrade.
|
|
142
209
|
const hostToken = bearer !== undefined && options.hosts && tokenType(bearer) === HOST_TOKEN_TYP;
|
|
143
|
-
|
|
210
|
+
// The harness token is our harness's; any other is a custom harness's own, or refused (R4.1).
|
|
211
|
+
const ours = !hostToken && bearerMatches(request.headers.authorization, options.token);
|
|
212
|
+
if (!hostToken && !ours && (bearer === undefined || !options.harnessOfToken))
|
|
144
213
|
return refuse(socket, 401, "Unauthorized", { "WWW-Authenticate": "Bearer" });
|
|
145
|
-
if (request.headers[HARNESS_API_HEADER] !==
|
|
146
|
-
return refuse(socket, 426, "Upgrade Required", { [HARNESS_API_HEADER]:
|
|
214
|
+
if (request.headers[HARNESS_API_HEADER] !== HARNESS_API_TRANSPORT)
|
|
215
|
+
return refuse(socket, 426, "Upgrade Required", { [HARNESS_API_HEADER]: HARNESS_API_TRANSPORT });
|
|
147
216
|
const host = hostToken
|
|
148
217
|
? options.hosts().then(async (authority) => {
|
|
149
218
|
if (!authority)
|
|
@@ -152,15 +221,25 @@ export async function startHarnessListener(options) {
|
|
|
152
221
|
return { sandboxId: claims.sandboxId, epoch: claims.epoch };
|
|
153
222
|
})
|
|
154
223
|
: Promise.resolve(undefined);
|
|
155
|
-
|
|
224
|
+
const harness = hostToken
|
|
225
|
+
? Promise.resolve(undefined)
|
|
226
|
+
: ours
|
|
227
|
+
? Promise.resolve(DEFAULT_HARNESS)
|
|
228
|
+
: options.harnessOfToken(bearer).then((id) => {
|
|
229
|
+
if (id === undefined)
|
|
230
|
+
throw new HostAuthError(401, "Unauthorized");
|
|
231
|
+
return id;
|
|
232
|
+
});
|
|
233
|
+
void Promise.all([options.attach(), host, harness]).then(([attach, hosted, harnessId]) => {
|
|
156
234
|
if (!attach || closing || (hostToken && !hosted))
|
|
157
235
|
return refuse(socket, 503, "Service Unavailable");
|
|
158
236
|
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
159
237
|
const peer = {
|
|
160
238
|
name: hosted
|
|
161
239
|
? `sandbox ${hosted.sandboxId}@${request.socket.remoteAddress ?? "unknown"}`
|
|
162
|
-
: `harness@${request.socket.remoteAddress ?? "unknown"}`,
|
|
240
|
+
: `harness ${harnessId}@${request.socket.remoteAddress ?? "unknown"}`,
|
|
163
241
|
...(hosted ? { host: hosted } : {}),
|
|
242
|
+
...(harnessId === undefined ? {} : { harness: harnessId }),
|
|
164
243
|
};
|
|
165
244
|
const channel = createChannel(wsPort(ws, { ...options.liveness, ping: true }), { validate: true });
|
|
166
245
|
const detach = attach(channel, peer);
|
package/dist/host/execution.d.ts
CHANGED
|
@@ -19,12 +19,14 @@
|
|
|
19
19
|
* services, so the open would fail. A core-only process relies on a Worker having registered
|
|
20
20
|
* them first.
|
|
21
21
|
*
|
|
22
|
-
* **One Worker URL.** `start` registers the Worker endpoint
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
22
|
+
* **One Worker URL per version.** `start` registers the Worker endpoint with Restate once, as
|
|
23
|
+
* `<NYLORUN_WORKER_URL>/nylorun/<version>` (`workerDeployment` in `infra/execution.ts`).
|
|
24
|
+
* Restate sends new invocations to the latest registered deployment, so several Workers that
|
|
25
|
+
* each register their own URL do not share load: the last one to start takes every new
|
|
26
|
+
* invocation. The local stack runs a single `runtime` container with `--service core,loop`. A
|
|
27
|
+
* deployment with several Worker processes (Helm, Cloud) must advertise one load-balanced
|
|
28
|
+
* `NYLORUN_WORKER_URL` shared by all of them; a rolling upgrade registers the new version's
|
|
29
|
+
* path next to the old one, which keeps the invocations it started.
|
|
28
30
|
*/
|
|
29
31
|
import type { DurableExecution } from "../execution/types.js";
|
|
30
32
|
import type { Logger } from "../tenant/types.js";
|
package/dist/host/gates.d.ts
CHANGED
|
@@ -81,6 +81,7 @@ export interface StartEgressOptions {
|
|
|
81
81
|
}
|
|
82
82
|
/**
|
|
83
83
|
* egress-gate (`--service egress`, F7.2): pods' CONNECT proxy, verifying egress tokens against the
|
|
84
|
-
* Tenant's public keys and reading its sandboxes from the same database as the gates
|
|
84
|
+
* Tenant's public keys and reading its sandboxes from the same database as the gates, where it
|
|
85
|
+
* also records each connection (R7.3).
|
|
85
86
|
*/
|
|
86
87
|
export declare function startEgress(options: StartEgressOptions): Promise<EgressGate>;
|
package/dist/host/gates.js
CHANGED
|
@@ -23,9 +23,10 @@ import { tenantPaths } from "../tenant/paths.js";
|
|
|
23
23
|
import { staleRun, verifyRunToken } from "../tenant/run-token.js";
|
|
24
24
|
import { createFsBlobStore } from "../blob/index.js";
|
|
25
25
|
import { createTenantVaults } from "../gates/tenant-vaults.js";
|
|
26
|
+
import { journalCheck } from "../gates/journal.js";
|
|
26
27
|
import { probeDatabase } from "../infra/database.js";
|
|
27
28
|
import { bindListener, headerValue, isAllowedRequestHost, sendRejected } from "./http.js";
|
|
28
|
-
import { startEgressGate, storeEgressSandboxes } from "../gates/egress.js";
|
|
29
|
+
import { startEgressGate, storeEgressRecord, storeEgressSandboxes, } from "../gates/egress.js";
|
|
29
30
|
import { verifyEgressToken } from "../sandbox/egress-token.js";
|
|
30
31
|
import { leafKeyPair } from "../keys/x509.js";
|
|
31
32
|
/** Above the gate's 600 s provider request timeout and the loop's 630 s client timeout. */
|
|
@@ -101,6 +102,7 @@ export async function startGates(options) {
|
|
|
101
102
|
mcp,
|
|
102
103
|
http: gateHttpTools(vaults, options.delivery ?? {}, logger),
|
|
103
104
|
toolCalls,
|
|
105
|
+
journal: journalCheck(vaults),
|
|
104
106
|
logger,
|
|
105
107
|
...(options.keys ? { keys: async () => (await vaults.open()).keys() } : {}),
|
|
106
108
|
});
|
|
@@ -153,7 +155,8 @@ export async function startGates(options) {
|
|
|
153
155
|
}
|
|
154
156
|
/**
|
|
155
157
|
* egress-gate (`--service egress`, F7.2): pods' CONNECT proxy, verifying egress tokens against the
|
|
156
|
-
* Tenant's public keys and reading its sandboxes from the same database as the gates
|
|
158
|
+
* Tenant's public keys and reading its sandboxes from the same database as the gates, where it
|
|
159
|
+
* also records each connection (R7.3).
|
|
157
160
|
*/
|
|
158
161
|
export async function startEgress(options) {
|
|
159
162
|
const { database } = options;
|
|
@@ -173,6 +176,11 @@ export async function startEgress(options) {
|
|
|
173
176
|
return verifyEgressToken(vault.store, vault.tenantId, raw, publicKeys);
|
|
174
177
|
},
|
|
175
178
|
sandboxes: { live: async (sandboxId) => storeEgressSandboxes((await tenant.open()).store).live(sandboxId) },
|
|
179
|
+
// Each connection on the Tenant's record (R7.3): the gates' one write into it.
|
|
180
|
+
record: {
|
|
181
|
+
holder: async (environmentId) => storeEgressRecord((await tenant.open()).store).holder(environmentId),
|
|
182
|
+
write: async (holder, connection) => storeEgressRecord((await tenant.open()).store).write(holder, connection),
|
|
183
|
+
},
|
|
176
184
|
credentials: {
|
|
177
185
|
bound: async (sandboxId, host) => (await tenant.open()).environmentSecretBound(sandboxId, host),
|
|
178
186
|
release: async (sandboxId, host) => (await tenant.open()).releaseEnvironmentSecret(sandboxId, host),
|
package/dist/host/main.js
CHANGED
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
* first start (`NYLORUN_TENANT_ID`, `NYLORUN_TENANT_NAME`; tenancy.md §4), trusting the issuers of the identity file
|
|
13
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
|
-
* logical replication (one process at a time holds the slot);
|
|
15
|
+
* logical replication (one process at a time holds the slot); with `OTEL_EXPORTER_OTLP_ENDPOINT`
|
|
16
|
+
* it also runs the trace projector, which posts the record's spans over OTLP from a slot of its
|
|
17
|
+
* own (R7.2); `/ready` reports
|
|
16
18
|
* the Tenant and the infrastructure checks. With an Object store
|
|
17
19
|
* (`NYLORUN_OBJECT_STORE_*`) the Tenant's blobs go to it through the `s3` BlobStore, and a
|
|
18
20
|
* process running core creates its bucket; without one they stay on disk (`fs`). A process
|
|
@@ -36,8 +38,12 @@ import { createPostgresTenantOpener } from "../tenant/store-pg.js";
|
|
|
36
38
|
import { openTenantRuntime } from "../tenant/runtime.js";
|
|
37
39
|
import { createPgoutputSource } from "../adapters/replication/pgoutput.js";
|
|
38
40
|
import { assertLogicalReplication } from "../store/postgres/connect.js";
|
|
39
|
-
import { createPostgresRecordReader } from "../store/postgres/record.js";
|
|
40
41
|
import { createStreamRelay } from "../streams/relay/core.js";
|
|
42
|
+
import { createPostgresRecordReader, createPostgresTurnMessages } from "../store/postgres/record.js";
|
|
43
|
+
import { createPostgresSessionStore } from "../store/postgres/store.js";
|
|
44
|
+
import { createTraceProjector, TRACE_SLOT } from "../traces/projector.js";
|
|
45
|
+
import { createOtlpPoster } from "../traces/otlp.js";
|
|
46
|
+
import { traceKey } from "../traces/key.js";
|
|
41
47
|
import { configForFactory } from "./config-for.js";
|
|
42
48
|
import { createHost } from "./create-host.js";
|
|
43
49
|
import { createHostExecution } from "./execution.js";
|
|
@@ -56,6 +62,7 @@ import { createRunGrants } from "../tenant/run-grants.js";
|
|
|
56
62
|
import { runHarness } from "../harness/main.js";
|
|
57
63
|
import { startHarnessListener } from "../harness-api/ws-server.js";
|
|
58
64
|
import { httpSandboxesClient } from "../sandbox/pods/client.js";
|
|
65
|
+
import { retry } from "../retry.js";
|
|
59
66
|
const nodeRequire = createRequire(import.meta.url);
|
|
60
67
|
function coreVersion() {
|
|
61
68
|
try {
|
|
@@ -121,22 +128,15 @@ function objectStore(config) {
|
|
|
121
128
|
* uploads and downloads fail meanwhile) and does not hold up the Host.
|
|
122
129
|
*/
|
|
123
130
|
async function ensureBucket(store, bucket, logger) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
bucket,
|
|
134
|
-
message: error instanceof Error ? error.message : String(error),
|
|
135
|
-
});
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
await new Promise((resolve) => setTimeout(resolve, Math.min(1000 * attempt, 5000)));
|
|
139
|
-
}
|
|
131
|
+
try {
|
|
132
|
+
await retry(() => store.ensureBucket(), { minMs: 1000, maxMs: 5000, attempts: 10 });
|
|
133
|
+
logger.info("object_store_ready", { bucket });
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
logger.error("object_store_unavailable", {
|
|
137
|
+
bucket,
|
|
138
|
+
message: error instanceof Error ? error.message : String(error),
|
|
139
|
+
});
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
/**
|
|
@@ -147,7 +147,7 @@ function warnRemovedSettings(stack, logger) {
|
|
|
147
147
|
if (stack.removedResolverVariables)
|
|
148
148
|
logger.warn("resolver_removed", {
|
|
149
149
|
variables: stack.removedResolverVariables,
|
|
150
|
-
message: "The credential resolver is gone (protocol 10) and these variables are ignored: keep a person's keys in their
|
|
150
|
+
message: "The credential resolver is gone (protocol 10) and these variables are ignored: keep a person's keys in their Environment's vault, or reach their accounts through a gateway",
|
|
151
151
|
});
|
|
152
152
|
}
|
|
153
153
|
/**
|
|
@@ -260,6 +260,7 @@ export async function main() {
|
|
|
260
260
|
mode: stack.listen ? "container" : "local",
|
|
261
261
|
endpoints: describeEndpoints(stack.endpoints),
|
|
262
262
|
objectStore: stack.objectStore ? "s3" : "fs",
|
|
263
|
+
traces: stack.traces?.export ? "otlp" : "off",
|
|
263
264
|
});
|
|
264
265
|
const infra = createInfra(stack, { logger });
|
|
265
266
|
const database = infra.database;
|
|
@@ -289,7 +290,11 @@ export async function main() {
|
|
|
289
290
|
// running core runs one, once the Tenant is open (the relay reads its id); the replication
|
|
290
291
|
// slot lets exactly one be active. Without S2, the Tenant relays its own commits.
|
|
291
292
|
let relay;
|
|
292
|
-
|
|
293
|
+
// With an OTLP endpoint (R7.2, Q5), the trace projector reads the record on its own slot: one
|
|
294
|
+
// process running core at a time, as for the relay. Without one there is no slot.
|
|
295
|
+
const traceExport = stack.services.has("core") ? stack.traces?.export : undefined;
|
|
296
|
+
let projector;
|
|
297
|
+
if ((streams || traceExport) && stack.services.has("core"))
|
|
293
298
|
await assertLogicalReplication(database);
|
|
294
299
|
const startRelay = (tenantId) => {
|
|
295
300
|
if (!streams || !stack.services.has("core") || relay)
|
|
@@ -307,6 +312,32 @@ export async function main() {
|
|
|
307
312
|
});
|
|
308
313
|
relay.start();
|
|
309
314
|
};
|
|
315
|
+
const startProjector = (tenantId) => {
|
|
316
|
+
if (!traceExport || projector)
|
|
317
|
+
return;
|
|
318
|
+
const store = createPostgresSessionStore({ sql: database, tenantId });
|
|
319
|
+
const log = (message, fields) => logger.info(message, fields);
|
|
320
|
+
projector = createTraceProjector({
|
|
321
|
+
source: createPgoutputSource({
|
|
322
|
+
connectionString: stack.endpoints.databaseUrl,
|
|
323
|
+
tenantId,
|
|
324
|
+
slot: TRACE_SLOT,
|
|
325
|
+
applicationName: "nylorun-trace-export",
|
|
326
|
+
log,
|
|
327
|
+
}),
|
|
328
|
+
post: createOtlpPoster({
|
|
329
|
+
url: traceExport.url,
|
|
330
|
+
headers: traceExport.headers,
|
|
331
|
+
resource: stack.traces.resource,
|
|
332
|
+
log: (message, fields) => logger.warn(message, fields),
|
|
333
|
+
}),
|
|
334
|
+
key: () => traceKey(store),
|
|
335
|
+
turnMessage: createPostgresTurnMessages(database),
|
|
336
|
+
content: new Set(stack.traces.content),
|
|
337
|
+
log,
|
|
338
|
+
});
|
|
339
|
+
projector.start();
|
|
340
|
+
};
|
|
310
341
|
// With the gates service the Tenant's vault-backed model calls, remote MCP calls and HTTP
|
|
311
342
|
// tool calls cross it, and this process never reads a model or MCP credential.
|
|
312
343
|
// Their credentials (F5): each advance's run token for its session's model and tool calls,
|
|
@@ -361,11 +392,17 @@ export async function main() {
|
|
|
361
392
|
...(blobs ? { blobs } : {}),
|
|
362
393
|
...(stack.harnessMode === "remote" ? { harness: "remote" } : {}),
|
|
363
394
|
...(pods ? { pods } : {}),
|
|
395
|
+
...(stack.traces
|
|
396
|
+
? { traces: { resource: stack.traces.resource, content: new Set(stack.traces.content) } }
|
|
397
|
+
: {}),
|
|
364
398
|
...opened,
|
|
365
399
|
}),
|
|
366
400
|
}),
|
|
367
401
|
logger,
|
|
368
|
-
onOpen: (handle) =>
|
|
402
|
+
onOpen: (handle) => {
|
|
403
|
+
startRelay(handle.envelope.id);
|
|
404
|
+
startProjector(handle.envelope.id);
|
|
405
|
+
},
|
|
369
406
|
});
|
|
370
407
|
const options = {
|
|
371
408
|
hostRoot,
|
|
@@ -384,6 +421,7 @@ export async function main() {
|
|
|
384
421
|
beforeTenants: async () => {
|
|
385
422
|
await hostExecution.stop();
|
|
386
423
|
await relay?.stop();
|
|
424
|
+
await projector?.stop();
|
|
387
425
|
// The harnesses' runs stop with the Tenant's advances; their connections close after.
|
|
388
426
|
},
|
|
389
427
|
afterTenants: async () => {
|
|
@@ -427,6 +465,11 @@ export async function main() {
|
|
|
427
465
|
const handle = resolved.kind === "open" ? resolved.handle : undefined;
|
|
428
466
|
return handle?.attachHarness ? (channel, peer) => handle.attachHarness(channel, peer) : undefined;
|
|
429
467
|
},
|
|
468
|
+
// Custom harnesses connect with their own tokens (R4.1).
|
|
469
|
+
harnessOfToken: async (token) => {
|
|
470
|
+
const resolved = await module.resolve();
|
|
471
|
+
return resolved.kind === "open" ? resolved.handle.harnessOfToken?.(token) : undefined;
|
|
472
|
+
},
|
|
430
473
|
logger,
|
|
431
474
|
});
|
|
432
475
|
await host.listen();
|
package/dist/host/operate.js
CHANGED
|
@@ -9,12 +9,16 @@
|
|
|
9
9
|
* nylorun-operate keys list [--json]
|
|
10
10
|
* nylorun-operate keys put <id> [--role application|management] [--json]
|
|
11
11
|
* nylorun-operate keys rm <id> [--json]
|
|
12
|
+
* nylorun-operate harness token put <id> [--json]
|
|
13
|
+
* nylorun-operate harness token list [--json]
|
|
14
|
+
* nylorun-operate harness token rm <id> [--json]
|
|
12
15
|
*
|
|
13
16
|
* `status` says whether the Tenant is open and, when it is not, why: from the database (no
|
|
14
17
|
* Tenant yet, an old layout, a schema newer or older than this Runtime) and from the running
|
|
15
18
|
* Host's `/ready` on this container's listen port. It needs no key, so it works when the Tenant
|
|
16
19
|
* cannot open. `keys put` prints the new key once on stdout (with `--json`, the whole
|
|
17
|
-
* response).
|
|
20
|
+
* response). `harness token put` makes a custom harness's own token (R4.1), bound to its harness
|
|
21
|
+
* id, and prints it once; putting it again rotates it. Exit codes: 0 done (`status`: the Tenant is open), 1 refused (an id, a role),
|
|
18
22
|
* 2 the Tenant cannot be opened (`status` still prints it), 64 usage.
|
|
19
23
|
*/
|
|
20
24
|
import { pathToFileURL } from "node:url";
|
|
@@ -26,6 +30,7 @@ import { createPostgresSessionStore } from "../store/postgres/store.js";
|
|
|
26
30
|
import { readTenantEnvelope } from "../store/postgres/tenant.js";
|
|
27
31
|
import { openError, TenantOpenError } from "../tenant/cause.js";
|
|
28
32
|
import { operatorKeys } from "../tenant/operator-keys.js";
|
|
33
|
+
import { deleteHarnessToken, listHarnessTokens, putHarnessToken } from "../tenant/harness-tokens.js";
|
|
29
34
|
import { RUNTIME_VERSION } from "../version.js";
|
|
30
35
|
import { DEFAULT_CONTAINER_LISTEN_PORT } from "./stack-config.js";
|
|
31
36
|
export const EXIT_REFUSED = 1;
|
|
@@ -35,7 +40,10 @@ const USAGE = `Usage:
|
|
|
35
40
|
nylorun-operate status [--json]
|
|
36
41
|
nylorun-operate keys list [--json]
|
|
37
42
|
nylorun-operate keys put <id> [--role application|management] [--json]
|
|
38
|
-
nylorun-operate keys rm <id> [--json]
|
|
43
|
+
nylorun-operate keys rm <id> [--json]
|
|
44
|
+
nylorun-operate harness token put <id> [--json]
|
|
45
|
+
nylorun-operate harness token list [--json]
|
|
46
|
+
nylorun-operate harness token rm <id> [--json]`;
|
|
39
47
|
class Exit extends Error {
|
|
40
48
|
code;
|
|
41
49
|
constructor(code, message) {
|
|
@@ -52,14 +60,14 @@ export async function runOperate(argv, io) {
|
|
|
52
60
|
throw new Exit(EXIT_USAGE, USAGE);
|
|
53
61
|
return await status(io, json);
|
|
54
62
|
}
|
|
55
|
-
const command = parse(argv);
|
|
63
|
+
const command = argv[0] === "harness" ? harnessCommand(argv) : parse(argv);
|
|
56
64
|
const url = io.env.NYLORUN_DATABASE_URL;
|
|
57
65
|
if (!url)
|
|
58
66
|
throw new Exit(EXIT_TENANT, "NYLORUN_DATABASE_URL is not set: run this inside the runtime container");
|
|
59
67
|
const sql = createPostgresClient(url, { max: 1 });
|
|
60
68
|
try {
|
|
61
|
-
const
|
|
62
|
-
return await command(
|
|
69
|
+
const store = await openStore(sql);
|
|
70
|
+
return await command(operatorKeys(store), io, store);
|
|
63
71
|
}
|
|
64
72
|
finally {
|
|
65
73
|
await sql.end({ timeout: 5 });
|
|
@@ -74,6 +82,60 @@ export async function runOperate(argv, io) {
|
|
|
74
82
|
return EXIT_TENANT;
|
|
75
83
|
}
|
|
76
84
|
}
|
|
85
|
+
/** `harness token put|list|rm` (R4.1). */
|
|
86
|
+
function harnessCommand(argv) {
|
|
87
|
+
const [, noun, verb, ...rest] = argv;
|
|
88
|
+
const json = rest.includes("--json");
|
|
89
|
+
const args = rest.filter((arg) => arg !== "--json");
|
|
90
|
+
if (noun !== "token")
|
|
91
|
+
throw new Exit(EXIT_USAGE, USAGE);
|
|
92
|
+
switch (verb) {
|
|
93
|
+
case "list":
|
|
94
|
+
if (args.length > 0)
|
|
95
|
+
throw new Exit(EXIT_USAGE, USAGE);
|
|
96
|
+
return async (_keys, io, store) => {
|
|
97
|
+
const tokens = await listHarnessTokens(store);
|
|
98
|
+
if (json)
|
|
99
|
+
io.out(JSON.stringify({ tokens }));
|
|
100
|
+
else
|
|
101
|
+
for (const token of tokens)
|
|
102
|
+
io.out(`${token.id}\t${token.createdAt}`);
|
|
103
|
+
return 0;
|
|
104
|
+
};
|
|
105
|
+
case "put": {
|
|
106
|
+
const [id] = args;
|
|
107
|
+
if (!id || args.length !== 1)
|
|
108
|
+
throw new Exit(EXIT_USAGE, USAGE);
|
|
109
|
+
return async (_keys, io, store) => {
|
|
110
|
+
const put = await putHarnessToken(store, id);
|
|
111
|
+
if ("reason" in put)
|
|
112
|
+
throw new Exit(EXIT_REFUSED, put.message);
|
|
113
|
+
if (json)
|
|
114
|
+
io.out(JSON.stringify(put));
|
|
115
|
+
else {
|
|
116
|
+
io.out(put.token);
|
|
117
|
+
io.err(`${put.rotated ? "Rotated" : "Created"} the token of harness ${put.id}. It is shown this once: the harness connects with it as NYLORUN_HARNESS_TOKEN and must say ${put.id} in hello.`);
|
|
118
|
+
}
|
|
119
|
+
return 0;
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
case "rm": {
|
|
123
|
+
const [id] = args;
|
|
124
|
+
if (!id || args.length !== 1)
|
|
125
|
+
throw new Exit(EXIT_USAGE, USAGE);
|
|
126
|
+
return async (_keys, io, store) => {
|
|
127
|
+
const deleted = await deleteHarnessToken(store, id);
|
|
128
|
+
if (json)
|
|
129
|
+
io.out(JSON.stringify({ id, deleted }));
|
|
130
|
+
else
|
|
131
|
+
io.err(deleted ? `Deleted the token of harness ${id}.` : `Harness ${id} has no token.`);
|
|
132
|
+
return 0;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
default:
|
|
136
|
+
throw new Exit(EXIT_USAGE, USAGE);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
77
139
|
function parse(argv) {
|
|
78
140
|
const [group, verb, ...rest] = argv;
|
|
79
141
|
if (group !== "keys")
|
|
@@ -1,3 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stack configuration for the Runtime process, parsed from an environment
|
|
3
|
+
* snapshot and argv. Pure: `host/main.ts` passes the process environment and
|
|
4
|
+
* arguments; nothing here reads ambient state.
|
|
5
|
+
*
|
|
6
|
+
* `--service core,loop` names the Runtime services the process runs (blueprint
|
|
7
|
+
* §19, D12): one image runs every service, and a container may pack several.
|
|
8
|
+
* Without the flag a process runs core and loop. `--role api|worker|all` is
|
|
9
|
+
* the deprecated name of the same choice (core, loop, or both). `gates` (the
|
|
10
|
+
* Model Gate) never shares a process with core or loop: it holds the
|
|
11
|
+
* credentials they must not. Only a process that runs core or loop parses the
|
|
12
|
+
* API listener (`NYLORUN_LISTEN_*`); only one that runs gates parses its
|
|
13
|
+
* listener (`NYLORUN_GATES_LISTEN_*`), and only one that runs loop parses
|
|
14
|
+
* where to reach the gate (`NYLORUN_GATES_URL`). Both read
|
|
15
|
+
* `NYLORUN_GATES_TOKEN`. In a container, loop requires the gate: the loop
|
|
16
|
+
* process must never hold a model credential. `egress` (egress-gate, F7.2) joins gates and keys
|
|
17
|
+
* in the gateway and parses its listener (`NYLORUN_EGRESS_LISTEN_*`, default `0.0.0.0:4200`).
|
|
18
|
+
* The credential resolver is gone (protocol 10): its `NYLORUN_RESOLVER_*` variables are ignored,
|
|
19
|
+
* and the Host logs `resolver_removed` when one is set.
|
|
20
|
+
*
|
|
21
|
+
* `harness` (F6.2) runs alone: it holds the harness credential and nothing else. It connects to
|
|
22
|
+
* core's Harness API listener (`NYLORUN_HARNESS_URL`, `NYLORUN_HARNESS_TOKEN`), calls models and
|
|
23
|
+
* remote MCP servers through the gates service (`NYLORUN_GATES_URL`, with run tokens only), and
|
|
24
|
+
* keeps its workspaces under `NYLORUN_HARNESS_ROOT`. It refuses to start with a database, the
|
|
25
|
+
* gates' or keys' credential, or Restate settings. A process running core starts the Harness API
|
|
26
|
+
* listener when `NYLORUN_HARNESS=remote` (`NYLORUN_HARNESS_LISTEN_*`, `NYLORUN_HARNESS_TOKEN`).
|
|
27
|
+
*
|
|
28
|
+
* Two listen modes:
|
|
29
|
+
* - **local** (no `NYLORUN_LISTEN_*` / `NYLORUN_ALLOWED_HOSTS`): the Host binds
|
|
30
|
+
* what `host.json` names, loopback only unless `allowNonLoopback`.
|
|
31
|
+
* - **container**: the Host binds `NYLORUN_LISTEN_HOST:NYLORUN_LISTEN_PORT`
|
|
32
|
+
* (default `0.0.0.0:4000`) and accepts only the `Host` headers in
|
|
33
|
+
* `NYLORUN_ALLOWED_HOSTS`, plus the loopback forms of the listen port for
|
|
34
|
+
* probes from inside the container.
|
|
35
|
+
*
|
|
36
|
+
* The Postgres, Restate and S2 endpoints are parsed and validated here;
|
|
37
|
+
* `infra/*` builds the clients from them. So is the Object store (`NYLORUN_OBJECT_STORE_*`),
|
|
38
|
+
* which every service may read; `host/main.ts` builds the `BlobStore` from it. So is who the Host's Tenant is when its database
|
|
39
|
+
* holds none yet (`NYLORUN_TENANT_ID`, `NYLORUN_TENANT_NAME`). So are traces (R7.2), for a
|
|
40
|
+
* process running core: where they go (`OTEL_EXPORTER_OTLP_ENDPOINT`, or
|
|
41
|
+
* `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`, with `OTEL_EXPORTER_OTLP_HEADERS`), what they say of
|
|
42
|
+
* their source (`OTEL_SERVICE_NAME`, `OTEL_RESOURCE_ATTRIBUTES`) and which content they carry
|
|
43
|
+
* (`NYLORUN_TRACE_CONTENT`, or all of it with `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true`).
|
|
44
|
+
*/
|
|
45
|
+
import { type TraceResource } from "../traces/otlp.js";
|
|
46
|
+
import { type TraceContentKind } from "../traces/map.js";
|
|
1
47
|
import { type BlockedAddress } from "../sandbox/pods/network-gate.js";
|
|
2
48
|
/** A Runtime service this release has. */
|
|
3
49
|
export type RuntimeService = "core" | "loop" | "gates" | "keys" | "egress" | "harness";
|
|
@@ -39,6 +85,12 @@ export interface StackEndpoints {
|
|
|
39
85
|
restateIngressUrl?: string;
|
|
40
86
|
restateAdminUrl?: string;
|
|
41
87
|
workerUrl?: string;
|
|
88
|
+
/**
|
|
89
|
+
* The Worker deployment's version, from `NYLORUN_WORKER_VERSION` (default: the Runtime's
|
|
90
|
+
* version): the Worker registers `<NYLORUN_WORKER_URL>/nylorun/<version>` with Restate
|
|
91
|
+
* (`infra/execution.ts`, `workerDeployment`).
|
|
92
|
+
*/
|
|
93
|
+
workerVersion?: string;
|
|
42
94
|
s2Endpoint?: string;
|
|
43
95
|
s2Token?: string;
|
|
44
96
|
workspaceStoreUrl?: string;
|
|
@@ -191,6 +243,24 @@ export interface StackConfig {
|
|
|
191
243
|
* (the `fs` adapter, `TenantPaths.blobs`).
|
|
192
244
|
*/
|
|
193
245
|
objectStore?: ObjectStoreConfig;
|
|
246
|
+
/**
|
|
247
|
+
* Present when the process runs core and an `OTEL_*` or `NYLORUN_TRACE_CONTENT` variable is set:
|
|
248
|
+
* how spans are made, for the trace projector and the trace route (R7.2, R7.5). `export` is
|
|
249
|
+
* present only with an endpoint (Q5): without one there is no replication slot and no cost.
|
|
250
|
+
*/
|
|
251
|
+
traces?: TraceConfig;
|
|
252
|
+
}
|
|
253
|
+
/** Traces (R7.2), from the standard `OTEL_*` variables and `NYLORUN_TRACE_CONTENT`. */
|
|
254
|
+
export interface TraceConfig {
|
|
255
|
+
/** `OTEL_SERVICE_NAME` (default `nylorun`) and `OTEL_RESOURCE_ATTRIBUTES`. */
|
|
256
|
+
resource: TraceResource;
|
|
257
|
+
/** The content kinds spans carry: none by default. */
|
|
258
|
+
content: TraceContentKind[];
|
|
259
|
+
/** Where the projector posts: the traces URL and `OTEL_EXPORTER_OTLP_HEADERS`. */
|
|
260
|
+
export?: {
|
|
261
|
+
url: string;
|
|
262
|
+
headers: Record<string, string>;
|
|
263
|
+
};
|
|
194
264
|
}
|
|
195
265
|
/** Where the Object store is and the credential for it (`NYLORUN_OBJECT_STORE_*`). */
|
|
196
266
|
export interface ObjectStoreConfig {
|