/** * [ref] seam②([ref] §2.1b,core 7.0.2 [ref] 件2;黑板 [ref]/[ref]/[ref] 键形对齐)—— * **会话 capture opt-out 记录的 SQL 载体** `session_capture_optout`:core `SessionCaptureRecordStore` * 的 Promise 形实现。SINGLE-FILE DUAL-DIALECT([ref] A12 形,照 memory-optout-grant-store-sql.ts: * 一个 `SqlSessionCaptureRecordStore` 接 `SqlDriver`,两个薄 ctor 子类落方言绑定)。 * * ── 为什么要这只店(core layout.d.ts §2.1b 原文方向)──────────────────────────────────────────── * core 缺省载体 = 控制面文件三腿(`fileSessionCaptureRecordStore`)。无状态多副本部署上文件形对 resume * 副本不可见 ——「record not found」恰好朝**继续采集**的方向坏(server F1 点名的形)。SQL 载体让一条 * 一次性隐私记录跨副本存活;供给它同时是 §2.1b 的能力信号:远端车道的 `capture:"off"` 声明从 * 409 `config.memory_capture_unsupported` 稳态转为真持久([ref] 前的稳态自此解除)。 * * ── 三腿逐腿失败法(core 契约,dist layout.d.ts + 文件三腿实现逐字亲读;引擎消费点 engine.js 亲读)── * · `mark` **create-only / first-record-wins,恒不 reject**:dup-key ⇒ `"existed"`(幂等重宣告, * 第一条记录赢 —— 恒不 UPDATE);其余任何失败(断连/超时/超宽键)⇒ `"unpersisted"` * (383 §2.8:只存在于进程内的 opt-out 会被第一次 resume 打破,「没落住」是具名拒绝,调用方必须 * 拒跑 —— 永不静默降级)。引擎侧对 rejection 也折 `"unpersisted"`(engine.js mark 的 `.then(settle, * () => "unpersisted")`),本店仍自行 catch:契约写在店上,不赖调用方兜底。 * · `read` **fail-closed**:行在而解不出 ⇒ 返回**合成记录**(句式与 core 文件腿逐字同形 —— * 损坏不许把一条隐私请求洗回采集);无行 ⇒ `undefined`(缺席才是「采集开」);存在性本身不可立 * (SQL 故障)⇒ **reject**(引擎 catch 折 INDETERMINATE fault 轴:故障永不谎报成「用户请求过」, * 也永不谎报成「没有记录」)。 * · `list` 枚举失败 ⇒ **reject**(读不出的名册不是空名册 —— consolidation 资格臂据此 fail-closed 拒整理, * engine.consolidationEligibility 亲读);单行解不出 ⇒ 合成记录(绝不静默漏行:漏一行 = 蒸馏器放大 * 一条作者要求扣留的内容)。 * * ── 键形法:**编码规范形**(codex R2-[high] 验真后改形;grant 店的「形状拒」在这里是错的)──────── * 提交面把 sessionId 定为**任意不透明串**(server.ts submit 门:「LENGTH-ONLY — NOT a uuidv7 shape * check」,≤64 字符,run-local `--session` / 匿名分享 id 等都合法)——带空白/控制字符的 id 是**在契约内** * 的真会话,店若按字节形状拒它,后果=该会话在 SQL 部署上「opt-out 永远落不住(unpersisted)+ 采集态 * 永远 indeterminate(commits 全抑制)」,是部署契约不兼容,不是输入加固。所以键列**不存生字节**,存 * `encodeURIComponent(sessionId)` 规范形 —— 与 **core 文件三腿给文件名做的编码逐字同源**(layout.js * `captureOptOutPath` 亲读),两载体一个键法: * · 编码字母表(`A-Za-z0-9-_.!~*'()%`)天然**无空白无控制字符** ⇒ TiDB utf8mb4_bin 的 PAD SPACE * 尾空格折行隐患在字母表层面消灭,任意合法 id 字节精确、互不串行; * · 64 字符 id 编码上界 192 ⇒ 列宽 255 恒容;编码后超 255 的 id(>64 字符,提交面本就拒)在 `mark` * 上答 `"unpersisted"`、在 `read` 上答 `undefined` —— 后者是**可证明的缺席**(本店写腿从不落这种键, * 列宽也物理放不下),不是 indeterminate; * · `list` 逐行做**规范形往返校验**(decode 失败 / re-encode ≠ 原字节 ⇒ 整名册 **reject**):带外/损坏 * 写入的非规范键(如生尾空格 "s1 ")若被静默放行,roster 键与 lineage 贡献者键精确比对不上 ⇒ * consolidation 资格臂**漏排除** opt-out 会话 = 蒸馏放大被扣留内容(codex R2-[medium] 验真); * reject 让资格臂 fail-closed 拒整理(`memory.consolidation_governance_unreadable`),方向正确。 * * ── 列 ────────────────────────────────────────────────────────────────────────────────────────── * session_id VARCHAR(255) PK 会话 id 的 encodeURIComponent 规范形(见上;宽=64 字符 id 的编码上界) * at_ms BIGINT NOT NULL 记录时刻(core `SessionCaptureOptOutRecord.at`,恒引擎钟) * reason TEXT NOT NULL 自由文本(verb 的 reason ≤500 由 wire 验;声明腿/SDK 直连宿主不设上限 * —— TEXT 而不是 VARCHAR(500):列溢出会把一条隐私请求变成 "unpersisted", * 为省字节引入那个失败向不值) * 刻意**无** updated_* 审计列:记录是一次性单向的(first-record-wins,恒不改写),`at_ms` 就是全部时间事实。 * * ── 方言差异(显式写在调用点,A12 doctrine)────────────────────────────────────────────────────── * · `?` vs `$n` 占位符; * · create-only:两方言都走**裸 INSERT + dup-key 判**(`sql-errors.ts` 的 `isDupKeyError` 单一登记谓词) * 而不是 `INSERT IGNORE` / `ON CONFLICT DO NOTHING` —— IGNORE 在 MySQL 协议下连带吞掉溢出/截断类 * 错误(把「没落住」洗成「落了」),DO NOTHING 则要靠 affected==0 反推 dup(与「没插但也没错」不可 * 判别)。裸 INSERT 让三种结局(插入成功/键已在/真失败)各有各的信号,恰是 mark 三态要的判别度。 * · BIGINT 读回:TiDB number,PG string —— `readAtMs` 两形都认,其余按行级损坏折合成记录(见 read 法)。 */ import type { Pool as MySqlPool } from "mysql2/promise"; import type { Pool as PgPool } from "pg"; import type { SessionCaptureOptOutMarkOutcome, SessionCaptureOptOutRecord, SessionCaptureRecordStore } from "@sema-agent/core"; import type { PgQueryFn } from "./pg-query.js"; import { type SqlDriver, type SqlDialect } from "./sql-driver.js"; export declare const SESSION_CAPTURE_OPTOUT_TABLE = "session_capture_optout"; /** 键列宽(64 字符 id 的 encodeURIComponent 上界 192 ≤ 255;DDL 与守卫同源此常量)。 */ export declare const SESSION_CAPTURE_KEY_WIDTH = 255; /** 键的规范形 = core 文件三腿给文件名做的同一只编码(layout.js `captureOptOutPath` 亲读)。 */ export declare function encodeSessionCaptureKey(sessionId: string): string; /** * `list` 的规范形往返校验(codex R2-[medium]):表里的键必须恰是某个 id 的 `encodeURIComponent` 输出。 * decode 失败 ⇒ **抛**具名错(原错随 `cause` 上交,不折默认值 —— [ref] 门② SHAPE C:catch 不许只 return * 默认值);re-encode ≠ 原字节(如带外写入的生尾空格键、小写十六进制)⇒ 返回 `undefined`;两形调用方都 * **拒整本名册**(fail-closed:一个比对不上 lineage 贡献者的键 = 资格臂漏排除 = 蒸馏放大被扣留内容)。 */ export declare function decodeCanonicalSessionCaptureKey(raw: string): string | undefined; /** 双方言 SessionCaptureRecordStore(Promise 形三腿)。见文件头的逐腿失败法。 */ export declare class SqlSessionCaptureRecordStore implements SessionCaptureRecordStore { private readonly driver; private readonly dialect; constructor(driver: SqlDriver, dialect: SqlDialect); /** create-only / first-record-wins;**恒不 reject**(任何失败 ⇒ `"unpersisted"`,调用方必须拒跑)。 */ mark(sessionId: string, record: SessionCaptureOptOutRecord): Promise; /** fail-closed 读:无行 ⇒ undefined;行在解不出 ⇒ 合成记录;SQL 故障 ⇒ reject(引擎折 fault 轴)。 */ read(sessionId: string): Promise; /** 全名册。枚举失败 ⇒ reject(读不出的名册不是空名册);值列损坏 ⇒ 合成记录,绝不静默漏行; * **键**非规范形 ⇒ reject 整本(codex R2-[medium]:一个比对不上 lineage 贡献者的键若被静默放行, * 资格臂就漏排除该会话 = 蒸馏放大被扣留内容;拒整本让 consolidation fail-closed 拒整理)。 */ list(): Promise>; } export declare class TiDBSessionCaptureRecordStore extends SqlSessionCaptureRecordStore { constructor(pool: MySqlPool); } export declare class PgSessionCaptureRecordStore extends SqlSessionCaptureRecordStore { constructor(pool: PgPool); } export declare const TIDB_SESSION_CAPTURE_OPTOUT_STATEMENTS: readonly string[]; export declare function ensurePgSessionCaptureOptOutSchema(q: PgQueryFn): Promise; //# sourceMappingURL=session-capture-record-store-sql.d.ts.map