@sema-agent/server 7.138.0 → 7.139.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 +276 -7
- package/README.md +7 -4
- package/README.zh-CN.md +6 -4
- package/USAGE.md +73 -29
- package/deploy/sema-up/README.md +1 -1
- package/deploy/sema-up/lib/lanes.json +3 -2
- package/dist/adoption/wire.d.ts +3 -3
- package/dist/approval-card.d.ts +20 -20
- package/dist/bench/s1/arms.d.ts +1 -1
- package/dist/bench/s1/arms.js +1 -3
- package/dist/bench/s1/run-firm.js +2 -2
- package/dist/boot/device-lane.d.ts +8 -3
- package/dist/boot/device-lane.js +7 -2
- package/dist/boot/execution-env.d.ts +4 -2
- package/dist/boot/execution-env.js +16 -24
- package/dist/boot/execution-lane.d.ts +1 -1
- package/dist/boot/governance-seams.d.ts +3 -2
- package/dist/boot/governance-seams.js +9 -11
- package/dist/boot/memory-consolidation.js +2 -2
- package/dist/boot/resolve-spec.d.ts +1 -1
- package/dist/boot/resolve-spec.js +8 -9
- package/dist/boot/retention-lane.d.ts +5 -5
- package/dist/boot/retention-lane.js +0 -6
- package/dist/boot/runner-deps.d.ts +3 -1
- package/dist/boot/runner-deps.js +2 -1
- package/dist/boot/session-faces.d.ts +0 -3
- package/dist/boot/session-faces.js +15 -8
- package/dist/boot/stage-02-web-admin-pull.js +2 -2
- package/dist/boot/stage-05-execution-env.js +2 -2
- package/dist/boot/stage-06-runners.js +0 -1
- package/dist/boot/stage-09-leader.js +1 -2
- package/dist/boot/stage-10-http-server.js +2 -2
- package/dist/boot/stores.js +4 -4
- package/dist/boot/web-admin.js +2 -2
- package/dist/capabilities/mcp-probe.js +1 -1
- package/dist/capabilities/scenarios.d.ts +1 -1
- package/dist/capability-facts.d.ts +15 -7
- package/dist/capability-facts.js +8 -3
- package/dist/config-catalog.js +7 -7
- package/dist/config-invariants.d.ts +23 -3
- package/dist/config-invariants.js +42 -0
- package/dist/config-provider.d.ts +19 -24
- package/dist/config-provider.js +27 -12
- package/dist/config-types.d.ts +10 -5
- package/dist/config.d.ts +33 -0
- package/dist/config.js +44 -15
- package/dist/delegated-session-owner.d.ts +2 -2
- package/dist/deployment-shape.d.ts +1 -1
- package/dist/device-enrollment.d.ts +41 -2
- package/dist/device-enrollment.js +27 -12
- package/dist/device-store.d.ts +58 -85
- package/dist/device-store.js +19 -49
- package/dist/device-ws-hub.js +2 -1
- package/dist/env-name-allowlist-knobs.d.ts +5 -4
- package/dist/exec-cut.d.ts +60 -0
- package/dist/exec-cut.js +36 -0
- package/dist/execution-lane-caps.d.ts +19 -0
- package/dist/execution-lane-caps.js +10 -0
- package/dist/execution-placement.d.ts +259 -0
- package/dist/execution-placement.js +199 -0
- package/dist/field-conflict.d.ts +3 -2
- package/dist/field-conflict.js +1 -0
- package/dist/hosted-posture.d.ts +2 -2
- package/dist/http/admission.js +8 -19
- package/dist/http/checkpoint-ctx.d.ts +21 -12
- package/dist/http/checkpoint-ctx.js +4 -2
- package/dist/http/claimed-run.d.ts +1 -1
- package/dist/http/durable-run-leg.js +9 -5
- package/dist/http/execution-context.d.ts +218 -0
- package/dist/http/execution-context.js +330 -0
- package/dist/http/resume-legs.js +50 -20
- package/dist/http/route-ctx.d.ts +14 -8
- package/dist/http/routes/background.d.ts +29 -1
- package/dist/http/routes/background.js +29 -2
- package/dist/http/routes/devices.d.ts +13 -19
- package/dist/http/routes/devices.js +21 -26
- package/dist/http/routes/session-sync.js +110 -15
- package/dist/http/routes/sessions.d.ts +6 -0
- package/dist/http/routes/sessions.js +115 -11
- package/dist/http/routes/side-query.d.ts +12 -4
- package/dist/http/routes/side-query.js +4 -5
- package/dist/http/routes/tasks.js +37 -31
- package/dist/http/server.d.ts +35 -19
- package/dist/http/server.js +11 -0
- package/dist/http/session-inherit-env.d.ts +3 -4
- package/dist/http/session-inherit-env.js +1 -1
- package/dist/http/wire-types.d.ts +8 -12
- package/dist/index.d.ts +1 -0
- package/dist/memory-sync-client.d.ts +53 -14
- package/dist/memory-sync-client.js +196 -116
- package/dist/memory-sync.d.ts +1 -1
- package/dist/memory-sync.js +3 -2
- package/dist/observability/cost-taxonomy.d.ts +2 -2
- package/dist/observability/fail-open.d.ts +12 -0
- package/dist/observability/fail-open.js +12 -0
- package/dist/observability/run-terminal.js +0 -1
- package/dist/orchestration/workflow-notify-journal.js +4 -4
- package/dist/org-memory-admission.d.ts +4 -3
- package/dist/org-memory-admission.js +6 -6
- package/dist/parked-decide.d.ts +34 -9
- package/dist/parked-decide.js +12 -19
- package/dist/parked-revive-surface.d.ts +12 -15
- package/dist/parked-revive-surface.js +3 -6
- package/dist/peer-seat.d.ts +13 -7
- package/dist/peer-seat.js +37 -3
- package/dist/plugins/approval-ask-store-file.d.ts +7 -0
- package/dist/plugins/approval-ask-store-file.js +11 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +6 -0
- package/dist/plugins/approval-ask-store-memory.js +12 -0
- package/dist/plugins/approval-ask-store-sql.d.ts +22 -5
- package/dist/plugins/approval-ask-store-sql.js +30 -12
- package/dist/plugins/caching-session-store.d.ts +6 -2
- package/dist/plugins/caching-session-store.js +9 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +6 -5
- package/dist/plugins/checkpoint-store-sql.js +5 -7
- package/dist/plugins/device-store-sql.d.ts +25 -51
- package/dist/plugins/device-store-sql.js +49 -103
- package/dist/plugins/execution-placement-sql.d.ts +151 -0
- package/dist/plugins/execution-placement-sql.js +223 -0
- package/dist/plugins/file-history-store-sql.d.ts +7 -6
- package/dist/plugins/file-history-store-sql.js +5 -5
- package/dist/plugins/file-run-store.d.ts +6 -1
- package/dist/plugins/file-run-store.js +4 -0
- package/dist/plugins/fork-routing-session-store.d.ts +6 -0
- package/dist/plugins/fork-routing-session-store.js +16 -0
- package/dist/plugins/local-session-store.d.ts +149 -50
- package/dist/plugins/local-session-store.js +382 -107
- package/dist/plugins/memory-key-guards.d.ts +4 -0
- package/dist/plugins/memory-key-guards.js +1 -0
- package/dist/plugins/memory-run-store.d.ts +1 -1
- package/dist/plugins/one-time-migrations.d.ts +111 -3
- package/dist/plugins/one-time-migrations.js +380 -10
- package/dist/plugins/outcome-ledger-sql.d.ts +2 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +13 -15
- package/dist/plugins/permission-rule-store-sql.js +13 -16
- package/dist/plugins/pg-pool.d.ts +7 -3
- package/dist/plugins/pg-session-storage.d.ts +31 -20
- package/dist/plugins/pg-session-storage.js +120 -52
- package/dist/plugins/remote-env-adb.d.ts +20 -4
- package/dist/plugins/remote-env-adb.js +70 -62
- package/dist/plugins/remote-env-device.d.ts +30 -12
- package/dist/plugins/remote-env-device.js +18 -17
- package/dist/plugins/remote-env-e2b.d.ts +18 -4
- package/dist/plugins/remote-env-e2b.js +41 -42
- package/dist/plugins/remote-env-file-error.d.ts +2 -0
- package/dist/plugins/remote-env-file-error.js +1 -1
- package/dist/plugins/remote-env-host.d.ts +21 -8
- package/dist/plugins/remote-env-host.js +66 -70
- package/dist/plugins/remote-env-k8s.d.ts +11 -16
- package/dist/plugins/remote-env-k8s.js +61 -54
- package/dist/plugins/remote-env-local-docker.d.ts +29 -6
- package/dist/plugins/remote-env-local-docker.js +46 -59
- package/dist/plugins/remote-env-ssh.d.ts +16 -4
- package/dist/plugins/remote-env-ssh.js +72 -56
- package/dist/plugins/remote-shell.d.ts +100 -3
- package/dist/plugins/remote-shell.js +166 -2
- package/dist/plugins/retention-store-sql.d.ts +9 -6
- package/dist/plugins/retention-store-sql.js +7 -1
- package/dist/plugins/run-store-sql.d.ts +13 -3
- package/dist/plugins/run-store-sql.js +5 -0
- package/dist/plugins/schema-consistency.d.ts +15 -5
- package/dist/plugins/schema-consistency.js +14 -8
- package/dist/plugins/schema-drift-sentence.d.ts +27 -0
- package/dist/plugins/schema-drift-sentence.js +38 -0
- package/dist/plugins/session-fork.d.ts +54 -0
- package/dist/plugins/session-fork.js +38 -1
- package/dist/plugins/session-row-sql.d.ts +22 -0
- package/dist/plugins/session-row-sql.js +81 -0
- package/dist/plugins/session-store.d.ts +1 -1
- package/dist/plugins/store-backend.d.ts +1 -1
- package/dist/plugins/store-backend.js +19 -7
- package/dist/plugins/store-contracts.d.ts +25 -5
- package/dist/plugins/tidb-pool.d.ts +12 -7
- package/dist/plugins/tidb-pool.js +9 -3
- package/dist/plugins/tidb-session-store.d.ts +57 -26
- package/dist/plugins/tidb-session-store.js +118 -50
- package/dist/plugins/tool-result-store-sql.d.ts +3 -2
- package/dist/plugins/tool-result-store-sql.js +18 -10
- package/dist/project-memory.d.ts +9 -15
- package/dist/project-memory.js +5 -4
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +6 -7
- package/dist/request-value-closure.js +1 -3
- package/dist/run-local.d.ts +6 -0
- package/dist/run-local.js +24 -20
- package/dist/runs.d.ts +2 -1
- package/dist/runs.js +3 -1
- package/dist/security.d.ts +38 -12
- package/dist/security.js +8 -2
- package/dist/server-secret-env.d.ts +9 -5
- package/dist/session-children-retire.d.ts +27 -9
- package/dist/session-children-retire.js +9 -5
- package/dist/session-incarnation.d.ts +19 -9
- package/dist/session-incarnation.js +4 -2
- package/dist/session-row.d.ts +114 -5
- package/dist/session-row.js +55 -5
- package/dist/session-titler.d.ts +2 -2
- package/dist/task-a2a.d.ts +1 -1
- package/dist/task-cwd.d.ts +6 -1
- package/dist/task-mcp.d.ts +3 -3
- package/dist/tool-approval.d.ts +6 -0
- package/dist/tool-approval.js +8 -1
- package/dist/trace/project.d.ts +1 -0
- package/dist/trace/project.js +1 -0
- package/dist/trace/self-minted-wire.d.ts +111 -2
- package/dist/trace/self-minted-wire.js +38 -1
- package/dist/url-refusal.d.ts +1 -1
- package/dist/web-admin-client/apply-effective.d.ts +17 -3
- package/dist/web-admin-client/apply-effective.js +20 -10
- package/dist/web-admin-client/skills-mcp.js +3 -2
- package/dist/web-admin-client/types.d.ts +6 -0
- package/package.json +3 -3
- package/wire-changes/server@7.139.0.json +348 -0
- package/dist/http/device-placement.d.ts +0 -175
- package/dist/http/device-placement.js +0 -145
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **执行放置的 SQL 唯一模块** —— `session_meta` 上 `execution_*` 七列 + `fork_key` 一列的列定义、住客谓词、行 → 格解码、每一种转换的语句,
|
|
3
|
+
* 与「写事务内先锁根行再读认领」那一读。规则在 `src/execution-placement.ts` 头注;本模块只说 SQL。
|
|
4
|
+
*
|
|
5
|
+
* ## 属主
|
|
6
|
+
* · **列定义唯一属主**(两方言):中央 DDL(`tidb-pool.ts` 的 `CREATE TABLE session_meta`、`pg-session-storage.ts` 的 `PG_SESSION_SCHEMA`)内插
|
|
7
|
+
* {@link sessionMetaPlacementColumnsDdl}(先例:`SESSION_META_INCARNATION_COLUMN`);起服列册对账(`schema-consistency.ts`)从同一份建表文本解析期望列集。
|
|
8
|
+
* 存量库的加列只由一次性迁移做,`ensureSchema` 不 ALTER(SCHEMA POLICY)。
|
|
9
|
+
* · **语句唯一属主**:会话店(执行席、导入写口)与设备店(首绑 / 换绑)、一次性迁移(搬绑定 / 回填)的事务都借这里的语句,别处零手写。
|
|
10
|
+
* 语句是方言无关的 `{ text, values }`(调用方用自己的连接跑:SqlDriver 的 `SqlTxConn`、node-pg 的客户端、mysql2 的连接都行)。
|
|
11
|
+
*
|
|
12
|
+
* ## 住客谓词(写与 judged 复核的唯一定址)
|
|
13
|
+
* `session_id = :root AND incarnation = :inc AND owner <=> :owner(PG:IS NOT DISTINCT FROM)AND placement_kind IS NULL` —— core 行期望判官 `sessionRowMatches`
|
|
14
|
+
* (属主 + 化身;会话行格写原语在锁住的行上判的就是它,`session-row.ts`)的 SQL 拼法(同一化身的属主会变:local M10 认领、identical 重戳、SQL 导入提交点改属主)。
|
|
15
|
+
* 0 行一律按住客复读分类(判官在 `execution-placement.ts`),永不对现住客重试。
|
|
16
|
+
*
|
|
17
|
+
* ## 读写序律(写事务内的认领读)
|
|
18
|
+
* 改根住客 / 放置的每个写者(换绑 CAS · 导入三写口 · identical 补 mirror · 删除终刀 · 留存删除)在写事务内、用该事务自己的连接先 `FOR UPDATE` 锁根
|
|
19
|
+
* `session_meta` 行,再以新语句锁定读 `task_active(root)`({@link lockRootAndReadClaim}):有认领 ⇒ 不写。全局锁序:设备行 → 根 meta → `task_*`
|
|
20
|
+
* (`run-store-sql.ts` 的 `createRun` 同序先锁根行)。认领持有者自己的初始放置写(place / bind)是唯一例外,不读认领、仍按住客谓词。
|
|
21
|
+
*/
|
|
22
|
+
import type { SqlDialect, SqlDriver, SqlExec } from "./sql-driver.js";
|
|
23
|
+
import type { DeviceOwner } from "../device-store.js";
|
|
24
|
+
import { type ExecutionLane } from "../execution-lane-caps.js";
|
|
25
|
+
import { type SessionFace } from "../session-incarnation.js";
|
|
26
|
+
import { type ExecutionPlacementCell, type ExecutionPlacementReading, type ExecutionPlacementSeat } from "../execution-placement.js";
|
|
27
|
+
/** 一条语句:文本 + 按序的参数(`?` / `$n` 已按方言铸好)。 */
|
|
28
|
+
export interface PlacementStatement {
|
|
29
|
+
readonly text: string;
|
|
30
|
+
readonly values: readonly unknown[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* `session_meta` 的八列(执行放置七列 + `fork_key`)—— 两方言各一句,**唯一属主**。宽度:车道词 ≤ 32、设备 id 与 `device` 表同宽 64、
|
|
34
|
+
* 属主两列与设备店同宽 190、fork 键 255(服务端拼的不透明串)。PG 文本列 `COLLATE "C"`(与同表同形)。全部可空:NULL = absent / 非键控 fork。
|
|
35
|
+
*/
|
|
36
|
+
export declare const SESSION_META_PLACEMENT_COLUMNS: readonly [{
|
|
37
|
+
readonly name: "execution_role";
|
|
38
|
+
readonly tidb: "execution_role VARCHAR(16) NULL";
|
|
39
|
+
readonly pg: "execution_role VARCHAR(16) COLLATE \"C\"";
|
|
40
|
+
}, {
|
|
41
|
+
readonly name: "execution_lane";
|
|
42
|
+
readonly tidb: "execution_lane VARCHAR(32) NULL";
|
|
43
|
+
readonly pg: "execution_lane VARCHAR(32) COLLATE \"C\"";
|
|
44
|
+
}, {
|
|
45
|
+
readonly name: "execution_device_id";
|
|
46
|
+
readonly tidb: "execution_device_id VARCHAR(64) NULL";
|
|
47
|
+
readonly pg: "execution_device_id VARCHAR(64) COLLATE \"C\"";
|
|
48
|
+
}, {
|
|
49
|
+
readonly name: "execution_device_owner_tenant";
|
|
50
|
+
readonly tidb: "execution_device_owner_tenant VARCHAR(190) NULL";
|
|
51
|
+
readonly pg: "execution_device_owner_tenant VARCHAR(190) COLLATE \"C\"";
|
|
52
|
+
}, {
|
|
53
|
+
readonly name: "execution_device_owner_subject";
|
|
54
|
+
readonly tidb: "execution_device_owner_subject VARCHAR(190) NULL";
|
|
55
|
+
readonly pg: "execution_device_owner_subject VARCHAR(190) COLLATE \"C\"";
|
|
56
|
+
}, {
|
|
57
|
+
readonly name: "execution_placed_at";
|
|
58
|
+
readonly tidb: "execution_placed_at DATETIME(3) NULL";
|
|
59
|
+
readonly pg: "execution_placed_at TIMESTAMPTZ(3)";
|
|
60
|
+
}, {
|
|
61
|
+
readonly name: "execution_rev";
|
|
62
|
+
readonly tidb: "execution_rev BIGINT NULL";
|
|
63
|
+
readonly pg: "execution_rev BIGINT";
|
|
64
|
+
}, {
|
|
65
|
+
readonly name: "fork_key";
|
|
66
|
+
readonly tidb: "fork_key VARCHAR(255) NULL";
|
|
67
|
+
readonly pg: "fork_key VARCHAR(255) COLLATE \"C\"";
|
|
68
|
+
}];
|
|
69
|
+
/** 中央 DDL 内插的那一段(列与列之间逗号分隔,不带首尾逗号)。 */
|
|
70
|
+
export declare function sessionMetaPlacementColumnsDdl(dialect: SqlDialect): string;
|
|
71
|
+
/** 导入提交点同句落 mirror 的 SET 片段与守卫谓词(会话店的提交点 UPDATE 内插这两段:`SET …, ${MIRROR_ON_COMMIT_SET} WHERE session_id = ? AND ${MIRROR_ON_COMMIT_GUARD}`)。 */
|
|
72
|
+
export declare const MIRROR_ON_COMMIT_SET = "execution_role = 'mirror'";
|
|
73
|
+
export declare const MIRROR_ON_COMMIT_GUARD = "(execution_role IS NULL OR execution_role <> 'writer')";
|
|
74
|
+
/** 导入新建 / 换装重插的那一行在执行与 fork 两格上的值:`fork_key` = 沿用的键(或 NULL)、`execution_role` = mirror(列名与值同出一处)。 */
|
|
75
|
+
export declare function importedRowCells(forkKey: string | null): {
|
|
76
|
+
readonly columns: string;
|
|
77
|
+
readonly values: readonly unknown[];
|
|
78
|
+
};
|
|
79
|
+
/** 读一行放置要的列(住客两列 + 委派判别位 + 执行七列;行 → 读数的唯一解码 {@link placementReadingFromRow} 吃的就是这一组)。导入写口的事务内锁读内插它。 */
|
|
80
|
+
export declare const SESSION_META_PLACEMENT_READ_COLUMNS = "owner, incarnation, placement_kind, execution_role, execution_lane, execution_device_id, execution_device_owner_tenant, execution_device_owner_subject, execution_placed_at, execution_rev";
|
|
81
|
+
/** 按根读一行(`lock` ⇒ `FOR UPDATE`:写事务内的当前读;非事务读不加锁)。 */
|
|
82
|
+
export declare function readPlacementRowStatement(dialect: SqlDialect, root: string, opts: {
|
|
83
|
+
readonly lock: boolean;
|
|
84
|
+
}): PlacementStatement;
|
|
85
|
+
/**
|
|
86
|
+
* place:absent → writer(L[, D]),`execution_rev = 0`。非 device 车道由会话店的执行席用(`device` 缺);device 车道的首绑由设备店在锁住设备行的
|
|
87
|
+
* 同一事务里用(`device` 在场)。时刻用调用方的钟(与 `updated_at` 同一只)。
|
|
88
|
+
*/
|
|
89
|
+
export declare function placeWriterStatement(dialect: SqlDialect, root: string, occupant: SessionFace, cell: {
|
|
90
|
+
readonly lane: ExecutionLane;
|
|
91
|
+
readonly device?: {
|
|
92
|
+
readonly id: string;
|
|
93
|
+
readonly owner: DeviceOwner;
|
|
94
|
+
};
|
|
95
|
+
}, now: Date): PlacementStatement;
|
|
96
|
+
/** bind:writer(device, ∅) → writer(device, D)(fresh 首绑撞上迁移回填的格),`execution_rev = 0`。 */
|
|
97
|
+
export declare function bindDeviceStatement(dialect: SqlDialect, root: string, occupant: SessionFace, device: {
|
|
98
|
+
readonly id: string;
|
|
99
|
+
readonly owner: DeviceOwner;
|
|
100
|
+
}, now: Date): PlacementStatement;
|
|
101
|
+
/** rebind:writer(device, D) → writer(device, D'),按住客 ∧ 两属主列 ∧ rev 定址,`execution_rev + 1`(恒变列)。属主两列不动(换绑只在同一属主的设备之间)。 */
|
|
102
|
+
export declare function rebindDeviceStatement(dialect: SqlDialect, root: string, occupant: SessionFace, owner: DeviceOwner, expectedRev: number, targetDeviceId: string, now: Date): PlacementStatement;
|
|
103
|
+
/** identical 臂补 mirror:absent / mirror → mirror,WHERE 住客谓词 ∧ 不是 writer。 */
|
|
104
|
+
export declare function markMirrorStatement(dialect: SqlDialect, root: string, occupant: SessionFace): PlacementStatement;
|
|
105
|
+
/** 回填(一次性迁移):无执行格、非委派分区、有 run 行的会话行 ⇒ writer(表唯一项)。幂等(第二次跑零行)。 */
|
|
106
|
+
export declare function backfillWriterStatement(dialect: SqlDialect, lane: ExecutionLane, now: Date): PlacementStatement;
|
|
107
|
+
/**
|
|
108
|
+
* 搬绑定(一次性迁移):按 `sourceTable`(退役改名后的绑定表,列 = `root_session_id, device_id, owner_tenant, owner_subject, bound_at, rev`)整行覆盖
|
|
109
|
+
* 根会话行的 device 位(`rev` 原值、`bound_at → execution_placed_at`)。只碰非委派分区行。MySQL 协议那张表的列是 VARBINARY ⇒ 转成字符串再比 / 再写。
|
|
110
|
+
* `sourceTable` 是调用方的常量表名(不是用户输入)。
|
|
111
|
+
*/
|
|
112
|
+
export declare function migrateDeviceBindingsStatement(dialect: SqlDialect, sourceTable: string): PlacementStatement;
|
|
113
|
+
/** 搬绑定的孤儿计数(绑定行指向的根会话行不在,或是委派分区行 ⇒ 丢弃):迁移收据里点名的那个数。 */
|
|
114
|
+
export declare function countOrphanBindingsStatement(dialect: SqlDialect, sourceTable: string): PlacementStatement;
|
|
115
|
+
/** 一行放置读不通(角色 / 车道不在闭集,或列的组合不是四态之一)—— 响亮拒,不读作 absent(那等于把一行坏数据当成「还没放」放行给首交)。 */
|
|
116
|
+
export declare class ExecutionPlacementRowError extends Error {
|
|
117
|
+
readonly sessionId: string;
|
|
118
|
+
readonly code = "execution_placement.row_unreadable";
|
|
119
|
+
constructor(sessionId: string, detail: string);
|
|
120
|
+
}
|
|
121
|
+
/** 行 → 格(三态;`forking` 只 local)。列的组合必须是四态之一,否则 {@link ExecutionPlacementRowError}。 */
|
|
122
|
+
export declare function placementCellFromRow(root: string, row: Readonly<Record<string, unknown>>): Exclude<ExecutionPlacementCell, {
|
|
123
|
+
role: "forking";
|
|
124
|
+
}>;
|
|
125
|
+
/** 一行 → 一次读数(住客 + 格)。行不在 / 委派分区行 ⇒ `undefined`;空化身 ⇒ 响亮拒(活住客的投影 `sessionFaceFromRow`)。 */
|
|
126
|
+
export declare function placementReadingFromRow(root: string, row: Readonly<Record<string, unknown>> | undefined): ExecutionPlacementReading | undefined;
|
|
127
|
+
/** 一条语句在调用方事务连接上的执行口(只要行):`SqlTxConn` / node-pg 客户端 / mysql2 连接各包一行即得。 */
|
|
128
|
+
export type PlacementRowsOn = (text: string, values: readonly unknown[]) => Promise<ReadonlyArray<Readonly<Record<string, unknown>>>>;
|
|
129
|
+
/** SqlDriver 连接的执行口。 */
|
|
130
|
+
export declare function rowsOnSqlExec(exec: SqlExec): PlacementRowsOn;
|
|
131
|
+
/** 锁根行之后读认领的读数:`rowPresent: false` ⇒ 根行不在(认领读不做,`activeTaskId` 恒 null)。 */
|
|
132
|
+
export interface RootClaimReading {
|
|
133
|
+
readonly rowPresent: boolean;
|
|
134
|
+
readonly activeTaskId: string | null;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 读写序律那一读(头注):**同一连接**上先 `SELECT … FROM session_meta WHERE session_id = ? FOR UPDATE` 锁根行,再以新语句
|
|
138
|
+
* `SELECT task_id FROM task_active WHERE session_id = ? FOR UPDATE` 锁定读认领(PG 读已提交下取新快照、TiDB 悲观事务是当前读)。
|
|
139
|
+
* 只在写事务内调;池级 `getActiveTaskId` 不是它(另一只连接,不在锁序里)。根行不在 ⇒ 不读认领。
|
|
140
|
+
*/
|
|
141
|
+
export declare function lockRootAndReadClaim(conn: PlacementRowsOn, dialect: SqlDialect, root: string): Promise<RootClaimReading>;
|
|
142
|
+
/** 写事务内按根的当前读(`FOR UPDATE`)→ 读数。 */
|
|
143
|
+
export declare function readPlacementOn(conn: PlacementRowsOn, dialect: SqlDialect, root: string): Promise<ExecutionPlacementReading | undefined>;
|
|
144
|
+
/**
|
|
145
|
+
* SQL 会话店的执行席(两孪生同一只实现:语句与判官都在这里与 `execution-placement.ts`,方言只在语句文本里)。`db` = 店自己的池包成的 SqlDriver。
|
|
146
|
+
* · `read` —— 一次池读;
|
|
147
|
+
* · `place` —— 一句自动提交的 UPDATE(认领持有者自己的初始放置写:不读认领、按住客谓词);0 行 ⇒ 按根复读、交判官分类;
|
|
148
|
+
* · `markMirror` —— 一只事务:锁根行读认领 → 锁定读这一行 → 判官 → 有认领 ⇒ `active` → UPDATE(持锁下必中一行)。
|
|
149
|
+
*/
|
|
150
|
+
export declare function createSqlExecutionPlacementSeat(db: Pick<SqlDriver, "dialect" | "query" | "connect">): ExecutionPlacementSeat;
|
|
151
|
+
//# sourceMappingURL=execution-placement-sql.d.ts.map
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { isExecutionLane } from "../execution-lane-caps.js";
|
|
2
|
+
import { sessionFaceFromRow } from "../session-incarnation.js";
|
|
3
|
+
import { toIso } from "./sql-row-helpers.js";
|
|
4
|
+
import { WRITABLE, assertPlaceableLane, settledMirrorOutcome, settledPlaceOutcome, } from "../execution-placement.js";
|
|
5
|
+
export const SESSION_META_PLACEMENT_COLUMNS = [
|
|
6
|
+
{ name: "execution_role", tidb: "execution_role VARCHAR(16) NULL", pg: `execution_role VARCHAR(16) COLLATE "C"` },
|
|
7
|
+
{ name: "execution_lane", tidb: "execution_lane VARCHAR(32) NULL", pg: `execution_lane VARCHAR(32) COLLATE "C"` },
|
|
8
|
+
{ name: "execution_device_id", tidb: "execution_device_id VARCHAR(64) NULL", pg: `execution_device_id VARCHAR(64) COLLATE "C"` },
|
|
9
|
+
{ name: "execution_device_owner_tenant", tidb: "execution_device_owner_tenant VARCHAR(190) NULL", pg: `execution_device_owner_tenant VARCHAR(190) COLLATE "C"` },
|
|
10
|
+
{ name: "execution_device_owner_subject", tidb: "execution_device_owner_subject VARCHAR(190) NULL", pg: `execution_device_owner_subject VARCHAR(190) COLLATE "C"` },
|
|
11
|
+
{ name: "execution_placed_at", tidb: "execution_placed_at DATETIME(3) NULL", pg: "execution_placed_at TIMESTAMPTZ(3)" },
|
|
12
|
+
{ name: "execution_rev", tidb: "execution_rev BIGINT NULL", pg: "execution_rev BIGINT" },
|
|
13
|
+
{ name: "fork_key", tidb: "fork_key VARCHAR(255) NULL", pg: `fork_key VARCHAR(255) COLLATE "C"` },
|
|
14
|
+
];
|
|
15
|
+
export function sessionMetaPlacementColumnsDdl(dialect) {
|
|
16
|
+
return SESSION_META_PLACEMENT_COLUMNS.map((c) => c[dialect]).join(",\n ");
|
|
17
|
+
}
|
|
18
|
+
const WRITER = "writer";
|
|
19
|
+
const MIRROR = "mirror";
|
|
20
|
+
const NOT_WRITER = "(execution_role IS NULL OR execution_role <> 'writer')";
|
|
21
|
+
export const MIRROR_ON_COMMIT_SET = "execution_role = 'mirror'";
|
|
22
|
+
export const MIRROR_ON_COMMIT_GUARD = NOT_WRITER;
|
|
23
|
+
export function importedRowCells(forkKey) {
|
|
24
|
+
return { columns: "fork_key, execution_role", values: [forkKey, MIRROR] };
|
|
25
|
+
}
|
|
26
|
+
export const SESSION_META_PLACEMENT_READ_COLUMNS = "owner, incarnation, placement_kind, execution_role, execution_lane, execution_device_id, execution_device_owner_tenant, execution_device_owner_subject, execution_placed_at, execution_rev";
|
|
27
|
+
function binder(dialect) {
|
|
28
|
+
const values = [];
|
|
29
|
+
return {
|
|
30
|
+
values,
|
|
31
|
+
p: (v) => {
|
|
32
|
+
values.push(v);
|
|
33
|
+
return dialect === "pg" ? `$${values.length}` : "?";
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function occupantPredicate(dialect, p, root, occupant) {
|
|
38
|
+
const nullSafeEq = dialect === "pg" ? "IS NOT DISTINCT FROM" : "<=>";
|
|
39
|
+
return `session_id = ${p(root)} AND incarnation = ${p(occupant.incarnation)} AND owner ${nullSafeEq} ${p(occupant.owner)} AND placement_kind IS NULL`;
|
|
40
|
+
}
|
|
41
|
+
export function readPlacementRowStatement(dialect, root, opts) {
|
|
42
|
+
const b = binder(dialect);
|
|
43
|
+
return { text: `SELECT ${SESSION_META_PLACEMENT_READ_COLUMNS} FROM session_meta WHERE session_id = ${b.p(root)}${opts.lock ? " FOR UPDATE" : ""}`, values: b.values };
|
|
44
|
+
}
|
|
45
|
+
export function placeWriterStatement(dialect, root, occupant, cell, now) {
|
|
46
|
+
const b = binder(dialect);
|
|
47
|
+
const set = `execution_role = '${WRITER}', execution_lane = ${b.p(cell.lane)}, execution_device_id = ${b.p(cell.device?.id ?? null)}, ` +
|
|
48
|
+
`execution_device_owner_tenant = ${b.p(cell.device?.owner.tenant ?? null)}, execution_device_owner_subject = ${b.p(cell.device?.owner.subject ?? null)}, ` +
|
|
49
|
+
`execution_placed_at = ${b.p(now)}, execution_rev = 0`;
|
|
50
|
+
return { text: `UPDATE session_meta SET ${set} WHERE ${occupantPredicate(dialect, b.p, root, occupant)} AND execution_role IS NULL`, values: b.values };
|
|
51
|
+
}
|
|
52
|
+
export function bindDeviceStatement(dialect, root, occupant, device, now) {
|
|
53
|
+
const b = binder(dialect);
|
|
54
|
+
const set = `execution_device_id = ${b.p(device.id)}, execution_device_owner_tenant = ${b.p(device.owner.tenant)}, execution_device_owner_subject = ${b.p(device.owner.subject)}, execution_placed_at = ${b.p(now)}, execution_rev = 0`;
|
|
55
|
+
return {
|
|
56
|
+
text: `UPDATE session_meta SET ${set} WHERE ${occupantPredicate(dialect, b.p, root, occupant)} AND execution_role = '${WRITER}' AND execution_lane = 'device' AND execution_device_id IS NULL`,
|
|
57
|
+
values: b.values,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export function rebindDeviceStatement(dialect, root, occupant, owner, expectedRev, targetDeviceId, now) {
|
|
61
|
+
const b = binder(dialect);
|
|
62
|
+
const set = `execution_device_id = ${b.p(targetDeviceId)}, execution_placed_at = ${b.p(now)}, execution_rev = execution_rev + 1`;
|
|
63
|
+
const where = `${occupantPredicate(dialect, b.p, root, occupant)} AND execution_role = '${WRITER}' AND execution_lane = 'device' ` +
|
|
64
|
+
`AND execution_device_owner_tenant = ${b.p(owner.tenant)} AND execution_device_owner_subject = ${b.p(owner.subject)} AND execution_rev = ${b.p(expectedRev)}`;
|
|
65
|
+
return { text: `UPDATE session_meta SET ${set} WHERE ${where}`, values: b.values };
|
|
66
|
+
}
|
|
67
|
+
export function markMirrorStatement(dialect, root, occupant) {
|
|
68
|
+
const b = binder(dialect);
|
|
69
|
+
return { text: `UPDATE session_meta SET execution_role = '${MIRROR}' WHERE ${occupantPredicate(dialect, b.p, root, occupant)} AND ${NOT_WRITER}`, values: b.values };
|
|
70
|
+
}
|
|
71
|
+
export function backfillWriterStatement(dialect, lane, now) {
|
|
72
|
+
const b = binder(dialect);
|
|
73
|
+
return {
|
|
74
|
+
text: `UPDATE session_meta SET execution_role = '${WRITER}', execution_lane = ${b.p(lane)}, execution_placed_at = ${b.p(now)}, execution_rev = 0 ` +
|
|
75
|
+
"WHERE execution_role IS NULL AND placement_kind IS NULL AND EXISTS (SELECT 1 FROM task_run tr WHERE tr.session_id = session_meta.session_id)",
|
|
76
|
+
values: b.values,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export function migrateDeviceBindingsStatement(dialect, sourceTable) {
|
|
80
|
+
if (dialect === "pg") {
|
|
81
|
+
return {
|
|
82
|
+
text: `UPDATE session_meta SET execution_role = '${WRITER}', execution_lane = 'device', execution_device_id = ds.device_id, ` +
|
|
83
|
+
"execution_device_owner_tenant = ds.owner_tenant, execution_device_owner_subject = ds.owner_subject, execution_placed_at = ds.bound_at, execution_rev = ds.rev " +
|
|
84
|
+
`FROM ${sourceTable} ds WHERE ds.root_session_id = session_meta.session_id AND session_meta.placement_kind IS NULL`,
|
|
85
|
+
values: [],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const str = (col) => `CONVERT(ds.${col} USING utf8mb4)`;
|
|
89
|
+
return {
|
|
90
|
+
text: `UPDATE session_meta sm JOIN ${sourceTable} ds ON ${str("root_session_id")} COLLATE utf8mb4_bin = sm.session_id ` +
|
|
91
|
+
`SET sm.execution_role = '${WRITER}', sm.execution_lane = 'device', sm.execution_device_id = ${str("device_id")}, ` +
|
|
92
|
+
`sm.execution_device_owner_tenant = ${str("owner_tenant")}, sm.execution_device_owner_subject = ${str("owner_subject")}, sm.execution_placed_at = ds.bound_at, sm.execution_rev = ds.rev ` +
|
|
93
|
+
"WHERE sm.placement_kind IS NULL",
|
|
94
|
+
values: [],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export function countOrphanBindingsStatement(dialect, sourceTable) {
|
|
98
|
+
const rootKey = dialect === "pg" ? "ds.root_session_id" : "CONVERT(ds.root_session_id USING utf8mb4) COLLATE utf8mb4_bin";
|
|
99
|
+
return {
|
|
100
|
+
text: `SELECT COUNT(*) AS orphans FROM ${sourceTable} ds WHERE NOT EXISTS (SELECT 1 FROM session_meta sm WHERE sm.session_id = ${rootKey} AND sm.placement_kind IS NULL)`,
|
|
101
|
+
values: [],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
export class ExecutionPlacementRowError extends Error {
|
|
105
|
+
sessionId;
|
|
106
|
+
code = "execution_placement.row_unreadable";
|
|
107
|
+
constructor(sessionId, detail) {
|
|
108
|
+
super(`session ${sessionId}: the execution placement columns do not form a placement (${detail}) — refusing to read it as any placement`);
|
|
109
|
+
this.sessionId = sessionId;
|
|
110
|
+
this.name = "ExecutionPlacementRowError";
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const isNull = (v) => v === null || v === undefined;
|
|
114
|
+
const text = (v) => (Buffer.isBuffer(v) ? v.toString("utf8") : String(v));
|
|
115
|
+
export function placementCellFromRow(root, row) {
|
|
116
|
+
const role = row.execution_role;
|
|
117
|
+
const deviceCols = [row.execution_device_id, row.execution_device_owner_tenant, row.execution_device_owner_subject];
|
|
118
|
+
if (isNull(role) || role === MIRROR) {
|
|
119
|
+
if (!isNull(row.execution_lane) || deviceCols.some((v) => !isNull(v)))
|
|
120
|
+
throw new ExecutionPlacementRowError(root, `role ${isNull(role) ? "NULL" : MIRROR} with a lane or device`);
|
|
121
|
+
return isNull(role) ? { role: "absent" } : { role: "mirror" };
|
|
122
|
+
}
|
|
123
|
+
if (role !== WRITER)
|
|
124
|
+
throw new ExecutionPlacementRowError(root, `role ${JSON.stringify(text(role))} is not writer / mirror / NULL`);
|
|
125
|
+
const lane = isNull(row.execution_lane) ? "" : text(row.execution_lane);
|
|
126
|
+
if (!isExecutionLane(lane))
|
|
127
|
+
throw new ExecutionPlacementRowError(root, `writer lane ${JSON.stringify(lane)} is not an execution lane`);
|
|
128
|
+
if (isNull(row.execution_device_id)) {
|
|
129
|
+
if (deviceCols.some((v) => !isNull(v)))
|
|
130
|
+
throw new ExecutionPlacementRowError(root, "device owner without a device");
|
|
131
|
+
return { role: "writer", lane };
|
|
132
|
+
}
|
|
133
|
+
if (lane !== "device")
|
|
134
|
+
throw new ExecutionPlacementRowError(root, `a device on the ${lane} lane`);
|
|
135
|
+
if (deviceCols.some(isNull) || isNull(row.execution_rev) || isNull(row.execution_placed_at))
|
|
136
|
+
throw new ExecutionPlacementRowError(root, "a device without its owner, rev or placement time");
|
|
137
|
+
return {
|
|
138
|
+
role: "writer",
|
|
139
|
+
lane,
|
|
140
|
+
device: {
|
|
141
|
+
id: text(row.execution_device_id),
|
|
142
|
+
owner: { tenant: text(row.execution_device_owner_tenant), subject: text(row.execution_device_owner_subject) },
|
|
143
|
+
rev: Number(row.execution_rev),
|
|
144
|
+
placedAt: toIso(row.execution_placed_at),
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
export function placementReadingFromRow(root, row) {
|
|
149
|
+
if (row === undefined || !isNull(row.placement_kind))
|
|
150
|
+
return undefined;
|
|
151
|
+
return { occupant: sessionFaceFromRow(root, isNull(row.owner) ? null : text(row.owner), row.incarnation), cell: placementCellFromRow(root, row) };
|
|
152
|
+
}
|
|
153
|
+
export function rowsOnSqlExec(exec) {
|
|
154
|
+
return async (sql, values) => (await exec.query(sql, [...values])).rows;
|
|
155
|
+
}
|
|
156
|
+
export async function lockRootAndReadClaim(conn, dialect, root) {
|
|
157
|
+
const ph = dialect === "pg" ? "$1" : "?";
|
|
158
|
+
const row = await conn(`SELECT session_id FROM session_meta WHERE session_id = ${ph} FOR UPDATE`, [root]);
|
|
159
|
+
if (row.length === 0)
|
|
160
|
+
return { rowPresent: false, activeTaskId: null };
|
|
161
|
+
const claim = await conn(`SELECT task_id FROM task_active WHERE session_id = ${ph} FOR UPDATE`, [root]);
|
|
162
|
+
return { rowPresent: true, activeTaskId: claim[0] === undefined ? null : text(claim[0].task_id) };
|
|
163
|
+
}
|
|
164
|
+
export async function readPlacementOn(conn, dialect, root) {
|
|
165
|
+
const s = readPlacementRowStatement(dialect, root, { lock: true });
|
|
166
|
+
return placementReadingFromRow(root, (await conn(s.text, s.values))[0]);
|
|
167
|
+
}
|
|
168
|
+
function placementInvariant(root, what) {
|
|
169
|
+
return new Error(`execution placement of session ${root}: ${what} matched no row while a re-read finds the cell writable — engine/driver anomaly, refusing to guess`);
|
|
170
|
+
}
|
|
171
|
+
export function createSqlExecutionPlacementSeat(db) {
|
|
172
|
+
const read = async (root) => {
|
|
173
|
+
const s = readPlacementRowStatement(db.dialect, root, { lock: false });
|
|
174
|
+
return placementReadingFromRow(root, (await db.query(s.text, [...s.values])).rows[0]);
|
|
175
|
+
};
|
|
176
|
+
return {
|
|
177
|
+
read,
|
|
178
|
+
async place(root, occupant, cell) {
|
|
179
|
+
assertPlaceableLane(root, cell);
|
|
180
|
+
const s = placeWriterStatement(db.dialect, root, occupant, { lane: cell.lane }, new Date());
|
|
181
|
+
if ((await db.query(s.text, [...s.values])).affected === 1)
|
|
182
|
+
return { outcome: "placed" };
|
|
183
|
+
const settled = settledPlaceOutcome(root, await read(root), occupant, cell);
|
|
184
|
+
if (settled === WRITABLE)
|
|
185
|
+
throw placementInvariant(root, "place");
|
|
186
|
+
return settled;
|
|
187
|
+
},
|
|
188
|
+
async markMirror(root, occupant) {
|
|
189
|
+
const conn = await db.connect();
|
|
190
|
+
try {
|
|
191
|
+
await conn.begin();
|
|
192
|
+
const rowsOn = rowsOnSqlExec(conn);
|
|
193
|
+
const claim = await lockRootAndReadClaim(rowsOn, db.dialect, root);
|
|
194
|
+
if (!claim.rowPresent) {
|
|
195
|
+
await conn.rollback();
|
|
196
|
+
return { outcome: "gone" };
|
|
197
|
+
}
|
|
198
|
+
const settled = settledMirrorOutcome(root, await readPlacementOn(rowsOn, db.dialect, root), occupant);
|
|
199
|
+
if (settled !== WRITABLE) {
|
|
200
|
+
await conn.rollback();
|
|
201
|
+
return settled;
|
|
202
|
+
}
|
|
203
|
+
if (claim.activeTaskId !== null) {
|
|
204
|
+
await conn.rollback();
|
|
205
|
+
return { outcome: "active", activeTaskId: claim.activeTaskId };
|
|
206
|
+
}
|
|
207
|
+
const s = markMirrorStatement(db.dialect, root, occupant);
|
|
208
|
+
if ((await conn.query(s.text, [...s.values])).affected !== 1)
|
|
209
|
+
throw placementInvariant(root, "markMirror under a held row lock");
|
|
210
|
+
await conn.commit();
|
|
211
|
+
return { outcome: "mirrored" };
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
await conn.rollback().catch(() => undefined);
|
|
215
|
+
throw err;
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
conn.release();
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=execution-placement-sql.js.map
|
|
@@ -183,15 +183,16 @@ export declare class PgFileHistoryStore extends SqlFileHistoryStore {
|
|
|
183
183
|
}
|
|
184
184
|
/**
|
|
185
185
|
* [ref] D1② — A7-recipe upgrade precondition assert (**refuse to start**, never a warn; the form and
|
|
186
|
-
* rationale follow `assertPermissionRuleTicketSchema` verbatim:
|
|
186
|
+
* rationale follow `assertPermissionRuleTicketSchema` verbatim: the boot check never alters a table, and
|
|
187
187
|
* `CREATE TABLE IF NOT EXISTS` is a no-op on an existing table whose columns predate this build).
|
|
188
188
|
*
|
|
189
189
|
* The probe is one always-empty `WHERE 1=0` read per table naming every column this build references.
|
|
190
|
-
* ONLY a provable missing-column error gets the
|
|
191
|
-
* (timeout / reset / permission / missing table) rethrows untouched:
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
190
|
+
* ONLY a provable missing-column error gets the missing-column refusal — everything else
|
|
191
|
+
* (timeout / reset / permission / missing table) rethrows untouched: diagnosing a connection blip as a
|
|
192
|
+
* schema drift is more expensive than the defect this gate exists to stop. The refusal's ways out are the
|
|
193
|
+
* closed set of three shared with the whole-database check (`describeProbedTableDrift`); none of them
|
|
194
|
+
* is destructive. Honest cost statement: the history lives in these tables (per-file REWIND history,
|
|
195
|
+
* working-tree convenience state) — session transcripts and every other durable subsystem are untouched.
|
|
195
196
|
*/
|
|
196
197
|
export declare function assertFileHistorySchema(query: (sql: string) => Promise<{
|
|
197
198
|
rows: Record<string, unknown>[];
|
|
@@ -3,6 +3,7 @@ import { captureFileBackupViaEnv, applyFileRestoreViaEnv, fileHistoryModeProvenD
|
|
|
3
3
|
import { BlobTooLargeError, SqlBlobBackend } from "./blob-backend.js";
|
|
4
4
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
5
5
|
import { isMissingColumnError } from "./sql-errors.js";
|
|
6
|
+
import { describeProbedTableDrift } from "./schema-drift-sentence.js";
|
|
6
7
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
7
8
|
import { foldCaughtFailure } from "../file-history-disclosure.js";
|
|
8
9
|
export const FILE_HISTORY_BLOB_TABLE = "file_history_blob";
|
|
@@ -835,11 +836,10 @@ export async function assertFileHistorySchema(query, dialect) {
|
|
|
835
836
|
throw err;
|
|
836
837
|
throw new Error(`${table} is missing required columns — refusing to start. This build's per-file rewind history (S-15) ` +
|
|
837
838
|
`references every probed column unconditionally; a partial table would fail mid-write and could publish a ` +
|
|
838
|
-
`half-shaped history graph.
|
|
839
|
-
|
|
840
|
-
`
|
|
841
|
-
|
|
842
|
-
`Underlying probe error: ${err instanceof Error ? err.message : String(err)}`);
|
|
839
|
+
`half-shaped history graph. The file_history_* tables hold the per-file REWIND history (working-tree convenience ` +
|
|
840
|
+
`state); session transcripts and all other durable subsystems are untouched, and an in-place ALTER keeps the history.\n` +
|
|
841
|
+
`Underlying probe error: ${err instanceof Error ? err.message : String(err)}\n\n` +
|
|
842
|
+
(await describeProbedTableDrift(query, dialect, [table])));
|
|
843
843
|
}
|
|
844
844
|
}
|
|
845
845
|
}
|
|
@@ -152,7 +152,7 @@ export declare class FileRunStore implements InProcessClaimFence {
|
|
|
152
152
|
whileHoldingClaim(sessionId: string, taskId: string, write: () => FenceLanded): boolean;
|
|
153
153
|
getRun(taskId: string): Promise<RunRecord | undefined>;
|
|
154
154
|
/** Undo THIS submission's binding (key + body fingerprint → null) — called only where the submit surface refuses the submission
|
|
155
|
-
* before any work and terminalizes the row it just claimed (the sync leg's zero-work refusal,
|
|
155
|
+
* before any work and terminalizes the row it just claimed (the sync leg's zero-work refusal, an execution-placement reject):
|
|
156
156
|
* the error answer invites a retry, and a retry with the same key must run, not replay the refusal. The row stays (it
|
|
157
157
|
* is the refusal's record); only the binding is freed. */
|
|
158
158
|
releaseIdemKey(taskId: string): Promise<void>;
|
|
@@ -332,6 +332,11 @@ export declare class FileRunStore implements InProcessClaimFence {
|
|
|
332
332
|
* 起服时调(`LocalBackend.runOneTimeMigrations`,任何读者之前);数据根独占 ⇒ 本车道没有滚动升级窗,不需要读侧半场。幂等:第二跑零命中零写盘。
|
|
333
333
|
*/
|
|
334
334
|
migrateLegacyHolderDeathCodes(): number;
|
|
335
|
+
/**
|
|
336
|
+
* 一次性迁移 **M32 的 local 腿**(执行放置回填;窗、删法与删后谁接住:窗口见 `plugins/one-time-migrations.ts` 登记表):有 run 行的会话 id(去重)。
|
|
337
|
+
* 起服时调(`LocalBackend.runOneTimeMigrations`,任何读者之前);读内存索引(构造时已从盘上 hydrate),零写。
|
|
338
|
+
*/
|
|
339
|
+
sessionIdsWithRuns(): ReadonlySet<string>;
|
|
335
340
|
/** Release every open event-log fd (called by LocalBackend.close so a graceful restart can re-open the data dir). */
|
|
336
341
|
dispose(): void;
|
|
337
342
|
}
|
|
@@ -280,6 +280,7 @@ export class FileRunStore {
|
|
|
280
280
|
"Refusing to boot with an EMPTY single-active claim index (every session would look unclaimed, so a delete or a fresh run could race a live/parked one). " +
|
|
281
281
|
"Fix the permissions/mount on that path, point LOCAL_DATA_ROOT at a readable dir, or unset RUN_STORE_STRICT_HYDRATE to boot anyway (the pre-existing behavior).");
|
|
282
282
|
}
|
|
283
|
+
recordFailOpen("server.run-store.file-active-claims-unreadable", detail);
|
|
283
284
|
claims = [];
|
|
284
285
|
}
|
|
285
286
|
const nonTerminalBySanitizedSession = new Map();
|
|
@@ -1009,6 +1010,9 @@ export class FileRunStore {
|
|
|
1009
1010
|
}
|
|
1010
1011
|
return migrated;
|
|
1011
1012
|
}
|
|
1013
|
+
sessionIdsWithRuns() {
|
|
1014
|
+
return new Set([...this.runs.values()].map((r) => r.sessionId));
|
|
1015
|
+
}
|
|
1012
1016
|
dispose() {
|
|
1013
1017
|
for (const log of this.eventLogs.values()) {
|
|
1014
1018
|
try {
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { type SessionStore, type AcquiredSession, type SessionStoreSummary, type SessionPlacement, type PlacedSessionRow } from "@sema-agent/core";
|
|
27
27
|
import { type DelegationTree } from "../delegated-session-owner.js";
|
|
28
|
+
import type { ExecutionPlacementSeat } from "../execution-placement.js";
|
|
29
|
+
import type { ForkToFn } from "./session-fork.js";
|
|
28
30
|
/** acquire 选项形(core 5.48 SessionStore 契约):[ref] placement=受信 spawner 的分区建会话声明。 */
|
|
29
31
|
type AcquireOpts = {
|
|
30
32
|
requireExisting?: boolean;
|
|
@@ -54,6 +56,10 @@ export declare class ForkRoutingSessionStore implements SessionStore {
|
|
|
54
56
|
olderThanMs?: number;
|
|
55
57
|
scope?: string;
|
|
56
58
|
}) => Promise<PlacedSessionRow[]>;
|
|
59
|
+
/** 执行席透传(host 有才有):执行放置是**根会话行**上的一格,根会话行住 host durable 店;transient 店没有会话行可放,也就不声明席。 */
|
|
60
|
+
readonly executionPlacement?: ExecutionPlacementSeat;
|
|
61
|
+
/** fork 续写的店契约透传(host 有才有):目标在 host 店原子新建,新 id 与源一并记 host 路由(与 {@link fork} 的 host 腿同)。 */
|
|
62
|
+
readonly forkTo?: ForkToFn;
|
|
57
63
|
constructor(host: SessionStore, transient: SessionStore);
|
|
58
64
|
private remember;
|
|
59
65
|
private pick;
|
|
@@ -11,6 +11,8 @@ export class ForkRoutingSessionStore {
|
|
|
11
11
|
placed = new Set();
|
|
12
12
|
placements;
|
|
13
13
|
listPlaced;
|
|
14
|
+
executionPlacement;
|
|
15
|
+
forkTo;
|
|
14
16
|
constructor(host, transient) {
|
|
15
17
|
this.host = host;
|
|
16
18
|
this.transient = transient;
|
|
@@ -23,6 +25,20 @@ export class ForkRoutingSessionStore {
|
|
|
23
25
|
if (host.fork === undefined && transient.fork === undefined) {
|
|
24
26
|
this.fork = undefined;
|
|
25
27
|
}
|
|
28
|
+
const hostFaces = host;
|
|
29
|
+
if (hostFaces.executionPlacement !== undefined)
|
|
30
|
+
this.executionPlacement = hostFaces.executionPlacement;
|
|
31
|
+
const hostForkTo = hostFaces.forkTo;
|
|
32
|
+
if (typeof hostForkTo === "function") {
|
|
33
|
+
this.forkTo = async (sourceId, owner, target) => {
|
|
34
|
+
const out = await hostForkTo.call(host, sourceId, owner, target);
|
|
35
|
+
if (out !== null && out.outcome !== "occupied") {
|
|
36
|
+
this.remember(sourceId, "host");
|
|
37
|
+
this.remember(out.id, "host");
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
26
42
|
}
|
|
27
43
|
remember(id, where) {
|
|
28
44
|
this.route.delete(id);
|