@sema-agent/server 7.73.1 → 7.74.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.
Files changed (41) hide show
  1. package/USAGE.md +8 -0
  2. package/dist/boot/runner-deps.d.ts +0 -4
  3. package/dist/boot/runner-deps.js +1 -2
  4. package/dist/boot/session-faces.js +0 -2
  5. package/dist/boot/stores.js +3 -16
  6. package/dist/config-types.d.ts +1 -1
  7. package/dist/deployment-governance.d.ts +23 -3
  8. package/dist/deployment-governance.js +23 -2
  9. package/dist/env-facts.d.ts +8 -1
  10. package/dist/http/route-ctx.d.ts +9 -8
  11. package/dist/http/routes/approvals-assistant.js +2 -2
  12. package/dist/http/routes/runs.js +1 -1
  13. package/dist/http/routes/workflows.js +4 -0
  14. package/dist/http/server.js +1 -0
  15. package/dist/main.js +0 -1
  16. package/dist/observability/fail-open.d.ts +1 -1
  17. package/dist/observability/fail-open.js +1 -1
  18. package/dist/orchestration/workflow-agent-session-index.d.ts +3 -2
  19. package/dist/plugins/approval-ask-store-sql.js +1 -1
  20. package/dist/plugins/blob-backend.d.ts +6 -7
  21. package/dist/plugins/checkpoint-store-sql.d.ts +13 -10
  22. package/dist/plugins/checkpoint-store-sql.js +6 -24
  23. package/dist/plugins/file-run-store.d.ts +18 -0
  24. package/dist/plugins/file-run-store.js +27 -0
  25. package/dist/plugins/one-time-migrations.d.ts +66 -0
  26. package/dist/plugins/one-time-migrations.js +193 -0
  27. package/dist/plugins/pg-pool.d.ts +6 -0
  28. package/dist/plugins/pg-pool.js +5 -14
  29. package/dist/plugins/retention-store-sql.js +0 -1
  30. package/dist/plugins/store-backend.d.ts +11 -15
  31. package/dist/plugins/store-backend.js +7 -26
  32. package/dist/plugins/task-attachment-store.d.ts +2 -2
  33. package/dist/plugins/tidb-pool.js +11 -23
  34. package/dist/plugins/workflow-run-store-sql.d.ts +9 -0
  35. package/dist/task-cwd.d.ts +12 -1
  36. package/dist/task-settings.d.ts +6 -0
  37. package/dist/trace/core-keyset-guard.d.ts +11 -2
  38. package/dist/trace/project.d.ts +28 -29
  39. package/dist/trace/project.js +12 -17
  40. package/dist/trace/redact.js +5 -1
  41. package/package.json +2 -2
