@nylorun/runtime 0.26.0-beta → 0.28.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 +171 -0
- package/README.md +18 -14
- package/dist/adapters/execution/restate.d.ts +15 -18
- package/dist/adapters/execution/restate.js +31 -27
- package/dist/adapters/replication/pgoutput.d.ts +1 -1
- package/dist/adapters/replication/pgoutput.js +3 -2
- package/dist/adapters/sandbox/local.js +8 -5
- package/dist/adapters/sandbox/virtual.js +2 -2
- package/dist/adapters/streams/s2.d.ts +1 -1
- package/dist/adapters/streams/s2.js +18 -14
- package/dist/api/a2a/card.d.ts +4 -3
- package/dist/api/a2a/card.js +4 -4
- package/dist/api/a2a/endpoint.js +3 -4
- package/dist/api/a2a/protocol.js +4 -0
- package/dist/api/a2a/routes.d.ts +2 -6
- package/dist/api/a2a/routes.js +30 -25
- package/dist/api/ag-ui/endpoint.js +2 -3
- package/dist/api/ag-ui/routes.js +8 -11
- package/dist/api/components.d.ts +6789 -3405
- package/dist/api/components.js +43 -30
- package/dist/api/gate/keys.js +7 -4
- package/dist/api/gate/routes.d.ts +6 -2
- package/dist/api/gate/routes.js +15 -11
- package/dist/api/http/app.js +9 -4
- package/dist/api/http/body.d.ts +17 -3
- package/dist/api/http/body.js +35 -7
- package/dist/api/http/respond.js +6 -2
- package/dist/api/http/routes/access.js +5 -4
- package/dist/api/http/routes/agent-manifests.d.ts +10 -0
- package/dist/api/http/routes/agent-manifests.js +135 -0
- package/dist/api/http/routes/agents.d.ts +11 -0
- package/dist/api/http/routes/agents.js +418 -0
- package/dist/api/http/routes/artifacts.js +21 -9
- package/dist/api/http/routes/coverage.d.ts +4 -4
- package/dist/api/http/routes/coverage.js +13 -7
- package/dist/api/http/routes/files.d.ts +2 -1
- package/dist/api/http/routes/files.js +31 -5
- package/dist/api/http/routes/mcp.js +3 -3
- package/dist/api/http/routes/me.d.ts +1 -1
- package/dist/api/http/routes/me.js +1 -2
- package/dist/api/http/routes/memories.d.ts +9 -0
- package/dist/api/http/routes/memories.js +152 -0
- package/dist/api/http/routes/reads.d.ts +10 -111
- package/dist/api/http/routes/reads.js +51 -29
- package/dist/api/http/routes/sessions.js +26 -54
- package/dist/api/http/routes/tenant.d.ts +2 -2
- package/dist/api/http/routes/tenant.js +77 -24
- package/dist/api/http/routes/triggers.d.ts +10 -0
- package/dist/api/http/routes/triggers.js +180 -0
- package/dist/api/inbound-files.d.ts +3 -9
- package/dist/api/inbound-files.js +18 -24
- package/dist/api/openapi.js +73 -47
- package/dist/artifacts/disk.d.ts +10 -14
- package/dist/artifacts/disk.js +99 -95
- package/dist/artifacts/files.d.ts +13 -4
- package/dist/artifacts/files.js +27 -9
- package/dist/artifacts/folder-upload.d.ts +75 -0
- package/dist/artifacts/folder-upload.js +263 -0
- package/dist/artifacts/folders.d.ts +14 -8
- package/dist/artifacts/folders.js +19 -11
- package/dist/artifacts/links.js +7 -6
- package/dist/artifacts/parts.d.ts +9 -14
- package/dist/artifacts/parts.js +11 -9
- package/dist/artifacts/pipe.d.ts +15 -0
- package/dist/artifacts/pipe.js +71 -0
- package/dist/artifacts/quota.d.ts +36 -0
- package/dist/artifacts/quota.js +44 -0
- package/dist/artifacts/service.d.ts +25 -1
- package/dist/artifacts/service.js +72 -27
- package/dist/blob/fs.js +2 -1
- package/dist/blob/s3.d.ts +6 -2
- package/dist/blob/s3.js +9 -7
- package/dist/blob/types.d.ts +1 -1
- package/dist/blob/types.js +1 -1
- package/dist/core/flow-host.d.ts +0 -1
- package/dist/core/limits.d.ts +11 -35
- package/dist/core/limits.js +5 -73
- package/dist/core/sandbox-routes.js +0 -1
- package/dist/core/turn-manifest.d.ts +1 -2
- package/dist/core/turn-manifest.js +4 -28
- package/dist/execution/memory.d.ts +8 -5
- package/dist/execution/memory.js +8 -6
- package/dist/execution/types.d.ts +1 -6
- package/dist/execution/types.js +0 -5
- package/dist/gates/contract.d.ts +3 -2
- package/dist/gates/contract.js +1 -1
- package/dist/gates/egress-credentials.js +5 -4
- package/dist/gates/egress.d.ts +13 -13
- package/dist/gates/egress.js +19 -21
- package/dist/gates/fixture-model.js +13 -2
- package/dist/gates/handler.d.ts +1 -3
- package/dist/gates/handler.js +4 -3
- package/dist/gates/http-client.d.ts +4 -3
- package/dist/gates/http-client.js +5 -7
- package/dist/gates/http-tool.d.ts +24 -4
- package/dist/gates/http-tool.js +41 -13
- package/dist/gates/in-process.d.ts +2 -1
- package/dist/gates/in-process.js +17 -9
- package/dist/gates/inflight.d.ts +2 -2
- package/dist/gates/inflight.js +3 -2
- package/dist/gates/journal.js +5 -7
- package/dist/gates/mcp-handler.d.ts +4 -3
- package/dist/gates/mcp-handler.js +15 -5
- package/dist/gates/meter.js +6 -5
- package/dist/gates/model-gate.d.ts +11 -2
- package/dist/gates/policy.d.ts +34 -6
- package/dist/gates/policy.js +66 -18
- package/dist/gates/provider-gate.d.ts +22 -0
- package/dist/gates/provider-gate.js +30 -0
- package/dist/gates/tool-calls.d.ts +1 -3
- package/dist/gates/tool-calls.js +2 -3
- package/dist/gates/tool-client.d.ts +1 -1
- package/dist/gates/tool-client.js +5 -6
- package/dist/gates/tool-contract.d.ts +8 -5
- package/dist/gates/tool-contract.js +1 -1
- package/dist/gates/tool-gate.d.ts +18 -7
- package/dist/gates/tool-gate.js +49 -7
- package/dist/harness/calls.d.ts +27 -25
- package/dist/harness/calls.js +40 -46
- package/dist/harness/executors.d.ts +10 -3
- package/dist/harness/executors.js +53 -23
- package/dist/harness/main.js +2 -2
- package/dist/harness/pod.d.ts +2 -2
- package/dist/harness/pod.js +7 -6
- package/dist/harness/run-tokens.d.ts +4 -1
- package/dist/harness/run-tokens.js +6 -2
- package/dist/harness/service.d.ts +17 -14
- package/dist/harness/service.js +21 -35
- package/dist/harness/setup.d.ts +0 -2
- package/dist/harness/setup.js +21 -15
- package/dist/harness/ws-client.d.ts +2 -2
- package/dist/harness/ws-client.js +7 -5
- package/dist/harness/ws-port.d.ts +3 -4
- package/dist/harness/ws-port.js +3 -4
- package/dist/harness-api/artifacts.d.ts +20 -0
- package/dist/harness-api/artifacts.js +478 -0
- package/dist/harness-api/in-process.d.ts +2 -2
- package/dist/harness-api/in-process.js +18 -16
- package/dist/harness-api/record.d.ts +10 -9
- package/dist/harness-api/record.js +113 -157
- package/dist/harness-api/renew.d.ts +1 -1
- package/dist/harness-api/renew.js +2 -2
- package/dist/harness-api/server.d.ts +1 -16
- package/dist/harness-api/server.js +86 -73
- package/dist/harness-api/start.d.ts +27 -5
- package/dist/harness-api/start.js +102 -19
- package/dist/harness-api/tool-journal.d.ts +15 -10
- package/dist/harness-api/tool-journal.js +26 -24
- package/dist/harness-api/workspace.d.ts +0 -4
- package/dist/harness-api/workspace.js +4 -47
- package/dist/harness-api/ws-server.js +10 -10
- package/dist/host/create-host.js +2 -3
- package/dist/host/execution.js +2 -3
- package/dist/host/gates.d.ts +2 -9
- package/dist/host/gates.js +15 -12
- package/dist/host/http.d.ts +2 -4
- package/dist/host/http.js +5 -29
- package/dist/host/main.js +10 -3
- package/dist/host/operate.js +12 -3
- package/dist/host/stack-config.d.ts +11 -0
- package/dist/host/stack-config.js +21 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/infra/index.js +2 -1
- package/dist/infra/readiness.d.ts +0 -2
- package/dist/infra/readiness.js +4 -3
- package/dist/keys/client.d.ts +1 -0
- package/dist/keys/client.js +2 -3
- package/dist/keys/contract.d.ts +0 -2
- package/dist/keys/contract.js +0 -2
- package/dist/keys/egress-ca.d.ts +0 -2
- package/dist/keys/egress-ca.js +2 -3
- package/dist/keys/keys.d.ts +15 -4
- package/dist/keys/keys.js +22 -1
- package/dist/keys/webhooks.d.ts +25 -0
- package/dist/keys/webhooks.js +62 -0
- package/dist/keys/x509.d.ts +8 -4
- package/dist/keys/x509.js +14 -5
- package/dist/management-openapi.json +3043 -1954
- package/dist/mcp/connect.d.ts +35 -26
- package/dist/mcp/connect.js +112 -141
- package/dist/mcp/pool.d.ts +27 -22
- package/dist/mcp/pool.js +34 -48
- package/dist/mcp/preview.d.ts +12 -5
- package/dist/mcp/preview.js +43 -26
- package/dist/mcp/snapshot.d.ts +35 -86
- package/dist/mcp/snapshot.js +93 -135
- package/dist/memory/service.d.ts +97 -0
- package/dist/memory/service.js +335 -0
- package/dist/memory/tool.d.ts +36 -0
- package/dist/memory/tool.js +84 -0
- package/dist/model/auth-store.js +5 -3
- package/dist/model/capabilities.d.ts +1 -1
- package/dist/model/catalog.d.ts +9 -36
- package/dist/model/catalog.js +6 -33
- package/dist/model/descriptor.d.ts +51 -0
- package/dist/model/descriptor.js +83 -0
- package/dist/model/models.d.ts +0 -3
- package/dist/model/models.js +3 -5
- package/dist/model/native-blocks.js +1 -1
- package/dist/model/pi-model.d.ts +16 -5
- package/dist/model/pi-model.js +86 -105
- package/dist/openapi.json +13653 -9986
- package/dist/reads/cursor.js +3 -1
- package/dist/reads/types.d.ts +3 -3
- package/dist/record/agent.d.ts +27 -0
- package/dist/record/agent.js +46 -0
- package/dist/record/append.d.ts +34 -2
- package/dist/record/append.js +50 -1
- package/dist/record/index.d.ts +2 -2
- package/dist/record/index.js +2 -2
- package/dist/redact.d.ts +1 -1
- package/dist/redact.js +3 -2
- package/dist/sandbox/egress-token.d.ts +3 -3
- package/dist/sandbox/egress-token.js +3 -3
- package/dist/sandbox/join.js +8 -7
- package/dist/sandbox/manager.d.ts +41 -41
- package/dist/sandbox/manager.js +112 -44
- package/dist/sandbox/placement.d.ts +2 -2
- package/dist/sandbox/placement.js +7 -10
- package/dist/sandbox/pods/client.d.ts +1 -10
- package/dist/sandbox/pods/client.js +8 -4
- package/dist/sandbox/pods/lifecycle.d.ts +25 -17
- package/dist/sandbox/pods/lifecycle.js +51 -29
- package/dist/sandbox/pods/network-gate.d.ts +4 -5
- package/dist/sandbox/pods/network-gate.js +7 -7
- package/dist/sandbox/pods/reconcile.js +14 -3
- package/dist/sandbox/pods/spec.js +8 -5
- package/dist/sandbox/policy.d.ts +7 -1
- package/dist/sandbox/policy.js +10 -3
- package/dist/sandbox/resolve.d.ts +11 -6
- package/dist/sandbox/resolve.js +19 -9
- package/dist/sandbox/setup.d.ts +6 -2
- package/dist/sandbox/setup.js +47 -14
- package/dist/sandbox/share.d.ts +5 -14
- package/dist/sandbox/share.js +5 -34
- package/dist/sandbox/skills.d.ts +7 -13
- package/dist/sandbox/skills.js +16 -31
- package/dist/sandbox/tenant-config.d.ts +2 -2
- package/dist/sandbox/tenant-config.js +4 -3
- package/dist/sandbox/tools.d.ts +7 -1
- package/dist/sandbox/tools.js +26 -26
- package/dist/sandbox/types.d.ts +10 -3
- package/dist/sandbox/types.js +9 -0
- package/dist/store/postgres/connect.d.ts +6 -5
- package/dist/store/postgres/connect.js +14 -11
- package/dist/store/postgres/control.js +13 -13
- package/dist/store/postgres/drizzle/0028_artifact_write_keys.sql +2 -0
- package/dist/store/postgres/drizzle/0029_protocol_15.sql +11 -0
- package/dist/store/postgres/drizzle/0030_protocol_15_runtime_block.sql +11 -0
- package/dist/store/postgres/drizzle/0031_protocol_15_mcp_snapshot.sql +8 -0
- package/dist/store/postgres/drizzle/0032_agents.sql +54 -0
- package/dist/store/postgres/drizzle/0033_agent_versions.sql +7 -0
- package/dist/store/postgres/drizzle/0034_protocol_17_session_queue.sql +13 -0
- package/dist/store/postgres/drizzle/0035_t4_triggers.sql +41 -0
- package/dist/store/postgres/drizzle/0036_t5_memories.sql +30 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +63 -0
- package/dist/store/postgres/reads.js +17 -82
- package/dist/store/postgres/record-writer.d.ts +4 -4
- package/dist/store/postgres/record-writer.js +9 -9
- package/dist/store/postgres/schema.d.ts +1382 -151
- package/dist/store/postgres/schema.js +191 -37
- package/dist/store/postgres/store.js +384 -105
- package/dist/store/postgres/tenant.js +1 -2
- package/dist/store/types.d.ts +172 -57
- package/dist/store/types.js +0 -1
- package/dist/streams/relay/core.d.ts +3 -3
- package/dist/streams/relay/core.js +9 -10
- package/dist/tenant/advance.js +41 -39
- package/dist/tenant/agent-manifests.d.ts +72 -0
- package/dist/tenant/agent-manifests.js +359 -0
- package/dist/tenant/agent-versions.d.ts +126 -0
- package/dist/tenant/agent-versions.js +389 -0
- package/dist/tenant/agents.d.ts +230 -0
- package/dist/tenant/{environments.js → agents.js} +493 -328
- package/dist/tenant/auth.d.ts +1 -1
- package/dist/tenant/auth.js +3 -3
- package/dist/tenant/commands.d.ts +63 -2
- package/dist/tenant/commands.js +240 -90
- package/dist/tenant/context.d.ts +57 -33
- package/dist/tenant/context.js +6 -2
- package/dist/tenant/definition-files.d.ts +25 -4
- package/dist/tenant/definition-files.js +85 -47
- package/dist/tenant/effects.js +15 -51
- package/dist/tenant/ephemeral.js +2 -1
- package/dist/tenant/history.d.ts +1 -1
- package/dist/tenant/history.js +1 -1
- package/dist/tenant/host-token.d.ts +3 -3
- package/dist/tenant/host-token.js +11 -10
- package/dist/tenant/http.d.ts +5 -8
- package/dist/tenant/http.js +9 -0
- package/dist/tenant/identity-file.d.ts +29 -6
- package/dist/tenant/identity-file.js +45 -15
- package/dist/tenant/issuers.d.ts +5 -5
- package/dist/tenant/issuers.js +34 -26
- package/dist/tenant/jwt.d.ts +8 -2
- package/dist/tenant/jwt.js +10 -2
- package/dist/tenant/limits.d.ts +87 -0
- package/dist/tenant/limits.js +172 -0
- package/dist/tenant/memories.d.ts +38 -0
- package/dist/tenant/memories.js +119 -0
- package/dist/tenant/module.d.ts +4 -1
- package/dist/tenant/module.js +9 -8
- package/dist/tenant/outbound.d.ts +11 -14
- package/dist/tenant/outbound.js +10 -15
- package/dist/tenant/reset.js +2 -2
- package/dist/tenant/run-grants.d.ts +6 -0
- package/dist/tenant/run-grants.js +7 -2
- package/dist/tenant/run-token.d.ts +0 -4
- package/dist/tenant/run-token.js +6 -11
- package/dist/tenant/runtime-limits.d.ts +24 -0
- package/dist/tenant/runtime-limits.js +27 -0
- package/dist/tenant/runtime.d.ts +1 -1
- package/dist/tenant/runtime.js +38 -43
- package/dist/tenant/session-streams.js +7 -6
- package/dist/tenant/session.d.ts +1 -1
- package/dist/tenant/session.js +1 -1
- package/dist/tenant/sessions.d.ts +28 -45
- package/dist/tenant/sessions.js +89 -209
- package/dist/tenant/signing-keys.d.ts +1 -1
- package/dist/tenant/signing-keys.js +5 -6
- package/dist/tenant/status.js +10 -10
- package/dist/tenant/streams.js +18 -20
- package/dist/tenant/sweep.d.ts +1 -21
- package/dist/tenant/sweep.js +51 -51
- package/dist/tenant/transcript.d.ts +70 -23
- package/dist/tenant/transcript.js +28 -22
- package/dist/tenant/triggers.d.ts +143 -0
- package/dist/tenant/triggers.js +715 -0
- package/dist/tenant/turn-timing.d.ts +11 -1
- package/dist/tenant/turn-timing.js +3 -1
- package/dist/tenant/types.d.ts +9 -14
- package/dist/tenant/worker.d.ts +4 -10
- package/dist/tenant/worker.js +10 -14
- package/dist/traces/ids.js +5 -1
- package/dist/traces/map.d.ts +10 -7
- package/dist/traces/map.js +16 -12
- package/dist/traces/otlp.d.ts +3 -3
- package/dist/traces/otlp.js +5 -3
- package/dist/traces/projector.d.ts +9 -4
- package/dist/traces/projector.js +13 -7
- package/dist/traces/read.js +6 -1
- package/dist/tuning.d.ts +390 -0
- package/dist/tuning.js +677 -0
- package/dist/vault/headers.js +4 -2
- package/dist/vault/service.d.ts +13 -8
- package/dist/vault/service.js +61 -17
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +13 -12
- package/dist/api/http/routes/environment-manifests.d.ts +0 -9
- package/dist/api/http/routes/environment-manifests.js +0 -118
- package/dist/api/http/routes/environments.d.ts +0 -11
- package/dist/api/http/routes/environments.js +0 -332
- package/dist/artifacts/export.d.ts +0 -8
- package/dist/artifacts/export.js +0 -204
- package/dist/artifacts/media-types.d.ts +0 -30
- package/dist/artifacts/media-types.js +0 -137
- package/dist/artifacts/workspace.d.ts +0 -60
- package/dist/artifacts/workspace.js +0 -45
- package/dist/media.d.ts +0 -10
- package/dist/media.js +0 -58
- package/dist/record/environment.d.ts +0 -25
- package/dist/record/environment.js +0 -44
- package/dist/sandbox/runtime-tools.d.ts +0 -9
- package/dist/sandbox/runtime-tools.js +0 -16
- package/dist/sandbox/session-sandbox.d.ts +0 -16
- package/dist/sandbox/session-sandbox.js +0 -77
- package/dist/tenant/artifact-tool.d.ts +0 -13
- package/dist/tenant/artifact-tool.js +0 -243
- package/dist/tenant/environment-manifests.d.ts +0 -57
- package/dist/tenant/environment-manifests.js +0 -200
- package/dist/tenant/environments.d.ts +0 -174
- package/dist/tenant/skill-tool.d.ts +0 -20
- package/dist/tenant/skill-tool.js +0 -67
- package/dist/tenant/tool-results.d.ts +0 -57
- package/dist/tenant/tool-results.js +0 -298
- package/dist/tenant/tool-search.d.ts +0 -39
- package/dist/tenant/tool-search.js +0 -103
|
@@ -1,22 +1,41 @@
|
|
|
1
|
+
import { hostTuning } from "../../tuning.js";
|
|
2
|
+
import { DEFAULT_SANDBOX_IMAGE } from "../policy.js";
|
|
1
3
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
4
|
+
* The repository an image reference names, as a container runtime writes it: the tag and digest
|
|
5
|
+
* dropped, Docker Hub's host and `library/` filled in (`python:3.13-slim` is
|
|
6
|
+
* `docker.io/library/python`).
|
|
4
7
|
*/
|
|
5
|
-
export function
|
|
8
|
+
export function repositoryOf(reference) {
|
|
9
|
+
let name = reference.replace(/^docker-pullable:\/\//, "");
|
|
10
|
+
const at = name.indexOf("@");
|
|
11
|
+
if (at >= 0)
|
|
12
|
+
name = name.slice(0, at);
|
|
13
|
+
const colon = name.lastIndexOf(":");
|
|
14
|
+
if (colon > name.lastIndexOf("/"))
|
|
15
|
+
name = name.slice(0, colon);
|
|
16
|
+
const parts = name.split("/");
|
|
17
|
+
const first = parts[0] ?? "";
|
|
18
|
+
const hasHost = parts.length > 1 && (first.includes(".") || first.includes(":") || first === "localhost");
|
|
19
|
+
if (hasHost)
|
|
20
|
+
return name;
|
|
21
|
+
return parts.length === 1 ? `docker.io/library/${name}` : `docker.io/${name}`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The pullable reference of the image id a container status names (`repo@sha256:…`), when it is
|
|
25
|
+
* a digest of `requested`'s repository; undefined for an id without a repository, or with one
|
|
26
|
+
* the node made up for an image loaded onto it, which no registry serves.
|
|
27
|
+
*/
|
|
28
|
+
export function imageDigestOf(imageID, requested) {
|
|
6
29
|
const id = imageID?.replace(/^docker-pullable:\/\//, "");
|
|
7
30
|
if (!id)
|
|
8
31
|
return undefined;
|
|
9
32
|
const at = id.indexOf("@sha256:");
|
|
10
|
-
|
|
33
|
+
if (at <= 0 || !/^[0-9a-f]{64}$/.test(id.slice(at + 8)))
|
|
34
|
+
return undefined;
|
|
35
|
+
return repositoryOf(id) === repositoryOf(requested) ? id : undefined;
|
|
11
36
|
}
|
|
12
37
|
/** A setup failure's `reason` starts with this (`sandbox/setup.ts`); it is sticky. */
|
|
13
38
|
export const SETUP_FAILED = "setup:";
|
|
14
|
-
/** A pod not ready this long after it was asked to run has failed. */
|
|
15
|
-
export const READY_DEADLINE_MS = 5 * 60_000;
|
|
16
|
-
/** How soon to look again while the pod changes. */
|
|
17
|
-
export const POLL_MS = 2_000;
|
|
18
|
-
/** How soon to look again when the sandboxes service does not answer. */
|
|
19
|
-
export const UNREACHABLE_RETRY_MS = 5_000;
|
|
20
39
|
/** The service's operation id of the row's current spec. */
|
|
21
40
|
export function opIdOf(pod) {
|
|
22
41
|
return `${pod.k8sName}.r${pod.rev}`;
|
|
@@ -28,6 +47,9 @@ function gone(status) {
|
|
|
28
47
|
const event = (type, payload) => ({ type, payload });
|
|
29
48
|
export function decide(facts) {
|
|
30
49
|
const { row, status, now } = facts;
|
|
50
|
+
// A pod not ready `readyDeadlineMs` after it was asked to run has failed; look again after
|
|
51
|
+
// `pollMs` while it changes, or `unreachableRetryMs` when the sandboxes service does not answer.
|
|
52
|
+
const { readyDeadlineMs, pollMs, unreachableRetryMs } = hostTuning().pods;
|
|
31
53
|
const pod = row.pod;
|
|
32
54
|
const patch = {};
|
|
33
55
|
const events = [];
|
|
@@ -49,17 +71,17 @@ export function decide(facts) {
|
|
|
49
71
|
if (pod.retiring !== undefined) {
|
|
50
72
|
const retiring = facts.retiring;
|
|
51
73
|
if (retiring === undefined)
|
|
52
|
-
again(
|
|
74
|
+
again(unreachableRetryMs);
|
|
53
75
|
else if (gone(retiring))
|
|
54
76
|
patch.retiring = null;
|
|
55
77
|
else {
|
|
56
78
|
if (retiring.exists && !retiring.deleting)
|
|
57
79
|
deletes.push(pod.retiring);
|
|
58
|
-
again(
|
|
80
|
+
again(pollMs);
|
|
59
81
|
}
|
|
60
82
|
}
|
|
61
83
|
if (status === undefined) {
|
|
62
|
-
again(
|
|
84
|
+
again(unreachableRetryMs);
|
|
63
85
|
return done();
|
|
64
86
|
}
|
|
65
87
|
if (pod.desired === "deleted") {
|
|
@@ -69,7 +91,7 @@ export function decide(facts) {
|
|
|
69
91
|
deletes.push(pod.k8sName);
|
|
70
92
|
if (pod.observed !== "deleting")
|
|
71
93
|
patch.observed = "deleting";
|
|
72
|
-
again(
|
|
94
|
+
again(pollMs);
|
|
73
95
|
return done();
|
|
74
96
|
}
|
|
75
97
|
// Lost is terminal: only a reset (a new name) leaves it.
|
|
@@ -77,7 +99,7 @@ export function decide(facts) {
|
|
|
77
99
|
if (status.exists && !status.deleting)
|
|
78
100
|
deletes.push(pod.k8sName);
|
|
79
101
|
if (status.exists)
|
|
80
|
-
again(
|
|
102
|
+
again(pollMs);
|
|
81
103
|
return done();
|
|
82
104
|
}
|
|
83
105
|
let desired = pod.desired;
|
|
@@ -91,7 +113,7 @@ export function decide(facts) {
|
|
|
91
113
|
patch.desired = desired;
|
|
92
114
|
patch.rev = rev;
|
|
93
115
|
patch.hostEpoch = pod.hostEpoch + 1;
|
|
94
|
-
events.push(event("
|
|
116
|
+
events.push(event("agent.suspended", { reason: "idle" }));
|
|
95
117
|
}
|
|
96
118
|
else
|
|
97
119
|
arm.push({ timer: "idle", at: due });
|
|
@@ -99,23 +121,23 @@ export function decide(facts) {
|
|
|
99
121
|
// An incarnation a pod has joined, whose Sandbox or volume is gone: lost.
|
|
100
122
|
if (pod.podUid !== undefined && (!status.exists || status.deleting || status.volume === "missing")) {
|
|
101
123
|
const reason = !status.exists
|
|
102
|
-
? "The
|
|
124
|
+
? "The Agent's pod and volume were deleted outside the Runtime"
|
|
103
125
|
: status.deleting
|
|
104
|
-
? "The
|
|
105
|
-
: "The
|
|
126
|
+
? "The Agent's pod is being deleted outside the Runtime"
|
|
127
|
+
: "The Agent's volume is gone";
|
|
106
128
|
patch.observed = "lost";
|
|
107
129
|
patch.reason = reason;
|
|
108
130
|
patch.hostEpoch = (patch.hostEpoch ?? pod.hostEpoch) + 1;
|
|
109
|
-
events.push(event("
|
|
131
|
+
events.push(event("agent.lost", { reason }));
|
|
110
132
|
if (status.exists && !status.deleting)
|
|
111
133
|
deletes.push(pod.k8sName);
|
|
112
134
|
if (status.exists)
|
|
113
|
-
again(
|
|
135
|
+
again(pollMs);
|
|
114
136
|
return done();
|
|
115
137
|
}
|
|
116
138
|
if (status.deleting) {
|
|
117
139
|
// The previous incarnation of this name is still going (an Environment deleted and created again).
|
|
118
|
-
again(
|
|
140
|
+
again(pollMs);
|
|
119
141
|
return done();
|
|
120
142
|
}
|
|
121
143
|
const opId = opIdOf({ k8sName: pod.k8sName, rev });
|
|
@@ -133,7 +155,7 @@ export function decide(facts) {
|
|
|
133
155
|
// A pod started by this apply (a create, a resume) is the Environment's first, not a relaunch.
|
|
134
156
|
if (desired === "running" && pod.podUid !== undefined)
|
|
135
157
|
patch.podUid = null;
|
|
136
|
-
again(
|
|
158
|
+
again(pollMs);
|
|
137
159
|
return done({
|
|
138
160
|
apply: { mode: desired === "running" ? "Running" : "Suspended", rotateJoin: !status.exists },
|
|
139
161
|
});
|
|
@@ -149,7 +171,7 @@ export function decide(facts) {
|
|
|
149
171
|
patch.reason = null;
|
|
150
172
|
}
|
|
151
173
|
// The image it runs, pinned by digest from now on (Q14).
|
|
152
|
-
const digest = pod.imageDigest === undefined ? imageDigestOf(status.imageID) : undefined;
|
|
174
|
+
const digest = pod.imageDigest === undefined ? imageDigestOf(status.imageID, facts.image) : undefined;
|
|
153
175
|
if (digest !== undefined)
|
|
154
176
|
patch.imageDigest = digest;
|
|
155
177
|
return done();
|
|
@@ -157,17 +179,17 @@ export function decide(facts) {
|
|
|
157
179
|
const startedAt = Date.parse(pod.startedAt ?? row.updatedAt);
|
|
158
180
|
if (pod.observed === "failed")
|
|
159
181
|
return done();
|
|
160
|
-
if (now - startedAt >=
|
|
182
|
+
if (now - startedAt >= readyDeadlineMs) {
|
|
161
183
|
const reason = [status.reason, status.message].filter(Boolean).join(": ") ||
|
|
162
|
-
`The pod was not ready within ${
|
|
184
|
+
`The pod was not ready within ${Math.round(readyDeadlineMs / 1000)} s`;
|
|
163
185
|
patch.observed = "failed";
|
|
164
186
|
patch.reason = reason;
|
|
165
|
-
events.push(event("
|
|
187
|
+
events.push(event("agent.failed", { reason }));
|
|
166
188
|
return done();
|
|
167
189
|
}
|
|
168
190
|
if (pod.observed !== "creating")
|
|
169
191
|
patch.observed = "creating";
|
|
170
|
-
again(
|
|
192
|
+
again(pollMs);
|
|
171
193
|
return done();
|
|
172
194
|
}
|
|
173
195
|
// Suspended, applied: suspended once its pod is gone.
|
|
@@ -176,6 +198,6 @@ export function decide(facts) {
|
|
|
176
198
|
patch.observed = "suspended";
|
|
177
199
|
return done();
|
|
178
200
|
}
|
|
179
|
-
again(
|
|
201
|
+
again(pollMs);
|
|
180
202
|
return done();
|
|
181
203
|
}
|
|
@@ -2,20 +2,19 @@ export interface BlockedAddress {
|
|
|
2
2
|
readonly host: string;
|
|
3
3
|
readonly port: number;
|
|
4
4
|
}
|
|
5
|
-
/** Consecutive failed connections that prove an address blocked. */
|
|
6
|
-
export declare const CONFIRMATIONS = 3;
|
|
7
5
|
/** The addresses a pod's NetworkPolicy must block: the API server's service address. */
|
|
8
6
|
export declare function blockedAddresses(env: Readonly<Record<string, string | undefined>>): BlockedAddress[];
|
|
9
7
|
/** Whether a TCP connection to `address` opens within `timeoutMs`. */
|
|
10
8
|
export declare function opens(address: BlockedAddress, timeoutMs: number): Promise<boolean>;
|
|
11
9
|
export interface NetworkGateOptions {
|
|
12
10
|
readonly addresses: readonly BlockedAddress[];
|
|
13
|
-
/** Give up after this long. Default 120 s. */
|
|
11
|
+
/** Give up after this long. Default `harness.networkPolicyTimeoutMs` (Host tuning, 120 s). */
|
|
14
12
|
readonly timeoutMs?: number;
|
|
15
|
-
/** One connection attempt. Default 1 s. */
|
|
13
|
+
/** One connection attempt. Default `harness.networkProbeMs` (1 s). */
|
|
16
14
|
readonly attemptMs?: number;
|
|
17
|
-
/** Between attempts after a connection opened. Default 250 ms. */
|
|
15
|
+
/** Between attempts after a connection opened. Default `harness.networkPauseMs` (250 ms). */
|
|
18
16
|
readonly pauseMs?: number;
|
|
17
|
+
/** Consecutive failed connections that prove an address blocked. Default `harness.networkConfirmations` (3). */
|
|
19
18
|
readonly confirmations?: number;
|
|
20
19
|
readonly log?: (message: string, fields: Record<string, unknown>) => void;
|
|
21
20
|
}
|
|
@@ -4,12 +4,11 @@
|
|
|
4
4
|
* (`10.96.0.1:443`) for 0.5 to 5 s after it started (8a). So before the engine joins, or runs
|
|
5
5
|
* anything, it connects to addresses the policy must block (the API server's service address,
|
|
6
6
|
* `KUBERNETES_SERVICE_HOST:KUBERNETES_SERVICE_PORT`, which the kubelet always sets) until each
|
|
7
|
-
* fails `
|
|
7
|
+
* fails `harness.networkConfirmations` times in a row (Host tuning) (refused, unreachable or timed out). A connection that
|
|
8
8
|
* succeeds starts the count again. Past `timeoutMs` the engine exits, and the pod restarts it.
|
|
9
9
|
*/
|
|
10
10
|
import { connect } from "node:net";
|
|
11
|
-
|
|
12
|
-
export const CONFIRMATIONS = 3;
|
|
11
|
+
import { hostTuning } from "../../tuning.js";
|
|
13
12
|
/** The addresses a pod's NetworkPolicy must block: the API server's service address. */
|
|
14
13
|
export function blockedAddresses(env) {
|
|
15
14
|
const override = env.NYLORUN_SANDBOX_BLOCKED_PROBE;
|
|
@@ -45,19 +44,20 @@ export function opens(address, timeoutMs) {
|
|
|
45
44
|
}
|
|
46
45
|
/** Resolves once every address is blocked; rejects past the timeout. */
|
|
47
46
|
export async function awaitNetworkPolicy(options) {
|
|
47
|
+
const tuning = hostTuning().harness;
|
|
48
48
|
const started = Date.now();
|
|
49
|
-
const deadline = started + (options.timeoutMs ??
|
|
50
|
-
const needed = options.confirmations ??
|
|
49
|
+
const deadline = started + (options.timeoutMs ?? tuning.networkPolicyTimeoutMs);
|
|
50
|
+
const needed = options.confirmations ?? tuning.networkConfirmations;
|
|
51
51
|
for (const address of options.addresses) {
|
|
52
52
|
let failures = 0;
|
|
53
53
|
let opened = 0;
|
|
54
54
|
while (failures < needed) {
|
|
55
55
|
if (Date.now() > deadline)
|
|
56
56
|
throw new Error(`The pod's NetworkPolicy is not in force: ${address.host}:${address.port} still answered ${opened} time(s)`);
|
|
57
|
-
if (await opens(address, options.attemptMs ??
|
|
57
|
+
if (await opens(address, options.attemptMs ?? tuning.networkProbeMs)) {
|
|
58
58
|
failures = 0;
|
|
59
59
|
opened += 1;
|
|
60
|
-
await new Promise((resolve) => setTimeout(resolve, options.pauseMs ??
|
|
60
|
+
await new Promise((resolve) => setTimeout(resolve, options.pauseMs ?? tuning.networkPauseMs));
|
|
61
61
|
}
|
|
62
62
|
else
|
|
63
63
|
failures += 1;
|
|
@@ -15,12 +15,15 @@
|
|
|
15
15
|
* infrastructure errors throw; Restate retries those.
|
|
16
16
|
*/
|
|
17
17
|
import { randomBytes } from "node:crypto";
|
|
18
|
+
import { DEFAULT_SANDBOX_IMAGE } from "../policy.js";
|
|
18
19
|
import { effectiveSandboxConfig, readSandboxConfig } from "../tenant-config.js";
|
|
19
20
|
import { podSandboxSpecOf } from "../resolve.js";
|
|
20
21
|
import { sha256Hex } from "../join.js";
|
|
21
22
|
import { recoverLostVolume } from "../../artifacts/disk.js";
|
|
22
23
|
import { decide } from "./lifecycle.js";
|
|
23
24
|
import { podLifecycleConfig, podSpecOf } from "./spec.js";
|
|
25
|
+
import { hostTuning } from "../../tuning.js";
|
|
26
|
+
import { idleMsOf, revertStart } from "../../tenant/agent-versions.js";
|
|
24
27
|
export async function reconcileSandbox(ctx, sandboxId, trigger) {
|
|
25
28
|
const pods = ctx.pods;
|
|
26
29
|
if (!pods || ctx.closed)
|
|
@@ -49,9 +52,11 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
|
|
|
49
52
|
return undefined;
|
|
50
53
|
}
|
|
51
54
|
};
|
|
52
|
-
|
|
55
|
+
// While a start would apply a new version (track T3), it idles at most `tenant.upgradeIdleMs`.
|
|
56
|
+
const lifecycle = { ...podLifecycleConfig(config, row.recipe), idleMs: idleMsOf(config, row) };
|
|
53
57
|
const decision = decide({
|
|
54
58
|
row,
|
|
59
|
+
image: row.applied.image ?? DEFAULT_SANDBOX_IMAGE,
|
|
55
60
|
config: lifecycle,
|
|
56
61
|
status: await status(row.pod.k8sName),
|
|
57
62
|
...(row.pod.retiring ? { retiring: await status(row.pod.retiring) } : {}),
|
|
@@ -116,7 +121,7 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
|
|
|
116
121
|
message: error instanceof Error ? error.message : String(error),
|
|
117
122
|
});
|
|
118
123
|
// Nothing of the decision is written: the next reconcile decides again.
|
|
119
|
-
return { retryAfterMs: decision.retryAfterMs ??
|
|
124
|
+
return { retryAfterMs: decision.retryAfterMs ?? hostTuning().pods.reconcileRetryMs };
|
|
120
125
|
}
|
|
121
126
|
}
|
|
122
127
|
const written = await ctx.store.tx(async (t) => {
|
|
@@ -137,9 +142,15 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
|
|
|
137
142
|
if (Object.keys(finalPatch).length > 0)
|
|
138
143
|
await t.updateEnvironmentPod(sandboxId, finalPatch, new Date().toISOString());
|
|
139
144
|
for (const item of decision.events)
|
|
140
|
-
await t.
|
|
145
|
+
await t.agentEvent(sandboxId, item.type, item.payload);
|
|
141
146
|
if (epochMoved)
|
|
142
147
|
await t.signal({ type: "host.revoked", sandboxId, epoch: finalPatch.hostEpoch });
|
|
148
|
+
// A pod that failed to start on a new version goes back to the version it ran (track T3).
|
|
149
|
+
if (finalPatch.observed === "failed" && current.startCheck) {
|
|
150
|
+
const failed = (await t.environment(sandboxId, { lock: true }));
|
|
151
|
+
await revertStart(t, failed, finalPatch.reason ?? "The pod failed", new Date().toISOString());
|
|
152
|
+
return { kind: "changed" };
|
|
153
|
+
}
|
|
143
154
|
return { kind: "written" };
|
|
144
155
|
});
|
|
145
156
|
if (written.kind === "changed")
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { parseSandboxDuration, parseSandboxSize } from "@nylorun/core/define";
|
|
7
7
|
import { DEFAULT_HARNESS } from "@nylorun/core/contracts";
|
|
8
|
-
import { memoryMiBOf } from "../policy.js";
|
|
8
|
+
import { idleMsOf, memoryMiBOf } from "../policy.js";
|
|
9
|
+
import { podTuningEnv } from "../../tuning.js";
|
|
9
10
|
import { opIdOf } from "./lifecycle.js";
|
|
10
11
|
/** The pod's grace period to stop when the Tenant sets none (D34). */
|
|
11
12
|
export const DEFAULT_STOP_GRACE_SECONDS = 10;
|
|
@@ -19,13 +20,13 @@ export const POD_HARNESS_ROOT = "/harness";
|
|
|
19
20
|
*/
|
|
20
21
|
export function podLifecycleConfig(config, recipe) {
|
|
21
22
|
const stopGrace = config.lifecycle?.stopGrace;
|
|
22
|
-
const tenantIdle =
|
|
23
|
+
const tenantIdle = idleMsOf({ ...(config.limits?.idle === undefined ? {} : { idle: config.limits.idle }) });
|
|
23
24
|
const own = recipe?.lifecycle.idle === undefined ? undefined : parseSandboxDuration(recipe.lifecycle.idle);
|
|
25
|
+
const graceMs = stopGrace === undefined ? undefined : parseSandboxDuration(stopGrace);
|
|
24
26
|
return {
|
|
25
27
|
idleMs: own === undefined ? tenantIdle : Math.min(own, tenantIdle),
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
: Math.max(1, Math.min(30, Math.round((parseSandboxDuration(stopGrace) ?? 10_000) / 1000))),
|
|
28
|
+
// Kubernetes counts the grace in whole seconds, at least one.
|
|
29
|
+
stopGraceSeconds: graceMs === undefined ? DEFAULT_STOP_GRACE_SECONDS : Math.max(1, Math.round(graceMs / 1000)),
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
/** The volume's size in GiB. */
|
|
@@ -53,6 +54,8 @@ export function podSpecOf(input) {
|
|
|
53
54
|
env: {
|
|
54
55
|
NYLORUN_SANDBOX_ID: input.environmentId,
|
|
55
56
|
NYLORUN_HARNESS_ROOT: POD_HARNESS_ROOT,
|
|
57
|
+
// The `harness` and `engine` tuning the operator set: the pod's engine runs with them.
|
|
58
|
+
...podTuningEnv(),
|
|
56
59
|
},
|
|
57
60
|
...(input.joinToken ? { joinToken: input.joinToken } : {}),
|
|
58
61
|
};
|
package/dist/sandbox/policy.d.ts
CHANGED
|
@@ -4,10 +4,16 @@ import type { ResolvedNetwork } from "./types.js";
|
|
|
4
4
|
export declare const DEFAULT_SANDBOX_IMAGE = "python:3.13-slim";
|
|
5
5
|
export declare const DEFAULT_SANDBOX_CPUS = 2;
|
|
6
6
|
export declare const DEFAULT_SANDBOX_MEMORY_MIB = 1024;
|
|
7
|
-
|
|
7
|
+
/** The default of the Tenant's Environment setting `limits.idle` (`environments.config`, D36). */
|
|
8
|
+
export declare const DEFAULT_SANDBOX_IDLE = "15m";
|
|
8
9
|
/** Package registries and code hosts. Concrete names so every backend can express them. */
|
|
9
10
|
export declare const DEV_PRESET_HOSTS: readonly string[];
|
|
10
11
|
export declare function resolveNetwork(manifest: SandboxManifest | undefined): ResolvedNetwork;
|
|
12
|
+
/**
|
|
13
|
+
* How long the sandbox may sit idle: its spec's `idle`, which `sandboxSpecOf` sets from the
|
|
14
|
+
* Environment's `lifecycle.idle` within the Tenant's `limits.idle`; else that setting's default
|
|
15
|
+
* (`DEFAULT_SANDBOX_IDLE`, D36).
|
|
16
|
+
*/
|
|
11
17
|
export declare function idleMsOf(manifest: SandboxManifest | undefined): number;
|
|
12
18
|
export declare function memoryMiBOf(manifest: SandboxManifest | undefined): number;
|
|
13
19
|
export declare function describeNetwork(network: ResolvedNetwork): string;
|
package/dist/sandbox/policy.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { parseSandboxDuration, parseSandboxSize, } from "@nylorun/core/define";
|
|
2
|
+
import { hostTuning } from "../tuning.js";
|
|
2
3
|
/** Docker Official Image: Debian with bash, coreutils and Python 3; small first pull. */
|
|
3
4
|
export const DEFAULT_SANDBOX_IMAGE = "python:3.13-slim";
|
|
4
5
|
export const DEFAULT_SANDBOX_CPUS = 2;
|
|
5
6
|
export const DEFAULT_SANDBOX_MEMORY_MIB = 1024;
|
|
6
|
-
|
|
7
|
+
/** The default of the Tenant's Environment setting `limits.idle` (`environments.config`, D36). */
|
|
8
|
+
export const DEFAULT_SANDBOX_IDLE = "15m";
|
|
7
9
|
/** Package registries and code hosts. Concrete names so every backend can express them. */
|
|
8
10
|
export const DEV_PRESET_HOSTS = Object.freeze([
|
|
9
11
|
// npm and yarn
|
|
@@ -50,14 +52,19 @@ export function resolveNetwork(manifest) {
|
|
|
50
52
|
suffixes: Object.freeze([...suffixes].sort()),
|
|
51
53
|
});
|
|
52
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* How long the sandbox may sit idle: its spec's `idle`, which `sandboxSpecOf` sets from the
|
|
57
|
+
* Environment's `lifecycle.idle` within the Tenant's `limits.idle`; else that setting's default
|
|
58
|
+
* (`DEFAULT_SANDBOX_IDLE`, D36).
|
|
59
|
+
*/
|
|
53
60
|
export function idleMsOf(manifest) {
|
|
54
|
-
return (manifest?.idle
|
|
61
|
+
return parseSandboxDuration(manifest?.idle ?? DEFAULT_SANDBOX_IDLE) ?? parseSandboxDuration(DEFAULT_SANDBOX_IDLE);
|
|
55
62
|
}
|
|
56
63
|
export function memoryMiBOf(manifest) {
|
|
57
64
|
const bytes = manifest?.resources?.memory
|
|
58
65
|
? parseSandboxSize(manifest.resources.memory)
|
|
59
66
|
: undefined;
|
|
60
|
-
return bytes ? Math.max(
|
|
67
|
+
return bytes ? Math.max(hostTuning().pods.memoryFloorMiB, Math.ceil(bytes / 1024 ** 2)) : DEFAULT_SANDBOX_MEMORY_MIB;
|
|
61
68
|
}
|
|
62
69
|
export function describeNetwork(network) {
|
|
63
70
|
const extra = network.preset === "dev"
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Resolves an
|
|
3
|
-
* and the sandbox spec a recipe gives a session
|
|
2
|
+
* Resolves an Agent's recipe (`PUT /v1/agents/{id}`) against its Tenant's limits,
|
|
3
|
+
* and the sandbox spec a recipe gives a session (`Session.sandbox`) and the pod. Pure: the
|
|
4
4
|
* caller supplies the configuration. Every problem is reported, not only the first, so one
|
|
5
5
|
* response lists all of them.
|
|
6
6
|
*/
|
|
7
|
-
import type { EnvironmentFile, EnvironmentKind, EnvironmentLifecycle, EnvironmentRecipe, EnvironmentSecretSlots, EnvironmentSetupCommand } from "@nylorun/core/contracts";
|
|
7
|
+
import type { EnvironmentFile, EnvironmentKind, EnvironmentLifecycle, EnvironmentLimits, EnvironmentRecipe, EnvironmentSecretSlots, EnvironmentSetupCommand } from "@nylorun/core/contracts";
|
|
8
8
|
import { type SandboxManifest } from "@nylorun/core/define";
|
|
9
9
|
import type { PodSandboxSpec } from "./pods/spec.js";
|
|
10
10
|
import { type EffectiveSandboxConfig } from "./tenant-config.js";
|
|
@@ -25,6 +25,11 @@ export interface RecipeRequest {
|
|
|
25
25
|
readonly files?: readonly EnvironmentFile[];
|
|
26
26
|
/** The secret slots (D64): passed through for every kind. */
|
|
27
27
|
readonly secrets?: EnvironmentSecretSlots;
|
|
28
|
+
/**
|
|
29
|
+
* Artifact and skill limits: passed through for every kind, as set. The caller checks them
|
|
30
|
+
* against the Tenant's (`tenant/limits.ts`), which this module does not read.
|
|
31
|
+
*/
|
|
32
|
+
readonly limits?: EnvironmentLimits;
|
|
28
33
|
}
|
|
29
34
|
export type RecipeResolution = {
|
|
30
35
|
readonly kind: "recipe";
|
|
@@ -33,11 +38,11 @@ export type RecipeResolution = {
|
|
|
33
38
|
readonly kind: "error";
|
|
34
39
|
readonly errors: readonly string[];
|
|
35
40
|
};
|
|
36
|
-
/** The recipe `request` asks for an
|
|
41
|
+
/** The recipe `request` asks for an Agent of `kind`, or every reason it cannot have it. */
|
|
37
42
|
export declare function resolveRecipe(kind: EnvironmentKind, request: RecipeRequest, config: EffectiveSandboxConfig): RecipeResolution;
|
|
38
43
|
/**
|
|
39
|
-
* The sandbox spec of a recipe: what a session
|
|
40
|
-
* workspace runs. Idle is the
|
|
44
|
+
* The sandbox spec of a recipe: what a session pins when it opens and the virtual
|
|
45
|
+
* workspace runs. Idle is the Agent's `lifecycle.idle`, else the Tenant's.
|
|
41
46
|
*/
|
|
42
47
|
export declare function sandboxSpecOf(recipe: EnvironmentRecipe, tenantIdle: string): SandboxManifest;
|
|
43
48
|
/** The pod's spec of a recipe: its sandbox spec and its volume's size. */
|
package/dist/sandbox/resolve.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DEFAULT_HARNESS } from "@nylorun/core/contracts";
|
|
2
2
|
import { parseSandboxDuration } from "@nylorun/core/define";
|
|
3
3
|
import { memoryMiB } from "./tenant-config.js";
|
|
4
|
-
/** The recipe `request` asks for an
|
|
4
|
+
/** The recipe `request` asks for an Agent of `kind`, or every reason it cannot have it. */
|
|
5
5
|
export function resolveRecipe(kind, request, config) {
|
|
6
6
|
const { limits } = config;
|
|
7
7
|
const errors = [];
|
|
@@ -9,7 +9,7 @@ export function resolveRecipe(kind, request, config) {
|
|
|
9
9
|
// Any harness id (R4.1): placement decides where it may run, when a session opens.
|
|
10
10
|
const harness = request.harness ?? DEFAULT_HARNESS;
|
|
11
11
|
if (pod && harness !== DEFAULT_HARNESS && request.image === undefined)
|
|
12
|
-
errors.push(`harness ${harness} needs image: a pod
|
|
12
|
+
errors.push(`harness ${harness} needs image: a pod Agent runs its image as the harness.`);
|
|
13
13
|
const lifecycle = request.lifecycle ?? {};
|
|
14
14
|
const idle = lifecycle.idle === undefined ? undefined : parseSandboxDuration(lifecycle.idle);
|
|
15
15
|
if (idle !== undefined && idle > (parseSandboxDuration(limits.idle) ?? Infinity))
|
|
@@ -17,15 +17,18 @@ export function resolveRecipe(kind, request, config) {
|
|
|
17
17
|
if (kind === "none") {
|
|
18
18
|
for (const field of ["image", "resources", "storage", "network", "setup", "files"])
|
|
19
19
|
if (request[field] !== undefined)
|
|
20
|
-
errors.push(`${field} is for an
|
|
20
|
+
errors.push(`${field} is for an Agent with a sandbox: kind none has none.`);
|
|
21
21
|
if (errors.length > 0)
|
|
22
22
|
return { kind: "error", errors };
|
|
23
|
-
return {
|
|
23
|
+
return {
|
|
24
|
+
kind: "recipe",
|
|
25
|
+
recipe: { harness, lifecycle: { ...lifecycle }, ...limitsOf(request), ...slotsOf(request) },
|
|
26
|
+
};
|
|
24
27
|
}
|
|
25
28
|
if (request.image !== undefined && !pod)
|
|
26
|
-
errors.push("image is for kind pod: a virtual
|
|
29
|
+
errors.push("image is for kind pod: a virtual Agent runs no images.");
|
|
27
30
|
if (request.storage !== undefined && !pod)
|
|
28
|
-
errors.push("storage is for kind pod: a virtual
|
|
31
|
+
errors.push("storage is for kind pod: a virtual Agent has no volume.");
|
|
29
32
|
for (const field of ["setup", "files"])
|
|
30
33
|
if (request[field] !== undefined && request[field].length > 0 && !pod)
|
|
31
34
|
errors.push(`${field} is for kind pod: it runs when a pod's disk is made or reset.`);
|
|
@@ -34,7 +37,7 @@ export function resolveRecipe(kind, request, config) {
|
|
|
34
37
|
if (!withinCeiling(host, limits.network))
|
|
35
38
|
errors.push(`network.allowedHosts includes ${host}, which this Tenant does not allow.`);
|
|
36
39
|
else if (host.startsWith("*.") && !pod)
|
|
37
|
-
errors.push(`network.allowedHosts includes ${host}; a virtual
|
|
40
|
+
errors.push(`network.allowedHosts includes ${host}; a virtual Agent allows exact host names only, such as ${host.slice(2)}.`);
|
|
38
41
|
}
|
|
39
42
|
const cpus = request.resources?.cpus ?? limits.defaultResources.cpus;
|
|
40
43
|
const memory = request.resources?.memory === undefined
|
|
@@ -57,10 +60,17 @@ export function resolveRecipe(kind, request, config) {
|
|
|
57
60
|
lifecycle: { ...lifecycle },
|
|
58
61
|
...(pod && request.setup?.length ? { setup: request.setup.map((command) => ({ ...command })) } : {}),
|
|
59
62
|
...(pod && request.files?.length ? { files: request.files.map((file) => ({ ...file })) } : {}),
|
|
63
|
+
...limitsOf(request),
|
|
60
64
|
...slotsOf(request),
|
|
61
65
|
},
|
|
62
66
|
};
|
|
63
67
|
}
|
|
68
|
+
/** The request's limits, when it sets any. */
|
|
69
|
+
function limitsOf(request) {
|
|
70
|
+
return request.limits !== undefined && Object.keys(request.limits).length > 0
|
|
71
|
+
? { limits: structuredClone(request.limits) }
|
|
72
|
+
: {};
|
|
73
|
+
}
|
|
64
74
|
/** The request's secret slots, when it declares any. */
|
|
65
75
|
function slotsOf(request) {
|
|
66
76
|
return request.secrets !== undefined && Object.keys(request.secrets).length > 0
|
|
@@ -68,8 +78,8 @@ function slotsOf(request) {
|
|
|
68
78
|
: {};
|
|
69
79
|
}
|
|
70
80
|
/**
|
|
71
|
-
* The sandbox spec of a recipe: what a session
|
|
72
|
-
* workspace runs. Idle is the
|
|
81
|
+
* The sandbox spec of a recipe: what a session pins when it opens and the virtual
|
|
82
|
+
* workspace runs. Idle is the Agent's `lifecycle.idle`, else the Tenant's.
|
|
73
83
|
*/
|
|
74
84
|
export function sandboxSpecOf(recipe, tenantIdle) {
|
|
75
85
|
const allow = recipe.network?.allowedHosts ?? [];
|
package/dist/sandbox/setup.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export { SETUP_FAILED };
|
|
|
6
6
|
export interface SetupPlan {
|
|
7
7
|
/** The applied recipe's version: written into the volume's marker. */
|
|
8
8
|
readonly recipeVersion: number;
|
|
9
|
+
/** The sha256 of the commands and files: a marker with another runs the setup again (T3). */
|
|
10
|
+
readonly setupHash: string;
|
|
9
11
|
readonly commands: readonly {
|
|
10
12
|
readonly command: string;
|
|
11
13
|
readonly cwd: string;
|
|
@@ -33,8 +35,10 @@ export interface SetupReport {
|
|
|
33
35
|
export declare function parseSetupReport(value: unknown): SetupReport | undefined;
|
|
34
36
|
/**
|
|
35
37
|
* Records a failed setup on Environment `id` held at host epoch `epoch` (the reporting engine's):
|
|
36
|
-
* the pod is `failed` with a reason that starts `setup:`, and `
|
|
37
|
-
* command failed and how
|
|
38
|
+
* the pod is `failed` with a reason that starts `setup:`, and `agent.failed` says which
|
|
39
|
+
* command failed and how, with the end of its output (the Tenant's `setupOutputChars`; the reason
|
|
40
|
+
* quotes `setupReasonChars` of it and `setupCommandExcerptChars` of the command). A report
|
|
41
|
+
* from an older host, or a success, changes nothing.
|
|
38
42
|
*/
|
|
39
43
|
export declare function recordSetup(ctx: TenantContext, id: string, epoch: number, report: SetupReport): Promise<void>;
|
|
40
44
|
/**
|