@nylorun/runtime 0.24.0-beta → 0.26.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 +143 -0
- package/README.md +18 -4
- package/dist/api/a2a/card.d.ts +11 -1
- package/dist/api/a2a/card.js +26 -2
- package/dist/api/a2a/endpoint.js +4 -2
- package/dist/api/a2a/protocol.d.ts +25 -2
- package/dist/api/a2a/protocol.js +20 -3
- package/dist/api/a2a/routes.d.ts +4 -1
- package/dist/api/a2a/routes.js +65 -7
- package/dist/api/ag-ui/endpoint.js +5 -2
- package/dist/api/ag-ui/history.d.ts +6 -1
- package/dist/api/ag-ui/history.js +35 -2
- package/dist/api/ag-ui/routes.js +53 -11
- package/dist/api/components.d.ts +1165 -678
- package/dist/api/components.js +16 -1
- package/dist/api/gate/keys.d.ts +2 -1
- package/dist/api/gate/keys.js +7 -0
- package/dist/api/gate/routes.js +13 -0
- package/dist/api/http/app.d.ts +2 -2
- package/dist/api/http/app.js +15 -4
- package/dist/api/http/body.d.ts +3 -3
- package/dist/api/http/body.js +7 -7
- package/dist/api/http/define.d.ts +1 -1
- package/dist/api/http/respond.js +1 -1
- package/dist/api/http/routes/environment-manifests.d.ts +9 -0
- package/dist/api/http/routes/environment-manifests.js +118 -0
- package/dist/api/http/routes/environments.js +61 -12
- package/dist/api/http/routes/reads.d.ts +52 -3
- package/dist/api/http/routes/sessions.js +14 -4
- package/dist/api/http/routes/tenant.js +35 -7
- package/dist/api/inbound-files.d.ts +65 -0
- package/dist/api/inbound-files.js +115 -0
- package/dist/api/openapi.js +18 -3
- package/dist/artifacts/disk.js +20 -14
- package/dist/artifacts/media-types.d.ts +15 -0
- package/dist/artifacts/media-types.js +56 -4
- package/dist/artifacts/parts.d.ts +12 -3
- package/dist/artifacts/parts.js +15 -4
- package/dist/artifacts/workspace.d.ts +4 -4
- package/dist/artifacts/workspace.js +6 -18
- package/dist/core/flow-host.d.ts +1 -1
- package/dist/core/flow-host.js +2 -2
- package/dist/core/sandbox-routes.js +2 -2
- package/dist/core/turn-manifest.d.ts +3 -2
- package/dist/core/turn-manifest.js +29 -5
- package/dist/gates/egress-credentials.js +1 -1
- package/dist/gates/egress.d.ts +13 -19
- package/dist/gates/egress.js +32 -29
- package/dist/gates/http-tool.d.ts +3 -4
- package/dist/gates/http-tool.js +23 -19
- package/dist/gates/journal.js +0 -1
- package/dist/gates/mcp-handler.js +2 -3
- package/dist/gates/policy.js +1 -3
- package/dist/gates/tool-contract.d.ts +1 -1
- package/dist/harness/calls.d.ts +2 -2
- package/dist/harness/calls.js +9 -12
- package/dist/harness/main.js +7 -1
- package/dist/harness/pod.d.ts +6 -1
- package/dist/harness/pod.js +5 -0
- package/dist/harness/service.js +5 -5
- package/dist/harness/setup.d.ts +2 -0
- package/dist/harness/setup.js +25 -1
- package/dist/harness-api/record.d.ts +1 -1
- package/dist/harness-api/record.js +13 -13
- package/dist/harness-api/server.d.ts +27 -4
- package/dist/harness-api/server.js +41 -8
- package/dist/harness-api/tool-journal.d.ts +17 -8
- package/dist/harness-api/tool-journal.js +41 -11
- package/dist/harness-api/workspace.d.ts +7 -5
- package/dist/harness-api/workspace.js +25 -19
- package/dist/host/gates.js +2 -2
- package/dist/host/stack-config.js +1 -1
- package/dist/keys/contract.d.ts +1 -1
- package/dist/keys/contract.js +1 -1
- package/dist/keys/keys.d.ts +9 -2
- package/dist/keys/keys.js +4 -1
- package/dist/management-openapi.json +366 -61
- package/dist/mcp/connect.d.ts +1 -2
- package/dist/mcp/connect.js +1 -4
- package/dist/mcp/pool.d.ts +1 -4
- package/dist/mcp/pool.js +13 -27
- package/dist/mcp/preview.d.ts +1 -1
- package/dist/mcp/preview.js +1 -2
- package/dist/mcp/snapshot.d.ts +11 -13
- package/dist/mcp/snapshot.js +27 -38
- package/dist/model/capabilities.d.ts +26 -0
- package/dist/model/capabilities.js +44 -0
- package/dist/model/catalog.d.ts +34 -3
- package/dist/model/catalog.js +38 -2
- package/dist/model/models.d.ts +2 -0
- package/dist/model/models.js +1 -1
- package/dist/model/native-blocks.d.ts +35 -0
- package/dist/model/native-blocks.js +170 -0
- package/dist/model/pi-model.js +106 -94
- package/dist/openapi.json +9554 -5076
- package/dist/record/environment.js +1 -1
- package/dist/sandbox/join.js +4 -0
- package/dist/sandbox/manager.d.ts +32 -14
- package/dist/sandbox/manager.js +132 -93
- package/dist/sandbox/placement.d.ts +1 -1
- package/dist/sandbox/placement.js +2 -2
- package/dist/sandbox/pods/client.d.ts +3 -1
- package/dist/sandbox/pods/lifecycle.d.ts +8 -0
- package/dist/sandbox/pods/lifecycle.js +15 -0
- package/dist/sandbox/pods/spec.js +2 -1
- package/dist/sandbox/resolve.d.ts +5 -3
- package/dist/sandbox/resolve.js +9 -2
- package/dist/sandbox/runtime-tools.d.ts +9 -0
- package/dist/sandbox/runtime-tools.js +16 -0
- package/dist/sandbox/session-sandbox.d.ts +4 -6
- package/dist/sandbox/session-sandbox.js +47 -94
- package/dist/sandbox/setup.d.ts +4 -1
- package/dist/sandbox/setup.js +46 -9
- package/dist/sandbox/share.d.ts +12 -7
- package/dist/sandbox/share.js +27 -25
- package/dist/sandbox/skills.d.ts +8 -6
- package/dist/sandbox/skills.js +24 -23
- package/dist/store/postgres/drizzle/0024_flat_manifest.sql +7 -0
- package/dist/store/postgres/drizzle/0025_environment_manifests.sql +14 -0
- package/dist/store/postgres/drizzle/0026_environment_forks.sql +1 -0
- package/dist/store/postgres/drizzle/0027_environment_image_digest.sql +1 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
- package/dist/store/postgres/migrate.js +1 -1
- package/dist/store/postgres/reads.js +8 -2
- package/dist/store/postgres/schema.d.ts +217 -0
- package/dist/store/postgres/schema.js +36 -2
- package/dist/store/postgres/store.js +79 -4
- package/dist/store/types.d.ts +68 -14
- package/dist/tenant/advance.js +3 -0
- package/dist/tenant/artifact-tool.d.ts +1 -1
- package/dist/tenant/artifact-tool.js +43 -22
- package/dist/tenant/commands.js +24 -4
- package/dist/tenant/context.d.ts +10 -4
- package/dist/tenant/effects.js +7 -9
- package/dist/tenant/environment-manifests.d.ts +57 -0
- package/dist/tenant/environment-manifests.js +200 -0
- package/dist/tenant/environments.d.ts +54 -47
- package/dist/tenant/environments.js +309 -85
- package/dist/tenant/ephemeral.d.ts +1 -1
- package/dist/tenant/host-token.js +1 -1
- package/dist/tenant/identity-file.d.ts +1 -1
- package/dist/tenant/identity-file.js +1 -1
- package/dist/tenant/model-setting.d.ts +14 -0
- package/dist/tenant/model-setting.js +51 -0
- package/dist/tenant/outbound.d.ts +6 -5
- package/dist/tenant/outbound.js +8 -6
- package/dist/tenant/sessions.d.ts +5 -1
- package/dist/tenant/sessions.js +45 -12
- package/dist/tenant/skill-tool.d.ts +3 -3
- package/dist/tenant/skill-tool.js +6 -11
- package/dist/tenant/sweep.js +1 -1
- package/dist/tenant/tool-results.js +1 -1
- package/dist/tenant/tool-search.d.ts +7 -11
- package/dist/tenant/tool-search.js +8 -5
- package/dist/tenant/transcript.d.ts +51 -32
- package/dist/tenant/transcript.js +2 -1
- package/dist/tenant/types.d.ts +2 -2
- package/dist/traces/ids.d.ts +0 -1
- package/dist/traces/ids.js +0 -4
- package/dist/traces/map.d.ts +2 -2
- package/dist/traces/map.js +1 -29
- package/dist/vault/coverage.js +11 -13
- package/dist/vault/host-model.d.ts +4 -1
- package/dist/vault/host-model.js +10 -2
- package/dist/vault/service.d.ts +49 -4
- package/dist/vault/service.js +153 -6
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/dist/sandbox/capability.d.ts +0 -3
- package/dist/sandbox/capability.js +0 -10
package/dist/core/flow-host.js
CHANGED
|
@@ -80,14 +80,14 @@ export function isWorkflowManifest(manifest) {
|
|
|
80
80
|
typeof manifest === "object" &&
|
|
81
81
|
manifest.kind === "workflow");
|
|
82
82
|
}
|
|
83
|
-
/** Flow tool-node effect: `tool` with path/key and no agent
|
|
83
|
+
/** Flow tool-node effect: `tool` with path/key and no tool name (an agent's tool call names one). */
|
|
84
84
|
export function isFlowToolEffect(request) {
|
|
85
85
|
return (request.kind === "tool" &&
|
|
86
86
|
typeof request.path === "string" &&
|
|
87
87
|
request.path.length > 0 &&
|
|
88
88
|
typeof request.key === "string" &&
|
|
89
89
|
request.key.length > 0 &&
|
|
90
|
-
request.
|
|
90
|
+
request.toolName === undefined);
|
|
91
91
|
}
|
|
92
92
|
export function isFlowEffect(request) {
|
|
93
93
|
return request.kind === "agent" || isFlowToolEffect(request);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* a tool run in the sandbox of the session's Environment.
|
|
4
4
|
*/
|
|
5
5
|
import { isSandboxToolName, } from "@nylorun/core/define";
|
|
6
|
-
import {
|
|
6
|
+
import { sandboxWorkspaceOf, sessionSandboxSpec, } from "../sandbox/share.js";
|
|
7
7
|
export class SandboxRouteError extends Error {
|
|
8
8
|
status;
|
|
9
9
|
constructor(status, message) {
|
|
@@ -41,7 +41,7 @@ async function runTool(deps, session, tool, input, signal) {
|
|
|
41
41
|
manifest: session.manifest,
|
|
42
42
|
...(workspace.environmentId === undefined ? {} : { environmentId: workspace.environmentId }),
|
|
43
43
|
};
|
|
44
|
-
return deps.sandbox.run(ref,
|
|
44
|
+
return deps.sandbox.run(ref, spec, tool, input, signal);
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
47
|
* `POST /v1/sessions/:id/sandbox/:tool` — application principal.
|
|
@@ -16,8 +16,9 @@ export type TurnManifestErr = {
|
|
|
16
16
|
readonly message: string;
|
|
17
17
|
};
|
|
18
18
|
/**
|
|
19
|
-
* Parse `message.manifest`, require it to be a variant of the session pin,
|
|
20
|
-
*
|
|
19
|
+
* Parse `message.manifest`, require it to be a variant of the session pin, and store the
|
|
20
|
+
* manifest the turn runs with: the variant with the pin's `runtime` block (a variant never sets
|
|
21
|
+
* one), by its hash. Applies to that turn only (caller must not latch).
|
|
21
22
|
*/
|
|
22
23
|
export declare function pinTurnManifest(args: {
|
|
23
24
|
readonly candidate: unknown;
|
|
@@ -2,8 +2,29 @@ import { AgentManifestSchema, } from "@nylorun/core/contracts";
|
|
|
2
2
|
import { hashManifest } from "@nylorun/core/compatibility";
|
|
3
3
|
import { isVariantOf } from "@nylorun/core/define";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* The candidate's agents used as tools with the `runtime` block the pin gave each, when the
|
|
6
|
+
* candidate left it out: a turn variant is written from the definition, and only the Runtime
|
|
7
|
+
* writes `runtime`. A candidate that sets one keeps it, and must then match the pin's.
|
|
8
|
+
*/
|
|
9
|
+
function withPinnedDelegates(candidate, pinned) {
|
|
10
|
+
if (candidate.tools === undefined)
|
|
11
|
+
return candidate;
|
|
12
|
+
const pinnedAgents = new Map((pinned.tools ?? []).flatMap((tool) => tool.agent !== undefined && !("kind" in tool.agent) ? [[tool.name, tool.agent]] : []));
|
|
13
|
+
let changed = false;
|
|
14
|
+
const tools = candidate.tools.map((tool) => {
|
|
15
|
+
const agent = tool.agent;
|
|
16
|
+
const prior = pinnedAgents.get(tool.name);
|
|
17
|
+
if (agent === undefined || "kind" in agent || agent.runtime !== undefined || prior?.runtime === undefined)
|
|
18
|
+
return tool;
|
|
19
|
+
changed = true;
|
|
20
|
+
return { ...tool, agent: { ...agent, runtime: prior.runtime } };
|
|
21
|
+
});
|
|
22
|
+
return changed ? { ...candidate, tools } : candidate;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Parse `message.manifest`, require it to be a variant of the session pin, and store the
|
|
26
|
+
* manifest the turn runs with: the variant with the pin's `runtime` block (a variant never sets
|
|
27
|
+
* one), by its hash. Applies to that turn only (caller must not latch).
|
|
7
28
|
*/
|
|
8
29
|
export function pinTurnManifest(args) {
|
|
9
30
|
const parsed = AgentManifestSchema.safeParse(args.candidate);
|
|
@@ -14,14 +35,17 @@ export function pinTurnManifest(args) {
|
|
|
14
35
|
message: "Turn manifest failed AgentManifestSchema validation",
|
|
15
36
|
};
|
|
16
37
|
}
|
|
17
|
-
const
|
|
18
|
-
if (!isVariantOf(
|
|
38
|
+
const candidate = withPinnedDelegates(parsed.data, args.pinned);
|
|
39
|
+
if (!isVariantOf(candidate, args.pinned)) {
|
|
19
40
|
return {
|
|
20
41
|
ok: false,
|
|
21
42
|
code: "loop.invalid-agent",
|
|
22
|
-
message:
|
|
43
|
+
message: candidate.runtime === undefined
|
|
44
|
+
? "Turn manifest is not a variant of the session's pinned manifest"
|
|
45
|
+
: "Turn manifest sets runtime, which only the Runtime writes: leave it out, and the turn runs with the session's",
|
|
23
46
|
};
|
|
24
47
|
}
|
|
48
|
+
const manifest = args.pinned.runtime === undefined ? candidate : { ...candidate, runtime: args.pinned.runtime };
|
|
25
49
|
const hash = hashManifest(manifest);
|
|
26
50
|
args.store.put(hash, manifest);
|
|
27
51
|
return { ok: true, manifest, hash };
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Nothing here knows a tool: the header name and its format are the credential's. A release the
|
|
13
13
|
* vault refuses (two secrets for one host, an unreadable value) answers 502 and is logged, never
|
|
14
14
|
* as an event; a value never is. Upgrades (WebSocket) and HTTP/2 are not served on this path.
|
|
15
|
-
* Each connection keeps a tally for its `egress
|
|
15
|
+
* Each connection keeps a tally for its `environment.egress` event (R7.3, D67): how many requests it
|
|
16
16
|
* read and which credentials it set, by id.
|
|
17
17
|
*/
|
|
18
18
|
import { createServer } from "node:http";
|
package/dist/gates/egress.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Server } from "node:http";
|
|
2
2
|
import { type Socket } from "node:net";
|
|
3
|
-
import type {
|
|
3
|
+
import type { EgressRunningTurn, EnvironmentEventPayload } from "@nylorun/core/contracts";
|
|
4
4
|
import type { EgressTokenVerdict } from "../sandbox/egress-token.js";
|
|
5
5
|
import type { SessionStore } from "../store/types.js";
|
|
6
6
|
import type { Logger } from "../tenant/types.js";
|
|
@@ -16,22 +16,17 @@ export interface EgressSandboxes {
|
|
|
16
16
|
readonly allow: readonly string[];
|
|
17
17
|
} | undefined>;
|
|
18
18
|
}
|
|
19
|
-
/**
|
|
20
|
-
export
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
/** One connection's record (R7.3): `egress.connection`'s payload, `environment.egress`'s too. */
|
|
25
|
-
export type EgressConnection = EventPayload<"egress.connection">;
|
|
26
|
-
/** Where egress-gate records connections (R7.3). */
|
|
19
|
+
/** One connection's record (R7.3, D67): `environment.egress`'s payload. */
|
|
20
|
+
export type EgressConnection = EnvironmentEventPayload<"environment.egress">;
|
|
21
|
+
/** How many running turns one connection names, at most. */
|
|
22
|
+
export declare const EGRESS_RUNNING_TURNS_MAX = 16;
|
|
23
|
+
/** Where egress-gate records connections (R7.3, D67). */
|
|
27
24
|
export interface EgressRecord {
|
|
28
|
-
/** The
|
|
29
|
-
|
|
30
|
-
/** Writes `connection` to
|
|
31
|
-
write(
|
|
25
|
+
/** The turns active on the Environment now, at most `EGRESS_RUNNING_TURNS_MAX`. */
|
|
26
|
+
runningTurns(environmentId: string): Promise<readonly EgressRunningTurn[]>;
|
|
27
|
+
/** Writes `connection` to its Environment's stream; a deleted Environment gets nothing. */
|
|
28
|
+
write(connection: EgressConnection): Promise<void>;
|
|
32
29
|
}
|
|
33
|
-
/** egress-gate as the writer of its events (R4.9): the gate saw the connection itself. */
|
|
34
|
-
export declare const EGRESS_WRITER: EventWriter;
|
|
35
30
|
/** Ports a tunnel may open. */
|
|
36
31
|
export declare const EGRESS_PORTS: readonly number[];
|
|
37
32
|
/** Tunnels one sandbox may hold open at once. */
|
|
@@ -93,10 +88,9 @@ export declare function startEgressGate(options: StartEgressGateOptions): Promis
|
|
|
93
88
|
*/
|
|
94
89
|
export declare function storeEgressSandboxes(store: SessionStore): EgressSandboxes;
|
|
95
90
|
/**
|
|
96
|
-
* `EgressRecord` over the Tenant's record (R7.3). The
|
|
97
|
-
* whose body has an `activeTurnId
|
|
98
|
-
* is `egress
|
|
99
|
-
* one, `environment.egress` on the Environment's stream, under its row's lock. A deleted
|
|
91
|
+
* `EgressRecord` over the Tenant's record (R7.3, D67). The running turns are the sessions on the
|
|
92
|
+
* Environment whose body has an `activeTurnId`, in id order, at most `EGRESS_RUNNING_TURNS_MAX`.
|
|
93
|
+
* The write is `environment.egress` on the Environment's stream, under its row's lock; a deleted
|
|
100
94
|
* Environment gets nothing.
|
|
101
95
|
*/
|
|
102
96
|
export declare function storeEgressRecord(store: SessionStore): EgressRecord;
|
package/dist/gates/egress.js
CHANGED
|
@@ -24,11 +24,13 @@
|
|
|
24
24
|
* (an absolute-form plain HTTP request included) is answered 405. Refusals are logged; the token
|
|
25
25
|
* never is. Tunnels close after `idleMs` without traffic in either direction.
|
|
26
26
|
*
|
|
27
|
-
* Every connection to a named host on a live token is recorded once (R7.3,
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
27
|
+
* Every connection to a named host on a live token is recorded once (R7.3, D67), when it closes
|
|
28
|
+
* or at once when the gate refuses it, as `environment.egress` on the Environment's own stream:
|
|
29
|
+
* the connection is the Environment's, since its token names the pod and turns run in parallel
|
|
30
|
+
* (D66). `runningTurns` lists the turns active on the Environment as it opened, as context and
|
|
31
|
+
* never as an owner: one read, started as the name resolves and never waited for, so a turn that
|
|
32
|
+
* moves while it is in flight may show either way. Host, port, outcome, bytes, duration and, for a
|
|
33
|
+
* credential host, the request count and credential ids; never a header value, a path or a body. A refusal
|
|
32
34
|
* before that (no token, a stale one, no `host:port`) stays a log line, and a client that hangs up
|
|
33
35
|
* before the gate answers leaves nothing. A write never holds the tunnel up, and a failed one is
|
|
34
36
|
* logged (`egress_record_failed`).
|
|
@@ -40,8 +42,8 @@ import { isSandboxHostPattern } from "@nylorun/core/define";
|
|
|
40
42
|
import { isPrivateAddress } from "../tenant/outbound.js";
|
|
41
43
|
import { bindListener } from "../host/http.js";
|
|
42
44
|
import { CONNECT_ESTABLISHED, credentialTerminator, } from "./egress-credentials.js";
|
|
43
|
-
/**
|
|
44
|
-
export const
|
|
45
|
+
/** How many running turns one connection names, at most. */
|
|
46
|
+
export const EGRESS_RUNNING_TURNS_MAX = 16;
|
|
45
47
|
/** Ports a tunnel may open. */
|
|
46
48
|
export const EGRESS_PORTS = [443, 80];
|
|
47
49
|
/** Tunnels one sandbox may hold open at once. */
|
|
@@ -146,16 +148,17 @@ function answer(socket, status, message, headers = []) {
|
|
|
146
148
|
].join("\r\n"));
|
|
147
149
|
}
|
|
148
150
|
function connectionRecord(sink, logger, client, at) {
|
|
149
|
-
let
|
|
151
|
+
let running;
|
|
150
152
|
let written = false;
|
|
151
|
-
const
|
|
153
|
+
const runningTurns = (to) => (running ??= to.runningTurns(at.environmentId));
|
|
152
154
|
const write = (fields) => {
|
|
153
155
|
if (written || !sink)
|
|
154
156
|
return;
|
|
155
157
|
written = true;
|
|
156
158
|
const socket = client;
|
|
157
159
|
const allowed = fields.outcome === "allowed";
|
|
158
|
-
const
|
|
160
|
+
const durationMs = Math.max(0, Date.now() - at.startedAt.getTime());
|
|
161
|
+
const connection = (turns) => ({
|
|
159
162
|
environmentId: at.environmentId,
|
|
160
163
|
host: at.host,
|
|
161
164
|
port: at.port,
|
|
@@ -165,11 +168,12 @@ function connectionRecord(sink, logger, client, at) {
|
|
|
165
168
|
// Up is what the sandbox sent after its CONNECT, down what it got after the gate's 200.
|
|
166
169
|
bytesUp: allowed ? Math.max(0, (socket.bytesRead ?? 0) - at.upFrom) : 0,
|
|
167
170
|
bytesDown: allowed ? Math.max(0, (socket.bytesWritten ?? 0) - CONNECT_ESTABLISHED.length) : 0,
|
|
168
|
-
durationMs
|
|
171
|
+
durationMs,
|
|
169
172
|
startedAt: at.startedAt.toISOString(),
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
+
runningTurns: [...turns],
|
|
174
|
+
});
|
|
175
|
+
runningTurns(sink)
|
|
176
|
+
.then((turns) => sink.write(connection(turns)))
|
|
173
177
|
.catch((error) => logger.error("egress_record_failed", {
|
|
174
178
|
sandboxId: at.environmentId,
|
|
175
179
|
host: at.host,
|
|
@@ -179,7 +183,7 @@ function connectionRecord(sink, logger, client, at) {
|
|
|
179
183
|
const record = {
|
|
180
184
|
credentialBound: false,
|
|
181
185
|
// A failed read is logged when the write needs it.
|
|
182
|
-
|
|
186
|
+
opened: () => void (sink && runningTurns(sink).catch(() => undefined)),
|
|
183
187
|
refused: (status, reason) => write({ outcome: "refused", reason, status }),
|
|
184
188
|
closed: (tally) => write({
|
|
185
189
|
outcome: "allowed",
|
|
@@ -257,8 +261,9 @@ export async function startEgressGate(options) {
|
|
|
257
261
|
return refuse(403, "not_a_host_name", { sandboxId }, `${host} is not a host name; IP literals are refused`);
|
|
258
262
|
if (!egressAllows(host, sandbox.allow))
|
|
259
263
|
return refuse(403, "host_not_allowed", { sandboxId }, `${host} is not in this sandbox's network.allow`);
|
|
260
|
-
//
|
|
261
|
-
|
|
264
|
+
// The turns running on the Environment as the connection opens: the read starts as the
|
|
265
|
+
// name resolves, and the 200 does not wait for it.
|
|
266
|
+
record.opened();
|
|
262
267
|
let mine = open.get(sandboxId);
|
|
263
268
|
if (!mine)
|
|
264
269
|
open.set(sandboxId, (mine = new Set()));
|
|
@@ -409,25 +414,23 @@ export function storeEgressSandboxes(store) {
|
|
|
409
414
|
};
|
|
410
415
|
}
|
|
411
416
|
/**
|
|
412
|
-
* `EgressRecord` over the Tenant's record (R7.3). The
|
|
413
|
-
* whose body has an `activeTurnId
|
|
414
|
-
* is `egress
|
|
415
|
-
* one, `environment.egress` on the Environment's stream, under its row's lock. A deleted
|
|
417
|
+
* `EgressRecord` over the Tenant's record (R7.3, D67). The running turns are the sessions on the
|
|
418
|
+
* Environment whose body has an `activeTurnId`, in id order, at most `EGRESS_RUNNING_TURNS_MAX`.
|
|
419
|
+
* The write is `environment.egress` on the Environment's stream, under its row's lock; a deleted
|
|
416
420
|
* Environment gets nothing.
|
|
417
421
|
*/
|
|
418
422
|
export function storeEgressRecord(store) {
|
|
419
423
|
return {
|
|
420
|
-
async
|
|
424
|
+
async runningTurns(environmentId) {
|
|
421
425
|
const sessions = await store.tx((t) => t.sessionsOnEnvironment(environmentId));
|
|
422
|
-
|
|
423
|
-
|
|
426
|
+
return sessions
|
|
427
|
+
.filter((session) => typeof session.activeTurnId === "string")
|
|
428
|
+
.map((session) => ({ sessionId: session.id, turnId: session.activeTurnId }))
|
|
429
|
+
.sort((a, b) => (a.sessionId < b.sessionId ? -1 : a.sessionId > b.sessionId ? 1 : 0))
|
|
430
|
+
.slice(0, EGRESS_RUNNING_TURNS_MAX);
|
|
424
431
|
},
|
|
425
|
-
async write(
|
|
432
|
+
async write(connection) {
|
|
426
433
|
await store.tx(async (t) => {
|
|
427
|
-
if (holder) {
|
|
428
|
-
await t.event(holder.sessionId, holder.turnId, "egress.connection", connection, EGRESS_WRITER);
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
434
|
if (!(await t.environment(connection.environmentId, { lock: true })))
|
|
432
435
|
return;
|
|
433
436
|
await t.environmentEvent(connection.environmentId, "environment.egress", connection);
|
|
@@ -24,14 +24,13 @@ export type HttpOutcome = {
|
|
|
24
24
|
};
|
|
25
25
|
/**
|
|
26
26
|
* One declared HTTP tool of a session: what the gate finds it by in the pinned manifest. An
|
|
27
|
-
* agent's HTTP tool is named by its
|
|
27
|
+
* agent's HTTP tool is named by its agent and tool name; a flow's HTTP stage or HTTP verifier
|
|
28
28
|
* by its stage key.
|
|
29
29
|
*/
|
|
30
30
|
export type HttpToolRef = {
|
|
31
31
|
readonly sessionId: string;
|
|
32
32
|
/** The agent used as a tool that declares it; absent for the session's root agent. */
|
|
33
33
|
readonly agentId?: string;
|
|
34
|
-
readonly capabilityId: string;
|
|
35
34
|
readonly toolName: string;
|
|
36
35
|
} | {
|
|
37
36
|
readonly sessionId: string;
|
|
@@ -57,8 +56,8 @@ export interface HttpToolTenant {
|
|
|
57
56
|
} | undefined>;
|
|
58
57
|
authorize(sessionId: string, request: McpCredentialRequest): Promise<AuthorizeResult>;
|
|
59
58
|
}
|
|
60
|
-
/** The HTTP tool `
|
|
61
|
-
export declare function declaredHttpTool(manifest: unknown, agentId: string | undefined,
|
|
59
|
+
/** The HTTP tool `toolName` of the agent `agentId` (the root when absent). */
|
|
60
|
+
export declare function declaredHttpTool(manifest: unknown, agentId: string | undefined, toolName: string | undefined): (ToolManifest & {
|
|
62
61
|
readonly http: HttpToolTarget;
|
|
63
62
|
}) | undefined;
|
|
64
63
|
/** The HTTP stage or HTTP verifier at stage key `stage` of a flow session's `manifest`. */
|
package/dist/gates/http-tool.js
CHANGED
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
* The loop names the tool; the gate finds its URL, method and credential in the session's pinned
|
|
4
4
|
* manifest, as it finds a remote MCP server, so the loop never chooses a URL or a credential.
|
|
5
5
|
*
|
|
6
|
-
* `callHttpTarget` is the request and its answer, for any declared HTTP target: the input as
|
|
7
|
-
*
|
|
6
|
+
* `callHttpTarget` is the request and its answer, for any declared HTTP target: the input as
|
|
7
|
+
* core's `httpToolRequest` maps it (path parameters in the URL, the rest as a JSON body or, for
|
|
8
|
+
* `GET` and `DELETE`, the query string) under the Host's address policy (`tenant/outbound.ts`),
|
|
9
|
+
* a 2xx answer as the output
|
|
8
10
|
* (JSON, or text when the answer is not JSON and no output schema asks for JSON), and every other
|
|
9
11
|
* answer, a timeout or a refused address as a failed outcome the model sees. `runHttpTool` adds
|
|
10
12
|
* what a tool call needs: the declaration, the vault credential and the identity headers.
|
|
@@ -14,21 +16,19 @@
|
|
|
14
16
|
* `Nylorun-Agent-Id` is the flow agent's.
|
|
15
17
|
*/
|
|
16
18
|
import { AGENT_ID_HEADER, SESSION_ID_HEADER, TURN_ID_HEADER, } from "@nylorun/core/compatibility";
|
|
17
|
-
import { HTTP_TOOL_DEFAULT_TIMEOUT_MS, delegateManifest, flowHttpTarget, isWorkflowManifest, schemaFromJSON, } from "@nylorun/core/define";
|
|
19
|
+
import { HTTP_TOOL_DEFAULT_TIMEOUT_MS, delegateManifest, flowHttpTarget, httpPathParameters, httpToolRequest, isWorkflowManifest, schemaFromJSON, } from "@nylorun/core/define";
|
|
18
20
|
import { scrubValues } from "../redact.js";
|
|
19
|
-
import { MAX_RESPONSE_BYTES,
|
|
21
|
+
import { MAX_RESPONSE_BYTES, send } from "../tenant/outbound.js";
|
|
20
22
|
import { credentialSecrets } from "../vault/headers.js";
|
|
21
23
|
/** How much of a failed answer's body the model sees. */
|
|
22
24
|
export const HTTP_ERROR_BODY_CHARS = 2_000;
|
|
23
|
-
/** The HTTP tool `
|
|
24
|
-
export function declaredHttpTool(manifest, agentId,
|
|
25
|
+
/** The HTTP tool `toolName` of the agent `agentId` (the root when absent). */
|
|
26
|
+
export function declaredHttpTool(manifest, agentId, toolName) {
|
|
25
27
|
const root = manifest;
|
|
26
|
-
if (!root ||
|
|
28
|
+
if (!root || typeof root !== "object" || isWorkflowManifest(root) || toolName === undefined)
|
|
27
29
|
return undefined;
|
|
28
30
|
const agent = agentId === undefined ? root : delegateManifest(root, agentId);
|
|
29
|
-
const tool = agent?.
|
|
30
|
-
.find((capability) => capability.id === capabilityId)
|
|
31
|
-
?.tools?.find((item) => item.name === toolName);
|
|
31
|
+
const tool = agent?.tools?.find((item) => item.name === toolName);
|
|
32
32
|
return tool?.http ? tool : undefined;
|
|
33
33
|
}
|
|
34
34
|
/** The HTTP stage or HTTP verifier at stage key `stage` of a flow session's `manifest`. */
|
|
@@ -44,18 +44,17 @@ export function declaredFlowHttp(manifest, stage) {
|
|
|
44
44
|
export function isHttpToolCall(manifest, request) {
|
|
45
45
|
if (request.kind !== "tool")
|
|
46
46
|
return false;
|
|
47
|
-
if (request.
|
|
47
|
+
if (request.toolName === undefined)
|
|
48
48
|
return declaredFlowHttp(manifest, request.key) !== undefined;
|
|
49
|
-
return declaredHttpTool(manifest, request.agent?.id, request.
|
|
49
|
+
return declaredHttpTool(manifest, request.agent?.id, request.toolName) !== undefined;
|
|
50
50
|
}
|
|
51
51
|
/** The ref a `tool` effect calls the Tool Gate with: its agent's HTTP tool, or its flow stage. */
|
|
52
52
|
export function httpToolRefOf(request) {
|
|
53
|
-
if (request.
|
|
53
|
+
if (request.toolName === undefined)
|
|
54
54
|
return { sessionId: request.sessionId, stage: request.key };
|
|
55
55
|
return {
|
|
56
56
|
sessionId: request.sessionId,
|
|
57
57
|
...(request.agent ? { agentId: request.agent.id } : {}),
|
|
58
|
-
capabilityId: request.capabilityId,
|
|
59
58
|
toolName: request.toolName,
|
|
60
59
|
};
|
|
61
60
|
}
|
|
@@ -64,11 +63,14 @@ export function httpToolRefOf(request) {
|
|
|
64
63
|
* the request is an outcome. Rejects only when `signal` aborts, with its reason.
|
|
65
64
|
*/
|
|
66
65
|
export async function callHttpTarget(target, input, options) {
|
|
66
|
+
const request = httpToolRequest(target, input);
|
|
67
|
+
if ("issue" in request)
|
|
68
|
+
return failed("tool.invalid-input", request.issue);
|
|
67
69
|
const timeoutMs = target.timeoutMs ?? HTTP_TOOL_DEFAULT_TIMEOUT_MS;
|
|
68
70
|
const timeout = AbortSignal.timeout(timeoutMs);
|
|
69
|
-
const result = await
|
|
71
|
+
const result = await send(request.url, request.body, { ...options.headers }, {
|
|
70
72
|
policy: options.policy,
|
|
71
|
-
method:
|
|
73
|
+
method: request.method,
|
|
72
74
|
signal: AbortSignal.any([options.signal, timeout]),
|
|
73
75
|
});
|
|
74
76
|
if (options.signal.aborted)
|
|
@@ -138,7 +140,6 @@ export async function runHttpTool(tenant, call, options) {
|
|
|
138
140
|
...("stage" in tool
|
|
139
141
|
? { stage: tool.stage }
|
|
140
142
|
: {
|
|
141
|
-
capability: tool.capabilityId,
|
|
142
143
|
tool: tool.toolName,
|
|
143
144
|
...(tool.agentId === undefined ? {} : { agent: tool.agentId }),
|
|
144
145
|
}),
|
|
@@ -154,7 +155,7 @@ export async function runHttpTool(tenant, call, options) {
|
|
|
154
155
|
const agentId = "stage" in tool ? undefined : tool.agentId;
|
|
155
156
|
const declared = "stage" in tool
|
|
156
157
|
? declaredFlowHttp(session.manifest, tool.stage)
|
|
157
|
-
: declaredHttpTool(session.manifest, agentId, tool.
|
|
158
|
+
: declaredHttpTool(session.manifest, agentId, tool.toolName);
|
|
158
159
|
if (!declared)
|
|
159
160
|
return failed("http.undeclared", "stage" in tool
|
|
160
161
|
? `'${tool.stage}' is not an HTTP stage of the flow`
|
|
@@ -174,7 +175,10 @@ export async function runHttpTool(tenant, call, options) {
|
|
|
174
175
|
return failed("http.credential", `The session's Environment holds no credential '${http.credential}' for ${authorized.url}`);
|
|
175
176
|
credential = authorized.headers;
|
|
176
177
|
identity = authorized.identity;
|
|
177
|
-
// A credential with `via` sends the call there, a gateway (R2b C2, Q9)
|
|
178
|
+
// A credential with `via` sends the call there, a gateway (R2b C2, Q9): to its one URL, so
|
|
179
|
+
// it has no place for the tool's URL parameters.
|
|
180
|
+
if (authorized.via !== undefined && httpPathParameters(http.url).length > 0)
|
|
181
|
+
return failed("http.credential", `The credential '${http.credential}' sends calls to its via URL, which cannot take the tool's {field} URL parameters`);
|
|
178
182
|
if (authorized.via !== undefined)
|
|
179
183
|
target = { ...http, url: authorized.via };
|
|
180
184
|
}
|
package/dist/gates/journal.js
CHANGED
|
@@ -28,7 +28,6 @@ export function journalCheck(vaults) {
|
|
|
28
28
|
const session = await t.get("sessions", request.sessionId);
|
|
29
29
|
const tool = mcpToolOf(session?.mcpSnapshot, request);
|
|
30
30
|
if (!tool ||
|
|
31
|
-
tool.capabilityId !== call.server.capabilityId ||
|
|
32
31
|
tool.serverName !== call.server.serverName ||
|
|
33
32
|
(tool.agentId ?? null) !== (call.server.agentId ?? null) ||
|
|
34
33
|
tool.serverToolName !== call.name)
|
|
@@ -13,13 +13,13 @@ export function createMcpHandler(options) {
|
|
|
13
13
|
const open = options.open ?? openMcpServer;
|
|
14
14
|
const live = new Map();
|
|
15
15
|
const opening = new Map();
|
|
16
|
-
const keyOf = (server) => JSON.stringify([server.sessionId, server.agentId ?? null, server.
|
|
16
|
+
const keyOf = (server) => JSON.stringify([server.sessionId, server.agentId ?? null, server.serverName]);
|
|
17
17
|
/** The declared remote server, from the session's pinned manifest. */
|
|
18
18
|
async function declared(vault, server) {
|
|
19
19
|
const session = await vault.session(server.sessionId);
|
|
20
20
|
if (!session)
|
|
21
21
|
throw new NotDeclared(`Session ${server.sessionId} not found`);
|
|
22
|
-
const found = findServer(session.manifest, server.agentId, server.
|
|
22
|
+
const found = findServer(session.manifest, server.agentId, server.serverName);
|
|
23
23
|
if (!found)
|
|
24
24
|
throw new NotDeclared(`MCP server '${server.serverName}' is not declared`);
|
|
25
25
|
return found.server;
|
|
@@ -174,7 +174,6 @@ export function createMcpHandler(options) {
|
|
|
174
174
|
function fields(server) {
|
|
175
175
|
return {
|
|
176
176
|
session: server.sessionId,
|
|
177
|
-
capability: server.capabilityId,
|
|
178
177
|
server: server.serverName,
|
|
179
178
|
...(server.agentId === undefined ? {} : { agent: server.agentId }),
|
|
180
179
|
};
|
package/dist/gates/policy.js
CHANGED
|
@@ -68,9 +68,7 @@ export function pinnedApproval(manifest, snapshot, request) {
|
|
|
68
68
|
const mcp = mcpToolOf(snapshot, request);
|
|
69
69
|
if (mcp)
|
|
70
70
|
return mcpToolSettingsIn(manifest, mcp).approval === "always";
|
|
71
|
-
|
|
72
|
-
return false;
|
|
73
|
-
return declaredHttpTool(manifest, request.agent?.id, request.capabilityId, request.toolName)?.approval === "always";
|
|
71
|
+
return declaredHttpTool(manifest, request.agent?.id, request.toolName)?.approval === "always";
|
|
74
72
|
}
|
|
75
73
|
function matches(rule, call) {
|
|
76
74
|
if (rule.kind !== undefined && rule.kind !== call.kind)
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* answers `{ok: false, error: {uncertain: true}}`, and the caller sends it again.
|
|
31
31
|
*
|
|
32
32
|
* HTTP tools (R2 M3). `POST /nylorun/v1/http-calls` `{tool, effectId, turnId, input}`: one call
|
|
33
|
-
* of an HTTP tool. The loop names the tool (`{sessionId?, agentId?,
|
|
33
|
+
* of an HTTP tool. The loop names the tool (`{sessionId?, agentId?, toolName}`), or a
|
|
34
34
|
* flow's HTTP stage or HTTP verifier by its stage key (`{sessionId?, stage}`); the gate finds its
|
|
35
35
|
* URL, method and credential in the session's pinned manifest
|
|
36
36
|
* (`gates/http-tool.ts`). A run token's turn is the call's; `turnId` counts only under core's
|
package/dist/harness/calls.d.ts
CHANGED
|
@@ -49,9 +49,9 @@ export declare function invokeModel(route: ModelRoute, request: HostEffect, sign
|
|
|
49
49
|
* so the next advance replays it.
|
|
50
50
|
*/
|
|
51
51
|
export declare function abortOn(signal: AbortSignal, kinds: readonly AbortReason[]): AbortSignal;
|
|
52
|
-
/** True when `request` calls `save_artifact
|
|
52
|
+
/** True when `request` calls `save_artifact`, which the Runtime pinned on `manifest`. */
|
|
53
53
|
export declare function isSaveArtifactCall(manifest: AgentManifest | undefined, request: HostEffect): boolean;
|
|
54
|
-
/** True when `request` calls `read_artifact` (R2b C11)
|
|
54
|
+
/** True when `request` calls `read_artifact` (R2b C11), which the Runtime pinned on `manifest`. */
|
|
55
55
|
export declare function isReadArtifactCall(manifest: AgentManifest | undefined, request: HostEffect): boolean;
|
|
56
56
|
/** True when `request` calls a tool of a declared MCP server (every one is remote). */
|
|
57
57
|
export declare function isRemoteMcpCall(routing: Pick<ToolRouting, "rootManifest" | "mcpSnapshot">, request: HostEffect): boolean;
|
package/dist/harness/calls.js
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
* Tenant's provider), the Tool Gate, the MCP pool, the SandboxManager. No store, no record: the
|
|
4
4
|
* session's routing comes with the run (`TurnStart.routing`).
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { READ_ARTIFACT_TOOL, SAVE_ARTIFACT_TOOL, } from "@nylorun/core/define";
|
|
7
7
|
import { runAbortKind } from "@nylorun/harness/api";
|
|
8
8
|
import { httpToolRefOf } from "../gates/http-tool.js";
|
|
9
9
|
import { classifyThrown } from "../model/classify.js";
|
|
10
10
|
import { findServer } from "../mcp/pool.js";
|
|
11
11
|
import { manifestFor, mcpToolOf } from "../mcp/snapshot.js";
|
|
12
|
-
import {
|
|
12
|
+
import { isPinnedRuntimeTool, sandboxToolSpec } from "../sandbox/runtime-tools.js";
|
|
13
13
|
/**
|
|
14
14
|
* Call the model for one effect. A provider failure comes back as a failure outcome
|
|
15
15
|
* (Model Calls §6), whichever provider serves the call; only an abort throws, and the
|
|
@@ -65,20 +65,18 @@ export function abortOn(signal, kinds) {
|
|
|
65
65
|
signal.addEventListener("abort", follow, { once: true });
|
|
66
66
|
return controller.signal;
|
|
67
67
|
}
|
|
68
|
-
/** True when `request` calls `save_artifact
|
|
68
|
+
/** True when `request` calls `save_artifact`, which the Runtime pinned on `manifest`. */
|
|
69
69
|
export function isSaveArtifactCall(manifest, request) {
|
|
70
70
|
return isArtifactsToolCall(manifest, request, SAVE_ARTIFACT_TOOL);
|
|
71
71
|
}
|
|
72
|
-
/** True when `request` calls `read_artifact` (R2b C11)
|
|
72
|
+
/** True when `request` calls `read_artifact` (R2b C11), which the Runtime pinned on `manifest`. */
|
|
73
73
|
export function isReadArtifactCall(manifest, request) {
|
|
74
74
|
return isArtifactsToolCall(manifest, request, READ_ARTIFACT_TOOL);
|
|
75
75
|
}
|
|
76
76
|
function isArtifactsToolCall(manifest, request, toolName) {
|
|
77
77
|
return (request.kind === "tool" &&
|
|
78
|
-
request.capabilityId === ARTIFACTS_CAPABILITY_ID &&
|
|
79
78
|
request.toolName === toolName &&
|
|
80
|
-
manifest
|
|
81
|
-
capability.tools?.some((tool) => tool.name === toolName) === true) === true);
|
|
79
|
+
isPinnedRuntimeTool(manifest, toolName));
|
|
82
80
|
}
|
|
83
81
|
/** True when `request` calls a tool of a declared MCP server (every one is remote). */
|
|
84
82
|
export function isRemoteMcpCall(routing, request) {
|
|
@@ -87,7 +85,7 @@ export function isRemoteMcpCall(routing, request) {
|
|
|
87
85
|
const tool = mcpToolOf(routing.mcpSnapshot, request);
|
|
88
86
|
if (!tool)
|
|
89
87
|
return false;
|
|
90
|
-
return findServer(routing.rootManifest, tool.agentId, tool.
|
|
88
|
+
return findServer(routing.rootManifest, tool.agentId, tool.serverName) !== undefined;
|
|
91
89
|
}
|
|
92
90
|
export async function callMcpTool(mcp, routing, request, signal,
|
|
93
91
|
/** The call's span context (R7.4), sent as `traceparent` where this process holds the connection. */
|
|
@@ -98,7 +96,6 @@ traceparent) {
|
|
|
98
96
|
return mcp.call({
|
|
99
97
|
sessionId: request.sessionId,
|
|
100
98
|
...(tool.agentId === undefined ? {} : { agentId: tool.agentId }),
|
|
101
|
-
capabilityId: tool.capabilityId,
|
|
102
99
|
serverName: tool.serverName,
|
|
103
100
|
serverToolName: tool.serverToolName,
|
|
104
101
|
args: request.input,
|
|
@@ -127,8 +124,8 @@ traceparent) {
|
|
|
127
124
|
}, signal);
|
|
128
125
|
}
|
|
129
126
|
export async function callSandboxTool(sandbox, routing, request, signal) {
|
|
130
|
-
const
|
|
131
|
-
if (!
|
|
127
|
+
const spec = sandboxToolSpec(manifestFor(routing.rootManifest, request.agent), request.toolName);
|
|
128
|
+
if (!spec)
|
|
132
129
|
throw new Error(`'${request.toolName ?? ""}' is not a sandbox tool`);
|
|
133
130
|
// Agents used as tools share the session's sandbox; the tree declares one sandbox spec.
|
|
134
131
|
return sandbox.run({
|
|
@@ -138,5 +135,5 @@ export async function callSandboxTool(sandbox, routing, request, signal) {
|
|
|
138
135
|
...(routing.environmentId === undefined ? {} : { environmentId: routing.environmentId }),
|
|
139
136
|
...(routing.environment === undefined ? {} : { environment: routing.environment }),
|
|
140
137
|
...(routing.claim === undefined ? {} : { claim: routing.claim }),
|
|
141
|
-
},
|
|
138
|
+
}, spec, request.toolName, request.input, signal);
|
|
142
139
|
}
|
package/dist/harness/main.js
CHANGED
|
@@ -40,6 +40,7 @@ export async function runHarness(stack, baseline) {
|
|
|
40
40
|
logger.info("host_stack_config", { services: [...stack.services], harness: config.url, gates: config.gatesUrl });
|
|
41
41
|
// A pod's engine trusts the network only once its NetworkPolicy blocks what it must.
|
|
42
42
|
let pod;
|
|
43
|
+
let prepare;
|
|
43
44
|
if (config.pod) {
|
|
44
45
|
const { waitedMs } = await awaitNetworkPolicy({
|
|
45
46
|
addresses: config.pod.blocked,
|
|
@@ -53,13 +54,16 @@ export async function runHarness(stack, baseline) {
|
|
|
53
54
|
// Join, then make the disk ready (R2d step 2) before taking any turn.
|
|
54
55
|
await pod.token();
|
|
55
56
|
const host = pod;
|
|
56
|
-
|
|
57
|
+
prepare = () => prepareDisk({
|
|
57
58
|
host,
|
|
58
59
|
exec: localExec({ env: baseline, proxyEnv: () => host.proxyEnv() }),
|
|
59
60
|
volumeFile: join(config.root, "volume-id"),
|
|
60
61
|
markerFile: join(config.root, "setup.json"),
|
|
61
62
|
logger,
|
|
62
63
|
});
|
|
64
|
+
// A fork onto other setup (Q17) runs it after connecting: core writes its disk through the connection.
|
|
65
|
+
if (!pod.afterSeed)
|
|
66
|
+
await prepare();
|
|
63
67
|
}
|
|
64
68
|
const token = pod ? () => pod.token() : config.token;
|
|
65
69
|
if (token === undefined)
|
|
@@ -79,6 +83,8 @@ export async function runHarness(stack, baseline) {
|
|
|
79
83
|
version: RUNTIME_VERSION,
|
|
80
84
|
onGrant: (grant) => runTokens.grant(grant),
|
|
81
85
|
});
|
|
86
|
+
if (pod?.afterSeed && prepare)
|
|
87
|
+
void prepare().catch((error) => logger.warn("environment_setup_failed", { message: error instanceof Error ? error.message : String(error) }));
|
|
82
88
|
const health = createServer((request, response) => {
|
|
83
89
|
if (request.url !== "/health") {
|
|
84
90
|
response.writeHead(404).end();
|
package/dist/harness/pod.d.ts
CHANGED
|
@@ -31,8 +31,13 @@ export interface PodHost {
|
|
|
31
31
|
readonly epoch: number | undefined;
|
|
32
32
|
/** The disk's setup from the last join, if its recipe has one. */
|
|
33
33
|
readonly setup: DiskSetup | undefined;
|
|
34
|
-
/** The last join's volume starts from a disk version core writes: no setup runs on it
|
|
34
|
+
/** The last join's volume starts from a disk version core writes: no setup runs on it… */
|
|
35
35
|
readonly seeded: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* …unless it is a fork onto other setup (Q17): the setup runs once core has written the
|
|
38
|
+
* version, which it does through this engine's connection, so the engine connects first.
|
|
39
|
+
*/
|
|
40
|
+
readonly afterSeed: boolean;
|
|
36
41
|
/** Whether to run the setup again after a failure, with the plan to run now. */
|
|
37
42
|
setupState(): Promise<{
|
|
38
43
|
retry: boolean;
|