@sema-agent/server 7.125.1 → 7.127.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.
Files changed (275) hide show
  1. package/MIGRATION.md +229 -2
  2. package/README.md +2 -2
  3. package/README.zh-CN.md +2 -2
  4. package/USAGE.md +346 -145
  5. package/deploy/sema-up/README.md +1 -1
  6. package/deploy/sema-up/chart/templates/networkpolicy-sandbox.yaml +57 -0
  7. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +32 -1
  8. package/deploy/sema-up/chart/templates/server-v4pro.yaml +3 -2
  9. package/deploy/sema-up/chart/templates/server.yaml +2 -1
  10. package/deploy/sema-up/chart/values.yaml +22 -0
  11. package/deploy/sema-up/cluster-up.sh +10 -10
  12. package/deploy/sema-up/kube-up.sh +20 -13
  13. package/deploy/sema-up/lib/dataroot.sh +1 -1
  14. package/deploy/sema-up/lib/envf.sh +5 -5
  15. package/deploy/sema-up/lib/flagshape.sh +5 -5
  16. package/deploy/sema-up/lib/lanes.py +1 -1
  17. package/deploy/sema-up/lib/node-addrs.sh +42 -0
  18. package/deploy/sema-up/lib/parse-machines.py +2 -2
  19. package/deploy/sema-up/lib/shell-access.sh +1 -1
  20. package/deploy/sema-up/preflight.sh +4 -4
  21. package/deploy/sema-up/sema-up.sh +33 -33
  22. package/deploy/sema-up/smoke.sh +3 -3
  23. package/deploy/sema-up/stack/compose.yaml +1 -1
  24. package/deploy/sema-up/stack/sema-stack-cluster.yaml +26 -3
  25. package/dist/approval-card.d.ts +20 -4
  26. package/dist/approval-content-kind.d.ts +38 -22
  27. package/dist/approval-content-kind.js +20 -4
  28. package/dist/audit.js +3 -0
  29. package/dist/bench/s1/arms.d.ts +9 -37
  30. package/dist/bench/s1/arms.js +4 -61
  31. package/dist/bench/s1/live-deps.d.ts +4 -10
  32. package/dist/bench/s1/live-deps.js +1 -222
  33. package/dist/bench/s1/oracle.d.ts +1 -1
  34. package/dist/bench/s1/reviewer.d.ts +1 -1
  35. package/dist/bench/s1/row.d.ts +5 -7
  36. package/dist/bench/s1/row.js +0 -2
  37. package/dist/bench/s1/run-firm.d.ts +2 -2
  38. package/dist/bench/s1/run-firm.js +1 -4
  39. package/dist/bench/s1/runner-ctx.d.ts +17 -29
  40. package/dist/bench/s1/runner-ctx.js +3 -7
  41. package/dist/bench/s1/tasks.d.ts +9 -46
  42. package/dist/bench/s1/tasks.js +4 -63
  43. package/dist/boot/execution-env.d.ts +38 -26
  44. package/dist/boot/execution-env.js +87 -41
  45. package/dist/boot/leader.d.ts +2 -1
  46. package/dist/boot/memory-boundary.d.ts +1 -1
  47. package/dist/boot/memory-consolidation.d.ts +37 -3
  48. package/dist/boot/memory-consolidation.js +18 -2
  49. package/dist/boot/parked-revive-gate.d.ts +0 -3
  50. package/dist/boot/parked-revive-gate.js +2 -2
  51. package/dist/boot/resolve-spec.d.ts +0 -2
  52. package/dist/boot/resolve-spec.js +12 -17
  53. package/dist/boot/retention-lane.d.ts +12 -1
  54. package/dist/boot/retention-lane.js +2 -0
  55. package/dist/boot/runner-deps.d.ts +7 -6
  56. package/dist/boot/runner-deps.js +5 -16
  57. package/dist/boot/session-faces.d.ts +7 -2
  58. package/dist/boot/session-faces.js +37 -1
  59. package/dist/boot/stage-03-store-backend.d.ts +1 -0
  60. package/dist/boot/stage-03-store-backend.js +2 -2
  61. package/dist/boot/stage-04-stores.d.ts +1 -0
  62. package/dist/boot/stage-05-execution-env.d.ts +2 -2
  63. package/dist/boot/stage-05-execution-env.js +7 -4
  64. package/dist/boot/stage-06-runners.d.ts +2 -3
  65. package/dist/boot/stage-06-runners.js +13 -9
  66. package/dist/boot/stage-07-capability-layer.d.ts +2 -3
  67. package/dist/boot/stage-07-capability-layer.js +3 -4
  68. package/dist/boot/stage-08-reapers.d.ts +2 -3
  69. package/dist/boot/stage-08-reapers.js +2 -2
  70. package/dist/boot/stage-09-leader.d.ts +2 -3
  71. package/dist/boot/stage-09-leader.js +2 -1
  72. package/dist/boot/stage-10-http-server.d.ts +2 -3
  73. package/dist/boot/stage-10-http-server.js +2 -2
  74. package/dist/boot/stores.d.ts +1 -0
  75. package/dist/boot/stores.js +4 -3
  76. package/dist/boot/web-admin.d.ts +5 -0
  77. package/dist/boot/web-admin.js +28 -14
  78. package/dist/brain.d.ts +2 -2
  79. package/dist/brain.js +1 -1
  80. package/dist/capabilities/builtin-tools.d.ts +2 -1
  81. package/dist/capabilities/composite-members.d.ts +1 -1
  82. package/dist/capabilities/composite-members.js +1 -1
  83. package/dist/capabilities/hands-lane.d.ts +9 -6
  84. package/dist/capabilities/hands-lane.js +2 -2
  85. package/dist/capabilities/memory-notice.d.ts +27 -22
  86. package/dist/capabilities/memory-notice.js +15 -17
  87. package/dist/capabilities/scenarios.d.ts +9 -14
  88. package/dist/capabilities/scenarios.js +5 -50
  89. package/dist/capability-facts.d.ts +22 -11
  90. package/dist/capability-facts.js +13 -6
  91. package/dist/config-catalog.js +14 -5
  92. package/dist/config-provider.d.ts +5 -1
  93. package/dist/config-provider.js +23 -4
  94. package/dist/config-types.d.ts +19 -2
  95. package/dist/config.js +41 -6
  96. package/dist/deployment-governance.d.ts +19 -21
  97. package/dist/deployment-governance.js +7 -8
  98. package/dist/env-name-allowlist-knobs.d.ts +5 -5
  99. package/dist/env-name-allowlist-knobs.js +1 -0
  100. package/dist/execution-lane-caps.d.ts +12 -0
  101. package/dist/execution-lane-caps.js +18 -4
  102. package/dist/fleet/subagent-tail-bus.js +15 -5
  103. package/dist/hooks/hook-runner.d.ts +84 -30
  104. package/dist/hooks/hook-runner.js +74 -41
  105. package/dist/http/admission.js +17 -10
  106. package/dist/http/durable-run-leg.d.ts +5 -4
  107. package/dist/http/durable-run-leg.js +2 -4
  108. package/dist/http/idempotency.d.ts +28 -6
  109. package/dist/http/idempotency.js +10 -2
  110. package/dist/http/principal-gate.d.ts +14 -0
  111. package/dist/http/principal-gate.js +7 -0
  112. package/dist/http/request-guards.d.ts +2 -1
  113. package/dist/http/request-guards.js +14 -14
  114. package/dist/http/resume-legs.d.ts +2 -2
  115. package/dist/http/resume-legs.js +20 -10
  116. package/dist/http/route-ctx.d.ts +15 -7
  117. package/dist/http/routes/a2a-serve.js +11 -7
  118. package/dist/http/routes/approvals-assistant.js +18 -3
  119. package/dist/http/routes/background.d.ts +1 -1
  120. package/dist/http/routes/background.js +1 -1
  121. package/dist/http/routes/capabilities.js +10 -2
  122. package/dist/http/routes/mcp-reconnect.js +1 -1
  123. package/dist/http/routes/runs.d.ts +4 -3
  124. package/dist/http/routes/runs.js +35 -14
  125. package/dist/http/routes/session-sync.js +14 -11
  126. package/dist/http/routes/sessions.js +9 -9
  127. package/dist/http/routes/side-query.js +6 -19
  128. package/dist/http/routes/tasks.js +29 -19
  129. package/dist/http/routes/workflows.js +8 -0
  130. package/dist/http/server.d.ts +4 -0
  131. package/dist/http/server.js +3 -5
  132. package/dist/http/submit-replay.d.ts +15 -8
  133. package/dist/http/submit-replay.js +6 -0
  134. package/dist/http/wire-types.d.ts +7 -9
  135. package/dist/index.d.ts +1 -1
  136. package/dist/index.js +1 -1
  137. package/dist/internal-run-env.d.ts +30 -0
  138. package/dist/internal-run-env.js +11 -0
  139. package/dist/leader/wire.js +3 -2
  140. package/dist/lsp/manager.js +1 -1
  141. package/dist/memory-scope.d.ts +19 -7
  142. package/dist/memory-scope.js +25 -8
  143. package/dist/observability/fail-open.d.ts +31 -3
  144. package/dist/observability/fail-open.js +31 -3
  145. package/dist/observability/metrics.d.ts +0 -1
  146. package/dist/observability/metrics.js +1 -4
  147. package/dist/observability/run-terminal.js +72 -4
  148. package/dist/permission-rule-vocab.d.ts +3 -2
  149. package/dist/permission-rule-vocab.js +1 -1
  150. package/dist/plan-cache-probe.d.ts +3 -2
  151. package/dist/plugins/approval-ask-store-sql.d.ts +1 -1
  152. package/dist/plugins/caching-session-store.d.ts +42 -22
  153. package/dist/plugins/caching-session-store.js +19 -9
  154. package/dist/plugins/checkpoint-store-sql.d.ts +42 -28
  155. package/dist/plugins/checkpoint-store-sql.js +57 -11
  156. package/dist/plugins/file-run-store.d.ts +25 -21
  157. package/dist/plugins/file-run-store.js +70 -34
  158. package/dist/plugins/k8s-exec-protocol.js +3 -0
  159. package/dist/plugins/local-checkpoint-store.d.ts +8 -5
  160. package/dist/plugins/local-checkpoint-store.js +5 -15
  161. package/dist/plugins/local-session-store.d.ts +66 -7
  162. package/dist/plugins/local-session-store.js +168 -101
  163. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +1 -1
  164. package/dist/plugins/memory-run-store.d.ts +19 -3
  165. package/dist/plugins/memory-run-store.js +38 -3
  166. package/dist/plugins/one-time-migrations.d.ts +165 -39
  167. package/dist/plugins/one-time-migrations.js +51 -144
  168. package/dist/plugins/pg-pool.js +11 -1
  169. package/dist/plugins/pg-session-storage.d.ts +2 -0
  170. package/dist/plugins/pg-session-storage.js +9 -5
  171. package/dist/plugins/remote-env-adb.d.ts +3 -0
  172. package/dist/plugins/remote-env-adb.js +1 -0
  173. package/dist/plugins/remote-env-device.d.ts +3 -0
  174. package/dist/plugins/remote-env-device.js +1 -0
  175. package/dist/plugins/remote-env-e2b.d.ts +4 -0
  176. package/dist/plugins/remote-env-e2b.js +2 -1
  177. package/dist/plugins/remote-env-host.d.ts +23 -1
  178. package/dist/plugins/remote-env-host.js +20 -2
  179. package/dist/plugins/remote-env-k8s.d.ts +264 -41
  180. package/dist/plugins/remote-env-k8s.js +551 -82
  181. package/dist/plugins/remote-env-local-docker.d.ts +4 -0
  182. package/dist/plugins/remote-env-local-docker.js +2 -1
  183. package/dist/plugins/remote-env-ssh.d.ts +3 -0
  184. package/dist/plugins/remote-env-ssh.js +1 -0
  185. package/dist/plugins/retention-store-sql.d.ts +4 -1
  186. package/dist/plugins/retention-store-sql.js +11 -9
  187. package/dist/plugins/run-store-sql.d.ts +44 -6
  188. package/dist/plugins/run-store-sql.js +92 -27
  189. package/dist/plugins/session-capture-record-store-sql.d.ts +2 -3
  190. package/dist/plugins/session-store.d.ts +4 -3
  191. package/dist/plugins/session-store.js +1 -1
  192. package/dist/plugins/sql-driver.d.ts +2 -1
  193. package/dist/plugins/store-backend.d.ts +12 -5
  194. package/dist/plugins/store-backend.js +13 -4
  195. package/dist/plugins/store-contracts.d.ts +20 -3
  196. package/dist/plugins/store-open-wait.js +3 -0
  197. package/dist/plugins/tidb-pool.js +15 -3
  198. package/dist/plugins/tidb-session-storage.js +3 -2
  199. package/dist/plugins/tidb-session-store.d.ts +2 -0
  200. package/dist/plugins/tidb-session-store.js +6 -2
  201. package/dist/request-key-closure.d.ts +32 -7
  202. package/dist/request-key-closure.js +25 -9
  203. package/dist/router/route-orchestration.d.ts +3 -11
  204. package/dist/router/route-orchestration.js +0 -4
  205. package/dist/rule-entry-list.js +0 -4
  206. package/dist/rules-consent.js +3 -0
  207. package/dist/run-acceptance.d.ts +51 -0
  208. package/dist/run-acceptance.js +27 -0
  209. package/dist/run-local.d.ts +5 -4
  210. package/dist/run-local.js +23 -17
  211. package/dist/runs.d.ts +7 -11
  212. package/dist/runs.js +6 -16
  213. package/dist/sandbox-egress-policy.d.ts +61 -0
  214. package/dist/sandbox-egress-policy.js +235 -0
  215. package/dist/security.d.ts +7 -0
  216. package/dist/security.js +5 -0
  217. package/dist/server-secret-env.d.ts +10 -4
  218. package/dist/server-secret-env.js +4 -1
  219. package/dist/session-error-wire.d.ts +2 -2
  220. package/dist/session-incarnation.d.ts +46 -2
  221. package/dist/session-incarnation.js +14 -3
  222. package/dist/session-sync-kernel.d.ts +10 -3
  223. package/dist/session-titler.d.ts +5 -3
  224. package/dist/spec-fields.d.ts +24 -8
  225. package/dist/spec-fields.js +21 -2
  226. package/dist/task-workflow.d.ts +6 -4
  227. package/dist/task-workflow.js +3 -2
  228. package/dist/tool-approval.d.ts +78 -6
  229. package/dist/tool-approval.js +7 -3
  230. package/dist/trace/core-keyset-guard.d.ts +6 -5
  231. package/dist/trace/engine-notice-wire.d.ts +26 -1
  232. package/dist/trace/engine-notice-wire.js +7 -2
  233. package/dist/trace/ledger-events.d.ts +9 -3
  234. package/dist/trace/ledger-sink.d.ts +7 -0
  235. package/dist/trace/ledger-sink.js +14 -4
  236. package/dist/trace/project.d.ts +57 -11
  237. package/dist/trace/project.js +54 -8
  238. package/dist/trace/self-minted-wire.d.ts +94 -2
  239. package/dist/trace/self-minted-wire.js +28 -0
  240. package/dist/trace/tool-output-media.d.ts +26 -0
  241. package/dist/trace/tool-output-media.js +57 -0
  242. package/dist/trace/wire-projection-faces.d.ts +7 -1
  243. package/dist/trace/wire-projection-faces.js +2 -1
  244. package/dist/web-admin-client/apply-effective.d.ts +13 -7
  245. package/dist/web-admin-client/apply-effective.js +11 -26
  246. package/dist/web-admin-client/apply-ledger.d.ts +2 -2
  247. package/dist/web-admin-client/candidate-gate.d.ts +39 -4
  248. package/dist/web-admin-client/candidate-gate.js +9 -2
  249. package/dist/web-admin-client/facade.d.ts +2 -2
  250. package/dist/web-admin-client/http-client.d.ts +3 -1
  251. package/dist/web-admin-client/http-client.js +4 -3
  252. package/dist/web-admin-client/read-warnings.d.ts +33 -5
  253. package/dist/web-admin-client/read-warnings.js +41 -6
  254. package/dist/web-admin-client/types.d.ts +11 -5
  255. package/dist/workspace-cache.d.ts +45 -0
  256. package/dist/workspace-cache.js +30 -0
  257. package/dist/workspace-space-commit.d.ts +116 -0
  258. package/dist/workspace-space-commit.js +117 -0
  259. package/dist/workspace-space.d.ts +75 -9
  260. package/dist/workspace-space.js +52 -1
  261. package/package.json +4 -4
  262. package/dist/boot/session-incarnation-seat.d.ts +0 -41
  263. package/dist/boot/session-incarnation-seat.js +0 -69
  264. package/dist/capabilities/code-review-council.d.ts +0 -49
  265. package/dist/capabilities/code-review-council.js +0 -111
  266. package/dist/capabilities/collab-wire.d.ts +0 -36
  267. package/dist/capabilities/collab-wire.js +0 -2
  268. package/dist/capabilities/collab-workflows.d.ts +0 -47
  269. package/dist/capabilities/collab-workflows.js +0 -90
  270. package/dist/capabilities/prompts/code-review.d.ts +0 -25
  271. package/dist/capabilities/prompts/code-review.js +0 -39
  272. package/dist/capabilities/prompts/team.d.ts +0 -10
  273. package/dist/capabilities/prompts/team.js +0 -6
  274. package/dist/capabilities/team.d.ts +0 -113
  275. package/dist/capabilities/team.js +0 -70
