@sema-agent/server 7.139.0 → 7.141.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 +471 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +94 -26
- package/dist/adoption/runner.js +1 -1
- package/dist/adoption/sql.js +1 -1
- package/dist/approval-batch-census.d.ts +27 -0
- package/dist/approval-batch-census.js +27 -0
- package/dist/approval-card.d.ts +37 -22
- package/dist/approval-card.js +6 -3
- package/dist/approval.d.ts +15 -0
- package/dist/approval.js +10 -0
- package/dist/audit.d.ts +4 -2
- package/dist/audit.js +2 -2
- package/dist/bench/s1/live-deps.js +3 -2
- package/dist/bench/s1/row.d.ts +5 -2
- package/dist/bench/s1/row.js +2 -22
- package/dist/boot/adoption.js +1 -1
- package/dist/boot/device-lane.d.ts +11 -0
- package/dist/boot/device-lane.js +16 -0
- package/dist/boot/engine-lease.d.ts +24 -3
- package/dist/boot/engine-lease.js +9 -2
- package/dist/boot/execution-env.js +18 -12
- package/dist/boot/execution-lane.d.ts +8 -0
- package/dist/boot/execution-lane.js +6 -1
- package/dist/boot/memory-boundary.d.ts +16 -53
- package/dist/boot/memory-boundary.js +10 -20
- package/dist/boot/memory-consolidation.d.ts +16 -12
- package/dist/boot/memory-consolidation.js +24 -13
- package/dist/boot/reapers.d.ts +2 -2
- package/dist/boot/resolve-spec.d.ts +3 -5
- package/dist/boot/resolve-spec.js +9 -8
- package/dist/boot/runner-deps.d.ts +3 -3
- package/dist/boot/runner-deps.js +1 -0
- package/dist/boot/session-faces.d.ts +1 -1
- package/dist/boot/session-faces.js +4 -3
- package/dist/boot/shutdown.js +5 -0
- package/dist/boot/stage-03-store-backend.d.ts +2 -6
- package/dist/boot/stage-03-store-backend.js +2 -3
- package/dist/boot/stage-04-stores.d.ts +2 -6
- package/dist/boot/stage-04-stores.js +5 -5
- package/dist/boot/stage-05-execution-env.d.ts +2 -6
- package/dist/boot/stage-05-execution-env.js +3 -1
- package/dist/boot/stage-06-runners.d.ts +2 -8
- package/dist/boot/stage-06-runners.js +3 -3
- package/dist/boot/stage-07-capability-layer.d.ts +2 -7
- package/dist/boot/stage-07-capability-layer.js +8 -10
- package/dist/boot/stage-08-reapers.d.ts +2 -7
- package/dist/boot/stage-09-leader.d.ts +2 -7
- package/dist/boot/stage-10-http-server.d.ts +2 -7
- package/dist/boot/stage-10-http-server.js +6 -4
- package/dist/boot/stores.d.ts +3 -6
- package/dist/boot/stores.js +37 -44
- package/dist/boot/task-list-lane.d.ts +1 -1
- package/dist/boot/task-list-lane.js +1 -1
- package/dist/boot/web-admin.js +36 -9
- package/dist/capabilities/execution-lane.d.ts +2 -2
- package/dist/capabilities/scenarios.d.ts +1 -1
- package/dist/capability-facts.d.ts +9 -0
- package/dist/capability-facts.js +9 -0
- package/dist/card-void.d.ts +38 -0
- package/dist/card-void.js +23 -0
- package/dist/config-catalog.js +12 -8
- package/dist/config-provider.d.ts +18 -17
- package/dist/config-provider.js +28 -36
- package/dist/config-types.d.ts +36 -9
- package/dist/config.js +56 -46
- package/dist/delegated-session-owner.d.ts +23 -20
- package/dist/delegated-session-owner.js +2 -2
- package/dist/deployment-shape.d.ts +9 -8
- package/dist/deployment-shape.js +4 -4
- package/dist/device-store.d.ts +7 -0
- package/dist/device-store.js +3 -0
- package/dist/execution-config-root.d.ts +22 -0
- package/dist/execution-config-root.js +35 -0
- package/dist/execution-lane-caps.d.ts +145 -107
- package/dist/execution-lane-caps.js +144 -83
- package/dist/execution-placement.d.ts +3 -0
- package/dist/execution-placement.js +7 -0
- package/dist/fleet/drain-entry.d.ts +3 -0
- package/dist/hosted-posture.d.ts +8 -0
- package/dist/hosted-posture.js +4 -0
- package/dist/http/admission.js +3 -3
- package/dist/http/caller-trust.d.ts +3 -2
- package/dist/http/caller-trust.js +2 -1
- package/dist/http/dispatch.js +4 -0
- package/dist/http/live-frame.d.ts +24 -0
- package/dist/http/live-frame.js +16 -0
- package/dist/http/memory-compliance-failure-reply.d.ts +3 -1
- package/dist/http/memory-compliance-failure-reply.js +5 -0
- package/dist/http/memory-layer-reply.d.ts +30 -0
- package/dist/http/memory-layer-reply.js +24 -0
- package/dist/http/resume-legs.js +49 -35
- package/dist/http/retired-service-deps.d.ts +1 -1
- package/dist/http/retired-service-deps.js +1 -1
- package/dist/http/route-table.js +2 -0
- package/dist/http/routes/adoption.js +1 -1
- package/dist/http/routes/approvals-assistant.js +1 -0
- package/dist/http/routes/capabilities.js +5 -4
- package/dist/http/routes/diagnostics.d.ts +0 -19
- package/dist/http/routes/diagnostics.js +16 -2
- package/dist/http/routes/memory-bundle.d.ts +2 -2
- package/dist/http/routes/memory-bundle.js +4 -1
- package/dist/http/routes/memory-compliance.js +9 -2
- package/dist/http/routes/memory-consolidation.js +12 -1
- package/dist/http/routes/memory-origin.js +14 -2
- package/dist/http/routes/memory-policy.js +7 -2
- package/dist/http/routes/run-permission-mode.d.ts +75 -0
- package/dist/http/routes/run-permission-mode.js +170 -0
- package/dist/http/routes/session-sync.d.ts +3 -6
- package/dist/http/routes/session-sync.js +7 -2
- package/dist/http/routes/sessions.js +33 -15
- package/dist/http/routes/tasks.js +67 -68
- package/dist/http/run-read-face.js +3 -3
- package/dist/http/send.d.ts +1 -1
- package/dist/http/send.js +2 -2
- package/dist/http/server.d.ts +14 -27
- package/dist/http/server.js +2 -2
- package/dist/http/sse-log.d.ts +3 -8
- package/dist/http/stored-body-replay.d.ts +3 -3
- package/dist/http/stored-body-replay.js +3 -1
- package/dist/identity-source.d.ts +1 -1
- package/dist/identity-source.js +1 -1
- package/dist/instruction-source-notice.d.ts +7 -0
- package/dist/instruction-source-notice.js +6 -6
- package/dist/lane-home-probe.d.ts +68 -42
- package/dist/lane-home-probe.js +53 -30
- package/dist/leader/wire.js +5 -4
- package/dist/mcp-assertion-header.d.ts +4 -0
- package/dist/mcp-assertion-header.js +5 -0
- package/dist/memory-bundle-engine.d.ts +16 -9
- package/dist/memory-bundle-engine.js +29 -6
- package/dist/memory-operator-faces.d.ts +85 -30
- package/dist/memory-operator-faces.js +136 -30
- package/dist/memory-posture.d.ts +1 -1
- package/dist/memory-project-layer.d.ts +3 -21
- package/dist/memory-project-layer.js +11 -8
- package/dist/memory-scope-stores.d.ts +73 -0
- package/dist/memory-scope-stores.js +86 -0
- package/dist/memory-scope.d.ts +33 -9
- package/dist/memory-scope.js +17 -5
- package/dist/memory-sync-client.d.ts +34 -9
- package/dist/memory-sync-client.js +108 -28
- package/dist/observability/fail-open.d.ts +30 -2
- package/dist/observability/fail-open.js +30 -2
- package/dist/observability/metrics.js +0 -1
- package/dist/observability/run-terminal.d.ts +6 -2
- package/dist/observability/run-terminal.js +25 -26
- package/dist/observability/secret-env-scrub.d.ts +20 -1
- package/dist/observability/secret-env-scrub.js +14 -0
- package/dist/orchestration/workflow-completion-inbox.d.ts +5 -2
- package/dist/orchestration/workflow-completion-inbox.js +13 -5
- package/dist/orchestration/workflow-notify-journal.d.ts +5 -3
- package/dist/orchestration/workflow-notify-journal.js +2 -1
- package/dist/permission-mode-admission.d.ts +68 -0
- package/dist/permission-mode-admission.js +23 -0
- package/dist/permission-rule-list.d.ts +55 -6
- package/dist/permission-rule-list.js +52 -5
- package/dist/plugins/adoption-log-sql.js +4 -4
- package/dist/plugins/approval-ask-store-sql.js +14 -14
- package/dist/plugins/approval-nonce-store.js +4 -4
- package/dist/plugins/blob-backend.d.ts +4 -4
- package/dist/plugins/blob-backend.js +7 -7
- package/dist/plugins/breaker-state-sql.js +2 -2
- package/dist/plugins/caching-session-store.d.ts +6 -0
- package/dist/plugins/caching-session-store.js +3 -0
- package/dist/plugins/checkpoint-index-columns.js +2 -2
- package/dist/plugins/checkpoint-store-sql.d.ts +65 -17
- package/dist/plugins/checkpoint-store-sql.js +90 -19
- package/dist/plugins/device-store-sql.d.ts +5 -2
- package/dist/plugins/device-store-sql.js +23 -20
- package/dist/plugins/ensure-index.d.ts +1 -2
- package/dist/plugins/execution-placement-sql.d.ts +19 -9
- package/dist/plugins/execution-placement-sql.js +20 -9
- package/dist/plugins/file-history-store-sql.d.ts +2 -2
- package/dist/plugins/file-history-store-sql.js +4 -4
- package/dist/plugins/file-run-store.d.ts +2 -2
- package/dist/plugins/file-run-store.js +1 -1
- package/dist/plugins/fork-routing-session-store.d.ts +10 -32
- package/dist/plugins/fork-routing-session-store.js +2 -61
- package/dist/plugins/image-bake-store-sql.js +9 -9
- package/dist/plugins/image-index-sql.js +11 -11
- package/dist/plugins/k8s-exec-protocol.d.ts +4 -1
- package/dist/plugins/k8s-exec-protocol.js +6 -1
- package/dist/plugins/leader-run-store-sql.js +4 -4
- package/dist/plugins/local-checkpoint-store.d.ts +10 -1
- package/dist/plugins/local-checkpoint-store.js +22 -0
- package/dist/plugins/local-session-store.d.ts +227 -35
- package/dist/plugins/local-session-store.js +518 -37
- package/dist/plugins/local-session-window.d.ts +17 -3
- package/dist/plugins/local-session-window.js +7 -13
- package/dist/plugins/mailbox-store-sql.d.ts +2 -1
- package/dist/plugins/mailbox-store-sql.js +4 -4
- package/dist/plugins/memory-engine-tidb.js +5 -5
- package/dist/plugins/memory-optout-grant-store-sql.js +1 -1
- package/dist/plugins/memory-run-store.d.ts +2 -2
- package/dist/plugins/memory-run-store.js +1 -1
- package/dist/plugins/memory-sync-store-tidb.js +3 -3
- package/dist/plugins/one-time-migrations.d.ts +52 -11
- package/dist/plugins/one-time-migrations.js +101 -39
- package/dist/plugins/org-delivery-quota-store.js +2 -2
- package/dist/plugins/outcome-ledger-sql.js +2 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +4 -4
- package/dist/plugins/permission-rule-store-sql.js +16 -16
- package/dist/plugins/pg-pool.js +3 -2
- package/dist/plugins/pg-session-storage.d.ts +2 -0
- package/dist/plugins/pg-session-storage.js +3 -2
- package/dist/plugins/remote-env-adb.d.ts +7 -24
- package/dist/plugins/remote-env-adb.js +22 -5
- package/dist/plugins/remote-env-device.d.ts +2 -28
- package/dist/plugins/remote-env-device.js +2 -1
- package/dist/plugins/remote-env-e2b.d.ts +40 -27
- package/dist/plugins/remote-env-e2b.js +180 -20
- package/dist/plugins/remote-env-file-error.d.ts +1 -1
- package/dist/plugins/remote-env-file-error.js +8 -2
- package/dist/plugins/remote-env-host.d.ts +19 -35
- package/dist/plugins/remote-env-host.js +26 -3
- package/dist/plugins/remote-env-k8s.d.ts +22 -14
- package/dist/plugins/remote-env-k8s.js +150 -26
- package/dist/plugins/remote-env-local-docker.d.ts +27 -24
- package/dist/plugins/remote-env-local-docker.js +91 -24
- package/dist/plugins/remote-env-ssh.d.ts +4 -24
- package/dist/plugins/remote-env-ssh.js +4 -3
- package/dist/plugins/resume-anchor-store-sql.js +2 -2
- package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
- package/dist/plugins/retention-lane-store-sql.js +2 -2
- package/dist/plugins/retention-store-sql.d.ts +1 -1
- package/dist/plugins/retention-store-sql.js +6 -6
- package/dist/plugins/run-store-sql.d.ts +3 -3
- package/dist/plugins/run-store-sql.js +19 -18
- package/dist/plugins/sandbox-read-floor.d.ts +273 -0
- package/dist/plugins/sandbox-read-floor.js +608 -0
- package/dist/plugins/schema-consistency.d.ts +6 -6
- package/dist/plugins/schema-consistency.js +1 -1
- package/dist/plugins/schema-ensure-legs.d.ts +2 -3
- package/dist/plugins/schema-ensure-legs.js +2 -2
- package/dist/plugins/schema-live-columns.d.ts +1 -1
- package/dist/plugins/session-capture-record-store-sql.js +1 -1
- package/dist/plugins/session-ownership.d.ts +2 -1
- package/dist/plugins/session-placement.d.ts +17 -4
- package/dist/plugins/session-placement.js +2 -1
- package/dist/plugins/session-rules-column.d.ts +1 -1
- package/dist/plugins/session-rules-column.js +1 -1
- package/dist/plugins/session-store.d.ts +18 -20
- package/dist/plugins/session-store.js +4 -12
- package/dist/plugins/session-workspace-cell.d.ts +1 -1
- package/dist/plugins/session-workspace-cell.js +1 -1
- package/dist/plugins/shared-memory-store-sql.js +2 -2
- package/dist/plugins/sql-driver.d.ts +10 -5
- package/dist/plugins/sql-driver.js +6 -6
- package/dist/plugins/sql-errors.js +1 -1
- package/dist/plugins/store-backend.d.ts +35 -6
- package/dist/plugins/store-backend.js +27 -10
- package/dist/plugins/store-contracts.d.ts +9 -2
- package/dist/plugins/task-list-store-sql.js +11 -11
- package/dist/plugins/tidb-pool.js +7 -6
- package/dist/plugins/tidb-session-storage.js +4 -4
- package/dist/plugins/tidb-session-store.d.ts +4 -1
- package/dist/plugins/tidb-session-store.js +16 -15
- package/dist/plugins/tool-result-store-sql.d.ts +2 -2
- package/dist/plugins/tool-result-store-sql.js +3 -3
- package/dist/plugins/user-input-ledger-sql.js +2 -2
- package/dist/plugins/workflow-journal-store-sql.js +5 -5
- package/dist/plugins/workflow-run-store-sql.js +19 -19
- package/dist/plugins/write-behind-counter.d.ts +2 -3
- package/dist/plugins/write-behind-counter.js +5 -5
- package/dist/resource-suspend.d.ts +14 -12
- package/dist/resource-suspend.js +1 -1
- package/dist/resource-window.d.ts +4 -3
- package/dist/run-cancel-context.d.ts +34 -0
- package/dist/run-cancel-context.js +7 -0
- package/dist/run-local.js +14 -19
- package/dist/runs.d.ts +47 -16
- package/dist/runs.js +112 -113
- package/dist/security.d.ts +2 -2
- package/dist/server-secret-env.d.ts +2 -1
- package/dist/server-secret-env.js +2 -4
- package/dist/session-error-wire.d.ts +22 -4
- package/dist/session-error-wire.js +16 -0
- package/dist/session-incarnation.d.ts +10 -4
- package/dist/session-incarnation.js +1 -1
- package/dist/session-policy-wire.d.ts +1 -1
- package/dist/session-sync-kernel.d.ts +42 -1
- package/dist/session-sync-kernel.js +17 -0
- package/dist/session-sync.js +16 -4
- package/dist/sql-engine-coordinates.d.ts +49 -0
- package/dist/sql-engine-coordinates.js +27 -0
- package/dist/sql-engine-posture.d.ts +1 -1
- package/dist/sql-engine-posture.js +1 -1
- package/dist/standing-answer-grant.d.ts +31 -2
- package/dist/standing-answer-grant.js +14 -2
- package/dist/store-posture.d.ts +1 -1
- package/dist/{plugins → stores-sql}/background-agent-store-sql.d.ts +2 -2
- package/dist/{plugins → stores-sql}/background-agent-store-sql.js +8 -8
- package/dist/{plugins → stores-sql}/session-policy-store-sql.d.ts +7 -5
- package/dist/{plugins → stores-sql}/session-policy-store-sql.js +12 -12
- package/dist/{plugins → stores-sql}/usage-window-store-sql.d.ts +1 -1
- package/dist/{plugins → stores-sql}/usage-window-store-sql.js +3 -3
- package/dist/task-a2a.d.ts +1 -1
- package/dist/task-a2a.js +5 -6
- package/dist/task-mcp.d.ts +21 -8
- package/dist/task-mcp.js +17 -9
- package/dist/task-settings.d.ts +52 -25
- package/dist/task-settings.js +91 -28
- package/dist/terminal.d.ts +103 -19
- package/dist/terminal.js +72 -20
- package/dist/trace/core-keyset-guard.d.ts +79 -2
- package/dist/trace/exit-seat.d.ts +7 -0
- package/dist/trace/exit-seat.js +1 -0
- package/dist/trace/model-segment.d.ts +2 -2
- package/dist/trace/redact.d.ts +3 -1
- package/dist/trace/redact.js +395 -110
- package/dist/trace/self-minted-wire.d.ts +226 -2
- package/dist/trace/self-minted-wire.js +79 -0
- package/dist/verifier-pause-card.d.ts +8 -0
- package/dist/verifier-pause-card.js +19 -0
- package/dist/web-admin-client/apply-effective.d.ts +5 -7
- package/dist/web-admin-client/apply-effective.js +2 -2
- package/dist/web-admin-client/candidate-gate.d.ts +11 -7
- package/dist/web-admin-client/candidate-gate.js +1 -1
- package/dist/web-admin-client/http-client.d.ts +5 -4
- package/dist/web-admin-client/http-client.js +2 -2
- package/dist/web-admin-client/read-warnings.d.ts +7 -17
- package/dist/web-admin-client/read-warnings.js +5 -9
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -2
- package/package.json +2 -2
- package/wire-changes/server@7.140.0.json +184 -0
- package/wire-changes/server@7.141.0.json +367 -0
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { ExecutionError } from "@sema-agent/core";
|
|
4
|
+
import { shellQuote } from "./remote-shell.js";
|
|
5
|
+
import { echoCallerText } from "../caller-text-echo.js";
|
|
6
|
+
import { ADAPTER_VOLUME_NAMES } from "./k8s-exec-protocol.js";
|
|
7
|
+
import { SANDBOX_READ_FLOOR_ENV, sandboxReadFloorState } from "../execution-lane-caps.js";
|
|
8
|
+
export const READ_FLOOR_UNAVAILABLE_REASONS = ["arch", "user_notif", "addfd_send", "uid_zero", "writable_system_path", "binary_digest", "relay_silent", "relay_identity", "provenance"];
|
|
9
|
+
const REASON_SET = new Set(READ_FLOOR_UNAVAILABLE_REASONS);
|
|
10
|
+
function reasonSentence(reason) {
|
|
11
|
+
switch (reason) {
|
|
12
|
+
case "arch":
|
|
13
|
+
return "the sandbox's machine architecture has no supervisor build (x86_64 and aarch64 only)";
|
|
14
|
+
case "user_notif":
|
|
15
|
+
return "the sandbox kernel does not offer seccomp user notification";
|
|
16
|
+
case "addfd_send":
|
|
17
|
+
return "the sandbox kernel cannot hand a judged file to the command (SECCOMP_IOCTL_NOTIF_ADDFD with SEND needs Linux 5.14 or later)";
|
|
18
|
+
case "uid_zero":
|
|
19
|
+
return "the command would run as root (a root command can read the raw block device below any path floor) — run the image as a non-root user";
|
|
20
|
+
case "writable_system_path":
|
|
21
|
+
return "a system path the supervisor starts from (PATH directories, the dynamic loader, /etc/ld.so.preload) is writable by the command's user";
|
|
22
|
+
case "binary_digest":
|
|
23
|
+
return "the supervisor binary in the sandbox is not the one this server pins";
|
|
24
|
+
case "relay_silent":
|
|
25
|
+
return "the relay that carries the floor's questions to this server did not answer";
|
|
26
|
+
case "relay_identity":
|
|
27
|
+
return "the supervisor could not authenticate its relay";
|
|
28
|
+
case "provenance":
|
|
29
|
+
return "the sandbox was not under the floor since it was created (a resumed sandbox without the floor's marker)";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function buildReadFloorUnavailableMessage(reason, detail, floor) {
|
|
33
|
+
const first = floor?.rows[0];
|
|
34
|
+
return (`read floor unavailable on this sandbox (${reason}): ${reasonSentence(reason)}${detail ? ` — ${detail}` : ""}. ` +
|
|
35
|
+
`This command carries a read-deny table${first !== undefined ? ` (first row "${first}")` : ""} this sandbox cannot keep, so it is refused rather than run with fewer rows. ` +
|
|
36
|
+
`To run this lane without the byte floor, set ${SANDBOX_READ_FLOOR_ENV}=off (the lane then declares keepsReadFloor=false: each run with read-deny rows is announced, or refused when READ_DENY_FLOOR_REQUIRED=true).`);
|
|
37
|
+
}
|
|
38
|
+
export function buildReadFloorMissingMessage(lane) {
|
|
39
|
+
return (`read floor: this ${lane} sandbox keeps the read-deny byte floor, and this command start carries no floor — it is refused rather than run a process the floor does not judge ` +
|
|
40
|
+
`(every start on a lane that keeps the floor must carry the run's read-deny table). ` +
|
|
41
|
+
`To run this lane without the byte floor, set ${SANDBOX_READ_FLOOR_ENV}=off.`);
|
|
42
|
+
}
|
|
43
|
+
export const ADAPTER_START = Object.freeze({ kind: "adapter" });
|
|
44
|
+
export const BOOTSTRAP_START = Object.freeze({ kind: "bootstrap" });
|
|
45
|
+
export function engineStart(readFloor, hold = false) {
|
|
46
|
+
return hold ? { kind: "engine", readFloor, hold } : { kind: "engine", readFloor };
|
|
47
|
+
}
|
|
48
|
+
const BARE = Object.freeze({ mode: "bare" });
|
|
49
|
+
const ARCH_OF_UNAME = Object.freeze({ x86_64: "x86_64", amd64: "x86_64", aarch64: "aarch64", arm64: "aarch64" });
|
|
50
|
+
export const FLOOR_DIR = "/opt/sema/floor";
|
|
51
|
+
export function floorBinaryPath(arch) {
|
|
52
|
+
return `${FLOOR_DIR}/${arch}/sema-floor`;
|
|
53
|
+
}
|
|
54
|
+
function laneLaunchPlan(lane) {
|
|
55
|
+
switch (lane) {
|
|
56
|
+
case "e2b":
|
|
57
|
+
return { start: "root", control: "root", dropToWorkspaceOwner: true, stateDir: `${FLOOR_DIR}/state` };
|
|
58
|
+
case "local-docker":
|
|
59
|
+
return { start: "exec-user", control: "root", dropToWorkspaceOwner: false, stateDir: `${FLOOR_DIR}/state` };
|
|
60
|
+
case "k8s":
|
|
61
|
+
return { start: "exec-user", control: "exec-user", dropToWorkspaceOwner: false, stateDir: "/tmp/.sema-floor-state" };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export const FLOOR_ARGV = {
|
|
65
|
+
run: (bin, startId, as) => [bin, "run", "--start", startId, ...(as !== undefined ? ["--as", as] : [])],
|
|
66
|
+
plain: (bin, as) => [bin, "plain", ...(as !== undefined ? ["--as", as] : [])],
|
|
67
|
+
serve: (bin, stateDir, fresh) => [bin, "serve", "--state", stateDir, ...(fresh ? ["--fresh"] : [])],
|
|
68
|
+
selfcheck: (bin, digest, as) => [bin, "selfcheck", "--digest", digest, ...(as !== undefined ? ["--as", as] : [])],
|
|
69
|
+
};
|
|
70
|
+
function shellLine(argv) {
|
|
71
|
+
return argv.map(shellQuote).join(" ");
|
|
72
|
+
}
|
|
73
|
+
export function parseSelfcheck(stdout, exitCode) {
|
|
74
|
+
const line = stdout.split("\n").map((l) => l.trim()).filter((l) => l !== "").at(-1) ?? "";
|
|
75
|
+
const okm = /^sema-floor selfcheck ok (\S+) (\S+)$/.exec(line);
|
|
76
|
+
if (okm && exitCode === 0)
|
|
77
|
+
return { ok: true, version: okm[1], arch: okm[2] };
|
|
78
|
+
const no = /^sema-floor selfcheck refused ([a-z_]+): ?(.*)$/.exec(line);
|
|
79
|
+
if (no && exitCode === 125 && REASON_SET.has(no[1]))
|
|
80
|
+
return { ok: false, reason: no[1], ...(no[2] ? { detail: echoCallerText(no[2], { maxChars: 300 }) } : {}) };
|
|
81
|
+
return { ok: false, reason: "binary_digest", detail: `the selfcheck answer is not one this server reads (exit ${exitCode}${line !== "" ? `, ${JSON.stringify(echoCallerText(line, { maxChars: 120 }))}` : ", no output"})` };
|
|
82
|
+
}
|
|
83
|
+
const START_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
84
|
+
export const MAX_PATHS_PER_QUESTION = 256;
|
|
85
|
+
export const MAX_RELAY_LINE_BYTES = 1024 * 1024;
|
|
86
|
+
const isInt = (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0;
|
|
87
|
+
const exactKeys = (o, keys) => {
|
|
88
|
+
const ks = Object.keys(o);
|
|
89
|
+
return ks.length === keys.length && keys.every((k) => Object.hasOwn(o, k));
|
|
90
|
+
};
|
|
91
|
+
export function parseRelayFrame(line) {
|
|
92
|
+
let v;
|
|
93
|
+
try {
|
|
94
|
+
v = JSON.parse(line);
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
return { t: "broken", why: `not JSON: ${echoCallerText(e instanceof Error ? e.message : String(e), { maxChars: 120 })}` };
|
|
98
|
+
}
|
|
99
|
+
if (typeof v !== "object" || v === null || Array.isArray(v))
|
|
100
|
+
return { t: "broken", why: "not a JSON object" };
|
|
101
|
+
const o = v;
|
|
102
|
+
const id = isInt(o.id) ? o.id : undefined;
|
|
103
|
+
const count = Array.isArray(o.paths) && o.paths.length >= 1 && o.paths.length <= MAX_PATHS_PER_QUESTION ? o.paths.length : undefined;
|
|
104
|
+
const broken = (why) => ({ t: "broken", why, ...(id !== undefined ? { id } : {}), ...(id !== undefined && count !== undefined ? { count } : {}) });
|
|
105
|
+
switch (o.t) {
|
|
106
|
+
case "ready":
|
|
107
|
+
return exactKeys(o, ["t", "v", "pid"]) && o.v === 1 && isInt(o.pid) ? { t: "ready", pid: o.pid } : broken("ready: fields");
|
|
108
|
+
case "refused":
|
|
109
|
+
return exactKeys(o, ["t", "why", "detail"]) && typeof o.why === "string" && typeof o.detail === "string" ? { t: "refused", why: o.why, detail: o.detail } : broken("refused: fields");
|
|
110
|
+
case "gone":
|
|
111
|
+
return exactKeys(o, ["t", "start"]) && typeof o.start === "string" && START_ID.test(o.start) ? { t: "gone", start: o.start } : broken("gone: fields");
|
|
112
|
+
case "q":
|
|
113
|
+
if (!exactKeys(o, ["t", "id", "start", "paths"]) || id === undefined || typeof o.start !== "string" || !START_ID.test(o.start) || count === undefined)
|
|
114
|
+
return broken("q: fields");
|
|
115
|
+
if (!o.paths.every((p) => typeof p === "string" && p.startsWith("/")))
|
|
116
|
+
return broken("q: a path that is not an absolute string");
|
|
117
|
+
return { t: "q", id, start: o.start, paths: o.paths };
|
|
118
|
+
default:
|
|
119
|
+
return broken("unknown frame type");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export const UNJUDGED_VERDICT = "read floor: no verdict for this start";
|
|
123
|
+
function judgeOne(floor, path) {
|
|
124
|
+
let v;
|
|
125
|
+
try {
|
|
126
|
+
v = floor.denies(path);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
v = undefined;
|
|
130
|
+
}
|
|
131
|
+
if (v === null)
|
|
132
|
+
return null;
|
|
133
|
+
if (typeof v === "string" && v !== "")
|
|
134
|
+
return v;
|
|
135
|
+
return floor.rows[0] ?? UNJUDGED_VERDICT;
|
|
136
|
+
}
|
|
137
|
+
export function createFloorJudge() {
|
|
138
|
+
const starts = new Map();
|
|
139
|
+
const unjudged = (id, n) => ({ t: "a", id, verdicts: Array.from({ length: n }, () => UNJUDGED_VERDICT) });
|
|
140
|
+
return {
|
|
141
|
+
register(floor, held) {
|
|
142
|
+
const id = randomBytes(12).toString("hex");
|
|
143
|
+
starts.set(id, { floor, open: held, connected: true });
|
|
144
|
+
return id;
|
|
145
|
+
},
|
|
146
|
+
launch(startId) {
|
|
147
|
+
const e = starts.get(startId);
|
|
148
|
+
if (e === undefined || !e.open)
|
|
149
|
+
return false;
|
|
150
|
+
e.connected = true;
|
|
151
|
+
return true;
|
|
152
|
+
},
|
|
153
|
+
release(startId) {
|
|
154
|
+
const e = starts.get(startId);
|
|
155
|
+
if (e === undefined)
|
|
156
|
+
return;
|
|
157
|
+
e.open = false;
|
|
158
|
+
if (!e.connected)
|
|
159
|
+
starts.delete(startId);
|
|
160
|
+
},
|
|
161
|
+
answer(frame) {
|
|
162
|
+
switch (frame.t) {
|
|
163
|
+
case "ready":
|
|
164
|
+
case "refused":
|
|
165
|
+
return undefined;
|
|
166
|
+
case "gone": {
|
|
167
|
+
const e = starts.get(frame.start);
|
|
168
|
+
if (e === undefined)
|
|
169
|
+
return undefined;
|
|
170
|
+
e.connected = false;
|
|
171
|
+
if (!e.open)
|
|
172
|
+
starts.delete(frame.start);
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
case "broken":
|
|
176
|
+
return frame.id !== undefined && frame.count !== undefined ? unjudged(frame.id, frame.count) : undefined;
|
|
177
|
+
case "q": {
|
|
178
|
+
const e = starts.get(frame.start);
|
|
179
|
+
if (e === undefined)
|
|
180
|
+
return unjudged(frame.id, frame.paths.length);
|
|
181
|
+
return { t: "a", id: frame.id, verdicts: frame.paths.map((p) => judgeOne(e.floor, p)) };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
get size() {
|
|
186
|
+
return starts.size;
|
|
187
|
+
},
|
|
188
|
+
clear() {
|
|
189
|
+
starts.clear();
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function fsFailure(e) {
|
|
194
|
+
const code = e instanceof Error ? Reflect.get(e, "code") : undefined;
|
|
195
|
+
return typeof code === "string" ? code : "unreadable";
|
|
196
|
+
}
|
|
197
|
+
export function createPackagedFloorBinaries(root = new URL("../../native/sandbox-read-floor/", import.meta.url)) {
|
|
198
|
+
let pinned;
|
|
199
|
+
const pins = () => {
|
|
200
|
+
if (pinned !== undefined)
|
|
201
|
+
return pinned;
|
|
202
|
+
let text;
|
|
203
|
+
try {
|
|
204
|
+
text = readFileSync(new URL("DIGESTS", root), "utf8");
|
|
205
|
+
}
|
|
206
|
+
catch (e) {
|
|
207
|
+
return (pinned = { why: `this package carries no supervisor digests (${fsFailure(e)})` });
|
|
208
|
+
}
|
|
209
|
+
const m = new Map();
|
|
210
|
+
for (const line of text.split("\n")) {
|
|
211
|
+
const hit = /^([0-9a-f]{64})\s+\*?(x86_64|aarch64)\/sema-floor\s*$/.exec(line.trim());
|
|
212
|
+
if (hit)
|
|
213
|
+
m.set(hit[2], hit[1]);
|
|
214
|
+
}
|
|
215
|
+
return (pinned = { pins: m });
|
|
216
|
+
};
|
|
217
|
+
return {
|
|
218
|
+
build(arch) {
|
|
219
|
+
const p = pins();
|
|
220
|
+
if ("why" in p)
|
|
221
|
+
return { ok: false, why: p.why };
|
|
222
|
+
const want = p.pins.get(arch);
|
|
223
|
+
if (want === undefined)
|
|
224
|
+
return { ok: false, why: `no supervisor build is pinned for ${arch}` };
|
|
225
|
+
let b;
|
|
226
|
+
try {
|
|
227
|
+
b = readFileSync(new URL(`${arch}/sema-floor`, root));
|
|
228
|
+
}
|
|
229
|
+
catch (e) {
|
|
230
|
+
return { ok: false, why: `the pinned ${arch} build is unreadable (${fsFailure(e)})` };
|
|
231
|
+
}
|
|
232
|
+
return createHash("sha256").update(b).digest("hex") === want ? { ok: true, digest: want, bytes: new Uint8Array(b) } : { ok: false, why: `the ${arch} build does not match its pinned digest` };
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function createBareFloor(lane, state) {
|
|
237
|
+
return {
|
|
238
|
+
lane,
|
|
239
|
+
state,
|
|
240
|
+
binaries: undefined,
|
|
241
|
+
wrap: () => ({ ok: true, value: BARE }),
|
|
242
|
+
release: () => { },
|
|
243
|
+
attach: async () => { },
|
|
244
|
+
refusePlacement: () => { },
|
|
245
|
+
dispose: () => { },
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
const okLaunch = (value) => ({ ok: true, value });
|
|
249
|
+
const spawnError = (message) => ({ ok: false, error: new ExecutionError("spawn_error", message) });
|
|
250
|
+
export function createKeptSandboxReadFloor(opts) {
|
|
251
|
+
const lane = opts.lane;
|
|
252
|
+
const plan = laneLaunchPlan(lane);
|
|
253
|
+
const judge = createFloorJudge();
|
|
254
|
+
const readyMs = opts.relayReadyMs ?? 15_000;
|
|
255
|
+
const maxRestarts = opts.relayRestarts ?? 3;
|
|
256
|
+
const backoffMs = opts.relayRestartBackoffMs ?? 1_000;
|
|
257
|
+
let phase = { kind: "idle" };
|
|
258
|
+
let generation = 0;
|
|
259
|
+
let attachBegun = false;
|
|
260
|
+
let verified;
|
|
261
|
+
let bin;
|
|
262
|
+
let as;
|
|
263
|
+
let transport;
|
|
264
|
+
let link;
|
|
265
|
+
let relayUp = false;
|
|
266
|
+
let restarting;
|
|
267
|
+
let placementRefused;
|
|
268
|
+
let disposed = false;
|
|
269
|
+
let broken = 0;
|
|
270
|
+
const say = (level, event, fields) => {
|
|
271
|
+
opts.logger?.[level]?.(event, { lane, ...fields });
|
|
272
|
+
};
|
|
273
|
+
const linkCall = (what, fn) => {
|
|
274
|
+
try {
|
|
275
|
+
fn();
|
|
276
|
+
}
|
|
277
|
+
catch (e) {
|
|
278
|
+
say("warn", "sandbox_read_floor_link_error", { what, error: echoCallerText(String(e), { maxChars: 200 }) });
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
const closeLink = () => {
|
|
282
|
+
const l = link;
|
|
283
|
+
link = undefined;
|
|
284
|
+
relayUp = false;
|
|
285
|
+
if (l !== undefined)
|
|
286
|
+
linkCall("close", () => l.close());
|
|
287
|
+
};
|
|
288
|
+
const openRelay = (gen, fresh) => new Promise((resolve) => {
|
|
289
|
+
if (transport === undefined || bin === undefined)
|
|
290
|
+
return resolve({ ok: false, reason: "relay_silent", detail: "the floor is not placed" });
|
|
291
|
+
let buffered = "";
|
|
292
|
+
let first = true;
|
|
293
|
+
let settled = false;
|
|
294
|
+
let dead = false;
|
|
295
|
+
let opened;
|
|
296
|
+
const pendingOut = [];
|
|
297
|
+
const settle = (v) => {
|
|
298
|
+
if (settled)
|
|
299
|
+
return;
|
|
300
|
+
settled = true;
|
|
301
|
+
clearTimeout(timer);
|
|
302
|
+
resolve(v);
|
|
303
|
+
};
|
|
304
|
+
const send = (line) => {
|
|
305
|
+
const o = opened;
|
|
306
|
+
if (o === undefined)
|
|
307
|
+
pendingOut.push(line);
|
|
308
|
+
else
|
|
309
|
+
linkCall("send", () => o.send(line));
|
|
310
|
+
};
|
|
311
|
+
const lost = (why) => {
|
|
312
|
+
if (dead)
|
|
313
|
+
return;
|
|
314
|
+
dead = true;
|
|
315
|
+
settle({ ok: false, reason: "relay_silent", detail: why });
|
|
316
|
+
const o = opened;
|
|
317
|
+
if (o !== undefined)
|
|
318
|
+
linkCall("close", () => o.close());
|
|
319
|
+
if (gen !== generation || o === undefined || link !== o)
|
|
320
|
+
return;
|
|
321
|
+
link = undefined;
|
|
322
|
+
const wasUp = relayUp;
|
|
323
|
+
relayUp = false;
|
|
324
|
+
if (!wasUp || disposed)
|
|
325
|
+
return;
|
|
326
|
+
say("warn", "sandbox_read_floor_relay_lost", { why });
|
|
327
|
+
void restartRelay(gen);
|
|
328
|
+
};
|
|
329
|
+
const timer = setTimeout(() => lost(`the relay sent no first frame within ${readyMs}ms`), readyMs);
|
|
330
|
+
timer.unref?.();
|
|
331
|
+
const events = {
|
|
332
|
+
data(chunk) {
|
|
333
|
+
if (dead || gen !== generation)
|
|
334
|
+
return;
|
|
335
|
+
buffered += chunk;
|
|
336
|
+
let nl;
|
|
337
|
+
while ((nl = buffered.indexOf("\n")) >= 0) {
|
|
338
|
+
const raw = buffered.slice(0, nl);
|
|
339
|
+
buffered = buffered.slice(nl + 1);
|
|
340
|
+
if (Buffer.byteLength(raw, "utf8") > MAX_RELAY_LINE_BYTES)
|
|
341
|
+
return lost(`the relay sent a frame over ${MAX_RELAY_LINE_BYTES} bytes`);
|
|
342
|
+
const line = raw.trim();
|
|
343
|
+
if (line === "")
|
|
344
|
+
continue;
|
|
345
|
+
const frame = parseRelayFrame(line);
|
|
346
|
+
if (first) {
|
|
347
|
+
first = false;
|
|
348
|
+
if (frame.t === "ready") {
|
|
349
|
+
relayUp = true;
|
|
350
|
+
settle({ ok: true });
|
|
351
|
+
}
|
|
352
|
+
else if (frame.t === "refused") {
|
|
353
|
+
settle({ ok: false, reason: REASON_SET.has(frame.why) ? frame.why : "binary_digest", detail: `the relay refused to serve: ${echoCallerText(frame.detail, { maxChars: 200 })}` });
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
settle({ ok: false, reason: "binary_digest", detail: "the relay's first frame is neither ready nor refused" });
|
|
357
|
+
}
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (frame.t === "broken") {
|
|
361
|
+
broken++;
|
|
362
|
+
say("warn", "sandbox_read_floor_frame_broken", { broken, refused: frame.count !== undefined, why: frame.why });
|
|
363
|
+
}
|
|
364
|
+
const out = judge.answer(frame);
|
|
365
|
+
if (out !== undefined)
|
|
366
|
+
send(`${JSON.stringify(out)}\n`);
|
|
367
|
+
}
|
|
368
|
+
if (Buffer.byteLength(buffered, "utf8") > MAX_RELAY_LINE_BYTES)
|
|
369
|
+
lost(`the relay sent more than ${MAX_RELAY_LINE_BYTES} bytes without a line end`);
|
|
370
|
+
},
|
|
371
|
+
closed(why) {
|
|
372
|
+
lost(`the relay closed (${echoCallerText(why, { maxChars: 200 })})`);
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
const o = transport.openRelay(plan.control, `exec ${shellLine(FLOOR_ARGV.serve(bin, plan.stateDir, fresh))}`, events);
|
|
376
|
+
opened = o;
|
|
377
|
+
if (dead)
|
|
378
|
+
return linkCall("close", () => o.close());
|
|
379
|
+
link = o;
|
|
380
|
+
for (const line of pendingOut.splice(0))
|
|
381
|
+
send(line);
|
|
382
|
+
});
|
|
383
|
+
const restartRelay = (gen) => (restarting ??= (async () => {
|
|
384
|
+
let last = { ok: false, reason: "relay_silent", detail: "no restart was attempted" };
|
|
385
|
+
try {
|
|
386
|
+
for (let i = 0; i < maxRestarts; i++) {
|
|
387
|
+
if (i > 0)
|
|
388
|
+
await new Promise((r) => setTimeout(r, backoffMs * 2 ** (i - 1)));
|
|
389
|
+
if (disposed || gen !== generation)
|
|
390
|
+
return;
|
|
391
|
+
last = await openRelay(gen, false);
|
|
392
|
+
if (last.ok) {
|
|
393
|
+
say("info", "sandbox_read_floor_relay_restarted", { attempt: i + 1 });
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (!last.ok)
|
|
398
|
+
fail(gen, last.reason, `${last.detail} (after ${maxRestarts} restart(s))`);
|
|
399
|
+
}
|
|
400
|
+
finally {
|
|
401
|
+
restarting = undefined;
|
|
402
|
+
}
|
|
403
|
+
})());
|
|
404
|
+
const fail = (gen, reason, detail) => {
|
|
405
|
+
if (gen !== generation || disposed)
|
|
406
|
+
return;
|
|
407
|
+
phase = { kind: "unavailable", reason, ...(detail !== undefined ? { detail } : {}) };
|
|
408
|
+
say("warn", "sandbox_read_floor_unavailable", { reason, ...(detail !== undefined ? { detail: detail.slice(0, 300) } : {}) });
|
|
409
|
+
};
|
|
410
|
+
const runLaunch = (startId) => ({ mode: "run", head: FLOOR_ARGV.run(bin, startId, as), identity: plan.start, startId });
|
|
411
|
+
return {
|
|
412
|
+
lane,
|
|
413
|
+
state: "kept",
|
|
414
|
+
binaries: opts.binaries,
|
|
415
|
+
wrap(start) {
|
|
416
|
+
switch (start.kind) {
|
|
417
|
+
case "bootstrap":
|
|
418
|
+
return phase.kind === "attaching" ? okLaunch({ mode: "control", identity: plan.control }) : spawnError(`read floor: a bootstrap start outside the floor's attach on this ${lane} sandbox is refused (an adapter defect)`);
|
|
419
|
+
case "adapter":
|
|
420
|
+
if (verified !== undefined)
|
|
421
|
+
return okLaunch({ mode: "plain", head: FLOOR_ARGV.plain(verified.bin, verified.as), identity: plan.start });
|
|
422
|
+
if (!attachBegun)
|
|
423
|
+
return okLaunch(BARE);
|
|
424
|
+
return spawnError(phase.kind === "unavailable"
|
|
425
|
+
? buildReadFloorUnavailableMessage(phase.reason, phase.detail, undefined)
|
|
426
|
+
: `read floor: an adapter command on this ${lane} sandbox while the floor's attach has not verified a supervisor — refused rather than run it outside the floor`);
|
|
427
|
+
case "under": {
|
|
428
|
+
if (phase.kind === "unavailable")
|
|
429
|
+
return spawnError(buildReadFloorUnavailableMessage(phase.reason, phase.detail, undefined));
|
|
430
|
+
if (bin === undefined || start.startId === undefined || !judge.launch(start.startId))
|
|
431
|
+
return spawnError(`read floor: the start this command reads on behalf of is no longer registered on this ${lane} sandbox — refused rather than read its output outside its floor`);
|
|
432
|
+
return okLaunch(runLaunch(start.startId));
|
|
433
|
+
}
|
|
434
|
+
case "engine": {
|
|
435
|
+
const floor = start.readFloor;
|
|
436
|
+
if (floor === undefined)
|
|
437
|
+
return spawnError(buildReadFloorMissingMessage(lane));
|
|
438
|
+
if (phase.kind === "unavailable")
|
|
439
|
+
return spawnError(buildReadFloorUnavailableMessage(phase.reason, phase.detail, floor));
|
|
440
|
+
if (phase.kind !== "ready" || bin === undefined)
|
|
441
|
+
return spawnError(buildReadFloorUnavailableMessage("relay_silent", "the floor is not attached to this sandbox yet", floor));
|
|
442
|
+
if (!relayUp && transport !== undefined)
|
|
443
|
+
void restartRelay(generation);
|
|
444
|
+
const id = judge.register(floor, start.hold === true);
|
|
445
|
+
return okLaunch(runLaunch(id));
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
release(startId) {
|
|
450
|
+
if (startId !== undefined)
|
|
451
|
+
judge.release(startId);
|
|
452
|
+
},
|
|
453
|
+
async attach(t, how) {
|
|
454
|
+
if (disposed)
|
|
455
|
+
return;
|
|
456
|
+
attachBegun = true;
|
|
457
|
+
const gen = ++generation;
|
|
458
|
+
closeLink();
|
|
459
|
+
judge.clear();
|
|
460
|
+
transport = t;
|
|
461
|
+
bin = undefined;
|
|
462
|
+
as = undefined;
|
|
463
|
+
phase = { kind: "attaching" };
|
|
464
|
+
if (placementRefused !== undefined)
|
|
465
|
+
return fail(gen, "binary_digest", placementRefused);
|
|
466
|
+
const u = await t.bootstrap("uname -m");
|
|
467
|
+
if (gen !== generation)
|
|
468
|
+
return;
|
|
469
|
+
if (!u.ok || u.exitCode !== 0)
|
|
470
|
+
return fail(gen, "relay_silent", `the sandbox did not answer the architecture probe (${u.ok ? `exit ${u.exitCode}` : echoCallerText(u.error, { maxChars: 200 })})`);
|
|
471
|
+
const arch = ARCH_OF_UNAME[u.stdout.trim()];
|
|
472
|
+
if (arch === undefined)
|
|
473
|
+
return fail(gen, "arch", `uname -m answered ${JSON.stringify(echoCallerText(u.stdout.trim(), { maxChars: 40 }))}`);
|
|
474
|
+
if (plan.dropToWorkspaceOwner) {
|
|
475
|
+
const o = await t.bootstrap(`stat -c %u -- ${shellQuote(how.workspaceRoot)}`);
|
|
476
|
+
if (gen !== generation)
|
|
477
|
+
return;
|
|
478
|
+
const uid = o.ok && o.exitCode === 0 ? o.stdout.trim() : "";
|
|
479
|
+
if (!/^\d{1,10}$/.test(uid))
|
|
480
|
+
return fail(gen, "relay_silent", `the sandbox did not answer the workspace owner probe for ${how.workspaceRoot}`);
|
|
481
|
+
if (uid === "0")
|
|
482
|
+
return fail(gen, "uid_zero", `${how.workspaceRoot} is owned by root`);
|
|
483
|
+
as = uid;
|
|
484
|
+
}
|
|
485
|
+
const build = opts.binaries.build(arch);
|
|
486
|
+
if (!build.ok)
|
|
487
|
+
return fail(gen, "binary_digest", `this server carries no usable supervisor build for ${arch} (${build.why})`);
|
|
488
|
+
const path = floorBinaryPath(arch);
|
|
489
|
+
const placed = await t.place(path, build.bytes);
|
|
490
|
+
if (gen !== generation)
|
|
491
|
+
return;
|
|
492
|
+
if (!placed.ok)
|
|
493
|
+
return fail(gen, "binary_digest", `placing the supervisor failed: ${echoCallerText(placed.error, { maxChars: 200 })}`);
|
|
494
|
+
bin = path;
|
|
495
|
+
const relay = await openRelay(gen, how.origin === "created");
|
|
496
|
+
if (gen !== generation)
|
|
497
|
+
return;
|
|
498
|
+
if (!relay.ok) {
|
|
499
|
+
bin = undefined;
|
|
500
|
+
return fail(gen, relay.reason, relay.detail);
|
|
501
|
+
}
|
|
502
|
+
const sc = await t.bootstrap(shellLine(FLOOR_ARGV.selfcheck(path, build.digest, as)));
|
|
503
|
+
if (gen !== generation)
|
|
504
|
+
return;
|
|
505
|
+
if (!sc.ok) {
|
|
506
|
+
bin = undefined;
|
|
507
|
+
return fail(gen, "relay_silent", `the selfcheck did not run (${echoCallerText(sc.error, { maxChars: 200 })})`);
|
|
508
|
+
}
|
|
509
|
+
const verdict = parseSelfcheck(sc.stdout, sc.exitCode);
|
|
510
|
+
if (!verdict.ok || verdict.arch !== arch) {
|
|
511
|
+
bin = undefined;
|
|
512
|
+
closeLink();
|
|
513
|
+
return verdict.ok ? fail(gen, "arch", `the supervisor reports ${verdict.arch}, the sandbox answered ${arch}`) : fail(gen, verdict.reason, verdict.detail);
|
|
514
|
+
}
|
|
515
|
+
phase = { kind: "ready" };
|
|
516
|
+
verified = { bin: path, as };
|
|
517
|
+
say("info", "sandbox_read_floor_attached", { arch, origin: how.origin, version: verdict.version });
|
|
518
|
+
},
|
|
519
|
+
refusePlacement(detail) {
|
|
520
|
+
placementRefused = detail;
|
|
521
|
+
},
|
|
522
|
+
dispose() {
|
|
523
|
+
disposed = true;
|
|
524
|
+
generation++;
|
|
525
|
+
closeLink();
|
|
526
|
+
judge.clear();
|
|
527
|
+
phase = { kind: "idle" };
|
|
528
|
+
},
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
export function createSandboxReadFloor(opts) {
|
|
532
|
+
const state = sandboxReadFloorState(opts.lane, opts.knob);
|
|
533
|
+
switch (state) {
|
|
534
|
+
case "kept":
|
|
535
|
+
return createKeptSandboxReadFloor({ lane: opts.lane, binaries: createPackagedFloorBinaries(), ...(opts.logger !== undefined ? { logger: opts.logger } : {}) });
|
|
536
|
+
case "off":
|
|
537
|
+
case "unserved":
|
|
538
|
+
return createBareFloor(opts.lane, state);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
export function floorArgv(launch, shellArgv) {
|
|
542
|
+
switch (launch.mode) {
|
|
543
|
+
case "bare":
|
|
544
|
+
case "control":
|
|
545
|
+
return shellArgv;
|
|
546
|
+
case "run":
|
|
547
|
+
case "plain":
|
|
548
|
+
return [...launch.head, "--", ...shellArgv];
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
export function e2bFloorCommand(launch, command, envs) {
|
|
552
|
+
switch (launch.mode) {
|
|
553
|
+
case "bare":
|
|
554
|
+
return { cmd: command, ...(envs !== undefined ? { envs: { ...envs } } : {}) };
|
|
555
|
+
case "control":
|
|
556
|
+
return { cmd: command, ...e2bUser(launch.identity) };
|
|
557
|
+
case "run":
|
|
558
|
+
return { cmd: `exec ${shellLine([...launch.head, ...e2bEnvFlags(envs), "--", "/bin/bash", "-l", "-c", command])}`, ...e2bUser(launch.identity) };
|
|
559
|
+
case "plain":
|
|
560
|
+
return { cmd: `exec ${shellLine([...launch.head, ...e2bEnvFlags(envs), "--", "/bin/bash", "-c", command])}`, ...e2bUser(launch.identity) };
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
const e2bUser = (identity) => {
|
|
564
|
+
switch (identity) {
|
|
565
|
+
case "root":
|
|
566
|
+
return { user: "root" };
|
|
567
|
+
case "exec-user":
|
|
568
|
+
return {};
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
const e2bEnvFlags = (envs) => Object.entries(envs ?? {}).flatMap(([k, v]) => ["--env", `${k}=${v}`]);
|
|
572
|
+
export function dockerIdentityFlags(launch) {
|
|
573
|
+
switch (launch.mode) {
|
|
574
|
+
case "bare":
|
|
575
|
+
return [];
|
|
576
|
+
case "control":
|
|
577
|
+
case "run":
|
|
578
|
+
case "plain":
|
|
579
|
+
switch (launch.identity) {
|
|
580
|
+
case "root":
|
|
581
|
+
return ["-u", "0"];
|
|
582
|
+
case "exec-user":
|
|
583
|
+
return [];
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
const ARCHES = ["x86_64", "aarch64"];
|
|
588
|
+
export const FLOOR_K8S_VOLUME_NAMES = Object.freeze({ x86_64: ADAPTER_VOLUME_NAMES.floorX86_64, aarch64: ADAPTER_VOLUME_NAMES.floorAarch64 });
|
|
589
|
+
export const FLOOR_K8S_CONFIGMAP_KEY = "sema-floor";
|
|
590
|
+
export function buildFloorPodSeat(floor) {
|
|
591
|
+
const binaries = floor.binaries;
|
|
592
|
+
if (floor.state !== "kept" || floor.lane !== "k8s" || binaries === undefined)
|
|
593
|
+
return undefined;
|
|
594
|
+
const configMaps = [];
|
|
595
|
+
const volumes = [];
|
|
596
|
+
const mounts = [];
|
|
597
|
+
for (const arch of ARCHES) {
|
|
598
|
+
const build = binaries.build(arch);
|
|
599
|
+
if (!build.ok)
|
|
600
|
+
continue;
|
|
601
|
+
const name = `sema-floor-${arch === "x86_64" ? "x86-64" : arch}-${build.digest.slice(0, 16)}`;
|
|
602
|
+
configMaps.push({ name, arch, binaryDataB64: Buffer.from(build.bytes).toString("base64") });
|
|
603
|
+
volumes.push({ name: FLOOR_K8S_VOLUME_NAMES[arch], configMap: { name, items: [{ key: FLOOR_K8S_CONFIGMAP_KEY, path: "sema-floor" }], defaultMode: 0o555 } });
|
|
604
|
+
mounts.push({ name: FLOOR_K8S_VOLUME_NAMES[arch], mountPath: `${FLOOR_DIR}/${arch}`, readOnly: true });
|
|
605
|
+
}
|
|
606
|
+
return { configMaps, volumes, mounts };
|
|
607
|
+
}
|
|
608
|
+
//# sourceMappingURL=sandbox-read-floor.js.map
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
*/
|
|
51
51
|
import type { Pool as MysqlPool } from "mysql2/promise";
|
|
52
52
|
import type { Pool as PgPool } from "pg";
|
|
53
|
-
import {
|
|
53
|
+
import type { SqlDialect } from "./sql-driver.js";
|
|
54
54
|
import { type SchemaProbeRead } from "./schema-live-columns.js";
|
|
55
55
|
import { StoreBootRefusalError } from "./store-contracts.js";
|
|
56
56
|
/** 一列:名字 + 它在 `CREATE TABLE` 里的**原样定义文本**(生成 ADD COLUMN 指路用,不另手写)。 */
|
|
@@ -72,7 +72,7 @@ export interface ParsedTable {
|
|
|
72
72
|
*/
|
|
73
73
|
export declare function parseCreateTable(stmt: string): ParsedTable | undefined;
|
|
74
74
|
/** 启动时真会建出来的**全部**表形(录制 → 解析)。同名表二次出现以**先到**为准(`IF NOT EXISTS` 的真语义)。 */
|
|
75
|
-
export declare function expectedTableShapes(dialect:
|
|
75
|
+
export declare function expectedTableShapes(dialect: SqlDialect): Promise<Map<string, ParsedTable>>;
|
|
76
76
|
/** 拒启码(机读)。附录 A / 拒启码表的登记名。 */
|
|
77
77
|
export declare const SCHEMA_MISMATCH_ERROR_CODE = "config.schema_mismatch";
|
|
78
78
|
/** 一张表的对账结论。 */
|
|
@@ -96,13 +96,13 @@ export declare function diffSchema(expected: Map<string, ParsedTable>, live: Map
|
|
|
96
96
|
*/
|
|
97
97
|
export declare function describeMismatchedTables(mismatches: readonly TableMismatch[]): string[];
|
|
98
98
|
/** 拒启文案(表头 + 逐表段与闭集三条出路 {@link describeMismatchedTables})。 */
|
|
99
|
-
export declare function describeSchemaMismatch(dialect:
|
|
99
|
+
export declare function describeSchemaMismatch(dialect: SqlDialect, mismatches: readonly TableMismatch[]): string;
|
|
100
100
|
/**
|
|
101
101
|
* 启动期对账:**建表之后**跑,不符即抛(= 拒启)。两方言 + InnoDB 同腿(MySQL 协议共用 `mysql` 臂)。
|
|
102
102
|
*
|
|
103
103
|
* 失败方向见文件头注:读抛 / 整库零行 ⇒ 拒。
|
|
104
104
|
*/
|
|
105
|
-
export declare function assertSchemaMatchesCode(read: SchemaProbeRead, dialect:
|
|
105
|
+
export declare function assertSchemaMatchesCode(read: SchemaProbeRead, dialect: SqlDialect): Promise<void>;
|
|
106
106
|
/**
|
|
107
107
|
* 跳版升级的库的拒启:退役 schema(快照索引表 / 单座列)任一仍在。**具名类型**:锚类型不锚文案(日志消费端与测试按 `code` / 类型分派);
|
|
108
108
|
* 码取列册对账同一个 `config.schema_mismatch`,运维面一个拒启码一个读法。拒句点名在场的坐标、说明第三条(旧拼法改写)同样没跑过、给出路。
|
|
@@ -119,10 +119,10 @@ export declare class RetiredSchemaPresentError extends StoreBootRefusalError {
|
|
|
119
119
|
* 快照索引表 / 单座列任一在场 ⇒ 抛 {@link RetiredSchemaPresentError}。新库(建表之前一列都没有)⇒ 不在场、过。读抛 ⇒ 原样上抛(拒启;不是确定的拒启表态,
|
|
120
120
|
* 开库等待窗照常重试 —— 与修前两条目录探针抛出时同一个去向)。
|
|
121
121
|
*/
|
|
122
|
-
export declare function assertRetiredSchemaAbsent(read: SchemaProbeRead, dialect:
|
|
122
|
+
export declare function assertRetiredSchemaAbsent(read: SchemaProbeRead, dialect: SqlDialect): Promise<void>;
|
|
123
123
|
/** 探针口的池参数:方言与它的池一起走(判别联合 ⇒ 不靠调用方断言「这个池是哪种」)。 */
|
|
124
124
|
export type RetiredSchemaProbeTarget = {
|
|
125
|
-
readonly dialect: "
|
|
125
|
+
readonly dialect: "mysql";
|
|
126
126
|
readonly pool: MysqlPool;
|
|
127
127
|
} | {
|
|
128
128
|
readonly dialect: "pg";
|
|
@@ -199,7 +199,7 @@ export async function assertRetiredSchemaAbsent(read, dialect) {
|
|
|
199
199
|
throw new RetiredSchemaPresentError(found);
|
|
200
200
|
}
|
|
201
201
|
export async function assertRetiredSchemaAbsentOnPool(target) {
|
|
202
|
-
if (target.dialect === "
|
|
202
|
+
if (target.dialect === "mysql") {
|
|
203
203
|
const conn = await target.pool.getConnection();
|
|
204
204
|
try {
|
|
205
205
|
await assertRetiredSchemaAbsent(async (sql) => (await conn.query(sql))[0], "mysql");
|