import type { Pool as MySqlPool } from "mysql2/promise"; import type { Pool as PgPool } from "pg"; import { type SqlDriver } from "./sql-driver.js"; import { type IndexSpec } from "./ensure-index.js"; /** 表名([ref] 单数纪律;`schema-naming-invariants` 的闭集词表执法)。 */ export declare const LEADER_RUN_TABLE = "leader_run"; /** leader run 的四态 —— 与 `leader/endpoint.ts` 的 `LeaderRun["status"]` 同词表(闭集)。 */ export type LeaderRunStatus = "running" | "completed" | "failed" | "needs_human"; /** 库里一行 leader run 的读出形。`result` 是 `LeaderResult` 的 JSON 原样(店不解释它的内部结构)。 */ export interface LeaderRunRecord { id: string; status: LeaderRunStatus; /** BL-4 属主(null = 匿名/单租)。GET 的 404 门读它。 */ owner: string | null; /** 运维列表面的可读预览(**不是**完整 objective —— 名字即语义,见 DDL 列注)。 */ objectivePreview: string; result?: unknown; error?: string; startedAtMs: number; finishedAtMs?: number; } /** 准入入参。`idemKey` 缺席(null)= 调用方没要求去重 ⇒ 每次都是新行(现行为)。 */ export interface NewLeaderRun { objective: string; owner: string | null; /** 已 scope 过的幂等键的 sha256 hex(见 {@link leaderIdemKey});null = 不去重。 */ idemKey: string | null; } /** 终局补丁 —— `finishRun` 一次写完(状态 + 结果/错误 + 完成时刻)。 */ export interface LeaderRunTerminal { status: Exclude; result?: unknown; error?: string; } /** `objective_preview` 的字符上限,与 DDL 的 VARCHAR(160) 同源(`task_run.objective_preview` 先例同宽)。 */ export declare const LEADER_OBJECTIVE_PREVIEW_CHARS = 160; /** * 幂等键的**落库形** = `sha256(scope \x1f raw)` 的 hex(64 字符,定宽)。 * * 为什么 hash 而不是像 bake 店那样存 scope 过的明文:明文形的列宽(VARCHAR(255))与「principal(≤190) * + 调用方自选的 raw 键(≤255)」的上界对不上 —— 溢出时 PG 报 `value too long`、非严格 MySQL **静默截断**, * 而截断一个去重键的后果是**两个不同的请求折叠成同一次 leader run**(= 一次该跑的 run 被静默吞掉)。 * 定宽 hash 让这条路径按构造不存在;顺带,库里也不再留调用方的原始键字面量。 * (碰撞面:sha256,不在工程风险量级。) */ export declare function leaderIdemKey(raw: string, owner: string | null): string; /** MySQL 协议方言的建表语句(真源;由 `tidb-pool.ts` 展开进中央 SCHEMA_STATEMENTS)。 */ export declare const TIDB_LEADER_RUN_STATEMENTS: readonly string[]; /** PG 方言的建表语句(MySQL 孪生的逐条翻译:JSON→JSONB,逐列 `COLLATE "C"`,内联 KEY→独立 CREATE INDEX)。 */ export declare const PG_LEADER_RUN_SCHEMA: readonly string[]; /** S-287:本 store 的索引**声明**(两方言共用一份)。PG 侧由下面的 `ensurePgLeaderRunSchema` 应用,MySQL 侧由 `tidb-pool.ts` 的中央 `ensureSchema` 应用(那里内联 `KEY` 已在 `CREATE TABLE` 里 ⇒ 新建库探到即零 DDL,存量库缺谁补谁)。加索引以外的 schema 变更仍归运维,见 `plugins/ensure-index.ts` 头注。 */ export declare const LEADER_RUN_INDEXES: readonly IndexSpec[]; /** 幂等的 PG schema 应用(中央 `ensurePgSchema` 组合它;集成套件也直接调)。 */ export declare function ensurePgLeaderRunSchema(q: (text: string, params?: unknown[]) => Promise): Promise; /** 清算腿写进 `error` 列的 WHY(消费方读到的就是这句;不是一个空的 failed)。 */ export declare const STALE_SWEEP_REASON: string; /** 双方言 leader-run 店。方言差异台账见文件头注。 */ export declare class SqlLeaderRunStore { protected readonly db: SqlDriver; constructor(db: SqlDriver); /** 挑本方言的 SQL 文本。两条语句**都写在调用点**(A12 判据:读者一眼看到两份)。 */ private q; /** * 准入一次 leader run(状态 `running`)。提交幂等照 `createBake`/`createRun` 的**原子赢或观察**: * `idem_key` 是 UNIQUE,重投的那一方 INSERT 撞 dup-key ⇒ 回读**既有行**返回 `{created:false}`, * 调用方据此**不再**起第二条后台腿。`idemKey=null` 恒插入(没要求去重)。 * * 🔴 为什么不是「先 SELECT 再 INSERT」:两个副本的 check-then-act 会双双看到「没有」然后双双插入 —— * 而这条路径的第二次插入意味着**第二次真 git push**。UNIQUE 是唯一能在两个进程之间成立的判据。 */ createRun(input: NewLeaderRun): Promise<{ created: boolean; run: LeaderRunRecord; }>; /** 按 id 读一行(跨副本可见性的全部依据)。不存在 ⇒ undefined。 */ getRun(id: string): Promise; /** 按幂等键读回既有行(dup-key 观察臂;也给集成套件直查)。 */ getByIdem(idemKey: string): Promise; /** * 陈旧 `running` 清算(codex 交叉复审 R1-high②,验真后修)。 * * 病:准入(INSERT)与驱动(后台腿)是**两步**,而驱动腿只活在受理它的那个进程里。副本在这两步之间 * 挂掉、或在一条 24h 的 run 中途挂掉,行就永远停在 `running`:跨副本读面永远显示「在跑」,而没有任何 * 东西在跑;更坏的是那把幂等键被永久绑在这条无人驱动的行上,重投拿回的还是它。 * * 处置 = **有界地说实话**,不是自动接管:把超期的 `running` 迁成 `failed` 并写清 WHY。 * 🔴 刻意**不做**跨副本接管(claim/lease/queued 状态机):接管一条产物是「真 git push」的 24h run * 是一个独立的设计决定(要持久化完整可恢复请求、要 fencing、要想清楚两个副本同时以为自己是属主时 * 谁去 push),不是一次清算腿能顺手带过的。本腿把「永远 running」这个**静默**面消灭掉,把接管留给 * 一件具名的后续设计;当前语义写在这里,不藏着。 * * 幂等键的后果同批写明:键**永久绑定**它当初准入的那条 run(与 `/v1/runs` 的 taskId 幂等同语义)。 * 一条被清算成 `failed` 的 run,用**同一个键**重投拿回的仍是那条 failed —— 调用方要重跑就换一个新键。 * * @param staleMs 超过这个时长仍是 `running` 就算陈旧(部署旋钮 `LEADER_RUN_STALE_MS`)。 * @param now 注入时钟(测试要确定性)。 * @returns 本次迁走的行数。 */ reapStaleRunning(staleMs: number, now: number): Promise; /** * TEST-ONLY:把一行的 `started_at_ms` 推到过去,好让 {@link reapStaleRunning} 的判据在一次测试里成立。 * 生产零调用(`started_at_ms` 只在准入时写一次)——具名 `ForTest` 是为了让这件事在 grep 里一眼可见, * 而不是长成一个看起来像正经写口的方法(本仓 `setStatus` 那条欠账的教训)。 */ backdateStartedAtForTest(id: string, startedAtMs: number): Promise; /** * 落终局。**只从 `running` 迁**(`WHERE status = 'running'`):终局是一次性的,一条已经结算过的 run * 不该被后来的写者改写。回执 = 是否真的迁了,调用方据此决定要不要出声(静默丢一次终局写 = 用户永远 * 看到 `running`)。 */ finishRun(id: string, patch: LeaderRunTerminal): Promise; } /** MySQL-协议孪生(TiDB / MySQL / MariaDB 同一只 mysql2 池)。 */ export declare class TiDBLeaderRunStore extends SqlLeaderRunStore { constructor(pool: MySqlPool); } /** PostgreSQL 孪生。 */ export declare class PgLeaderRunStore extends SqlLeaderRunStore { constructor(pool: PgPool); } //# sourceMappingURL=leader-run-store-sql.d.ts.map