@sema-agent/server 7.135.0 → 7.137.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 +237 -2
- package/USAGE.md +146 -41
- package/deploy/sema-up/README.md +15 -1
- package/deploy/sema-up/chart/templates/NOTES.txt +1 -1
- package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +7 -1
- package/deploy/sema-up/chart/values.yaml +1 -1
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +7 -1
- package/dist/approval-card.d.ts +60 -4
- package/dist/approval-card.js +18 -1
- package/dist/approval-content-kind.d.ts +7 -0
- package/dist/approval-content-kind.js +8 -1
- 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/auth-bridge.d.ts +34 -5
- package/dist/auth-bridge.js +22 -5
- package/dist/boot/budget-tracing.d.ts +1 -2
- package/dist/boot/budget-tracing.js +2 -2
- package/dist/boot/execution-env.d.ts +24 -1
- package/dist/boot/execution-env.js +69 -6
- package/dist/boot/org-memory.js +1 -1
- package/dist/boot/reapers.d.ts +3 -0
- package/dist/boot/reapers.js +15 -1
- package/dist/boot/resolve-spec.d.ts +4 -0
- package/dist/boot/resolve-spec.js +24 -22
- package/dist/boot/runner-deps.js +4 -2
- package/dist/boot/stage-03-store-backend.d.ts +1 -1
- package/dist/boot/stage-04-stores.d.ts +2 -2
- package/dist/boot/stage-05-execution-env.d.ts +4 -2
- package/dist/boot/stage-05-execution-env.js +11 -2
- package/dist/boot/stage-06-runners.d.ts +4 -2
- package/dist/boot/stage-06-runners.js +5 -1
- package/dist/boot/stage-07-capability-layer.d.ts +5 -2
- package/dist/boot/stage-07-capability-layer.js +1 -1
- package/dist/boot/stage-08-reapers.d.ts +5 -2
- package/dist/boot/stage-08-reapers.js +2 -1
- package/dist/boot/stage-09-leader.d.ts +5 -2
- package/dist/boot/stage-10-http-server.d.ts +5 -2
- package/dist/boot/stage-10-http-server.js +3 -2
- package/dist/boot/stores.d.ts +1 -1
- package/dist/boot/stores.js +2 -3
- package/dist/boot/web-admin.js +10 -17
- package/dist/brain.js +12 -3
- 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 +3 -3
- package/dist/budget.js +4 -0
- 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 +5 -4
- 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 +3 -7
- package/dist/capability-facts.js +3 -5
- package/dist/center-credential-renewer.js +5 -1
- package/dist/center-credential.d.ts +18 -4
- package/dist/center-credential.js +18 -3
- 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 +3 -3
- package/dist/config-catalog.js +21 -16
- package/dist/config-d-migration.js +1 -1
- package/dist/config-invariants.d.ts +1 -1
- package/dist/config-invariants.js +8 -0
- package/dist/config-lkg.d.ts +0 -38
- package/dist/config-lkg.js +1 -83
- package/dist/config-types.d.ts +21 -4
- package/dist/config.d.ts +1 -1
- package/dist/config.js +60 -16
- package/dist/deployment-governance.d.ts +2 -2
- package/dist/deployment-governance.js +7 -9
- package/dist/deployment-shape.d.ts +1 -1
- package/dist/device-ws-protocol.d.ts +1 -0
- package/dist/device-ws-protocol.js +2 -1
- package/dist/elicitation.d.ts +8 -7
- package/dist/elicitation.js +9 -8
- package/dist/file-history-disclosure.d.ts +8 -4
- package/dist/file-history-disclosure.js +24 -6
- package/dist/fleet-client.d.ts +32 -1
- package/dist/fleet-client.js +24 -4
- package/dist/fleet-lease.d.ts +7 -3
- package/dist/fleet-lease.js +31 -23
- package/dist/hooks/classic-stations.d.ts +11 -54
- package/dist/hooks/hook-llm.js +14 -8
- package/dist/hooks/hook-runner.d.ts +4 -4
- package/dist/hooks/hook-runner.js +2 -2
- package/dist/http/admission.js +10 -7
- 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 +76 -16
- package/dist/http/checkpoint-ctx.js +45 -1
- package/dist/http/dispatch.js +24 -19
- package/dist/http/durable-run-leg.d.ts +5 -1
- package/dist/http/durable-run-leg.js +1 -1
- package/dist/http/health-self-report.d.ts +2 -2
- package/dist/http/mcp-remote-text.d.ts +5 -0
- package/dist/http/mcp-remote-text.js +7 -0
- package/dist/http/principal-gate.d.ts +15 -10
- package/dist/http/principal-gate.js +48 -9
- package/dist/http/resume-ctx-read.d.ts +2 -16
- package/dist/http/resume-ctx-read.js +2 -3
- package/dist/http/resume-legs.d.ts +2 -20
- package/dist/http/resume-legs.js +52 -38
- package/dist/http/route-ctx.d.ts +8 -2
- package/dist/http/routes/approvals-assistant.d.ts +0 -2
- package/dist/http/routes/approvals-assistant.js +65 -16
- package/dist/http/routes/capabilities.js +11 -1
- package/dist/http/routes/mcp-reconnect.d.ts +1 -1
- package/dist/http/routes/mcp-reconnect.js +5 -7
- package/dist/http/routes/runs.js +4 -3
- package/dist/http/routes/session-sync.js +70 -12
- package/dist/http/routes/sessions.js +20 -15
- package/dist/http/routes/tasks.js +8 -6
- package/dist/http/server.d.ts +4 -0
- package/dist/http/stored-body-replay.d.ts +3 -3
- package/dist/http/stored-body-replay.js +6 -1
- package/dist/http/submit-ctx.js +1 -1
- package/dist/http/wire-types.d.ts +57 -30
- package/dist/identity-source.d.ts +65 -0
- package/dist/identity-source.js +45 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/memory-store-refusal.d.ts +3 -3
- package/dist/model-entry-refusal.d.ts +2 -0
- package/dist/model-entry-refusal.js +11 -0
- package/dist/model-route-gap.d.ts +49 -4
- package/dist/model-route-gap.js +34 -1
- package/dist/model-select.d.ts +6 -17
- package/dist/model-select.js +0 -3
- package/dist/namespaced-rule-name.d.ts +1 -1
- package/dist/object-store-buckets.d.ts +13 -0
- package/dist/object-store-buckets.js +3 -0
- package/dist/observability/fail-open.d.ts +19 -7
- package/dist/observability/fail-open.js +19 -7
- package/dist/observability/metrics.js +2 -2
- package/dist/observability/principal-context.d.ts +4 -1
- package/dist/observability/principal-context.js +6 -1
- package/dist/observability/run-terminal.js +1 -0
- 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 +4 -3
- package/dist/parked-decide.js +29 -9
- package/dist/peer-directory.d.ts +2 -1
- package/dist/peer-directory.js +7 -5
- package/dist/permission-rule-list.d.ts +2 -2
- package/dist/plugins/approval-ask-store-sql.d.ts +8 -0
- package/dist/plugins/approval-ask-store-sql.js +3 -0
- package/dist/plugins/blob-backend.js +2 -1
- package/dist/plugins/checkpoint-index-columns.d.ts +50 -1
- package/dist/plugins/checkpoint-index-columns.js +28 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +46 -12
- package/dist/plugins/checkpoint-store-sql.js +64 -35
- package/dist/plugins/file-history-store-sql.d.ts +6 -0
- package/dist/plugins/file-history-store-sql.js +37 -37
- 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 +112 -0
- package/dist/plugins/k8s-pod-reaper.js +122 -0
- package/dist/plugins/local-checkpoint-store.d.ts +3 -6
- package/dist/plugins/local-checkpoint-store.js +7 -6
- package/dist/plugins/memory-run-store.js +2 -0
- package/dist/plugins/one-time-migrations.d.ts +49 -37
- package/dist/plugins/one-time-migrations.js +87 -128
- 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 +16 -17
- package/dist/plugins/pg-pool.js +4 -0
- package/dist/plugins/pg-session-storage.js +2 -2
- package/dist/plugins/remote-env-adb.d.ts +3 -0
- package/dist/plugins/remote-env-adb.js +1 -0
- package/dist/plugins/remote-env-device.d.ts +3 -0
- package/dist/plugins/remote-env-device.js +4 -0
- package/dist/plugins/remote-env-e2b.d.ts +3 -0
- package/dist/plugins/remote-env-e2b.js +1 -0
- 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 +7 -1
- package/dist/plugins/remote-env-host.js +7 -1
- package/dist/plugins/remote-env-k8s.d.ts +154 -14
- package/dist/plugins/remote-env-k8s.js +300 -21
- package/dist/plugins/remote-env-local-docker.d.ts +3 -0
- package/dist/plugins/remote-env-local-docker.js +1 -0
- package/dist/plugins/remote-env-ssh.d.ts +3 -0
- package/dist/plugins/remote-env-ssh.js +1 -0
- package/dist/plugins/run-store-sql.js +10 -4
- package/dist/plugins/s3-answer.d.ts +15 -0
- package/dist/plugins/s3-answer.js +5 -0
- package/dist/plugins/session-ownership.d.ts +17 -3
- package/dist/plugins/session-ownership.js +19 -5
- package/dist/plugins/snapshot-bucket-presence.d.ts +39 -4
- package/dist/plugins/snapshot-bucket-presence.js +35 -7
- package/dist/plugins/sql-errors.d.ts +60 -0
- package/dist/plugins/sql-errors.js +64 -1
- package/dist/plugins/store-contracts.d.ts +10 -1
- package/dist/plugins/store-fault.d.ts +4 -0
- package/dist/plugins/store-fault.js +10 -0
- package/dist/plugins/tidb-pool.js +8 -1
- package/dist/plugins/tidb-session-store.js +2 -2
- package/dist/plugins/web-search.d.ts +1 -1
- package/dist/plugins/web-search.js +2 -2
- package/dist/request-declaration-refusal.d.ts +94 -0
- package/dist/request-declaration-refusal.js +78 -0
- package/dist/request-image-urls.d.ts +6 -0
- package/dist/request-image-urls.js +27 -0
- package/dist/request-key-closure.d.ts +5 -0
- package/dist/request-key-closure.js +5 -2
- 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 +7 -4
- package/dist/run-local.d.ts +2 -2
- package/dist/runs.js +2 -1
- package/dist/runtime-caps-resolver.js +1 -1
- 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/sandbox-pkg-source.d.ts +28 -1
- package/dist/sandbox-pkg-source.js +1 -2
- package/dist/sealed-key.d.ts +19 -0
- package/dist/sealed-key.js +47 -4
- package/dist/security.d.ts +16 -4
- package/dist/security.js +8 -3
- package/dist/server-secret-env.d.ts +6 -0
- package/dist/server-secret-env.js +14 -0
- package/dist/session-error-wire.d.ts +14 -6
- package/dist/session-error-wire.js +2 -1
- package/dist/session-incarnation.d.ts +16 -5
- package/dist/session-incarnation.js +7 -4
- package/dist/session-policy-wire.d.ts +1 -1
- package/dist/session-policy-wire.js +2 -2
- package/dist/session-row.d.ts +3 -17
- package/dist/session-row.js +4 -0
- package/dist/session-sync.d.ts +15 -3
- package/dist/session-sync.js +5 -2
- package/dist/spec-fields.d.ts +43 -13
- package/dist/spec-fields.js +33 -12
- 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 +52 -81
- package/dist/task-a2a.js +219 -89
- package/dist/task-mcp.d.ts +7 -15
- package/dist/task-mcp.js +16 -51
- package/dist/task-settings.d.ts +2 -2
- package/dist/tool-approval.d.ts +35 -12
- package/dist/tool-approval.js +59 -39
- package/dist/tool-rule-names.d.ts +5 -0
- package/dist/trace/core-keyset-guard.d.ts +5 -5
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/exit-seat.js +37 -13
- package/dist/trace/ledger-sink.d.ts +4 -5
- package/dist/trace/ledger-sink.js +3 -7
- package/dist/trace/project.d.ts +37 -3
- package/dist/trace/project.js +20 -6
- package/dist/trace/redact.d.ts +40 -12
- package/dist/trace/redact.js +332 -128
- package/dist/trace/self-minted-wire.d.ts +641 -0
- package/dist/trace/self-minted-wire.js +201 -0
- package/dist/trace/wire-projection-faces.d.ts +5 -3
- package/dist/trace/wire-projection-faces.js +7 -3
- package/dist/url-credential-refusal.d.ts +52 -0
- package/dist/url-credential-refusal.js +14 -0
- package/dist/url-refusal.d.ts +13 -4
- package/dist/url-refusal.js +8 -2
- package/dist/web-admin-client/apply-effective.js +0 -1
- package/dist/web-admin-client/center-request.d.ts +20 -2
- package/dist/web-admin-client/center-request.js +40 -5
- package/dist/web-admin-client/http-client.js +6 -6
- package/dist/web-admin-client/types.d.ts +5 -5
- package/dist/workspace-space.d.ts +27 -0
- package/dist/workspace-space.js +26 -0
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +11 -5
- package/package.json +4 -3
- package/scripts/drain-check-local.mjs +2 -1
- package/scripts/drain-check.mysql.sql +3 -1
- package/scripts/drain-check.pg.sql +3 -1
- package/skills/schedule.md +1 -1
- package/wire-changes/server@7.104.0.json +134 -0
- package/wire-changes/server@7.105.0.json +53 -0
- package/wire-changes/server@7.106.0.json +475 -0
- package/wire-changes/server@7.107.0.json +64 -0
- package/wire-changes/server@7.108.0.json +153 -0
- package/wire-changes/server@7.109.0.json +46 -0
- package/wire-changes/server@7.110.0.json +90 -0
- package/wire-changes/server@7.111.0.json +86 -0
- package/wire-changes/server@7.112.0.json +60 -0
- package/wire-changes/server@7.113.0.json +26 -0
- package/wire-changes/server@7.114.0.json +26 -0
- package/wire-changes/server@7.115.0.json +97 -0
- package/wire-changes/server@7.116.0.json +218 -0
- package/wire-changes/server@7.116.1.json +26 -0
- package/wire-changes/server@7.117.0.json +65 -0
- package/wire-changes/server@7.118.0.json +129 -0
- package/wire-changes/server@7.119.0.json +65 -0
- package/wire-changes/server@7.120.0.json +89 -0
- package/wire-changes/server@7.121.0.json +219 -0
- package/wire-changes/server@7.122.0.json +54 -0
- package/wire-changes/server@7.123.0.json +26 -0
- package/wire-changes/server@7.124.0.json +336 -0
- package/wire-changes/server@7.124.1.json +26 -0
- package/wire-changes/server@7.125.0.json +111 -0
- package/wire-changes/server@7.126.0.json +285 -0
- package/wire-changes/server@7.127.0.json +77 -0
- package/wire-changes/server@7.127.1.json +26 -0
- package/wire-changes/server@7.127.2.json +53 -0
- package/wire-changes/server@7.128.0.json +26 -0
- package/wire-changes/server@7.129.0.json +106 -0
- package/wire-changes/server@7.129.1.json +26 -0
- package/wire-changes/server@7.130.0.json +26 -0
- package/wire-changes/server@7.131.0.json +118 -0
- package/wire-changes/server@7.132.0.json +226 -0
- package/wire-changes/server@7.133.0.json +1155 -0
- package/wire-changes/server@7.134.0.json +107 -0
- package/wire-changes/server@7.135.0.json +174 -0
- package/wire-changes/server@7.136.0.json +448 -0
- package/wire-changes/server@7.137.0.json +825 -0
- package/dist/url-credential.d.ts +0 -52
- package/dist/url-credential.js +0 -49
|
@@ -1,35 +1,125 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { BROKER_VOLUME_NAME } from "../broker/constants.js";
|
|
2
|
+
export const SANDBOX_POD_LABELS = { app: "sema-sandbox", "managed-by": "sema-server" };
|
|
3
|
+
export const SANDBOX_RUN_LABEL = "sema.io/run";
|
|
4
|
+
export const SANDBOX_RUN_END_HOLD_ANNOTATION = "sema.io/run-end-hold-ms";
|
|
5
|
+
export const SANDBOX_SECRET_SEAT_VERBS = { env: ["create"], lease: ["create", "patch"] };
|
|
6
|
+
const IDENTITY_LABEL_KEYS = new Set([...Object.keys(SANDBOX_POD_LABELS), SANDBOX_RUN_LABEL]);
|
|
7
|
+
const LABEL_VALUE = /^(?:[A-Za-z0-9](?:[-A-Za-z0-9_.]{0,61}[A-Za-z0-9])?)?$/;
|
|
8
|
+
export function isLabelValue(v) {
|
|
9
|
+
return LABEL_VALUE.test(v);
|
|
10
|
+
}
|
|
11
|
+
export const ADAPTER_VOLUME_NAMES = { cache: "ws-cache", brokerLease: BROKER_VOLUME_NAME };
|
|
12
|
+
const RESERVED_VOLUME_NAMES = new Set(Object.values(ADAPTER_VOLUME_NAMES));
|
|
13
|
+
const agentContainerOf = (pod) => pod.spec.containers?.[0];
|
|
14
|
+
const POD_SPEC_PATCH_FIELDS = {
|
|
15
|
+
volumes: {
|
|
16
|
+
judge: (volumes) => {
|
|
17
|
+
assertVolumesPodLocal(volumes, "volumes");
|
|
18
|
+
for (const v of volumes)
|
|
19
|
+
assertPatchVolumeName(v?.name, "volumes");
|
|
20
|
+
},
|
|
21
|
+
apply: (pod, volumes) => {
|
|
22
|
+
if (volumes.length > 0)
|
|
23
|
+
pod.spec.volumes = [...(pod.spec.volumes ?? []), ...volumes];
|
|
24
|
+
},
|
|
25
|
+
withBroker: "allowed",
|
|
26
|
+
},
|
|
27
|
+
volumeMounts: {
|
|
28
|
+
judge: (mounts) => {
|
|
29
|
+
for (const m of mounts)
|
|
30
|
+
assertPatchVolumeName(m?.name, "volumeMounts");
|
|
31
|
+
},
|
|
32
|
+
apply: (pod, mounts) => {
|
|
33
|
+
const container = agentContainerOf(pod);
|
|
34
|
+
if (container && mounts.length > 0)
|
|
35
|
+
container.volumeMounts = [...(container.volumeMounts ?? []), ...mounts];
|
|
36
|
+
},
|
|
37
|
+
withBroker: "allowed",
|
|
38
|
+
},
|
|
39
|
+
podSecurityContext: {
|
|
40
|
+
judge: (sc) => {
|
|
41
|
+
assertHardeningOnly(sc, "podSecurityContext");
|
|
42
|
+
if ("sysctls" in sc) {
|
|
43
|
+
throw new Error("podSpecPatch.podSecurityContext rejected — `sysctls` is not a patch key (the pod's sysctls are minted only by the lane's IPv6 posture, K8S_SANDBOX_IPV6)");
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
apply: (pod, sc) => {
|
|
47
|
+
pod.spec.securityContext = { ...(pod.spec.securityContext ?? {}), ...sc };
|
|
48
|
+
},
|
|
49
|
+
withBroker: {
|
|
50
|
+
refused: "podSpecPatch.podSecurityContext is not allowed on a pod that carries the member-credential broker — every pod-level key would reach the broker container too; put the hardening into containerSecurityContext (it lands on the agent container only)",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
containerSecurityContext: {
|
|
54
|
+
judge: (sc) => assertHardeningOnly(sc, "containerSecurityContext"),
|
|
55
|
+
apply: (pod, sc) => {
|
|
56
|
+
const container = agentContainerOf(pod);
|
|
57
|
+
if (container)
|
|
58
|
+
container.securityContext = { ...(container.securityContext ?? {}), ...sc };
|
|
59
|
+
},
|
|
60
|
+
withBroker: "allowed",
|
|
61
|
+
},
|
|
62
|
+
labels: {
|
|
63
|
+
judge: () => { },
|
|
64
|
+
apply: (pod, labels) => {
|
|
65
|
+
const merged = { ...pod.metadata.labels };
|
|
66
|
+
for (const [k, v] of Object.entries(labels))
|
|
67
|
+
if (!IDENTITY_LABEL_KEYS.has(k))
|
|
68
|
+
merged[k] = v;
|
|
69
|
+
pod.metadata.labels = merged;
|
|
70
|
+
},
|
|
71
|
+
withBroker: "allowed",
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
const PATCH_FIELD_ORDER = Object.keys(POD_SPEC_PATCH_FIELDS);
|
|
75
|
+
export function applyPodSpecPatch(pod, patch, presence = { broker: false }) {
|
|
76
|
+
const present = judgePodSpecPatch(patch, presence);
|
|
77
|
+
for (const k of present)
|
|
78
|
+
applyField(k, pod, patch);
|
|
79
|
+
}
|
|
80
|
+
export function podSpecPatchBrokerRefusal(patch) {
|
|
81
|
+
if (patch === undefined)
|
|
82
|
+
return undefined;
|
|
83
|
+
try {
|
|
84
|
+
judgePodSpecPatch(patch, { broker: true });
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
return e instanceof Error ? e.message : String(e);
|
|
5
89
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (v !== undefined)
|
|
12
|
-
identity[k] = v;
|
|
90
|
+
}
|
|
91
|
+
function judgePodSpecPatch(patch, presence) {
|
|
92
|
+
for (const key of Object.keys(patch)) {
|
|
93
|
+
if (!Object.hasOwn(POD_SPEC_PATCH_FIELDS, key)) {
|
|
94
|
+
throw new Error(`podSpecPatch rejected — ${JSON.stringify(key)} is not a patch field (the patch fields are ${PATCH_FIELD_ORDER.join(" / ")}; any other key is refused, never ignored)`);
|
|
13
95
|
}
|
|
14
|
-
pod.metadata.labels = { ...pod.metadata.labels, ...patch.labels, ...identity };
|
|
15
96
|
}
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
97
|
+
const present = PATCH_FIELD_ORDER.filter((k) => patch[k] !== undefined);
|
|
98
|
+
if (presence.broker) {
|
|
99
|
+
for (const k of present) {
|
|
100
|
+
const column = POD_SPEC_PATCH_FIELDS[k].withBroker;
|
|
101
|
+
if (column !== "allowed")
|
|
102
|
+
throw new Error(column.refused);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
for (const k of present)
|
|
106
|
+
judgeField(k, patch);
|
|
107
|
+
return present;
|
|
108
|
+
}
|
|
109
|
+
function judgeField(key, patch) {
|
|
110
|
+
POD_SPEC_PATCH_FIELDS[key].judge(patch[key]);
|
|
111
|
+
}
|
|
112
|
+
function applyField(key, pod, patch) {
|
|
113
|
+
POD_SPEC_PATCH_FIELDS[key].apply(pod, patch[key]);
|
|
114
|
+
}
|
|
115
|
+
function assertPatchVolumeName(name, where) {
|
|
116
|
+
if (typeof name !== "string" || name === "")
|
|
117
|
+
throw new Error(`podSpecPatch.${where} rejected — every entry needs a non-empty string \`name\``);
|
|
118
|
+
if (RESERVED_VOLUME_NAMES.has(name)) {
|
|
119
|
+
throw new Error(`podSpecPatch.${where} rejected — volume name ${JSON.stringify(name)} is reserved (the adapter mints it on the pod itself: ${[...RESERVED_VOLUME_NAMES].join(" / ")}); name the patch volume something else`);
|
|
28
120
|
}
|
|
29
121
|
}
|
|
30
122
|
function assertHardeningOnly(sc, where) {
|
|
31
|
-
if (!sc)
|
|
32
|
-
return;
|
|
33
123
|
const bad = (msg) => {
|
|
34
124
|
throw new Error(`podSpecPatch.${where} rejected — it may only HARDEN, never weaken isolation: ${msg}`);
|
|
35
125
|
};
|
|
@@ -53,8 +143,6 @@ function assertHardeningOnly(sc, where) {
|
|
|
53
143
|
bad('seccompProfile.type:"Unconfined" (disables syscall filtering)');
|
|
54
144
|
}
|
|
55
145
|
function assertVolumesPodLocal(volumes, where) {
|
|
56
|
-
if (!volumes?.length)
|
|
57
|
-
return;
|
|
58
146
|
const POD_LOCAL = new Set(["emptyDir", "configMap", "secret", "projected", "downwardAPI", "ephemeral"]);
|
|
59
147
|
for (const v of volumes) {
|
|
60
148
|
const vol = v;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** A pod DELETE the API answered with something other than "deleted" (2xx) or "not there" (404): a Role without `delete`, a 5xx — the pod is still in the namespace. */
|
|
2
|
+
export declare class PodDeleteRefused extends Error {
|
|
3
|
+
readonly status: number;
|
|
4
|
+
constructor(status: number);
|
|
5
|
+
}
|
|
6
|
+
/** 删失败的原因码(闭集两形):API 回了 2xx / 404 以外的答复 ⇒ `http_<状态>`;请求本身抛(传输失败 / 超时)⇒ `request_failed`。 */
|
|
7
|
+
export declare function podDeleteCauseOf(error: unknown): string;
|
|
8
|
+
/** 一次删失败的读数(座自己填 `why` 与 `effect`)。 */
|
|
9
|
+
export interface PodDeleteFailure {
|
|
10
|
+
readonly namespace: string;
|
|
11
|
+
readonly pod: string;
|
|
12
|
+
/** 这只 pod 为什么要删(每个座一个闭集词)。 */
|
|
13
|
+
readonly why: string;
|
|
14
|
+
/** 被抛出的那一只(只读它的类型取原因码,其文字不上任何一行)。 */
|
|
15
|
+
readonly error: unknown;
|
|
16
|
+
/** 留下什么(运维行的后果句,英文,不带值)。 */
|
|
17
|
+
readonly effect: string;
|
|
18
|
+
/** 运维行座:可缺 / 可没有 `error` 档(计数不依赖座)。 */
|
|
19
|
+
readonly logger?: {
|
|
20
|
+
error?(event: string, fields?: Record<string, unknown>): void;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** 上报一次删 pod 失败(计数 + 一行 error)。不抛。 */
|
|
24
|
+
export declare function reportPodDeleteFailed(f: PodDeleteFailure): void;
|
|
25
|
+
//# sourceMappingURL=k8s-pod-delete-failure.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { recordFailOpen } from "../observability/fail-open.js";
|
|
2
|
+
export class PodDeleteRefused extends Error {
|
|
3
|
+
status;
|
|
4
|
+
constructor(status) {
|
|
5
|
+
super(`pod delete answered HTTP ${status}`);
|
|
6
|
+
this.status = status;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function podDeleteCauseOf(error) {
|
|
10
|
+
return error instanceof PodDeleteRefused ? `http_${error.status}` : "request_failed";
|
|
11
|
+
}
|
|
12
|
+
export function reportPodDeleteFailed(f) {
|
|
13
|
+
const cause = podDeleteCauseOf(f.error);
|
|
14
|
+
recordFailOpen("server.sandbox-pod.delete-failed", `namespace=${f.namespace} pod=${f.pod} why=${f.why} cause=${cause}`);
|
|
15
|
+
f.logger?.error?.("sandbox_pod_delete_failed", { pod: f.pod, namespace: f.namespace, why: f.why, cause, effect: f.effect });
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=k8s-pod-delete-failure.js.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* k8s 沙箱 Pod 的 reaper —— server 建出来、却没被自己的 run 收尾删掉的 Pod 由它删(该 Pod 的 Secret 以 `ownerReferences` 属于它,随它由集群 GC)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么要它:`activeDeadlineSeconds` 只让容器到点终止、Pod 进 `Failed`,**不删 Pod 对象**;run 收尾的 `destroy()` 是删 Pod 的正常路,
|
|
5
|
+
* 而 server 进程崩溃 / 收尾里删失败(`destroy` 的删是吞错的)时 Pod 对象(连同它的 Secret)就一直留着。
|
|
6
|
+
*
|
|
7
|
+
* 法(只删两形;判决链的缺省臂是「不动作」,删必须被显式选中 —— 销毁不可逆,拿不准恒不删,与 E2B 孤儿回收同一方向纪律):
|
|
8
|
+
* ② `status.phase ∈ {Succeeded, Failed}` —— 容器已全部结束,删它不掐断任何东西;不看 run、不看标签;
|
|
9
|
+
* ① 带 run 标签(`sema.io/run`,只有顶层 run 的 Pod 带)、run 行在 server 店里**已是终态**,且自 run 行**转终态的时刻**(行上的 `updatedAt` ——
|
|
10
|
+
* 三只 run 店的终态写当拍盖它)起超过**这只 Pod 自带的**上界:`max(租约上限 + 宽限 = 6 min, 工作区快照时限 + 引擎拆环境放行 15 s)`
|
|
11
|
+
* ({@link sandboxRunEndHoldMs};租约上限 = 凭据代理的租约律那一只 `LEASE_TTL_MAX_MS`,本模块不另立)。
|
|
12
|
+
* 第二项是 run 收尾的 `destroy()` 在终态之后还可能在 Pod 上存快照的最长时间(引擎的拆环境腿 15 s 放行、run 随即落终态,快照按数据时限接着跑)——
|
|
13
|
+
* 那个数据时限是**建这只 Pod 的副本**的配置,所以上界**随 Pod 走、写一次读一次**:适配器建 Pod 时按自己的快照时限算一次、写进注解
|
|
14
|
+
* `sema.io/run-end-hold-ms`(`k8s-exec-protocol.ts` `SANDBOX_RUN_END_HOLD_ANNOTATION`),回收器只读 Pod 自带的这一格,**从不**读回收器所在副本的配置
|
|
15
|
+
* (列举是整个命名空间的 Pod:滚动改 `WORKSPACE_SNAPSHOT_TIMEOUT_MS` 时,一个时限短的副本不得替时限长的副本判它的 Pod)。
|
|
16
|
+
* 带 run 标签的 Pod 上注解缺席(不是本服务写的:标签与注解由建 Pod 的适配器在同一个条件下一起写)、不是十进制整数、不在 [租约上限 + 宽限, 快照时限上限 + 引擎拆环境放行] 里
|
|
17
|
+
* ⇒ 不按 ① 删(`hold_unreadable`;这只 Pod 由自己的 `activeDeadlineSeconds` 到点后命中 ② 收)。7.136.x 及以前建的 Pod 没有 run 标签(标签与注解都是本版起写)⇒ 落在下面
|
|
18
|
+
* 「不带 run 标签」一类(`no_run_label`),同样只按 ② 收。持久化开 / 关两种部署同一条律。
|
|
19
|
+
* **活 run 的 Pod 不由本模块判**:run 行非终态(活 / 停驻)、查不到、读抛、词表外 ⇒ 不删 —— 它由自己的截止(`activeDeadlineSeconds` 到点
|
|
20
|
+
* ⇒ 之后命中 ②)与取消 / 收尾路径收;本模块从不按 Pod 存在时长判活。
|
|
21
|
+
* 不带 run 标签的 Pod(委派子代 / 服务自己的 run / 没有 run id)只按 ② 删:会话级后台子代刻意活过父 run 的回合,「会话里没有非终态 run」
|
|
22
|
+
* 推不出「这只 Pod 没人用」。
|
|
23
|
+
*
|
|
24
|
+
* 没有 run 店(无存储后端的部署)⇒ ② 照删;带 run 标签的 Pod 跳过 `run_store_absent`(查不了 run ⇒ 不删)。
|
|
25
|
+
* 每轮一行结构化日志 `sandbox_pod_reap_round`(扫了几只、按闭集原因分的删 / 跳过计数),每删一只一行 `sandbox_pod_reaped`;**列举失败**这一轮一只都删不了 ——
|
|
26
|
+
* F 类兜底口 `server.sandbox-pod-reaper.list-failed` 计一次 + 一行 warn `sandbox_pod_reap_list_failed`(下一轮一个周期后照常再列);**某一只删失败**不挡其余 ——
|
|
27
|
+
* 与连接清理同一只上报口 `reportPodDeleteFailed`(F 类计数 `server.sandbox-pod.delete-failed` + 一行 error `sandbox_pod_delete_failed`,`why=reaper_<删它的理由>`,下一轮再判再删)。
|
|
28
|
+
* 每副本起服后第一个维护 tick 跑一次、此后每 {@link SANDBOX_POD_REAP_PERIOD_MS} 一次(`boot/reapers.ts`);多副本同时删同一只 ⇒ 404 计 `gone`。
|
|
29
|
+
*/
|
|
30
|
+
import type { Logger } from "../observability/logger.js";
|
|
31
|
+
import type { Metrics } from "../observability/metrics.js";
|
|
32
|
+
/** ① 的宽限(在租约上限之外再等的一段)。常量。 */
|
|
33
|
+
export declare const SANDBOX_POD_REAP_GRACE_MS = 60000;
|
|
34
|
+
/**
|
|
35
|
+
* 引擎在 run 收尾时给「拆执行环境」那条腿的放行时限:超过它 run 照样落终态,`destroy()` 脱离着继续跑。值 = core 的 `TEARDOWN_LEG_MAX_MS`
|
|
36
|
+
* (`dist/core/runner/teardown-bounded.js`)—— core 不从包根导出它(包的 exports 只有 `.` / `./bench` / `./fixtures`),本仓只能照抄这一个数;
|
|
37
|
+
* 收口 = 向 core 要一个包根读口后改读、删本常量。
|
|
38
|
+
*/
|
|
39
|
+
export declare const ENGINE_TEARDOWN_RELEASE_MS = 15000;
|
|
40
|
+
/** ① 的上界:`max(租约上限 + 宽限, 快照时限 + 引擎拆环境放行)`。唯一的调用方 = 适配器建 Pod 的那一拍(快照时限 = 建它的副本的 `dataTimeoutMs`),结果写进 Pod 的注解。 */
|
|
41
|
+
export declare function sandboxRunEndHoldMs(snapshotTimeoutMs: number): number;
|
|
42
|
+
/** 一个副本两轮回收之间的间隔。常量。 */
|
|
43
|
+
export declare const SANDBOX_POD_REAP_PERIOD_MS: number;
|
|
44
|
+
/** 列举出来的一只沙箱 Pod(只带判官要读的几样;时刻是毫秒)。 */
|
|
45
|
+
export interface SandboxPodListing {
|
|
46
|
+
readonly name: string;
|
|
47
|
+
/** 标签 `sema.io/run`:顶层 run 的 id;缺席 = 这只 Pod 不点名 run。 */
|
|
48
|
+
readonly runId?: string;
|
|
49
|
+
/** 注解 `sema.io/run-end-hold-ms` 的原串(建它的副本写的 ① 上界);缺席 = Pod 上没有这一格(或不是串)。判官自己读它({@link judgeSandboxPod})。 */
|
|
50
|
+
readonly runEndHold?: string;
|
|
51
|
+
readonly phase?: string;
|
|
52
|
+
/** `metadata.deletionTimestamp` 在场:已在删除中。 */
|
|
53
|
+
readonly terminating: boolean;
|
|
54
|
+
}
|
|
55
|
+
/** reaper 对沙箱命名空间的窄面(真实装配 = `remote-env-k8s.ts` 的 `createSandboxPodApi`;测试注入假面)。 */
|
|
56
|
+
export interface SandboxPodApi {
|
|
57
|
+
readonly namespace: string;
|
|
58
|
+
/** 按身份标签列本命名空间的沙箱 Pod。失败抛。 */
|
|
59
|
+
list(): Promise<SandboxPodListing[]>;
|
|
60
|
+
/** 删一只;404 ⇒ `gone`。其余失败抛。 */
|
|
61
|
+
delete(name: string): Promise<"deleted" | "gone">;
|
|
62
|
+
}
|
|
63
|
+
/** 一行 run 在判官眼里的两位。状态词按**串**读:滚动升级里新副本写下的词本进程可能不认(`runStatusClassOf` 答 `undefined` ⇒ 不删)。
|
|
64
|
+
* `updatedAt` = 行最后一次被写的时刻(ISO);终态行上它就是转终态那一拍(之后改它的写只会让 reaper 等得更久)。 */
|
|
65
|
+
export interface SandboxPodRunRow {
|
|
66
|
+
readonly status: string;
|
|
67
|
+
readonly updatedAt: string;
|
|
68
|
+
}
|
|
69
|
+
/** run 店的窄读口(三只 run 店共有的 `getRun`)。 */
|
|
70
|
+
export interface SandboxPodRunReader {
|
|
71
|
+
getRun(taskId: string): Promise<SandboxPodRunRow | null | undefined>;
|
|
72
|
+
}
|
|
73
|
+
/** 一只 Pod 的 run 读数(闭集):读到行 / 店答没有这一行 / 读抛 / 本部署没有 run 店可问。 */
|
|
74
|
+
export type SandboxPodRunReading = {
|
|
75
|
+
readonly found: SandboxPodRunRow;
|
|
76
|
+
} | {
|
|
77
|
+
readonly absent: true;
|
|
78
|
+
} | {
|
|
79
|
+
readonly failed: true;
|
|
80
|
+
} | {
|
|
81
|
+
readonly storeAbsent: true;
|
|
82
|
+
};
|
|
83
|
+
export type SandboxPodDeleteReason = "containers_ended" | "run_ended_past_hold";
|
|
84
|
+
export type SandboxPodSkipReason = "terminating" | "no_run_label" | "run_store_absent" | "run_unread" | "run_read_failed" | "run_status_unknown" | "run_live" | "run_end_unreadable" | "hold_unreadable" | "within_run_end_hold";
|
|
85
|
+
export type SandboxPodVerdict = {
|
|
86
|
+
readonly delete: SandboxPodDeleteReason;
|
|
87
|
+
} | {
|
|
88
|
+
readonly skip: SandboxPodSkipReason;
|
|
89
|
+
};
|
|
90
|
+
/** 一只 Pod 删不删(纯判官;法见文件头)。`now` 毫秒;① 的上界只读 Pod 自带的注解(`pod.runEndHold`),没有第二个来源。 */
|
|
91
|
+
export declare function judgeSandboxPod(pod: SandboxPodListing, run: SandboxPodRunReading, now: number): SandboxPodVerdict;
|
|
92
|
+
/** 一轮的读数(轮次行与计数的来源)。`deleted` / `skipped` 只列出现过的原因。 */
|
|
93
|
+
export interface SandboxPodReapRound {
|
|
94
|
+
readonly listed: boolean;
|
|
95
|
+
readonly scanned: number;
|
|
96
|
+
readonly deleted: Partial<Record<SandboxPodDeleteReason, number>>;
|
|
97
|
+
readonly skipped: Partial<Record<SandboxPodSkipReason, number>>;
|
|
98
|
+
/** 判了删、删时已不在(404)。 */
|
|
99
|
+
readonly gone: number;
|
|
100
|
+
readonly deleteFailed: number;
|
|
101
|
+
}
|
|
102
|
+
export interface SandboxPodReaper {
|
|
103
|
+
/** 一轮:列举 → 逐只判 → 删被判删的。不抛(列举失败 ⇒ `listed: false` + F 计数 + 一行 warn)。`runs` 缺席 = 本部署没有 run 店(② 照删)。 */
|
|
104
|
+
reapOnce(runs: SandboxPodRunReader | undefined): Promise<SandboxPodReapRound>;
|
|
105
|
+
}
|
|
106
|
+
export declare function createSandboxPodReaper(deps: {
|
|
107
|
+
readonly api: SandboxPodApi;
|
|
108
|
+
readonly logger?: Pick<Logger, "info" | "warn" | "error">;
|
|
109
|
+
readonly metrics?: Pick<Metrics, "inc">;
|
|
110
|
+
readonly now?: () => number;
|
|
111
|
+
}): SandboxPodReaper;
|
|
112
|
+
//# sourceMappingURL=k8s-pod-reaper.d.ts.map
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { recordFailOpen } from "../observability/fail-open.js";
|
|
2
|
+
import { reportPodDeleteFailed } from "./k8s-pod-delete-failure.js";
|
|
3
|
+
import { runStatusClassOf } from "./store-contracts.js";
|
|
4
|
+
import { LEASE_TTL_MAX_MS } from "../broker/lease-file.js";
|
|
5
|
+
import { WORKSPACE_SNAPSHOT_TIMEOUT_MS_RANGE } from "../workspace-space.js";
|
|
6
|
+
export const SANDBOX_POD_REAP_GRACE_MS = 60_000;
|
|
7
|
+
export const ENGINE_TEARDOWN_RELEASE_MS = 15_000;
|
|
8
|
+
export function sandboxRunEndHoldMs(snapshotTimeoutMs) {
|
|
9
|
+
return Math.max(LEASE_TTL_MAX_MS + SANDBOX_POD_REAP_GRACE_MS, snapshotTimeoutMs + ENGINE_TEARDOWN_RELEASE_MS);
|
|
10
|
+
}
|
|
11
|
+
const RUN_END_HOLD_READABLE = { min: LEASE_TTL_MAX_MS + SANDBOX_POD_REAP_GRACE_MS, max: WORKSPACE_SNAPSHOT_TIMEOUT_MS_RANGE.max + ENGINE_TEARDOWN_RELEASE_MS };
|
|
12
|
+
function runEndHoldOf(raw) {
|
|
13
|
+
if (raw === undefined || !/^[0-9]{1,16}$/.test(raw))
|
|
14
|
+
return undefined;
|
|
15
|
+
const ms = Number(raw);
|
|
16
|
+
return Number.isSafeInteger(ms) && ms >= RUN_END_HOLD_READABLE.min && ms <= RUN_END_HOLD_READABLE.max ? ms : undefined;
|
|
17
|
+
}
|
|
18
|
+
export const SANDBOX_POD_REAP_PERIOD_MS = 5 * 60_000;
|
|
19
|
+
const CLASS_VERDICT = {
|
|
20
|
+
live: "live",
|
|
21
|
+
parked: "live",
|
|
22
|
+
succeeded: "terminal",
|
|
23
|
+
unsuccessful: "terminal",
|
|
24
|
+
};
|
|
25
|
+
export function judgeSandboxPod(pod, run, now) {
|
|
26
|
+
if (pod.terminating)
|
|
27
|
+
return { skip: "terminating" };
|
|
28
|
+
if (pod.phase === "Succeeded" || pod.phase === "Failed")
|
|
29
|
+
return { delete: "containers_ended" };
|
|
30
|
+
if (pod.runId === undefined)
|
|
31
|
+
return { skip: "no_run_label" };
|
|
32
|
+
if ("storeAbsent" in run)
|
|
33
|
+
return { skip: "run_store_absent" };
|
|
34
|
+
if ("failed" in run)
|
|
35
|
+
return { skip: "run_read_failed" };
|
|
36
|
+
if ("absent" in run)
|
|
37
|
+
return { skip: "run_unread" };
|
|
38
|
+
const cls = runStatusClassOf(run.found.status);
|
|
39
|
+
if (cls === undefined)
|
|
40
|
+
return { skip: "run_status_unknown" };
|
|
41
|
+
if (CLASS_VERDICT[cls] === "live")
|
|
42
|
+
return { skip: "run_live" };
|
|
43
|
+
const endedAt = Date.parse(run.found.updatedAt);
|
|
44
|
+
if (!Number.isFinite(endedAt))
|
|
45
|
+
return { skip: "run_end_unreadable" };
|
|
46
|
+
const hold = runEndHoldOf(pod.runEndHold);
|
|
47
|
+
if (hold === undefined)
|
|
48
|
+
return { skip: "hold_unreadable" };
|
|
49
|
+
if (now - endedAt <= hold)
|
|
50
|
+
return { skip: "within_run_end_hold" };
|
|
51
|
+
return { delete: "run_ended_past_hold" };
|
|
52
|
+
}
|
|
53
|
+
export function createSandboxPodReaper(deps) {
|
|
54
|
+
const now = deps.now ?? Date.now;
|
|
55
|
+
return {
|
|
56
|
+
async reapOnce(runs) {
|
|
57
|
+
const deleted = {};
|
|
58
|
+
const skipped = {};
|
|
59
|
+
let gone = 0;
|
|
60
|
+
let deleteFailed = 0;
|
|
61
|
+
let pods;
|
|
62
|
+
try {
|
|
63
|
+
pods = await deps.api.list();
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
const error = e instanceof Error ? e.message : String(e);
|
|
67
|
+
recordFailOpen("server.sandbox-pod-reaper.list-failed", `namespace=${deps.api.namespace} ${error}`);
|
|
68
|
+
deps.logger?.warn("sandbox_pod_reap_list_failed", {
|
|
69
|
+
namespace: deps.api.namespace,
|
|
70
|
+
error,
|
|
71
|
+
effect: "sandbox pods were not deleted this round — ended pods and pods of ended runs (with their Secrets) stay until a later round lists them (one period later)",
|
|
72
|
+
});
|
|
73
|
+
return { listed: false, scanned: 0, deleted, skipped, gone, deleteFailed };
|
|
74
|
+
}
|
|
75
|
+
const reads = new Map();
|
|
76
|
+
const readRun = (runId) => {
|
|
77
|
+
if (runs === undefined)
|
|
78
|
+
return Promise.resolve({ storeAbsent: true });
|
|
79
|
+
let r = reads.get(runId);
|
|
80
|
+
if (r === undefined) {
|
|
81
|
+
r = runs.getRun(runId).then((row) => (row === undefined || row === null ? { absent: true } : { found: row }), () => ({ failed: true }));
|
|
82
|
+
reads.set(runId, r);
|
|
83
|
+
}
|
|
84
|
+
return r;
|
|
85
|
+
};
|
|
86
|
+
const at = now();
|
|
87
|
+
for (const pod of pods) {
|
|
88
|
+
const reading = pod.runId === undefined ? { absent: true } : await readRun(pod.runId);
|
|
89
|
+
const verdict = judgeSandboxPod(pod, reading, at);
|
|
90
|
+
if ("skip" in verdict) {
|
|
91
|
+
skipped[verdict.skip] = (skipped[verdict.skip] ?? 0) + 1;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const outcome = await deps.api.delete(pod.name);
|
|
96
|
+
if (outcome === "gone") {
|
|
97
|
+
gone++;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
deleted[verdict.delete] = (deleted[verdict.delete] ?? 0) + 1;
|
|
101
|
+
deps.metrics?.inc("sandbox_pods_reaped_total", { reason: verdict.delete });
|
|
102
|
+
deps.logger?.info("sandbox_pod_reaped", { namespace: deps.api.namespace, pod: pod.name, reason: verdict.delete, ...(pod.runId !== undefined ? { run: pod.runId } : {}) });
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
deleteFailed++;
|
|
106
|
+
reportPodDeleteFailed({
|
|
107
|
+
namespace: deps.api.namespace,
|
|
108
|
+
pod: pod.name,
|
|
109
|
+
why: `reaper_${verdict.delete}`,
|
|
110
|
+
error: e,
|
|
111
|
+
effect: "the pod, and the Secret it owns, stay in the sandbox namespace; the reaper tries this pod again next round (one period later), until then it holds static data only",
|
|
112
|
+
logger: deps.logger,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const round = { listed: true, scanned: pods.length, deleted, skipped, gone, deleteFailed };
|
|
117
|
+
deps.logger?.info("sandbox_pod_reap_round", { namespace: deps.api.namespace, ...round });
|
|
118
|
+
return round;
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=k8s-pod-reaper.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Checkpoint, type CheckpointSummary, type CheckpointToken, type PendingSteerInput, type ReopenReason, type ResolveExpectation, type ExecutionOutcomeRecordWord, type GateOutcome, type ResumeOutcome, type StoreDurability, type StoreFidelity } from "@sema-agent/core";
|
|
2
|
-
import { type CheckpointAskCandidate, type
|
|
2
|
+
import { type CheckpointAskCandidate, type CtxRowRewrite, type CtxRowsRewrite, type DecidedApprovalRecord, type PendingCheckpoint, type PendingRowBinding } from "./checkpoint-store-sql.js";
|
|
3
3
|
import type { SealedCheckpointCtx } from "../http/checkpoint-ctx.js";
|
|
4
4
|
export interface LocalCheckpointStoreOptions {
|
|
5
5
|
/** taskId join for the pending card (the local FileRunStore's `getActiveTaskId`). Absent ⇒ `taskId: null`. */
|
|
@@ -213,15 +213,12 @@ export declare class LocalCheckpointStore {
|
|
|
213
213
|
*/
|
|
214
214
|
getCtx<T extends object = object>(sessionId: string): Promise<T | null>;
|
|
215
215
|
/**
|
|
216
|
-
* 🔴
|
|
216
|
+
* 🔴 **一次性迁移的 ctx 行改写店腿**(local 孪生;`checkpoint-store-sql.ts` `CheckpointCtxRowsRewriter`,判词与 SQL 孪生 `rewriteCtxRows` 逐条同):
|
|
217
217
|
* 逐档读(信封经读腿**同一只**判形 —— 读不出 ⇒ 计数、跳过、**不删**),`rewrite` 交回新体 ⇒ 信封原样(`sessionId` / `updatedAt` 不动:收割按 `updatedAt` 判)
|
|
218
218
|
* 只换 `ctx`,经写腿同一只形闸、同一只原子写落回原档。封签绑的是信封里的 `sessionId`(与 `getCtx` 认行的那一格同一个)。
|
|
219
219
|
* 数据根独占 + 起服在任何读写者之前跑 ⇒ 本车道没有并发写者,不需要 CAS;没有一行装不下这回事(`tooLarge` 恒 0)。
|
|
220
220
|
*/
|
|
221
|
-
|
|
222
|
-
readonly ctx: SealedCheckpointCtx;
|
|
223
|
-
readonly values: number;
|
|
224
|
-
} | undefined): Promise<CtxRowsRewriteM28>;
|
|
221
|
+
rewriteCtxRows(rewrite: CtxRowRewrite): Promise<CtxRowsRewrite>;
|
|
225
222
|
/** GC ctx files whose lifecycle is over — same three guards as the SQL twins (stale ∧ no pending ∧ no active run). */
|
|
226
223
|
reapCtx(cutoff: number): Promise<number>;
|
|
227
224
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { denySweepOwns, parkedApprovalCard } from "../approval-content-kind.js";
|
|
3
|
-
import { parkRowFacts } from "../approval-card.js";
|
|
3
|
+
import { parkRowFacts, skillCardFacts } from "../approval-card.js";
|
|
4
4
|
import { durableRowIdentity } from "../checkpoint-identity.js";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
6
|
import { existsSync, readFileSync, readdirSync, unlinkSync, mkdirSync } from "node:fs";
|
|
@@ -155,7 +155,7 @@ export class LocalCheckpointStore {
|
|
|
155
155
|
for (const summary of await this.inner.listByScope(s)) {
|
|
156
156
|
const cp = await this.inner.get(summary.token);
|
|
157
157
|
if (cp && cp.status === "pending")
|
|
158
|
-
out.push({ token: summary.token, cp });
|
|
158
|
+
out.push({ token: summary.token, cp, summary });
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
return out;
|
|
@@ -165,7 +165,7 @@ export class LocalCheckpointStore {
|
|
|
165
165
|
}
|
|
166
166
|
async listPending(scope) {
|
|
167
167
|
const rows = [];
|
|
168
|
-
for (const { token, cp } of await this.pendings(scope)) {
|
|
168
|
+
for (const { token, cp, summary } of await this.pendings(scope)) {
|
|
169
169
|
const pa = cp.pendingAction;
|
|
170
170
|
const gate = cp.gate;
|
|
171
171
|
const gateKind = queueRowGateKind(gate?.kind);
|
|
@@ -193,6 +193,7 @@ export class LocalCheckpointStore {
|
|
|
193
193
|
...(pa?.hasBidiControls === true ? { hasBidiControls: true } : {}),
|
|
194
194
|
...((card) => (card !== undefined ? { approvalCard: card } : {}))(parkedApprovalCard(cp.gate)),
|
|
195
195
|
...parkRowFacts(cp.pendingAction),
|
|
196
|
+
...skillCardFacts(summary),
|
|
196
197
|
});
|
|
197
198
|
}
|
|
198
199
|
return rows.sort((a, b) => (b.riskDescriptor?.severity ?? 0) - (a.riskDescriptor?.severity ?? 0) || a.createdAt - b.createdAt);
|
|
@@ -326,7 +327,7 @@ export class LocalCheckpointStore {
|
|
|
326
327
|
const rec = refusingCtx(sessionId, () => decodeJsonColumn(readFileSync(p, "utf8"), createCtxRecordReader(), `checkpoint-ctx ${ctxFileOf(sessionId)} for session ${JSON.stringify(sessionId)}`));
|
|
327
328
|
return rec.sessionId === sessionId ? rec.ctx : null;
|
|
328
329
|
}
|
|
329
|
-
async
|
|
330
|
+
async rewriteCtxRows(rewrite) {
|
|
330
331
|
const out = { scanned: 0, rewritten: 0, valuesRewritten: 0, unreadable: 0, tooLarge: 0 };
|
|
331
332
|
for (const f of readdirSync(this.ctxDir)) {
|
|
332
333
|
if (!f.endsWith(".json"))
|
|
@@ -334,7 +335,7 @@ export class LocalCheckpointStore {
|
|
|
334
335
|
const p = join(this.ctxDir, f);
|
|
335
336
|
let rec;
|
|
336
337
|
try {
|
|
337
|
-
rec = decodeJsonColumn(readFileSync(p, "utf8"), createCtxRecordReader(), `checkpoint-ctx ${f} (
|
|
338
|
+
rec = decodeJsonColumn(readFileSync(p, "utf8"), createCtxRecordReader(), `checkpoint-ctx ${f} (ctx row rewrite scan)`);
|
|
338
339
|
}
|
|
339
340
|
catch {
|
|
340
341
|
out.unreadable += 1;
|
|
@@ -345,7 +346,7 @@ export class LocalCheckpointStore {
|
|
|
345
346
|
if (next === undefined)
|
|
346
347
|
continue;
|
|
347
348
|
const text = JSON.stringify({ sessionId: rec.sessionId, updatedAt: rec.updatedAt, ctx: next.ctx });
|
|
348
|
-
assertJsonColumnWritable(text, createCtxRecordReader(), `checkpoint ctx for session ${JSON.stringify(rec.sessionId)} (local
|
|
349
|
+
assertJsonColumnWritable(text, createCtxRecordReader(), `checkpoint ctx for session ${JSON.stringify(rec.sessionId)} (local ctx row rewrite)`);
|
|
349
350
|
atomicWriteFile(this.tmpDir, p, text);
|
|
350
351
|
out.rewritten += 1;
|
|
351
352
|
out.valuesRewritten += next.values;
|
|
@@ -43,6 +43,7 @@ export class MemoryRunStore {
|
|
|
43
43
|
errorCode: r.errorCode,
|
|
44
44
|
jobId: r.jobId,
|
|
45
45
|
source: r.source,
|
|
46
|
+
identity: r.identity,
|
|
46
47
|
instanceId: r.instanceId,
|
|
47
48
|
cancelRequested: r.cancelRequested,
|
|
48
49
|
objectivePreview: r.objectivePreview,
|
|
@@ -75,6 +76,7 @@ export class MemoryRunStore {
|
|
|
75
76
|
objectivePreview: meta.objectivePreview ?? null,
|
|
76
77
|
idemKey,
|
|
77
78
|
idemBodyFingerprint: meta.idem?.bodyFingerprint ?? null,
|
|
79
|
+
identity: meta.identity ?? null,
|
|
78
80
|
createdAt: now,
|
|
79
81
|
updatedAt: now,
|
|
80
82
|
cancelRequested: false,
|