@nylorun/runtime 0.23.0-beta → 0.25.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 +252 -0
- package/README.md +87 -28
- package/dist/adapters/execution/restate.js +1 -1
- 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/api/a2a/card.d.ts +11 -1
- package/dist/api/a2a/card.js +26 -2
- package/dist/api/a2a/endpoint.js +9 -3
- package/dist/api/a2a/protocol.d.ts +25 -2
- package/dist/api/a2a/protocol.js +20 -3
- package/dist/api/a2a/routes.d.ts +6 -1
- package/dist/api/a2a/routes.js +67 -9
- package/dist/api/a2a/tasks.js +2 -1
- package/dist/api/ag-ui/endpoint.js +8 -3
- package/dist/api/ag-ui/history.d.ts +6 -1
- package/dist/api/ag-ui/history.js +39 -3
- package/dist/api/ag-ui/routes.d.ts +4 -1
- package/dist/api/ag-ui/routes.js +63 -20
- package/dist/api/ag-ui/translate.js +3 -2
- package/dist/api/components.d.ts +5327 -1091
- package/dist/api/components.js +40 -16
- package/dist/api/gate/keys.d.ts +2 -1
- package/dist/api/gate/keys.js +14 -1
- package/dist/api/gate/routes.d.ts +6 -0
- package/dist/api/gate/routes.js +52 -3
- package/dist/api/http/app.js +17 -6
- package/dist/api/http/body.d.ts +3 -3
- package/dist/api/http/body.js +7 -7
- 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/environment-manifests.d.ts +9 -0
- package/dist/api/http/routes/environment-manifests.js +118 -0
- package/dist/api/http/routes/environments.d.ts +11 -0
- package/dist/api/http/routes/environments.js +332 -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 +94 -9
- package/dist/api/http/routes/reads.js +54 -5
- package/dist/api/http/routes/sessions.js +24 -9
- package/dist/api/http/routes/tenant.js +65 -15
- package/dist/api/inbound-files.d.ts +65 -0
- package/dist/api/inbound-files.js +115 -0
- package/dist/api/openapi.js +48 -28
- package/dist/artifacts/disk.d.ts +59 -0
- package/dist/artifacts/disk.js +569 -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/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/service.d.ts +2 -1
- package/dist/artifacts/service.js +6 -1
- package/dist/artifacts/workspace.d.ts +5 -5
- package/dist/artifacts/workspace.js +7 -19
- package/dist/core/flow-host.d.ts +3 -4
- package/dist/core/flow-host.js +5 -2
- 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 +11 -44
- package/dist/core/turn-manifest.d.ts +3 -2
- package/dist/core/turn-manifest.js +29 -5
- package/dist/execution/types.d.ts +1 -1
- 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 +22 -1
- package/dist/gates/egress.js +116 -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 +9 -6
- package/dist/gates/http-tool.js +11 -17
- 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 +51 -0
- package/dist/gates/mcp-handler.d.ts +4 -0
- package/dist/gates/mcp-handler.js +7 -4
- 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 +132 -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 +11 -69
- package/dist/gates/tool-contract.js +1 -85
- package/dist/gates/tool-gate.d.ts +1 -1
- package/dist/harness/calls.d.ts +14 -7
- package/dist/harness/calls.js +30 -24
- package/dist/harness/executors.js +12 -9
- package/dist/harness/main.js +24 -5
- package/dist/harness/pod.d.ts +39 -0
- package/dist/harness/pod.js +46 -0
- package/dist/harness/service.d.ts +0 -1
- package/dist/harness/service.js +17 -9
- package/dist/harness/setup.d.ts +29 -0
- package/dist/harness/setup.js +174 -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 +180 -50
- package/dist/harness-api/redact.d.ts +3 -0
- package/dist/harness-api/redact.js +45 -0
- package/dist/harness-api/server.d.ts +35 -4
- package/dist/harness-api/server.js +130 -17
- 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 +105 -0
- package/dist/harness-api/tool-journal.js +132 -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 +12 -7
- package/dist/harness-api/workspace.js +47 -25
- package/dist/harness-api/ws-server.d.ts +11 -3
- package/dist/harness-api/ws-server.js +94 -15
- package/dist/host/gates.d.ts +2 -1
- package/dist/host/gates.js +10 -2
- package/dist/host/main.js +54 -5
- package/dist/host/operate.js +67 -5
- package/dist/host/stack-config.d.ts +64 -0
- package/dist/host/stack-config.js +62 -2
- package/dist/keys/client.js +4 -1
- package/dist/keys/keys.d.ts +13 -6
- package/dist/keys/keys.js +9 -8
- package/dist/management-openapi.json +1264 -2171
- package/dist/mcp/connect.d.ts +26 -22
- package/dist/mcp/connect.js +30 -34
- package/dist/mcp/pool.d.ts +3 -6
- package/dist/mcp/pool.js +14 -28
- package/dist/mcp/preview.d.ts +3 -3
- package/dist/mcp/preview.js +10 -21
- package/dist/mcp/snapshot.d.ts +16 -24
- package/dist/mcp/snapshot.js +33 -45
- 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 +114 -94
- package/dist/openapi.json +12822 -3943
- 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/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 -15
- package/dist/sandbox/manager.d.ts +47 -21
- package/dist/sandbox/manager.js +162 -107
- package/dist/sandbox/placement.d.ts +14 -13
- package/dist/sandbox/placement.js +31 -14
- package/dist/sandbox/pods/client.d.ts +9 -2
- package/dist/sandbox/pods/lifecycle.d.ts +36 -27
- package/dist/sandbox/pods/lifecycle.js +29 -26
- package/dist/sandbox/pods/reconcile.js +18 -12
- package/dist/sandbox/pods/spec.d.ts +15 -13
- package/dist/sandbox/pods/spec.js +17 -20
- package/dist/sandbox/records.d.ts +2 -2
- package/dist/sandbox/records.js +3 -3
- package/dist/sandbox/resolve.d.ts +37 -27
- package/dist/sandbox/resolve.js +73 -41
- 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 +64 -0
- package/dist/sandbox/setup.js +160 -0
- package/dist/sandbox/share.d.ts +21 -26
- package/dist/sandbox/share.js +36 -63
- package/dist/sandbox/skills.d.ts +8 -6
- package/dist/sandbox/skills.js +24 -23
- 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/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/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 +56 -0
- package/dist/store/postgres/reads.js +123 -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 +1126 -736
- package/dist/store/postgres/schema.js +136 -73
- package/dist/store/postgres/store.js +195 -67
- package/dist/store/types.d.ts +165 -50
- package/dist/tenant/advance.d.ts +7 -6
- package/dist/tenant/advance.js +118 -42
- package/dist/tenant/artifact-tool.d.ts +1 -1
- package/dist/tenant/artifact-tool.js +46 -26
- package/dist/tenant/commands.d.ts +15 -2
- package/dist/tenant/commands.js +115 -14
- package/dist/tenant/context.d.ts +56 -24
- package/dist/tenant/context.js +4 -15
- package/dist/tenant/effects.d.ts +1 -1
- package/dist/tenant/effects.js +44 -29
- package/dist/tenant/environment-manifests.d.ts +57 -0
- package/dist/tenant/environment-manifests.js +200 -0
- package/dist/tenant/environments.d.ts +174 -0
- package/dist/tenant/environments.js +1044 -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/model-setting.d.ts +14 -0
- package/dist/tenant/model-setting.js +51 -0
- 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 +7 -1
- package/dist/tenant/session.d.ts +3 -3
- package/dist/tenant/session.js +0 -14
- package/dist/tenant/sessions.d.ts +18 -14
- package/dist/tenant/sessions.js +99 -143
- package/dist/tenant/signing-keys.d.ts +5 -1
- package/dist/tenant/skill-tool.d.ts +3 -3
- package/dist/tenant/skill-tool.js +6 -11
- package/dist/tenant/status.js +3 -3
- package/dist/tenant/sweep.js +13 -7
- 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 +245 -36
- package/dist/tenant/transcript.js +64 -12
- 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 +41 -0
- package/dist/traces/ids.js +81 -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 +289 -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 +62 -0
- package/dist/vault/error.d.ts +11 -1
- package/dist/vault/error.js +5 -1
- package/dist/vault/host-model.d.ts +4 -1
- package/dist/vault/host-model.js +10 -2
- package/dist/vault/service.d.ts +92 -48
- package/dist/vault/service.js +462 -376
- 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 +13 -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/sandbox/capability.d.ts +0 -3
- package/dist/sandbox/capability.js +0 -10
- package/dist/tenant/sandboxes.d.ts +0 -123
- package/dist/tenant/sandboxes.js +0 -566
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A pod
|
|
2
|
+
* A pod Environment's lifecycle (blueprint D34, D36), as one pure decision: from the row, the
|
|
3
3
|
* Tenant's lifecycle settings, the Sandbox's status as the sandboxes service sees it and the
|
|
4
4
|
* clock, what to ask of the service, what to write to the row, which events to record, and
|
|
5
5
|
* when to look again. `reconcile.ts` gathers the facts, carries the decision out and runs it
|
|
@@ -12,42 +12,51 @@
|
|
|
12
12
|
* - **Every change** the Runtime asks for bumps `rev`; the `PUT` carries `opId =
|
|
13
13
|
* <name>.r<rev>`, so a repeated apply is a no-op at the service, and a Sandbox whose `opId`
|
|
14
14
|
* is not the row's is applied again.
|
|
15
|
-
* - **Idle** (`trigger: idle`): no turn on
|
|
16
|
-
* Tenant's `limits.idle` → `Suspended` (`
|
|
17
|
-
* kept; the next turn start asks for `Running` again.
|
|
18
|
-
* - **TTL**: the Sandbox carries `shutdownTime = createdAt + lifecycle.ttl` and the Tenant's
|
|
19
|
-
* `onExpiry` as its `shutdownPolicy`. Once it passes, the sandbox is `expired`
|
|
20
|
-
* (`sandbox.expired`), its host epoch moves (its tokens stop working) and turns are refused
|
|
21
|
-
* (`sandbox_expired`); with `onExpiry: delete` it is deleted. A `PUT` with a longer TTL moves
|
|
22
|
-
* `expiresAt`, and the next apply revives it with the later `shutdownTime`.
|
|
15
|
+
* - **Idle** (`trigger: idle`): no turn on a session that names it, and none ended within its
|
|
16
|
+
* `lifecycle.idle` (at most the Tenant's `limits.idle`) → `Suspended` (`environment.suspended`,
|
|
17
|
+
* reason `idle`). The volume is kept; the next turn start asks for `Running` again.
|
|
23
18
|
* - **Lost**: an incarnation that has run (a pod joined) whose Sandbox is gone or whose volume
|
|
24
|
-
* is missing is `lost` (`
|
|
25
|
-
* only a reset brings the
|
|
26
|
-
* engine's join also checks the id it keeps on the volume (`join.ts`): a new volume is
|
|
27
|
-
* too. A pod replaced on the same volume is not lost: the new pod's join bumps the epoch
|
|
28
|
-
* (`
|
|
19
|
+
* is missing is `lost` (`environment.lost`): its host epoch moves, its Sandbox is deleted, and
|
|
20
|
+
* only a reset brings the Environment back. agent-sandbox recreates a deleted claim empty, so
|
|
21
|
+
* the engine's join also checks the id it keeps on the volume (`join.ts`): a new volume is
|
|
22
|
+
* lost too. A pod replaced on the same volume is not lost: the new pod's join bumps the epoch
|
|
23
|
+
* (`environment.relaunched`).
|
|
29
24
|
* - **Delete** (`desired: deleted`): the Sandbox is deleted, and the row once it is gone.
|
|
30
|
-
* - **Reset** (`tenant/
|
|
25
|
+
* - **Reset** (`tenant/environments.ts`) bumps the volume generation: a new name, so a new
|
|
31
26
|
* Sandbox and volume; the old name is `retiring` and deleted here.
|
|
27
|
+
*
|
|
28
|
+
* - **Setup** (R2d step 2): a failed setup (`sandbox/setup.ts`) leaves the pod `failed` with a
|
|
29
|
+
* reason that starts `setup:`, even while the pod is ready, until a turn start asks it to run.
|
|
30
|
+
* - **Image** (Q14): the first ready pod's workload image is recorded by digest (`imageDigest`),
|
|
31
|
+
* and every later apply runs that digest, so a stop, relaunch or reset never pulls another
|
|
32
|
+
* image under the same tag. An image id with no repository (a locally loaded image) pins nothing.
|
|
33
|
+
*
|
|
34
|
+
* An unused Environment is deleted by the Tenant sweep (`lifecycle.deleteAfter`), not here.
|
|
32
35
|
*/
|
|
33
|
-
import type {
|
|
34
|
-
import type { SandboxPodPatch, SandboxPodState
|
|
36
|
+
import type { EnvironmentEventPayload, EnvironmentEventType } from "@nylorun/core/contracts";
|
|
37
|
+
import type { Environment, SandboxPodPatch, SandboxPodState } from "../../store/types.js";
|
|
35
38
|
import type { PodStatus } from "./client.js";
|
|
39
|
+
/**
|
|
40
|
+
* The pullable reference of the image id a container status names (`repo@sha256:…`), or
|
|
41
|
+
* undefined for one without a repository, such as an image loaded onto the node.
|
|
42
|
+
*/
|
|
43
|
+
export declare function imageDigestOf(imageID: string | undefined): string | undefined;
|
|
44
|
+
/** A setup failure's `reason` starts with this (`sandbox/setup.ts`); it is sticky. */
|
|
45
|
+
export declare const SETUP_FAILED = "setup:";
|
|
36
46
|
/** A pod not ready this long after it was asked to run has failed. */
|
|
37
47
|
export declare const READY_DEADLINE_MS: number;
|
|
38
48
|
/** How soon to look again while the pod changes. */
|
|
39
49
|
export declare const POLL_MS = 2000;
|
|
40
50
|
/** How soon to look again when the sandboxes service does not answer. */
|
|
41
51
|
export declare const UNREACHABLE_RETRY_MS = 5000;
|
|
42
|
-
/** The
|
|
52
|
+
/** The lifecycle settings (D36), read each time a decision is made. */
|
|
43
53
|
export interface PodLifecycleConfig {
|
|
44
|
-
readonly onExpiry: "retain" | "delete";
|
|
45
54
|
readonly idleMs: number;
|
|
46
55
|
}
|
|
47
|
-
/** Why the decision is made: a reconcile, or
|
|
48
|
-
export type PodTrigger = "reconcile" | "idle"
|
|
49
|
-
export type PodTimer = "idle"
|
|
50
|
-
export type PodRow =
|
|
56
|
+
/** Why the decision is made: a reconcile, or the idle timer firing. */
|
|
57
|
+
export type PodTrigger = "reconcile" | "idle";
|
|
58
|
+
export type PodTimer = "idle";
|
|
59
|
+
export type PodRow = Environment & {
|
|
51
60
|
readonly pod: SandboxPodState;
|
|
52
61
|
};
|
|
53
62
|
export interface PodFacts {
|
|
@@ -57,14 +66,14 @@ export interface PodFacts {
|
|
|
57
66
|
readonly status: PodStatus | undefined;
|
|
58
67
|
/** The retiring Sandbox's status, when the row names one. */
|
|
59
68
|
readonly retiring?: PodStatus | undefined;
|
|
60
|
-
/** A session
|
|
69
|
+
/** A session that names the Environment has a turn running. */
|
|
61
70
|
readonly busy: boolean;
|
|
62
71
|
readonly now: number;
|
|
63
72
|
readonly trigger: PodTrigger;
|
|
64
73
|
}
|
|
65
|
-
export interface PodEvent<T extends
|
|
74
|
+
export interface PodEvent<T extends EnvironmentEventType = EnvironmentEventType> {
|
|
66
75
|
readonly type: T;
|
|
67
|
-
readonly payload:
|
|
76
|
+
readonly payload: EnvironmentEventPayload<T>;
|
|
68
77
|
}
|
|
69
78
|
export interface PodDecision {
|
|
70
79
|
/** Apply the row's spec (after `patch`) in this mode; `rotateJoin` mints a new join token. */
|
|
@@ -76,7 +85,7 @@ export interface PodDecision {
|
|
|
76
85
|
readonly delete: readonly string[];
|
|
77
86
|
readonly patch: SandboxPodPatch;
|
|
78
87
|
readonly events: readonly PodEvent[];
|
|
79
|
-
/** The Sandbox is gone and the
|
|
88
|
+
/** The Sandbox is gone and the Environment deleted: remove the row. */
|
|
80
89
|
readonly removeRow?: true;
|
|
81
90
|
/** Look again this soon. */
|
|
82
91
|
readonly retryAfterMs?: number;
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pullable reference of the image id a container status names (`repo@sha256:…`), or
|
|
3
|
+
* undefined for one without a repository, such as an image loaded onto the node.
|
|
4
|
+
*/
|
|
5
|
+
export function imageDigestOf(imageID) {
|
|
6
|
+
const id = imageID?.replace(/^docker-pullable:\/\//, "");
|
|
7
|
+
if (!id)
|
|
8
|
+
return undefined;
|
|
9
|
+
const at = id.indexOf("@sha256:");
|
|
10
|
+
return at > 0 && /^[0-9a-f]{64}$/.test(id.slice(at + 8)) ? id : undefined;
|
|
11
|
+
}
|
|
12
|
+
/** A setup failure's `reason` starts with this (`sandbox/setup.ts`); it is sticky. */
|
|
13
|
+
export const SETUP_FAILED = "setup:";
|
|
1
14
|
/** A pod not ready this long after it was asked to run has failed. */
|
|
2
15
|
export const READY_DEADLINE_MS = 5 * 60_000;
|
|
3
16
|
/** How soon to look again while the pod changes. */
|
|
@@ -67,24 +80,6 @@ export function decide(facts) {
|
|
|
67
80
|
again(POLL_MS);
|
|
68
81
|
return done();
|
|
69
82
|
}
|
|
70
|
-
const expiresAt = pod.expiresAt === undefined ? undefined : Date.parse(pod.expiresAt);
|
|
71
|
-
if (expiresAt !== undefined && now >= expiresAt) {
|
|
72
|
-
if (pod.observed !== "expired") {
|
|
73
|
-
patch.observed = "expired";
|
|
74
|
-
patch.hostEpoch = pod.hostEpoch + 1;
|
|
75
|
-
events.push(event("sandbox.expired", { onExpiry: facts.config.onExpiry }));
|
|
76
|
-
}
|
|
77
|
-
if (facts.config.onExpiry === "delete") {
|
|
78
|
-
patch.desired = "deleted";
|
|
79
|
-
patch.rev = pod.rev + 1;
|
|
80
|
-
if (status.exists && !status.deleting)
|
|
81
|
-
deletes.push(pod.k8sName);
|
|
82
|
-
again(POLL_MS);
|
|
83
|
-
}
|
|
84
|
-
return done();
|
|
85
|
-
}
|
|
86
|
-
if (expiresAt !== undefined)
|
|
87
|
-
arm.push({ timer: "ttl", at: expiresAt });
|
|
88
83
|
let desired = pod.desired;
|
|
89
84
|
let rev = pod.rev;
|
|
90
85
|
if (facts.trigger === "idle" && desired === "running" && !facts.busy) {
|
|
@@ -96,7 +91,7 @@ export function decide(facts) {
|
|
|
96
91
|
patch.desired = desired;
|
|
97
92
|
patch.rev = rev;
|
|
98
93
|
patch.hostEpoch = pod.hostEpoch + 1;
|
|
99
|
-
events.push(event("
|
|
94
|
+
events.push(event("environment.suspended", { reason: "idle" }));
|
|
100
95
|
}
|
|
101
96
|
else
|
|
102
97
|
arm.push({ timer: "idle", at: due });
|
|
@@ -104,14 +99,14 @@ export function decide(facts) {
|
|
|
104
99
|
// An incarnation a pod has joined, whose Sandbox or volume is gone: lost.
|
|
105
100
|
if (pod.podUid !== undefined && (!status.exists || status.deleting || status.volume === "missing")) {
|
|
106
101
|
const reason = !status.exists
|
|
107
|
-
? "The
|
|
102
|
+
? "The Environment's pod and volume were deleted outside the Runtime"
|
|
108
103
|
: status.deleting
|
|
109
|
-
? "The
|
|
110
|
-
: "The
|
|
104
|
+
? "The Environment's pod is being deleted outside the Runtime"
|
|
105
|
+
: "The Environment's volume is gone";
|
|
111
106
|
patch.observed = "lost";
|
|
112
107
|
patch.reason = reason;
|
|
113
108
|
patch.hostEpoch = (patch.hostEpoch ?? pod.hostEpoch) + 1;
|
|
114
|
-
events.push(event("
|
|
109
|
+
events.push(event("environment.lost", { reason }));
|
|
115
110
|
if (status.exists && !status.deleting)
|
|
116
111
|
deletes.push(pod.k8sName);
|
|
117
112
|
if (status.exists)
|
|
@@ -119,7 +114,7 @@ export function decide(facts) {
|
|
|
119
114
|
return done();
|
|
120
115
|
}
|
|
121
116
|
if (status.deleting) {
|
|
122
|
-
// The previous incarnation of this name is still going (
|
|
117
|
+
// The previous incarnation of this name is still going (an Environment deleted and created again).
|
|
123
118
|
again(POLL_MS);
|
|
124
119
|
return done();
|
|
125
120
|
}
|
|
@@ -135,7 +130,7 @@ export function decide(facts) {
|
|
|
135
130
|
patch.startedAt = new Date(now).toISOString();
|
|
136
131
|
patch.reason = null;
|
|
137
132
|
}
|
|
138
|
-
// A pod started by this apply (a create, a resume) is the
|
|
133
|
+
// A pod started by this apply (a create, a resume) is the Environment's first, not a relaunch.
|
|
139
134
|
if (desired === "running" && pod.podUid !== undefined)
|
|
140
135
|
patch.podUid = null;
|
|
141
136
|
again(POLL_MS);
|
|
@@ -144,11 +139,19 @@ export function decide(facts) {
|
|
|
144
139
|
});
|
|
145
140
|
}
|
|
146
141
|
if (desired === "running") {
|
|
142
|
+
// A failed setup (R2d step 2) stays failed until a turn start asks the pod to run again,
|
|
143
|
+
// though the pod itself is ready: its engine takes no turn meanwhile.
|
|
144
|
+
if (pod.observed === "failed" && pod.reason?.startsWith(SETUP_FAILED))
|
|
145
|
+
return done();
|
|
147
146
|
if (status.ready) {
|
|
148
147
|
if (pod.observed !== "running") {
|
|
149
148
|
patch.observed = "running";
|
|
150
149
|
patch.reason = null;
|
|
151
150
|
}
|
|
151
|
+
// The image it runs, pinned by digest from now on (Q14).
|
|
152
|
+
const digest = pod.imageDigest === undefined ? imageDigestOf(status.imageID) : undefined;
|
|
153
|
+
if (digest !== undefined)
|
|
154
|
+
patch.imageDigest = digest;
|
|
152
155
|
return done();
|
|
153
156
|
}
|
|
154
157
|
const startedAt = Date.parse(pod.startedAt ?? row.updatedAt);
|
|
@@ -159,7 +162,7 @@ export function decide(facts) {
|
|
|
159
162
|
`The pod was not ready within ${READY_DEADLINE_MS / 60_000} minutes`;
|
|
160
163
|
patch.observed = "failed";
|
|
161
164
|
patch.reason = reason;
|
|
162
|
-
events.push(event("
|
|
165
|
+
events.push(event("environment.failed", { reason }));
|
|
163
166
|
return done();
|
|
164
167
|
}
|
|
165
168
|
if (pod.observed !== "creating")
|
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
* infrastructure errors throw; Restate retries those.
|
|
16
16
|
*/
|
|
17
17
|
import { randomBytes } from "node:crypto";
|
|
18
|
-
import { readSandboxConfig } from "../tenant-config.js";
|
|
18
|
+
import { effectiveSandboxConfig, readSandboxConfig } from "../tenant-config.js";
|
|
19
|
+
import { podSandboxSpecOf } from "../resolve.js";
|
|
19
20
|
import { sha256Hex } from "../join.js";
|
|
21
|
+
import { recoverLostVolume } from "../../artifacts/disk.js";
|
|
20
22
|
import { decide } from "./lifecycle.js";
|
|
21
23
|
import { podLifecycleConfig, podSpecOf } from "./spec.js";
|
|
22
24
|
export async function reconcileSandbox(ctx, sandboxId, trigger) {
|
|
@@ -24,11 +26,11 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
|
|
|
24
26
|
if (!pods || ctx.closed)
|
|
25
27
|
return {};
|
|
26
28
|
const read = await ctx.store.tx(async (t) => {
|
|
27
|
-
const row = await t.
|
|
29
|
+
const row = await t.environment(sandboxId);
|
|
28
30
|
if (!row || row.kind !== "pod" || !row.pod)
|
|
29
31
|
return undefined;
|
|
30
32
|
const config = await readSandboxConfig(t);
|
|
31
|
-
const busy = (await t.
|
|
33
|
+
const busy = (await t.sessionsOnEnvironment(sandboxId)).some((session) => session.activeTurnId != null);
|
|
32
34
|
return { row: row, config, busy };
|
|
33
35
|
});
|
|
34
36
|
if (!read)
|
|
@@ -47,7 +49,7 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
|
|
|
47
49
|
return undefined;
|
|
48
50
|
}
|
|
49
51
|
};
|
|
50
|
-
const lifecycle = podLifecycleConfig(config);
|
|
52
|
+
const lifecycle = podLifecycleConfig(config, row.recipe);
|
|
51
53
|
const decision = decide({
|
|
52
54
|
row,
|
|
53
55
|
config: lifecycle,
|
|
@@ -69,10 +71,10 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
|
|
|
69
71
|
joinToken = randomBytes(32).toString("base64url");
|
|
70
72
|
rev += 1;
|
|
71
73
|
const recorded = await ctx.store.tx(async (t) => {
|
|
72
|
-
const current = await t.
|
|
74
|
+
const current = await t.environment(sandboxId, { lock: true });
|
|
73
75
|
if (!unchanged(current?.pod))
|
|
74
76
|
return false;
|
|
75
|
-
await t.
|
|
77
|
+
await t.updateEnvironmentPod(sandboxId, { joinTokenHash: sha256Hex(joinToken), rev }, new Date().toISOString());
|
|
76
78
|
return true;
|
|
77
79
|
});
|
|
78
80
|
if (!recorded)
|
|
@@ -95,12 +97,13 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
|
|
|
95
97
|
}
|
|
96
98
|
if (decision.apply) {
|
|
97
99
|
const spec = podSpecOf({
|
|
98
|
-
sandboxId,
|
|
99
|
-
spec: row.
|
|
100
|
+
environmentId: sandboxId,
|
|
101
|
+
spec: podSandboxSpecOf(row.applied, effectiveSandboxConfig(config).limits.idle),
|
|
100
102
|
pod: { ...row.pod, rev },
|
|
101
103
|
config,
|
|
102
104
|
mode: decision.apply.mode,
|
|
103
105
|
harnessImage: pods.harnessImage,
|
|
106
|
+
harness: row.applied.harness,
|
|
104
107
|
...(joinToken ? { joinToken } : {}),
|
|
105
108
|
});
|
|
106
109
|
try {
|
|
@@ -117,13 +120,13 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
|
|
|
117
120
|
}
|
|
118
121
|
}
|
|
119
122
|
const written = await ctx.store.tx(async (t) => {
|
|
120
|
-
const current = await t.
|
|
123
|
+
const current = await t.environment(sandboxId, { lock: true });
|
|
121
124
|
if (!current?.pod)
|
|
122
125
|
return { kind: "gone" };
|
|
123
126
|
if (!unchanged(current.pod))
|
|
124
127
|
return { kind: "changed" };
|
|
125
128
|
if (decision.removeRow) {
|
|
126
|
-
await t.
|
|
129
|
+
await t.deleteEnvironment(sandboxId);
|
|
127
130
|
return { kind: "removed" };
|
|
128
131
|
}
|
|
129
132
|
const epochMoved = patch.hostEpoch !== undefined && patch.hostEpoch !== null;
|
|
@@ -132,9 +135,9 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
|
|
|
132
135
|
...(epochMoved ? { hostEpoch: current.pod.hostEpoch + 1 } : {}),
|
|
133
136
|
};
|
|
134
137
|
if (Object.keys(finalPatch).length > 0)
|
|
135
|
-
await t.
|
|
138
|
+
await t.updateEnvironmentPod(sandboxId, finalPatch, new Date().toISOString());
|
|
136
139
|
for (const item of decision.events)
|
|
137
|
-
await t.
|
|
140
|
+
await t.environmentEvent(sandboxId, item.type, item.payload);
|
|
138
141
|
if (epochMoved)
|
|
139
142
|
await t.signal({ type: "host.revoked", sandboxId, epoch: finalPatch.hostEpoch });
|
|
140
143
|
return { kind: "written" };
|
|
@@ -145,6 +148,9 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
|
|
|
145
148
|
await ctx.sandbox.removeSandbox(sandboxId).catch(() => undefined);
|
|
146
149
|
return {};
|
|
147
150
|
}
|
|
151
|
+
// A lost volume with a disk version starts again from the latest one (R2d step 3).
|
|
152
|
+
if (patch.observed === "lost" && (await recoverLostVolume(ctx, sandboxId).catch(() => false)))
|
|
153
|
+
return { retryAfterMs: 0 };
|
|
148
154
|
return {
|
|
149
155
|
...(decision.retryAfterMs === undefined ? {} : { retryAfterMs: decision.retryAfterMs }),
|
|
150
156
|
...(decision.arm.length > 0 ? { arm: decision.arm } : {}),
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `PUT /v1/pods/{name}` body of a pod
|
|
2
|
+
* The `PUT /v1/pods/{name}` body of a pod Environment: its applied recipe, the Tenant's lifecycle
|
|
3
3
|
* settings (D36, read at every apply) and the incarnation's fences. The pod shape itself is
|
|
4
4
|
* rendered only by the sandboxes service.
|
|
5
5
|
*/
|
|
6
6
|
import { type SandboxManifest } from "@nylorun/core/define";
|
|
7
|
-
import type
|
|
7
|
+
import { type EnvironmentRecipe } from "@nylorun/core/contracts";
|
|
8
|
+
import type { TenantSandboxConfig } from "../tenant-config.js";
|
|
8
9
|
import type { SandboxPodState } from "../../store/types.js";
|
|
9
10
|
import type { PodSpec } from "./client.js";
|
|
10
11
|
import { type PodLifecycleConfig } from "./lifecycle.js";
|
|
@@ -14,32 +15,33 @@ export declare const DEFAULT_STOP_GRACE_SECONDS = 10;
|
|
|
14
15
|
export declare const DEFAULT_STORAGE_GIB = 5;
|
|
15
16
|
/** Where the engine in the pod keeps its records, plugin data and home (on the volume). */
|
|
16
17
|
export declare const POD_HARNESS_ROOT = "/harness";
|
|
17
|
-
/** What a pod
|
|
18
|
+
/** What a pod's spec holds beyond a virtual sandbox's. */
|
|
18
19
|
export type PodSandboxSpec = SandboxManifest & {
|
|
19
20
|
readonly storage?: string;
|
|
20
|
-
readonly lifecycle?: {
|
|
21
|
-
readonly ttl?: string;
|
|
22
|
-
};
|
|
23
21
|
};
|
|
24
|
-
/**
|
|
25
|
-
|
|
22
|
+
/**
|
|
23
|
+
* The lifecycle settings with their defaults: the Tenant's (D36), and the Environment's
|
|
24
|
+
* `lifecycle.idle` within the Tenant's `limits.idle` when `recipe` is given.
|
|
25
|
+
*/
|
|
26
|
+
export declare function podLifecycleConfig(config: TenantSandboxConfig, recipe?: Pick<EnvironmentRecipe, "lifecycle">): PodLifecycleConfig & {
|
|
26
27
|
readonly stopGraceSeconds: number;
|
|
27
28
|
};
|
|
28
29
|
/** The volume's size in GiB. */
|
|
29
30
|
export declare function storageGiBOf(spec: PodSandboxSpec): number;
|
|
30
|
-
/** The Tenant's default image, when its default sandbox names one. */
|
|
31
|
-
export declare function tenantDefaultImage(config: TenantSandboxConfig): string | undefined;
|
|
32
31
|
export interface PodSpecInput {
|
|
33
|
-
readonly
|
|
32
|
+
readonly environmentId: string;
|
|
34
33
|
readonly spec: PodSandboxSpec;
|
|
35
34
|
readonly pod: SandboxPodState;
|
|
36
35
|
readonly config: TenantSandboxConfig;
|
|
37
36
|
readonly mode: "Running" | "Suspended";
|
|
38
37
|
/** The Runtime image the engine is copied from. */
|
|
39
38
|
readonly harnessImage: string;
|
|
39
|
+
/**
|
|
40
|
+
* The harness the pod runs (R4.4): ours, copied in from `harnessImage`, or a custom one, which
|
|
41
|
+
* is the workload image itself and runs as its entrypoint.
|
|
42
|
+
*/
|
|
43
|
+
readonly harness?: string;
|
|
40
44
|
/** The join token, when this apply rotates it. */
|
|
41
45
|
readonly joinToken?: string;
|
|
42
46
|
}
|
|
43
47
|
export declare function podSpecOf(input: PodSpecInput): PodSpec;
|
|
44
|
-
/** When a sandbox with this TTL, created at `createdAt`, expires. */
|
|
45
|
-
export declare function expiresAtOf(createdAt: string, ttl: string | undefined): string | undefined;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `PUT /v1/pods/{name}` body of a pod
|
|
2
|
+
* The `PUT /v1/pods/{name}` body of a pod Environment: its applied recipe, the Tenant's lifecycle
|
|
3
3
|
* settings (D36, read at every apply) and the incarnation's fences. The pod shape itself is
|
|
4
4
|
* rendered only by the sandboxes service.
|
|
5
5
|
*/
|
|
6
6
|
import { parseSandboxDuration, parseSandboxSize } from "@nylorun/core/define";
|
|
7
|
+
import { DEFAULT_HARNESS } from "@nylorun/core/contracts";
|
|
7
8
|
import { memoryMiBOf } from "../policy.js";
|
|
8
9
|
import { opIdOf } from "./lifecycle.js";
|
|
9
10
|
/** The pod's grace period to stop when the Tenant sets none (D34). */
|
|
@@ -12,12 +13,16 @@ export const DEFAULT_STOP_GRACE_SECONDS = 10;
|
|
|
12
13
|
export const DEFAULT_STORAGE_GIB = 5;
|
|
13
14
|
/** Where the engine in the pod keeps its records, plugin data and home (on the volume). */
|
|
14
15
|
export const POD_HARNESS_ROOT = "/harness";
|
|
15
|
-
/**
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* The lifecycle settings with their defaults: the Tenant's (D36), and the Environment's
|
|
18
|
+
* `lifecycle.idle` within the Tenant's `limits.idle` when `recipe` is given.
|
|
19
|
+
*/
|
|
20
|
+
export function podLifecycleConfig(config, recipe) {
|
|
17
21
|
const stopGrace = config.lifecycle?.stopGrace;
|
|
22
|
+
const tenantIdle = parseSandboxDuration(config.limits?.idle ?? "15m") ?? 15 * 60_000;
|
|
23
|
+
const own = recipe?.lifecycle.idle === undefined ? undefined : parseSandboxDuration(recipe.lifecycle.idle);
|
|
18
24
|
return {
|
|
19
|
-
|
|
20
|
-
idleMs: parseSandboxDuration(config.limits?.idle ?? "15m") ?? 15 * 60_000,
|
|
25
|
+
idleMs: own === undefined ? tenantIdle : Math.min(own, tenantIdle),
|
|
21
26
|
stopGraceSeconds: stopGrace === undefined
|
|
22
27
|
? DEFAULT_STOP_GRACE_SECONDS
|
|
23
28
|
: Math.max(1, Math.min(30, Math.round((parseSandboxDuration(stopGrace) ?? 10_000) / 1000))),
|
|
@@ -28,35 +33,27 @@ export function storageGiBOf(spec) {
|
|
|
28
33
|
const bytes = spec.storage === undefined ? undefined : parseSandboxSize(spec.storage);
|
|
29
34
|
return bytes === undefined ? DEFAULT_STORAGE_GIB : Math.max(1, Math.ceil(bytes / 1024 ** 3));
|
|
30
35
|
}
|
|
31
|
-
/** The Tenant's default image, when its default sandbox names one. */
|
|
32
|
-
export function tenantDefaultImage(config) {
|
|
33
|
-
const fallback = config.default;
|
|
34
|
-
return typeof fallback === "object" ? fallback.image : undefined;
|
|
35
|
-
}
|
|
36
36
|
export function podSpecOf(input) {
|
|
37
37
|
const { spec, pod, config } = input;
|
|
38
38
|
const lifecycle = podLifecycleConfig(config);
|
|
39
|
-
|
|
39
|
+
// Pinned by digest once a pod ran it (Q14).
|
|
40
|
+
const image = pod.imageDigest ?? spec.image;
|
|
41
|
+
const ours = (input.harness ?? DEFAULT_HARNESS) === DEFAULT_HARNESS;
|
|
40
42
|
return {
|
|
41
43
|
opId: opIdOf(pod),
|
|
42
44
|
mode: input.mode,
|
|
43
45
|
...(image ? { image } : {}),
|
|
44
|
-
|
|
46
|
+
// A custom harness is its image: the pod runs the image's entrypoint, nothing copied in.
|
|
47
|
+
...(ours ? { harnessImage: input.harnessImage } : { entrypoint: "image" }),
|
|
45
48
|
cpus: spec.resources?.cpus ?? 1,
|
|
46
49
|
memoryMiB: memoryMiBOf(spec),
|
|
47
50
|
storageGiB: storageGiBOf(spec),
|
|
48
51
|
stopGraceSeconds: lifecycle.stopGraceSeconds,
|
|
49
|
-
|
|
50
|
-
shutdownPolicy: lifecycle.onExpiry === "delete" ? "Delete" : "Retain",
|
|
52
|
+
shutdownPolicy: "Retain",
|
|
51
53
|
env: {
|
|
52
|
-
NYLORUN_SANDBOX_ID: input.
|
|
54
|
+
NYLORUN_SANDBOX_ID: input.environmentId,
|
|
53
55
|
NYLORUN_HARNESS_ROOT: POD_HARNESS_ROOT,
|
|
54
56
|
},
|
|
55
57
|
...(input.joinToken ? { joinToken: input.joinToken } : {}),
|
|
56
58
|
};
|
|
57
59
|
}
|
|
58
|
-
/** When a sandbox with this TTL, created at `createdAt`, expires. */
|
|
59
|
-
export function expiresAtOf(createdAt, ttl) {
|
|
60
|
-
const ms = ttl === undefined ? undefined : parseSandboxDuration(ttl);
|
|
61
|
-
return ms === undefined ? undefined : new Date(Date.parse(createdAt) + ms).toISOString();
|
|
62
|
-
}
|
|
@@ -16,10 +16,10 @@ export declare function workspacePrefix(scope: string): string;
|
|
|
16
16
|
/** The key of session `sessionId`'s own workspace. */
|
|
17
17
|
export declare function sessionWorkspaceKey(scope: string, sessionId: string): string;
|
|
18
18
|
/**
|
|
19
|
-
* The key of sandbox resource `
|
|
19
|
+
* The key of sandbox resource `environmentId`'s workspace: apart from every session key (16 hex
|
|
20
20
|
* characters after the prefix). The virtual backend keeps it in `<sandboxes>/<key>/workspace`.
|
|
21
21
|
*/
|
|
22
|
-
export declare function sandboxWorkspaceKey(scope: string,
|
|
22
|
+
export declare function sandboxWorkspaceKey(scope: string, environmentId: string): string;
|
|
23
23
|
/** The records in the Tenant's `sandboxes` table. */
|
|
24
24
|
export declare function storeSandboxRecords(store: SessionStore): SandboxRecords;
|
|
25
25
|
/**
|
package/dist/sandbox/records.js
CHANGED
|
@@ -21,11 +21,11 @@ export function sessionWorkspaceKey(scope, sessionId) {
|
|
|
21
21
|
return workspacePrefix(scope) + createHash("sha256").update(sessionId).digest("hex").slice(0, 16);
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
* The key of sandbox resource `
|
|
24
|
+
* The key of sandbox resource `environmentId`'s workspace: apart from every session key (16 hex
|
|
25
25
|
* characters after the prefix). The virtual backend keeps it in `<sandboxes>/<key>/workspace`.
|
|
26
26
|
*/
|
|
27
|
-
export function sandboxWorkspaceKey(scope,
|
|
28
|
-
return `${workspacePrefix(scope)}sbx-${createHash("sha256").update(
|
|
27
|
+
export function sandboxWorkspaceKey(scope, environmentId) {
|
|
28
|
+
return `${workspacePrefix(scope)}sbx-${createHash("sha256").update(environmentId).digest("hex").slice(0, 16)}`;
|
|
29
29
|
}
|
|
30
30
|
/** The records in the Tenant's `sandboxes` table. */
|
|
31
31
|
export function storeSandboxRecords(store) {
|
|
@@ -1,37 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Resolves
|
|
3
|
-
*
|
|
4
|
-
* reported, not only the first, so one
|
|
2
|
+
* Resolves an Environment's recipe (`PUT /v1/environments/{id}`) against its Tenant's limits,
|
|
3
|
+
* and the sandbox spec a recipe gives a session's pinned manifest (its `runtime` block) and the pod. Pure: the
|
|
4
|
+
* caller supplies the configuration. Every problem is reported, not only the first, so one
|
|
5
|
+
* response lists all of them.
|
|
5
6
|
*/
|
|
6
|
-
import type {
|
|
7
|
-
import type
|
|
7
|
+
import type { EnvironmentFile, EnvironmentKind, EnvironmentLifecycle, EnvironmentRecipe, EnvironmentSecretSlots, EnvironmentSetupCommand } from "@nylorun/core/contracts";
|
|
8
|
+
import { type SandboxManifest } from "@nylorun/core/define";
|
|
9
|
+
import type { PodSandboxSpec } from "./pods/spec.js";
|
|
8
10
|
import { type EffectiveSandboxConfig } from "./tenant-config.js";
|
|
9
|
-
|
|
10
|
-
export
|
|
11
|
-
readonly
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
/** What a `PUT` asks of the recipe: the request's fields, over the existing recipe's. */
|
|
12
|
+
export interface RecipeRequest {
|
|
13
|
+
readonly image?: string;
|
|
14
|
+
readonly resources?: {
|
|
15
|
+
readonly cpus?: number;
|
|
16
|
+
readonly memory?: string;
|
|
17
|
+
};
|
|
18
|
+
readonly storage?: string;
|
|
19
|
+
readonly network?: {
|
|
20
|
+
readonly allowedHosts?: readonly string[];
|
|
21
|
+
};
|
|
22
|
+
readonly harness?: string;
|
|
23
|
+
readonly lifecycle?: EnvironmentLifecycle;
|
|
24
|
+
readonly setup?: readonly EnvironmentSetupCommand[];
|
|
25
|
+
readonly files?: readonly EnvironmentFile[];
|
|
26
|
+
/** The secret slots (D64): passed through for every kind. */
|
|
27
|
+
readonly secrets?: EnvironmentSecretSlots;
|
|
28
|
+
}
|
|
29
|
+
export type RecipeResolution = {
|
|
30
|
+
readonly kind: "recipe";
|
|
31
|
+
readonly recipe: EnvironmentRecipe;
|
|
16
32
|
} | {
|
|
17
33
|
readonly kind: "error";
|
|
18
|
-
readonly status: 400 | 403;
|
|
19
34
|
readonly errors: readonly string[];
|
|
20
35
|
};
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
*/
|
|
31
|
-
readonly kind?: "virtual" | "pod";
|
|
32
|
-
}
|
|
33
|
-
export declare const SUBJECT_INLINE_MESSAGE = "Defining a sandbox needs an application key. Use the Tenant default, or open the session from the application backend.";
|
|
34
|
-
export declare function resolveSandbox(input: ResolveSandboxInput): SandboxResolution;
|
|
36
|
+
/** The recipe `request` asks for an Environment of `kind`, or every reason it cannot have it. */
|
|
37
|
+
export declare function resolveRecipe(kind: EnvironmentKind, request: RecipeRequest, config: EffectiveSandboxConfig): RecipeResolution;
|
|
38
|
+
/**
|
|
39
|
+
* The sandbox spec of a recipe: what a session's `runtime` block pins and the virtual
|
|
40
|
+
* workspace runs. Idle is the Environment's `lifecycle.idle`, else the Tenant's.
|
|
41
|
+
*/
|
|
42
|
+
export declare function sandboxSpecOf(recipe: EnvironmentRecipe, tenantIdle: string): SandboxManifest;
|
|
43
|
+
/** The pod's spec of a recipe: its sandbox spec and its volume's size. */
|
|
44
|
+
export declare function podSandboxSpecOf(recipe: EnvironmentRecipe, tenantIdle: string): PodSandboxSpec;
|
|
35
45
|
/** Whether `host` (a name or `*.suffix` pattern) is inside one of the ceiling's patterns. */
|
|
36
46
|
export declare function withinCeiling(host: string, ceiling: readonly string[]): boolean;
|
|
37
47
|
/** Problems with a Tenant configuration, checked when an operator saves it. */
|