@sema-agent/server 7.149.0 → 7.150.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 +122 -9
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +39 -15
- package/dist/approval-card.d.ts +1 -1
- package/dist/approval-gate-currency.d.ts +1 -1
- package/dist/audit.d.ts +1 -1
- package/dist/audit.js +2 -2
- package/dist/background-session-reap.d.ts +1 -1
- package/dist/boot/deferred-sandbox-path-env.d.ts +5 -3
- package/dist/boot/deferred-sandbox-path-env.js +1 -2
- package/dist/boot/parked-revive-gate.d.ts +5 -5
- package/dist/boot/parked-revive-gate.js +2 -3
- package/dist/boot/resolve-spec.d.ts +4 -5
- package/dist/boot/resolve-spec.js +61 -25
- package/dist/boot/retention-lane.d.ts +1 -1
- package/dist/boot/session-faces.js +21 -21
- package/dist/boot/stage-03-store-backend.js +2 -2
- package/dist/boot/stage-06-runners.js +1 -2
- package/dist/boot/stage-07-capability-layer.js +1 -1
- package/dist/boot/stage-10-http-server.d.ts +33 -1
- package/dist/boot/stage-10-http-server.js +9 -4
- package/dist/boot/web-admin.d.ts +1 -1
- package/dist/capability-facts.d.ts +65 -56
- package/dist/capability-facts.js +42 -35
- package/dist/config-catalog.js +1 -1
- package/dist/config-types.d.ts +12 -11
- package/dist/deployment-governance.d.ts +7 -28
- package/dist/deployment-governance.js +1 -14
- package/dist/device-ws-hub.d.ts +1 -0
- package/dist/device-ws-hub.js +64 -28
- package/dist/device-ws-protocol.d.ts +20 -6
- package/dist/device-ws-protocol.js +9 -2
- package/dist/engine-identity.d.ts +7 -1
- package/dist/engine-identity.js +5 -0
- package/dist/field-conflict.d.ts +2 -2
- package/dist/fleet/engine-signal.d.ts +14 -1
- package/dist/fleet/engine-signal.js +5 -1
- package/dist/fleet/fleet-reconciler.d.ts +1 -1
- package/dist/fleet/serving-lease.d.ts +18 -3
- package/dist/fleet/serving-lease.js +81 -35
- package/dist/fleet-client.d.ts +13 -11
- package/dist/fleet-client.js +53 -33
- package/dist/history-anchor.js +1 -1
- package/dist/hitl-delivery.d.ts +1 -3
- package/dist/hitl-delivery.js +1 -1
- package/dist/http/active-run-conflict.d.ts +3 -7
- package/dist/http/active-run-conflict.js +2 -2
- package/dist/http/admission.js +2 -2
- package/dist/http/approval-sweeps.js +2 -1
- package/dist/http/caller-trust.d.ts +2 -2
- package/dist/http/caller-trust.js +3 -3
- package/dist/http/checkpoint-ctx.d.ts +18 -63
- package/dist/http/checkpoint-ctx.js +1 -24
- package/dist/http/claim-heartbeat.d.ts +4 -4
- package/dist/http/claim-heartbeat.js +3 -3
- package/dist/http/dispatch.js +2 -2
- package/dist/http/durable-run-leg.js +4 -4
- package/dist/http/owner-access.d.ts +243 -19
- package/dist/http/owner-access.js +184 -5
- package/dist/http/readiness.d.ts +4 -2
- package/dist/http/resume-ctx-read.js +16 -1
- package/dist/http/resume-legs.js +18 -14
- package/dist/http/route-ctx.d.ts +23 -11
- package/dist/http/routes/a2a-serve.js +15 -14
- package/dist/http/routes/approvals-assistant.d.ts +3 -2
- package/dist/http/routes/approvals-assistant.js +8 -9
- package/dist/http/routes/capabilities.js +4 -3
- package/dist/http/routes/config-catalog.d.ts +1 -1
- package/dist/http/routes/mcp-reconnect.js +2 -2
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/notify-wake.js +2 -3
- package/dist/http/routes/projects.d.ts +1 -0
- package/dist/http/routes/projects.js +5 -3
- package/dist/http/routes/retention-ops.d.ts +1 -1
- package/dist/http/routes/run-permission-mode.d.ts +1 -1
- package/dist/http/routes/run-permission-mode.js +2 -2
- package/dist/http/routes/runs.d.ts +1 -1
- package/dist/http/routes/runs.js +23 -30
- package/dist/http/routes/session-memory-sync.js +4 -5
- package/dist/http/routes/session-sync.js +3 -7
- package/dist/http/routes/sessions.d.ts +1 -1
- package/dist/http/routes/sessions.js +18 -44
- package/dist/http/routes/tasks.js +9 -8
- package/dist/http/routes/trace-usage.js +14 -14
- package/dist/http/routes/workflows.d.ts +1 -0
- package/dist/http/routes/workflows.js +20 -38
- package/dist/http/server.d.ts +8 -4
- package/dist/http/server.js +5 -1
- package/dist/http/stored-body-replay.d.ts +3 -24
- package/dist/http/stored-body-replay.js +2 -12
- package/dist/http/submit-ctx.d.ts +1 -2
- package/dist/http/submit-ctx.js +3 -2
- package/dist/http/submit-replay.d.ts +15 -2
- package/dist/http/submit-replay.js +10 -4
- package/dist/http/wire-types.d.ts +6 -3
- package/dist/leader/endpoint.js +9 -8
- package/dist/leg-facts.d.ts +8 -2
- package/dist/leg-facts.js +1 -0
- package/dist/mcp-authorization-face.d.ts +18 -14
- package/dist/mcp-authorization-face.js +3 -2
- package/dist/memory-store-refusal.d.ts +1 -1
- package/dist/observability/corrupt-read-seat.d.ts +1 -1
- package/dist/observability/fail-open.d.ts +5 -5
- package/dist/observability/fail-open.js +5 -5
- package/dist/observability/logger.d.ts +7 -0
- package/dist/observability/logger.js +3 -1
- package/dist/observability/run-terminal.d.ts +1 -1
- package/dist/owner-faces.d.ts +27 -3
- package/dist/owner-faces.js +2 -0
- package/dist/peer-seat.d.ts +1 -1
- package/dist/peer-seat.js +52 -59
- package/dist/plugins/exec-path-law.d.ts +34 -0
- package/dist/plugins/exec-path-law.js +13 -0
- package/dist/plugins/file-run-store.js +1 -1
- package/dist/plugins/local-checkpoint-store.d.ts +4 -1
- package/dist/plugins/local-checkpoint-store.js +3 -0
- package/dist/plugins/memory-run-store.js +1 -1
- package/dist/plugins/one-time-migrations.d.ts +74 -49
- package/dist/plugins/one-time-migrations.js +134 -69
- package/dist/plugins/pg-pool.js +4 -0
- package/dist/plugins/posix-shell-fs.js +4 -2
- package/dist/plugins/remote-env-adb.js +3 -2
- package/dist/plugins/remote-env-device.js +8 -6
- package/dist/plugins/remote-env-e2b.d.ts +3 -1
- package/dist/plugins/remote-env-e2b.js +11 -8
- package/dist/plugins/remote-env-host.d.ts +1 -1
- package/dist/plugins/remote-env-host.js +20 -11
- package/dist/plugins/remote-env-k8s.js +3 -2
- package/dist/plugins/remote-env-local-docker.js +3 -2
- package/dist/plugins/remote-env-ssh.js +4 -3
- package/dist/plugins/remote-shell.d.ts +3 -1
- package/dist/plugins/remote-shell.js +3 -3
- package/dist/plugins/replica-registration.d.ts +35 -0
- package/dist/plugins/replica-registration.js +48 -0
- package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
- package/dist/plugins/run-store-sql.d.ts +1 -1
- package/dist/plugins/run-store-sql.js +2 -2
- package/dist/plugins/scenario-prefix-switch-m42.d.ts +6 -12
- package/dist/plugins/scenario-prefix-switch-m42.js +7 -5
- package/dist/plugins/schema-consistency.d.ts +7 -5
- package/dist/plugins/schema-consistency.js +29 -16
- package/dist/plugins/schema-drift-sentence.js +1 -1
- package/dist/plugins/serving-requirement.d.ts +49 -0
- package/dist/plugins/serving-requirement.js +53 -0
- package/dist/plugins/stop-then-start-sentence.d.ts +42 -3
- package/dist/plugins/stop-then-start-sentence.js +44 -0
- package/dist/plugins/store-backend.d.ts +1 -1
- package/dist/plugins/store-contracts.d.ts +12 -2
- package/dist/plugins/store-contracts.js +3 -0
- package/dist/plugins/tidb-pool.js +4 -0
- package/dist/process-instance.d.ts +3 -0
- package/dist/process-instance.js +7 -0
- package/dist/project-context.d.ts +66 -0
- package/dist/project-context.js +70 -0
- package/dist/question.d.ts +9 -21
- package/dist/question.js +28 -19
- package/dist/run-cancel-card.d.ts +1 -1
- package/dist/run-cancel-card.js +2 -1
- package/dist/security.d.ts +1 -1
- package/dist/session-agent.d.ts +1 -10
- package/dist/session-agent.js +0 -6
- package/dist/session-incarnation.d.ts +18 -4
- package/dist/session-incarnation.js +4 -1
- package/dist/session-notify-door.d.ts +4 -4
- package/dist/session-notify-door.js +5 -6
- package/dist/session-rules-seat.d.ts +4 -3
- package/dist/session-rules-seat.js +3 -11
- package/dist/session-sync.js +3 -3
- package/dist/stores-sql/checkpoint-store-sql.d.ts +21 -2
- package/dist/stores-sql/checkpoint-store-sql.js +106 -18
- package/dist/stores-sql/retention-store-sql.d.ts +1 -1
- package/dist/task-cwd.d.ts +4 -3
- package/dist/task-settings.d.ts +13 -0
- package/dist/task-settings.js +13 -7
- package/dist/tool-approval.d.ts +7 -6
- package/dist/tool-approval.js +41 -37
- package/dist/trace/engine-notice-wire.d.ts +14 -0
- package/dist/trace/engine-notice-wire.js +18 -1
- package/dist/trace/project.d.ts +4 -3
- package/dist/trace/project.js +3 -2
- package/dist/trace/projection-table.d.ts +33 -2
- package/dist/trace/self-minted-wire.d.ts +150 -17
- package/dist/trace/self-minted-wire.js +54 -16
- package/dist/trace/tool-card.d.ts +3 -0
- package/dist/trace/tool-card.js +24 -0
- package/dist/trace/tool-output-media.d.ts +2 -1
- package/dist/unattended-permission-mode.d.ts +1 -1
- package/dist/web-admin-client/apply-effective.d.ts +1 -1
- package/dist/web-admin-client/effective-keys.d.ts +1 -1
- package/dist/web-admin-client/skills-mcp.d.ts +1 -1
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -29
- package/package.json +2 -2
- package/wire-changes/server@7.150.0.json +335 -0
|
@@ -30,7 +30,9 @@ import { isUnusableArchive } from "../execution-placement.js";
|
|
|
30
30
|
import { IDENTITY_COLUMN_NAME, SCENARIO_PREFIX_LIKE, SCENARIO_PREFIX_MARKER_ID, ScenarioPrefixSwitchRefusedError } from "./scenario-prefix-switch-m42.js";
|
|
31
31
|
import { MIGRATION_MARKER_TABLE, SKILL_EXEMPTION_CUTOFF_ID, markIfAbsentSql, migrationMarkerDdl } from "./one-time-migration-marker.js";
|
|
32
32
|
import { describeUpgradeDriftTail, expectedTableShapes } from "./schema-consistency.js";
|
|
33
|
-
import { stopThenStartAction } from "./stop-then-start-sentence.js";
|
|
33
|
+
import { olderReplicaEvidenceSentence, stopThenStartAction } from "./stop-then-start-sentence.js";
|
|
34
|
+
import { buildThisReplicaRegistration, knowsCapability, readReplicaRegistrations, registerReplica } from "./replica-registration.js";
|
|
35
|
+
import { assertReplicaTablesPresent, assertServingRequirementsMet } from "./serving-requirement.js";
|
|
34
36
|
const RETIRED_SESSION_RULE_BUCKETS = ["commandAllow", "commandDeny"];
|
|
35
37
|
export function buildMigrationReceipts() {
|
|
36
38
|
return {
|
|
@@ -197,20 +199,6 @@ const OLD_GRAMMAR_SESSION_RULES_COLUMN = "rule_entries_old_grammar";
|
|
|
197
199
|
const RETIRED_ASK_CAUSE_COLUMN = "denied_reason";
|
|
198
200
|
const ASK_EXECUTION_LANE_COLUMN = "execution_lane";
|
|
199
201
|
export const ONE_TIME_MIGRATIONS = [
|
|
200
|
-
{
|
|
201
|
-
id: "M12",
|
|
202
|
-
introducedIn: "7.122.0",
|
|
203
|
-
releasedOn: "2026-10-09",
|
|
204
|
-
removableAfter: "2026-11-08",
|
|
205
|
-
removal: "删模式词读法记号的判据与拒臂:`http/stored-body-replay.ts` 的 `permissionModeReadingUnstated`;`http/checkpoint-ctx.ts` 的 `permissionModeReadingStated` / `permissionModeReadingRefusal` / `PERMISSION_MODE_READINGS`、" +
|
|
206
|
-
"`StoredBodyReplayRefusal` 的 `permission_mode_reading` 臂及其日志字段与答复臂、`buildCheckpointCtx` 与 `withPermissionModeRewritten` 两处记号写点" +
|
|
207
|
-
"(后者删后 `resume-legs.ts` `drivePlanReview` 里对它的那一处调用回到只换体的 `{ ...ctx, body }`)、`CheckpointCtx.permissionModeReading`、" +
|
|
208
|
-
"`http/stored-body-replay.ts` 里 `StoredReplayDeployment` 的 `autonomy` 位与 `storedBodyReplayRefusal` 读 `deployment.autonomy` 的那一句(这一位只服务这一问;四个问点传 `deps.config` 整份,home 位还服务 `permissions` 一问,不动);" +
|
|
209
|
-
"`test/stored-request-prompt-profile-m30.test.ts` 的 SHARED 表里 `StoredBodyReplayRefusal` 那一行(rows 含 M12;M30 的登记行组已先删时该表已不在,这一句无事可做):rows 去掉 M12、整行并入同文件的 SITES(值 = 该行的 files 数组)",
|
|
210
|
-
afterRemoval: "没有响亮臂:删码之后仍停驻、在 7.121.x 及以前写下的行(把 `APPROVAL_TERMINAL_BACKSTOP_MS` 调长的部署,或跳过了带本条的版本直接升的库)在设了 " +
|
|
211
|
-
"`AUTONOMY=plan` 的部署上按体里的原词重放(运维的 plan 不再被按段重钳)—— 升级前把这些停驻的 run 决掉 / 取消,或升级窗内设 `AUTONOMY=read-only`",
|
|
212
|
-
reportKeys: [],
|
|
213
|
-
},
|
|
214
202
|
{
|
|
215
203
|
id: "M13",
|
|
216
204
|
introducedIn: "7.123.0",
|
|
@@ -630,15 +618,18 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
630
618
|
introducedIn: "7.147.0",
|
|
631
619
|
releasedOn: "2026-11-07",
|
|
632
620
|
removableAfter: "2026-12-07",
|
|
633
|
-
removal: "删 `src/plugins/execution-lane-rename-m40.ts` 整只文件;本文件的 M40 步(`renameRetiredExecutionLaneWordsM40` / `refuseWhileRunsHeartbeat
|
|
634
|
-
"`EXECUTION_LANE_RENAME_REFUSED_CODE
|
|
635
|
-
"`store-backend.ts` 的 `LocalBackend.ensureSchema` 第一步与 `executionLaneWordsRenamed`
|
|
621
|
+
removal: "删 `src/plugins/execution-lane-rename-m40.ts` 整只文件;本文件的 M40 步(`renameRetiredExecutionLaneWordsM40` / `refuseWhileRunsHeartbeat`〔读证据的那一处调用;证据读法 `readOlderReplicaEvidence` 与判活窗输入归 M52 行〕/ `ExecutionLaneRenameRefusedError` / " +
|
|
622
|
+
"`EXECUTION_LANE_RENAME_REFUSED_CODE`,`migrateStepsOn` 第一步那一行)、报告 / 收据 / 日志 / `NO_SQL_MIGRATIONS` 的 `executionLaneWordsRenamed`、头注 M40 段;" +
|
|
623
|
+
"`store-backend.ts` 的 `LocalBackend.ensureSchema` 第一步与 `executionLaneWordsRenamed` 字段;" +
|
|
636
624
|
"`local-session-store.ts` 的内核 `renameRetiredExecutionLaneWordsInCellsM40` 与数据根准入的具名读者 `readSessionMetaSidecarInWindow`(在内存里套的那一步;准入的解码点改用 `readSessionMetaSidecar`);" +
|
|
637
625
|
"`LocalSessionStore.renameRetiredExecutionLaneWordM40`、`FileApprovalAskStore.renameRetiredExecutionLaneWordM40` 与内核的 `renameExecutionLaneWord`;" +
|
|
638
626
|
"`approval-ask-store-sql.ts` / `execution-placement-sql.ts` 拒句里的 `retiredExecutionLaneHintM40(…)` 补句;`test/execution-lane-rename-m40.test.ts` 整只文件与 `test/execution-placement-switch-db-integration.test.ts` 的 M40 组",
|
|
639
627
|
afterRemoval: "既有读口的拒臂接住(响亮,不再改名):仍带旧词的行 —— 跳过了带本条的版本直接升的库、窗关之后还在写的更早版本副本 —— SQL 问行读口拒、会话执行格读口拒(`ExecutionPlacementRowError`)," +
|
|
640
628
|
"local 会话元数据边档整只严格拒启(`config.schema_mismatch`),local 问账日志由 M32 回填的词表外拒臂拒启(M32 删码之后:那只问行原样装回)。出路 = 升级时先过一次带本条的版本并起服",
|
|
641
|
-
stopThenStart:
|
|
629
|
+
stopThenStart: {
|
|
630
|
+
reason: "renames the retired execution-lane word \"in-process\" to \"none\" on stored approval asks and sessions; a replica of an earlier version would keep writing the old word after the rename",
|
|
631
|
+
owedOn: owesExecutionLaneRenameM40,
|
|
632
|
+
},
|
|
642
633
|
reportKeys: ["executionLaneWordsRenamed"],
|
|
643
634
|
},
|
|
644
635
|
{
|
|
@@ -649,10 +640,13 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
649
640
|
removal: "删本文件的 M41 步(`moveSessionCellsToSideTable` / `refuseSessionExtMoveWhileRunsHeartbeat` / `deploymentColumnsOnMeta` / `SessionExtMoveRefusedError` / `SESSION_EXT_MOVE_REFUSED_CODE` / " +
|
|
650
641
|
"`SessionExtMoveRefusalReason` / `sameCell` / `SESSION_EXT_MOVE_PAGE`,`migrateStepsOn` 里那两行)、报告 / 收据 / 日志 / `NO_SQL_MIGRATIONS` 的 `sessionExtRowsMoved` / `sessionMetaColumnsDropped`、头注 M41 段;" +
|
|
651
642
|
"M32 读形里的 `cellsOnSideTable` 判(它读本条的 `deploymentColumnsOnMeta`):M32 还在时改成恒旁表形 —— 删 M32 写 `session_meta` 八列的那些臂(② ④ ④b ⑥ 与 ③ 的 `migrateDeviceBindingsStatement` 臂)," +
|
|
652
|
-
"留 ③ 的旁表臂(`migrateDeviceBindingsOntoSideTableStatements`:围栏拒句的「采旧」在任何库上仍要能落到执行格);M32 先于本条删码时这些随 M32 一并删;`
|
|
643
|
+
"留 ③ 的旁表臂(`migrateDeviceBindingsOntoSideTableStatements`:围栏拒句的「采旧」在任何库上仍要能落到执行格);M32 先于本条删码时这些随 M32 一并删;`test/session-ext-move-db-integration.test.ts` 整只文件。能力名单 `REPLICA_CAPABILITIES` 里的 `M41` 不删(只增)",
|
|
653
644
|
afterRemoval: "跳过了带本条的版本直接升上来的库:`session_meta` 上仍带部署格列 ⇒ 起服列册对账拒启(`config.schema_mismatch`,那几列是本树之外的多余列,拒句不给 ALTER —— 出路是带本条的版本);" +
|
|
654
645
|
"部署格从旁表读,旧列上的值不被任何读者读。出路 = 升级时先过一次带本条的版本并起服",
|
|
655
|
-
stopThenStart:
|
|
646
|
+
stopThenStart: {
|
|
647
|
+
reason: "moves the session deployment cells from session_meta to session_ext and then retires the old columns; a cell an earlier replica writes between the copy and the retirement is lost, and every cell write it makes afterwards fails",
|
|
648
|
+
owedOn: owesSessionExtMoveM41,
|
|
649
|
+
},
|
|
656
650
|
reportKeys: ["sessionExtRowsMoved", "sessionMetaColumnsDropped"],
|
|
657
651
|
shapeChanges: [{ kind: "drop", table: "session_meta", columns: SESSION_EXT_MOVED_COLUMNS.map((c) => c.name) }],
|
|
658
652
|
},
|
|
@@ -662,11 +656,14 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
662
656
|
releasedOn: "2026-11-08",
|
|
663
657
|
removableAfter: "2026-12-08",
|
|
664
658
|
removal: "删 `src/plugins/scenario-prefix-switch-m42.ts` 整只文件;本文件的 M42 步(`switchScenarioPrefixM42` / `scannedIdentityColumnsM42`,`migrateStepsOn` 第一步那一行)、报告 / 收据 / 日志的 `scenarioPrefixSourcesScanned`、头注 M42 段;" +
|
|
665
|
-
"`
|
|
659
|
+
"`store-backend.ts` 的 `LocalBackend.runOneTimeMigrations` 里 M42 那一腿(`switchLocalScenarioPrefixM42`)与四只 File 店的 `scenarioPrefixOwnerRowsM42`(run / 会话 / 问 / 附件);`test/scenario-prefix-switch-m42.test.ts` 与 `test/scenario-prefix-switch-db-integration.test.ts` 两只文件。" +
|
|
666
660
|
"标记表 `one-time-migration-marker.ts` 不随删(常设:中央建表语句建它,`Skill` 豁免截点那一行的家);库里留下的 `M42` 标记行 / 数据根里的标记文件无人再读,不删",
|
|
667
661
|
afterRemoval: "没有响亮臂(如实):跳过了带本条的版本直接升的库,存量里以 `_scn:` 开头的身份被恢复管线读作某个人的场景(拆不出合文法的场景与人的 ⇒ `owner.malformed_subject` 响亮)。" +
|
|
668
662
|
"出路 = 升级时先过一次带本条的版本并起服(它扫一次、命中即拒启点名)",
|
|
669
|
-
stopThenStart:
|
|
663
|
+
stopThenStart: {
|
|
664
|
+
reason: "scans stored identities for the reserved scenario prefix and records a marker; an earlier replica does not reserve the prefix and would keep accepting such identities after the scan",
|
|
665
|
+
owedOn: owesScenarioPrefixSwitchM42,
|
|
666
|
+
},
|
|
670
667
|
reportKeys: ["scenarioPrefixSourcesScanned"],
|
|
671
668
|
},
|
|
672
669
|
{
|
|
@@ -789,9 +786,25 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
789
786
|
"报告 / 收据 / `changedAnything` / `readingFields` / `NO_SQL_MIGRATIONS` 的 `sessionDeletingSwitchMarked`、头注 M51 段;`test/session-deleting-state-db-integration.test.ts` 的 M51 组;" +
|
|
790
787
|
"库里留下的 `M51` 标记行无人再读,不删。**不随本条删**:在删态本身(列、围栏、规则 L 的在删半、终刀判令牌 —— 常设)、标记表 `one-time-migration-marker.ts`(常设)",
|
|
791
788
|
afterRemoval: "没有响亮臂(也无需):窗 30 天之后更早版本的副本早已不在;跳过了带本条的版本直接升上来的库 —— 跨两版以上的混跑本就不被支持(7.147.0 起的停-迁-起)。出路 = 升级时先过一次带本条的版本并起服",
|
|
792
|
-
stopThenStart:
|
|
789
|
+
stopThenStart: {
|
|
790
|
+
reason: "switches session deletion to a deleting state that every replica must read; an earlier replica does not read it and would accept a submission on a session this version is deleting",
|
|
791
|
+
owedOn: owesSessionDeletingSwitchM51,
|
|
792
|
+
},
|
|
793
793
|
reportKeys: ["sessionDeletingSwitchMarked"],
|
|
794
794
|
},
|
|
795
|
+
{
|
|
796
|
+
id: "M52",
|
|
797
|
+
introducedIn: "7.150.0",
|
|
798
|
+
releasedOn: "2026-11-09",
|
|
799
|
+
removableAfter: "2026-12-09",
|
|
800
|
+
removal: "删 `readOlderReplicaEvidence` 的心跳一半(按 `instance_id` 分组读 `task_run` 心跳的那一句、减去登记为知道该迁移的持有者那一步)与它的判活窗输入:`LiveRunWindowInput`、`SqlOneTimeMigrationInput` 里的那一半、" +
|
|
801
|
+
"`store-backend.ts` 的 `buildSqlOneTimeMigrationInput` 读 `runStaleSec` 的那一半、夹具 `test/helpers/db-schema-boot.ts` 的 `liveRunWindowMs`;停-迁-起守卫若已全部删码(M40 / M41 / M42 / M43 / M51 的窗都早于本条关)," +
|
|
802
|
+
"连同 `readOlderReplicaEvidence` 与 `stop-then-start-sentence.ts` 的 `OlderReplicaEvidence` / `OlderReplicaHolder` / `olderReplicaEvidenceSentence` 一并删;" +
|
|
803
|
+
"`test/run-liveness-single-source-gate.test.ts` ② 的 `one-time-migrations.ts` 白名单行与 ③ `store-backend.ts` 理由里「一次性迁移守卫」半句;`test/older-replica-evidence-db-integration.test.ts` 的证据组;头注 M52 段。" +
|
|
804
|
+
"**不随本条删**:副本能力登记(`replica-registration.ts`,能力名单只增)与起服准入(`serving-requirement.ts`)—— 常设",
|
|
805
|
+
afterRemoval: "没有响亮臂(如实):早于副本登记的副本不登记,删码之后它们对停-迁-起守卫完全不可见,忙与闲同 —— 发布日之后的天数不证明旧进程不在;出路只有运维:起带停-迁-起迁移的版本之前停光早于本条的副本",
|
|
806
|
+
reportKeys: [],
|
|
807
|
+
},
|
|
795
808
|
];
|
|
796
809
|
export function migrationWindowOpen(window, today) {
|
|
797
810
|
return window.removableAfter === null || today <= window.removableAfter;
|
|
@@ -808,7 +821,7 @@ export function migrationCarried(id) {
|
|
|
808
821
|
return ONE_TIME_MIGRATIONS.some((w) => w.id === id);
|
|
809
822
|
}
|
|
810
823
|
export function stopThenStartMigrations(registry = ONE_TIME_MIGRATIONS) {
|
|
811
|
-
return registry.flatMap((row) => (row.stopThenStart === undefined ? [] : [{ id: row.id,
|
|
824
|
+
return registry.flatMap((row) => (row.stopThenStart === undefined ? [] : [{ id: row.id, ...row.stopThenStart }]));
|
|
812
825
|
}
|
|
813
826
|
export const NO_SQL_MIGRATIONS = {
|
|
814
827
|
sessionRuleColumn: "file-store",
|
|
@@ -898,16 +911,27 @@ async function columnExists(db, dialect, table, column) {
|
|
|
898
911
|
export function createSqlImportedLayerRootSource(exec) {
|
|
899
912
|
return { candidateOwners: async () => ({ owners: await listRuleBucketOwners(exec), unnamedBuckets: 0 }), durable: new SqlDurableRulePartitionProvider(exec), isWholeBucketRefusal: isSqlBucketRefusal, isVanishedBucket: isSqlBucketVanished };
|
|
900
913
|
}
|
|
901
|
-
export async function runSqlOneTimeMigrations(dialect, pool, input) {
|
|
902
|
-
|
|
914
|
+
export async function runSqlOneTimeMigrations(dialect, pool, input, me = buildThisReplicaRegistration()) {
|
|
915
|
+
const head = async (db) => {
|
|
916
|
+
await assertReplicaTablesPresent(db, dialect, me);
|
|
917
|
+
await registerReplica(db, dialect, me, Date.now());
|
|
918
|
+
await assertServingRequirementsMet(db, me);
|
|
919
|
+
};
|
|
920
|
+
const locked = async (db) => {
|
|
921
|
+
await assertServingRequirementsMet(db, me);
|
|
922
|
+
return migrateOn(db, dialect, input);
|
|
923
|
+
};
|
|
924
|
+
return dialect === "mysql" ? withMysqlLock(pool, head, locked) : withPgLock(pool, head, locked);
|
|
903
925
|
}
|
|
904
|
-
async function withMysqlLock(pool, run) {
|
|
926
|
+
async function withMysqlLock(pool, head, run) {
|
|
905
927
|
const conn = await pool.getConnection();
|
|
906
928
|
let locked = false;
|
|
907
929
|
try {
|
|
930
|
+
const db = mysqlDb(conn);
|
|
931
|
+
await head(db);
|
|
908
932
|
await acquireEnsureSchemaLock(conn, "oneTimeMigrations");
|
|
909
933
|
locked = true;
|
|
910
|
-
return await run(
|
|
934
|
+
return await run(db);
|
|
911
935
|
}
|
|
912
936
|
finally {
|
|
913
937
|
if (locked)
|
|
@@ -915,13 +939,15 @@ async function withMysqlLock(pool, run) {
|
|
|
915
939
|
conn.release();
|
|
916
940
|
}
|
|
917
941
|
}
|
|
918
|
-
async function withPgLock(pool, run) {
|
|
942
|
+
async function withPgLock(pool, head, run) {
|
|
919
943
|
const client = await pool.connect();
|
|
920
944
|
let locked = false;
|
|
921
945
|
try {
|
|
946
|
+
const db = pgDb(client);
|
|
947
|
+
await head(db);
|
|
922
948
|
await acquirePgEnsureSchemaLock(client, "oneTimeMigrations");
|
|
923
949
|
locked = true;
|
|
924
|
-
return await run(
|
|
950
|
+
return await run(db);
|
|
925
951
|
}
|
|
926
952
|
finally {
|
|
927
953
|
if (locked)
|
|
@@ -1096,20 +1122,37 @@ async function markWithdrawEnabledM43(db, dialect, receipts, input) {
|
|
|
1096
1122
|
const marked = await db.rows(dialect === "mysql" ? `SELECT 1 AS present FROM ${MIGRATION_MARKER_TABLE} WHERE migration_id = ?` : `SELECT 1 AS present FROM ${MIGRATION_MARKER_TABLE} WHERE migration_id = $1`, [WITHDRAW_MARK_ID]);
|
|
1097
1123
|
if (marked.length > 0)
|
|
1098
1124
|
return;
|
|
1099
|
-
if ((await
|
|
1125
|
+
if ((await readOlderReplicaEvidence(db, dialect, input, "M43")) !== undefined)
|
|
1100
1126
|
return;
|
|
1101
1127
|
receipts.withdrawMarkWritten += await db.exec(markIfAbsentSql(dialect), [WITHDRAW_MARK_ID, Date.now()]);
|
|
1102
1128
|
}
|
|
1129
|
+
async function migrationMarked(read, dialect, id) {
|
|
1130
|
+
if (!(await tableExists({ rows: read }, dialect, MIGRATION_MARKER_TABLE)))
|
|
1131
|
+
return false;
|
|
1132
|
+
return (await read(`SELECT 1 AS present FROM ${MIGRATION_MARKER_TABLE} WHERE migration_id = '${id}'`)).length > 0;
|
|
1133
|
+
}
|
|
1134
|
+
async function owesExecutionLaneRenameM40(read, dialect) {
|
|
1135
|
+
return (await executionLaneWordRowsM40(read, dialect)).pendingRows > 0;
|
|
1136
|
+
}
|
|
1137
|
+
async function owesSessionExtMoveM41(read, dialect) {
|
|
1138
|
+
return (await deploymentColumnsOnMeta({ rows: read }, dialect)).length > 0;
|
|
1139
|
+
}
|
|
1140
|
+
async function owesScenarioPrefixSwitchM42(read, dialect) {
|
|
1141
|
+
return !(await migrationMarked(read, dialect, SCENARIO_PREFIX_MARKER_ID));
|
|
1142
|
+
}
|
|
1143
|
+
async function owesSessionDeletingSwitchM51(read, dialect) {
|
|
1144
|
+
return !(await migrationMarked(read, dialect, SESSION_DELETING_MARK_ID));
|
|
1145
|
+
}
|
|
1103
1146
|
export const SESSION_DELETING_MARK_ID = "M51";
|
|
1104
1147
|
export const SESSION_DELETING_SWITCH_REFUSED_CODE = "config.session_deleting_switch_refused";
|
|
1105
1148
|
export class SessionDeletingSwitchRefusedError extends StoreBootRefusalError {
|
|
1106
1149
|
code = SESSION_DELETING_SWITCH_REFUSED_CODE;
|
|
1107
|
-
reason = "
|
|
1108
|
-
constructor(
|
|
1109
|
-
super(`${SESSION_DELETING_SWITCH_REFUSED_CODE} (reason:
|
|
1110
|
-
`(a submission on a session being deleted is refused), but ${
|
|
1150
|
+
reason = "older_replica";
|
|
1151
|
+
constructor(evidence, tail = "") {
|
|
1152
|
+
super(`${SESSION_DELETING_SWITCH_REFUSED_CODE} (reason: older_replica): this version deletes a session through a deleting state that every replica must read ` +
|
|
1153
|
+
`(a submission on a session being deleted is refused), but ${olderReplicaEvidenceSentence(evidence)} — ` +
|
|
1111
1154
|
"a replica of an earlier version may still be serving; it does not read that state and would accept a submission on a session this version is deleting. Nothing was changed and no marker was written. " +
|
|
1112
|
-
stopThenStartAction(
|
|
1155
|
+
stopThenStartAction(evidence.after) +
|
|
1113
1156
|
tail);
|
|
1114
1157
|
this.name = "SessionDeletingSwitchRefusedError";
|
|
1115
1158
|
}
|
|
@@ -1117,12 +1160,11 @@ export class SessionDeletingSwitchRefusedError extends StoreBootRefusalError {
|
|
|
1117
1160
|
async function switchSessionDeletingM51(db, dialect, receipts, input) {
|
|
1118
1161
|
if (!(await tableExists(db, dialect, MIGRATION_MARKER_TABLE)))
|
|
1119
1162
|
await execUntil(db, migrationMarkerDdl(dialect), () => tableExists(db, dialect, MIGRATION_MARKER_TABLE));
|
|
1120
|
-
|
|
1121
|
-
if (marked.length > 0)
|
|
1163
|
+
if (!(await owesSessionDeletingSwitchM51((sql) => db.rows(sql), dialect)))
|
|
1122
1164
|
return;
|
|
1123
|
-
const
|
|
1124
|
-
if (
|
|
1125
|
-
throw new SessionDeletingSwitchRefusedError(
|
|
1165
|
+
const evidence = await readOlderReplicaEvidence(db, dialect, input, "M51");
|
|
1166
|
+
if (evidence !== undefined)
|
|
1167
|
+
throw new SessionDeletingSwitchRefusedError(evidence, await describeUpgradeDriftTail((sql) => db.rows(sql), dialect));
|
|
1126
1168
|
receipts.sessionDeletingSwitchMarked += await db.exec(markIfAbsentSql(dialect), [SESSION_DELETING_MARK_ID, Date.now()]);
|
|
1127
1169
|
}
|
|
1128
1170
|
export async function scannedIdentityColumnsM42(db, dialect) {
|
|
@@ -1142,8 +1184,7 @@ export async function scannedIdentityColumnsM42(db, dialect) {
|
|
|
1142
1184
|
export async function switchScenarioPrefixM42(db, dialect, receipts, input) {
|
|
1143
1185
|
if (!(await tableExists(db, dialect, MIGRATION_MARKER_TABLE)))
|
|
1144
1186
|
await execUntil(db, migrationMarkerDdl(dialect), () => tableExists(db, dialect, MIGRATION_MARKER_TABLE));
|
|
1145
|
-
|
|
1146
|
-
if (marked.length > 0)
|
|
1187
|
+
if (!(await owesScenarioPrefixSwitchM42((sql) => db.rows(sql), dialect)))
|
|
1147
1188
|
return;
|
|
1148
1189
|
const columns = await scannedIdentityColumnsM42(db, dialect);
|
|
1149
1190
|
const hits = [];
|
|
@@ -1156,9 +1197,9 @@ export async function switchScenarioPrefixM42(db, dialect, receipts, input) {
|
|
|
1156
1197
|
receipts.scenarioPrefixSourcesScanned += columns.length;
|
|
1157
1198
|
if (hits.length > 0)
|
|
1158
1199
|
throw new ScenarioPrefixSwitchRefusedError("stored_prefix", hits, undefined, await describeUpgradeDriftTail((sql) => db.rows(sql), dialect));
|
|
1159
|
-
const
|
|
1160
|
-
if (
|
|
1161
|
-
throw new ScenarioPrefixSwitchRefusedError("
|
|
1200
|
+
const evidence = await readOlderReplicaEvidence(db, dialect, input, "M42");
|
|
1201
|
+
if (evidence !== undefined)
|
|
1202
|
+
throw new ScenarioPrefixSwitchRefusedError("older_replica", [], evidence, await describeUpgradeDriftTail((sql) => db.rows(sql), dialect));
|
|
1162
1203
|
await db.exec(markIfAbsentSql(dialect), [SCENARIO_PREFIX_MARKER_ID, Date.now()]);
|
|
1163
1204
|
}
|
|
1164
1205
|
export const EXECUTION_LANE_RENAME_REFUSED_CODE = "config.execution_lane_rename_refused";
|
|
@@ -1191,16 +1232,20 @@ export class ExecutionLaneRenameWriteRefusedError extends StoreBootRefusalError
|
|
|
1191
1232
|
}
|
|
1192
1233
|
}
|
|
1193
1234
|
const EXECUTION_LANE_TABLES = [APPROVAL_ASK_TABLE, "session_meta", SESSION_EXT_TABLE];
|
|
1194
|
-
|
|
1235
|
+
async function executionLaneWordRowsM40(read, dialect) {
|
|
1195
1236
|
const tables = [];
|
|
1196
1237
|
for (const table of EXECUTION_LANE_TABLES)
|
|
1197
|
-
if (await columnExists(
|
|
1238
|
+
if (await columnExists({ rows: read }, dialect, table, "execution_lane"))
|
|
1198
1239
|
tables.push(table);
|
|
1199
1240
|
let pendingRows = 0;
|
|
1200
1241
|
for (const table of tables) {
|
|
1201
|
-
const [row] = await
|
|
1242
|
+
const [row] = await read(`SELECT COUNT(*) AS n FROM ${table} WHERE execution_lane = '${RETIRED_EXECUTION_LANE_WORD_M40}'`);
|
|
1202
1243
|
pendingRows += Number(row?.["n"] ?? 0);
|
|
1203
1244
|
}
|
|
1245
|
+
return { tables, pendingRows };
|
|
1246
|
+
}
|
|
1247
|
+
export async function renameRetiredExecutionLaneWordsM40(db, dialect, receipts, input) {
|
|
1248
|
+
const { tables, pendingRows } = await executionLaneWordRowsM40((sql) => db.rows(sql), dialect);
|
|
1204
1249
|
if (pendingRows === 0)
|
|
1205
1250
|
return;
|
|
1206
1251
|
await refuseWhileRunsHeartbeat(db, dialect, pendingRows, input);
|
|
@@ -1208,34 +1253,54 @@ export async function renameRetiredExecutionLaneWordsM40(db, dialect, receipts,
|
|
|
1208
1253
|
receipts.executionLaneWordsRenamed += await db.exec(dialect === "mysql" ? `UPDATE ${table} SET execution_lane = ? WHERE execution_lane = ?` : `UPDATE ${table} SET execution_lane = $1 WHERE execution_lane = $2`, [RENAMED_EXECUTION_LANE_M40, RETIRED_EXECUTION_LANE_WORD_M40]);
|
|
1209
1254
|
}
|
|
1210
1255
|
}
|
|
1211
|
-
async function
|
|
1256
|
+
export async function readOlderReplicaEvidence(db, dialect, input, migrationId) {
|
|
1212
1257
|
if (!(await tableExists(db, dialect, "task_run")))
|
|
1213
1258
|
return undefined;
|
|
1214
1259
|
const since = new Date(Date.now() - input.liveRunWindowMs);
|
|
1215
|
-
const
|
|
1216
|
-
|
|
1260
|
+
const groups = (await db.rows(`SELECT instance_id, COUNT(*) AS n, MAX(updated_at) AS newest FROM task_run WHERE status = 'running' AND updated_at >= ${dialect === "mysql" ? "?" : "$1"} GROUP BY instance_id`, [since]))
|
|
1261
|
+
.map((r) => ({ holder: typeof r["instance_id"] === "string" ? r["instance_id"] : undefined, runs: Number(r["n"] ?? 0), newest: r["newest"] }))
|
|
1262
|
+
.filter((g) => g.runs > 0);
|
|
1263
|
+
if (groups.length === 0)
|
|
1264
|
+
return undefined;
|
|
1265
|
+
const registered = await readReplicaRegistrations({ query: async (sql, params) => ({ rows: await db.rows(sql, params), affected: 0 }) }, dialect, groups.flatMap((g) => (g.holder === undefined ? [] : [g.holder])));
|
|
1266
|
+
let unregisteredRuns = 0;
|
|
1267
|
+
let newestMs = Number.NaN;
|
|
1268
|
+
const registeredWithout = [];
|
|
1269
|
+
for (const g of groups) {
|
|
1270
|
+
const r = g.holder === undefined ? undefined : registered.get(g.holder);
|
|
1271
|
+
if (r !== undefined && knowsCapability(r.capabilities, migrationId))
|
|
1272
|
+
continue;
|
|
1273
|
+
if (r === undefined)
|
|
1274
|
+
unregisteredRuns += g.runs;
|
|
1275
|
+
else
|
|
1276
|
+
registeredWithout.push({ instanceId: r.instanceId, releaseLine: r.releaseLine, hostName: r.hostName, pid: r.pid, runs: g.runs });
|
|
1277
|
+
const cell = g.newest instanceof Date ? g.newest : new Date(String(g.newest));
|
|
1278
|
+
if (!Number.isNaN(cell.getTime()) && !(cell.getTime() <= newestMs))
|
|
1279
|
+
newestMs = cell.getTime();
|
|
1280
|
+
}
|
|
1281
|
+
const liveRuns = unregisteredRuns + registeredWithout.reduce((n, h) => n + h.runs, 0);
|
|
1217
1282
|
if (liveRuns === 0)
|
|
1218
1283
|
return undefined;
|
|
1219
|
-
const newestCell = row?.["newest"];
|
|
1220
|
-
const newest = newestCell instanceof Date ? newestCell : new Date(String(newestCell));
|
|
1221
1284
|
const now = Date.now();
|
|
1222
|
-
const restartAtMs = (Number.isNaN(
|
|
1285
|
+
const restartAtMs = (Number.isNaN(newestMs) ? now : newestMs) + input.liveRunWindowMs;
|
|
1223
1286
|
return {
|
|
1287
|
+
migration: migrationId,
|
|
1224
1288
|
liveRuns,
|
|
1225
1289
|
windowSec: Math.round(input.liveRunWindowMs / 1000),
|
|
1226
1290
|
restartAfter: new Date(restartAtMs).toISOString(),
|
|
1227
1291
|
after: `${new Date(restartAtMs).toISOString()} (${Math.max(0, Math.ceil((restartAtMs - now) / 1000))}s from now)`,
|
|
1292
|
+
unregisteredRuns,
|
|
1293
|
+
registeredWithout: registeredWithout.sort((a, b) => a.instanceId.localeCompare(b.instanceId)),
|
|
1228
1294
|
};
|
|
1229
1295
|
}
|
|
1230
1296
|
async function refuseWhileRunsHeartbeat(db, dialect, pendingRows, input) {
|
|
1231
|
-
const
|
|
1232
|
-
if (
|
|
1297
|
+
const evidence = await readOlderReplicaEvidence(db, dialect, input, "M40");
|
|
1298
|
+
if (evidence === undefined)
|
|
1233
1299
|
return;
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
stopThenStartAction(after) +
|
|
1300
|
+
throw new ExecutionLaneRenameRefusedError(pendingRows, evidence.liveRuns, evidence.restartAfter, `${pendingRows} stored rows (approval asks / sessions) carry the execution lane word "${RETIRED_EXECUTION_LANE_WORD_M40}", the name an earlier version gave the lane with no execution environment, ` +
|
|
1301
|
+
`which this version calls "${RENAMED_EXECUTION_LANE_M40}" and does not read; this boot renames them (one-time migration M40), but ${olderReplicaEvidenceSentence(evidence)} ` +
|
|
1302
|
+
`— a replica of an earlier version may still be serving, and it would keep writing the old word after the rename. No stored row was changed. ` +
|
|
1303
|
+
stopThenStartAction(evidence.after) +
|
|
1239
1304
|
(await describeUpgradeDriftTail((sql) => db.rows(sql), dialect)));
|
|
1240
1305
|
}
|
|
1241
1306
|
export const SESSION_EXT_MOVE_REFUSED_CODE = "config.session_ext_move_refused";
|
|
@@ -1266,13 +1331,13 @@ export async function refuseSessionExtMoveWhileRunsHeartbeat(db, dialect, input)
|
|
|
1266
1331
|
const columns = await deploymentColumnsOnMeta(db, dialect);
|
|
1267
1332
|
if (columns.length === 0)
|
|
1268
1333
|
return;
|
|
1269
|
-
const
|
|
1270
|
-
if (
|
|
1334
|
+
const evidence = await readOlderReplicaEvidence(db, dialect, input, "M41");
|
|
1335
|
+
if (evidence === undefined)
|
|
1271
1336
|
return;
|
|
1272
|
-
throw new SessionExtMoveRefusedError("
|
|
1273
|
-
`(one-time migration M41: copy, verify, then drop the columns), but ${
|
|
1274
|
-
"a replica of an earlier version
|
|
1275
|
-
stopThenStartAction(
|
|
1337
|
+
throw new SessionExtMoveRefusedError("older_replica", `${columns.length} session cells (${columns.map((c) => c.name).join(", ")}) still live on session_meta; this version keeps them on the side table ${SESSION_EXT_TABLE} and moves them on this boot ` +
|
|
1338
|
+
`(one-time migration M41: copy, verify, then drop the columns), but ${olderReplicaEvidenceSentence(evidence)} — ` +
|
|
1339
|
+
"a replica of an earlier version may still be serving; a cell it writes between the copy and the drop would be lost, and every cell write it makes after the drop fails. Nothing was changed. " +
|
|
1340
|
+
stopThenStartAction(evidence.after) +
|
|
1276
1341
|
(await describeUpgradeDriftTail((sql) => db.rows(sql), dialect)));
|
|
1277
1342
|
}
|
|
1278
1343
|
export async function moveSessionCellsToSideTable(db, dialect, receipts) {
|
package/dist/plugins/pg-pool.js
CHANGED
|
@@ -27,6 +27,8 @@ import { INBOX_PAYLOAD_COLUMN } from "./inbox-payload-column.js";
|
|
|
27
27
|
import { CENTRAL_SCHEMA_INDEXES } from "./schema-indexes.js";
|
|
28
28
|
import { PRINCIPAL_MAX_BYTES } from "../principal-shape.js";
|
|
29
29
|
import { SKILL_EXEMPTION_CUTOFF_ID, markIfAbsentSql, migrationMarkerDdl } from "./one-time-migration-marker.js";
|
|
30
|
+
import { replicaRegistrationDdl } from "./replica-registration.js";
|
|
31
|
+
import { servingRequirementDdl } from "./serving-requirement.js";
|
|
30
32
|
export const PG_SCHEMA_STATEMENTS = [
|
|
31
33
|
`CREATE TABLE IF NOT EXISTS task_run (
|
|
32
34
|
task_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
@@ -421,6 +423,8 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
421
423
|
PRIMARY KEY (store_id, path)
|
|
422
424
|
)`,
|
|
423
425
|
migrationMarkerDdl("pg"),
|
|
426
|
+
replicaRegistrationDdl("pg"),
|
|
427
|
+
servingRequirementDdl("pg"),
|
|
424
428
|
];
|
|
425
429
|
export function pgPoolOptions(pgCfg, dbQueryTimeoutMs) {
|
|
426
430
|
return {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { anchorUnder } from "./exec-path-law.js";
|
|
2
3
|
import { FileError, } from "@sema-agent/core";
|
|
3
4
|
import { runSymlinkChain, shellQuote, kindFromMode } from "./remote-shell.js";
|
|
4
5
|
import { classifyFsStderr, fileErrorFromExec } from "./remote-env-file-error.js";
|
|
@@ -60,11 +61,12 @@ export function createPosixShellFs(deps) {
|
|
|
60
61
|
const names = r.value.stdout.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
61
62
|
const out = [];
|
|
62
63
|
for (const name of names) {
|
|
63
|
-
const
|
|
64
|
+
const entry = anchorUnder(path.posix, abs, name);
|
|
65
|
+
const info = await fs.statFileInfo(entry, abortSignal);
|
|
64
66
|
if (info.ok)
|
|
65
67
|
out.push(info.value);
|
|
66
68
|
else
|
|
67
|
-
out.push({ name, path:
|
|
69
|
+
out.push({ name, path: entry, kind: "file", size: 0, mtimeMs: 0 });
|
|
68
70
|
}
|
|
69
71
|
return ok(out);
|
|
70
72
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { anchoredPath, joinedPath } from "./exec-path-law.js";
|
|
2
3
|
import os from "node:os";
|
|
3
4
|
import fs from "node:fs/promises";
|
|
4
5
|
import { spawn } from "node:child_process";
|
|
@@ -65,7 +66,7 @@ export class RemoteAdbExecutionEnv {
|
|
|
65
66
|
this.injectedConnectOnce = deps?.connectOnce;
|
|
66
67
|
}
|
|
67
68
|
resolve(p) {
|
|
68
|
-
return
|
|
69
|
+
return anchoredPath(path.posix, this.cwd, p);
|
|
69
70
|
}
|
|
70
71
|
workspaceHandle() {
|
|
71
72
|
return { sandboxId: this.cfg.serial, provider: PROVIDER, mountPath: this.cwd };
|
|
@@ -328,7 +329,7 @@ export class RemoteAdbExecutionEnv {
|
|
|
328
329
|
return ok(this.resolve(p));
|
|
329
330
|
}
|
|
330
331
|
async joinPath(parts) {
|
|
331
|
-
return ok(path.posix
|
|
332
|
+
return ok(joinedPath(path.posix, parts));
|
|
332
333
|
}
|
|
333
334
|
async fsGate(p) {
|
|
334
335
|
if (this.destroyed)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { posix } from "node:path";
|
|
2
|
+
import { anchoredPath, anchorUnder, joinedPath } from "./exec-path-law.js";
|
|
2
3
|
import { ExecutionError, FileError, RemoteExecutionError, } from "@sema-agent/core";
|
|
3
4
|
import { capabilitiesFor } from "../execution-lane-caps.js";
|
|
4
5
|
import { DEVICE_ERROR_CODES, isDeviceAbsolutePath, } from "../device-store.js";
|
|
@@ -174,7 +175,7 @@ export class RemoteDeviceExecutionEnv {
|
|
|
174
175
|
"performing a NON-atomic write here is exactly the silent downgrade guarded writes exist to prevent (#157). Refusing; the per-task factory mints a correctly-shaped env on the next task.", absPath);
|
|
175
176
|
}
|
|
176
177
|
resolve(p) {
|
|
177
|
-
return
|
|
178
|
+
return anchoredPath(DEVICE_PATH_FAMILY, this.cwd, p);
|
|
178
179
|
}
|
|
179
180
|
instructionCwd(requested) {
|
|
180
181
|
return requested === undefined ? this.cwd : this.resolve(requested);
|
|
@@ -183,7 +184,7 @@ export class RemoteDeviceExecutionEnv {
|
|
|
183
184
|
return ok(this.resolve(p));
|
|
184
185
|
}
|
|
185
186
|
async joinPath(parts) {
|
|
186
|
-
return ok(
|
|
187
|
+
return ok(joinedPath(DEVICE_PATH_FAMILY, parts));
|
|
187
188
|
}
|
|
188
189
|
workspaceHandle() {
|
|
189
190
|
return {
|
|
@@ -557,7 +558,7 @@ export class RemoteDeviceExecutionEnv {
|
|
|
557
558
|
return this.wrongOutcome("stat", o.kind, abs);
|
|
558
559
|
if (!o.info)
|
|
559
560
|
return { ok: false, error: new FileError("unknown", "device returned a stat outcome without info — protocol violation", abs) };
|
|
560
|
-
return ok(toFileInfo(o.info));
|
|
561
|
+
return ok(toFileInfo(o.info, abs));
|
|
561
562
|
});
|
|
562
563
|
}
|
|
563
564
|
async doReadLink(p, abortSignal) {
|
|
@@ -642,7 +643,7 @@ export class RemoteDeviceExecutionEnv {
|
|
|
642
643
|
return this.wrongOutcome("stat", o.kind, abs);
|
|
643
644
|
if (!o.entries)
|
|
644
645
|
return { ok: false, error: new FileError("unknown", "device returned a stat outcome without entries — protocol violation", abs) };
|
|
645
|
-
return ok(o.entries.map(toFileInfo));
|
|
646
|
+
return ok(o.entries.map((w) => toFileInfo(w, anchorUnder(DEVICE_PATH_FAMILY, abs, posix.basename(w.path)))));
|
|
646
647
|
});
|
|
647
648
|
}
|
|
648
649
|
async writeFile(p, content, abortSignal) {
|
|
@@ -738,8 +739,9 @@ function mergedEnv(taskEnv, commandEnv) {
|
|
|
738
739
|
function markTruncated(text, truncated) {
|
|
739
740
|
return truncated === true ? `${text}\n[output truncated by the device executor]` : text;
|
|
740
741
|
}
|
|
741
|
-
|
|
742
|
-
|
|
742
|
+
const DEVICE_PATH_FAMILY = { sep: posix.sep, isAbsolute: isDeviceAbsolutePath };
|
|
743
|
+
function toFileInfo(w, at) {
|
|
744
|
+
return { name: posix.basename(at), path: at, kind: w.kind === "dir" ? "directory" : w.kind === "symlink" ? "symlink" : "file", size: w.size, mtimeMs: w.mtimeMs };
|
|
743
745
|
}
|
|
744
746
|
function toWireExpectation(e) {
|
|
745
747
|
return {
|
|
@@ -45,7 +45,7 @@ export interface RemoteContainerEnvConfig extends RemoteEnvObservers, LaneHomeCo
|
|
|
45
45
|
template?: string;
|
|
46
46
|
/** Sandbox lifetime in ms (E2B billing window). Default {@link DEFAULT_TIMEOUT_MS}. */
|
|
47
47
|
timeoutMs?: number;
|
|
48
|
-
/** Workspace root inside the VM;
|
|
48
|
+
/** Workspace root inside the VM; a relative path is anchored at it (`exec-path-law.ts`: anchored, never folded). Default {@link DEFAULT_MOUNT_PATH}. */
|
|
49
49
|
mountPath?: string;
|
|
50
50
|
/** Outbound network for the sandbox (clay: service needs external-network capability). Default `true`. */
|
|
51
51
|
allowInternetAccess?: boolean;
|
|
@@ -365,6 +365,8 @@ export declare class RemoteContainerExecutionEnv implements RemoteExecutionEnv,
|
|
|
365
365
|
private ensureConnected;
|
|
366
366
|
private fsReady;
|
|
367
367
|
private tempResult;
|
|
368
|
+
/** 提供方的条目 → `FileInfo`。`path` 取本环境的拼法(`at`:被问的那只路径 / 列目录时锚在它之下的条目),不取提供方回报的那一份
|
|
369
|
+
* —— 提供方可能已把 `..` 折掉,而执行缝路径律(`exec-path-law.ts`)要求条目名在交来的路径之下、能原样再交回来。 */
|
|
368
370
|
private toFileInfo;
|
|
369
371
|
private fileError;
|
|
370
372
|
/** A thrown provider error → this exec's error. The two cuts (aborted, the provider's own command deadline) are a command that RAN: they carry what it had
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { anchoredPath, anchorUnder, joinedPath } from "./exec-path-law.js";
|
|
2
3
|
import { Sandbox, CommandExitError, FileType } from "e2b";
|
|
3
4
|
import { FileError, ExecutionError, RemoteExecutionError, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
|
|
4
5
|
import { capabilitiesFor } from "../execution-lane-caps.js";
|
|
@@ -862,7 +863,7 @@ export class RemoteContainerExecutionEnv {
|
|
|
862
863
|
return ok(this.resolve(p));
|
|
863
864
|
}
|
|
864
865
|
async joinPath(parts) {
|
|
865
|
-
return ok(path.posix
|
|
866
|
+
return ok(joinedPath(path.posix, parts));
|
|
866
867
|
}
|
|
867
868
|
async readTextFile(p, abortSignal) {
|
|
868
869
|
const sbx = await this.fsReady(p);
|
|
@@ -934,8 +935,9 @@ export class RemoteContainerExecutionEnv {
|
|
|
934
935
|
if (!sbx.ok)
|
|
935
936
|
return sbx;
|
|
936
937
|
try {
|
|
937
|
-
const
|
|
938
|
-
|
|
938
|
+
const abs = this.resolve(p);
|
|
939
|
+
const info = await this.rpc("files.getInfo", (signal) => sbx.value.files.getInfo(abs, { signal }), abortSignal);
|
|
940
|
+
return ok(this.toFileInfo(info, abs));
|
|
939
941
|
}
|
|
940
942
|
catch (e) {
|
|
941
943
|
return { ok: false, error: this.fileError(e, this.resolve(p), abortSignal) };
|
|
@@ -946,8 +948,9 @@ export class RemoteContainerExecutionEnv {
|
|
|
946
948
|
if (!sbx.ok)
|
|
947
949
|
return sbx;
|
|
948
950
|
try {
|
|
949
|
-
const
|
|
950
|
-
|
|
951
|
+
const abs = this.resolve(p);
|
|
952
|
+
const entries = await this.rpc("files.list", (signal) => sbx.value.files.list(abs, { signal }), abortSignal);
|
|
953
|
+
return ok(entries.map((e) => this.toFileInfo(e, anchorUnder(path.posix, abs, e.name))));
|
|
951
954
|
}
|
|
952
955
|
catch (e) {
|
|
953
956
|
return { ok: false, error: this.fileError(e, this.resolve(p), abortSignal) };
|
|
@@ -1044,7 +1047,7 @@ export class RemoteContainerExecutionEnv {
|
|
|
1044
1047
|
return this.tempResult(r, "mktemp");
|
|
1045
1048
|
}
|
|
1046
1049
|
resolve(p) {
|
|
1047
|
-
return
|
|
1050
|
+
return anchoredPath(path.posix, this.cwd, p);
|
|
1048
1051
|
}
|
|
1049
1052
|
async rpc(label, op, userSignal, timeoutMs = this.cfg.rpcTimeoutMs) {
|
|
1050
1053
|
const deadline = new AbortController();
|
|
@@ -1163,11 +1166,11 @@ export class RemoteContainerExecutionEnv {
|
|
|
1163
1166
|
return { ok: false, error: new FileError("unknown", r.value.stderr.trim() || `${op} failed`) };
|
|
1164
1167
|
return ok(r.value.stdout.trim());
|
|
1165
1168
|
}
|
|
1166
|
-
toFileInfo(e) {
|
|
1169
|
+
toFileInfo(e, at) {
|
|
1167
1170
|
const kind = e.symlinkTarget ? "symlink" : e.type === FileType.DIR ? "directory" : "file";
|
|
1168
1171
|
return {
|
|
1169
1172
|
name: e.name,
|
|
1170
|
-
path:
|
|
1173
|
+
path: at,
|
|
1171
1174
|
kind,
|
|
1172
1175
|
size: e.size ?? 0,
|
|
1173
1176
|
mtimeMs: e.modifiedTime ? e.modifiedTime.getTime() : 0,
|
|
@@ -112,7 +112,7 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
|
|
|
112
112
|
/**
|
|
113
113
|
* core 7.56.0 [ref]:shell 子进程继承的环境里**收不到**的条目(名 + 规则,从不带值)—— 与 {@link cdPath} 同一只组合的读面
|
|
114
114
|
* (`server-secret-env.ts` 的 `withheldFromHostSubprocess`:继承组合扣下的发现,减去 `cfg.env` 这条显式授予重供的键)。逐命令的 `options.env`
|
|
115
|
-
* 是引擎自己交的显式授予,它知道(core
|
|
115
|
+
* 是引擎自己交的显式授予,它知道(core [ref] 同一残余)。只读:不披露、不计数、不碰 shell。
|
|
116
116
|
*/
|
|
117
117
|
get withheldEnv(): readonly SecretEnvFinding[];
|
|
118
118
|
private readonly workspaceDir;
|