import type { ToolResultProvenance, ToolResultStore, ToolResultSlice, ToolResultDeletionReport } from "@sema-agent/core"; import type { Pool as MySqlPool } from "mysql2/promise"; import type { Pool as PgPool, PoolClient as PgPoolClient } from "pg"; import { type SqlDriver } from "./sql-driver.js"; import { type IndexSpec } from "./ensure-index.js"; /** PG schema for the tool_result table — the PG translation of tidb-pool.ts's tool_result DDL * (LONGTEXT→TEXT, DATETIME(3)→TIMESTAMPTZ(3), inline KEY→separate CREATE INDEX). Disjoint from other stores' * tables, so a self-contained ensureSchema is safe (central pg-pool aggregation is done separately). */ export declare const PG_TOOL_RESULT_SCHEMA: string[]; /** S-287:本 store 的索引**声明**(两方言共用一份)。PG 侧由下面的 `ensureSchema` 应用,MySQL 侧由 `tidb-pool.ts` 的中央 `ensureSchema` 应用(那里内联 `KEY` 已在 `CREATE TABLE` 里 ⇒ 新建库探到即零 DDL,存量库缺谁补谁)。加索引以外的 schema 变更仍归运维,见 `plugins/ensure-index.ts` 头注。 */ export declare const TOOL_RESULT_INDEXES: readonly IndexSpec[]; /** Idempotent schema apply for the tool_result table (for the integration test to call). */ export declare function ensureSchema(pool: PgPool | PgPoolClient): Promise; /** * [ref] 升级前置断言 —— **拒启**,不是 warn(codex 复审 [high],已核真)。 * * 病:本仓不发 `ALTER TABLE` 迁移(标准裁定:改列就改 CREATE + 删库重建)。于是一台**没删表**就升上来的 * 部署,`CREATE TABLE IF NOT EXISTS` 对它是空操作,旧表既没有出处两列也只有 `VARCHAR(190)` 的 ref 列。 * 那样跑起来的后果不是「少个功能」:每一次 offload 写都会撞 unknown column 报错,而 core 的 offload * 失败臂会把错误吞成一条内联占位("[offload LOST at write time…]")—— 服务照跑,工具产物全丢,日志里只有 * 一句看不出根因的话。这正是「响亮或 fail-closed,禁静默降级」那条要挡的形态。 * * 判据用**能力探测**而不是版本号/information_schema:发一条恒空的 `WHERE 1=0` 读,列不在就报错。 * 探不通即拒启,错误文案直接给出两条方言的动作(删表重建),不让运维去猜。 */ export declare function assertToolResultProvenanceSchema(query: (sql: string) => Promise<{ rows: Record[]; }>, dialect: "tidb" | "pg"): Promise; /** Dual-dialect durable ToolResultStore. See the file header for the dialect-delta ledger. */ export declare class SqlToolResultStore implements ToolResultStore { protected readonly db: SqlDriver; /** [ref] 车1:托管留存声明。读法与「为什么 SQL 店答 managed 而三方法的实现体在 `retention-store-sql.ts`」 * 逐字见 {@link MANAGED_RETENTION}(本店的 `tool_result` 行由那只聚合店按期清:随会话树删 + 孤儿腿)。 */ readonly retention: import("@sema-agent/core").RetentionDeclaration; constructor(db: SqlDriver); /** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE. */ private q; /** D1(试剂盒揪出,[ref] 语义):ref 是主键,而 `deleteBySession` 只认**记录下来的出处**与单射的 * `tr_~` 前缀(重扫二轮起;旧注写的 `tr__%` 前缀清理已作废,理由见该方法顶注)—— * 不合规 ref 的行既进不来、也就谈不上被 session 删除清掉,只能等 TTL。 * * 🔴 判定**单源**:直调 core 的 `assertSafeToolResultRef`(core 2.8.0 起入公共面)。 * 在此之前这里是那 6 个判别条件的**本地镜像** —— 我在 [ref] 主动交出过这条裂缝:镜像是第二真源, * core 哪天收紧一格(加 Windows 保留名、长度上限……)我方不会跟着动,而**试剂盒会继续绿** * (它测的是「拒不拒」,不是「按同一张表拒」)。core 按请求追加了导出,镜像随之删除,裂缝闭合。 * 抛错文案与 core 逐字相同,所以 wire 与既有钉都不变。 */ private isUnsafeRef; private assertSafeRef; /** * [ref](core 5.26.0)—— 出处的**写面**。语义一律取 core 单源(`assertToolResultProvenanceMatch`), * 本方法只负责把它落到 SQL 上: * · **写一次选举同时定属主** —— 内容与属主是同一条 INSERT,不存在「内容写进去了属主还没跟上」的窗口 * (core 头注允许 file 双对象后端出现这个窗口,单行后端没有,别自造); * · 抢输的那一方(IGNORE / DO NOTHING ⇒ affected=0)回读已存属主再判:同属主(或本次无出处)= * 幂等空转;异属主 = `ToolResultRefConflictError` typed 拒。**绝不静默 keep-first**:ref 是主键, * 静默空转会让第二位写者拿着自己的 ref 读回第一位的字节; * · 无出处的行永久 unowned —— 后续带出处的 put 不回填(没有证据的收养),`ownerOf` 继续答 undefined。 */ put(ref: string, content: string, provenance?: ToolResultProvenance): Promise; /** 一次回读同时回答两件事:**行在不在**(reap/purge 窗口的判别位)与**属主是谁**。两件事必须来自同一 * 条 SELECT —— 分两次问会重新引入它要消灭的那个窗口。 */ private readOwner; /** * [ref] —— 出处的**读面**。未知 ref 与「存了但无属主」两种情况都答 `undefined`:读面对二者一视同仁 * (fail-closed,谁都没被授权),所以这里也不必把它们分开报。 */ ownerOf(ref: string): Promise; get(ref: string, opts?: { offset?: number; limit?: number; }): Promise; /** TTL reap: delete results older than `cutoffMs`. Returns rows removed. * C6: a real DB error must NOT collapse into the same `0` a legitimate "nothing was old enough" returns — * those are different facts (query failed vs. query succeeded on an empty set) and folding them together * makes a stuck/broken reaper indistinguishable from a healthy quiet one. Matches every sibling reaper's * form (roster-store-sql.ts `reapOlderThan`, checkpoint-store-sql.ts `reapExpired`, * workflow-journal-store-sql.ts `reapExpired`): let the error propagate — the periodic-sweep call site * (boot/reapers.ts) already wraps this call in `.catch(() => undefined)` so the reap loop itself never dies. */ reapOlderThan(cutoffMs: number): Promise; /** * E21 (§0.5 session delete) — purge offloaded tool results for one session. Selection is by the row's * **recorded provenance** (`owner_session_id`, the [ref] column `put` writes in the same statement as the * content) and by NOTHING else — **zero ref-derived arms**(codex R1-[high] 收窄:连单射新前缀也不作 * 属主证据,理由在方法体内逐字引 core 顶注)。Returns `{deleted, unattributable}`; why NO prefix * (legacy `_` or injective `~`) is a deletion selector is inline below. * * ⚠️ 旧注两处已作废(重扫二轮更正,留档防复辟):① 「core namespaces every ref as `tr__`」—— * 5.26.0 起是 `~` 分隔的单射四段形,`_` 形只剩存量;② 「there is no `session_id` (or `owner`) column to * guard on」—— [ref] 起**有**(`owner_session_id` / `owner_task_id`,boot 期还有拒启断言把没升级的表挡在 * 外面),所以「按出处选行」不再是「awkward ref-correlated EXISTS」,它就是本方法现在的主选择器。 * * 路由侧的 owner 门照旧在(DELETE 路由先 `ownerOf` 对属主、同一协调器里先跑 owner-guarded 的 run 账本 * 删除),但它不再是唯一防线:LIKE 转义只挡「精心构造的 id 把前缀撑宽」,挡不住「**合法**的邻会话 id * 恰好是本会话 id 的扩展」——那一条现在由出处判定挡。 */ deleteBySession(sessionId: string): Promise; } /** MySQL-protocol (TiDB) binding — historical class name + ctor shape preserved. */ export declare class TiDBToolResultStore extends SqlToolResultStore { constructor(pool: MySqlPool); } /** PostgreSQL binding — historical class name + ctor shape preserved. */ export declare class PgToolResultStore extends SqlToolResultStore { constructor(pool: PgPool); } //# sourceMappingURL=tool-result-store-sql.d.ts.map