@sema-agent/server 7.142.0 → 7.144.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 +608 -0
- package/USAGE.md +63 -36
- package/dist/adoption/plan.js +22 -0
- package/dist/approval-ask-audit-store.d.ts +7 -5
- package/dist/approval-ask-audit-store.js +12 -6
- package/dist/approval-ask-machine.d.ts +15 -7
- package/dist/approval-ask-machine.js +10 -4
- package/dist/approval-card.d.ts +155 -39
- package/dist/approval-card.js +111 -31
- package/dist/approval-queue-kind.d.ts +27 -0
- package/dist/approval-queue-kind.js +6 -0
- package/dist/approval-reconciler.d.ts +8 -8
- package/dist/approval-reconciler.js +3 -3
- package/dist/audit.js +4 -2
- package/dist/boot/budget-tracing.d.ts +1 -1
- package/dist/boot/execution-env.js +1 -1
- package/dist/boot/governance-seams.d.ts +26 -41
- package/dist/boot/governance-seams.js +61 -28
- package/dist/boot/late-binds.d.ts +7 -4
- package/dist/boot/late-binds.js +3 -1
- package/dist/boot/leader.js +2 -4
- package/dist/boot/memory-consolidation.d.ts +0 -8
- package/dist/boot/memory-consolidation.js +3 -3
- package/dist/boot/reapers.js +5 -4
- package/dist/boot/resolve-spec.d.ts +1 -1
- package/dist/boot/resolve-spec.js +8 -8
- package/dist/boot/runner-deps.d.ts +14 -6
- package/dist/boot/runner-deps.js +6 -17
- package/dist/boot/shutdown.d.ts +9 -0
- package/dist/boot/shutdown.js +18 -2
- package/dist/boot/stage-01-config.js +2 -0
- package/dist/boot/stage-02-web-admin-pull.js +3 -0
- package/dist/boot/stage-03-store-backend.d.ts +4 -1
- package/dist/boot/stage-03-store-backend.js +1 -2
- package/dist/boot/stage-04-stores.d.ts +5 -2
- package/dist/boot/stage-04-stores.js +3 -5
- package/dist/boot/stage-05-execution-env.d.ts +5 -2
- package/dist/boot/stage-06-runners.d.ts +5 -2
- package/dist/boot/stage-06-runners.js +1 -3
- package/dist/boot/stage-07-capability-layer.d.ts +5 -2
- package/dist/boot/stage-07-capability-layer.js +6 -2
- package/dist/boot/stage-08-reapers.d.ts +5 -2
- 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 +4 -1
- package/dist/boot/stores.d.ts +5 -2
- package/dist/boot/stores.js +7 -7
- package/dist/boot/web-admin.d.ts +5 -1
- package/dist/boot/web-admin.js +31 -19
- package/dist/capabilities/hands-lane.d.ts +9 -39
- package/dist/capabilities/hands-lane.js +8 -13
- package/dist/capabilities/mcp-probe.d.ts +3 -2
- package/dist/capabilities/scenarios.d.ts +23 -17
- package/dist/capabilities/scenarios.js +2 -2
- package/dist/capabilities/skills.d.ts +1 -17
- package/dist/capabilities/skills.js +0 -8
- package/dist/capability-facts.d.ts +8 -8
- package/dist/capability-facts.js +4 -4
- package/dist/card-void.d.ts +6 -9
- package/dist/config-catalog.js +1 -1
- package/dist/config-d-migration.d.ts +2 -0
- package/dist/config-dir-trust.js +58 -29
- package/dist/config-invariants.d.ts +7 -1
- package/dist/config-invariants.js +7 -0
- package/dist/config-provider.d.ts +10 -0
- package/dist/config-provider.js +15 -4
- package/dist/config-root-migration.d.ts +13 -0
- package/dist/config-root-migration.js +99 -0
- package/dist/config-types.d.ts +5 -5
- package/dist/config.d.ts +4 -3
- package/dist/config.js +1 -1
- package/dist/delegated-session-owner.d.ts +35 -151
- package/dist/delegated-session-owner.js +1 -83
- package/dist/device-ws-protocol.d.ts +1 -0
- package/dist/device-ws-protocol.js +2 -1
- package/dist/entry-mode-clamp-notice.d.ts +1 -1
- package/dist/env-model-table-gate.d.ts +53 -0
- package/dist/env-model-table-gate.js +53 -0
- package/dist/execution-placement.d.ts +7 -4
- package/dist/fleet/fleet-bus.d.ts +28 -52
- package/dist/fleet/fleet-bus.js +35 -100
- package/dist/fleet/fleet-reconciler.d.ts +4 -4
- package/dist/fleet/fleet-reconciler.js +1 -1
- package/dist/fleet/subagent-tail-bus.d.ts +14 -11
- package/dist/fleet/subagent-tail-bus.js +15 -28
- package/dist/hooks/classic-stations.d.ts +3 -1
- package/dist/hooks/classic-stations.js +2 -0
- package/dist/hooks/hook-runner.js +28 -7
- package/dist/host-decision.d.ts +54 -20
- package/dist/host-decision.js +42 -14
- package/dist/http/admission.d.ts +1 -1
- package/dist/http/admission.js +7 -3
- package/dist/http/approval-sweeps.js +11 -8
- package/dist/http/caller-trust.d.ts +1 -1
- package/dist/http/durable-run-leg.js +2 -2
- package/dist/http/engine-refusal.d.ts +16 -0
- package/dist/http/engine-refusal.js +17 -0
- package/dist/http/memory-compliance-failure-reply.d.ts +1 -3
- package/dist/http/memory-compliance-failure-reply.js +4 -6
- package/dist/http/owner-access.d.ts +11 -0
- package/dist/http/owner-access.js +13 -0
- package/dist/http/principal-gate.d.ts +1 -1
- package/dist/http/refusal-body.d.ts +7 -3
- package/dist/http/refusal-body.js +1 -0
- package/dist/http/resume-legs.d.ts +21 -7
- package/dist/http/resume-legs.js +82 -53
- package/dist/http/resume-restatement.d.ts +39 -0
- package/dist/http/resume-restatement.js +25 -0
- package/dist/http/route-ctx.d.ts +35 -10
- package/dist/http/route-ctx.js +1 -1
- package/dist/http/routes/approvals-assistant.d.ts +9 -1
- package/dist/http/routes/approvals-assistant.js +143 -31
- package/dist/http/routes/capabilities.js +11 -2
- package/dist/http/routes/memory-policy.d.ts +2 -2
- package/dist/http/routes/memory-policy.js +1 -1
- package/dist/http/routes/notify-wake.js +23 -25
- package/dist/http/routes/rules.js +48 -13
- package/dist/http/routes/runs.js +9 -8
- package/dist/http/routes/session-memory-sync.d.ts +34 -6
- package/dist/http/routes/session-memory-sync.js +27 -10
- package/dist/http/routes/session-sync.js +13 -20
- package/dist/http/routes/sessions.d.ts +2 -2
- package/dist/http/routes/sessions.js +22 -23
- package/dist/http/routes/tasks.js +27 -49
- package/dist/http/server.d.ts +13 -7
- package/dist/http/server.js +2 -2
- package/dist/http/stored-body-replay.js +15 -4
- package/dist/http/wire-types.d.ts +17 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lane-home-probe.d.ts +15 -0
- package/dist/lane-home-probe.js +4 -0
- package/dist/leader/leader.js +5 -2
- package/dist/leader/wire.d.ts +7 -1
- package/dist/leader/wire.js +6 -5
- package/dist/leg-facts.d.ts +37 -0
- package/dist/leg-facts.js +15 -0
- package/dist/memory-bundle-engine.d.ts +1 -4
- package/dist/memory-bundle-engine.js +2 -2
- package/dist/memory-operator-faces.d.ts +30 -66
- package/dist/memory-operator-faces.js +17 -88
- package/dist/memory-scope-stores.d.ts +38 -40
- package/dist/memory-scope-stores.js +51 -36
- package/dist/memory-scope.d.ts +0 -9
- package/dist/memory-scope.js +0 -9
- package/dist/memory-sync-client.js +14 -3
- package/dist/memory-sync.d.ts +20 -4
- package/dist/memory-sync.js +23 -4
- package/dist/model-plane-face.d.ts +13 -7
- package/dist/model-plane-face.js +2 -1
- package/dist/observability/corrupt-read-seat.d.ts +5 -9
- package/dist/observability/corrupt-read-seat.js +1 -2
- package/dist/observability/fail-open.d.ts +30 -10
- package/dist/observability/fail-open.js +27 -10
- package/dist/observability/run-terminal.d.ts +1 -1
- package/dist/observability/run-terminal.js +6 -2
- package/dist/orchestration/workflow-completion-inbox.d.ts +5 -19
- package/dist/orchestration/workflow-completion-inbox.js +0 -12
- package/dist/orchestration/workflow-notify-journal.d.ts +3 -2
- package/dist/orchestration/workflow-park-decision.d.ts +8 -3
- package/dist/orchestration/workflow-park-decision.js +5 -3
- package/dist/org-delivery-gate.d.ts +11 -0
- package/dist/org-delivery-gate.js +19 -1
- package/dist/parked-decide.d.ts +13 -9
- package/dist/parked-decide.js +7 -7
- package/dist/parked-revive-surface.d.ts +6 -7
- package/dist/parked-revive-surface.js +1 -1
- package/dist/peer-inbound-face.d.ts +122 -0
- package/dist/peer-inbound-face.js +123 -0
- package/dist/permission-rule-list.d.ts +13 -9
- package/dist/permission-rule-list.js +7 -3
- package/dist/plugins/approval-ask-store-file.js +18 -6
- package/dist/plugins/approval-ask-store-memory.js +8 -6
- package/dist/plugins/approval-ask-store-sql.d.ts +28 -13
- package/dist/plugins/approval-ask-store-sql.js +30 -11
- package/dist/plugins/caching-session-store.d.ts +0 -3
- package/dist/plugins/caching-session-store.js +0 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +45 -38
- package/dist/plugins/checkpoint-store-sql.js +108 -45
- package/dist/plugins/file-history-store-sql.d.ts +24 -18
- package/dist/plugins/file-history-store-sql.js +24 -19
- package/dist/plugins/file-run-store.d.ts +29 -0
- package/dist/plugins/file-run-store.js +97 -8
- package/dist/plugins/fork-routing-session-store.d.ts +8 -1
- package/dist/plugins/fork-routing-session-store.js +6 -0
- package/dist/plugins/imported-layer-root-retirement.d.ts +82 -0
- package/dist/plugins/imported-layer-root-retirement.js +96 -0
- package/dist/plugins/local-checkpoint-store.d.ts +6 -8
- package/dist/plugins/local-checkpoint-store.js +5 -4
- package/dist/plugins/local-session-store.d.ts +69 -30
- package/dist/plugins/local-session-store.js +133 -57
- package/dist/plugins/local-session-window.d.ts +4 -2
- package/dist/plugins/mailbox-recipient-lifecycle.d.ts +9 -31
- package/dist/plugins/mailbox-recipient-lifecycle.js +4 -0
- package/dist/plugins/mailbox-store-sql.d.ts +12 -8
- package/dist/plugins/mailbox-store-sql.js +76 -7
- package/dist/plugins/memory-sync-store-pg.d.ts +15 -0
- package/dist/plugins/memory-sync-store-pg.js +21 -0
- package/dist/plugins/memory-sync-store-tidb.d.ts +2 -0
- package/dist/plugins/memory-sync-store-tidb.js +13 -1
- package/dist/plugins/one-time-migrations.d.ts +44 -14
- package/dist/plugins/one-time-migrations.js +118 -15
- package/dist/plugins/org-delivery-quota-store.d.ts +1 -1
- package/dist/plugins/org-delivery-quota-store.js +2 -12
- package/dist/plugins/park-commit-watch.d.ts +1 -1
- package/dist/plugins/permission-rule-store-file.d.ts +10 -1
- package/dist/plugins/permission-rule-store-file.js +15 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +31 -1
- package/dist/plugins/permission-rule-store-sql.js +35 -2
- package/dist/plugins/pg-session-storage.d.ts +1 -9
- package/dist/plugins/pg-session-storage.js +2 -19
- package/dist/plugins/remote-env-device.d.ts +8 -11
- package/dist/plugins/remote-env-device.js +6 -3
- package/dist/plugins/remote-env-host.d.ts +8 -4
- package/dist/plugins/remote-env-host.js +7 -9
- package/dist/plugins/remote-env-k8s.d.ts +37 -24
- package/dist/plugins/remote-env-k8s.js +50 -17
- package/dist/plugins/remote-shell.js +12 -2
- package/dist/plugins/retention-store-sql.d.ts +2 -2
- package/dist/plugins/sandbox-read-floor.d.ts +5 -7
- package/dist/plugins/sandbox-read-floor.js +2 -7
- package/dist/plugins/schema-consistency.d.ts +5 -1
- package/dist/plugins/session-fork.d.ts +1 -1
- package/dist/plugins/session-ownership.d.ts +9 -0
- package/dist/plugins/session-ownership.js +6 -0
- package/dist/plugins/session-store.d.ts +13 -16
- package/dist/plugins/session-store.js +9 -10
- package/dist/plugins/sql-errors.d.ts +10 -1
- package/dist/plugins/sql-errors.js +13 -0
- package/dist/plugins/sql-json-column.d.ts +24 -2
- package/dist/plugins/sql-json-column.js +38 -3
- package/dist/plugins/store-backend.d.ts +23 -28
- package/dist/plugins/store-backend.js +12 -3
- package/dist/plugins/store-contracts.d.ts +29 -5
- package/dist/plugins/store-contracts.js +4 -1
- package/dist/plugins/task-attachment-store.d.ts +2 -2
- package/dist/plugins/task-attachment-store.js +37 -52
- package/dist/plugins/tidb-session-store.d.ts +2 -14
- package/dist/plugins/tidb-session-store.js +2 -20
- package/dist/plugins/tool-result-store-sql.d.ts +9 -8
- package/dist/plugins/tool-result-store-sql.js +23 -2
- package/dist/plugins/wiring-section-codec.d.ts +32 -0
- package/dist/plugins/wiring-section-codec.js +55 -0
- package/dist/plugins/workspace-fill.d.ts +1 -1
- package/dist/rule-entry-list.d.ts +3 -2
- package/dist/rule-entry-list.js +5 -1
- package/dist/rules-consent.d.ts +28 -5
- package/dist/rules-consent.js +40 -28
- package/dist/run-local.js +11 -6
- package/dist/runs.d.ts +12 -8
- package/dist/runs.js +10 -7
- package/dist/runtime-governance.d.ts +1 -1
- package/dist/security.d.ts +0 -4
- package/dist/server-secret-env.d.ts +4 -0
- package/dist/session-error-wire.d.ts +34 -23
- package/dist/session-error-wire.js +8 -3
- package/dist/session-incarnation.d.ts +3 -3
- package/dist/session-notify-door.d.ts +106 -0
- package/dist/session-notify-door.js +97 -0
- package/dist/session-sync.d.ts +9 -14
- package/dist/session-sync.js +5 -5
- package/dist/session-workspace.d.ts +3 -13
- package/dist/session-workspace.js +0 -9
- package/dist/standing-answer-grant.d.ts +5 -0
- package/dist/standing-answer-grant.js +10 -0
- package/dist/stores-sql/session-policy-store-sql.d.ts +4 -2
- package/dist/stores-sql/session-policy-store-sql.js +3 -13
- package/dist/task-mcp.d.ts +1 -1
- package/dist/task-settings.d.ts +11 -8
- package/dist/task-settings.js +31 -11
- package/dist/tool-approval.d.ts +55 -35
- package/dist/tool-approval.js +112 -74
- package/dist/trace/core-keyset-guard.d.ts +12 -9
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +4 -2
- package/dist/trace/exit-seat.d.ts +7 -1
- package/dist/trace/exit-seat.js +1 -0
- package/dist/trace/ledger-sink.d.ts +2 -2
- package/dist/trace/ledger-sink.js +12 -4
- package/dist/trace/model-segment.d.ts +74 -25
- package/dist/trace/model-segment.js +141 -32
- package/dist/trace/project.d.ts +15 -6
- package/dist/trace/project.js +12 -4
- package/dist/trace/redact.d.ts +72 -9
- package/dist/trace/redact.js +1509 -471
- package/dist/trace/self-minted-wire.d.ts +598 -47
- package/dist/trace/self-minted-wire.js +190 -20
- package/dist/trace/wire-projection-faces.d.ts +1 -1
- package/dist/trace/wire-projection-faces.js +1 -1
- package/dist/verifier-pause-card.d.ts +33 -5
- package/dist/verifier-pause-card.js +15 -10
- package/dist/web-admin-client/apply-effective.d.ts +12 -16
- package/dist/web-admin-client/apply-effective.js +90 -10
- package/dist/workspace-space.d.ts +19 -3
- package/dist/workspace-space.js +8 -4
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -1
- package/package.json +3 -2
- package/wire-changes/server@7.143.0.json +1167 -0
- package/wire-changes/server@7.144.0.json +291 -0
- package/dist/session-records-skipped-notice.d.ts +0 -21
- package/dist/session-records-skipped-notice.js +0 -27
|
@@ -314,21 +314,18 @@ export declare class RemoteDeviceExecutionEnv implements RemoteExecutionEnv {
|
|
|
314
314
|
* 🔴 **工厂认设备不认住客**(读口一只 = 会话店执行席 `seat.read(root)`,与解析器 / 换绑门 / 执行放置读面 / 准入链同一只读):按 placement 根读**当前**格,
|
|
315
315
|
* 与这条腿带来的设备句柄 `honor` 比,不同即 typed 拒。判序(一次行读):
|
|
316
316
|
* root = ctx.placementRootSessionId // 空 ⇒ config.placement_root_invalid
|
|
317
|
-
* honor = ctx.
|
|
317
|
+
* honor = ctx.placementTarget?.deviceId // 引擎交来的放置围栏(这条腿必须落在哪台设备上)
|
|
318
318
|
* cell = seat.read(root).cell // 行不在 / 格缺 / mirror / writer(device, ∅) ⇒ device.not_bound(唯一读法 `boundDeviceOf`)
|
|
319
319
|
* honor ≠ undefined ∧ 格上设备 ≠ honor ⇒ device.identity_mismatch(句点名两台,零连接)
|
|
320
320
|
* readDeviceForAdmission(格上设备, 格上属主)
|
|
321
321
|
* 工厂**不**读 `task_run`、不读子代分区行、不按 `ctx.taskId`、不比化身、不比属主(住客谓词只用于写与 judged 复核,不是工厂的读者)。
|
|
322
|
-
*
|
|
323
|
-
* ⇒
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
* (修在源头)。敞口(在那个字段到货之前成立):活过父 turn 的后台子代,在根换绑之后新派的腿会落到根当前设备 —— 换绑半边由换绑门的子代活性判关闭(归换绑门那一车),
|
|
330
|
-
* 删除半边另有票。字段到货后的形:`honor = ctx.resumeWorkspace?.deviceId ?? <放置句柄>.deviceId`,放置句柄的 provider 不是本车道 ⇒ 装配拒(`config.placement_root_invalid` 同级)。
|
|
331
|
-
* 本仓任何文本不得宣称工厂「一律按住客定址」。
|
|
322
|
+
* 围栏(`ExecutionEnvFactoryContext.placementTarget`,引擎每条腿一个答案):派生它的那条腿的环境报过的目标 —— 委派子代 / 工作流 agent / 被唤醒的
|
|
323
|
+
* 保留 agent 带唤醒腿的目标,级联后续阶 / 验证腿带第一条腿的目标,续跑腿带检查点行记下的目标。围栏点名了设备 ⇒ 本工厂**只**在那台设备上铸 ——
|
|
324
|
+
* 根此刻绑的不是它(换绑过)⇒ `device.identity_mismatch`(换绑后赎回的同一个词,零新码);不连另一台、不改道(新设备没有那个工作区)。
|
|
325
|
+
* 「绑的就是它但现在不在线」与新跑同形:构造成功、`connect()` 回 typed `device.offline`(从不连别的设备)。围栏缺席(顶层首腿,或派生它的腿
|
|
326
|
+
* 不是远端环境)或不点名设备 ⇒ 普通路由,按根读当前格。围栏的 `provider` 不进判序(引擎的回执也只比设备)。
|
|
327
|
+
* 句柄(`workspaceHandle()`)恒报连接真到达的设备,不抄围栏;引擎拿这份报告复核每条带围栏的腿(不符 ⇒ 续跑腿 `resume.env_failed`、
|
|
328
|
+
* 其余腿 `config.placement_not_honored`,都在腿跑之前)—— 本工厂在连接之前就拒,那两个词只在报告与围栏真不一致时出现。
|
|
332
329
|
*/
|
|
333
330
|
export declare function deviceExecutionEnvFactory(deps: DeviceEnvFactoryDeps): ExecutionEnvFactory;
|
|
334
331
|
/**
|
|
@@ -17,6 +17,7 @@ export const DEVICE_OUTCOME_TO_FILE_ERROR = {
|
|
|
17
17
|
is_directory: "is_directory",
|
|
18
18
|
liveness_no_output: "unknown",
|
|
19
19
|
name_too_long: "name_too_long",
|
|
20
|
+
symlink_loop: "symlink_loop",
|
|
20
21
|
not_directory: "not_directory",
|
|
21
22
|
not_found: "not_found",
|
|
22
23
|
not_supported: "not_supported",
|
|
@@ -40,6 +41,7 @@ export const DEVICE_OUTCOME_TO_EXEC_ERROR = {
|
|
|
40
41
|
is_directory: "unknown",
|
|
41
42
|
liveness_no_output: "liveness_no_output",
|
|
42
43
|
name_too_long: "unknown",
|
|
44
|
+
symlink_loop: "unknown",
|
|
43
45
|
not_directory: "unknown",
|
|
44
46
|
not_found: "unknown",
|
|
45
47
|
not_supported: "shell_unavailable",
|
|
@@ -771,6 +773,7 @@ const OUTCOME_TO_REMOTE_ERROR = {
|
|
|
771
773
|
is_directory: "unknown",
|
|
772
774
|
liveness_no_output: "liveness_no_output",
|
|
773
775
|
name_too_long: "unknown",
|
|
776
|
+
symlink_loop: "unknown",
|
|
774
777
|
not_directory: "unknown",
|
|
775
778
|
not_found: "unknown",
|
|
776
779
|
not_supported: "unsupported",
|
|
@@ -806,10 +809,10 @@ export function deviceExecutionEnvFactory(deps) {
|
|
|
806
809
|
throw deviceFactoryRefusal(DEVICE_ERROR_CODES.NOT_BOUND, `device lane: no device binding for placement root ${rootSessionId} [${DEVICE_ERROR_CODES.NOT_BOUND}] — ` +
|
|
807
810
|
"the submit-time admission chain must bind the root session to a device before a run reaches the factory (v2 §4.3.2)");
|
|
808
811
|
}
|
|
809
|
-
const honor = ctx.
|
|
812
|
+
const honor = ctx.placementTarget?.deviceId;
|
|
810
813
|
if (honor !== undefined && honor !== binding.id) {
|
|
811
|
-
throw deviceFactoryRefusal(DEVICE_ERROR_CODES.IDENTITY_MISMATCH, `device lane: this
|
|
812
|
-
"
|
|
814
|
+
throw deviceFactoryRefusal(DEVICE_ERROR_CODES.IDENTITY_MISMATCH, `device lane: this leg is placed on device ${honor} (the device the work it continues ran on), but placement root ${rootSessionId} is now bound to ${binding.id} [${DEVICE_ERROR_CODES.IDENTITY_MISMATCH}] — ` +
|
|
815
|
+
"work already under way does not follow a rebind (the new device has no copy of that workspace); finish or cancel the running or parked task, then start it again on the device now bound");
|
|
813
816
|
}
|
|
814
817
|
const view = await deps.store.readDeviceForAdmission(binding.id, binding.owner);
|
|
815
818
|
if (!view) {
|
|
@@ -71,11 +71,8 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
|
|
|
71
71
|
cwd: string;
|
|
72
72
|
/** core `ExecutionEnv.homeDir`([ref]):本机执行用户的 home —— 主张在场 ⇒ 主张(构造时与子进程拿到的 `HOME` 对证过),否则构造时读到的那个 `HOME`,见 {@link LaneHomeSeat}。 */
|
|
73
73
|
get homeDir(): string | undefined;
|
|
74
|
-
/** 家目录的座(主张在场 ⇒
|
|
74
|
+
/** 家目录的座(主张在场 ⇒ 构造时对证,证不了 ⇒ 构造抛、环境不铸 · 否则构造时实测)。读数 = {@link childHome}:本类自己组合给子进程的那份 env,不 spawn。 */
|
|
75
75
|
private readonly homeSeat;
|
|
76
|
-
/** 家目录:主张与子进程拿到的 `HOME` 对不上 ⇒ 这只环境的拒句;`shellReady` / `fsReady` / `connect` 三道门原样回它,一条命令、一次文件动作都不做。
|
|
77
|
-
* 读数是本类自己组合的 env(纯函数,同一份输入同一个答案)⇒ 构造时判一次、记一辈子。 */
|
|
78
|
-
private readonly homeRefusal;
|
|
79
76
|
/**
|
|
80
77
|
* core 7.49.0 [ref]:shell 子进程**真拿到的** `CDPATH` 条目(`[]` = 没有)—— 删除守卫按它解裸 `cd <dir>`。本车道的命令就是本机子进程,
|
|
81
78
|
* env 是本类自己组合的(继承姿态 + `cfg.env`),所以说得出:读同一只组合({@link childEnv} 的**读面**,不披露、不钉 shell),切法与 core
|
|
@@ -84,6 +81,13 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
|
|
|
84
81
|
* {@link hostShell}(win32 上未落定同步抛 —— 修前每条 Bash 的策略链都因此被拒)。
|
|
85
82
|
*/
|
|
86
83
|
get cdPath(): readonly string[];
|
|
84
|
+
/**
|
|
85
|
+
* core 7.62.0 [ref]([ref]):shell 子进程**收到的**环境变量名(从不带值)—— 引擎判「git 会不会读一只文本没绑的 `GIT_*`」的唯一来源(声明且没有 `GIT_*`
|
|
86
|
+
* ⇒ `git show HEAD:<path>` 的对象路径只在仓库顶判;缺席 ⇒ 每层祖先照判)。与 {@link cdPath} 同一只组合的读面(继承姿态 + `cfg.env`,不 spawn、不记剥除、不碰 shell);
|
|
87
|
+
* 逐命令的 `options.env` 由引擎自己交,它知道(core 残余:逐命令 env 加一个名)。win32 spawn 层钉的 `SHELL` 不在这一读里(不是 `GIT_*`,不改这一判)。
|
|
88
|
+
* 只有本车道声明:远端车道的 shell 跑在对方自己构建的环境里,适配器说不出(缺席)。
|
|
89
|
+
*/
|
|
90
|
+
get receivedEnvNames(): readonly string[];
|
|
87
91
|
/**
|
|
88
92
|
* core 7.56.0 [ref]:shell 子进程继承的环境里**收不到**的条目(名 + 规则,从不带值)—— 与 {@link cdPath} 同一只组合的读面
|
|
89
93
|
* (`server-secret-env.ts` 的 `withheldFromHostSubprocess`:继承组合扣下的发现,减去 `cfg.env` 这条显式授予重供的键)。逐命令的 `options.env`
|
|
@@ -13,7 +13,7 @@ import { IS_WIN32, resolveHostShell, hostShell, pinHostShell, spawnGroupOptions,
|
|
|
13
13
|
import { BackgroundShellManager, seedMemStream, feedMemStream, drainMemStream } from "./background-shell-support.js";
|
|
14
14
|
import { FileError, NodeExecutionEnv, inheritEnvPolicyProblem, ExecutionError, RemoteExecutionError, RollingTailBuffer, walkSymlinkChain, markTruncated, SchedulerError, BackgroundShellError, } from "@sema-agent/core";
|
|
15
15
|
import { capabilitiesFor } from "../execution-lane-caps.js";
|
|
16
|
-
import { LaneHomeSeat } from "../lane-home-probe.js";
|
|
16
|
+
import { LaneHomeSeat, laneHomeRefusal } from "../lane-home-probe.js";
|
|
17
17
|
import { composeHostSubprocessEnv, hostSubprocessEnv, withheldFromHostSubprocess } from "../server-secret-env.js";
|
|
18
18
|
import { nameTooLongCode } from "./remote-env-file-error.js";
|
|
19
19
|
import { trackEphemeralHostWorkspace, untrackEphemeralHostWorkspace } from "./host-workspace-registry.js";
|
|
@@ -64,11 +64,13 @@ export class RemoteHostExecutionEnv {
|
|
|
64
64
|
return this.homeSeat.home;
|
|
65
65
|
}
|
|
66
66
|
homeSeat;
|
|
67
|
-
homeRefusal;
|
|
68
67
|
get cdPath() {
|
|
69
68
|
const value = this.childEnv(undefined, undefined).CDPATH;
|
|
70
69
|
return value === undefined || value === "" ? [] : value.split(":");
|
|
71
70
|
}
|
|
71
|
+
get receivedEnvNames() {
|
|
72
|
+
return Object.entries(this.childEnv(undefined, undefined)).flatMap(([name, value]) => (value === undefined ? [] : [name]));
|
|
73
|
+
}
|
|
72
74
|
get withheldEnv() {
|
|
73
75
|
return withheldFromHostSubprocess({ policy: this.cfg.inheritEnv, allow: [] }, this.cfg.env);
|
|
74
76
|
}
|
|
@@ -148,7 +150,9 @@ export class RemoteHostExecutionEnv {
|
|
|
148
150
|
this.cwd = this.workspaceDir;
|
|
149
151
|
this.handle = { sandboxId: this.workspaceDir, provider: PROVIDER, mountPath: this.workspaceDir };
|
|
150
152
|
this.homeSeat = new LaneHomeSeat(PROVIDER, config, IS_WIN32 ? "win32" : "posix");
|
|
151
|
-
|
|
153
|
+
const homeVerdict = this.homeSeat.settle(this.childHome());
|
|
154
|
+
if (homeVerdict !== undefined)
|
|
155
|
+
throw laneHomeRefusal(homeVerdict);
|
|
152
156
|
}
|
|
153
157
|
workspaceHandle() {
|
|
154
158
|
return this.handle;
|
|
@@ -158,8 +162,6 @@ export class RemoteHostExecutionEnv {
|
|
|
158
162
|
return unsupported("connect(snapshotId)");
|
|
159
163
|
if (this.destroyed)
|
|
160
164
|
return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env already destroyed") };
|
|
161
|
-
if (this.homeRefusal !== undefined)
|
|
162
|
-
return { ok: false, error: new RemoteExecutionError("connect_failed", this.homeRefusal) };
|
|
163
165
|
if (config?.abortSignal?.aborted)
|
|
164
166
|
return { ok: false, error: new RemoteExecutionError("aborted", "connect aborted") };
|
|
165
167
|
try {
|
|
@@ -1262,8 +1264,6 @@ export class RemoteHostExecutionEnv {
|
|
|
1262
1264
|
async shellReady() {
|
|
1263
1265
|
if (this.destroyed)
|
|
1264
1266
|
return { ok: false, error: new ExecutionError("shell_unavailable", "execution env already destroyed") };
|
|
1265
|
-
if (this.homeRefusal !== undefined)
|
|
1266
|
-
return { ok: false, error: new ExecutionError("shell_unavailable", this.homeRefusal) };
|
|
1267
1267
|
try {
|
|
1268
1268
|
try {
|
|
1269
1269
|
await resolveHostShell();
|
|
@@ -1295,8 +1295,6 @@ export class RemoteHostExecutionEnv {
|
|
|
1295
1295
|
return (this.spoolRootDir !== undefined && inside(this.spoolRootDir)) || (this.persistent && inside(this.workspaceDir));
|
|
1296
1296
|
}
|
|
1297
1297
|
async fsReady(p, opts) {
|
|
1298
|
-
if (this.homeRefusal !== undefined)
|
|
1299
|
-
return { ok: false, error: new FileError("target_unavailable", this.homeRefusal, this.resolve(p)) };
|
|
1300
1298
|
if (this.destroyed) {
|
|
1301
1299
|
const lingering = opts?.lingeringBgWrite === true && this._bgManager?.hasShells() === true && this.isLingeringMirrorTarget(p);
|
|
1302
1300
|
if (!lingering)
|
|
@@ -386,6 +386,7 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
386
386
|
* loud `workspace_latest_dangling`, the pointer deleted, an empty workspace); absent (404 — the key, with the bucket there) ⇒ a fresh pod; a read that
|
|
387
387
|
* FAILS ⇒ a loud `connect_failed` — a 404 `NoSuchBucket` included ({@link refuseMissingBucket}) — never a silent fresh workspace (a run that believes it resumed while it did not is
|
|
388
388
|
* the worst form). Once the pod holds the workspace, this env arms its fence seat (its destroy will commit).
|
|
389
|
+
* The same walk ({@link establish}) is the restore point of a parked run's resumed leg whose park packed nothing ({@link resumeVM}).
|
|
389
390
|
* Persistence off / no store ⇒ a fresh pod, exactly as before. Persistence on but no such session (the occupant absent) ⇒ a
|
|
390
391
|
* fresh pod and one `workspace_space_unavailable` line (no restore by any other key). The occupant read FAULTED ⇒ the same
|
|
391
392
|
* loud `connect_failed` as a failed `latest` read, no pod ({@link provision} refuses it — every entry that would create one).
|
|
@@ -397,14 +398,16 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
397
398
|
* as it stands to every later connect on every lane — the previous commit is not waited on again, `latest` is not read again, no pod is created.
|
|
398
399
|
*/
|
|
399
400
|
connect(config?: RemoteConnectConfig): Promise<ConnectResult>;
|
|
400
|
-
/** The one walk of a connect ({@link connect} runs it once at a time)
|
|
401
|
+
/** The one walk of a connect ({@link connect} runs it once at a time) — also the resumed leg's walk when its park packed nothing ({@link restorePointOf},
|
|
402
|
+
* under the resume's own hold of the same gate). */
|
|
401
403
|
private establish;
|
|
402
404
|
/**
|
|
403
405
|
* The restore point's fill (`workspace-fill.ts`): a pod whose workspace this env just minted or restored is handed over only once the fill seat placed
|
|
404
406
|
* its content — through {@link fillChannel} (this adapter's own exec, never the public face: inside a connect the public face waits on that very connect).
|
|
405
407
|
* A fill that does not complete releases the pod (no tool and no run-end snapshot may see a half-filled workspace); a refusal that would answer the same
|
|
406
408
|
* again (`standing`) is held for the env ({@link holdRefusal}). No seat ⇒ the pod as it came. `origin` tells the seat whether the workspace was minted
|
|
407
|
-
* (a new pod with nothing restored: the space off / unavailable / without `latest`, or
|
|
409
|
+
* (a new pod with nothing restored: the space off / unavailable / without `latest`, or a dangling restore point — `latest` or a park snapshot whose archive is gone)
|
|
410
|
+
* or restored (the space's `latest`, or a park snapshot whose archive came back). A park that packed nothing resumes through a new connect, so it reads as that connect does.
|
|
408
411
|
*/
|
|
409
412
|
private settle;
|
|
410
413
|
/** The restore point's channel: a fixed command through the one start seam (start form `adapter`, the engine's commands' identity) and a write in
|
|
@@ -447,9 +450,11 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
447
450
|
* never its text. Not a knob: there is no honest way to run a space we could not read — a retry of the run reads it again.
|
|
448
451
|
*/
|
|
449
452
|
private refuseUnreadableOccupant;
|
|
450
|
-
/** Every space-lane operator line: the space is named by its incarnation's log prefix only
|
|
451
|
-
* stderr, a fetch error) passes {@link redactIncarnations} — the full token never reaches a log body. */
|
|
453
|
+
/** Every space-lane operator line: the space is named by its incarnation's log prefix only ({@link snapshotLine} does the rest). */
|
|
452
454
|
private spaceLine;
|
|
455
|
+
/** Every snapshot operator line, both lanes — the ONE redaction seat: each string field (a key, a curl stderr, a fetch error, an effect) passes
|
|
456
|
+
* {@link redactIncarnations}, so a full incarnation-shaped token never reaches a log body, whatever text carried it there. */
|
|
457
|
+
private snapshotLine;
|
|
453
458
|
/** A fresh pod (create → Running → workspace root), bounded retry — THE seat every pod of this env is created through
|
|
454
459
|
* ({@link connect} and, via {@link restoreSnapshot}, {@link resumeVM}). A lane keyed by an occupant read that faulted ⇒ refused
|
|
455
460
|
* here, before any request ({@link refuseUnreadableOccupant}): never retried (the reading is this env's, fixed at construction). */
|
|
@@ -651,11 +656,14 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
651
656
|
*/
|
|
652
657
|
private uploadSnapshot;
|
|
653
658
|
/**
|
|
654
|
-
* Resume a workspace
|
|
655
|
-
*
|
|
656
|
-
*
|
|
657
|
-
*
|
|
658
|
-
*
|
|
659
|
+
* Resume a parked run's workspace onto a FRESH pod (resume = executionEnvFactory rebuild + resumeVM). Where the leg's workspace comes from is
|
|
660
|
+
* read off the park's snapshot id — it says what the park packed ({@link snapshotIdHolds}), never guessed from what the store answers:
|
|
661
|
+
* - an archive ⇒ provision a pod, then download + untar it into the workspace ({@link restoreSnapshot}, lane `park`): it came back ⇒ `restored`
|
|
662
|
+
* (never seeded); it is not there ⇒ a dangling restore point, the same rule on both lanes ({@link dropDangling}: one line, an empty workspace);
|
|
663
|
+
* - nothing (the env held no pod when it parked — the run parked before anything touched its hand) ⇒ there is no workspace of the park to
|
|
664
|
+
* restore, so the leg's restore point is the one a new connect has ({@link establish}): space open ⇒ the previous commit waited out and
|
|
665
|
+
* `latest` restored (none ⇒ a fresh workspace), space off ⇒ a fresh workspace — never a fresh workspace beside a `latest` that the leg's
|
|
666
|
+
* run-end commit would then overwrite. No archive is asked for under such an id.
|
|
659
667
|
*
|
|
660
668
|
* [ref]①(priorHandle 根保真)——k8s 走**诚实臂**而非绑定臂:pod 的 workspace volume 挂在**当前**
|
|
661
669
|
* 配置根(cfg.mountPath),往 priorHandle.mountPath 绑=往容器 rootfs untar 大工作区,ephemeral-storage
|
|
@@ -666,15 +674,14 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
666
674
|
resumeVM(snapshotId: SnapshotId, options?: VmLifecycleOptions): Promise<ConnectResult>;
|
|
667
675
|
/** The one walk of a resume ({@link resumeVM} runs it under the connect gate). */
|
|
668
676
|
private resumeWalk;
|
|
669
|
-
/**
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
*
|
|
673
|
-
*
|
|
674
|
-
*
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
* snapshot then becomes the new `latest`.
|
|
677
|
+
/** The resumed leg's restore point, chosen by what the park's id names ({@link resumeVM}) — a closed set, one arm each. */
|
|
678
|
+
private restorePointOf;
|
|
679
|
+
/**
|
|
680
|
+
* The ONE tar download (both lanes): provision a FRESH pod, download + untar the lane's archive into the workspace. Only an id that names an
|
|
681
|
+
* archive reaches it ({@link snapshotIdHolds}: a park that packed nothing resumes through {@link establish}; `latest` names only an object
|
|
682
|
+
* that was written). So a missing archive (404) is one fact on both lanes — the restore point names data that is gone (removed later, by the
|
|
683
|
+
* bucket's lifecycle rule or a hand) ⇒ loud and continuable, ONE rule: {@link dropDangling} (one `workspace_latest_dangling` line), and the
|
|
684
|
+
* run continues on the clean workspace, origin `minted` (no restorable tree ⇒ the restore point seeds it, exactly as on a fresh connect).
|
|
678
685
|
* An attempt that does not complete releases the pod it provisioned: no tool and no run-end snapshot may ever see a torn
|
|
679
686
|
* workspace. A transient class ({@link restoreFailureIsTransient}) is tried again on a fresh pod after the `retry.backoffMs`
|
|
680
687
|
* pause, up to the lane's {@link RESTORE_MAX_ATTEMPTS}; a non-transient one ends the restore at once. The lane then says what
|
|
@@ -691,12 +698,18 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
691
698
|
/** The pause before a restore's next attempt (`retry.backoffMs`, the adapter's one retry pause). `false` ⇒ aborted (now or during it). */
|
|
692
699
|
private restorePause;
|
|
693
700
|
/**
|
|
694
|
-
*
|
|
695
|
-
*
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
|
|
699
|
-
|
|
701
|
+
* A restore point names an archive that is not there — the data is gone by an operator rule (a bucket lifecycle rule, a hand): loud and
|
|
702
|
+
* continuable, ONE rule on both lanes — one `workspace_latest_dangling{lane, key, …}` line, and the run continues on the empty workspace
|
|
703
|
+
* {@link restoreSnapshot} hands over. `key` = the object the id names, spelled by {@link snapshotBase} (the one spelling of both partitions).
|
|
704
|
+
* What each lane adds is only what it holds:
|
|
705
|
+
* - space: its `latest` pointer is deleted (the next run must not trip on it again); the line carries `space` and `deleted` — a delete that
|
|
706
|
+
* fails rides on the same line (`deleted: false` + the error) and the next run says it once more. This env is the space's only writer here
|
|
707
|
+
* (the commit fence admits one per space), so the delete is a plain one. The run's own run-end snapshot then becomes the new `latest`;
|
|
708
|
+
* - park: there is no pointer to drop (the id lives in the engine's checkpoint); the line carries the session id. With the space open, the
|
|
709
|
+
* resumed leg's run-end snapshot then becomes the space's new `latest`.
|
|
710
|
+
* Both lines pass the one redaction seat ({@link snapshotLine}).
|
|
711
|
+
*/
|
|
712
|
+
private dropDangling;
|
|
700
713
|
/**
|
|
701
714
|
* Honest post-resume consistency ([ref] §5 council #8): the workspace FILES are restored byte-for-byte
|
|
702
715
|
* from the snapshot, so read-before-edit hashing already lines up — there's nothing to re-fetch. In-VM
|
|
@@ -8,7 +8,7 @@ import { appendFileStaged, buildCwdEnvCommand, createCwdGuardedCall, shellQuote,
|
|
|
8
8
|
import { presignS3Url, presignS3ListUrl } from "./s3-presign.js";
|
|
9
9
|
import { WORKSPACE_SNAPSHOT_BUCKET_DEFAULT } from "../object-store-buckets.js";
|
|
10
10
|
import { SNAPSHOT_BUCKET_MISSING, isNoSuchBucketAnswer } from "./s3-answer.js";
|
|
11
|
-
import { K8S_WORKSPACE_MOUNT_DEFAULT, SNAPSHOT_KEY_SEGMENT, WORKSPACE_SNAPSHOT_PREFIX_DEFAULT, WORKSPACE_SNAPSHOT_TIMEOUT_MS_DEFAULT, mintSnapshotId, snapshotKeyPrefixProblem, snapshotNameOwnedBy } from "../workspace-space.js";
|
|
11
|
+
import { K8S_WORKSPACE_MOUNT_DEFAULT, SNAPSHOT_KEY_SEGMENT, WORKSPACE_SNAPSHOT_PREFIX_DEFAULT, WORKSPACE_SNAPSHOT_TIMEOUT_MS_DEFAULT, mintSnapshotId, snapshotIdHolds, snapshotKeyPrefixProblem, snapshotNameOwnedBy } from "../workspace-space.js";
|
|
12
12
|
import { spaceCommitAlive } from "../workspace-space-commit.js";
|
|
13
13
|
import { incarnationLogPrefix, redactIncarnations } from "../session-incarnation.js";
|
|
14
14
|
import { WORKSPACE_CACHE_SPACE_LABEL, workspaceCacheClaimOf, workspaceCacheEnv } from "../workspace-cache.js";
|
|
@@ -509,8 +509,10 @@ export class RemoteK8sExecutionEnv {
|
|
|
509
509
|
return spaceReadFailed("occupant", ` (${code}) — refusing to start this run on a workspace that could be neither restored nor saved; retry the run once the session store answers, or set ${ways}`);
|
|
510
510
|
}
|
|
511
511
|
spaceLine(event, space, fields) {
|
|
512
|
-
|
|
513
|
-
|
|
512
|
+
this.snapshotLine(event, { space: incarnationLogPrefix(space.incarnation), ...fields });
|
|
513
|
+
}
|
|
514
|
+
snapshotLine(event, fields) {
|
|
515
|
+
this.logger?.warn(event, Object.fromEntries(Object.entries(fields).map(([k, v]) => [k, typeof v === "string" ? redactIncarnations(v) : v])));
|
|
514
516
|
}
|
|
515
517
|
provision(signal) {
|
|
516
518
|
const space = this.spaceLane;
|
|
@@ -1224,12 +1226,12 @@ export class RemoteK8sExecutionEnv {
|
|
|
1224
1226
|
async uploadSnapshot(target, boundMs = this.cfg.dataTimeoutMs) {
|
|
1225
1227
|
if (target.lane === "park" && this.inFlight.size > 0)
|
|
1226
1228
|
return { ok: false, error: new RemoteExecutionError("command_in_flight", "cannot suspend while a command is in flight (would snapshot a torn workspace)") };
|
|
1227
|
-
const snapshotId = mintSnapshotId(this.occupantIncarnation);
|
|
1228
1229
|
if (!this.podName) {
|
|
1229
1230
|
return target.lane === "space"
|
|
1230
1231
|
? { ok: false, error: new RemoteExecutionError("connect_failed", "the sandbox pod was gone when the run-end snapshot reached the upload — nothing was saved (the workspace could not be read), and `latest` keeps naming the previous snapshot") }
|
|
1231
|
-
: ok(
|
|
1232
|
+
: ok(mintSnapshotId(this.occupantIncarnation, "nothing"));
|
|
1232
1233
|
}
|
|
1234
|
+
const snapshotId = mintSnapshotId(this.occupantIncarnation, "archive");
|
|
1233
1235
|
const presigned = this.presignSnapshot({ ...target, method: "PUT", snapshotId });
|
|
1234
1236
|
if (!presigned.ok)
|
|
1235
1237
|
return presigned;
|
|
@@ -1273,13 +1275,26 @@ export class RemoteK8sExecutionEnv {
|
|
|
1273
1275
|
this.logger?.warn("sandbox_pod_released_before_restore", { pod: this.podName, ...(this.cfg.sessionId ? { sessionId: this.cfg.sessionId } : {}), effect: "this env held a pod minted before the park snapshot's restore — it is deleted; the restore provisions the leg's one pod" });
|
|
1274
1276
|
await this.releasePod();
|
|
1275
1277
|
}
|
|
1276
|
-
const r = await this.
|
|
1278
|
+
const r = await this.restorePointOf(snapshotId, options?.abortSignal);
|
|
1277
1279
|
if (!r.ok)
|
|
1278
1280
|
return r;
|
|
1279
1281
|
this.handleSnapshotId = snapshotId;
|
|
1280
1282
|
this.armSpace();
|
|
1281
1283
|
return ok(this.workspaceHandle());
|
|
1282
1284
|
}
|
|
1285
|
+
async restorePointOf(snapshotId, abortSignal) {
|
|
1286
|
+
const holds = snapshotIdHolds(snapshotId);
|
|
1287
|
+
switch (holds) {
|
|
1288
|
+
case "archive":
|
|
1289
|
+
return this.settle(await this.restoreSnapshot({ lane: "park" }, snapshotId, abortSignal));
|
|
1290
|
+
case "nothing":
|
|
1291
|
+
return this.establish(abortSignal ? { abortSignal } : undefined);
|
|
1292
|
+
default: {
|
|
1293
|
+
const unreachable = holds;
|
|
1294
|
+
throw new Error(`unhandled snapshot id family: ${String(unreachable)}`);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1283
1298
|
async restoreSnapshot(target, snapshotId, abortSignal) {
|
|
1284
1299
|
const presigned = this.presignSnapshot({ ...target, method: "GET", snapshotId });
|
|
1285
1300
|
if (!presigned.ok)
|
|
@@ -1303,8 +1318,7 @@ export class RemoteK8sExecutionEnv {
|
|
|
1303
1318
|
: undefined;
|
|
1304
1319
|
if (!failure) {
|
|
1305
1320
|
if (r.ok && r.value.stdout.includes("EMPTY_SNAPSHOT")) {
|
|
1306
|
-
|
|
1307
|
-
await this.dropDanglingLatest(target.space, snapshotId);
|
|
1321
|
+
await this.dropDangling(target, snapshotId);
|
|
1308
1322
|
return { ok: true, value: this.workspaceHandle(), origin: "minted" };
|
|
1309
1323
|
}
|
|
1310
1324
|
return { ok: true, value: this.workspaceHandle(), origin: "restored" };
|
|
@@ -1347,15 +1361,34 @@ export class RemoteK8sExecutionEnv {
|
|
|
1347
1361
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1348
1362
|
});
|
|
1349
1363
|
}
|
|
1350
|
-
async
|
|
1351
|
-
const
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1364
|
+
async dropDangling(target, snapshotId) {
|
|
1365
|
+
const key = `${this.snapshotBase(target)}${snapshotId}.tar.gz`;
|
|
1366
|
+
switch (target.lane) {
|
|
1367
|
+
case "space": {
|
|
1368
|
+
const dropped = await this.writePointer(target.space, "latest", null);
|
|
1369
|
+
this.spaceLine("workspace_latest_dangling", target.space, {
|
|
1370
|
+
lane: "space",
|
|
1371
|
+
key,
|
|
1372
|
+
deleted: dropped.ok,
|
|
1373
|
+
...(dropped.ok ? {} : { error: dropped.error.message }),
|
|
1374
|
+
effect: "the space's latest snapshot is gone (removed by a bucket lifecycle rule or by hand) — this run starts from an empty /workspace; its run-end snapshot becomes the new latest",
|
|
1375
|
+
});
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
case "park":
|
|
1379
|
+
this.snapshotLine("workspace_latest_dangling", {
|
|
1380
|
+
lane: "park",
|
|
1381
|
+
key,
|
|
1382
|
+
...(this.cfg.sessionId ? { sessionId: this.cfg.sessionId } : {}),
|
|
1383
|
+
effect: "the parked run's snapshot is gone (removed by a bucket lifecycle rule or by hand) — the resumed leg starts from an empty /workspace" +
|
|
1384
|
+
(this.spaceLane.state === "open" ? "; its run-end snapshot becomes the space's new latest" : ""),
|
|
1385
|
+
});
|
|
1386
|
+
return;
|
|
1387
|
+
default: {
|
|
1388
|
+
const unreachable = target;
|
|
1389
|
+
throw new Error(`unhandled snapshot lane: ${String(unreachable.lane)}`);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1359
1392
|
}
|
|
1360
1393
|
async postResumeInit() {
|
|
1361
1394
|
if (!this.cfg.s3Snapshot)
|
|
@@ -157,7 +157,7 @@ export async function runSymlinkChain(absPath, run) {
|
|
|
157
157
|
const STAGED_FILE = "data";
|
|
158
158
|
const STAGE_DIR_REFUSED_EXIT = 3;
|
|
159
159
|
const STAGE_DIR_UNSEALED_EXIT = 4;
|
|
160
|
-
const STAGE_DIR_UNSEALED_MESSAGE = "sema: the private staging directory could not be made private (chmod 700 failed or did not take effect,
|
|
160
|
+
const STAGE_DIR_UNSEALED_MESSAGE = "sema: the private staging directory could not be made private (chmod 700 failed or did not take effect, the mode or owner read back differently, or an inherited ACL entry could not be removed or read on this platform — the filesystem ignores permission bits or the parent directory carries a default or inheritable ACL); nothing was staged";
|
|
161
161
|
export const STAGE_DIR_LEFT_MARK = "sema: staging directory left behind:";
|
|
162
162
|
export function buildStagingPaths(abs, nonce = randomBytes(6).toString("hex")) {
|
|
163
163
|
const cut = abs.lastIndexOf("/");
|
|
@@ -174,7 +174,17 @@ function privateMkdir(dir) {
|
|
|
174
174
|
}
|
|
175
175
|
function hardenPrivate(dir) {
|
|
176
176
|
const statOf = (gnu, bsd) => `$(stat -c ${gnu} -- ${dir} 2>/dev/null || stat -f ${bsd} -- ${dir} 2>/dev/null)`;
|
|
177
|
-
return `( chmod -- 700 ${dir} 2>/dev/null && m=${statOf("%a", "%Lp")} && u=${statOf("%u", "%u")} && case "$m" in 700|[1-7]700) [ "$u" = "$(id -u)" ];; *) false;; esac )`;
|
|
177
|
+
return `( chmod -- 700 ${dir} 2>/dev/null && m=${statOf("%a", "%Lp")} && u=${statOf("%u", "%u")} && case "$m" in 700|[1-7]700) [ "$u" = "$(id -u)" ];; *) false;; esac && ${aclCleared(dir)} )`;
|
|
178
|
+
}
|
|
179
|
+
function aclCleared(dir) {
|
|
180
|
+
const marker = `ls_out=$(cd ${dir} && ls -ld . 2>&1) && [ -z "$(printf '%s\\n' "$ls_out" | sed -n 2p)" ] && mode_word=\${ls_out%% *} && ` +
|
|
181
|
+
`case "$mode_word" in d?????????*+*) echo +;; d?????????*) ;; *) false;; esac`;
|
|
182
|
+
return (`plat=$(uname -s 2>/dev/null) && case "$plat" in ` +
|
|
183
|
+
`Linux) acl_read() { :; }; acl_strip() { false; };; ` +
|
|
184
|
+
`Darwin) acl_read() { ls_out=$(cd ${dir} && ls -led . 2>&1) && case "$ls_out" in d?????????*) ;; *) false;; esac && printf '%s\\n' "$ls_out" | sed 1d; }; acl_strip() { chmod -N ${dir}; };; ` +
|
|
185
|
+
`FreeBSD) acl_read() { ${marker}; }; acl_strip() { setfacl -b ${dir}; };; ` +
|
|
186
|
+
`SunOS) acl_read() { ${marker}; }; acl_strip() { chmod A- ${dir}; };; ` +
|
|
187
|
+
`*) false;; esac && acl=$(acl_read 2>/dev/null) && { [ -z "$acl" ] || { acl_strip 2>/dev/null && acl=$(acl_read 2>/dev/null) && [ -z "$acl" ]; }; }`);
|
|
178
188
|
}
|
|
179
189
|
function emptyPrivate(dir) {
|
|
180
190
|
return `[ ! -e ${dir}/${STAGED_FILE} ] && [ ! -L ${dir}/${STAGED_FILE} ]`;
|
|
@@ -387,7 +387,7 @@ export declare class SqlRetentionStore implements ManagedRetentionCapability {
|
|
|
387
387
|
* 它的域完全靠「属主 sessionId 的墓碑」推。而会话 id 是**调用方可自选**的 ⇒ 两个租户先后用过同一个 id
|
|
388
388
|
* 完全合法;两边都删过之后,同一个 row_key 上会有**两个域**的墓碑,这时任何一边的 sweep 拿自己的墓碑
|
|
389
389
|
* 去认领这些字节都只是猜。判据因此是:**存在第二个域的同名墓碑 ⇒ 这批行不可归属 ⇒ 一根不删**,
|
|
390
|
-
* 实体清理退回既有的 TTL 腿(`
|
|
390
|
+
* 实体清理退回既有的 TTL 腿(core `ToolResultStore.reap`)。少删一次是可接受的;删掉另一个租户的字节、还跳过它的
|
|
391
391
|
* legal hold 与审计域,不可接受。
|
|
392
392
|
*/
|
|
393
393
|
protected orphanToolResultCandidates(exec: {
|
|
@@ -475,7 +475,7 @@ export declare class SqlRetentionStore implements ManagedRetentionCapability {
|
|
|
475
475
|
* 也只计 `skipped` 不删。会话本体的清理归 {@link SqlRetentionStore.deleteExpiredSessions}(它会连着
|
|
476
476
|
* 这行一起删),两条腿因此不重叠也不互相抢行。
|
|
477
477
|
* · **一根不碰的那一支**:`owner_session_id IS NULL` 的**无出处**行。没有出处就没有域,按任何域去删它
|
|
478
|
-
* 都是猜;实体清理归既有的 TTL 腿(`
|
|
478
|
+
* 都是猜;实体清理归既有的 TTL 腿(core `ToolResultStore.reap`,`boot/reapers.ts` 驱动)。这与 core 的
|
|
479
479
|
* 「an UNOWNED entry is never deleted」是同一条纪律的两处执行点。
|
|
480
480
|
*
|
|
481
481
|
* 事务形与另外两只方法逐字同款(hold 哨兵行锁读 → 删除+墓碑 → 审计行,同 commit 同 rollback)。
|
|
@@ -5,11 +5,9 @@ export declare const READ_FLOOR_UNAVAILABLE_REASONS: readonly ["arch", "user_not
|
|
|
5
5
|
export type ReadFloorUnavailableReason = (typeof READ_FLOOR_UNAVAILABLE_REASONS)[number];
|
|
6
6
|
/** F2:附着不过的环境上,带地板起动的拒句(纯字符串 ⇒ `build*`)。首句点名原因词;点名表的首行与旋钮。 */
|
|
7
7
|
export declare function buildReadFloorUnavailableMessage(reason: ReadFloorUnavailableReason, detail: string | undefined, floor: ReadFloor | undefined): string;
|
|
8
|
-
/** F6:守地板的环境上,不带地板的引擎起动的拒句。 */
|
|
9
|
-
export declare function buildReadFloorMissingMessage(lane: SandboxFloorLane): string;
|
|
10
8
|
/**
|
|
11
9
|
* 一次起动是什么(闭集,调用方必须表态 —— 没有缺省臂):
|
|
12
|
-
* · `engine` —— 经执行环境公共起动口要起的命令(引擎的,或替引擎干活的服务端调用方的)
|
|
10
|
+
* · `engine` —— 经执行环境公共起动口要起的命令(引擎的,或替引擎干活的服务端调用方的);它带的地板就是它的表,缺席 = 表无行。`hold` = 适配器之后还会以
|
|
13
11
|
* 同一起动号再起 `run`(k8s 后台作业的轮询 / 停 / 收尾读的是这次起动的产物,按这次起动的地板跑),登记留到 {@link SandboxReadFloor.release};
|
|
14
12
|
* · `under` —— 以一次已登记(`hold`)起动的号再起一只 `run`(上面那类轮询);
|
|
15
13
|
* · `adapter` —— 适配器自己的固定命令(文件面助手、探针、数据搬运、全杀);附着成功之后经监督者的 `plain` 起;
|
|
@@ -29,7 +27,7 @@ export type FloorStart = {
|
|
|
29
27
|
};
|
|
30
28
|
export declare const ADAPTER_START: FloorStart;
|
|
31
29
|
export declare const BOOTSTRAP_START: FloorStart;
|
|
32
|
-
/** 引擎起动:`readFloor` 原样交(`undefined` =
|
|
30
|
+
/** 引擎起动:`readFloor` 原样交(`undefined` = 这次起动的表一行都没有)。 */
|
|
33
31
|
export declare function engineStart(readFloor: ReadFloor | undefined, hold?: boolean): FloorStart;
|
|
34
32
|
/** 起动身份:`root` = 车道以 root 起(e2b `user:"root"`、local-docker `-u 0`);`exec-user` = 车道自己的执行身份(改前的形)。 */
|
|
35
33
|
export type FloorLaunchIdentity = "root" | "exec-user";
|
|
@@ -125,8 +123,8 @@ export interface RelayAnswer {
|
|
|
125
123
|
export declare const UNJUDGED_VERDICT = "read floor: no verdict for this start";
|
|
126
124
|
/** 判官:适配器进程里唯一回答「这条路径拒不拒」的东西。live object ⇒ `create*`。 */
|
|
127
125
|
export interface FloorJudge {
|
|
128
|
-
/**
|
|
129
|
-
register(floor: ReadFloor, held: boolean): string;
|
|
126
|
+
/** 登记一次引擎起动(带它的表;`undefined` = 表无行),回它的起动号(随机、不可猜)。`held` ⇒ 之后还会以这个号再起 `run`(`under`),直到 {@link release};否则这一次就是它唯一的起动。 */
|
|
127
|
+
register(floor: ReadFloor | undefined, held: boolean): string;
|
|
130
128
|
/** 以这个号再起一次 `run`(`under`)之前问:在册且适配器没说过「不再起」⇒ `true`(记下又有一只监督者将连上);否则 `false`(起动拒)。 */
|
|
131
129
|
launch(startId: string): boolean;
|
|
132
130
|
/** 适配器不会再以这个号起 `run` 了。登记要两件都到才撤(协议 §5.5):这一句 ∧ 此后的 `gone` —— `gone` 已在 ⇒ 当即撤;否则等它(还连着的监督者照旧有判官答)。 */
|
|
@@ -202,7 +200,7 @@ export interface SandboxReadFloor {
|
|
|
202
200
|
readonly state: SandboxReadFloorState;
|
|
203
201
|
/** 这只模块钉住的监督者二进制(守地板 ⇒ 在;裸模块 ⇒ 缺席)。k8s 的 pod 座({@link buildFloorPodSeat})只读它 —— 放进 pod 的与自检核的是同一份钉。 */
|
|
204
202
|
readonly binaries: FloorBinaries | undefined;
|
|
205
|
-
/** 唯一接缝:一次起动 → 答案,或 `spawn_error`(F2 /
|
|
203
|
+
/** 唯一接缝:一次起动 → 答案,或 `spawn_error`(F2 / 时机不对的引导起动 / 已撤的号)。 */
|
|
206
204
|
wrap(start: FloorStart): Result<FloorLaunch, ExecutionError>;
|
|
207
205
|
/** 适配器不会再以这个号起 `run`(`hold` 起动的收尾)。登记在其后的 `gone` 到时撤(还连着的监督者照旧有判官答)。 */
|
|
208
206
|
release(startId: string | undefined): void;
|
|
@@ -35,11 +35,6 @@ export function buildReadFloorUnavailableMessage(reason, detail, floor) {
|
|
|
35
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
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
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
38
|
export const ADAPTER_START = Object.freeze({ kind: "adapter" });
|
|
44
39
|
export const BOOTSTRAP_START = Object.freeze({ kind: "bootstrap" });
|
|
45
40
|
export function engineStart(readFloor, hold = false) {
|
|
@@ -121,6 +116,8 @@ export function parseRelayFrame(line) {
|
|
|
121
116
|
}
|
|
122
117
|
export const UNJUDGED_VERDICT = "read floor: no verdict for this start";
|
|
123
118
|
function judgeOne(floor, path) {
|
|
119
|
+
if (floor === undefined)
|
|
120
|
+
return null;
|
|
124
121
|
let v;
|
|
125
122
|
try {
|
|
126
123
|
v = floor.denies(path);
|
|
@@ -433,8 +430,6 @@ export function createKeptSandboxReadFloor(opts) {
|
|
|
433
430
|
}
|
|
434
431
|
case "engine": {
|
|
435
432
|
const floor = start.readFloor;
|
|
436
|
-
if (floor === undefined)
|
|
437
|
-
return spawnError(buildReadFloorMissingMessage(lane));
|
|
438
433
|
if (phase.kind === "unavailable")
|
|
439
434
|
return spawnError(buildReadFloorUnavailableMessage(phase.reason, phase.detail, floor));
|
|
440
435
|
if (phase.kind !== "ready" || bin === undefined)
|
|
@@ -73,7 +73,11 @@ export interface ParsedTable {
|
|
|
73
73
|
export declare function parseCreateTable(stmt: string): ParsedTable | undefined;
|
|
74
74
|
/** 启动时真会建出来的**全部**表形(录制 → 解析)。同名表二次出现以**先到**为准(`IF NOT EXISTS` 的真语义)。 */
|
|
75
75
|
export declare function expectedTableShapes(dialect: SqlDialect): Promise<Map<string, ParsedTable>>;
|
|
76
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* 拒启码(机读)。附录 A / 拒启码表的登记名。含义 = 「本构建读不出的持久形,开始服务之前判」,一个码三只臂:本文件的列册对账与退役 schema 两道(SQL),
|
|
78
|
+
* 以及 local 数据根的严格边档对账(`local-session-store.ts` 的 `LocalDataRootSchemaMismatchError`,`LocalBackend.ensureSchema()` 调;拒句同形:
|
|
79
|
+
* 码开头的表头 · 逐坐标一段 · 「NEWER than this build」方向句 · 闭集出路)。
|
|
80
|
+
*/
|
|
77
81
|
export declare const SCHEMA_MISMATCH_ERROR_CODE = "config.schema_mismatch";
|
|
78
82
|
/** 一张表的对账结论。 */
|
|
79
83
|
export interface TableMismatch {
|
|
@@ -28,7 +28,7 @@ export interface ForkLogRow {
|
|
|
28
28
|
readonly ts: unknown;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
* 两只 SQL 孪生的行怎么交给引擎的 `selectForkRows`(按答案挑行 + 新会话的叶;分叉点不在日志里 ⇒ `invalid_fork_target`、路径中途悬空父 / 父链成环 ⇒ `invalid_session
|
|
31
|
+
* 两只 SQL 孪生的行怎么交给引擎的 `selectForkRows`(按答案挑行 + 新会话的叶;分叉点不在日志里 ⇒ `invalid_fork_target`、路径中途悬空父 / 父链成环 ⇒ `corrupt_session`〔core 7.61.x 及以前 `invalid_session`〕,
|
|
32
32
|
* 引擎的原词原句)。本仓不再自写父链走法 —— 那曾是与引擎 `getPathToRoot` 同义的第二份。
|
|
33
33
|
*/
|
|
34
34
|
export declare const FORK_LOG_ROW_SHAPE: ForkRowShape<ForkLogRow>;
|
|
@@ -22,6 +22,15 @@ export declare function forkOwnerOf(requested: string | null | undefined, source
|
|
|
22
22
|
* 「首写」由调用方判(已记下属主的会话上的调用照旧静默不改,不经这里)。判官 = {@link isSessionOwnerValue}(core 7.56.0 [ref] `principalNameOf`)。
|
|
23
23
|
*/
|
|
24
24
|
export declare function assertFirstOwnerWritable(sessionId: string, owner: unknown): asserts owner is string | null;
|
|
25
|
+
/**
|
|
26
|
+
* placed 行属主格的唯一读法(core 7.62.0 [ref] 盒义务 7;三只持久会话店的 placing write 同调):记下的属主 = 引擎交来的 `placement.owner` **原样** ——
|
|
27
|
+
* 委派树的属主由引擎判(没人派生的 run 读自己那一行、按化身绑一次,原样带给每一层子代;`null` = 根匿名),店只记、不判、不推断,也不进元组
|
|
28
|
+
* (`SessionPlacementRecord` 没有属主)。值不是主体名也不是 `null`(空串、空白、缺席 —— 绕过类型的调用)⇒ 响亮拒(`SessionError("storage")`,一个字节不写):
|
|
29
|
+
* 一只谁也不指的属主会让行要么谁都读得到、要么谁都读不到。判官 = {@link isSessionOwnerValue},与 `register` 首写同一只。
|
|
30
|
+
*/
|
|
31
|
+
export declare function placedSessionOwnerOf(sessionId: string, placement: {
|
|
32
|
+
readonly owner?: unknown;
|
|
33
|
+
}): string | null;
|
|
25
34
|
/**
|
|
26
35
|
* 导入者的属主 —— 换装的写腿(公开 `importEntries` / `replaceEntries` 与暂存提交)把调用方给的属主重戳进会话行之前的唯一读法:
|
|
27
36
|
* 主体名或 `null`(匿名)⇒ 原样交回;别的一切(空串、空白串、带控制 / 分隔 / 非连接符格式字符的串、非串)⇒ `TypeError` 响亮拒 ——
|
|
@@ -22,6 +22,12 @@ export function assertFirstOwnerWritable(sessionId, owner) {
|
|
|
22
22
|
throw new SessionError("storage", `register(${sessionId}): a first ownership write must carry a principal name or null (anonymous), got ${ownerValueFault(owner)} — ` +
|
|
23
23
|
"a value that names nobody would leave the session claimable by anyone or ownable by no one; refusing, nothing written");
|
|
24
24
|
}
|
|
25
|
+
export function placedSessionOwnerOf(sessionId, placement) {
|
|
26
|
+
const owner = placement.owner;
|
|
27
|
+
if (isSessionOwnerValue(owner))
|
|
28
|
+
return owner;
|
|
29
|
+
throw new SessionError("storage", `acquire(${sessionId}): a placed session's owner (placement.owner, the delegation tree's owner the engine judged) must be a principal name or null (an anonymous tree), got ${ownerValueFault(owner)} — refusing, nothing written`);
|
|
30
|
+
}
|
|
25
31
|
export function importerOwnerOf(owner) {
|
|
26
32
|
if (isSessionOwnerValue(owner))
|
|
27
33
|
return owner;
|