@@ -1,4 +1,7 @@
1
- import type { WorkspaceSyncCapability } from "../workspace-space.js";
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";
4
+ import { type SandboxEgressGap, type SandboxIpv6 } from "../sandbox-egress-policy.js";
2
5
  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
6
  import type { PodSpecPatch } from "./k8s-exec-protocol.js";
4
7
  type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
@@ -37,10 +40,20 @@ export interface K8sEnvConfig {
37
40
  rpcTimeoutMs?: number;
38
41
  /** Zero-progress liveness bound for commands (no output AND not finished ⇒ hang). */
39
42
  livenessMs?: number;
40
- /** File-transfer bound. */
43
+ /** The ONE data bound (`WORKSPACE_SNAPSHOT_TIMEOUT_MS`; default = `WORKSPACE_SNAPSHOT_TIMEOUT_MS_DEFAULT`, owned by
44
+ * `workspace-space.ts`): the snapshot upload's and restore's wall-clock, the deadline this replica's commits write into
45
+ * their space-commit fence row (a reader judges another run's row by the deadline IN that row, never by this field), and
46
+ * a large file transfer's wall-clock. Every use reads this field — never a second number. */
41
47
  dataTimeoutMs?: number;
48
+ /** The snapshot's exclusion table (`WORKSPACE_SNAPSHOT_EXCLUDE`, items already in the closed form — `workspace-space.ts`):
49
+ * the ONE tar upload ({@link RemoteK8sExecutionEnv.uploadSnapshot}, both lanes) passes `--exclude=./<item>` per item.
50
+ * Absent / empty ⇒ the upload command is what it always was. */
51
+ snapshotExclude?: readonly string[];
42
52
  cpu?: string;
43
53
  memory?: string;
54
+ /** The sandbox pod's IPv6 posture (`K8S_SANDBOX_IPV6`; closed set + default owned by `sandbox-egress-policy.ts`): `disabled` ⇒ the
55
+ * pod-level sysctls of {@link sandboxPodSysctls}; `enabled` (default) ⇒ no `securityContext` key, the pod byte-identical to before. */
56
+ ipv6?: SandboxIpv6;
44
57
  retry?: {
45
58
  maxAttempts?: number;
46
59
  backoffMs?: number;
@@ -58,8 +71,8 @@ export interface K8sEnvConfig {
58
71
  * trifecta. The presigned URL is used only inside an adapter-issued exec at suspend/resume time, so it never
59
72
  * reaches an agent turn.
60
73
  *
61
- * WORKSPACE SPACE (`workspacePersist`, default `off` — opt-in `space`): the same store also carries the workspace ACROSS
62
- * RUNS, in its own sub-prefix `<prefix>/<spaceIncarnation>/space/` — `space/latest` (body = the newest snapshotId) names the
74
+ * WORKSPACE SPACE (`workspacePersist`, default `space`; `off` = a fresh workspace every run): the same store also carries the workspace ACROSS
75
+ * RUNS, in its own sub-prefix `<prefix>/<incarnation>/space/` — `space/latest` (body = the newest snapshotId) names the
63
76
  * snapshot `connect()` restores; `destroy()` snapshots into `space/`, moves `latest`, keeps the newest `workspaceSnapshotKeep`
64
77
  * (the same one upload / one download as suspend / resume, see `SnapshotTarget`). The pointer read / write, the listing and the deletes are
65
78
  * WORKER-side requests to `endpoint` (the snapshot-BLOB offload dials the same `MINIO_ENDPOINT` from the worker).
@@ -74,7 +87,7 @@ export interface K8sEnvConfig {
74
87
  presignTtlSec?: number;
75
88
  /** The workspace-space policy (the capability fact `workspaceSpace`'s witness, handed in by the factory; the adapter
76
89
  * never resolves defaults itself). Absent or `persist: "off"` ⇒ no persistence across runs. */
77
- space?: WorkspaceSyncCapability;
90
+ space?: WorkspaceSpacePolicy;
78
91
  };
79
92
  /**
80
93
  * The PARK lane's partition (`<prefix>/<sessionId>/<snapshotId>.tar.gz`), set by {@link k8sExecutionEnvFactory} from
@@ -84,18 +97,22 @@ export interface K8sEnvConfig {
84
97
  * it never decides who reads. Segment shape is validated in exactly one place ({@link RemoteK8sExecutionEnv.presignSnapshot}).
85
98
  */
86
99
  sessionId?: string;
100
+ /** The SPACE lane's occupant — handed in per run by the factory binding (`boot/execution-env.ts`); see {@link SpaceOccupant}. */
101
+ spaceOccupant?: SpaceOccupant;
87
102
  /**
88
- * The SPACE lane's partition (`<prefix>/<spaceIncarnation>/space/…`): the session OCCUPANT's incarnation token
89
- * (`session-incarnation.ts`, the one owner of "is this the same occupant"), handed in per run by the factory binding
90
- * (`boot/execution-env.ts`) from the same seat the memory engine reads. Why not the session id: the space lane is read
91
- * by whoever runs next in the session, so its partition decides WHO reads — a session id deleted and registered again
92
- * (by anyone) is a new occupant and starts from an empty workspace. Absent while persistence is on ⇒ the space lane
93
- * does not open for this env (connect restores nothing, destroy saves nothing) and one `workspace_space_unavailable`
94
- * line says so — never a fallback to the session id. Never logged whole: operator lines carry `incarnationLogPrefix`.
103
+ * The build-cache volume per workspace space (`WORKSPACE_CACHE=volume`; the capability fact `workspaceCache`'s witness, handed
104
+ * in by the factory — absent for a delegated child, whose env opens no space). Keyed by the SAME space key as the space lane
105
+ * (`spaceOccupant.incarnation`): connect finds / creates the space's claim (`workspaceCacheClaimOf`) and mounts it at `mount`
106
+ * — outside the workspace mount, so no snapshot ever covers it — and layers the cache env closed table under `podEnv`
107
+ * (an explicit operator value of the same name wins). A volume that cannot be had (create refused, not bound within
108
+ * `dataTimeoutMs`, no such session ⇒ no space key) ⇒ the pod starts WITHOUT it and without the cache env: `recordFailOpen` + one
109
+ * `workspace_cache_unavailable` line. An occupant read that FAULTED is not a miss: the run is refused, as on the space lane
110
+ * ({@link SpaceOccupant}). Absent ⇒ no claim request, the pod as before.
95
111
  */
96
- spaceIncarnation?: string;
112
+ cache?: WorkspaceCacheCapability;
97
113
  /** Operator log seat (the e2b adapter's shape): `workspace_snapshot_failed` / `workspace_snapshot_prune_failed` /
98
- * `workspace_space_unavailable` / `workspace_commit_abandoned` / `workspace_commit_marker_stuck`. */
114
+ * `workspace_space_unavailable` / `workspace_space_unreadable_occupant` / `workspace_commit_abandoned` / `workspace_latest_dangling` /
115
+ * `workspace_restore_failed` / `workspace_cache_fresh` / `workspace_cache_unavailable`. */
99
116
  logger?: {
100
117
  warn(event: string, fields?: Record<string, unknown>): void;
101
118
  };
@@ -115,8 +132,10 @@ export interface K8sEnvConfig {
115
132
  podSpecPatch?: PodSpecPatch;
116
133
  /** RFC B5 (sema-internal server/docs/design/ENV-SELECT-AND-REGION-SOURCE.md 抽象点②): operator-trusted env injected into the
117
134
  * 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-region config, NEVER task-controlled. Absent = the
119
- * pod is byte-identical to today. */
135
+ * pkg-source.sh hook materializes at start). Deployment config, NEVER task-controlled. This is the pod's ONE container-env
136
+ * seat: the region package-source set AND the deployer-named forwarding (`K8S_SANDBOX_ENV` keys, original values) arrive
137
+ * here already merged by the boot assembly (explicit names win). Values go into `containers[0].env[]` and nowhere else —
138
+ * never a command string, the workspace or a snapshot. Absent = the pod is byte-identical to today. */
120
139
  podEnv?: Record<string, string>;
121
140
  }
122
141
  export { type PodSpecPatch, applyPodSpecPatch } from "./k8s-exec-protocol.js";
@@ -150,11 +169,52 @@ export interface K8sEnvDeps {
150
169
  /** Worker-side object-store requests (the space pointer, listing, deletes). Default: global `fetch`. */
151
170
  fetch?: typeof fetch;
152
171
  }
172
+ /**
173
+ * 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
174
+ * readings of the session row's cell, read by id from the session store (`SessionStore.incarnationOf`) when the run's env is built:
175
+ * - known: `incarnation` — the partition (`<prefix>/<incarnation>/space/…`): the session OCCUPANT's incarnation token
176
+ * (`session-incarnation.ts`, the one owner of "is this the same occupant"). Why not the session id: the space lane is read by
177
+ * whoever runs next in the session, so its partition decides WHO reads — a session id deleted and registered again (by anyone)
178
+ * is a new occupant and starts from an empty workspace. Never logged whole: operator lines carry `incarnationLogPrefix`;
179
+ * `fence` — this env's seat on the space's commit fence (the run store's `workspace_space_commit` row,
180
+ * `workspace-space-commit.ts`): `connect()` waits on it, the run-end commit holds it;
181
+ * - absent: `unread: "incarnation_unknown"` — there is NO space for this run: the store answered that it has no such session
182
+ * (deleted / never registered), or this replica holds no commit fence to key one by (see `occupantOf`) ⇒ a lane keyed by it
183
+ * does not open (no restore, no save, no volume) and says so in one line;
184
+ * - faulted: `fault` — the read did not answer (it threw, or answered something that is not a token): the code of the store's
185
+ * error (never its text), `incarnation_read_failed` without one. A space we could not read is not "no space" ⇒ a lane keyed by
186
+ * it refuses this env (`RemoteK8sExecutionEnv.provision`, the one pod seat): the run never starts on a workspace it believes is its own.
187
+ * Never a fallback to the session id.
188
+ */
189
+ export type SpaceOccupant = {
190
+ readonly incarnation: string;
191
+ readonly fence: SpaceCommitFence;
192
+ } | {
193
+ readonly unread: "incarnation_unknown";
194
+ } | {
195
+ readonly fault: string;
196
+ };
197
+ /** One failed stage of {@link RemoteK8sExecutionEnv.reclaimSpaceSnapshots}: whose partition (the space's `space/` or the park lane's), listing it, deleting from it,
198
+ * or the reclaim call's deadline passing before or while on it (`budget`: the rest was not attempted). */
199
+ export interface ReclaimFailure {
200
+ readonly lane: "space" | "park";
201
+ readonly stage: "list" | "delete" | "budget";
202
+ readonly detail: string;
203
+ }
204
+ /** The failed cache-volume half of a reclaim ({@link RemoteK8sExecutionEnv.reclaimSpaceCache}); `reason` is the closed word set of
205
+ * `workspace_cache_reclaim_failed` from the reclaim seat: the delete-by-label failed or answered non-2xx (`delete_failed`), or the reclaim call's
206
+ * 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). */
207
+ export interface CacheReclaimFailure {
208
+ readonly reason: Exclude<CacheReclaimReason, "occupant_unreadable">;
209
+ readonly detail: string;
210
+ }
153
211
  export { execSocketTlsOptions } from "./k8s-exec-protocol.js";
154
212
  /** `Error`-or-event → message. Bun's ws shim emits browser-style ErrorEvents (NOT instanceof Error) whose
155
213
  * String() is "[object ErrorEvent]" — that masked the real TLS failure for a whole drill. */
156
214
  export declare function errorMessageOf(e: unknown): string;
157
215
  export { exitCodeFromStatus } from "./k8s-exec-protocol.js";
216
+ /** The sandbox namespace when `K8S_NAMESPACE` is unset (the one spelling: the adapter and the boot's egress line read it). */
217
+ export declare const K8S_SANDBOX_NAMESPACE_DEFAULT = "sandboxes";
158
218
  export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, BackgroundShellCapability {
159
219
  /**
160
220
  * 🔴 **S-354 / core 7.20.0([ref])`canonicalPathAuthoritative: true`** —— 本适配器的 `canonicalPath`
@@ -183,14 +243,30 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
183
243
  private inFlightExecs;
184
244
  /** Memoized in-flight connect (lazy-connect; prevents concurrent double pod-create — the E2B race lesson). */
185
245
  private connectPromise?;
246
+ /** The space restore's refusal: `latest` names an archive that is there and did not come back. A fact of this env until `destroy` —
247
+ * written once, by {@link restoreSnapshot} at the restore's end; read by {@link connect} alone (the lazy and the explicit connect alike),
248
+ * which answers it to every later connect: the same refusal, zero pods, zero store requests, no second line. */
249
+ private restoreRefusal?;
186
250
  /** core `ExecutionEnv.homeDir`([ref]):本沙箱/目标机上执行用户的 home。见配置同名字段。 */
187
251
  readonly homeDir?: string | undefined;
252
+ /** core 7.49.0 #1554:shell 子进程真拿到的 `CDPATH` 条目 —— 本车道的命令跑在 pod 里(镜像与 pod spec 的 env),本类不读那份 env ⇒ `"unknown"`(core 契约:说不出就说 unknown,
253
+ * 不许报一张没读过的空表;删除守卫因此对裸 `cd <相对名>` 之后的删除问人,而不是按「没有 CDPATH」放行)。 */
254
+ readonly cdPath: "unknown";
188
255
  private readonly cfg;
189
256
  private readonly deps;
257
+ /** The known occupant's incarnation (`undefined` = this run has no known occupant: a delegated child, an unread session, a direct construction) — the
258
+ * ownership mark of every snapshot id this env mints, both lanes ({@link mintSnapshotId}); the space lane's partition is the same reading. */
259
+ private readonly occupantIncarnation;
190
260
  /** This env's space lane (resolved once from the policy + the occupant's incarnation; see {@link SpaceLane}). */
191
261
  private readonly spaceLane;
192
262
  /** `workspace_space_unavailable` is said once per env. */
193
263
  private spaceUnavailableSaid;
264
+ /** `workspace_space_unreadable_occupant` is said once per env (a retried lazy connect is refused again, silently). */
265
+ private unreadableOccupantSaid;
266
+ /** This env's cache volume lane (resolved once from the cache policy + the space key; see {@link CacheLane}). */
267
+ private readonly cacheLane;
268
+ /** A cache volume that could not be had is counted and said once per env (a retried connect does not count again). */
269
+ private cacheUnavailableSaid;
194
270
  private readonly logger?;
195
271
  private readonly onWorkspaceSnapshot?;
196
272
  constructor(config: K8sEnvConfig, deps?: K8sEnvDeps);
@@ -208,29 +284,97 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
208
284
  private openExec;
209
285
  /**
210
286
  * 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`; a missing archive is
213
- * refused); absent (404) ⇒ a fresh pod; a read that FAILS ⇒ a loud
214
- * `connect_failed` — never a silent fresh workspace (a run that believes it resumed while it did not is the worst form).
215
- * Persistence off / no store ⇒ a fresh pod, exactly as before. Persistence on but the occupant unknown ⇒ a fresh pod
216
- * and one `workspace_space_unavailable` line (no restore by any other key).
287
+ * ({@link awaitSpaceCommit}: the run store's commit row), then read `<prefix>/<incarnation>/space/latest`: present ⇒
288
+ * restore it through {@link restoreSnapshot} (the one tar download, shared with `resumeVM`; an archive that is gone ⇒
289
+ * loud `workspace_latest_dangling`, the pointer deleted, an empty workspace); absent (404) ⇒ a fresh pod; a read that
290
+ * FAILS ⇒ a loud `connect_failed` — never a silent fresh workspace (a run that believes it resumed while it did not is
291
+ * the worst form). Once the pod holds the workspace, this env arms its fence seat (its destroy will commit).
292
+ * Persistence off / no store ⇒ a fresh pod, exactly as before. Persistence on but no such session (the occupant absent) ⇒ a
293
+ * fresh pod and one `workspace_space_unavailable` line (no restore by any other key). The occupant read FAULTED ⇒ the same
294
+ * loud `connect_failed` as a failed `latest` read, no pod ({@link provision} refuses it — every entry that would create one).
295
+ * One restore per env: a restore that ended refused ({@link restoreRefusal}) is answered as it stands to every later connect — the
296
+ * previous commit is not waited on again, `latest` is not read again, no pod is created.
217
297
  */
218
298
  connect(config?: RemoteConnectConfig): Promise<ConnectResult>;
219
- /** The space lane is on but this env's occupant is unknown — said once per env (at connect, or at a destroy with a pod). */
299
+ /** This env holds a workspace its `destroy()` will commit ⇒ arm its seat on the space's commit fence (from here the
300
+ * run's terminal write fences the space; `workspace-space-commit.ts`). Each write of this run's row sets its deadline to
301
+ * now + the upload's own wall-clock bound (`dataTimeoutMs`). No-op unless the space lane is open. */
302
+ private armSpace;
303
+ /** The env no longer holds a workspace to commit (a commit ended, a park released the pod, or destroy found no pod) ⇒
304
+ * end its fence seat: the row this run wrote, if any, is deleted. A delete that fails leaves the row until its deadline —
305
+ * the next run waits at most that long and says `workspace_commit_abandoned` (the loud form of a stuck row). */
306
+ private endSpace;
307
+ /**
308
+ * One call to the space's commit fence (the run store), bounded like every other request this adapter makes
309
+ * (`rpcTimeoutMs`) and abortable: a store that never answers never hangs connect or destroy. The store call itself is
310
+ * not cancellable — a late answer is dropped; what it may have written dies at its own deadline (the fence's one rule).
311
+ * Rejects with {@link FenceCallAborted} on abort.
312
+ */
313
+ private fenceCall;
314
+ /** The space lane is on but this run's session has no occupant (no such session — {@link SpaceOccupant}) — said once per env
315
+ * (at connect, or at a destroy with a pod). `reason` = the reading's word. */
220
316
  private sayUnavailable;
317
+ /**
318
+ * A lane that is on is keyed by an occupant read that FAULTED (`unreadable`) ⇒ this env creates no pod: the refusal, in the one
319
+ * space-read failure form ({@link spaceReadFailed} — a failed `latest` / commit-row read says the same), naming the knob of every
320
+ * lane that refuses; one `workspace_space_unreadable_occupant{sessionId, code}` line per env. `code` = the store error's code,
321
+ * 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.
322
+ */
323
+ private refuseUnreadableOccupant;
221
324
  /** Every space-lane operator line: the space is named by its incarnation's log prefix only, and free text (a curl
222
325
  * stderr, a fetch error) passes {@link redactIncarnations} — the full token never reaches a log body. */
223
326
  private spaceLine;
224
- /** A fresh pod (create → Running → workspace root), bounded retry. Shared by {@link connect} and {@link resumeVM}. */
327
+ /** A fresh pod (create → Running → workspace root), bounded retry — THE seat every pod of this env is created through
328
+ * ({@link connect} and, via {@link restoreSnapshot}, {@link resumeVM}). A lane keyed by an occupant read that faulted ⇒ refused
329
+ * here, before any request ({@link refuseUnreadableOccupant}): never retried (the reading is this env's, fixed at construction). */
225
330
  private provision;
226
331
  /**
227
332
  * Lazy-connect on first use — 🔴 core NEVER calls `connect()` (verified: the Runner only calls `resumeVM` on
228
333
  * the resume path), so an adapter that demands an explicit connect can never work through the production
229
334
  * hand-tool path (lazy-connect belongs in the adapter). Memoized so concurrent first ops share ONE
230
- * pod create; a failed connect clears the memo (next op retries); `destroyed` is terminal.
335
+ * pod create; `destroyed` is terminal. A failed connect clears the memo — how long a failure stands is {@link connect}'s, not
336
+ * this memo's: a failure of that attempt (a pod that could not be created, a space read that failed) ⇒ the next op connects
337
+ * afresh; the space restore's refusal is the env's ({@link restoreRefusal}) ⇒ the next op's connect answers that same refusal at once.
231
338
  */
232
339
  private ensureConnected;
340
+ /**
341
+ * One pod creation attempt. With the cache volume open: first the space's claim ({@link attachCache} — found, or created),
342
+ * 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
343
+ * without the volume is created in its place (the run starts; the miss is counted). Every other failure is the attempt's.
344
+ */
233
345
  private connectOnce;
346
+ /** The cache volume for this pod creation, or `undefined` (off, or not to be had — counted and said there). */
347
+ private attachCache;
348
+ /** Find the space's claim, or create it (labelled with the space digest; size = the policy's; the cluster's default
349
+ * StorageClass). `fresh` = this call created it. A concurrent creator (409) is a find. Any other answer is a miss. */
350
+ private ensureCacheClaim;
351
+ /** Is the claim bound? (A read that fails counts as not bound — the bind deadline still decides.) */
352
+ private cacheClaimBound;
353
+ /**
354
+ * Reclaim one ended space's cache volume: delete the claims labelled `sema.space=<its digest>` (one deletecollection —
355
+ * a claim still mounted by a terminating pod is kept by the cluster's pvc-protection until the pod is gone). None ⇒ zero
356
+ * action. The request runs under the reclaim call's `deadline` (built once per call by {@link createSpaceReclaimer}): already passed ⇒
357
+ * 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).
358
+ */
359
+ reclaimSpaceCache(spaceKey: string, deadline: AbortSignal): Promise<{
360
+ ok: true;
361
+ deleted: number;
362
+ } | ({
363
+ ok: false;
364
+ } & CacheReclaimFailure)>;
365
+ /**
366
+ * The egress self-check's two reads (`GET` nodes, `GET` the sandbox namespace's NetworkPolicies) judged by the one rule
367
+ * ({@link sandboxEgressGaps}) against the labels this env mints on its pods (base identity + the pod-spec patch's additions, the
368
+ * same merge `createPod` applies). A read that is refused / fails / times out is a `unreadable` gap, never a guess. Never throws.
369
+ */
370
+ egressPolicyGaps(): Promise<SandboxEgressGap[]>;
371
+ /** The one spelling of "the reclaim call's deadline passed" — both halves of the reclaim (volume and objects) say it with this. */
372
+ private reclaimDeadlinePassed;
373
+ /** The cache volume could not be had: counted (`recordFailOpen`) and one operator line — once per env. */
374
+ private cacheUnavailable;
375
+ /** Create one pod (with the cache volume when given) and wait for it to run. A {@link CacheMiss} = a pod carrying the
376
+ * volume did not become ready (its claim unbound past the bind deadline, or bound and still not ready) — already deleted. */
377
+ private createPod;
234
378
  /**
235
379
  * Build a validated, presigned URL for a snapshot object of either lane (see {@link SnapshotTarget}) — the ONE place a
236
380
  * snapshot object key is spelled, and the one place each lane's partition is chosen (park = the session id, space = the
@@ -243,6 +387,30 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
243
387
  * snapshotKey()+s3Presign() pair so the two call sites can't diverge.
244
388
  */
245
389
  private presignSnapshot;
390
+ /** 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).
391
+ * The partition segment is validated by `presignSnapshot` before a URL is signed. */
392
+ private snapshotBase;
393
+ /**
394
+ * Reclaim an ENDED space's snapshot objects (the reclaim seat's half of {@link createSpaceReclaimer}): list the whole `space/` partition
395
+ * of `spaceKey` (archives, `latest`, anything else this adapter ever wrote there) and, when this env is keyed by a session
396
+ * (`cfg.sessionId`), the park lane's `<prefix>/<sessionId>/*.tar.gz` whose names carry the ended occupant's ownership mark (see the two
397
+ * paragraphs below: unmarked / `none` objects stay) — the session is gone, so nothing can ever read either again.
398
+ * Each object is deleted over the same presigned DELETE the prune uses. Never throws: answers the failed stages (an empty answer = all
399
+ * gone, or nothing was there). An object whose name this adapter could not have written, or that sits deeper than the lane's own
400
+ * partition, is left alone. A truncated listing (more than 1000 objects) deletes what it saw and says so.
401
+ * 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
402
+ * 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
403
+ * 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
404
+ * 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
405
+ * also keeps its own `rpcTimeoutMs`; the deadline is what bounds their sum.)
406
+ * 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
407
+ * late (a retention sweep walks many sessions). So the park lane deletes only objects whose NAME carries the ended occupant's ownership mark
408
+ * ({@link snapshotNameOwnedBy}; the mark is minted into every snapshot id by {@link mintSnapshotId}, `uploadSnapshot`): a later occupant's
409
+ * 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 —
410
+ * 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/`
411
+ * partition needs no such guard (its partition is the incarnation, which a new occupant does not share).
412
+ */
413
+ reclaimSpaceSnapshots(spaceKey: string, deadline: AbortSignal): Promise<ReclaimFailure[]>;
246
414
  /** Worker-side object-store request, bounded by the control-plane RPC wall-clock — or a tighter caller deadline — and
247
415
  * the caller's abort. */
248
416
  private storeFetch;
@@ -256,12 +424,20 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
256
424
  * caller deadline tighter than the RPC bound. */
257
425
  private writePointer;
258
426
  /**
259
- * Wait out the previous run's space commit. `space/committing` present and younger than the upload's own wall-clock
260
- * bound (`dataTimeoutMs` — the SAME number `uploadSnapshot` gives its tar + curl exec) ⇒ re-read every
261
- * {@link SPACE_COMMIT_POLL_MS} until it is gone or has aged past that bound. A marker at / past the bound = a commit
262
- * whose worker died inside it (a live upload fails at the bound and clears its marker) ⇒ one
263
- * `workspace_commit_abandoned` line; `latest` still names the previous COMPLETE snapshot. A marker read that fails, or
264
- * a body that is not an epoch-ms timestamp, is a loud `connect_failed` (the same form as a failed `latest` read).
427
+ * Wait out the previous run's space commit — its row in the run store's `workspace_space_commit` (the ONE seat of
428
+ * "a commit of this space is in flight", `workspace-space-commit.ts`; written by that run's terminal write before the
429
+ * session's slot was released, so a next run can never start before it). The row carries its WRITER's deadline; this
430
+ * reader brings no number of its own: alive (`spaceCommitAlive`) ⇒ re-read every {@link SPACE_COMMIT_POLL_MS} until the
431
+ * row is gone. Read dead = a run whose worker died between its terminal write and its destroy (state `pending`), inside
432
+ * the upload, or while moving `latest` — or a writer whose renewal lands at this very moment. So the read alone
433
+ * decides nothing: the reader ABANDONS the row ({@link SpaceCommitFence.abandon} — one locked conditional delete, judged
434
+ * on the store's clock under the row's lock). Deleted ⇒ one `workspace_commit_abandoned{space, remainingMs, state}` line
435
+ * (`remainingMs` ≤ 0: how far past the writer's deadline, as read) and `latest` (still the previous COMPLETE snapshot)
436
+ * is restored; not deleted (renewed / taken / gone) ⇒ re-read at once. A row read or an abandon that fails is a loud
437
+ * `connect_failed` (never a guess that no commit is in flight).
438
+ * How long connect may wait here has no bound of its own (no knob, and NOT this replica's `dataTimeoutMs`): it is the
439
+ * deadline the writer keeps in its row — each of that run's at most three writes (terminal, commit start, publish) sets
440
+ * it to now + the writer's bound — plus the run's abort signal, which ends the wait at once.
265
441
  */
266
442
  private awaitSpaceCommit;
267
443
  /**
@@ -292,7 +468,9 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
292
468
  }>;
293
469
  /**
294
470
  * 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).
471
+ * release the pod. `suspendVM` = lane `park`; the run-end space snapshot = lane `space` (its open space). The exclusion
472
+ * table (`snapshotExclude`) is applied HERE and nowhere else — each item one `--exclude=./<item>` argument through the one
473
+ * `shellQuote`, anchored at the workspace root (`-C <mount> .` names every member `./…`); no table ⇒ the command as it always was.
296
474
  */
297
475
  private uploadSnapshot;
298
476
  /**
@@ -309,12 +487,34 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
309
487
  resumeVM(snapshotId: SnapshotId, options?: VmLifecycleOptions): Promise<ConnectResult>;
310
488
  /**
311
489
  * The ONE tar download (both lanes): provision a FRESH pod, download + untar the lane's archive into the workspace.
312
- * `missing: "empty"` (park: a plan-only suspend has no object) tolerates a 404 as a clean workspace; `missing:
313
- * "refuse"` (space: its pointer is only ever written after a successful upload ⇒ a 404 there is lost data) refuses it.
314
- * A restore that does not complete releases the pod it provisioned: no tool and no run-end snapshot may ever see a
315
- * torn or empty-by-accident workspace.
490
+ * A missing archive (404) starts from the clean workspace on both lanes — what differs is what it means:
491
+ * - park: a plan-only suspend has no object — nothing to say;
492
+ * - space: `latest` dangles (its pointer is only written after a successful upload, so the archive was removed later —
493
+ * by the bucket's lifecycle rule or a hand): the data is gone by an operator rule ⇒ loud and continuable —
494
+ * {@link dropDanglingLatest} (one `workspace_latest_dangling` line, the pointer deleted); the run's own run-end
495
+ * snapshot then becomes the new `latest`.
496
+ * An attempt that does not complete releases the pod it provisioned: no tool and no run-end snapshot may ever see a torn
497
+ * workspace. A transient class ({@link restoreFailureIsTransient}) is tried again on a fresh pod after the `retry.backoffMs`
498
+ * pause, up to the lane's {@link RESTORE_MAX_ATTEMPTS}; a non-transient one ends the restore at once. The lane then says what
499
+ * the end means:
500
+ * - park: a plain `connect_failed` — its caller retries; nothing said, nothing held;
501
+ * - space: the archive `latest` names is THERE and did not come back ({@link RestoreFailureClass}): the run is refused all the
502
+ * same — never an empty workspace (the user's changes are still in the bucket) — with exactly one `workspace_restore_failed`
503
+ * line (its `attempts`; an attempt that failed before one that succeeded is no line) and a refusal naming the two ways out,
504
+ * held for the env ({@link restoreRefusal}).
505
+ * A pod that could not be created is that attempt's failure, not the archive's: returned as it is — no line, nothing held, the
506
+ * next connect tries again. An abort during the pause ends the restore `aborted` (not the archive's either).
316
507
  */
317
508
  private restoreSnapshot;
509
+ /** The pause before a restore's next attempt (`retry.backoffMs`, the adapter's one retry pause). `false` ⇒ aborted (now or during it). */
510
+ private restorePause;
511
+ /**
512
+ * `latest` names an archive that is not there (S-1438): one `workspace_latest_dangling{space, key, deleted}` line and the
513
+ * pointer is deleted (the next run must not trip on it again); the run continues on the empty workspace. A pointer
514
+ * delete that fails rides on the same line (`deleted: false` + the error) — the next run then says it once more.
515
+ * This env is the space's only writer here (the commit fence admits one per space), so the delete is a plain one.
516
+ */
517
+ private dropDanglingLatest;
318
518
  /**
319
519
  * Honest post-resume consistency ([ref] §5 council #8): the workspace FILES are restored byte-for-byte
320
520
  * from the snapshot, so read-before-edit hashing already lines up — there's nothing to re-fetch. In-VM
@@ -334,15 +534,18 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
334
534
  * Delete the sandbox pod. Idempotent, never throws. Space lane open and a pod exists ⇒ FIRST the run-end space
335
535
  * snapshot ({@link persistWorkspace}); its failure is loud and never blocks the delete (the pod lifetime is the
336
536
  * leak guard). A run that never connected has no workspace to save — `latest` stays where it was. Space lane
337
- * unavailable (occupant unknown) ⇒ nothing is saved (the once-per-env line says so).
537
+ * unavailable (no such session) ⇒ nothing is saved (the once-per-env line says so); unreadable ⇒ there is no pod
538
+ * ({@link provision} refused every creation).
338
539
  */
339
540
  destroy(): Promise<void>;
340
541
  cleanup(): Promise<void>;
341
542
  /**
342
- * Run-end space snapshot: PUT the `committing` marker → the one tar upload ({@link uploadSnapshot}, lane `space` — it
343
- * also releases the pod) → move `latest` → keep N → DELETE the marker (always, saved or failed).
344
- * Upload or pointer failure ⇒ one `workspace_snapshot_failed` line + the `failed` outcome; `latest` keeps naming the
345
- * previous snapshot, so the next run restores THAT — never an empty workspace dressed up as a restore.
543
+ * Run-end space snapshot, under the space's commit fence (the run store's row, `workspace-space-commit.ts`): the fence
544
+ * first (this run's row moves to its upload state; lost ⇒ no upload, loud) → the one tar upload ({@link uploadSnapshot},
545
+ * lane `space` — it also releases the pod) → the fence again (this run's row moves to its publishing state; lost ⇒
546
+ * `latest` is not published, loud) → move `latest` → keep N → the row is deleted (always, saved or failed).
547
+ * A lost fence, an upload or a pointer failure ⇒ one `workspace_snapshot_failed` line + the `failed` outcome; `latest`
548
+ * keeps naming the previous snapshot, so the next run restores THAT — never an empty workspace dressed up as a restore.
346
549
  */
347
550
  private persistWorkspace;
348
551
  private execUrl;
@@ -457,6 +660,26 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
457
660
  * 车PL:buffered `exec` 上守卫触发 ⇒ `spawn_error`)。 */
458
661
  private workingDirOf;
459
662
  }
663
+ /**
664
+ * The ONE reclaim seat for an ended space ({@link SpaceReclaimer}) — called where a space ENDS (the session-delete coordinator and the
665
+ * managed-retention sweep). For each ended space it reclaims everything the space left behind, over this deployment's k8s config (it
666
+ * never creates a pod): its build-cache volume (the claims labelled by the space key; only when the cache volume is configured),
667
+ * 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
668
+ * snapshot store is configured). Each part is independent: one part failing never skips another. A failed part ⇒ one operator line
669
+ * (`workspace_cache_reclaim_failed{reason}` for the volume, `workspace_space_reclaim_failed{stage}` for the objects) + `recordFailOpen`; never throws.
670
+ * 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
671
+ * 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
672
+ * 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
673
+ * walked but sends nothing: its volume says `reason: "budget"`, its objects `stage: "budget"` (the volume stays for deletion by label, the objects for the
674
+ * bucket's lifecycle rule). A caller that must not wait for the seat (the session-delete answer) starts it and does not await it.
675
+ */
676
+ export declare function createSpaceReclaimer(config: K8sEnvConfig, deps?: K8sEnvDeps): SpaceReclaimer;
677
+ /**
678
+ * The k8s lane's boot-time egress self-check (once per boot, never holds the boot): the gaps between the sandbox pods' egress
679
+ * policy and what the cluster API shows (`sandbox-egress-policy.ts` — the closed set and the rule). A reading, not the boundary:
680
+ * nodes that join after boot are read at the next boot. Never throws (a read failure is an `unreadable` gap).
681
+ */
682
+ export declare function checkSandboxEgressPolicy(config: K8sEnvConfig, deps?: K8sEnvDeps): Promise<SandboxEgressGap[]>;
460
683
  /**
461
684
  * `ExecutionEnvFactory` for the k8s pod-sandbox backend — one fresh (unconnected) env per task; the pod is
462
685
  * created lazily on connect and deleted by `destroy()` (Runner-owned lifetime; `activeDeadlineSeconds` is the