@sema-agent/server 7.73.1 → 7.75.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/USAGE.md +8 -0
- package/dist/boot/runner-deps.d.ts +0 -4
- package/dist/boot/runner-deps.js +1 -2
- package/dist/boot/session-faces.js +0 -2
- package/dist/boot/stores.js +3 -16
- package/dist/config-types.d.ts +1 -1
- package/dist/deployment-governance.d.ts +23 -3
- package/dist/deployment-governance.js +23 -2
- package/dist/env-facts.d.ts +8 -1
- package/dist/http/route-ctx.d.ts +9 -8
- package/dist/http/routes/approvals-assistant.js +2 -2
- package/dist/http/routes/runs.js +1 -1
- package/dist/http/routes/workflows.js +4 -0
- package/dist/http/server.js +4 -3
- package/dist/main.js +0 -1
- package/dist/observability/fail-open.d.ts +2 -2
- package/dist/observability/fail-open.js +2 -2
- package/dist/observability/run-terminal-log.d.ts +2 -1
- package/dist/orchestration/workflow-agent-session-index.d.ts +41 -83
- package/dist/orchestration/workflow-agent-session-index.js +14 -33
- package/dist/parked-decide.d.ts +30 -33
- package/dist/parked-decide.js +11 -13
- package/dist/plugins/approval-ask-store-sql.js +1 -1
- package/dist/plugins/blob-backend.d.ts +6 -7
- package/dist/plugins/checkpoint-store-sql.d.ts +13 -10
- package/dist/plugins/checkpoint-store-sql.js +6 -24
- package/dist/plugins/file-run-store.d.ts +18 -0
- package/dist/plugins/file-run-store.js +27 -0
- package/dist/plugins/one-time-migrations.d.ts +66 -0
- package/dist/plugins/one-time-migrations.js +193 -0
- package/dist/plugins/pg-pool.d.ts +6 -0
- package/dist/plugins/pg-pool.js +5 -14
- package/dist/plugins/retention-store-sql.js +0 -1
- package/dist/plugins/store-backend.d.ts +11 -15
- package/dist/plugins/store-backend.js +7 -26
- package/dist/plugins/task-attachment-store.d.ts +2 -2
- package/dist/plugins/tidb-pool.js +11 -23
- package/dist/plugins/workflow-run-store-sql.d.ts +21 -21
- package/dist/plugins/workflow-run-store-sql.js +2 -16
- package/dist/task-cwd.d.ts +12 -1
- package/dist/task-settings.d.ts +6 -0
- package/dist/trace/core-keyset-guard.d.ts +11 -2
- package/dist/trace/project.d.ts +31 -31
- package/dist/trace/project.js +12 -17
- package/dist/trace/redact.js +295 -16
- package/package.json +3 -3
|
@@ -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
|
*
|
package/dist/plugins/pg-pool.js
CHANGED
|
@@ -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
|
-
--
|
|
85
|
-
--
|
|
86
|
-
--
|
|
87
|
-
--
|
|
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
|
-
/**
|
|
225
|
-
*
|
|
226
|
-
*
|
|
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
|
-
/**
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
6
|
-
*
|
|
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 (
|
|
226
|
-
--
|
|
227
|
-
--
|
|
228
|
-
|
|
229
|
-
--
|
|
230
|
-
--
|
|
231
|
-
--
|
|
232
|
-
--
|
|
233
|
-
--
|
|
234
|
-
--
|
|
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
|
|
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,
|
|
@@ -78,29 +78,29 @@ export declare function withRedactedError<T extends {
|
|
|
78
78
|
* write (including the TERMINAL one) keeps failing → the durable row is stuck `running` forever and the 24h
|
|
79
79
|
* orphan sweep finalizes a genuinely-completed run as `abandoned`. Instead degrade the PAYLOAD, not the write.
|
|
80
80
|
*
|
|
81
|
-
* 🔴
|
|
82
|
-
*
|
|
83
|
-
* `
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
81
|
+
* 🔴 **`parks[]` 是这条阶梯唯一不许碰的东西**(core 7.17.0 [ref] 义务②)。
|
|
82
|
+
* 它不是观测位,是**下一次 resume 的准入输入**:core 的 resume 准入在任何 agent 被派发之前读记录上的
|
|
83
|
+
* 这一位,读不到就整条拒(`workflow.park_truth_unreadable` / `parks_unreadable`)——不是「少一条 park」,
|
|
84
|
+
* 是**这条 run 再也 resume 不了**;它同时也是本仓 `/decide` 的 workflow 车道复核「这条 park 属于这条 run」
|
|
85
|
+
* 的**单一真源**(S-252)。实现上三级全部走 `{...上一级}` 展开、没有任何一级点名裁它,机器背书 =
|
|
86
|
+
* `test/slim-oversize-run.test.ts` 里那两格(最狠的一级上 `parks` 仍逐字完整 + 负控:记录本来没有
|
|
87
|
+
* 这一位时不许凭空补 `[]`)。缺席 ⇒ 不补:补 `[]` 会把「这版引擎没写这个位」伪造成「这条 run 没有 park」,
|
|
88
|
+
* 而 core 对两者的处置相反。
|
|
87
89
|
*
|
|
88
|
-
*
|
|
90
|
+
* 🔴 **S-252(7.75.0):阶梯不再为 park 另存第二份恢复身份。** 7.71.0 的 S-192 曾在 `agents[]` 里逐行保住
|
|
91
|
+
* `callKey`/`sessionId`/`parkedCheckpointToken`(外加两级 `label` 预算与一枚不可恢复标记),因为当时
|
|
92
|
+
* `/decide` 的复核只认那一行。core 7.17.0 之后同一条记录上有了 `parks[]`,那份保留就成了**第二个会漂的
|
|
93
|
+
* 定义**(真案:最后一级清空 `agents` 之后 token 仍躺在同一个 blob 的 `parks` 里,而 `/decide` 照拒)。
|
|
94
|
+
* ⇒ 整段退役,判别式改读单一真源。退役后的阶梯每一级都**不大于**退役前的对应级,物理下界不劣化。
|
|
95
|
+
*
|
|
96
|
+
* 阶梯 = 一张**按价值排序的放弃清单**,一级一级试到装得下为止:
|
|
89
97
|
* step 1 —— 两个无界字符串(`result` / `error`)截断带标记;
|
|
90
|
-
* step 2 —— `phases` / `
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* 一边原样留着它们、一边把唯一那条 parked 行删掉盖标记 —— 自己违反了自己那条判据,
|
|
97
|
-
* 一条本来赎得回的审批被判死(生产上界 4 MiB 上真复现);
|
|
98
|
-
* step 4 —— 恢复身份行上的**展示名**(`label`)—— 它是这条行上唯一可弃的展示物,身份四位恒在
|
|
99
|
-
* (codex r2-[high],红先复现后修:修前阶梯一边留着每行 200 字符的 label、一边把**全部**身份
|
|
100
|
-
* 删掉盖标记 —— 同一条判据第二次被自己违反;6000 条 CJK label 的 parked 行在生产上界上
|
|
101
|
-
* 实测 `agents:[]` + `parks:6000`,而清空 label 后 6000 条身份**全都装得下**);
|
|
102
|
-
* step 5 —— 连只剩身份的行都装不下 ⇒ 丢掉它们并落**显式不可恢复标记**({@link PARK_IDENTITY_LOSS_KEY}),
|
|
103
|
-
* `/decide` 与 SLA sweep 读到它就**响亮拒**,而不是让一条赎回不了的 park 看起来像普通 miss。
|
|
98
|
+
* step 2 —— 展示载荷整段丢(`phases` / `agents` / `groups`),`error` 收成短串;
|
|
99
|
+
* step 3 —— 顶层**观测载荷**:`effectiveArgs` 与 `resultFull` 整段丢、`name` / `description` 先脱后切。
|
|
100
|
+
* core 对 `effectiveArgs` 的成文口径逐字是 OBSERVATION-ONLY(never a gate input,never re-read by
|
|
101
|
+
* the engine;resume 的身份由 journal 的 callKey 携带,不是这个字段),`resultFull` 同类(取回面),
|
|
102
|
+
* 两者都**无界**;`name` / `description` 是脚本(LLM 撰写)经 `export const meta` 供的展示头,
|
|
103
|
+
* 同样无界 ⇒ **先脱后切**(切口会把读面脱敏所依赖的闭合引号一起切掉,半截凭据原样上 wire)。
|
|
104
104
|
* Returns null only when even the marked skeleton is oversize (physics — caller keeps the prior revision,
|
|
105
105
|
* matching the old behavior for that corner). Dialect-neutral (pure JS, no SQL) — shared verbatim by both twins. */
|
|
106
106
|
export declare function slimOversizeRun(run: WorkflowRun & {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { redactErrorMessage, redactHead } from "../observability/run-terminal-log.js";
|
|
2
2
|
import { summarizeWorkflowRun, isTerminalWorkflowStatus, WorkflowRunStoreError } from "@sema-agent/core";
|
|
3
3
|
import { foldKeyFamily, MAX_PENDING_PER_SESSION, PURGE_FENCE_MS, SERVED_FENCE_MS, } from "../orchestration/workflow-completion-inbox.js";
|
|
4
|
-
import {
|
|
4
|
+
import {} from "../orchestration/workflow-agent-session-index.js";
|
|
5
5
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
6
6
|
import { isDupKeyError } from "./sql-errors.js";
|
|
7
7
|
export const MAX_RUN_BLOB_BYTES = 4 * 1024 * 1024;
|
|
@@ -24,9 +24,8 @@ export function slimOversizeRun(run, maxBytes) {
|
|
|
24
24
|
let blob = JSON.stringify(step1);
|
|
25
25
|
if (Buffer.byteLength(blob) <= maxBytes)
|
|
26
26
|
return blob;
|
|
27
|
-
const identities = buildParkedRecoveryIdentities(run.agents);
|
|
28
27
|
const shortError = step1.error ? String(step1.error).slice(0, 1000) + MARK : MARK;
|
|
29
|
-
const step2 = { ...step1, phases: [], agents:
|
|
28
|
+
const step2 = { ...step1, phases: [], agents: [], groups: [], error: shortError };
|
|
30
29
|
blob = JSON.stringify(step2);
|
|
31
30
|
if (Buffer.byteLength(blob) <= maxBytes)
|
|
32
31
|
return blob;
|
|
@@ -38,19 +37,6 @@ export function slimOversizeRun(run, maxBytes) {
|
|
|
38
37
|
if (typeof step3.description === "string")
|
|
39
38
|
step3.description = redactHead(step3.description, SLIM_HEADER_MAX);
|
|
40
39
|
blob = JSON.stringify(step3);
|
|
41
|
-
if (Buffer.byteLength(blob) <= maxBytes)
|
|
42
|
-
return blob;
|
|
43
|
-
const bareIdentities = buildParkedRecoveryIdentities(run.agents, 0);
|
|
44
|
-
const step4 = { ...step3, agents: bareIdentities };
|
|
45
|
-
blob = JSON.stringify(step4);
|
|
46
|
-
if (Buffer.byteLength(blob) <= maxBytes)
|
|
47
|
-
return blob;
|
|
48
|
-
const step5 = {
|
|
49
|
-
...step4,
|
|
50
|
-
agents: [],
|
|
51
|
-
...(identities.length > 0 ? { [PARK_IDENTITY_LOSS_KEY]: buildParkIdentityLoss(identities.length) } : {}),
|
|
52
|
-
};
|
|
53
|
-
blob = JSON.stringify(step5);
|
|
54
40
|
return Buffer.byteLength(blob) <= maxBytes ? blob : null;
|
|
55
41
|
}
|
|
56
42
|
export class SqlWorkflowRunStore {
|
package/dist/task-cwd.d.ts
CHANGED
|
@@ -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
|
package/dist/task-settings.d.ts
CHANGED
|
@@ -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
|