@@ -0,0 +1,66 @@
1
+ import type { Pool as MysqlPool } from "mysql2/promise";
2
+ import type { Pool as PgPool } from "pg";
3
+ /** 迁移读数(启动日志一行写全;测试按它断言,不靠日志文本)。 */
4
+ export interface OneTimeMigrationReport {
5
+ /** M1:退役快照索引表 —— `dropped` = 本次真 drop 了,`absent` = 早已不在(第二跑/全新库)。 */
6
+ readonly retiredSnapshotTable: "dropped" | "absent";
7
+ /** M1:drop 之前表上的行数(`absent` 时恒 0)。非零 = 这次迁移真删掉了存量行,必须让运维看见。 */
8
+ readonly retiredSnapshotRows: number;
9
+ /** M2:退役单座列 —— `dropped` = 本次真 DROP COLUMN,`absent` = 早已不在。 */
10
+ readonly retiredSteerSeatColumn: "dropped" | "absent";
11
+ /** M3:改名的行数(账本 `tool_end` 行 + 已决 checkpoint 的执行判词列,两者相加)。 */
12
+ readonly askOriginRowsMigrated: number;
13
+ }
14
+ /** 三条迁移都不适用的载体(local 车道没有 SQL 表)。 */
15
+ export declare const NO_SQL_MIGRATIONS: Omit<OneTimeMigrationReport, "askOriginRowsMigrated">;
16
+ /** 启动期的日志口(logger 的子集)。两级各有其用,见 {@link logOneTimeMigrations}。 */
17
+ export interface MigrationLogSink {
18
+ warn(msg: string, meta?: Record<string, unknown>): void;
19
+ info(msg: string, meta?: Record<string, unknown>): void;
20
+ }
21
+ /**
22
+ * M2 的拒启。**具名类型**:`openStoreBackendWithFallback` 对默认推导的 local 有降级臂,而本拒启若与
23
+ * 普通 Error 同形会被吃掉 —— operator 面对的就会是「服务起来了,但那条指令悄悄没了」
24
+ * (`RunStoreStrictHydrateError` 的同族先例,锚类型不锚文案)。
25
+ */
26
+ export declare class RetiredSteerSeatError extends Error {
27
+ readonly rows: number;
28
+ /** 结构化事件名(日志消费端按它分派;文案会变,这个不会)。 */
29
+ readonly code = "checkpoint.pending_steer_seat_retired";
30
+ constructor(rows: number);
31
+ }
32
+ /**
33
+ * SQL 两后端的一次性迁移。**调用点只有一个**(boot/stores.ts,在 `ensureSchema` 之后、任何路由装配
34
+ * 之前)—— 拒启在那一刻仍然是拒启(还没开始服务),而 `ensureSchema` 那条通道的契约是「只发 CREATE」
35
+ * (`test/ensure-schema.test.ts` 的运行时门),增量语句一条都不许进去。
36
+ *
37
+ * ## 并发(codex r1 [medium],亲核复现后修)
38
+ * 一次滚动升级会让 N 个副本同时跑到这里。整段因此跑在**一条连接**上、握着**与 `ensureSchema` 同一把**
39
+ * advisory 锁(MySQL 具名锁 / PG `pg_advisory_lock`):本段是「启动期改 schema 的活」,与 ensure 属同一类,
40
+ * 复用同一把锁 ⇒ 本仓这一类工作只有**一个**锁身份,而且本段也不会与另一个副本的 ensure 交错。
41
+ * 锁拿不到时按既有先例**降级**(不阻塞启动),但降级不等于放任:每条破坏性 DDL 的成功判据是它的
42
+ * **后置条件**({@link execUntil}),所以「另一个副本抢先删掉了同一列」不会把滚动升级变成启动失败循环。
43
+ *
44
+ * ## 次序(codex r1 [medium] 同轮)
45
+ * 读 → 非破坏改写 → 破坏性 DDL。①拒启臂是纯读,拒的时候盘上一个字节没动;②M3 是改值不丢信息的
46
+ * UPDATE,它是最可能超时的一步,放在删表删列**之前**;③真正的破坏排在最后,且失败时把**已完成的
47
+ * 破坏性步骤及其读数**写进拒因 —— 否则「删完表再失败」会让重试看到一张不存在的表、把行数报成 0,
48
+ * 证据永久消失。
49
+ *
50
+ * @throws {RetiredSteerSeatError} M2 探到非空的退役席位(唯一的拒启臂)。
51
+ */
52
+ export declare function runSqlOneTimeMigrations(dialect: "tidb" | "pg", pool: MysqlPool | PgPool): Promise<OneTimeMigrationReport>;
53
+ /**
54
+ * M3 的**纯**改写(local 车道的账本行逐条走它;SQL 两腿在服务端用等价的 JSON 路径表达同一件事)。
55
+ * 不改原对象(调用方与账本共享引用):有变更时换一只信封,原记录一个字节不动;无变更 ⇒ 返回
56
+ * `undefined`,调用方据此**不写盘**(第二跑因此零 I/O)。
57
+ */
58
+ export declare function migrateRetiredAskOrigin(type: string, data: unknown): Record<string, unknown> | undefined;
59
+ /**
60
+ * 启动日志的读数行。零命中(第二跑起的常态)也照发 —— 「跑过了、什么都没改」与「压根没跑」必须可分辨。
61
+ *
62
+ * 🔴 **级别按「这次真动了没有」分**:动了 ⇒ `warn`(有人的持久数据被改写/删除了,运维该看见一次);
63
+ * 没动 ⇒ `info`。把恒常的空转也发成 warn 会训练运维忽略 warn —— 那是在为了本段的可见性去损耗整条告警轴。
64
+ */
65
+ export declare function logOneTimeMigrations(log: MigrationLogSink, backend: string, report: OneTimeMigrationReport): void;
66
+ //# sourceMappingURL=one-time-migrations.d.ts.map
@@ -0,0 +1,193 @@
1
+ import { acquireEnsureSchemaLock, releaseEnsureSchemaLock } from "./tidb-pool.js";
2
+ import { ENSURE_SCHEMA_LOCK_KEY } from "./pg-pool.js";
3
+ export const NO_SQL_MIGRATIONS = {
4
+ retiredSnapshotTable: "absent",
5
+ retiredSnapshotRows: 0,
6
+ retiredSteerSeatColumn: "absent",
7
+ };
8
+ const RETIRED_SNAPSHOT_TABLE = "snapshot_manifest";
9
+ const RETIRED_STEER_SEAT_COLUMN = "pending_steer";
10
+ const RETIRED_ASK_ORIGIN = { from: "unresolvable", to: "ancestor_marked" };
11
+ const GATE_BEARING_LEDGER_EVENT_TYPE = "tool_end";
12
+ export class RetiredSteerSeatError extends Error {
13
+ rows;
14
+ code = "checkpoint.pending_steer_seat_retired";
15
+ constructor(rows) {
16
+ super(`checkpoint.pending_steer_seat_retired: ${rows} checkpoint row(s) still carry a value in the RETIRED single-seat ` +
17
+ `steering column \`${RETIRED_STEER_SEAT_COLUMN}\`. That seat is gone from the engine (a resume of such a row is ` +
18
+ `refused pre-CAS), so the value is an operator instruction nothing will ever deliver, and this service refuses ` +
19
+ `to guess what it should become (folding a bare seat into the queue would mint a queue entry the engine never ` +
20
+ `wrote). Note that no earlier version ever cleared that cell after delivering it, so most non-NULL cells sit on ` +
21
+ `rows that can never be resumed again — those are stale and safe to clear: ` +
22
+ `\`UPDATE checkpoint SET ${RETIRED_STEER_SEAT_COLUMN} = NULL WHERE ${RETIRED_STEER_SEAT_COLUMN} IS NOT NULL AND ` +
23
+ `(status = 'expired' OR execution_outcome IS NOT NULL)\`. Any cell left after that is on a still-live row and holds an ` +
24
+ `undelivered instruction: decide or cancel those runs on server 7.73.x first (or accept dropping the instruction), ` +
25
+ `then clear the rest (\`UPDATE checkpoint SET ${RETIRED_STEER_SEAT_COLUMN} = NULL WHERE ${RETIRED_STEER_SEAT_COLUMN} IS NOT NULL\`) ` +
26
+ `and start this version again.`);
27
+ this.rows = rows;
28
+ this.name = "RetiredSteerSeatError";
29
+ }
30
+ }
31
+ function mysqlDb(conn) {
32
+ return {
33
+ async rows(sql) {
34
+ const [rows] = await conn.query(sql);
35
+ return rows;
36
+ },
37
+ async exec(sql) {
38
+ const [r] = await conn.query(sql);
39
+ return Number(r.affectedRows ?? 0);
40
+ },
41
+ };
42
+ }
43
+ function pgDb(client) {
44
+ return {
45
+ async rows(sql) {
46
+ const r = await client.query(sql);
47
+ return r.rows;
48
+ },
49
+ async exec(sql) {
50
+ const r = await client.query(sql);
51
+ return r.rowCount ?? 0;
52
+ },
53
+ };
54
+ }
55
+ async function tableExists(db, dialect, table) {
56
+ const sql = dialect === "tidb"
57
+ ? `SELECT 1 AS present FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = '${table}'`
58
+ : `SELECT 1 AS present FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = '${table}'`;
59
+ return (await db.rows(sql)).length > 0;
60
+ }
61
+ async function execUntil(db, sql, postcondition) {
62
+ try {
63
+ await db.exec(sql);
64
+ }
65
+ catch (e) {
66
+ if (!(await postcondition()))
67
+ throw e;
68
+ }
69
+ }
70
+ async function columnExists(db, dialect, table, column) {
71
+ const scope = dialect === "tidb" ? "DATABASE()" : "current_schema()";
72
+ const sql = `SELECT 1 AS present FROM information_schema.columns WHERE table_schema = ${scope} AND table_name = '${table}' AND column_name = '${column}'`;
73
+ return (await db.rows(sql)).length > 0;
74
+ }
75
+ export async function runSqlOneTimeMigrations(dialect, pool) {
76
+ return dialect === "tidb" ? withMysqlLock(pool, (db) => migrateOn(db, "tidb")) : withPgLock(pool, (db) => migrateOn(db, "pg"));
77
+ }
78
+ async function withMysqlLock(pool, run) {
79
+ const conn = await pool.getConnection();
80
+ let locked = false;
81
+ try {
82
+ locked = await acquireEnsureSchemaLock(conn, "oneTimeMigrations");
83
+ return await run(mysqlDb(conn));
84
+ }
85
+ finally {
86
+ if (locked)
87
+ await releaseEnsureSchemaLock(conn, "oneTimeMigrations");
88
+ conn.release();
89
+ }
90
+ }
91
+ async function withPgLock(pool, run) {
92
+ const client = await pool.connect();
93
+ let locked = false;
94
+ try {
95
+ try {
96
+ await client.query("SELECT pg_advisory_lock($1)", [ENSURE_SCHEMA_LOCK_KEY]);
97
+ locked = true;
98
+ }
99
+ catch (err) {
100
+ console.warn(`[oneTimeMigrations] pg_advisory_lock(${ENSURE_SCHEMA_LOCK_KEY}) failed; running the migration unserialized:`, err);
101
+ }
102
+ return await run(pgDb(client));
103
+ }
104
+ finally {
105
+ if (locked) {
106
+ await client
107
+ .query("SELECT pg_advisory_unlock($1)", [ENSURE_SCHEMA_LOCK_KEY])
108
+ .catch((err) => void console.warn(`[oneTimeMigrations] pg_advisory_unlock(${ENSURE_SCHEMA_LOCK_KEY}) failed (auto-freed on connection close):`, err));
109
+ }
110
+ client.release();
111
+ }
112
+ }
113
+ async function migrateOn(db, dialect) {
114
+ const seatColumnPresent = await columnExists(db, dialect, "checkpoint", RETIRED_STEER_SEAT_COLUMN);
115
+ if (seatColumnPresent) {
116
+ const seatRows = await db.rows(`SELECT COUNT(*) AS n FROM checkpoint WHERE ${RETIRED_STEER_SEAT_COLUMN} IS NOT NULL`);
117
+ const seats = Number(seatRows[0]?.n ?? 0);
118
+ if (seats > 0)
119
+ throw new RetiredSteerSeatError(seats);
120
+ }
121
+ const snapshotTablePresent = await tableExists(db, dialect, RETIRED_SNAPSHOT_TABLE);
122
+ const retiredSchemaPresent = seatColumnPresent || snapshotTablePresent;
123
+ const askOriginRowsMigrated = !retiredSchemaPresent
124
+ ? 0
125
+ : dialect === "tidb"
126
+ ? (await db.exec(`UPDATE task_event SET data = JSON_SET(data, '$.gate.origin', '${RETIRED_ASK_ORIGIN.to}') ` +
127
+ `WHERE type = '${GATE_BEARING_LEDGER_EVENT_TYPE}' AND JSON_UNQUOTE(JSON_EXTRACT(data, '$.gate.origin')) = '${RETIRED_ASK_ORIGIN.from}'`)) +
128
+ (await db.exec(`UPDATE checkpoint SET execution_outcome = JSON_SET(execution_outcome, '$.origin', '${RETIRED_ASK_ORIGIN.to}') ` +
129
+ `WHERE JSON_UNQUOTE(JSON_EXTRACT(execution_outcome, '$.origin')) = '${RETIRED_ASK_ORIGIN.from}'`))
130
+ : (await db.exec(`UPDATE task_event SET data = jsonb_set(data, '{gate,origin}', '"${RETIRED_ASK_ORIGIN.to}"') ` +
131
+ `WHERE type = '${GATE_BEARING_LEDGER_EVENT_TYPE}' AND data->'gate'->>'origin' = '${RETIRED_ASK_ORIGIN.from}'`)) +
132
+ (await db.exec(`UPDATE checkpoint SET execution_outcome = jsonb_set(execution_outcome, '{origin}', '"${RETIRED_ASK_ORIGIN.to}"') ` +
133
+ `WHERE execution_outcome->>'origin' = '${RETIRED_ASK_ORIGIN.from}'`));
134
+ let retiredSnapshotTable = "absent";
135
+ let retiredSnapshotRows = 0;
136
+ let retiredSteerSeatColumn = "absent";
137
+ const destroyed = [];
138
+ try {
139
+ const counted = !snapshotTablePresent
140
+ ? undefined
141
+ : await db.rows(`SELECT COUNT(*) AS n FROM ${RETIRED_SNAPSHOT_TABLE}`).then((rows) => Number(rows[0]?.n ?? 0), async (e) => {
142
+ if (await tableExists(db, dialect, RETIRED_SNAPSHOT_TABLE))
143
+ throw e;
144
+ return undefined;
145
+ });
146
+ if (counted !== undefined) {
147
+ retiredSnapshotRows = counted;
148
+ await execUntil(db, `DROP TABLE IF EXISTS ${RETIRED_SNAPSHOT_TABLE}`, async () => !(await tableExists(db, dialect, RETIRED_SNAPSHOT_TABLE)));
149
+ retiredSnapshotTable = "dropped";
150
+ destroyed.push(`dropped table ${RETIRED_SNAPSHOT_TABLE} (${retiredSnapshotRows} row(s))`);
151
+ }
152
+ if (seatColumnPresent) {
153
+ await execUntil(db, `ALTER TABLE checkpoint DROP COLUMN ${RETIRED_STEER_SEAT_COLUMN}`, async () => !(await columnExists(db, dialect, "checkpoint", RETIRED_STEER_SEAT_COLUMN)));
154
+ retiredSteerSeatColumn = "dropped";
155
+ destroyed.push(`dropped column checkpoint.${RETIRED_STEER_SEAT_COLUMN}`);
156
+ }
157
+ }
158
+ catch (e) {
159
+ if (destroyed.length === 0)
160
+ throw e;
161
+ throw new Error(`one-time migration failed AFTER completing destructive steps — record these before retrying, a retry cannot ` +
162
+ `report them again: ${destroyed.join("; ")}. Underlying failure: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
163
+ }
164
+ return { retiredSnapshotTable, retiredSnapshotRows, retiredSteerSeatColumn, askOriginRowsMigrated };
165
+ }
166
+ export function migrateRetiredAskOrigin(type, data) {
167
+ if (type !== GATE_BEARING_LEDGER_EVENT_TYPE)
168
+ return undefined;
169
+ if (data === null || typeof data !== "object" || Array.isArray(data))
170
+ return undefined;
171
+ const d = data;
172
+ const gate = d.gate;
173
+ if (gate === null || typeof gate !== "object" || Array.isArray(gate))
174
+ return undefined;
175
+ const g = gate;
176
+ if (g.origin !== RETIRED_ASK_ORIGIN.from)
177
+ return undefined;
178
+ return { ...d, gate: { ...g, origin: RETIRED_ASK_ORIGIN.to } };
179
+ }
180
+ export function logOneTimeMigrations(log, backend, report) {
181
+ const changed = report.retiredSnapshotTable === "dropped" || report.retiredSteerSeatColumn === "dropped" || report.askOriginRowsMigrated > 0;
182
+ (changed ? log.warn : log.info).call(log, "one_time_migrations_applied", {
183
+ backend,
184
+ retiredSnapshotTable: report.retiredSnapshotTable,
185
+ retiredSnapshotRows: report.retiredSnapshotRows,
186
+ retiredSteerSeatColumn: report.retiredSteerSeatColumn,
187
+ askOriginRowsMigrated: report.askOriginRowsMigrated,
188
+ note: "one-time persisted-data migrations for this version's retirements (retired whole-tree snapshot index table " +
189
+ "dropped; retired single-seat steering column dropped; retired ask-origin spelling rewritten in place). This " +
190
+ "whole migration step is removed in the next minor — start this version at least once before upgrading past it.",
191
+ });
192
+ }
193
+ //# sourceMappingURL=one-time-migrations.js.map
@@ -69,6 +69,12 @@ export type PgPoolWarnSink = (msg: string, meta?: Record<string, unknown>) => vo
69
69
  * `onIdleClientError` 缺省 console.warn(与本文件 `ensurePgSchema` 的既有告警姿势一致)——生产
70
70
  * 装配(store-backend `createStoreBackend`)递结构化 logger 的 warn 座。 */
71
71
  export declare function createPgPool(opts: string | pg.PoolConfig, onIdleClientError?: PgPoolWarnSink): pg.Pool;
72
+ /** A fixed bigint key for the `pg_advisory_lock` that serializes the whole `ensurePgSchema` DDL run (see below).
73
+ * Arbitrary constant; only its uniqueness within this app matters (advisory-lock keys are app-private).
74
+ * Exported so **boot-time schema-shaped work outside this function** serializes against it under the SAME key —
75
+ * one lock identity for the whole class (the one-time migration段 is the current user; it runs after ensure and
76
+ * must not interleave with another replica's ensure). */
77
+ export declare const ENSURE_SCHEMA_LOCK_KEY = 4906;
72
78
  /** Idempotent central schema apply (the PG twin of tidb-pool.ts ensureSchema): the run/checkpoint tables here +
73
79
  * every PG store's own (disjoint) tables. Order is irrelevant (CREATE IF NOT EXISTS, no cross-table FKs).
74
80
  *
@@ -81,11 +81,10 @@ export const PG_SCHEMA_STATEMENTS = [
81
81
  terminal_at_ms BIGINT,
82
82
  gate_kind VARCHAR(32) COLLATE "C",
83
83
  bound_input_hash VARCHAR(190) COLLATE "C",
84
- -- pending_steer / pending_steer_queue / pending_steer_rev:队列化后的 durable steering(core 5.14.0
85
- -- #147)。三列的语义、「为什么队列另起一列而不是覆盖旧列」(滚动升级窗里旧副本的无条件覆盖写)、
86
- -- 48000 字节预算、以及「为什么另起一个整数 rev 列而不拿 JSON 旧值当 CAS 守卫」的完整由来见 MySQL
87
- -- 孪生(tidb-pool.ts 同三列的行内注)。PG text 无长度上限,预算由 core 侧统一执法。
88
- pending_steer TEXT COLLATE "C",
84
+ -- pending_steer_queue / pending_steer_rev:队列化后的 durable steering(core 5.14.0 #147)。两列的语义、
85
+ -- 退役的单座列 pending_steer 为何删列(以及存量库的一次性迁移与那条拒启臂)、48000 字节预算、
86
+ -- 以及「为什么另起一个整数 rev 列而不拿 JSON 旧值当 CAS 守卫」的完整由来见 MySQL 孪生
87
+ -- (tidb-pool.ts 同两列的行内注)。PG text 无长度上限,预算由 core 侧统一执法。
89
88
  pending_steer_queue TEXT COLLATE "C",
90
89
  pending_steer_rev BIGINT NOT NULL DEFAULT 0,
91
90
  risk_descriptor TEXT COLLATE "C",
@@ -155,14 +154,6 @@ export const PG_SCHEMA_STATEMENTS = [
155
154
  PRIMARY KEY (policy_key)
156
155
  )`,
157
156
  `CREATE INDEX IF NOT EXISTS idx_session_policy_session ON session_policy (session_id)`,
158
- `CREATE TABLE IF NOT EXISTS snapshot_manifest (
159
- scope VARCHAR(64) COLLATE "C" NOT NULL,
160
- snap_key VARCHAR(64) COLLATE "C" NOT NULL,
161
- rel_path VARCHAR(1024) COLLATE "C" NOT NULL,
162
- blob_hash CHAR(64) COLLATE "C" NOT NULL
163
- )`,
164
- `CREATE INDEX IF NOT EXISTS idx_snapshot_manifest_sk ON snapshot_manifest (scope, snap_key)`,
165
- `CREATE INDEX IF NOT EXISTS idx_snapshot_manifest_blob ON snapshot_manifest (blob_hash)`,
166
157
  `CREATE TABLE IF NOT EXISTS snapshot_blob (
167
158
  blob_hash CHAR(64) COLLATE "C" PRIMARY KEY,
168
159
  byte_len BIGINT NOT NULL,
@@ -400,7 +391,7 @@ export function createPgPool(opts, onIdleClientError) {
400
391
  });
401
392
  return pool;
402
393
  }
403
- const ENSURE_SCHEMA_LOCK_KEY = 4906;
394
+ export const ENSURE_SCHEMA_LOCK_KEY = 4906;
404
395
  export async function ensurePgSchema(pool) {
405
396
  const client = await pool.connect();
406
397
  let locked = false;
@@ -377,7 +377,6 @@ export class SqlRetentionStore {
377
377
  ["approval_exemption", "session_id"],
378
378
  ["session_policy", "session_id"],
379
379
  ...(this.opts.taskAttachmentTable ? [["task_attachment", "session_id"]] : []),
380
- ["snapshot_manifest", "scope"],
381
380
  ["file_history_tracked", "scope"],
382
381
  ["file_history_version", "scope"],
383
382
  ["file_history_boundary", "scope"],
@@ -4,6 +4,7 @@ import type { SessionStore, SessionPolicyStore, FileHistoryStore, WorkflowJourna
4
4
  import { type OutcomeSink } from "./file-outcome-sink.js";
5
5
  import { type InboxWarn } from "./workflow-run-store-sql.js";
6
6
  import { FileRunStore } from "./file-run-store.js";
7
+ import { type OneTimeMigrationReport } from "./one-time-migrations.js";
7
8
  import { LocalCheckpointStore } from "./local-checkpoint-store.js";
8
9
  import { FileResumeAnchorStore } from "./file-resume-anchor-store.js";
9
10
  import { type ApprovalExemptionStore } from "./approval-exemption-store.js";
@@ -221,22 +222,17 @@ export interface StoreBackend {
221
222
  * 于是单机默认部署上 `memoryCapture:"off"` 两条 ingress 结构性不可达(B-085)。
222
223
  * 刻意仍不给 local 一个**内存** twin:内存形会在重启时把一条隐私记录静默丢掉(比文件三腿严格更差)。 */
223
224
  sessionCaptureRecords(): SessionCaptureRecordSource | undefined;
224
- /** S-1 legacy-epoch discriminant (core `RunnerDeps.legacyRewindBoundaryProbe`): does the RETIRED whole-tree
225
- * snapshot epoch hold a boundary for (sessionId, entryId)? Reads the legacy `snapshot_manifest` table.
226
- * SQL backends only (local never had a whole-tree epoch table). Diagnostic input on the refusal path —
227
- * a probe that cannot tell answers undefined (reads exactly like an absent probe), never throws. */
228
- legacyRewindBoundaryProbe?(query: {
229
- sessionId: string;
230
- entryId: string;
231
- }): Promise<boolean | undefined>;
232
- /** Retired-epoch byte hygiene: GC grace-passed orphan `snapshot_blob` rows/objects (rows the retention
233
- * lane's `snapshot_manifest` sweeps keep orphaning — without this leg their BYTES would outlive every
234
- * manifest reference forever, incl. MinIO objects). SQL backends only; reaper-driven. */
225
+ /** Retired-epoch byte hygiene: GC grace-passed orphan `snapshot_blob` rows/objects — the whole-tree snapshot
226
+ * epoch is gone (its manifest index table retired with it), so EVERY row in that domain is an orphan and its
227
+ * BYTES (incl. MinIO objects) would otherwise outlive the epoch forever. SQL backends only; reaper-driven. */
235
228
  legacySnapshotBlobSweep?(): Promise<number>;
236
- /** Retired-epoch right-to-delete: drop the session's LEGACY `snapshot_manifest` rows on the online E21
237
- * purge (the retention lane already sweeps them on expiry — the two delete legs must converge on the
238
- * same row set; a user's DELETE must reach old-epoch data too). Bytes go via the async legacy sweep. */
239
- legacySnapshotPurge?(sessionId: string): Promise<number>;
229
+ /** 🔴 **一次性迁移(随 7.75.0 连同 `one-time-migrations.ts` 一起从接口上删)** —— 本版三处退役落在
230
+ * 已写进库里的字节上,升一次级改完、兼容臂当场删干净(硬 breaking 纪律:持久数据面 = 响亮拒 +
231
+ * 一次性迁移,窗过即删,永不长期双读)。逐条语义、幂等与拒启臂见该模块头注。
232
+ *
233
+ * **必选,不是 optional**:漏实装一支后端 = 那条车道的存量字节永远停在旧读法上,而读腿已经只认新的
234
+ * ⇒ 必须是编译期错误,不是运行期的一次静默跳过。 */
235
+ runOneTimeMigrations(): Promise<OneTimeMigrationReport>;
240
236
  /** SVC-2 (core CORE-7) durable WorkflowJournalStore — the cross-replica resume journal for an LLM-authored workflow
241
237
  * (RunWorkflowOptions.journalStore). REQUIRED on all backends (tidb/pg = durable twins; local = core's
242
238
  * InMemoryWorkflowJournalStore, single-replica/in-process).
@@ -7,6 +7,7 @@ import { TiDBOutcomeLedger, PgOutcomeLedger } from "./outcome-ledger-sql.js";
7
7
  import { FileOutcomeSink } from "./file-outcome-sink.js";
8
8
  import { TiDBWorkflowRunStore, TiDBWorkflowCompletionInbox, TiDBWorkflowNotifyJournalStore, TiDBWorkflowAgentSessionIndex, PgWorkflowRunStore, PgWorkflowCompletionInbox, PgWorkflowNotifyJournalStore, PgWorkflowAgentSessionIndex } from "./workflow-run-store-sql.js";
9
9
  import { FileRunStore, RunStoreStrictHydrateError } from "./file-run-store.js";
10
+ import { NO_SQL_MIGRATIONS, runSqlOneTimeMigrations } from "./one-time-migrations.js";
10
11
  import { LocalSessionStore } from "./local-session-store.js";
11
12
  import { LocalCheckpointStore } from "./local-checkpoint-store.js";
12
13
  import { FileResumeAnchorStore } from "./file-resume-anchor-store.js";
@@ -73,29 +74,8 @@ function legacyBlobBackend(config, dialect, pool) {
73
74
  ...(s.presignTtlSec ? { presignTtlSec: s.presignTtlSec } : {}),
74
75
  }, { dialect, pool });
75
76
  }
76
- async function legacyProbeImpl(dialect, pool, query) {
77
- try {
78
- if (dialect === "tidb") {
79
- const [rows] = await pool.query("SELECT 1 FROM snapshot_manifest WHERE scope = ? AND snap_key = ? LIMIT 1", [query.sessionId, query.entryId]);
80
- return rows.length > 0;
81
- }
82
- const r = await pool.query("SELECT 1 FROM snapshot_manifest WHERE scope = $1 AND snap_key = $2 LIMIT 1", [query.sessionId, query.entryId]);
83
- return r.rows.length > 0;
84
- }
85
- catch {
86
- return undefined;
87
- }
88
- }
89
- async function legacyPurgeImpl(dialect, pool, sessionId) {
90
- if (dialect === "tidb") {
91
- const [r] = await pool.query("DELETE FROM snapshot_manifest WHERE scope = ?", [sessionId]);
92
- return Number(r.affectedRows ?? 0);
93
- }
94
- const r = await pool.query("DELETE FROM snapshot_manifest WHERE scope = $1", [sessionId]);
95
- return r.rowCount ?? 0;
96
- }
97
77
  async function legacySweepImpl(config, dialect, pool) {
98
- const sql = "SELECT blob_hash FROM snapshot_blob WHERE blob_hash NOT IN (SELECT blob_hash FROM snapshot_manifest)";
78
+ const sql = "SELECT blob_hash FROM snapshot_blob";
99
79
  let orphans;
100
80
  if (dialect === "tidb") {
101
81
  const [rows] = await pool.query(sql);
@@ -140,9 +120,8 @@ class TiDBBackend {
140
120
  memoryOptOutGrant() { return new TiDBMemoryOptOutGrantStore(this.pool); }
141
121
  sessionCaptureRecords() { return () => (this.captureRecordsInst ??= new TiDBSessionCaptureRecordStore(this.pool)); }
142
122
  captureRecordsInst;
143
- legacyRewindBoundaryProbe(query) { return legacyProbeImpl("tidb", this.pool, query); }
144
123
  legacySnapshotBlobSweep() { return legacySweepImpl(this.config, "tidb", this.pool); }
145
- legacySnapshotPurge(sessionId) { return legacyPurgeImpl("tidb", this.pool, sessionId); }
124
+ runOneTimeMigrations() { return runSqlOneTimeMigrations("tidb", this.pool); }
146
125
  workflowJournal() { return new TiDBWorkflowJournalStore(this.pool); }
147
126
  outcomeSink() { return new TiDBOutcomeLedger(this.pool); }
148
127
  workflowRun(onWarn) { return new TiDBWorkflowRunStore(this.pool, onWarn); }
@@ -188,9 +167,8 @@ class PgBackend {
188
167
  memoryOptOutGrant() { return new PgMemoryOptOutGrantStore(this.pool); }
189
168
  sessionCaptureRecords() { return () => (this.captureRecordsInst ??= new PgSessionCaptureRecordStore(this.pool)); }
190
169
  captureRecordsInst;
191
- legacyRewindBoundaryProbe(query) { return legacyProbeImpl("pg", this.pool, query); }
192
170
  legacySnapshotBlobSweep() { return legacySweepImpl(this.config, "pg", this.pool); }
193
- legacySnapshotPurge(sessionId) { return legacyPurgeImpl("pg", this.pool, sessionId); }
171
+ runOneTimeMigrations() { return runSqlOneTimeMigrations("pg", this.pool); }
194
172
  workflowJournal() { return new PgWorkflowJournalStore(this.pool); }
195
173
  outcomeSink() { return new PgOutcomeLedger(this.pool); }
196
174
  workflowRun(onWarn) { return new PgWorkflowRunStore(this.pool, onWarn); }
@@ -269,6 +247,9 @@ class LocalBackend {
269
247
  : (plane) => fileSessionCaptureRecordStore(plane?.controlDir ?? ownControlDir);
270
248
  }
271
249
  async ensureSchema() { }
250
+ async runOneTimeMigrations() {
251
+ return { ...NO_SQL_MIGRATIONS, askOriginRowsMigrated: await this.runStore.migrateRetiredAskOriginRows() };
252
+ }
272
253
  async close() {
273
254
  await this.sessionStore.dispose();
274
255
  this.runStore.dispose();
@@ -2,8 +2,8 @@
2
2
  * TaskAttachmentStore —— D-1 通用文件上传的**独立附件 store**(clay 拍 2026-07-27:不复用 fileSnapshot
3
3
  * 的 content-addressed blob 面,新建)。TiDB/PG 双方言 + local 文件店(local-task-attachment-store.ts)。
4
4
  *
5
- * 为什么独立而不是骑 SqlBlobBackend/snapshot_blob:那套的引用追踪=「manifest 里没有的即 orphan」,
6
- * 附件不在任何 snapshot_manifest ⇒ 会被 sweepOrphanBlobs 在 1h grace 后误收。生命周期也不同:
5
+ * 为什么独立而不是骑 SqlBlobBackend/snapshot_blob:那套的引用追踪=「本域 manifest 里没有的即 orphan」,
6
+ * 附件不在任何一域的 manifest 里 ⇒ 会被孤儿扫在 1h grace 后误收。生命周期也不同:
7
7
  * snapshot blob 跟 session 的工作树;**附件跟 task/session 的绑定**(上传先行、提交时绑定、E21 随会话
8
8
  * 级联删、未绑定的按 TTL 收割)。
9
9
  *
@@ -222,20 +222,16 @@ export const SCHEMA_STATEMENTS = [
222
222
  -- D-1 TOCTOU binding guard is UNREACHABLE by the portal without it — it can then only do the unbound legacy
223
223
  -- fallback). NULL on PRE-D-1 rows ⇒ that unbound fallback.
224
224
  bound_input_hash VARCHAR(190) NULL,
225
- -- pending_steer (design/80 D-A, durable steering):**队列化之前**的单座列,存 {text,trusted}。
226
- -- core 5.14.0(#147 BREAKING)之后本服务**只读不写**它:存量行的那一座由 core readPendingSteerQueue
227
- -- 折成 member 0,零迁移。NULL = 没有旧座。Never touches status/resolve (design/80 inv #4)。
228
- pending_steer TEXT NULL,
229
- -- pending_steer_queue:core 5.14.0(#147)的**有界有序队列**(PendingSteerEntry[] 的 JSON)。
230
- -- 为什么队列另起一列、而不是把信封写回 pending_steer(codex 复审 2026-08-06 F3,采纳):滚动升级/
231
- -- 回滚窗里旧副本仍在跑,它对 pending_steer 是**无条件覆盖**写 —— 队列若躺在同一列,旧副本接的下一条
232
- -- steer 会把 5.14 排好的整队悄悄抹掉,正是 #147 要消灭的那种静默丢失换了个位置复发。分列之后:
233
- -- 旧副本写它的单座 ⇒ 落 pending_steer,新读侧把它折成 member 0,**两边都活着**;旧副本读不到队列
234
- -- 是 core 自己已披露的回滚代价(CHANGELOG:rolling back across the queue event loses steers),
235
- -- 那半无法在店面消除,但**写侧互相清零**这半可以,且代价只是一个可空列。
236
- -- 容量:core 的 PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES = 48000 字节是对 {pendingSteer,queue} 整信封的
237
- -- 硬上界(越界 fail-loud steering.queue_full,绝不驱逐)⇒ 本列恒小于它,MySQL TEXT 的 65535 字节
238
- -- 够用,**不升 LONGTEXT**(core 常量注里点名「under a MySQL TEXT column with UTF-8 headroom」)。
225
+ -- 🔴 队列化之前的**单座**列 pending_steer 已**退役删列**:那个席位在引擎侧没了(core 7.17.0 #717 d
226
+ -- 对带席的行 PRE-CAS 拒 seat_retired),留着一列没人读、没人写的字节只会让下一个人以为还有两种读法。
227
+ -- 存量库由启动期的一次性迁移处置(plugins/one-time-migrations.ts,随 7.75.0 删):列上还有非空值 ⇒
228
+ -- **响亮拒启**并给出两条出路(回 7.16.0 把那些 checkpoint resume 一次让座位排空 / 运维手工清列),
229
+ -- 清空之后 DROP COLUMN。刻意**不**自动把裸单座折进队列:那等于本仓替引擎铸一条它从没写过的队列条目。
230
+ -- pending_steer_queue:core 5.14.0(#147)的**有界有序队列**(PendingSteerEntry[] 的 JSON),现在是本表上
231
+ -- durable steering 的唯一载体。Never touches status/resolve (design/80 inv #4)。
232
+ -- 容量:core 的 PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES = 48000 字节是整信封的硬上界(越界 fail-loud
233
+ -- steering.queue_full,绝不驱逐)⇒ 本列恒小于它,MySQL TEXT 的 65535 字节够用,**不升 LONGTEXT**
234
+ -- (core 常量注里点名「under a MySQL TEXT column with UTF-8 headroom」)。
239
235
  pending_steer_queue TEXT NULL,
240
236
  -- pending_steer_rev:pending_steer_queue 列**自己**的乐观锁计数(与 resolve/reopen 的 rev 是两个轴,
241
237
  -- 刻意不复用)。队列化之后 setPendingSteer 从「一条无条件 UPDATE」变成「读-改-写」,中间那道窗必须
@@ -332,14 +328,6 @@ export const SCHEMA_STATEMENTS = [
332
328
  updated_at DATETIME(3) NOT NULL,
333
329
  PRIMARY KEY (policy_key),
334
330
  KEY idx_session_policy_session (session_id)
335
- ) COLLATE utf8mb4_bin`,
336
- `CREATE TABLE IF NOT EXISTS snapshot_manifest (
337
- scope VARCHAR(64) NOT NULL,
338
- snap_key VARCHAR(64) NOT NULL,
339
- rel_path VARCHAR(1024) NOT NULL,
340
- blob_hash CHAR(64) NOT NULL,
341
- KEY idx_snapshot_manifest_sk (scope, snap_key),
342
- KEY idx_snapshot_manifest_blob (blob_hash)
343
331
  ) COLLATE utf8mb4_bin`,
344
332
  `CREATE TABLE IF NOT EXISTS snapshot_blob (
345
333
  blob_hash CHAR(64) NOT NULL,
@@ -348,7 +336,7 @@ export const SCHEMA_STATEMENTS = [
348
336
  -- comment above; this column is where it is enforced). It was originally LONGBLOB NOT NULL back when the
349
337
  -- SQL store was the ONLY blob backend; the MinIO offload then required an INDEX-ONLY row here (bytes NULL,
350
338
  -- the bytes living in MinIO) so that gcOrphanBlobs / reap / deleteBySession (E21 purge) / sweepOrphanBlobs
351
- -- keep computing the orphan/grace SET from snapshot_blob for BOTH backends identically — and so an E21 purge
339
+ -- keep computing the grace SET from snapshot_blob for BOTH backends identically — and so an E21 purge
352
340
  -- can DELETE the MinIO object instead of leaking it. Re-adding NOT NULL here would silently break MinIO-mode
353
341
  -- reference tracking. See blob-backend.ts MinioBlobBackend.
354
342
  bytes LONGBLOB NULL,
@@ -85,6 +85,15 @@ export declare function withRedactedError<T extends {
85
85
  * 里找不到那一行 ⇒ 落 legacy 腿拿一个**错因**的 409 `conflict.resume_context_unavailable`,人的批准无处兑付;
86
86
  * 同时 core resume 路径上那道「blob 里 parked 而 journal 缺条目 ⇒ 拒 resume」的防线(`prior.agents`)也随之失明。
87
87
  *
88
+ * 🔴 **7.74.0(core 7.17.0 [ref] 义务②):`parks[]` 排在恢复身份之上 —— 一级都不裁。**
89
+ * 它不是观测位,是**下一次 resume 的准入输入**:core 的 resume 准入在任何 agent 被派发之前读记录上的
90
+ * 这一位,读不到就整条拒(`workflow.park_truth_unreadable` / `parks_unreadable`)——不是「少一条 park」,
91
+ * 是**这条 run 再也 resume 不了**。所以阶梯的判据现在是两条,按这个次序:①`parks` 永不放弃;
92
+ * ②恢复身份行最后放弃。实现上五级全部走 `{...上一级}` 展开、没有任何一级点名裁它,机器背书 =
93
+ * `test/slim-oversize-run.test.ts` 里那两格(最狠的 step 5 上 `parks` 仍逐字完整 + 负控:记录本来没有
94
+ * 这一位时不许凭空补 `[]`)。缺席 ⇒ 不补:补 `[]` 会把「这版引擎没写这个位」伪造成「这条 run 没有 park」,
95
+ * 而 core 对两者的处置相反。
96
+ *
88
97
  * 阶梯 = 一张**按价值排序的放弃清单**,一级一级试到装得下为止(**判据只有一条:恢复身份最后放弃**):
89
98
  * step 1 —— 两个无界字符串(`result` / `error`)截断带标记;
90
99
  * step 2 —— `phases` / `groups` 整段丢,`agents` 收敛成**恢复身份行**
@@ -5,7 +5,18 @@ export declare const MAX_CWD_CHARS = 4096;
5
5
  * over-long, and NUL-byte injection. NOT an existence check (the env surfaces a missing dir at first tool use).
6
6
  * Defense-in-depth (double-review LOW): `path.isAbsolute` is SYNTACTIC — `/a/../../etc` is "absolute" yet contains
7
7
  * `..` traversal that the host env would `path.resolve` away. Single-user host lane = the caller's own machine (no
8
- * cross-tenant escape), but we reject any path with a `..` segment so the honored cwd is the literal path sent. */
8
+ * cross-tenant escape), but we reject any path with a `..` segment so the honored cwd is the literal path sent.
9
+ *
10
+ * 🔴 **接缝登记(7.74.0 / core 7.17.0 [ref],今日无可达分歧 —— 登记的是「以后会分歧」)**:这条判断回答的是
11
+ * 「引擎眼里这条 cwd 算不算绝对」,而引擎自本版起按**树的家族**判(`isAbsoluteForFamily(pathFamilyOf({root,cwd}), p)`,
12
+ * 家族从 root/cwd 的**拼法**读,**从不**读 `process.platform`)。本函数用的是 Node 的宿主口径,两者:
13
+ * · **posix 家族逐字同判**(2026-09-12 实测六形 `/etc/passwd` / `a/b` / `\\host\share\x` / `C:\x` /
14
+ * `//host/share/x` / `/c/x` 六格全同)⇒ 今天(Linux 宿主 + posix 树,本位又只在单用户 `host` 车道上场)
15
+ * **零分歧**,不改;
16
+ * · **win 家族分歧在 `/`-开头但没有盘符的那一类**(`/etc/passwd`、`/c/x`):Node 的 win32 口径说「绝对」,
17
+ * 引擎说「rooted 但未完全限定」(要拼上基准的盘/共享根才成一条真路径)。⇒ **哪天 `host` 车道要接 win 树,
18
+ * 先改这里**:把判据换成 core 的 `isAbsoluteForFamily`,并把家族从这条 cwd 自己的拼法取。
19
+ * 钉见 `test/task-cwd.test.ts` 的「[ref] 路径家族接缝」格(实测读数,不是推断)。 */
9
20
  export declare function isValidCwd(cwd: unknown): cwd is string;
10
21
  /**
11
22
  * The deployment-level gate: does THIS deployment honor a caller-supplied `cwd`? TRUE only for the single-user
@@ -344,6 +344,12 @@ export interface FsWriteGateWiring {
344
344
  * to confine the exemption to POSIX-absolute canonical keys — core's `canonicalizeTarget` short-circuits
345
345
  * backslash-UNC forms to `ok:true, key=raw` WITHOUT consulting the env, and an unconfined name-keyed grant
346
346
  * would turn those unresolvable forms into an allow (codex review 2026-08-05).
347
+ * 🔴 **坐标刷新(7.74.0 / core 7.17.0 [ref];结论不变,前提收窄了)**:那条短路现在**只在 `win` 家族上**
348
+ * (`dist/tools/fs/safety.js` 的 `canonicalizeTarget`:`family === "win" && isUncSpelling(...)` 才折 UNC
349
+ * 并直接返回);在 `posix` 家族上,同一个反斜杠 UNC 拼法在更早一步的 `win32NamespaceScreen` 上就被
350
+ * **拒**(`unc_on_posix_tree`),压根到不了短路。⇒ 今日部署形(posix 树)这条腿的暴露面比当年更小,
351
+ * 而本臂的 POSIX-absolute 收敛**继续是对的**:它挡的是「未经 env 解析的 key 拿到 name-keyed 放行」,
352
+ * 在两个家族上都成立,且方向仍是 fail-closed。不改实现。
347
353
  * The probe is consulted by the gate RIGHT BEFORE it would ask (after the exempt/accept dir layering — dist 亲读:true ⇒
348
354
  * allow with decisionReason "rule", a THROWN probe ⇒ not exempt = fail-closed to ask). Same canonical
349
355
  * toolName key space as the ask-policy layer's exemption probe — ONE grant serves both layers; the gate-level
@@ -148,10 +148,16 @@ type _GuardSettlementReverse = AssertAllKeysHandled<Exclude<SettlementProjected,
148
148
  type SettlementWhoProjected = "party" | "approver" | "window";
149
149
  type _GuardSettlementWho = AssertAllKeysHandled<Exclude<SettlementWhoKeys, SettlementWhoProjected>>;
150
150
  type _GuardSettlementWhoReverse = AssertAllKeysHandled<Exclude<SettlementWhoProjected, SettlementWhoKeys>>;
151
- type WorkflowRunForwarded = "id" | "scope" | "status" | "startedAt" | "endedAt" | "createdAt" | "rev" | "name" | "description" | "result" | "error" | "errorCode" | "agentFailures" | "stats" | "phases" | "agents" | "groups";
151
+ type WorkflowRunForwarded = "id" | "scope" | "status" | "startedAt" | "endedAt" | "createdAt" | "rev" | "parks" | "name" | "description" | "result" | "error" | "errorCode" | "agentFailures" | "stats" | "phases" | "agents" | "groups";
152
152
  type WorkflowRunStripped = "sourceTaskId" | "originatingSessionId" | "effectiveArgs" | "budgetOvershoot" | "timeoutInterruption" | "resultFull" | "completionId" | "resume" | "journalSkips";
153
153
  type _GuardWorkflowRun = AssertAllKeysHandled<Exclude<keyof WorkflowRun, WorkflowRunForwarded | WorkflowRunStripped>>;
154
154
  type _GuardWorkflowRunReverse = AssertAllKeysHandled<Exclude<WorkflowRunForwarded | WorkflowRunStripped, keyof WorkflowRun>>;
155
+ type ParkRowKeys = keyof NonNullable<WorkflowRun["parks"]>[number];
156
+ type ParkRowProjected = "callKey" | "sessionId" | "originRunId";
157
+ type ParkRowWithheld = "token";
158
+ type _GuardParkRow = AssertAllKeysHandled<Exclude<ParkRowKeys, ParkRowProjected | ParkRowWithheld>>;
159
+ type _NoDoubleBookParkRow = AssertAllKeysHandled<Extract<ParkRowProjected, ParkRowWithheld>>;
160
+ type _GuardParkRowReverse = AssertAllKeysHandled<Exclude<ParkRowProjected | ParkRowWithheld, ParkRowKeys>>;
155
161
  export type CoreKeysetGuards = [
156
162
  _GuardNotification,
157
163
  _GuardNotificationReverse,
@@ -223,7 +229,10 @@ export type CoreKeysetGuards = [
223
229
  _GuardSettlementWho,
224
230
  _GuardSettlementWhoReverse,
225
231
  _GuardWorkflowRun,
226
- _GuardWorkflowRunReverse
232
+ _GuardWorkflowRunReverse,
233
+ _GuardParkRow,
234
+ _NoDoubleBookParkRow,
235
+ _GuardParkRowReverse
227
236
  ];
228
237
  export {};
229
238
  //# sourceMappingURL=core-keyset-guard.d.ts.map