@sema-agent/server 7.125.1 → 7.126.0
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/MIGRATION.md +195 -2
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/USAGE.md +263 -140
- package/deploy/sema-up/README.md +1 -1
- package/deploy/sema-up/chart/templates/networkpolicy-sandbox.yaml +47 -0
- package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +5 -1
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +2 -2
- package/deploy/sema-up/chart/templates/server.yaml +1 -1
- package/deploy/sema-up/chart/values.yaml +12 -0
- package/deploy/sema-up/cluster-up.sh +10 -10
- package/deploy/sema-up/kube-up.sh +11 -10
- package/deploy/sema-up/lib/dataroot.sh +1 -1
- package/deploy/sema-up/lib/envf.sh +5 -5
- package/deploy/sema-up/lib/flagshape.sh +5 -5
- package/deploy/sema-up/lib/lanes.py +1 -1
- package/deploy/sema-up/lib/parse-machines.py +2 -2
- package/deploy/sema-up/lib/shell-access.sh +1 -1
- package/deploy/sema-up/preflight.sh +4 -4
- package/deploy/sema-up/sema-up.sh +33 -33
- package/deploy/sema-up/smoke.sh +3 -3
- package/deploy/sema-up/stack/compose.yaml +1 -1
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +6 -2
- package/dist/approval-card.d.ts +20 -4
- package/dist/approval-content-kind.d.ts +38 -22
- package/dist/approval-content-kind.js +20 -4
- package/dist/audit.js +2 -0
- package/dist/bench/s1/arms.d.ts +9 -37
- package/dist/bench/s1/arms.js +4 -61
- package/dist/bench/s1/live-deps.d.ts +4 -10
- package/dist/bench/s1/live-deps.js +1 -222
- package/dist/bench/s1/oracle.d.ts +1 -1
- package/dist/bench/s1/reviewer.d.ts +1 -1
- package/dist/bench/s1/row.d.ts +5 -7
- package/dist/bench/s1/row.js +0 -2
- package/dist/bench/s1/run-firm.d.ts +2 -2
- package/dist/bench/s1/run-firm.js +1 -4
- package/dist/bench/s1/runner-ctx.d.ts +17 -29
- package/dist/bench/s1/runner-ctx.js +3 -7
- package/dist/bench/s1/tasks.d.ts +9 -46
- package/dist/bench/s1/tasks.js +4 -63
- package/dist/boot/execution-env.d.ts +38 -26
- package/dist/boot/execution-env.js +71 -38
- package/dist/boot/memory-boundary.d.ts +1 -1
- package/dist/boot/parked-revive-gate.d.ts +0 -3
- package/dist/boot/parked-revive-gate.js +2 -2
- package/dist/boot/resolve-spec.d.ts +0 -2
- package/dist/boot/resolve-spec.js +12 -17
- package/dist/boot/retention-lane.d.ts +12 -1
- package/dist/boot/retention-lane.js +2 -0
- package/dist/boot/runner-deps.d.ts +5 -5
- package/dist/boot/runner-deps.js +5 -16
- package/dist/boot/session-faces.d.ts +5 -1
- package/dist/boot/session-faces.js +37 -1
- package/dist/boot/stage-03-store-backend.d.ts +1 -0
- package/dist/boot/stage-03-store-backend.js +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -0
- package/dist/boot/stage-05-execution-env.d.ts +2 -2
- package/dist/boot/stage-05-execution-env.js +7 -4
- package/dist/boot/stage-06-runners.d.ts +2 -3
- package/dist/boot/stage-06-runners.js +8 -7
- package/dist/boot/stage-07-capability-layer.d.ts +2 -3
- package/dist/boot/stage-07-capability-layer.js +3 -4
- package/dist/boot/stage-08-reapers.d.ts +2 -3
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/boot/stage-09-leader.d.ts +2 -3
- package/dist/boot/stage-09-leader.js +2 -1
- package/dist/boot/stage-10-http-server.d.ts +2 -3
- package/dist/boot/stage-10-http-server.js +2 -2
- package/dist/boot/stores.d.ts +1 -0
- package/dist/boot/stores.js +4 -3
- package/dist/boot/web-admin.d.ts +5 -0
- package/dist/boot/web-admin.js +28 -14
- package/dist/brain.d.ts +2 -2
- package/dist/brain.js +1 -1
- package/dist/capabilities/builtin-tools.d.ts +2 -1
- package/dist/capabilities/composite-members.d.ts +1 -1
- package/dist/capabilities/composite-members.js +1 -1
- package/dist/capabilities/hands-lane.d.ts +9 -6
- package/dist/capabilities/hands-lane.js +2 -2
- package/dist/capabilities/memory-notice.d.ts +27 -22
- package/dist/capabilities/memory-notice.js +15 -17
- package/dist/capabilities/scenarios.d.ts +9 -14
- package/dist/capabilities/scenarios.js +5 -50
- package/dist/capability-facts.d.ts +22 -11
- package/dist/capability-facts.js +13 -6
- package/dist/config-catalog.js +11 -4
- package/dist/config-provider.d.ts +5 -1
- package/dist/config-provider.js +23 -4
- package/dist/config-types.d.ts +14 -1
- package/dist/config.js +39 -6
- package/dist/deployment-governance.d.ts +19 -21
- package/dist/deployment-governance.js +7 -8
- package/dist/env-name-allowlist-knobs.d.ts +5 -5
- package/dist/env-name-allowlist-knobs.js +1 -0
- package/dist/execution-lane-caps.js +2 -2
- package/dist/hooks/hook-runner.d.ts +84 -30
- package/dist/hooks/hook-runner.js +74 -41
- package/dist/http/admission.js +12 -9
- package/dist/http/durable-run-leg.d.ts +5 -4
- package/dist/http/durable-run-leg.js +1 -1
- package/dist/http/idempotency.d.ts +28 -6
- package/dist/http/idempotency.js +10 -2
- package/dist/http/principal-gate.d.ts +14 -0
- package/dist/http/principal-gate.js +7 -0
- package/dist/http/request-guards.d.ts +2 -1
- package/dist/http/request-guards.js +14 -14
- package/dist/http/resume-legs.d.ts +2 -2
- package/dist/http/resume-legs.js +5 -4
- package/dist/http/route-ctx.d.ts +8 -6
- package/dist/http/routes/a2a-serve.js +11 -7
- package/dist/http/routes/approvals-assistant.js +18 -3
- package/dist/http/routes/background.d.ts +1 -1
- package/dist/http/routes/background.js +1 -1
- package/dist/http/routes/capabilities.js +10 -2
- package/dist/http/routes/mcp-reconnect.js +1 -1
- package/dist/http/routes/runs.d.ts +4 -3
- package/dist/http/routes/runs.js +12 -13
- package/dist/http/routes/session-sync.js +14 -11
- package/dist/http/routes/sessions.js +9 -9
- package/dist/http/routes/side-query.js +6 -19
- package/dist/http/routes/tasks.js +12 -11
- package/dist/http/server.d.ts +4 -0
- package/dist/http/server.js +2 -2
- package/dist/http/submit-replay.d.ts +15 -8
- package/dist/http/submit-replay.js +6 -0
- package/dist/http/wire-types.d.ts +7 -9
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/internal-run-env.d.ts +30 -0
- package/dist/internal-run-env.js +11 -0
- package/dist/leader/wire.js +3 -2
- package/dist/memory-scope.d.ts +19 -7
- package/dist/memory-scope.js +25 -8
- package/dist/observability/fail-open.d.ts +15 -3
- package/dist/observability/fail-open.js +15 -3
- package/dist/observability/metrics.d.ts +0 -1
- package/dist/observability/metrics.js +0 -3
- package/dist/observability/run-terminal.js +69 -4
- package/dist/permission-rule-vocab.d.ts +3 -2
- package/dist/permission-rule-vocab.js +1 -1
- package/dist/plan-cache-probe.d.ts +3 -2
- package/dist/plugins/approval-ask-store-sql.d.ts +1 -1
- package/dist/plugins/caching-session-store.d.ts +3 -0
- package/dist/plugins/caching-session-store.js +3 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +42 -28
- package/dist/plugins/checkpoint-store-sql.js +57 -11
- package/dist/plugins/file-run-store.d.ts +25 -21
- package/dist/plugins/file-run-store.js +70 -34
- package/dist/plugins/local-checkpoint-store.d.ts +8 -5
- package/dist/plugins/local-checkpoint-store.js +5 -15
- package/dist/plugins/local-session-store.d.ts +66 -7
- package/dist/plugins/local-session-store.js +168 -101
- package/dist/plugins/mailbox-recipient-lifecycle.d.ts +1 -1
- package/dist/plugins/memory-run-store.d.ts +19 -3
- package/dist/plugins/memory-run-store.js +38 -3
- package/dist/plugins/one-time-migrations.d.ts +141 -39
- package/dist/plugins/one-time-migrations.js +32 -143
- package/dist/plugins/pg-pool.js +11 -1
- package/dist/plugins/pg-session-storage.d.ts +2 -0
- package/dist/plugins/pg-session-storage.js +9 -5
- package/dist/plugins/remote-env-adb.d.ts +3 -0
- package/dist/plugins/remote-env-adb.js +1 -0
- package/dist/plugins/remote-env-device.d.ts +3 -0
- package/dist/plugins/remote-env-device.js +1 -0
- package/dist/plugins/remote-env-e2b.d.ts +4 -0
- package/dist/plugins/remote-env-e2b.js +2 -1
- package/dist/plugins/remote-env-host.d.ts +12 -0
- package/dist/plugins/remote-env-host.js +11 -1
- package/dist/plugins/remote-env-k8s.d.ts +227 -39
- package/dist/plugins/remote-env-k8s.js +450 -75
- package/dist/plugins/remote-env-local-docker.d.ts +4 -0
- package/dist/plugins/remote-env-local-docker.js +2 -1
- package/dist/plugins/remote-env-ssh.d.ts +3 -0
- package/dist/plugins/remote-env-ssh.js +1 -0
- package/dist/plugins/retention-store-sql.d.ts +4 -1
- package/dist/plugins/retention-store-sql.js +11 -9
- package/dist/plugins/run-store-sql.d.ts +44 -6
- package/dist/plugins/run-store-sql.js +92 -27
- package/dist/plugins/session-capture-record-store-sql.d.ts +2 -3
- package/dist/plugins/sql-driver.d.ts +2 -1
- package/dist/plugins/store-backend.js +1 -1
- package/dist/plugins/store-contracts.d.ts +17 -3
- package/dist/plugins/tidb-pool.js +15 -3
- package/dist/plugins/tidb-session-storage.js +3 -2
- package/dist/plugins/tidb-session-store.d.ts +2 -0
- package/dist/plugins/tidb-session-store.js +6 -2
- package/dist/request-key-closure.d.ts +12 -0
- package/dist/request-key-closure.js +3 -3
- package/dist/router/route-orchestration.d.ts +3 -11
- package/dist/router/route-orchestration.js +0 -4
- package/dist/rule-entry-list.js +0 -4
- package/dist/rules-consent.js +3 -0
- package/dist/run-local.d.ts +5 -4
- package/dist/run-local.js +23 -17
- package/dist/runs.js +1 -0
- package/dist/security.d.ts +7 -0
- package/dist/security.js +5 -0
- package/dist/server-secret-env.d.ts +10 -4
- package/dist/server-secret-env.js +4 -1
- package/dist/session-incarnation.d.ts +46 -2
- package/dist/session-incarnation.js +14 -3
- package/dist/session-sync-kernel.d.ts +10 -3
- package/dist/spec-fields.d.ts +24 -8
- package/dist/spec-fields.js +21 -2
- package/dist/task-workflow.d.ts +6 -4
- package/dist/task-workflow.js +3 -2
- package/dist/tool-approval.d.ts +78 -6
- package/dist/tool-approval.js +7 -3
- package/dist/trace/core-keyset-guard.d.ts +4 -4
- package/dist/trace/engine-notice-wire.d.ts +26 -1
- package/dist/trace/engine-notice-wire.js +7 -2
- package/dist/trace/project.d.ts +8 -3
- package/dist/trace/project.js +15 -1
- package/dist/trace/self-minted-wire.d.ts +104 -2
- package/dist/trace/self-minted-wire.js +35 -0
- package/dist/web-admin-client/apply-effective.d.ts +13 -7
- package/dist/web-admin-client/apply-effective.js +11 -26
- package/dist/web-admin-client/apply-ledger.d.ts +2 -2
- package/dist/web-admin-client/candidate-gate.d.ts +39 -4
- package/dist/web-admin-client/candidate-gate.js +9 -2
- package/dist/web-admin-client/facade.d.ts +2 -2
- package/dist/web-admin-client/http-client.d.ts +3 -1
- package/dist/web-admin-client/http-client.js +4 -3
- package/dist/web-admin-client/read-warnings.d.ts +33 -5
- package/dist/web-admin-client/read-warnings.js +41 -6
- package/dist/web-admin-client/types.d.ts +11 -5
- package/dist/workspace-cache.d.ts +45 -0
- package/dist/workspace-cache.js +30 -0
- package/dist/workspace-space-commit.d.ts +116 -0
- package/dist/workspace-space-commit.js +117 -0
- package/dist/workspace-space.d.ts +75 -9
- package/dist/workspace-space.js +52 -1
- package/package.json +4 -4
- package/dist/boot/session-incarnation-seat.d.ts +0 -41
- package/dist/boot/session-incarnation-seat.js +0 -69
- package/dist/capabilities/code-review-council.d.ts +0 -49
- package/dist/capabilities/code-review-council.js +0 -111
- package/dist/capabilities/collab-wire.d.ts +0 -36
- package/dist/capabilities/collab-wire.js +0 -2
- package/dist/capabilities/collab-workflows.d.ts +0 -47
- package/dist/capabilities/collab-workflows.js +0 -90
- package/dist/capabilities/prompts/code-review.d.ts +0 -25
- package/dist/capabilities/prompts/code-review.js +0 -39
- package/dist/capabilities/prompts/team.d.ts +0 -10
- package/dist/capabilities/prompts/team.js +0 -6
- package/dist/capabilities/team.d.ts +0 -113
- package/dist/capabilities/team.js +0 -70
|
@@ -88,6 +88,9 @@ export declare class RemoteDeviceExecutionEnv implements RemoteExecutionEnv {
|
|
|
88
88
|
private readonly cfg;
|
|
89
89
|
/** core `ExecutionEnv.homeDir`([ref]):设备上执行用户的 home。见 `DeviceEnvConfig.homeDir`。 */
|
|
90
90
|
readonly homeDir?: string | undefined;
|
|
91
|
+
/** core 7.49.0 #1554:shell 子进程真拿到的 `CDPATH` 条目 —— 本车道的命令跑在设备上(设备自己的 env),本类不读那份 env ⇒ `"unknown"`(core 契约:说不出就说 unknown,
|
|
92
|
+
* 不许报一张没读过的空表;删除守卫因此对裸 `cd <相对名>` 之后的删除问人,而不是按「没有 CDPATH」放行)。 */
|
|
93
|
+
readonly cdPath: "unknown";
|
|
91
94
|
/**
|
|
92
95
|
* 🔴 `suspendable: false` 在 core 契约里**同时断言**「工作区在目标上外部持久」(remote-env.ts 的
|
|
93
96
|
* load-bearing 不变量)—— 设备工作区 = 员工机磁盘,真持久 ✅,park-only 降级腿因此合法可用。
|
|
@@ -120,6 +120,7 @@ function assertPosixDevicePlatform(os, deviceId) {
|
|
|
120
120
|
export class RemoteDeviceExecutionEnv {
|
|
121
121
|
cfg;
|
|
122
122
|
homeDir;
|
|
123
|
+
cdPath = "unknown";
|
|
123
124
|
capabilities = { isolation: false, suspendable: false, canonicalPathAuthoritative: true };
|
|
124
125
|
externalContentTarget = true;
|
|
125
126
|
hostLocalPaths = false;
|
|
@@ -100,6 +100,9 @@ export declare class RemoteContainerExecutionEnv implements RemoteExecutionEnv,
|
|
|
100
100
|
cwd: string;
|
|
101
101
|
/** core `ExecutionEnv.homeDir`([ref]):本沙箱/目标机上执行用户的 home。见配置同名字段。 */
|
|
102
102
|
readonly homeDir?: string | undefined;
|
|
103
|
+
/** core 7.49.0 #1554:shell 子进程真拿到的 `CDPATH` 条目 —— 本车道的命令跑在 e2b 沙箱里(模板镜像的 env),本类不读那份 env ⇒ `"unknown"`(core 契约:说不出就说 unknown,
|
|
104
|
+
* 不许报一张没读过的空表;删除守卫因此对裸 `cd <相对名>` 之后的删除问人,而不是按「没有 CDPATH」放行)。 */
|
|
105
|
+
readonly cdPath: "unknown";
|
|
103
106
|
private readonly cfg;
|
|
104
107
|
private readonly sandboxApi;
|
|
105
108
|
private sbx?;
|
|
@@ -129,6 +132,7 @@ export declare class RemoteContainerExecutionEnv implements RemoteExecutionEnv,
|
|
|
129
132
|
readonly capabilities: {
|
|
130
133
|
isolation: boolean;
|
|
131
134
|
suspendable: boolean;
|
|
135
|
+
canonicalPathAuthoritative: boolean;
|
|
132
136
|
};
|
|
133
137
|
workspaceHandle(): WorkspaceHandle;
|
|
134
138
|
/**
|
|
@@ -19,6 +19,7 @@ const ok = (value) => ({ ok: true, value });
|
|
|
19
19
|
export class RemoteContainerExecutionEnv {
|
|
20
20
|
cwd;
|
|
21
21
|
homeDir;
|
|
22
|
+
cdPath = "unknown";
|
|
22
23
|
cfg;
|
|
23
24
|
sandboxApi;
|
|
24
25
|
sbx;
|
|
@@ -42,7 +43,7 @@ export class RemoteContainerExecutionEnv {
|
|
|
42
43
|
this.homeDir = config.homeDir;
|
|
43
44
|
this.sandboxApi = deps.sandboxApi ?? Sandbox;
|
|
44
45
|
}
|
|
45
|
-
capabilities = { isolation: true, suspendable: true };
|
|
46
|
+
capabilities = { isolation: true, suspendable: true, canonicalPathAuthoritative: true };
|
|
46
47
|
workspaceHandle() {
|
|
47
48
|
if (!this.handle) {
|
|
48
49
|
throw new RemoteExecutionError("connect_failed", "workspaceHandle() called before connect()");
|
|
@@ -94,6 +94,12 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
|
|
|
94
94
|
cwd: string;
|
|
95
95
|
/** core `ExecutionEnv.homeDir`([ref]):本沙箱/目标机上执行用户的 home。见配置同名字段。 */
|
|
96
96
|
readonly homeDir?: string | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* core 7.49.0 #1554:shell 子进程**真拿到的** `CDPATH` 条目(`[]` = 没有)—— 删除守卫按它解裸 `cd <dir>`。本车道的命令就是本机子进程,
|
|
99
|
+
* env 是本类自己组合的(继承姿态 + `cfg.env`),所以说得出:读同一只组合({@link childEnv},不披露),切法与 core `NodeExecutionEnv` 同
|
|
100
|
+
* (缺席 / 空串 ⇒ `[]`,否则按 `:` 切)。逐命令的 `options.env` 由引擎自己交,它知道。
|
|
101
|
+
*/
|
|
102
|
+
get cdPath(): readonly string[];
|
|
97
103
|
private readonly workspaceDir;
|
|
98
104
|
private readonly cfg;
|
|
99
105
|
/** Workspace created on disk (lazily, on first use). */
|
|
@@ -406,7 +412,13 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
|
|
|
406
412
|
* source of the shape rule) minus the config catalog's `secret`-typed names (`scrubServerSecrets`, 7.99.0 F7). `"all"` = full inherit (opt-out); a `string[]` = strict allowlist. Legacy `inheritProcessEnv`
|
|
407
413
|
* maps `true`→`"all"` / `false`→`[]`. */
|
|
408
414
|
private inheritMode;
|
|
415
|
+
/** 每次 spawn 交给子进程的 env(组合 + 剥除披露)。 */
|
|
409
416
|
private mergeEnv;
|
|
417
|
+
/**
|
|
418
|
+
* 子进程 env 的**唯一组合**。`disclose` = 这一次是不是真要交给子进程(是 ⇒ scrub 腿经 `hostSubprocessEnv` 记剥除;否 ⇒ 只读组合,
|
|
419
|
+
* 不计数 —— {@link cdPath} 这类「子进程会拿到什么」的读者走这一臂,一次没发生的 spawn 不进剥除账)。
|
|
420
|
+
*/
|
|
421
|
+
private childEnv;
|
|
410
422
|
private fileError;
|
|
411
423
|
}
|
|
412
424
|
/**
|
|
@@ -10,7 +10,7 @@ import { hostBackgroundShellEnabled, hostExecSpoolEnabled } from "../config.js";
|
|
|
10
10
|
import { resolveHostShell, hostShell, spawnGroupOptions, killTreeHard, killTreeSoft, hardKillGroupNow, collapseWin32EnvKeys, adoptHostForegroundChild } from "./host-platform.js";
|
|
11
11
|
import { BackgroundShellManager, seedMemStream, feedMemStream, drainMemStream } from "./background-shell-support.js";
|
|
12
12
|
import { FileError, ExecutionError, RemoteExecutionError, RollingTailBuffer, markTruncated, SchedulerError, BackgroundShellError, } from "@sema-agent/core";
|
|
13
|
-
import { allowlistedEnv, hostSubprocessEnv } from "../server-secret-env.js";
|
|
13
|
+
import { allowlistedEnv, composeHostSubprocessEnv, hostSubprocessEnv } from "../server-secret-env.js";
|
|
14
14
|
import { trackEphemeralHostWorkspace, untrackEphemeralHostWorkspace } from "./host-workspace-registry.js";
|
|
15
15
|
const PROVIDER = "host";
|
|
16
16
|
const DEFAULT_COMMAND_TIMEOUT_MS = 30 * 60_000;
|
|
@@ -57,6 +57,10 @@ export class RemoteHostExecutionEnv {
|
|
|
57
57
|
}
|
|
58
58
|
cwd;
|
|
59
59
|
homeDir;
|
|
60
|
+
get cdPath() {
|
|
61
|
+
const value = this.childEnv(undefined, false).CDPATH;
|
|
62
|
+
return value === undefined || value === "" ? [] : value.split(":");
|
|
63
|
+
}
|
|
60
64
|
workspaceDir;
|
|
61
65
|
cfg;
|
|
62
66
|
prepared = false;
|
|
@@ -1308,6 +1312,9 @@ export class RemoteHostExecutionEnv {
|
|
|
1308
1312
|
return "scrub";
|
|
1309
1313
|
}
|
|
1310
1314
|
mergeEnv(perCommand) {
|
|
1315
|
+
return this.childEnv(perCommand, true);
|
|
1316
|
+
}
|
|
1317
|
+
childEnv(perCommand, disclose) {
|
|
1311
1318
|
const mode = this.inheritMode();
|
|
1312
1319
|
let base;
|
|
1313
1320
|
if (mode === "all")
|
|
@@ -1315,6 +1322,9 @@ export class RemoteHostExecutionEnv {
|
|
|
1315
1322
|
else if (Array.isArray(mode)) {
|
|
1316
1323
|
base = allowlistedEnv(mode, process.env);
|
|
1317
1324
|
}
|
|
1325
|
+
else if (!disclose) {
|
|
1326
|
+
base = composeHostSubprocessEnv([]);
|
|
1327
|
+
}
|
|
1318
1328
|
else {
|
|
1319
1329
|
base = hostSubprocessEnv({ site: "server.remote-env-host.inherit-scrub", allow: [] });
|
|
1320
1330
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type SpaceReclaimer, type WorkspaceSpacePolicy, type CacheReclaimReason } from "../workspace-space.js";
|
|
2
|
+
import { type SpaceCommitFence } from "../workspace-space-commit.js";
|
|
3
|
+
import { type WorkspaceCacheCapability } from "../workspace-cache.js";
|
|
2
4
|
import { FileError, ExecutionError, RemoteExecutionError, type ExecutionEnv, type RemoteExecutionEnv, type WorkspaceHandle, type FileInfo, type Result, type SymlinkChain, type OutputChunk, type ExecStreamOptions, type RemoteConnectConfig, type SnapshotId, type SessionToken, type VmLifecycleOptions, type ExecutionEnvFactory, type BackgroundShellCapability, type BackgroundShellId, type BackgroundShellError, type BackgroundSpawnOptions, type BackgroundPoll } from "@sema-agent/core";
|
|
3
5
|
import type { PodSpecPatch } from "./k8s-exec-protocol.js";
|
|
4
6
|
type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
|
|
@@ -37,8 +39,15 @@ export interface K8sEnvConfig {
|
|
|
37
39
|
rpcTimeoutMs?: number;
|
|
38
40
|
/** Zero-progress liveness bound for commands (no output AND not finished ⇒ hang). */
|
|
39
41
|
livenessMs?: number;
|
|
40
|
-
/**
|
|
42
|
+
/** The ONE data bound (`WORKSPACE_SNAPSHOT_TIMEOUT_MS`; default = `WORKSPACE_SNAPSHOT_TIMEOUT_MS_DEFAULT`, owned by
|
|
43
|
+
* `workspace-space.ts`): the snapshot upload's and restore's wall-clock, the deadline this replica's commits write into
|
|
44
|
+
* their space-commit fence row (a reader judges another run's row by the deadline IN that row, never by this field), and
|
|
45
|
+
* a large file transfer's wall-clock. Every use reads this field — never a second number. */
|
|
41
46
|
dataTimeoutMs?: number;
|
|
47
|
+
/** The snapshot's exclusion table (`WORKSPACE_SNAPSHOT_EXCLUDE`, items already in the closed form — `workspace-space.ts`):
|
|
48
|
+
* the ONE tar upload ({@link RemoteK8sExecutionEnv.uploadSnapshot}, both lanes) passes `--exclude=./<item>` per item.
|
|
49
|
+
* Absent / empty ⇒ the upload command is what it always was. */
|
|
50
|
+
snapshotExclude?: readonly string[];
|
|
42
51
|
cpu?: string;
|
|
43
52
|
memory?: string;
|
|
44
53
|
retry?: {
|
|
@@ -58,8 +67,8 @@ export interface K8sEnvConfig {
|
|
|
58
67
|
* trifecta. The presigned URL is used only inside an adapter-issued exec at suspend/resume time, so it never
|
|
59
68
|
* reaches an agent turn.
|
|
60
69
|
*
|
|
61
|
-
* WORKSPACE SPACE (`workspacePersist`, default `off`
|
|
62
|
-
* RUNS, in its own sub-prefix `<prefix>/<
|
|
70
|
+
* WORKSPACE SPACE (`workspacePersist`, default `space`; `off` = a fresh workspace every run): the same store also carries the workspace ACROSS
|
|
71
|
+
* RUNS, in its own sub-prefix `<prefix>/<incarnation>/space/` — `space/latest` (body = the newest snapshotId) names the
|
|
63
72
|
* snapshot `connect()` restores; `destroy()` snapshots into `space/`, moves `latest`, keeps the newest `workspaceSnapshotKeep`
|
|
64
73
|
* (the same one upload / one download as suspend / resume, see `SnapshotTarget`). The pointer read / write, the listing and the deletes are
|
|
65
74
|
* WORKER-side requests to `endpoint` (the snapshot-BLOB offload dials the same `MINIO_ENDPOINT` from the worker).
|
|
@@ -74,7 +83,7 @@ export interface K8sEnvConfig {
|
|
|
74
83
|
presignTtlSec?: number;
|
|
75
84
|
/** The workspace-space policy (the capability fact `workspaceSpace`'s witness, handed in by the factory; the adapter
|
|
76
85
|
* never resolves defaults itself). Absent or `persist: "off"` ⇒ no persistence across runs. */
|
|
77
|
-
space?:
|
|
86
|
+
space?: WorkspaceSpacePolicy;
|
|
78
87
|
};
|
|
79
88
|
/**
|
|
80
89
|
* The PARK lane's partition (`<prefix>/<sessionId>/<snapshotId>.tar.gz`), set by {@link k8sExecutionEnvFactory} from
|
|
@@ -84,18 +93,22 @@ export interface K8sEnvConfig {
|
|
|
84
93
|
* it never decides who reads. Segment shape is validated in exactly one place ({@link RemoteK8sExecutionEnv.presignSnapshot}).
|
|
85
94
|
*/
|
|
86
95
|
sessionId?: string;
|
|
96
|
+
/** The SPACE lane's occupant — handed in per run by the factory binding (`boot/execution-env.ts`); see {@link SpaceOccupant}. */
|
|
97
|
+
spaceOccupant?: SpaceOccupant;
|
|
87
98
|
/**
|
|
88
|
-
* The
|
|
89
|
-
*
|
|
90
|
-
* (`
|
|
91
|
-
*
|
|
92
|
-
* (
|
|
93
|
-
*
|
|
94
|
-
* line
|
|
99
|
+
* The build-cache volume per workspace space (`WORKSPACE_CACHE=volume`; the capability fact `workspaceCache`'s witness, handed
|
|
100
|
+
* in by the factory — absent for a delegated child, whose env opens no space). Keyed by the SAME space key as the space lane
|
|
101
|
+
* (`spaceOccupant.incarnation`): connect finds / creates the space's claim (`workspaceCacheClaimOf`) and mounts it at `mount`
|
|
102
|
+
* — outside the workspace mount, so no snapshot ever covers it — and layers the cache env closed table under `podEnv`
|
|
103
|
+
* (an explicit operator value of the same name wins). A volume that cannot be had (create refused, not bound within
|
|
104
|
+
* `dataTimeoutMs`, no such session ⇒ no space key) ⇒ the pod starts WITHOUT it and without the cache env: `recordFailOpen` + one
|
|
105
|
+
* `workspace_cache_unavailable` line. An occupant read that FAULTED is not a miss: the run is refused, as on the space lane
|
|
106
|
+
* ({@link SpaceOccupant}). Absent ⇒ no claim request, the pod as before.
|
|
95
107
|
*/
|
|
96
|
-
|
|
108
|
+
cache?: WorkspaceCacheCapability;
|
|
97
109
|
/** Operator log seat (the e2b adapter's shape): `workspace_snapshot_failed` / `workspace_snapshot_prune_failed` /
|
|
98
|
-
* `workspace_space_unavailable` / `workspace_commit_abandoned` / `
|
|
110
|
+
* `workspace_space_unavailable` / `workspace_space_unreadable_occupant` / `workspace_commit_abandoned` / `workspace_latest_dangling` /
|
|
111
|
+
* `workspace_restore_failed` / `workspace_cache_fresh` / `workspace_cache_unavailable`. */
|
|
99
112
|
logger?: {
|
|
100
113
|
warn(event: string, fields?: Record<string, unknown>): void;
|
|
101
114
|
};
|
|
@@ -115,8 +128,10 @@ export interface K8sEnvConfig {
|
|
|
115
128
|
podSpecPatch?: PodSpecPatch;
|
|
116
129
|
/** RFC B5 (sema-internal server/docs/design/ENV-SELECT-AND-REGION-SOURCE.md 抽象点②): operator-trusted env injected into the
|
|
117
130
|
* sandbox container (e.g. `SEMA_PKG_SOURCE=cn|global` — the runtime package-source selector the image's
|
|
118
|
-
* pkg-source.sh hook materializes at start). Deployment
|
|
119
|
-
*
|
|
131
|
+
* pkg-source.sh hook materializes at start). Deployment config, NEVER task-controlled. This is the pod's ONE container-env
|
|
132
|
+
* seat: the region package-source set AND the deployer-named forwarding (`K8S_SANDBOX_ENV` keys, original values) arrive
|
|
133
|
+
* here already merged by the boot assembly (explicit names win). Values go into `containers[0].env[]` and nowhere else —
|
|
134
|
+
* never a command string, the workspace or a snapshot. Absent = the pod is byte-identical to today. */
|
|
120
135
|
podEnv?: Record<string, string>;
|
|
121
136
|
}
|
|
122
137
|
export { type PodSpecPatch, applyPodSpecPatch } from "./k8s-exec-protocol.js";
|
|
@@ -150,6 +165,45 @@ export interface K8sEnvDeps {
|
|
|
150
165
|
/** Worker-side object-store requests (the space pointer, listing, deletes). Default: global `fetch`. */
|
|
151
166
|
fetch?: typeof fetch;
|
|
152
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* One run's space occupant — the space key of both lanes keyed by it (the space lane, the cache volume) — a closed set of three
|
|
170
|
+
* readings of the session row's cell, read by id from the session store (`SessionStore.incarnationOf`) when the run's env is built:
|
|
171
|
+
* - known: `incarnation` — the partition (`<prefix>/<incarnation>/space/…`): the session OCCUPANT's incarnation token
|
|
172
|
+
* (`session-incarnation.ts`, the one owner of "is this the same occupant"). Why not the session id: the space lane is read by
|
|
173
|
+
* whoever runs next in the session, so its partition decides WHO reads — a session id deleted and registered again (by anyone)
|
|
174
|
+
* is a new occupant and starts from an empty workspace. Never logged whole: operator lines carry `incarnationLogPrefix`;
|
|
175
|
+
* `fence` — this env's seat on the space's commit fence (the run store's `workspace_space_commit` row,
|
|
176
|
+
* `workspace-space-commit.ts`): `connect()` waits on it, the run-end commit holds it;
|
|
177
|
+
* - absent: `unread: "incarnation_unknown"` — there is NO space for this run: the store answered that it has no such session
|
|
178
|
+
* (deleted / never registered), or this replica holds no commit fence to key one by (see `occupantOf`) ⇒ a lane keyed by it
|
|
179
|
+
* does not open (no restore, no save, no volume) and says so in one line;
|
|
180
|
+
* - faulted: `fault` — the read did not answer (it threw, or answered something that is not a token): the code of the store's
|
|
181
|
+
* error (never its text), `incarnation_read_failed` without one. A space we could not read is not "no space" ⇒ a lane keyed by
|
|
182
|
+
* it refuses this env (`RemoteK8sExecutionEnv.provision`, the one pod seat): the run never starts on a workspace it believes is its own.
|
|
183
|
+
* Never a fallback to the session id.
|
|
184
|
+
*/
|
|
185
|
+
export type SpaceOccupant = {
|
|
186
|
+
readonly incarnation: string;
|
|
187
|
+
readonly fence: SpaceCommitFence;
|
|
188
|
+
} | {
|
|
189
|
+
readonly unread: "incarnation_unknown";
|
|
190
|
+
} | {
|
|
191
|
+
readonly fault: string;
|
|
192
|
+
};
|
|
193
|
+
/** One failed stage of {@link RemoteK8sExecutionEnv.reclaimSpaceSnapshots}: whose partition (the space's `space/` or the park lane's), listing it, deleting from it,
|
|
194
|
+
* or the reclaim call's deadline passing before or while on it (`budget`: the rest was not attempted). */
|
|
195
|
+
export interface ReclaimFailure {
|
|
196
|
+
readonly lane: "space" | "park";
|
|
197
|
+
readonly stage: "list" | "delete" | "budget";
|
|
198
|
+
readonly detail: string;
|
|
199
|
+
}
|
|
200
|
+
/** The failed cache-volume half of a reclaim ({@link RemoteK8sExecutionEnv.reclaimSpaceCache}); `reason` is the closed word set of
|
|
201
|
+
* `workspace_cache_reclaim_failed` from the reclaim seat: the delete-by-label failed or answered non-2xx (`delete_failed`), or the reclaim call's
|
|
202
|
+
* deadline passed before it was sent or while it was in flight (`budget` — the same word the object half's `stage` uses for the same cause). */
|
|
203
|
+
export interface CacheReclaimFailure {
|
|
204
|
+
readonly reason: Exclude<CacheReclaimReason, "occupant_unreadable">;
|
|
205
|
+
readonly detail: string;
|
|
206
|
+
}
|
|
153
207
|
export { execSocketTlsOptions } from "./k8s-exec-protocol.js";
|
|
154
208
|
/** `Error`-or-event → message. Bun's ws shim emits browser-style ErrorEvents (NOT instanceof Error) whose
|
|
155
209
|
* String() is "[object ErrorEvent]" — that masked the real TLS failure for a whole drill. */
|
|
@@ -185,12 +239,24 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
185
239
|
private connectPromise?;
|
|
186
240
|
/** core `ExecutionEnv.homeDir`([ref]):本沙箱/目标机上执行用户的 home。见配置同名字段。 */
|
|
187
241
|
readonly homeDir?: string | undefined;
|
|
242
|
+
/** core 7.49.0 #1554:shell 子进程真拿到的 `CDPATH` 条目 —— 本车道的命令跑在 pod 里(镜像与 pod spec 的 env),本类不读那份 env ⇒ `"unknown"`(core 契约:说不出就说 unknown,
|
|
243
|
+
* 不许报一张没读过的空表;删除守卫因此对裸 `cd <相对名>` 之后的删除问人,而不是按「没有 CDPATH」放行)。 */
|
|
244
|
+
readonly cdPath: "unknown";
|
|
188
245
|
private readonly cfg;
|
|
189
246
|
private readonly deps;
|
|
247
|
+
/** The known occupant's incarnation (`undefined` = this run has no known occupant: a delegated child, an unread session, a direct construction) — the
|
|
248
|
+
* ownership mark of every snapshot id this env mints, both lanes ({@link mintSnapshotId}); the space lane's partition is the same reading. */
|
|
249
|
+
private readonly occupantIncarnation;
|
|
190
250
|
/** This env's space lane (resolved once from the policy + the occupant's incarnation; see {@link SpaceLane}). */
|
|
191
251
|
private readonly spaceLane;
|
|
192
252
|
/** `workspace_space_unavailable` is said once per env. */
|
|
193
253
|
private spaceUnavailableSaid;
|
|
254
|
+
/** `workspace_space_unreadable_occupant` is said once per env (a retried lazy connect is refused again, silently). */
|
|
255
|
+
private unreadableOccupantSaid;
|
|
256
|
+
/** This env's cache volume lane (resolved once from the cache policy + the space key; see {@link CacheLane}). */
|
|
257
|
+
private readonly cacheLane;
|
|
258
|
+
/** A cache volume that could not be had is counted and said once per env (a retried connect does not count again). */
|
|
259
|
+
private cacheUnavailableSaid;
|
|
194
260
|
private readonly logger?;
|
|
195
261
|
private readonly onWorkspaceSnapshot?;
|
|
196
262
|
constructor(config: K8sEnvConfig, deps?: K8sEnvDeps);
|
|
@@ -208,20 +274,47 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
208
274
|
private openExec;
|
|
209
275
|
/**
|
|
210
276
|
* Connect = the workspace SPACE's restore point. Space lane open ⇒ first wait out a commit still in flight
|
|
211
|
-
* ({@link awaitSpaceCommit}), then read `<prefix>/<incarnation>/space/latest`: present ⇒
|
|
212
|
-
* restore it through {@link restoreSnapshot} (the one tar download, shared with `resumeVM`;
|
|
213
|
-
*
|
|
214
|
-
* `connect_failed` — never a silent fresh workspace (a run that believes it resumed while it did not is
|
|
215
|
-
*
|
|
216
|
-
*
|
|
277
|
+
* ({@link awaitSpaceCommit}: the run store's commit row), then read `<prefix>/<incarnation>/space/latest`: present ⇒
|
|
278
|
+
* restore it through {@link restoreSnapshot} (the one tar download, shared with `resumeVM`; an archive that is gone ⇒
|
|
279
|
+
* loud `workspace_latest_dangling`, the pointer deleted, an empty workspace); absent (404) ⇒ a fresh pod; a read that
|
|
280
|
+
* FAILS ⇒ a loud `connect_failed` — never a silent fresh workspace (a run that believes it resumed while it did not is
|
|
281
|
+
* the worst form). Once the pod holds the workspace, this env arms its fence seat (its destroy will commit).
|
|
282
|
+
* Persistence off / no store ⇒ a fresh pod, exactly as before. Persistence on but no such session (the occupant absent) ⇒ a
|
|
283
|
+
* fresh pod and one `workspace_space_unavailable` line (no restore by any other key). The occupant read FAULTED ⇒ the same
|
|
284
|
+
* loud `connect_failed` as a failed `latest` read, no pod ({@link provision} refuses it — every entry that would create one).
|
|
217
285
|
*/
|
|
218
286
|
connect(config?: RemoteConnectConfig): Promise<ConnectResult>;
|
|
219
|
-
/**
|
|
287
|
+
/** This env holds a workspace its `destroy()` will commit ⇒ arm its seat on the space's commit fence (from here the
|
|
288
|
+
* run's terminal write fences the space; `workspace-space-commit.ts`). Each write of this run's row sets its deadline to
|
|
289
|
+
* now + the upload's own wall-clock bound (`dataTimeoutMs`). No-op unless the space lane is open. */
|
|
290
|
+
private armSpace;
|
|
291
|
+
/** The env no longer holds a workspace to commit (a commit ended, a park released the pod, or destroy found no pod) ⇒
|
|
292
|
+
* end its fence seat: the row this run wrote, if any, is deleted. A delete that fails leaves the row until its deadline —
|
|
293
|
+
* the next run waits at most that long and says `workspace_commit_abandoned` (the loud form of a stuck row). */
|
|
294
|
+
private endSpace;
|
|
295
|
+
/**
|
|
296
|
+
* One call to the space's commit fence (the run store), bounded like every other request this adapter makes
|
|
297
|
+
* (`rpcTimeoutMs`) and abortable: a store that never answers never hangs connect or destroy. The store call itself is
|
|
298
|
+
* not cancellable — a late answer is dropped; what it may have written dies at its own deadline (the fence's one rule).
|
|
299
|
+
* Rejects with {@link FenceCallAborted} on abort.
|
|
300
|
+
*/
|
|
301
|
+
private fenceCall;
|
|
302
|
+
/** The space lane is on but this run's session has no occupant (no such session — {@link SpaceOccupant}) — said once per env
|
|
303
|
+
* (at connect, or at a destroy with a pod). `reason` = the reading's word. */
|
|
220
304
|
private sayUnavailable;
|
|
305
|
+
/**
|
|
306
|
+
* A lane that is on is keyed by an occupant read that FAULTED (`unreadable`) ⇒ this env creates no pod: the refusal, in the one
|
|
307
|
+
* space-read failure form ({@link spaceReadFailed} — a failed `latest` / commit-row read says the same), naming the knob of every
|
|
308
|
+
* lane that refuses; one `workspace_space_unreadable_occupant{sessionId, code}` line per env. `code` = the store error's code,
|
|
309
|
+
* never its text. Not a knob: there is no honest way to run a space we could not read — a retry of the run reads it again.
|
|
310
|
+
*/
|
|
311
|
+
private refuseUnreadableOccupant;
|
|
221
312
|
/** Every space-lane operator line: the space is named by its incarnation's log prefix only, and free text (a curl
|
|
222
313
|
* stderr, a fetch error) passes {@link redactIncarnations} — the full token never reaches a log body. */
|
|
223
314
|
private spaceLine;
|
|
224
|
-
/** A fresh pod (create → Running → workspace root), bounded retry
|
|
315
|
+
/** A fresh pod (create → Running → workspace root), bounded retry — THE seat every pod of this env is created through
|
|
316
|
+
* ({@link connect} and, via {@link restoreSnapshot}, {@link resumeVM}). A lane keyed by an occupant read that faulted ⇒ refused
|
|
317
|
+
* here, before any request ({@link refuseUnreadableOccupant}): never retried (the reading is this env's, fixed at construction). */
|
|
225
318
|
private provision;
|
|
226
319
|
/**
|
|
227
320
|
* Lazy-connect on first use — 🔴 core NEVER calls `connect()` (verified: the Runner only calls `resumeVM` on
|
|
@@ -230,7 +323,38 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
230
323
|
* pod create; a failed connect clears the memo (next op retries); `destroyed` is terminal.
|
|
231
324
|
*/
|
|
232
325
|
private ensureConnected;
|
|
326
|
+
/**
|
|
327
|
+
* One pod creation attempt. With the cache volume open: first the space's claim ({@link attachCache} — found, or created),
|
|
328
|
+
* then the pod carrying it; a pod that only waits on a claim that does not bind within the data bound is deleted and ONE pod
|
|
329
|
+
* without the volume is created in its place (the run starts; the miss is counted). Every other failure is the attempt's.
|
|
330
|
+
*/
|
|
233
331
|
private connectOnce;
|
|
332
|
+
/** The cache volume for this pod creation, or `undefined` (off, or not to be had — counted and said there). */
|
|
333
|
+
private attachCache;
|
|
334
|
+
/** Find the space's claim, or create it (labelled with the space digest; size = the policy's; the cluster's default
|
|
335
|
+
* StorageClass). `fresh` = this call created it. A concurrent creator (409) is a find. Any other answer is a miss. */
|
|
336
|
+
private ensureCacheClaim;
|
|
337
|
+
/** Is the claim bound? (A read that fails counts as not bound — the bind deadline still decides.) */
|
|
338
|
+
private cacheClaimBound;
|
|
339
|
+
/**
|
|
340
|
+
* Reclaim one ended space's cache volume: delete the claims labelled `sema.space=<its digest>` (one deletecollection —
|
|
341
|
+
* a claim still mounted by a terminating pod is kept by the cluster's pvc-protection until the pod is gone). None ⇒ zero
|
|
342
|
+
* action. The request runs under the reclaim call's `deadline` (built once per call by {@link createSpaceReclaimer}): already passed ⇒
|
|
343
|
+
* nothing is sent; passing while the request is in flight ⇒ the request is cut (its own `rpcTimeoutMs` stays as this control-plane request's socket-idle bound; the object half's `storeFetch` bound is a whole-request wall clock — the deadline is what bounds both). Answers the outcome; never throws. Used by {@link createSpaceReclaimer} only (a control-plane env, no pod).
|
|
344
|
+
*/
|
|
345
|
+
reclaimSpaceCache(spaceKey: string, deadline: AbortSignal): Promise<{
|
|
346
|
+
ok: true;
|
|
347
|
+
deleted: number;
|
|
348
|
+
} | ({
|
|
349
|
+
ok: false;
|
|
350
|
+
} & CacheReclaimFailure)>;
|
|
351
|
+
/** The one spelling of "the reclaim call's deadline passed" — both halves of the reclaim (volume and objects) say it with this. */
|
|
352
|
+
private reclaimDeadlinePassed;
|
|
353
|
+
/** The cache volume could not be had: counted (`recordFailOpen`) and one operator line — once per env. */
|
|
354
|
+
private cacheUnavailable;
|
|
355
|
+
/** Create one pod (with the cache volume when given) and wait for it to run. A {@link CacheMiss} = a pod carrying the
|
|
356
|
+
* volume did not become ready (its claim unbound past the bind deadline, or bound and still not ready) — already deleted. */
|
|
357
|
+
private createPod;
|
|
234
358
|
/**
|
|
235
359
|
* Build a validated, presigned URL for a snapshot object of either lane (see {@link SnapshotTarget}) — the ONE place a
|
|
236
360
|
* snapshot object key is spelled, and the one place each lane's partition is chosen (park = the session id, space = the
|
|
@@ -243,6 +367,30 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
243
367
|
* snapshotKey()+s3Presign() pair so the two call sites can't diverge.
|
|
244
368
|
*/
|
|
245
369
|
private presignSnapshot;
|
|
370
|
+
/** A lane's partition prefix inside the bucket — the ONE place it is spelled (`presignSnapshot` signs under it; the reclaim reads the listing against it).
|
|
371
|
+
* The partition segment is validated by `presignSnapshot` before a URL is signed. */
|
|
372
|
+
private snapshotBase;
|
|
373
|
+
/**
|
|
374
|
+
* Reclaim an ENDED space's snapshot objects (the reclaim seat's half of {@link createSpaceReclaimer}): list the whole `space/` partition
|
|
375
|
+
* of `spaceKey` (archives, `latest`, anything else this adapter ever wrote there) and, when this env is keyed by a session
|
|
376
|
+
* (`cfg.sessionId`), the park lane's `<prefix>/<sessionId>/*.tar.gz` whose names carry the ended occupant's ownership mark (see the two
|
|
377
|
+
* paragraphs below: unmarked / `none` objects stay) — the session is gone, so nothing can ever read either again.
|
|
378
|
+
* Each object is deleted over the same presigned DELETE the prune uses. Never throws: answers the failed stages (an empty answer = all
|
|
379
|
+
* gone, or nothing was there). An object whose name this adapter could not have written, or that sits deeper than the lane's own
|
|
380
|
+
* partition, is left alone. A truncated listing (more than 1000 objects) deletes what it saw and says so.
|
|
381
|
+
* Every LIST and DELETE of both partitions runs under the reclaim call's `deadline` — ONE absolute deadline per call of {@link createSpaceReclaimer}, built at its
|
|
382
|
+
* entry from `dataTimeoutMs` (the snapshot time bound; no knob of its own) and shared by every space of the call and by the cache-volume half; this method builds
|
|
383
|
+
* none of its own. Passed before this space is reached ⇒ nothing is sent; passing on the way ⇒ the request in flight is cut and nothing further is sent. Either
|
|
384
|
+
* way the answer carries one `stage: "budget"` failure for the lane it stopped on: what was not yet deleted stays for the bucket's lifecycle rule. (Each request
|
|
385
|
+
* also keeps its own `rpcTimeoutMs`; the deadline is what bounds their sum.)
|
|
386
|
+
* The park partition is keyed by the SESSION ID, which another tenant may register again once the old row is gone — and a reclaim can run
|
|
387
|
+
* late (a retention sweep walks many sessions). So the park lane deletes only objects whose NAME carries the ended occupant's ownership mark
|
|
388
|
+
* ({@link snapshotNameOwnedBy}; the mark is minted into every snapshot id by {@link mintSnapshotId}, `uploadSnapshot`): a later occupant's
|
|
389
|
+
* objects carry its own mark, and a name with no mark (written before marks existed, or by a run with no occupant) is nobody's to delete here —
|
|
390
|
+
* the bucket's lifecycle rule is its fallback. The judgment reads NO clock (not the object store's stamp, not this replica's). The `space/`
|
|
391
|
+
* partition needs no such guard (its partition is the incarnation, which a new occupant does not share).
|
|
392
|
+
*/
|
|
393
|
+
reclaimSpaceSnapshots(spaceKey: string, deadline: AbortSignal): Promise<ReclaimFailure[]>;
|
|
246
394
|
/** Worker-side object-store request, bounded by the control-plane RPC wall-clock — or a tighter caller deadline — and
|
|
247
395
|
* the caller's abort. */
|
|
248
396
|
private storeFetch;
|
|
@@ -256,12 +404,20 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
256
404
|
* caller deadline tighter than the RPC bound. */
|
|
257
405
|
private writePointer;
|
|
258
406
|
/**
|
|
259
|
-
* Wait out the previous run's space commit
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
407
|
+
* Wait out the previous run's space commit — its row in the run store's `workspace_space_commit` (the ONE seat of
|
|
408
|
+
* "a commit of this space is in flight", `workspace-space-commit.ts`; written by that run's terminal write before the
|
|
409
|
+
* session's slot was released, so a next run can never start before it). The row carries its WRITER's deadline; this
|
|
410
|
+
* reader brings no number of its own: alive (`spaceCommitAlive`) ⇒ re-read every {@link SPACE_COMMIT_POLL_MS} until the
|
|
411
|
+
* row is gone. Read dead = a run whose worker died between its terminal write and its destroy (state `pending`), inside
|
|
412
|
+
* the upload, or while moving `latest` — or a writer whose renewal lands at this very moment. So the read alone
|
|
413
|
+
* decides nothing: the reader ABANDONS the row ({@link SpaceCommitFence.abandon} — one locked conditional delete, judged
|
|
414
|
+
* on the store's clock under the row's lock). Deleted ⇒ one `workspace_commit_abandoned{space, remainingMs, state}` line
|
|
415
|
+
* (`remainingMs` ≤ 0: how far past the writer's deadline, as read) and `latest` (still the previous COMPLETE snapshot)
|
|
416
|
+
* is restored; not deleted (renewed / taken / gone) ⇒ re-read at once. A row read or an abandon that fails is a loud
|
|
417
|
+
* `connect_failed` (never a guess that no commit is in flight).
|
|
418
|
+
* How long connect may wait here has no bound of its own (no knob, and NOT this replica's `dataTimeoutMs`): it is the
|
|
419
|
+
* deadline the writer keeps in its row — each of that run's at most three writes (terminal, commit start, publish) sets
|
|
420
|
+
* it to now + the writer's bound — plus the run's abort signal, which ends the wait at once.
|
|
265
421
|
*/
|
|
266
422
|
private awaitSpaceCommit;
|
|
267
423
|
/**
|
|
@@ -292,7 +448,9 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
292
448
|
}>;
|
|
293
449
|
/**
|
|
294
450
|
* The ONE tar upload (both lanes): tar the workspace to a pod-local file, presigned PUT into the lane's namespace,
|
|
295
|
-
* release the pod. `suspendVM` = lane `park`; the run-end space snapshot = lane `space` (its open space).
|
|
451
|
+
* release the pod. `suspendVM` = lane `park`; the run-end space snapshot = lane `space` (its open space). The exclusion
|
|
452
|
+
* table (`snapshotExclude`) is applied HERE and nowhere else — each item one `--exclude=./<item>` argument through the one
|
|
453
|
+
* `shellQuote`, anchored at the workspace root (`-C <mount> .` names every member `./…`); no table ⇒ the command as it always was.
|
|
296
454
|
*/
|
|
297
455
|
private uploadSnapshot;
|
|
298
456
|
/**
|
|
@@ -309,12 +467,25 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
309
467
|
resumeVM(snapshotId: SnapshotId, options?: VmLifecycleOptions): Promise<ConnectResult>;
|
|
310
468
|
/**
|
|
311
469
|
* The ONE tar download (both lanes): provision a FRESH pod, download + untar the lane's archive into the workspace.
|
|
312
|
-
*
|
|
313
|
-
*
|
|
470
|
+
* A missing archive (404) starts from the clean workspace on both lanes — what differs is what it means:
|
|
471
|
+
* - park: a plan-only suspend has no object — nothing to say;
|
|
472
|
+
* - space: `latest` dangles (its pointer is only written after a successful upload, so the archive was removed later —
|
|
473
|
+
* by the bucket's lifecycle rule or a hand): the data is gone by an operator rule ⇒ loud and continuable —
|
|
474
|
+
* {@link dropDanglingLatest} (one `workspace_latest_dangling` line, the pointer deleted); the run's own run-end
|
|
475
|
+
* snapshot then becomes the new `latest`.
|
|
314
476
|
* A restore that does not complete releases the pod it provisioned: no tool and no run-end snapshot may ever see a
|
|
315
|
-
* torn
|
|
477
|
+
* torn workspace. On the space lane that failure is the archive `latest` names being THERE but not coming back
|
|
478
|
+
* ({@link RestoreFailureClass}): the run is refused all the same — never an empty workspace (the user's changes are still in
|
|
479
|
+
* the bucket) — with exactly one `workspace_restore_failed` line and a refusal naming the two ways out.
|
|
316
480
|
*/
|
|
317
481
|
private restoreSnapshot;
|
|
482
|
+
/**
|
|
483
|
+
* `latest` names an archive that is not there (S-1438): one `workspace_latest_dangling{space, key, deleted}` line and the
|
|
484
|
+
* pointer is deleted (the next run must not trip on it again); the run continues on the empty workspace. A pointer
|
|
485
|
+
* delete that fails rides on the same line (`deleted: false` + the error) — the next run then says it once more.
|
|
486
|
+
* This env is the space's only writer here (the commit fence admits one per space), so the delete is a plain one.
|
|
487
|
+
*/
|
|
488
|
+
private dropDanglingLatest;
|
|
318
489
|
/**
|
|
319
490
|
* Honest post-resume consistency ([ref] §5 council #8): the workspace FILES are restored byte-for-byte
|
|
320
491
|
* from the snapshot, so read-before-edit hashing already lines up — there's nothing to re-fetch. In-VM
|
|
@@ -334,15 +505,18 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
334
505
|
* Delete the sandbox pod. Idempotent, never throws. Space lane open and a pod exists ⇒ FIRST the run-end space
|
|
335
506
|
* snapshot ({@link persistWorkspace}); its failure is loud and never blocks the delete (the pod lifetime is the
|
|
336
507
|
* leak guard). A run that never connected has no workspace to save — `latest` stays where it was. Space lane
|
|
337
|
-
* unavailable (
|
|
508
|
+
* unavailable (no such session) ⇒ nothing is saved (the once-per-env line says so); unreadable ⇒ there is no pod
|
|
509
|
+
* ({@link provision} refused every creation).
|
|
338
510
|
*/
|
|
339
511
|
destroy(): Promise<void>;
|
|
340
512
|
cleanup(): Promise<void>;
|
|
341
513
|
/**
|
|
342
|
-
* Run-end space snapshot
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
514
|
+
* Run-end space snapshot, under the space's commit fence (the run store's row, `workspace-space-commit.ts`): the fence
|
|
515
|
+
* first (this run's row moves to its upload state; lost ⇒ no upload, loud) → the one tar upload ({@link uploadSnapshot},
|
|
516
|
+
* lane `space` — it also releases the pod) → the fence again (this run's row moves to its publishing state; lost ⇒
|
|
517
|
+
* `latest` is not published, loud) → move `latest` → keep N → the row is deleted (always, saved or failed).
|
|
518
|
+
* A lost fence, an upload or a pointer failure ⇒ one `workspace_snapshot_failed` line + the `failed` outcome; `latest`
|
|
519
|
+
* keeps naming the previous snapshot, so the next run restores THAT — never an empty workspace dressed up as a restore.
|
|
346
520
|
*/
|
|
347
521
|
private persistWorkspace;
|
|
348
522
|
private execUrl;
|
|
@@ -457,6 +631,20 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
457
631
|
* 车PL:buffered `exec` 上守卫触发 ⇒ `spawn_error`)。 */
|
|
458
632
|
private workingDirOf;
|
|
459
633
|
}
|
|
634
|
+
/**
|
|
635
|
+
* The ONE reclaim seat for an ended space ({@link SpaceReclaimer}) — called where a space ENDS (the session-delete coordinator and the
|
|
636
|
+
* managed-retention sweep). For each ended space it reclaims everything the space left behind, over this deployment's k8s config (it
|
|
637
|
+
* never creates a pod): its build-cache volume (the claims labelled by the space key; only when the cache volume is configured),
|
|
638
|
+
* its snapshot objects — the `space/` partition (archives + `latest`) and the park lane's objects in `<prefix>/<sessionId>/` that carry the ended occupant's mark (only when a
|
|
639
|
+
* snapshot store is configured). Each part is independent: one part failing never skips another. A failed part ⇒ one operator line
|
|
640
|
+
* (`workspace_cache_reclaim_failed{reason}` for the volume, `workspace_space_reclaim_failed{stage}` for the objects) + `recordFailOpen`; never throws.
|
|
641
|
+
* ONE call = ONE absolute deadline (the snapshot bound, `dataTimeoutMs`; no knob of its own), built at the entry and nowhere else: every request of the call — each
|
|
642
|
+
* space's cache-volume delete and every LIST / DELETE of its objects — runs under it. A session delete calls with its one space; the managed-retention sweep calls
|
|
643
|
+
* once per domain per round with the spaces that round ended there. When the deadline passes the request in flight is cut and every space not yet done is still
|
|
644
|
+
* walked but sends nothing: its volume says `reason: "budget"`, its objects `stage: "budget"` (the volume stays for deletion by label, the objects for the
|
|
645
|
+
* bucket's lifecycle rule). A caller that must not wait for the seat (the session-delete answer) starts it and does not await it.
|
|
646
|
+
*/
|
|
647
|
+
export declare function createSpaceReclaimer(config: K8sEnvConfig, deps?: K8sEnvDeps): SpaceReclaimer;
|
|
460
648
|
/**
|
|
461
649
|
* `ExecutionEnvFactory` for the k8s pod-sandbox backend — one fresh (unconnected) env per task; the pod is
|
|
462
650
|
* created lazily on connect and deleted by `destroy()` (Runner-owned lifetime; `activeDeadlineSeconds` is the
|