@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.
- package/MIGRATION.md +501 -139
- package/README.md +1 -0
- package/README.zh-CN.md +1 -0
- package/USAGE.md +149 -34
- package/deploy/sema-up/README.md +14 -0
- package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +7 -1
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +7 -1
- package/dist/approval-card.d.ts +21 -14
- package/dist/approval-card.js +1 -0
- package/dist/approval-content-kind.d.ts +6 -0
- package/dist/approval-content-kind.js +7 -0
- package/dist/approval-gate-currency.d.ts +48 -6
- package/dist/approval-gate-currency.js +16 -9
- package/dist/approval-hmac.d.ts +13 -1
- package/dist/approval-hmac.js +1 -1
- package/dist/approval.js +3 -3
- package/dist/audit.js +1 -0
- package/dist/auth-bridge.js +2 -2
- package/dist/background-agent-name.d.ts +5 -0
- package/dist/background-agent-name.js +5 -0
- package/dist/boot/budget-tracing.d.ts +1 -12
- package/dist/boot/deployment-mods.d.ts +46 -16
- package/dist/boot/deployment-mods.js +82 -6
- package/dist/boot/execution-env.d.ts +24 -1
- package/dist/boot/execution-env.js +69 -6
- package/dist/boot/governance-seams.d.ts +10 -17
- package/dist/boot/governance-seams.js +5 -17
- package/dist/boot/leader.d.ts +3 -0
- package/dist/boot/leader.js +1 -0
- package/dist/boot/org-delivery.js +15 -2
- package/dist/boot/reapers.d.ts +3 -0
- package/dist/boot/reapers.js +15 -1
- package/dist/boot/resolve-spec.d.ts +7 -0
- package/dist/boot/resolve-spec.js +36 -26
- package/dist/boot/retention-lane.d.ts +11 -5
- package/dist/boot/retention-lane.js +2 -2
- package/dist/boot/runner-deps.d.ts +7 -3
- package/dist/boot/runner-deps.js +1 -0
- package/dist/boot/stage-03-store-backend.d.ts +1 -1
- package/dist/boot/stage-04-stores.d.ts +2 -13
- package/dist/boot/stage-05-execution-env.d.ts +6 -13
- package/dist/boot/stage-05-execution-env.js +17 -5
- package/dist/boot/stage-06-runners.d.ts +6 -13
- package/dist/boot/stage-06-runners.js +10 -3
- package/dist/boot/stage-07-capability-layer.d.ts +7 -13
- package/dist/boot/stage-07-capability-layer.js +3 -3
- package/dist/boot/stage-08-reapers.d.ts +7 -13
- package/dist/boot/stage-08-reapers.js +2 -1
- package/dist/boot/stage-09-leader.d.ts +7 -13
- package/dist/boot/stage-09-leader.js +2 -2
- package/dist/boot/stage-10-http-server.d.ts +7 -13
- package/dist/boot/stage-10-http-server.js +6 -3
- package/dist/boot/stores.d.ts +1 -1
- package/dist/boot/web-admin.d.ts +5 -2
- package/dist/boot/web-admin.js +3 -3
- package/dist/brain.js +5 -5
- package/dist/broker/broker.d.ts +89 -0
- package/dist/broker/broker.js +287 -0
- package/dist/broker/constants.d.ts +39 -0
- package/dist/broker/constants.js +15 -0
- package/dist/broker/http1.d.ts +111 -0
- package/dist/broker/http1.js +256 -0
- package/dist/broker/lease-book.d.ts +87 -0
- package/dist/broker/lease-book.js +142 -0
- package/dist/broker/lease-file.d.ts +60 -0
- package/dist/broker/lease-file.js +127 -0
- package/dist/broker/main.d.ts +2 -0
- package/dist/broker/main.js +20 -0
- package/dist/budget.d.ts +17 -15
- package/dist/budget.js +2 -2
- package/dist/capabilities/composite-members.d.ts +2 -1
- package/dist/capabilities/composite-members.js +2 -1
- package/dist/capabilities/engine-capabilities.js +1 -0
- package/dist/capabilities/mcp-probe.d.ts +36 -11
- package/dist/capabilities/mcp-probe.js +44 -5
- package/dist/capabilities/scenarios.d.ts +23 -8
- package/dist/capabilities/scenarios.js +13 -8
- package/dist/capabilities/skills.d.ts +28 -5
- package/dist/capabilities/skills.js +8 -0
- package/dist/capability-facts.d.ts +6 -0
- package/dist/capability-facts.js +5 -0
- package/dist/center-credential-renewer.js +5 -1
- package/dist/checkpoint-ctx-seal-m28.d.ts +3 -10
- package/dist/checkpoint-ctx-seal-m28.js +1 -1
- package/dist/checkpoint-identity.d.ts +13 -6
- package/dist/config-catalog.js +16 -7
- package/dist/config-types.d.ts +24 -7
- package/dist/config.d.ts +1 -1
- package/dist/config.js +42 -13
- package/dist/deployment-governance.d.ts +2 -2
- package/dist/deployment-governance.js +7 -9
- package/dist/deployment-shape.d.ts +6 -6
- package/dist/device-ws-protocol.d.ts +1 -0
- package/dist/device-ws-protocol.js +2 -1
- package/dist/file-history-disclosure.d.ts +4 -2
- package/dist/file-history-disclosure.js +2 -0
- package/dist/gate-settlement.d.ts +71 -0
- package/dist/gate-settlement.js +40 -0
- package/dist/hooks/classic-stations.d.ts +12 -54
- package/dist/hooks/classic-stations.js +1 -0
- package/dist/hooks/command-spawn.d.ts +2 -2
- package/dist/hooks/hook-runner.js +14 -14
- package/dist/http/admission.js +14 -9
- package/dist/http/approval-sweeps.js +4 -3
- package/dist/http/caller-trust.d.ts +2 -1
- package/dist/http/caller-trust.js +1 -3
- package/dist/http/checkpoint-ctx.d.ts +48 -7
- package/dist/http/checkpoint-ctx.js +40 -0
- package/dist/http/dispatch.d.ts +4 -0
- package/dist/http/dispatch.js +18 -10
- package/dist/http/durable-run-leg.d.ts +5 -1
- package/dist/http/durable-run-leg.js +5 -3
- package/dist/http/health-self-report.d.ts +2 -2
- package/dist/http/memory-compliance-failure-reply.js +3 -0
- package/dist/http/principal-gate.d.ts +15 -10
- package/dist/http/principal-gate.js +48 -9
- package/dist/http/resume-legs.d.ts +7 -31
- package/dist/http/resume-legs.js +185 -126
- package/dist/http/route-ctx.d.ts +38 -53
- package/dist/http/route-ctx.js +2 -5
- package/dist/http/routes/a2a-serve.js +10 -4
- package/dist/http/routes/agents-roster.d.ts +1 -1
- package/dist/http/routes/agents-roster.js +5 -1
- package/dist/http/routes/approvals-assistant.js +24 -10
- package/dist/http/routes/capabilities.js +23 -11
- package/dist/http/routes/mcp-reconnect.d.ts +3 -2
- package/dist/http/routes/mcp-reconnect.js +7 -7
- package/dist/http/routes/memory-consolidation.d.ts +1 -1
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/rules.js +3 -3
- package/dist/http/routes/runs.js +13 -8
- package/dist/http/routes/session-sync.js +42 -6
- package/dist/http/routes/sessions.js +6 -3
- package/dist/http/routes/side-query.js +2 -3
- package/dist/http/routes/tasks.js +794 -780
- package/dist/http/server.d.ts +35 -25
- package/dist/http/stored-body-replay.d.ts +3 -3
- package/dist/http/stored-body-replay.js +6 -1
- package/dist/http/wire-types.d.ts +66 -33
- package/dist/identity-source.d.ts +65 -0
- package/dist/identity-source.js +45 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/leader/wire.d.ts +7 -0
- package/dist/leader/wire.js +5 -4
- package/dist/mcp-assertion-header.d.ts +51 -40
- package/dist/mcp-assertion-header.js +10 -11
- package/dist/mcp-authorization-face.d.ts +27 -0
- package/dist/mcp-authorization-face.js +15 -0
- package/dist/memory-operator-faces.d.ts +13 -4
- package/dist/memory-operator-faces.js +1 -17
- package/dist/memory-store-refusal.d.ts +3 -3
- package/dist/memory-sync-client.js +72 -57
- package/dist/memory-sync.d.ts +1 -1
- package/dist/model-entry-refusal.d.ts +2 -0
- package/dist/model-entry-refusal.js +11 -0
- package/dist/model-route-gap.d.ts +6 -22
- package/dist/model-route-gap.js +7 -5
- package/dist/model-select.d.ts +6 -17
- package/dist/model-select.js +0 -3
- package/dist/mods-declaration.d.ts +110 -0
- package/dist/mods-declaration.js +109 -0
- package/dist/namespaced-rule-name.d.ts +1 -1
- package/dist/observability/err-text.d.ts +5 -0
- package/dist/observability/err-text.js +9 -0
- package/dist/observability/fail-open.d.ts +17 -1
- package/dist/observability/fail-open.js +17 -1
- package/dist/observability/metrics.js +1 -1
- package/dist/observability/run-terminal.js +1 -0
- package/dist/observability/secret-env-scrub.d.ts +1 -1
- package/dist/observability/secret-env-scrub.js +1 -1
- package/dist/operator-ask.d.ts +2 -2
- package/dist/org-delivery-gate.js +1 -1
- package/dist/org-memory-admission.d.ts +2 -2
- package/dist/org-memory-admission.js +39 -2
- package/dist/parked-decide.d.ts +15 -2
- package/dist/parked-decide.js +17 -10
- package/dist/peer-directory.js +2 -0
- package/dist/peer-seat.d.ts +150 -0
- package/dist/peer-seat.js +239 -0
- package/dist/permission-rule-list.d.ts +2 -2
- package/dist/permission-rule-vocab.d.ts +7 -7
- package/dist/permission-rule-vocab.js +3 -3
- package/dist/plain-string-record.d.ts +38 -0
- package/dist/plain-string-record.js +38 -0
- package/dist/plugins/background-agent-store-sql.js +6 -3
- package/dist/plugins/caching-session-store.d.ts +5 -0
- package/dist/plugins/caching-session-store.js +6 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +34 -29
- package/dist/plugins/checkpoint-store-sql.js +14 -14
- package/dist/plugins/file-history-store-sql.js +10 -16
- package/dist/plugins/file-run-store.js +8 -2
- package/dist/plugins/k8s-exec-protocol.d.ts +69 -13
- package/dist/plugins/k8s-exec-protocol.js +116 -28
- package/dist/plugins/k8s-pod-delete-failure.d.ts +25 -0
- package/dist/plugins/k8s-pod-delete-failure.js +17 -0
- package/dist/plugins/k8s-pod-reaper.d.ts +106 -0
- package/dist/plugins/k8s-pod-reaper.js +122 -0
- package/dist/plugins/local-checkpoint-store.d.ts +4 -17
- package/dist/plugins/local-checkpoint-store.js +3 -4
- package/dist/plugins/local-session-store.d.ts +66 -27
- package/dist/plugins/local-session-store.js +60 -28
- package/dist/plugins/mailbox-store-sql.d.ts +11 -3
- package/dist/plugins/mailbox-store-sql.js +103 -15
- package/dist/plugins/memory-run-store.js +2 -0
- package/dist/plugins/memory-sync-store-pg.d.ts +16 -5
- package/dist/plugins/memory-sync-store-tidb.d.ts +3 -3
- package/dist/plugins/one-time-migrations.d.ts +34 -12
- package/dist/plugins/one-time-migrations.js +51 -9
- package/dist/plugins/permission-rule-store-file.js +2 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +17 -17
- package/dist/plugins/permission-rule-store-sql.js +37 -30
- package/dist/plugins/pg-pool.js +4 -0
- package/dist/plugins/pg-session-storage.d.ts +10 -2
- package/dist/plugins/pg-session-storage.js +20 -7
- package/dist/plugins/remote-env-adb.d.ts +6 -1
- package/dist/plugins/remote-env-adb.js +2 -1
- package/dist/plugins/remote-env-device.d.ts +6 -1
- package/dist/plugins/remote-env-device.js +5 -1
- package/dist/plugins/remote-env-e2b.d.ts +5 -0
- package/dist/plugins/remote-env-e2b.js +2 -1
- package/dist/plugins/remote-env-file-error.d.ts +8 -1
- package/dist/plugins/remote-env-file-error.js +7 -1
- package/dist/plugins/remote-env-host.d.ts +10 -2
- package/dist/plugins/remote-env-host.js +8 -2
- package/dist/plugins/remote-env-k8s.d.ts +140 -11
- package/dist/plugins/remote-env-k8s.js +277 -15
- package/dist/plugins/remote-env-local-docker.d.ts +5 -0
- package/dist/plugins/remote-env-local-docker.js +2 -1
- package/dist/plugins/remote-env-ssh.d.ts +6 -1
- package/dist/plugins/remote-env-ssh.js +2 -1
- package/dist/plugins/retention-store-sql.d.ts +14 -2
- package/dist/plugins/retention-store-sql.js +6 -0
- package/dist/plugins/run-store-sql.js +10 -4
- package/dist/plugins/session-fork.d.ts +10 -13
- package/dist/plugins/session-fork.js +5 -25
- package/dist/plugins/session-ownership.d.ts +24 -3
- package/dist/plugins/session-ownership.js +24 -5
- package/dist/plugins/session-workspace-cell.d.ts +17 -0
- package/dist/plugins/session-workspace-cell.js +38 -0
- package/dist/plugins/sql-errors.d.ts +23 -2
- package/dist/plugins/sql-errors.js +15 -0
- package/dist/plugins/store-backend.js +7 -7
- package/dist/plugins/store-contracts.d.ts +10 -1
- package/dist/plugins/store-fault.d.ts +2 -2
- package/dist/plugins/store-fault.js +4 -2
- package/dist/plugins/tidb-pool.js +13 -0
- package/dist/plugins/tidb-session-store.d.ts +17 -2
- package/dist/plugins/tidb-session-store.js +18 -7
- package/dist/plugins/tool-result-store-sql.d.ts +13 -6
- package/dist/plugins/tool-result-store-sql.js +8 -7
- package/dist/plugins/worktree-isolation.d.ts +3 -2
- package/dist/plugins/worktree-isolation.js +1 -7
- package/dist/project-identity.d.ts +1 -14
- package/dist/project-identity.js +0 -4
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +31 -2
- package/dist/request-value-closure.js +7 -1
- package/dist/rule-entry-list.d.ts +3 -2
- package/dist/rule-entry-list.js +2 -2
- package/dist/rules-consent.js +12 -7
- package/dist/run-local.d.ts +2 -2
- package/dist/run-local.js +12 -6
- package/dist/runs.d.ts +7 -1
- package/dist/runs.js +8 -2
- package/dist/runtime-governance.d.ts +5 -5
- package/dist/sandbox-git-lease.d.ts +49 -0
- package/dist/sandbox-git-lease.js +60 -0
- package/dist/sandbox-git-plane.d.ts +84 -0
- package/dist/sandbox-git-plane.js +105 -0
- package/dist/security.d.ts +16 -4
- package/dist/security.js +8 -3
- package/dist/server-secret-env.d.ts +12 -3
- package/dist/server-secret-env.js +18 -2
- package/dist/session-error-wire.d.ts +14 -6
- package/dist/session-error-wire.js +2 -1
- package/dist/session-incarnation.d.ts +4 -5
- package/dist/session-incarnation.js +2 -2
- package/dist/session-policy-wire.d.ts +1 -1
- package/dist/session-policy-wire.js +2 -2
- package/dist/session-sync.d.ts +1 -1
- package/dist/session-workspace.d.ts +130 -0
- package/dist/session-workspace.js +83 -0
- package/dist/spec-fields.d.ts +44 -14
- package/dist/spec-fields.js +33 -12
- package/dist/standing-answer-grant.d.ts +2 -13
- package/dist/standing-answer-grant.js +0 -7
- package/dist/stored-request-prompt-profile-m30.d.ts +49 -0
- package/dist/stored-request-prompt-profile-m30.js +25 -0
- package/dist/task-a2a.d.ts +44 -14
- package/dist/task-a2a.js +46 -20
- package/dist/task-mcp.d.ts +34 -21
- package/dist/task-mcp.js +46 -22
- package/dist/task-settings.d.ts +2 -2
- package/dist/tool-approval.d.ts +43 -9
- package/dist/tool-approval.js +57 -15
- package/dist/tool-rule-names.d.ts +5 -0
- package/dist/trace/core-keyset-guard.d.ts +34 -19
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +4 -0
- package/dist/trace/exit-seat.d.ts +20 -1
- package/dist/trace/exit-seat.js +5 -2
- package/dist/trace/extension-call-face.d.ts +33 -0
- package/dist/trace/extension-call-face.js +39 -0
- package/dist/trace/ledger-sink.d.ts +4 -5
- package/dist/trace/ledger-sink.js +3 -7
- package/dist/trace/project.d.ts +62 -10
- package/dist/trace/project.js +49 -9
- package/dist/trace/redact.d.ts +18 -6
- package/dist/trace/redact.js +154 -60
- package/dist/trace/self-minted-wire.d.ts +500 -7
- package/dist/trace/self-minted-wire.js +182 -1
- package/dist/trace/wire-projection-faces.d.ts +7 -5
- package/dist/trace/wire-projection-faces.js +9 -4
- package/dist/user-authored-text.d.ts +2 -2
- package/dist/user-authored-text.js +16 -1
- package/dist/web-admin-client/apply-effective.js +0 -1
- package/dist/web-admin-client/center-request.d.ts +23 -4
- package/dist/web-admin-client/center-request.js +42 -8
- package/dist/web-admin-client/http-client.js +6 -6
- package/dist/web-admin-client/skills-mcp.d.ts +8 -4
- package/dist/web-admin-client/skills-mcp.js +31 -18
- package/dist/web-admin-client/types.d.ts +10 -5
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +11 -5
- package/package.json +3 -3
- package/wire-changes/server@7.137.0.json +825 -0
- 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
|
-
|
|
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
|
|
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": "
|
|
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
|
|
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:
|
|
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
|
-
...(
|
|
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
|
-
...(
|
|
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
|
-
|
|
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?.
|
|
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
|
-
* 布尔字面量零影响(
|
|
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)
|
|
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
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
"VALUES (
|
|
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,
|