@sema-agent/server 7.136.0 → 7.138.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 (327) hide show
  1. package/MIGRATION.md +501 -139
  2. package/README.md +1 -0
  3. package/README.zh-CN.md +1 -0
  4. package/USAGE.md +149 -34
  5. package/deploy/sema-up/README.md +14 -0
  6. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +7 -1
  7. package/deploy/sema-up/stack/sema-stack-cluster.yaml +7 -1
  8. package/dist/approval-card.d.ts +21 -14
  9. package/dist/approval-card.js +1 -0
  10. package/dist/approval-content-kind.d.ts +6 -0
  11. package/dist/approval-content-kind.js +7 -0
  12. package/dist/approval-gate-currency.d.ts +48 -6
  13. package/dist/approval-gate-currency.js +16 -9
  14. package/dist/approval-hmac.d.ts +13 -1
  15. package/dist/approval-hmac.js +1 -1
  16. package/dist/approval.js +3 -3
  17. package/dist/audit.js +1 -0
  18. package/dist/auth-bridge.js +2 -2
  19. package/dist/background-agent-name.d.ts +5 -0
  20. package/dist/background-agent-name.js +5 -0
  21. package/dist/boot/budget-tracing.d.ts +1 -12
  22. package/dist/boot/deployment-mods.d.ts +46 -16
  23. package/dist/boot/deployment-mods.js +82 -6
  24. package/dist/boot/execution-env.d.ts +24 -1
  25. package/dist/boot/execution-env.js +69 -6
  26. package/dist/boot/governance-seams.d.ts +10 -17
  27. package/dist/boot/governance-seams.js +5 -17
  28. package/dist/boot/leader.d.ts +3 -0
  29. package/dist/boot/leader.js +1 -0
  30. package/dist/boot/org-delivery.js +15 -2
  31. package/dist/boot/reapers.d.ts +3 -0
  32. package/dist/boot/reapers.js +15 -1
  33. package/dist/boot/resolve-spec.d.ts +7 -0
  34. package/dist/boot/resolve-spec.js +36 -26
  35. package/dist/boot/retention-lane.d.ts +11 -5
  36. package/dist/boot/retention-lane.js +2 -2
  37. package/dist/boot/runner-deps.d.ts +7 -3
  38. package/dist/boot/runner-deps.js +1 -0
  39. package/dist/boot/stage-03-store-backend.d.ts +1 -1
  40. package/dist/boot/stage-04-stores.d.ts +2 -13
  41. package/dist/boot/stage-05-execution-env.d.ts +6 -13
  42. package/dist/boot/stage-05-execution-env.js +17 -5
  43. package/dist/boot/stage-06-runners.d.ts +6 -13
  44. package/dist/boot/stage-06-runners.js +10 -3
  45. package/dist/boot/stage-07-capability-layer.d.ts +7 -13
  46. package/dist/boot/stage-07-capability-layer.js +3 -3
  47. package/dist/boot/stage-08-reapers.d.ts +7 -13
  48. package/dist/boot/stage-08-reapers.js +2 -1
  49. package/dist/boot/stage-09-leader.d.ts +7 -13
  50. package/dist/boot/stage-09-leader.js +2 -2
  51. package/dist/boot/stage-10-http-server.d.ts +7 -13
  52. package/dist/boot/stage-10-http-server.js +6 -3
  53. package/dist/boot/stores.d.ts +1 -1
  54. package/dist/boot/web-admin.d.ts +5 -2
  55. package/dist/boot/web-admin.js +3 -3
  56. package/dist/brain.js +5 -5
  57. package/dist/broker/broker.d.ts +89 -0
  58. package/dist/broker/broker.js +287 -0
  59. package/dist/broker/constants.d.ts +39 -0
  60. package/dist/broker/constants.js +15 -0
  61. package/dist/broker/http1.d.ts +111 -0
  62. package/dist/broker/http1.js +256 -0
  63. package/dist/broker/lease-book.d.ts +87 -0
  64. package/dist/broker/lease-book.js +142 -0
  65. package/dist/broker/lease-file.d.ts +60 -0
  66. package/dist/broker/lease-file.js +127 -0
  67. package/dist/broker/main.d.ts +2 -0
  68. package/dist/broker/main.js +20 -0
  69. package/dist/budget.d.ts +17 -15
  70. package/dist/budget.js +2 -2
  71. package/dist/capabilities/composite-members.d.ts +2 -1
  72. package/dist/capabilities/composite-members.js +2 -1
  73. package/dist/capabilities/engine-capabilities.js +1 -0
  74. package/dist/capabilities/mcp-probe.d.ts +36 -11
  75. package/dist/capabilities/mcp-probe.js +44 -5
  76. package/dist/capabilities/scenarios.d.ts +23 -8
  77. package/dist/capabilities/scenarios.js +13 -8
  78. package/dist/capabilities/skills.d.ts +28 -5
  79. package/dist/capabilities/skills.js +8 -0
  80. package/dist/capability-facts.d.ts +6 -0
  81. package/dist/capability-facts.js +5 -0
  82. package/dist/center-credential-renewer.js +5 -1
  83. package/dist/checkpoint-ctx-seal-m28.d.ts +3 -10
  84. package/dist/checkpoint-ctx-seal-m28.js +1 -1
  85. package/dist/checkpoint-identity.d.ts +13 -6
  86. package/dist/config-catalog.js +16 -7
  87. package/dist/config-types.d.ts +24 -7
  88. package/dist/config.d.ts +1 -1
  89. package/dist/config.js +42 -13
  90. package/dist/deployment-governance.d.ts +2 -2
  91. package/dist/deployment-governance.js +7 -9
  92. package/dist/deployment-shape.d.ts +6 -6
  93. package/dist/device-ws-protocol.d.ts +1 -0
  94. package/dist/device-ws-protocol.js +2 -1
  95. package/dist/file-history-disclosure.d.ts +4 -2
  96. package/dist/file-history-disclosure.js +2 -0
  97. package/dist/gate-settlement.d.ts +71 -0
  98. package/dist/gate-settlement.js +40 -0
  99. package/dist/hooks/classic-stations.d.ts +12 -54
  100. package/dist/hooks/classic-stations.js +1 -0
  101. package/dist/hooks/command-spawn.d.ts +2 -2
  102. package/dist/hooks/hook-runner.js +14 -14
  103. package/dist/http/admission.js +14 -9
  104. package/dist/http/approval-sweeps.js +4 -3
  105. package/dist/http/caller-trust.d.ts +2 -1
  106. package/dist/http/caller-trust.js +1 -3
  107. package/dist/http/checkpoint-ctx.d.ts +48 -7
  108. package/dist/http/checkpoint-ctx.js +40 -0
  109. package/dist/http/dispatch.d.ts +4 -0
  110. package/dist/http/dispatch.js +18 -10
  111. package/dist/http/durable-run-leg.d.ts +5 -1
  112. package/dist/http/durable-run-leg.js +5 -3
  113. package/dist/http/health-self-report.d.ts +2 -2
  114. package/dist/http/memory-compliance-failure-reply.js +3 -0
  115. package/dist/http/principal-gate.d.ts +15 -10
  116. package/dist/http/principal-gate.js +48 -9
  117. package/dist/http/resume-legs.d.ts +7 -31
  118. package/dist/http/resume-legs.js +185 -126
  119. package/dist/http/route-ctx.d.ts +38 -53
  120. package/dist/http/route-ctx.js +2 -5
  121. package/dist/http/routes/a2a-serve.js +10 -4
  122. package/dist/http/routes/agents-roster.d.ts +1 -1
  123. package/dist/http/routes/agents-roster.js +5 -1
  124. package/dist/http/routes/approvals-assistant.js +24 -10
  125. package/dist/http/routes/capabilities.js +23 -11
  126. package/dist/http/routes/mcp-reconnect.d.ts +3 -2
  127. package/dist/http/routes/mcp-reconnect.js +7 -7
  128. package/dist/http/routes/memory-consolidation.d.ts +1 -1
  129. package/dist/http/routes/memory-origin.d.ts +1 -1
  130. package/dist/http/routes/rules.js +3 -3
  131. package/dist/http/routes/runs.js +13 -8
  132. package/dist/http/routes/session-sync.js +42 -6
  133. package/dist/http/routes/sessions.js +6 -3
  134. package/dist/http/routes/side-query.js +2 -3
  135. package/dist/http/routes/tasks.js +794 -780
  136. package/dist/http/server.d.ts +35 -25
  137. package/dist/http/stored-body-replay.d.ts +3 -3
  138. package/dist/http/stored-body-replay.js +6 -1
  139. package/dist/http/wire-types.d.ts +66 -33
  140. package/dist/identity-source.d.ts +65 -0
  141. package/dist/identity-source.js +45 -0
  142. package/dist/index.d.ts +2 -0
  143. package/dist/index.js +1 -0
  144. package/dist/leader/wire.d.ts +7 -0
  145. package/dist/leader/wire.js +5 -4
  146. package/dist/mcp-assertion-header.d.ts +51 -40
  147. package/dist/mcp-assertion-header.js +10 -11
  148. package/dist/mcp-authorization-face.d.ts +27 -0
  149. package/dist/mcp-authorization-face.js +15 -0
  150. package/dist/memory-operator-faces.d.ts +13 -4
  151. package/dist/memory-operator-faces.js +1 -17
  152. package/dist/memory-store-refusal.d.ts +3 -3
  153. package/dist/memory-sync-client.js +72 -57
  154. package/dist/memory-sync.d.ts +1 -1
  155. package/dist/model-entry-refusal.d.ts +2 -0
  156. package/dist/model-entry-refusal.js +11 -0
  157. package/dist/model-route-gap.d.ts +6 -22
  158. package/dist/model-route-gap.js +7 -5
  159. package/dist/model-select.d.ts +6 -17
  160. package/dist/model-select.js +0 -3
  161. package/dist/mods-declaration.d.ts +110 -0
  162. package/dist/mods-declaration.js +109 -0
  163. package/dist/namespaced-rule-name.d.ts +1 -1
  164. package/dist/observability/err-text.d.ts +5 -0
  165. package/dist/observability/err-text.js +9 -0
  166. package/dist/observability/fail-open.d.ts +17 -1
  167. package/dist/observability/fail-open.js +17 -1
  168. package/dist/observability/metrics.js +1 -1
  169. package/dist/observability/run-terminal.js +1 -0
  170. package/dist/observability/secret-env-scrub.d.ts +1 -1
  171. package/dist/observability/secret-env-scrub.js +1 -1
  172. package/dist/operator-ask.d.ts +2 -2
  173. package/dist/org-delivery-gate.js +1 -1
  174. package/dist/org-memory-admission.d.ts +2 -2
  175. package/dist/org-memory-admission.js +39 -2
  176. package/dist/parked-decide.d.ts +15 -2
  177. package/dist/parked-decide.js +17 -10
  178. package/dist/peer-directory.js +2 -0
  179. package/dist/peer-seat.d.ts +150 -0
  180. package/dist/peer-seat.js +239 -0
  181. package/dist/permission-rule-list.d.ts +2 -2
  182. package/dist/permission-rule-vocab.d.ts +7 -7
  183. package/dist/permission-rule-vocab.js +3 -3
  184. package/dist/plain-string-record.d.ts +38 -0
  185. package/dist/plain-string-record.js +38 -0
  186. package/dist/plugins/background-agent-store-sql.js +6 -3
  187. package/dist/plugins/caching-session-store.d.ts +5 -0
  188. package/dist/plugins/caching-session-store.js +6 -0
  189. package/dist/plugins/checkpoint-store-sql.d.ts +34 -29
  190. package/dist/plugins/checkpoint-store-sql.js +14 -14
  191. package/dist/plugins/file-history-store-sql.js +10 -16
  192. package/dist/plugins/file-run-store.js +8 -2
  193. package/dist/plugins/k8s-exec-protocol.d.ts +69 -13
  194. package/dist/plugins/k8s-exec-protocol.js +116 -28
  195. package/dist/plugins/k8s-pod-delete-failure.d.ts +25 -0
  196. package/dist/plugins/k8s-pod-delete-failure.js +17 -0
  197. package/dist/plugins/k8s-pod-reaper.d.ts +106 -0
  198. package/dist/plugins/k8s-pod-reaper.js +122 -0
  199. package/dist/plugins/local-checkpoint-store.d.ts +4 -17
  200. package/dist/plugins/local-checkpoint-store.js +3 -4
  201. package/dist/plugins/local-session-store.d.ts +66 -27
  202. package/dist/plugins/local-session-store.js +60 -28
  203. package/dist/plugins/mailbox-store-sql.d.ts +11 -3
  204. package/dist/plugins/mailbox-store-sql.js +103 -15
  205. package/dist/plugins/memory-run-store.js +2 -0
  206. package/dist/plugins/memory-sync-store-pg.d.ts +16 -5
  207. package/dist/plugins/memory-sync-store-tidb.d.ts +3 -3
  208. package/dist/plugins/one-time-migrations.d.ts +34 -12
  209. package/dist/plugins/one-time-migrations.js +51 -9
  210. package/dist/plugins/permission-rule-store-file.js +2 -2
  211. package/dist/plugins/permission-rule-store-sql.d.ts +17 -17
  212. package/dist/plugins/permission-rule-store-sql.js +37 -30
  213. package/dist/plugins/pg-pool.js +4 -0
  214. package/dist/plugins/pg-session-storage.d.ts +10 -2
  215. package/dist/plugins/pg-session-storage.js +20 -7
  216. package/dist/plugins/remote-env-adb.d.ts +6 -1
  217. package/dist/plugins/remote-env-adb.js +2 -1
  218. package/dist/plugins/remote-env-device.d.ts +6 -1
  219. package/dist/plugins/remote-env-device.js +5 -1
  220. package/dist/plugins/remote-env-e2b.d.ts +5 -0
  221. package/dist/plugins/remote-env-e2b.js +2 -1
  222. package/dist/plugins/remote-env-file-error.d.ts +8 -1
  223. package/dist/plugins/remote-env-file-error.js +7 -1
  224. package/dist/plugins/remote-env-host.d.ts +10 -2
  225. package/dist/plugins/remote-env-host.js +8 -2
  226. package/dist/plugins/remote-env-k8s.d.ts +140 -11
  227. package/dist/plugins/remote-env-k8s.js +277 -15
  228. package/dist/plugins/remote-env-local-docker.d.ts +5 -0
  229. package/dist/plugins/remote-env-local-docker.js +2 -1
  230. package/dist/plugins/remote-env-ssh.d.ts +6 -1
  231. package/dist/plugins/remote-env-ssh.js +2 -1
  232. package/dist/plugins/retention-store-sql.d.ts +14 -2
  233. package/dist/plugins/retention-store-sql.js +6 -0
  234. package/dist/plugins/run-store-sql.js +10 -4
  235. package/dist/plugins/session-fork.d.ts +10 -13
  236. package/dist/plugins/session-fork.js +5 -25
  237. package/dist/plugins/session-ownership.d.ts +24 -3
  238. package/dist/plugins/session-ownership.js +24 -5
  239. package/dist/plugins/session-workspace-cell.d.ts +17 -0
  240. package/dist/plugins/session-workspace-cell.js +38 -0
  241. package/dist/plugins/sql-errors.d.ts +23 -2
  242. package/dist/plugins/sql-errors.js +15 -0
  243. package/dist/plugins/store-backend.js +7 -7
  244. package/dist/plugins/store-contracts.d.ts +10 -1
  245. package/dist/plugins/store-fault.d.ts +2 -2
  246. package/dist/plugins/store-fault.js +4 -2
  247. package/dist/plugins/tidb-pool.js +13 -0
  248. package/dist/plugins/tidb-session-store.d.ts +17 -2
  249. package/dist/plugins/tidb-session-store.js +18 -7
  250. package/dist/plugins/tool-result-store-sql.d.ts +13 -6
  251. package/dist/plugins/tool-result-store-sql.js +8 -7
  252. package/dist/plugins/worktree-isolation.d.ts +3 -2
  253. package/dist/plugins/worktree-isolation.js +1 -7
  254. package/dist/project-identity.d.ts +1 -14
  255. package/dist/project-identity.js +0 -4
  256. package/dist/request-key-closure.js +1 -1
  257. package/dist/request-value-closure.d.ts +31 -2
  258. package/dist/request-value-closure.js +7 -1
  259. package/dist/rule-entry-list.d.ts +3 -2
  260. package/dist/rule-entry-list.js +2 -2
  261. package/dist/rules-consent.js +12 -7
  262. package/dist/run-local.d.ts +2 -2
  263. package/dist/run-local.js +12 -6
  264. package/dist/runs.d.ts +7 -1
  265. package/dist/runs.js +8 -2
  266. package/dist/runtime-governance.d.ts +5 -5
  267. package/dist/sandbox-git-lease.d.ts +49 -0
  268. package/dist/sandbox-git-lease.js +60 -0
  269. package/dist/sandbox-git-plane.d.ts +84 -0
  270. package/dist/sandbox-git-plane.js +105 -0
  271. package/dist/security.d.ts +16 -4
  272. package/dist/security.js +8 -3
  273. package/dist/server-secret-env.d.ts +12 -3
  274. package/dist/server-secret-env.js +18 -2
  275. package/dist/session-error-wire.d.ts +14 -6
  276. package/dist/session-error-wire.js +2 -1
  277. package/dist/session-incarnation.d.ts +4 -5
  278. package/dist/session-incarnation.js +2 -2
  279. package/dist/session-policy-wire.d.ts +1 -1
  280. package/dist/session-policy-wire.js +2 -2
  281. package/dist/session-sync.d.ts +1 -1
  282. package/dist/session-workspace.d.ts +130 -0
  283. package/dist/session-workspace.js +83 -0
  284. package/dist/spec-fields.d.ts +44 -14
  285. package/dist/spec-fields.js +33 -12
  286. package/dist/standing-answer-grant.d.ts +2 -13
  287. package/dist/standing-answer-grant.js +0 -7
  288. package/dist/stored-request-prompt-profile-m30.d.ts +49 -0
  289. package/dist/stored-request-prompt-profile-m30.js +25 -0
  290. package/dist/task-a2a.d.ts +44 -14
  291. package/dist/task-a2a.js +46 -20
  292. package/dist/task-mcp.d.ts +34 -21
  293. package/dist/task-mcp.js +46 -22
  294. package/dist/task-settings.d.ts +2 -2
  295. package/dist/tool-approval.d.ts +43 -9
  296. package/dist/tool-approval.js +57 -15
  297. package/dist/tool-rule-names.d.ts +5 -0
  298. package/dist/trace/core-keyset-guard.d.ts +34 -19
  299. package/dist/trace/engine-notice-wire.d.ts +1 -1
  300. package/dist/trace/engine-notice-wire.js +4 -0
  301. package/dist/trace/exit-seat.d.ts +20 -1
  302. package/dist/trace/exit-seat.js +5 -2
  303. package/dist/trace/extension-call-face.d.ts +33 -0
  304. package/dist/trace/extension-call-face.js +39 -0
  305. package/dist/trace/ledger-sink.d.ts +4 -5
  306. package/dist/trace/ledger-sink.js +3 -7
  307. package/dist/trace/project.d.ts +62 -10
  308. package/dist/trace/project.js +49 -9
  309. package/dist/trace/redact.d.ts +18 -6
  310. package/dist/trace/redact.js +154 -60
  311. package/dist/trace/self-minted-wire.d.ts +500 -7
  312. package/dist/trace/self-minted-wire.js +182 -1
  313. package/dist/trace/wire-projection-faces.d.ts +7 -5
  314. package/dist/trace/wire-projection-faces.js +9 -4
  315. package/dist/user-authored-text.d.ts +2 -2
  316. package/dist/user-authored-text.js +16 -1
  317. package/dist/web-admin-client/apply-effective.js +0 -1
  318. package/dist/web-admin-client/center-request.d.ts +23 -4
  319. package/dist/web-admin-client/center-request.js +42 -8
  320. package/dist/web-admin-client/http-client.js +6 -6
  321. package/dist/web-admin-client/skills-mcp.d.ts +8 -4
  322. package/dist/web-admin-client/skills-mcp.js +31 -18
  323. package/dist/web-admin-client/types.d.ts +10 -5
  324. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +11 -5
  325. package/package.json +3 -3
  326. package/wire-changes/server@7.137.0.json +825 -0
  327. package/wire-changes/server@7.138.0.json +446 -0
@@ -13,6 +13,7 @@ import { spaceCommitAlive } from "../workspace-space-commit.js";
13
13
  import { incarnationLogPrefix, redactIncarnations } from "../session-incarnation.js";
14
14
  import { WORKSPACE_CACHE_SPACE_LABEL, workspaceCacheClaimOf, workspaceCacheEnv } from "../workspace-cache.js";
15
15
  import { recordFailOpen } from "../observability/fail-open.js";
16
+ import { PodDeleteRefused, reportPodDeleteFailed } from "./k8s-pod-delete-failure.js";
16
17
  import { buildLivenessCut, commandBoundsOf, laneLivenessOf, LIVENESS_MS_DEFAULT } from "../exec-bounds.js";
17
18
  import { SANDBOX_IPV6_DEFAULT, sandboxEgressGaps, sandboxPodSysctls } from "../sandbox-egress-policy.js";
18
19
  import { FileError, ExecutionError, RemoteExecutionError, withRetry, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
@@ -39,9 +40,12 @@ const unsupported = (op) => ({
39
40
  const SA_DIR = "/var/run/secrets/kubernetes.io/serviceaccount";
40
41
  const EXEC_SUBPROTOCOLS = ["v5.channel.k8s.io", "v4.channel.k8s.io"];
41
42
  const WRITE_CHUNK_BYTES = 64 * 1024;
42
- import { applyPodSpecPatch } from "./k8s-exec-protocol.js";
43
+ import { applyPodSpecPatch, ADAPTER_VOLUME_NAMES, SANDBOX_POD_LABELS, SANDBOX_RUN_END_HOLD_ANNOTATION, SANDBOX_RUN_LABEL, SANDBOX_SECRET_SEAT_VERBS, isLabelValue } from "./k8s-exec-protocol.js";
43
44
  export { applyPodSpecPatch } from "./k8s-exec-protocol.js";
44
- const CACHE_VOLUME = "ws-cache";
45
+ import { sandboxRunEndHoldMs } from "./k8s-pod-reaper.js";
46
+ import { BROKER_COMMAND, BROKER_CONTAINER_NAME, BROKER_LEASE_DIR, BROKER_LEASE_KEY, BROKER_PROBE_ARG, BROKER_VOLUME_NAME } from "../broker/constants.js";
47
+ import { buildSandboxLeaseFile, placeholderLeaseEntries, sandboxGitRouteEnv } from "../sandbox-git-lease.js";
48
+ const CACHE_VOLUME = ADAPTER_VOLUME_NAMES.cache;
45
49
  function occupantKeyOf(occupant) {
46
50
  if (occupant === undefined)
47
51
  return { state: "unavailable", reason: "incarnation_unknown" };
@@ -168,12 +172,39 @@ export function errorMessageOf(e) {
168
172
  }
169
173
  export { exitCodeFromStatus } from "./k8s-exec-protocol.js";
170
174
  export const K8S_SANDBOX_NAMESPACE_DEFAULT = "sandboxes";
171
- const SANDBOX_POD_LABELS = { app: "sema-sandbox", "managed-by": "sema-server" };
175
+ const SANDBOX_SECRET_NAME_PREFIX = "sbx-env-";
176
+ const SANDBOX_SECRET_LABEL = "sema.io/sandbox-secret";
177
+ const secretEnvKey = (name) => `env.${name}`;
178
+ const SANDBOX_LEASE_NAME_PREFIX = "sbx-lease-";
179
+ const SANDBOX_LEASE_LABEL = "sema.io/sandbox-lease";
180
+ function brokerContainer(image) {
181
+ return {
182
+ name: BROKER_CONTAINER_NAME,
183
+ image,
184
+ command: [...BROKER_COMMAND],
185
+ env: [{ name: "BUN_RUNTIME_TRANSPILER_CACHE_PATH", value: "0" }],
186
+ securityContext: {
187
+ runAsUser: 0,
188
+ runAsGroup: 0,
189
+ runAsNonRoot: false,
190
+ privileged: false,
191
+ allowPrivilegeEscalation: false,
192
+ readOnlyRootFilesystem: true,
193
+ capabilities: { drop: ["ALL"] },
194
+ seccompProfile: { type: "RuntimeDefault" },
195
+ procMount: "Default",
196
+ },
197
+ volumeMounts: [{ name: BROKER_VOLUME_NAME, mountPath: BROKER_LEASE_DIR, readOnly: true }],
198
+ readinessProbe: { exec: { command: [...BROKER_COMMAND, BROKER_PROBE_ARG] }, periodSeconds: 2, timeoutSeconds: 5 },
199
+ resources: { limits: { cpu: "250m", memory: "256Mi" } },
200
+ };
201
+ }
172
202
  export class RemoteK8sExecutionEnv {
173
203
  capabilities;
174
204
  cwd;
175
205
  podName;
176
206
  podIP;
207
+ leaseRef;
177
208
  destroyed = false;
178
209
  handleSnapshotId;
179
210
  inFlight = new Set();
@@ -185,6 +216,7 @@ export class RemoteK8sExecutionEnv {
185
216
  }
186
217
  homeSeat;
187
218
  cdPath = "unknown";
219
+ withheldEnv = [];
188
220
  cfg;
189
221
  deps;
190
222
  occupantIncarnation;
@@ -220,6 +252,9 @@ export class RemoteK8sExecutionEnv {
220
252
  ...(config.sessionId ? { sessionId: config.sessionId } : {}),
221
253
  ...(config.podSpecPatch ? { podSpecPatch: config.podSpecPatch } : {}),
222
254
  ...(config.podEnv ? { podEnv: config.podEnv } : {}),
255
+ ...(config.podSecretEnv && Object.keys(config.podSecretEnv).length > 0 ? { podSecretEnv: config.podSecretEnv } : {}),
256
+ ...(config.runId !== undefined && config.runId !== "" && isLabelValue(config.runId) ? { runId: config.runId } : {}),
257
+ ...(config.sandboxBroker !== undefined ? { sandboxBroker: config.sandboxBroker } : {}),
223
258
  ...(config.snapshotExclude && config.snapshotExclude.length > 0 ? { snapshotExclude: config.snapshotExclude } : {}),
224
259
  };
225
260
  this.cwd = this.cfg.mountPath;
@@ -233,7 +268,7 @@ export class RemoteK8sExecutionEnv {
233
268
  this.logger = config.logger;
234
269
  this.onWorkspaceSnapshot = config.onWorkspaceSnapshot;
235
270
  this.cacheMisses = config.cacheMisses;
236
- this.capabilities = { isolation: true, suspendable: !!this.cfg.s3Snapshot, canonicalPathAuthoritative: true };
271
+ this.capabilities = { isolation: true, suspendable: !!this.cfg.s3Snapshot, canonicalPathAuthoritative: true, sharesProcessTable: false };
237
272
  const bgMax = Math.max(1, Math.min(K8S_BG_MAX_TIMEOUT_SEC, Math.floor(this.cfg.timeoutMs / 1000)));
238
273
  this.backgroundCapabilities = {
239
274
  supported: true,
@@ -243,6 +278,9 @@ export class RemoteK8sExecutionEnv {
243
278
  supportsDetach: true,
244
279
  };
245
280
  }
281
+ get sandboxLeaseRef() {
282
+ return this.leaseRef !== undefined && this.leaseRef.podName === this.podName ? this.leaseRef : undefined;
283
+ }
246
284
  get lifetimeMs() {
247
285
  return this.cfg.timeoutMs;
248
286
  }
@@ -293,7 +331,7 @@ export class RemoteK8sExecutionEnv {
293
331
  headers: {
294
332
  authorization: `Bearer ${token}`,
295
333
  accept: "application/json",
296
- ...(payload ? { "content-type": "application/json", "content-length": Buffer.byteLength(payload) } : {}),
334
+ ...(payload ? { "content-type": opts?.contentType ?? "application/json", "content-length": Buffer.byteLength(payload) } : {}),
297
335
  },
298
336
  ...(isHttps ? { ca: ca ? [ca] : undefined, rejectUnauthorized: !this.cfg.insecureTls } : {}),
299
337
  timeout: opts?.timeoutMs ?? this.cfg.rpcTimeoutMs,
@@ -509,7 +547,7 @@ export class RemoteK8sExecutionEnv {
509
547
  const claim = {
510
548
  apiVersion: "v1",
511
549
  kind: "PersistentVolumeClaim",
512
- metadata: { name: cache.claim.name, labels: { [WORKSPACE_CACHE_SPACE_LABEL]: cache.claim.label, "managed-by": "sema-server" } },
550
+ metadata: { name: cache.claim.name, labels: { [WORKSPACE_CACHE_SPACE_LABEL]: cache.claim.label, "managed-by": SANDBOX_POD_LABELS["managed-by"] } },
513
551
  spec: { accessModes: ["ReadWriteOnce"], resources: { requests: { storage: cache.size } } },
514
552
  };
515
553
  const made = await this.request("POST", base, claim, { signal });
@@ -562,6 +600,38 @@ export class RemoteK8sExecutionEnv {
562
600
  applyPodSpecPatch(probe, { labels: this.cfg.podSpecPatch.labels });
563
601
  return sandboxEgressGaps({ nodes, policies, podLabels: probe.metadata.labels, ipv6: this.cfg.ipv6 });
564
602
  }
603
+ async secretSeatReading(seat) {
604
+ const answers = await Promise.all(SANDBOX_SECRET_SEAT_VERBS[seat].map(async (verb) => {
605
+ try {
606
+ const r = await this.request("POST", "/apis/authorization.k8s.io/v1/selfsubjectaccessreviews", {
607
+ apiVersion: "authorization.k8s.io/v1",
608
+ kind: "SelfSubjectAccessReview",
609
+ spec: { resourceAttributes: { namespace: this.cfg.namespace, verb, resource: "secrets" } },
610
+ });
611
+ const allowed = r.json?.status?.allowed;
612
+ const answered = r.status >= 200 && r.status < 300;
613
+ return answered && typeof allowed === "boolean" ? { verb, allowed } : { verb, unasked: answered ? "answer without status.allowed" : `HTTP ${r.status}` };
614
+ }
615
+ catch (e) {
616
+ return { verb, unasked: errorMessageOf(e) };
617
+ }
618
+ }));
619
+ const denied = answers.flatMap((a) => ("allowed" in a && !a.allowed ? [a.verb] : []));
620
+ const unasked = answers.flatMap((a) => ("unasked" in a ? [`${a.verb}: ${a.unasked}`] : []));
621
+ if (denied.length > 0)
622
+ return { state: "denied", verbs: denied };
623
+ if (unasked.length > 0)
624
+ return { state: "unverified", detail: unasked.join("; ") };
625
+ return { state: "granted" };
626
+ }
627
+ async listSandboxPods() {
628
+ const selector = encodeURIComponent(Object.entries(SANDBOX_POD_LABELS).map(([k, v]) => `${k}=${v}`).join(","));
629
+ const r = await this.request("GET", `/api/v1/namespaces/${this.cfg.namespace}/pods?labelSelector=${selector}`);
630
+ if (r.status < 200 || r.status >= 300)
631
+ throw new Error(`sandbox pod listing answered HTTP ${r.status}`);
632
+ const items = r.json?.items;
633
+ return (Array.isArray(items) ? items : []).flatMap((item) => sandboxPodListingOf(item) ?? []);
634
+ }
565
635
  reclaimDeadlinePassed(what) {
566
636
  return `reclaim deadline (${this.cfg.dataTimeoutMs} ms, the snapshot time bound, one per reclaim call) passed — ${what}`;
567
637
  }
@@ -579,19 +649,36 @@ export class RemoteK8sExecutionEnv {
579
649
  }
580
650
  async createPod(signal, cache) {
581
651
  const ns = this.cfg.namespace;
582
- const podEnv = { ...(cache ? workspaceCacheEnv(cache.mount) : {}), ...(this.cfg.podEnv ?? {}) };
652
+ const secretEnv = this.cfg.podSecretEnv ?? {};
653
+ const secretName = Object.keys(secretEnv).length > 0 ? `${SANDBOX_SECRET_NAME_PREFIX}${randomUUID()}` : undefined;
654
+ const broker = this.cfg.sandboxBroker;
655
+ const leaseName = broker !== undefined ? `${SANDBOX_LEASE_NAME_PREFIX}${randomUUID()}` : undefined;
656
+ const placeholder = broker !== undefined ? buildSandboxLeaseFile(broker.origins, placeholderLeaseEntries(broker.origins)) : undefined;
657
+ if (placeholder !== undefined && !placeholder.ok) {
658
+ return this.holdRefusal(new RemoteExecutionError("connect_failed", `the member-credential broker cannot be set up for this pod: ${placeholder.problem}`));
659
+ }
660
+ const layered = { ...(cache ? workspaceCacheEnv(cache.mount) : {}), ...(this.cfg.podEnv ?? {}) };
661
+ const envEntries = [
662
+ ...Object.entries({ ...layered, ...(broker !== undefined ? sandboxGitRouteEnv(broker.origins) : {}) })
663
+ .filter(([name]) => !Object.hasOwn(secretEnv, name))
664
+ .map(([name, value]) => ({ name, value: String(value) })),
665
+ ...(secretName !== undefined ? Object.keys(secretEnv).map((name) => ({ name, valueFrom: { secretKeyRef: { name: secretName, key: secretEnvKey(name) } } })) : []),
666
+ ];
667
+ const pkgSourceMarked = Boolean(this.cfg.podEnv?.SEMA_PKG_SOURCE || secretEnv.SEMA_PKG_SOURCE);
583
668
  const sysctls = sandboxPodSysctls(this.cfg.ipv6);
584
669
  const pod = {
585
670
  apiVersion: "v1",
586
671
  kind: "Pod",
587
672
  metadata: {
588
673
  generateName: "sbx-",
589
- labels: { ...SANDBOX_POD_LABELS },
674
+ labels: { ...SANDBOX_POD_LABELS, ...(this.cfg.runId !== undefined ? { [SANDBOX_RUN_LABEL]: this.cfg.runId } : {}) },
675
+ ...(this.cfg.runId !== undefined ? { annotations: { [SANDBOX_RUN_END_HOLD_ANNOTATION]: String(sandboxRunEndHoldMs(this.cfg.dataTimeoutMs)) } } : {}),
590
676
  },
591
677
  spec: {
592
678
  ...(this.cfg.runtimeClass ? { runtimeClassName: this.cfg.runtimeClass } : {}),
593
679
  ...(sysctls ? { securityContext: { sysctls: sysctls.map((x) => ({ ...x })) } } : {}),
594
680
  restartPolicy: "Never",
681
+ ...(broker !== undefined ? { shareProcessNamespace: false } : {}),
595
682
  automountServiceAccountToken: false,
596
683
  activeDeadlineSeconds: Math.ceil(this.cfg.timeoutMs / 1000),
597
684
  terminationGracePeriodSeconds: 1,
@@ -599,18 +686,30 @@ export class RemoteK8sExecutionEnv {
599
686
  {
600
687
  name: "sandbox",
601
688
  image: this.cfg.image,
602
- command: this.cfg.podEnv?.SEMA_PKG_SOURCE
689
+ command: pkgSourceMarked
603
690
  ? ["sh", "-c", "[ -f /opt/sema/pkg-source.sh ] && sh /opt/sema/pkg-source.sh || true; sleep infinity"]
604
691
  : ["sh", "-c", "sleep infinity"],
605
- ...(Object.keys(podEnv).length > 0 ? { env: Object.entries(podEnv).map(([name, value]) => ({ name, value: String(value) })) } : {}),
692
+ ...(envEntries.length > 0 ? { env: envEntries } : {}),
606
693
  ...(cache ? { volumeMounts: [{ name: CACHE_VOLUME, mountPath: cache.mount }] } : {}),
607
694
  resources: { limits: { cpu: this.cfg.cpu, memory: this.cfg.memory } },
608
695
  },
696
+ ...(broker !== undefined ? [brokerContainer(broker.image)] : []),
609
697
  ],
610
- ...(cache ? { volumes: [{ name: CACHE_VOLUME, persistentVolumeClaim: { claimName: cache.claim.name } }] } : {}),
698
+ ...((volumes) => (volumes.length > 0 ? { volumes } : {}))([
699
+ ...(cache ? [{ name: CACHE_VOLUME, persistentVolumeClaim: { claimName: cache.claim.name } }] : []),
700
+ ...(leaseName !== undefined ? [{ name: BROKER_VOLUME_NAME, secret: { secretName: leaseName, items: [{ key: BROKER_LEASE_KEY, path: BROKER_LEASE_KEY }], defaultMode: 0o400 } }] : []),
701
+ ]),
611
702
  },
612
703
  };
613
- if (this.cfg.podSpecPatch)
704
+ if (this.cfg.podSpecPatch && broker !== undefined) {
705
+ try {
706
+ applyPodSpecPatch(pod, this.cfg.podSpecPatch, { broker: true });
707
+ }
708
+ catch (e) {
709
+ return this.holdRefusal(new RemoteExecutionError("connect_failed", `${errorMessageOf(e)} — no pod is created (the member-credential broker never shares a pod with a patch that could reach it)`));
710
+ }
711
+ }
712
+ else if (this.cfg.podSpecPatch)
614
713
  applyPodSpecPatch(pod, this.cfg.podSpecPatch);
615
714
  let created;
616
715
  try {
@@ -628,6 +727,20 @@ export class RemoteK8sExecutionEnv {
628
727
  const name = created.json?.metadata?.name;
629
728
  if (!name)
630
729
  return { ok: false, error: new RemoteExecutionError("connect_failed", "pod create returned no name") };
730
+ if (secretName !== undefined) {
731
+ const sealed = await this.createPodSecret(secretName, name, created.json?.metadata?.uid, secretEnv, signal);
732
+ if (!sealed.ok) {
733
+ await this.discardRefusedPod(name, "env_secret_unsealed");
734
+ return sealed;
735
+ }
736
+ }
737
+ if (leaseName !== undefined && placeholder?.ok === true) {
738
+ const leased = await this.createLeaseSecret(leaseName, name, created.json?.metadata?.uid, placeholder.text, signal);
739
+ if (!leased.ok) {
740
+ await this.discardRefusedPod(name, "lease_secret_unsealed");
741
+ return leased;
742
+ }
743
+ }
631
744
  const deadline = Date.now() + this.cfg.readyTimeoutMs;
632
745
  let podIP;
633
746
  for (;;) {
@@ -645,7 +758,8 @@ export class RemoteK8sExecutionEnv {
645
758
  }
646
759
  const status = got.json?.status;
647
760
  const phase = status?.phase;
648
- if (phase === "Running") {
761
+ const brokerStatus = status?.containerStatuses?.find((c) => c.name === BROKER_CONTAINER_NAME);
762
+ if (phase === "Running" && (broker === undefined || brokerStatus?.ready === true)) {
649
763
  podIP = status?.podIP;
650
764
  break;
651
765
  }
@@ -653,11 +767,16 @@ export class RemoteK8sExecutionEnv {
653
767
  await this.deletePod(name).catch(() => { });
654
768
  return this.refusePod(name, phase, status?.reason, status?.message);
655
769
  }
656
- const waiting = status?.containerStatuses?.[0]?.state?.waiting;
770
+ const waiting = status?.containerStatuses?.find((c) => c.name !== BROKER_CONTAINER_NAME)?.state?.waiting;
657
771
  if (waiting?.reason === "ImagePullBackOff" || waiting?.reason === "ErrImagePull") {
658
772
  await this.deletePod(name).catch(() => { });
659
773
  return { ok: false, error: new RemoteExecutionError("connect_failed", `image pull failed for ${this.cfg.image}: ${waiting.message ?? waiting.reason}`) };
660
774
  }
775
+ const brokerWaiting = brokerStatus?.state?.waiting;
776
+ if (broker !== undefined && (brokerWaiting?.reason === "ImagePullBackOff" || brokerWaiting?.reason === "ErrImagePull")) {
777
+ await this.discardRefusedPod(name, "broker_image_pull_failed");
778
+ return { ok: false, error: new RemoteExecutionError("connect_failed", `image pull failed for the member-credential broker image ${broker.image} (K8S_SANDBOX_BROKER_IMAGE): ${brokerWaiting.message ?? brokerWaiting.reason}`) };
779
+ }
661
780
  if (Date.now() > deadline) {
662
781
  if (cache) {
663
782
  const bound = await this.cacheClaimBound(cache, signal);
@@ -671,6 +790,9 @@ export class RemoteK8sExecutionEnv {
671
790
  : { cacheMiss: "claim_unbound", detail: `the claim ${cache.claim.name} was not bound within ${this.cfg.dataTimeoutMs}ms (no default StorageClass, or its provisioner is down)` };
672
791
  }
673
792
  await this.deletePod(name).catch(() => { });
793
+ if (phase === "Running" && broker !== undefined) {
794
+ return { ok: false, error: new RemoteExecutionError("timeout", `the member-credential broker container was not ready within ${this.cfg.readyTimeoutMs}ms (it reads its lease file and answers its own readiness probe; image ${broker.image})`) };
795
+ }
674
796
  return { ok: false, error: new RemoteExecutionError("timeout", `pod not Running within ${this.cfg.readyTimeoutMs}ms (phase=${phase ?? "?"})`) };
675
797
  }
676
798
  await new Promise((r) => setTimeout(r, 1000));
@@ -681,6 +803,7 @@ export class RemoteK8sExecutionEnv {
681
803
  }
682
804
  this.podName = name;
683
805
  this.podIP = podIP;
806
+ this.leaseRef = leaseName !== undefined && broker !== undefined ? { namespace: ns, podName: name, leaseSecretName: leaseName, origins: [...broker.origins] } : undefined;
684
807
  const askHome = this.homeSeat.asks;
685
808
  const mk = await this.runExec(`mkdir -p ${shellQuote(this.cfg.mountPath)}${askHome ? ` && { ${LANE_HOME_PROBE_COMMAND} || true; }` : ""}`);
686
809
  if (!mk.ok || mk.value.exitCode !== 0) {
@@ -703,6 +826,94 @@ export class RemoteK8sExecutionEnv {
703
826
  this.logger?.warn("workspace_cache_fresh", { space: incarnationLogPrefix(cache.spaceKey), claim: cache.claim.name, size: cache.size });
704
827
  return ok(this.workspaceHandle());
705
828
  }
829
+ async createPodSecret(secretName, podName, podUid, values, signal) {
830
+ const encoded = Object.entries(values).map(([n, v]) => [secretEnvKey(n), Buffer.from(String(v), "utf8").toString("base64")]);
831
+ return this.createOwnedSecret({
832
+ name: secretName,
833
+ kind: "env",
834
+ label: SANDBOX_SECRET_LABEL,
835
+ immutable: true,
836
+ data: Object.fromEntries(encoded),
837
+ hidden: [...Object.values(values).map(String), ...encoded.map(([, b64]) => b64)],
838
+ what: "sandbox secret",
839
+ effect: "the pod is deleted (deployer-named sandbox env reaches a pod only through its own Secret, never as plaintext in the pod spec)",
840
+ }, podName, podUid, signal);
841
+ }
842
+ async createLeaseSecret(leaseName, podName, podUid, leaseText, signal) {
843
+ const b64 = Buffer.from(leaseText, "utf8").toString("base64");
844
+ return this.createOwnedSecret({
845
+ name: leaseName,
846
+ kind: "lease",
847
+ label: SANDBOX_LEASE_LABEL,
848
+ immutable: false,
849
+ data: { [BROKER_LEASE_KEY]: b64 },
850
+ hidden: [b64],
851
+ what: "sandbox lease secret",
852
+ effect: "the pod is deleted (a pod that carries the member-credential broker never runs without the lease its broker reads)",
853
+ }, podName, podUid, signal);
854
+ }
855
+ async createOwnedSecret(seat, podName, podUid, signal) {
856
+ const { effect } = seat;
857
+ if (typeof podUid !== "string" || podUid === "")
858
+ return { ok: false, error: new RemoteExecutionError("connect_failed", `pod create returned no uid — the ${seat.what} cannot be bound to its pod; ${effect}`) };
859
+ const secret = {
860
+ apiVersion: "v1",
861
+ kind: "Secret",
862
+ metadata: {
863
+ name: seat.name,
864
+ labels: { "managed-by": SANDBOX_POD_LABELS["managed-by"], [seat.label]: podName },
865
+ ownerReferences: [{ apiVersion: "v1", kind: "Pod", name: podName, uid: podUid, blockOwnerDeletion: false }],
866
+ },
867
+ type: "Opaque",
868
+ ...(seat.immutable ? { immutable: true } : {}),
869
+ data: seat.data,
870
+ };
871
+ const hidden = seat.hidden.filter((v) => v !== "");
872
+ const scrub = (text) => hidden.reduce((out, v) => out.split(v).join("<redacted>"), text);
873
+ let made;
874
+ try {
875
+ made = await this.request("POST", `/api/v1/namespaces/${this.cfg.namespace}/secrets`, secret, { signal });
876
+ }
877
+ catch (e) {
878
+ if (signal?.aborted)
879
+ return { ok: false, error: new RemoteExecutionError("aborted", "connect aborted") };
880
+ return { ok: false, error: new RemoteExecutionError("connect_failed", `${seat.what} create RPC failed: ${scrub(errorMessageOf(e)).slice(0, 300)}; ${effect}`) };
881
+ }
882
+ if (made.status === 200 || made.status === 201)
883
+ return { ok: true };
884
+ const detail = scrub(JSON.stringify(made.json) ?? "").slice(0, 300);
885
+ if (made.status === 401 || made.status === 403) {
886
+ return { ok: false, error: new RemoteExecutionError("auth_failed", `${seat.what} create denied (${made.status}): ${detail} — the sandbox Role needs secrets ${SANDBOX_SECRET_SEAT_VERBS[seat.kind].join(" / ")}; ${effect}`) };
887
+ }
888
+ return { ok: false, error: new RemoteExecutionError("connect_failed", `${seat.what} create failed (${made.status}): ${detail}; ${effect}`) };
889
+ }
890
+ async writeLease(ref, entries) {
891
+ const fail = (code, message) => {
892
+ this.logger?.warn("sandbox_lease_write_failed", { namespace: ref.namespace, pod: ref.podName, secretName: ref.leaseSecretName, code, detail: message });
893
+ return { ok: false, code, message };
894
+ };
895
+ const built = buildSandboxLeaseFile(ref.origins, entries);
896
+ if (!built.ok)
897
+ return fail("lease_invalid", `the lease was not sent — the broker would refuse it: ${built.problem}`);
898
+ const b64 = Buffer.from(built.text, "utf8").toString("base64");
899
+ const hidden = [b64, ...entries.flatMap((e) => (e.state === "ready" ? [e.secret, Buffer.from(`${e.username}:${e.secret}`, "utf8").toString("base64")] : []))].filter((v) => v !== "");
900
+ const scrub = (text) => hidden.reduce((out, v) => out.split(v).join("<redacted>"), text);
901
+ let r;
902
+ try {
903
+ r = await this.request("PATCH", `/api/v1/namespaces/${ref.namespace}/secrets/${encodeURIComponent(ref.leaseSecretName)}`, { data: { [BROKER_LEASE_KEY]: b64 } }, { contentType: "application/merge-patch+json" });
904
+ }
905
+ catch (e) {
906
+ return fail("write_failed", `the lease write request failed: ${scrub(errorMessageOf(e)).slice(0, 300)}`);
907
+ }
908
+ if (r.status === 200 || r.status === 201)
909
+ return { ok: true };
910
+ const detail = scrub(JSON.stringify(r.json) ?? "").slice(0, 300);
911
+ if (r.status === 401 || r.status === 403)
912
+ return fail("auth_failed", `the lease write was denied (${r.status}): ${detail} — the sandbox Role needs secrets ${SANDBOX_SECRET_SEAT_VERBS.lease.join(" / ")}`);
913
+ if (r.status === 404)
914
+ return fail("gone", `the lease Secret ${ref.leaseSecretName} is gone (its pod ${ref.podName} was deleted)`);
915
+ return fail("write_failed", `the lease write failed (${r.status}): ${detail}`);
916
+ }
706
917
  presignSnapshot(req) {
707
918
  const s3 = this.cfg.s3Snapshot;
708
919
  const [label, partition] = req.lane === "space" ? ["spaceIncarnation", req.space.incarnation] : ["sessionId", this.cfg.sessionId ?? "_"];
@@ -1090,7 +1301,27 @@ export class RemoteK8sExecutionEnv {
1090
1301
  return this.connect();
1091
1302
  }
1092
1303
  async deletePod(name) {
1093
- await this.request("DELETE", `/api/v1/namespaces/${this.cfg.namespace}/pods/${name}?gracePeriodSeconds=0`, undefined, { timeoutMs: this.cfg.rpcTimeoutMs });
1304
+ const r = await this.request("DELETE", `/api/v1/namespaces/${this.cfg.namespace}/pods/${encodeURIComponent(name)}?gracePeriodSeconds=0`, undefined, { timeoutMs: this.cfg.rpcTimeoutMs });
1305
+ if (r.status === 404)
1306
+ return "gone";
1307
+ if (r.status < 200 || r.status >= 300)
1308
+ throw new PodDeleteRefused(r.status);
1309
+ return "deleted";
1310
+ }
1311
+ async discardRefusedPod(name, why) {
1312
+ try {
1313
+ await this.deletePod(name);
1314
+ }
1315
+ catch (e) {
1316
+ reportPodDeleteFailed({
1317
+ namespace: this.cfg.namespace,
1318
+ pod: name,
1319
+ why,
1320
+ error: e,
1321
+ effect: "the refused connect's pod, and the Secret it owns, stay in the sandbox namespace until the pod reaper reaches them (static data; the connect is refused either way)",
1322
+ logger: this.logger,
1323
+ });
1324
+ }
1094
1325
  }
1095
1326
  async destroy() {
1096
1327
  await this.disposeBackgroundShells().catch(() => { });
@@ -1878,6 +2109,37 @@ export function createSpaceReclaimer(config, deps) {
1878
2109
  export async function checkSandboxEgressPolicy(config, deps) {
1879
2110
  return new RemoteK8sExecutionEnv(config, deps).egressPolicyGaps();
1880
2111
  }
2112
+ export async function readSandboxSecretSeat(config, seat, deps) {
2113
+ return new RemoteK8sExecutionEnv(config, deps).secretSeatReading(seat);
2114
+ }
2115
+ export async function writeSandboxLease(config, ref, entries, deps) {
2116
+ return new RemoteK8sExecutionEnv(config, deps).writeLease(ref, entries);
2117
+ }
2118
+ export function createSandboxPodApi(config, deps) {
2119
+ let env;
2120
+ const control = () => (env ??= new RemoteK8sExecutionEnv(config, deps));
2121
+ return {
2122
+ namespace: config.namespace ?? K8S_SANDBOX_NAMESPACE_DEFAULT,
2123
+ list: () => control().listSandboxPods(),
2124
+ delete: (name) => control().deletePod(name),
2125
+ };
2126
+ }
2127
+ function sandboxPodListingOf(item) {
2128
+ const o = item;
2129
+ const name = o?.metadata?.name;
2130
+ if (typeof name !== "string" || name === "")
2131
+ return undefined;
2132
+ const runId = o?.metadata?.labels?.[SANDBOX_RUN_LABEL];
2133
+ const runEndHold = o?.metadata?.annotations?.[SANDBOX_RUN_END_HOLD_ANNOTATION];
2134
+ const phase = o?.status?.phase;
2135
+ return {
2136
+ name,
2137
+ ...(typeof runId === "string" && runId !== "" ? { runId } : {}),
2138
+ ...(typeof runEndHold === "string" ? { runEndHold } : {}),
2139
+ ...(typeof phase === "string" ? { phase } : {}),
2140
+ terminating: typeof o?.metadata?.deletionTimestamp === "string",
2141
+ };
2142
+ }
1881
2143
  export function k8sExecutionEnvFactory(config, deps) {
1882
2144
  return (ctx) => new RemoteK8sExecutionEnv({ ...config, sessionId: ctx.sessionId }, deps);
1883
2145
  }
@@ -62,10 +62,12 @@ export interface LocalDockerEnvDeps {
62
62
  }
63
63
  export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv {
64
64
  /** DUAL-MODE-DESIGN §5: a container is a real isolation boundary; v1 has no managed memory snapshot. */
65
+ /** `sharesProcessTable`(core 7.57.0,必填):命令跑在本机容器自己的 PID 命名空间里(不传 `--pid=host`),不在本进程所在的进程表。 */
65
66
  readonly capabilities: {
66
67
  isolation: boolean;
67
68
  suspendable: boolean;
68
69
  canonicalPathAuthoritative: boolean;
70
+ sharesProcessTable: boolean;
69
71
  };
70
72
  /** Working directory inside the container; relative paths resolve against it (ExecutionEnv contract). */
71
73
  cwd: string;
@@ -74,6 +76,9 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
74
76
  /** core 7.49.0 [ref]:shell 子进程真拿到的 `CDPATH` 条目 —— 本车道的命令跑在容器里(镜像的 env),本类不读那份 env ⇒ `"unknown"`(core 契约:说不出就说 unknown,
75
77
  * 不许报一张没读过的空表;删除守卫因此对裸 `cd <相对名>` 之后的删除问人,而不是按「没有 CDPATH」放行)。 */
76
78
  readonly cdPath: "unknown";
79
+ /** core 7.56.0 [ref]:shell 子进程继承的环境里收不到的条目 —— 本车道 shell 的环境在容器里**构建**(本进程的 env 从不注入,只有引擎逐次交的 `env`
80
+ * 这条显式授予),没有一条是从本进程继承的 ⇒ `[]`(core 契约:「`[]` when its shell environment is built rather than inherited」)。 */
81
+ readonly withheldEnv: readonly [];
77
82
  private readonly cfg;
78
83
  private readonly spawnFn;
79
84
  /** Stable container NAME (also our handle id). Created on connect; targeted by every docker exec/cp/rm. */
@@ -21,12 +21,13 @@ const unsupported = (op) => ({
21
21
  error: new RemoteExecutionError("unsupported", `${op} is not supported on a local-docker target (v1: a container is an isolation boundary but not a managed memory snapshot; capabilities.suspendable=false)`),
22
22
  });
23
23
  export class RemoteLocalDockerExecutionEnv {
24
- capabilities = { isolation: true, suspendable: false, canonicalPathAuthoritative: true };
24
+ capabilities = { isolation: true, suspendable: false, canonicalPathAuthoritative: true, sharesProcessTable: false };
25
25
  cwd;
26
26
  get homeDir() {
27
27
  return this.homeSeat.home;
28
28
  }
29
29
  cdPath = "unknown";
30
+ withheldEnv = [];
30
31
  cfg;
31
32
  spawnFn;
32
33
  containerName;
@@ -59,14 +59,16 @@ export declare class RemoteSshExecutionEnv implements RemoteExecutionEnv {
59
59
  * (RPC 断、传输掉),不是「一个它看不见的命名空间」⇒ 读边界的执行期复核**拒**,而不是让词法判决站着。
60
60
  * 缺席是今天每个适配器的读法(整链沉默 ⇒ 词法判决成立、命令照跑),所以这一声明是**收紧**方向。
61
61
  * 严格读法 `=== true`(core `canonicalPathIsAuthoritative`);`isSuspendable` 一族同样严格,对本仓的
62
- * 布尔字面量零影响(三键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
62
+ * 布尔字面量零影响(四键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
63
63
  * core 的 `config.execution_env_capability_invalid` 普查响亮播报,本仓的受众表已登记)。
64
64
  * 谁该声明由闭集门看着(`test/execution-lane-caps.test.ts` 的逐 lane 对账:新 lane 不表态 = 编译红)。
65
65
  */
66
+ /** `sharesProcessTable`(core 7.57.0,必填):命令跑在 SSH 目标机上;目标恰是本机(或本进程所在容器的宿主)时命令与本进程同表,这一形今天没有配置面可声明 `true`。 */
66
67
  readonly capabilities: {
67
68
  isolation: boolean;
68
69
  suspendable: boolean;
69
70
  canonicalPathAuthoritative: boolean;
71
+ sharesProcessTable: boolean;
70
72
  };
71
73
  /** Working directory; relative paths resolve against it (ExecutionEnv contract). Starts at mountPath. */
72
74
  cwd: string;
@@ -95,6 +97,9 @@ export declare class RemoteSshExecutionEnv implements RemoteExecutionEnv {
95
97
  /** core 7.49.0 [ref]:shell 子进程真拿到的 `CDPATH` 条目 —— 本车道的命令跑在远端登录 shell 里(目标机的 profile / env),本类不读那份 env ⇒ `"unknown"`(core 契约:说不出就说 unknown,
96
98
  * 不许报一张没读过的空表;删除守卫因此对裸 `cd <相对名>` 之后的删除问人,而不是按「没有 CDPATH」放行)。 */
97
99
  readonly cdPath: "unknown";
100
+ /** core 7.56.0 [ref]:shell 子进程继承的环境里收不到的条目 —— 本车道 shell 的环境在目标机上**构建**(本进程的 env 从不注入,只有引擎逐次交的 `env`
101
+ * 这条显式授予),没有一条是从本进程继承的 ⇒ `[]`(core 契约:「`[]` when its shell environment is built rather than inherited」)。 */
102
+ readonly withheldEnv: readonly [];
98
103
  private readonly cfg;
99
104
  private readonly injectedConnectOnce?;
100
105
  constructor(config: SshEnvConfig, deps?: SshEnvDeps);
@@ -18,7 +18,7 @@ function exitCodeForSignalKill(signal) {
18
18
  return 128 + (signalNumber(`SIG${signal}`) ?? 9);
19
19
  }
20
20
  export class RemoteSshExecutionEnv {
21
- capabilities = { isolation: false, suspendable: false, canonicalPathAuthoritative: true };
21
+ capabilities = { isolation: false, suspendable: false, canonicalPathAuthoritative: true, sharesProcessTable: false };
22
22
  cwd;
23
23
  conn;
24
24
  sftpPromise;
@@ -32,6 +32,7 @@ export class RemoteSshExecutionEnv {
32
32
  homeChecked;
33
33
  homeRefusal;
34
34
  cdPath = "unknown";
35
+ withheldEnv = [];
35
36
  cfg;
36
37
  injectedConnectOnce;
37
38
  constructor(config, deps) {
@@ -52,7 +52,11 @@ import { type SqlDriver, type SqlTxConn } from "./sql-driver.js";
52
52
  import { type IndexSpec } from "./ensure-index.js";
53
53
  import type { EndedSpace } from "../workspace-space.js";
54
54
  /**
55
- * 三只**被托管**的 SQL 店(session / checkpoint / tool-result)共用的 `retention` 声明常量。
55
+ * 三只**被托管**的 SQL 店(session / checkpoint / tool-result)在扫车道开着时的 `retention` 声明常量。
56
+ *
57
+ * 🔴 **谁说这个词**(引擎 7.58.0 起:店说 `"managed"` 而部署不跑执行器 ⇒ 锁不锁都拒启):「这只店的行有没有人按期删」不是店类自己知道的事,
58
+ * 是装配它的部署知道的事。唯一事实 = 扫车道开不开({@link retentionSweepRuns});后端铸店时按它给词({@link sqlStoreRetention}),
59
+ * 交给引擎启动检查的执行器读同一个事实(开 ⇒ 执行器,关 ⇒ `null`)。店构造不给词 ⇒ `"none"`(引擎对缺席的同一读法)。
56
60
  *
57
61
  * 🔴 为什么是一个共享常量而不是各写各的字面量:这一格是 core `assertRetentionCapability` 的**唯一**读点,
58
62
  * 三只店的答案必须同进同退 —— 哪天本能力从某只店的行上撤了(比如 tool_result 改由别处清),漏改一处
@@ -66,8 +70,16 @@ import type { EndedSpace } from "../workspace-space.js";
66
70
  * file/in-memory 形没有任何东西会按期删它们的行 ⇒ 显式 `"none"`(缺席也读 none,但显式是文档义务)。
67
71
  */
68
72
  export declare const MANAGED_RETENTION: RetentionDeclaration;
69
- /** file/in-memory 形的诚实声明(见 {@link MANAGED_RETENTION} 的读法说明)。 */
73
+ /** file/in-memory 形、以及扫车道关着的 SQL 店的诚实声明(见 {@link MANAGED_RETENTION} 的读法说明)。 */
70
74
  export declare const UNMANAGED_RETENTION: RetentionDeclaration;
75
+ /** 本部署跑不跑托管留存的扫车道(`RETENTION_SWEEP_INTERVAL_SEC > 0`)—— 三只 SQL 店的声明与交给引擎启动检查的执行器只读这一处。 */
76
+ export declare function retentionSweepRuns(sweep: {
77
+ readonly intervalSec: number;
78
+ }): boolean;
79
+ /** 后端铸三只被托管的 SQL 店时交给店的词:扫车道开 ⇒ `"managed"`,关 ⇒ `"none"`。 */
80
+ export declare function sqlStoreRetention(sweep: {
81
+ readonly intervalSec: number;
82
+ }): RetentionDeclaration;
71
83
  /** 追加式审计表(设计稿 §6)。 */
72
84
  export declare const RETENTION_AUDIT_TABLE = "retention_audit";
73
85
  /** per-domain 法务保留标记(设计稿 §5)——**行在 = 该域整体冻结**。 */
@@ -5,6 +5,12 @@ import { ensureIndexes } from "./ensure-index.js";
5
5
  import { incarnationFromCell } from "../session-incarnation.js";
6
6
  export const MANAGED_RETENTION = "managed";
7
7
  export const UNMANAGED_RETENTION = "none";
8
+ export function retentionSweepRuns(sweep) {
9
+ return sweep.intervalSec > 0;
10
+ }
11
+ export function sqlStoreRetention(sweep) {
12
+ return retentionSweepRuns(sweep) ? MANAGED_RETENTION : UNMANAGED_RETENTION;
13
+ }
8
14
  export const RETENTION_AUDIT_TABLE = "retention_audit";
9
15
  export const RETENTION_HOLD_TABLE = "retention_hold";
10
16
  export const RETENTION_LEASE_TABLE = "retention_lease";
@@ -10,10 +10,11 @@ import { RUN_HOLDER_DEATH_CODES, RUN_STALLED_ERROR, readLegacyHolderDeathCode }
10
10
  import { notifyRunTerminal, runTerminalLogLevel } from "../observability/run-terminal.js";
11
11
  import { mysqlDriver, pgDriver, dialectJsonEncoder, SQL_DB_NOW_MS, sqlDbNowMs } from "./sql-driver.js";
12
12
  import { isDupKeyError } from "./sql-errors.js";
13
+ import { readRunIdentityCells, runIdentityCells } from "../identity-source.js";
13
14
  import { decideSpaceCommitAbandon, decideSpaceCommitClaim, fenceSpaceCommitAtTerminal, spaceCommitStateOf } from "../workspace-space-commit.js";
14
15
  function runRowCols(dialect) {
15
16
  return (`task_id, session_id, owner, status, ${jsonColumnText(dialect, "result")}, error, error_code, job_id, source, ` +
16
- "instance_id, cancel_requested, objective_preview, created_at, updated_at");
17
+ "identity_source, identity_scope, identity_principal_inc, instance_id, cancel_requested, objective_preview, created_at, updated_at");
17
18
  }
18
19
  const readPersistedTaskResultBody = createObjectColumnReader();
19
20
  function decodeResultColumn(v, taskId) {
@@ -84,15 +85,19 @@ export class SqlRunStore {
84
85
  try {
85
86
  await conn.query(this.q("INSERT INTO task_active (session_id, task_id, since) VALUES (?,?,?)", "INSERT INTO task_active (session_id, task_id, since) VALUES ($1,$2,$3)"), [sessionId, taskId, now]);
86
87
  stage = "row";
87
- await conn.query(this.q("INSERT INTO task_run (task_id, session_id, owner, status, instance_id, job_id, source, objective_preview, idem_key, idem_body_fingerprint, created_at, updated_at) " +
88
- "VALUES (?,?,?,'running',?,?,?,?,?,?,?,?)", "INSERT INTO task_run (task_id, session_id, owner, status, instance_id, job_id, source, objective_preview, idem_key, idem_body_fingerprint, created_at, updated_at) " +
89
- "VALUES ($1,$2,$3,'running',$4,$5,$6,$7,$8,$9,$10,$11)"), [
88
+ const cells = runIdentityCells(meta.identity ?? null);
89
+ await conn.query(this.q("INSERT INTO task_run (task_id, session_id, owner, status, instance_id, job_id, source, identity_source, identity_scope, identity_principal_inc, objective_preview, idem_key, idem_body_fingerprint, created_at, updated_at) " +
90
+ "VALUES (?,?,?,'running',?,?,?,?,?,?,?,?,?,?,?)", "INSERT INTO task_run (task_id, session_id, owner, status, instance_id, job_id, source, identity_source, identity_scope, identity_principal_inc, objective_preview, idem_key, idem_body_fingerprint, created_at, updated_at) " +
91
+ "VALUES ($1,$2,$3,'running',$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14)"), [
90
92
  taskId,
91
93
  sessionId,
92
94
  owner,
93
95
  instanceId,
94
96
  meta.jobId ?? null,
95
97
  meta.source ?? null,
98
+ cells.identitySource,
99
+ cells.identityScope,
100
+ cells.identityPrincipalInc,
96
101
  this.db.dialect === "tidb"
97
102
  ? meta.objectivePreview ?? null
98
103
  : meta.objectivePreview == null
@@ -299,6 +304,7 @@ export class SqlRunStore {
299
304
  errorCode: readLegacyHolderDeathCode({ status: String(r.status), error: r.error ?? null, errorCode: r.error_code ?? null }),
300
305
  jobId: r.job_id ?? null,
301
306
  source: r.source ?? null,
307
+ identity: readRunIdentityCells(r.identity_source, r.identity_scope, r.identity_principal_inc),
302
308
  instanceId: r.instance_id ?? null,
303
309
  cancelRequested: Boolean(r.cancel_requested),
304
310
  objectivePreview: r.objective_preview ?? null,