/** * [ref] 件6(core 5.46.0 提货批,[ref] §2.3-2)—— **external-origin 标记不可变律**的 SQL 孪生实现。 * * ## 这条法是什么、为什么落在本仓 * * [ref] slice 1 给「已暴露会话」的普通记忆写打上引擎铸的 `frontmatter.origin` 标记(taint + * 成因闭集 + 铸造时刻),取代 [ref] 那种「整条隔离」的全禁。标记的全部价值来自**不可洗白**: * 一旦某个 id 的**已提交**状态带了标记,之后**任何**写法(update / 裸 add / `guard:"absent"` add / * 同批 delete+re-add)都必须把它 DEEP-EQUAL 地带下去,否则拒绝。合法出口只有一个 —— **已提交的** * 墓碑(上一批真的 delete 掉了),那之后这个 id 开始一段未标记的新生命。 * * core 5.46.0 把这条法写进了**记忆后端契约试剂盒** * (`dist/core/memory-engine/memory-backend-contract.js`,新增一条必跑条目),而该契约**压在本仓身上**: * core 只养 File backend,Pg / TiDB 两方言归 server。提货当天真双库实跑的红读数([ref] 教训的 * 直接兑现,不是自我声明「本批无 SQL 面」): * `[engine=tidb] contract case "[ref]: origin marker round-trips; …": * an origin-stripping update must not apply — 1 !== 0` * ⇒ 两只 SQL 孪生此前**没有**这条法:一条被标记为外部来源的记忆条目,可以被后续一次普通 update * 悄悄抹掉标记(洗白),而 File backend 会拒。这是安全轴上的真缺口,不是「新契约还没接」。 * * ## 为什么是一个共享模块而不是两处各写一遍 * * 本仓的成文教训(`key-resolver.ts` 顶注「链只有一条,重复实现就会长出半条链的偏差」; * `memory-key-guards.ts` 同族先例):两支方言各写一份判据,迟早在某一支上漏掉一个 op 拼写 —— * 而漏掉的那一支**没有任何报错面**,表现只是「这台机器上的标记可以被洗掉」。判据函数与批次基线 * 记录器都在这里,两支后端只负责在自己的 I/O 点上调用。 * * ## 判据的两半(缺一不可) * * ① **判据本身**({@link originWhitewashRefusal}):`committedOriginOf` / `originEquals` 都用 core 的 * 导出实现,**不重写** —— 它们要处理 `frontmatter.origin` 与「保留在 `extra` 里的 origin 形字节块」 * 两种承载形(legacy / 外来),自己写一份近似的等于给洗白留一条旁路。 * ② **批次基线**({@link rememberBatchOrigin}):两支 SQL 后端逐条 patch 直打真库(没有 File backend * 那种「先规划整批再落盘」的阶段),所以同批 `delete` 之后再 `add` 时,库里已经没有那一行了 —— * 只看当下的库会把它误判成合法新生命。基线 map 按**本批第一次触碰**记下该 id 的已提交 origin, * 之后同批的任何 op 都按那一份判。⇒「同批 delete+re-add 拒 / 已提交墓碑后 add 放」这条分界 * (core 契约逐字要求的那条)才成立。 */ import { type MemoryEntryFrontmatter, type MemoryEntryHeader } from "@sema-agent/core"; /** * [ref] 增量(sema-comms [ref] 勘误 + core 仓 `1a5ebaa1`,conformance 30→31)—— **歧义 origin 表示** * 的拒绝门,与不可变律同批、同一个写路径上。 * * ## 病 * * 同一条目**同时**携带 typed `frontmatter.origin` 与一个**冲突的** origin 形 `extra` 块(或多个彼此 * 冲突的 extra 块)时:不可变律的比较器走 `committedOriginOf`,而它在 typed 在场时优先读 typed —— * 于是那枚 typed 满足「标记原样带下去」,冲突的块却照样进了存储。任何一次**重解析**(File 的投影 * 往返 / sync 对拍 / bundle 校验)都可能答出**另一枚**标记 ⇒ 成员改写的载具。不可变律本身没漏, * 漏的是「根本不该收下这种表示」这一步。 * * ## 钥匙在 DISAGREEMENT,不在载体数 * * 载体彼此**一致**的多载形(typed 旁边一个逐成员相等的 extra 形)是合法的 legacy 承载,必须照收 —— * 那正是序列化闭合(同一条记录在 File 投影里往返一圈就长这样)。「见到 extra 就拒」会把正常的 * legacy 条目全部打死。 * * ## 判据禁手抄 * * 谓词一律用 core 导出的 {@link ambiguousOriginRepresentation}:它要解 `extra` 里的 origin 形块 * (跨块字段组合、解析期块重排都算歧义),自己写一份近似的等于给洗白开口子 —— 这条在 [ref] 就写死过。 * core 5.47.0 起该谓词进公开导出面([ref] 请托兑现),本仓的「撤除条件钉」也因此在 5.47 提货当天 * 准时翻红,本批就是那条钉指定的兑现动作。 * * ## 文案与 File 逐字同 * * 拒绝文案照抄 core `file-backend.ts` 的 `planOne`(亲读 `1a5ebaa1` 前后两版确认未变),因为 * 契约按 `/representation conflict|malformed patch refused/` 匹配,而**跨后端同一输入必须同一答复** * —— 消费方按 reason 分类处置,两支方言各写各的文案就等于让同一件事在不同部署上分叉。 * * ## 判位 * * 与 File 同precedence:**表示合法性先于状态比较** —— 排在 id/键形守卫之后、add/update 分支 * (guard 算术、跨 scope 拒、白洗判据、rev CAS)之前,且在**任何 I/O 之前**(零写拒)。 */ export declare function ambiguousOriginRefusal(fm: MemoryEntryFrontmatter): string | undefined; /** * [ref](core 5.47/5.48 契约冻结面,[ref] §5.2)—— **头面 exposure 事实**的同一份归一化。 * * 契约逐字:`listHeaders` 与 `search` 两个头面都要携带 `exposure`,且**同一条归一化** * (`committedOriginOf`:typed `frontmatter.origin` 与 legacy 的 `extra` origin 形字节块两种承载, * 一次判、两面同答)。这是**数据事实**不是模式:后端恒报库里存着什么,读侧怎么处置(不透明句柄/ * 横幅/两带序)归引擎层的 provenance 模式。缺席 ⇔ 没标记(不等于「证明干净」)。 * * 为什么与不可变律同住一个模块:两者读的是**同一个** committed origin。判据(白洗拒)与事实 * (exposure 携带)分家写,迟早在某一支方言上一边认得 extra 形、另一边只认 typed 形 —— 那时 * 「洗不掉的标记」在检索面**看不见**,而两支后端谁都不会报错。 */ export declare function exposureCarriage(fm: MemoryEntryFrontmatter): { exposure?: "external"; }; /** * 两带序的带号:未标记 = 0(**先**),已标记 = 1。 * * 契约的载荷理由(core r1-9,逐字转述):两带序必须落在**截断之前** —— 一页 limit 全是带标条目时, * 排在 limit+1 位的干净条目会被饿死,而工具层**无法**从一页已截断的结果里把它捞回来。 * `exposureBands` 缺席/false ⇒ 单带序,与 pre-336 逐字节同(`provenance:"off"` 的读面)。 */ export declare function exposureBandOf(header: { exposure?: "external"; }): 0 | 1; /** core `/^\s*origin\s*:/`(`extraOriginCarriers` 的开块判据)的 SQL 孪生。 */ export declare const ORIGIN_CARRIER_LINE_SQL_REGEX = "^[\\u0009\\u000a\\u000b\\u000c\\u000d\\u0020\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]*origin[\\u0009\\u000a\\u000b\\u000c\\u000d\\u0020\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]*:"; /** * 「这一行带 committed origin 标记」的 SQL 布尔式(两种承载形同判,与 {@link exposureCarriage} 同源)。 * `column` = 该表 frontmatter 列的表达式(调用方自己保证它是列名/限定名,**不接受外来串**)。 */ export declare function originMarkedSqlPredicate(column: string): string; /** * 本批次的「已提交 frontmatter」基线:id → 本批**第一次**触碰它时库里那一份(无行 ⇒ `undefined`)。 * * [ref](core 5.55.0 conformance c35-c40,[ref])从「只记 origin」升为记**整份** committed * frontmatter:origin 不可变律与 distilled 不可变律读的是同一份基线,将来第三个不可变 frontmatter * 字段进契约时零基线改动 —— 两只平行 Map 就是 F1a(姊妹腿)的温床,一只忘了在 delete 腿记账, * 那一律的「同批 delete+re-add 拒」就静默失效。 */ export type BatchCommittedBaseline = Map; /** 新建一只批次基线(每次 `applyPatches` 一只;跨批次**不得**复用 —— 已提交的墓碑正是靠「换批清零」放行)。 */ export declare function createBatchCommittedBaseline(): BatchCommittedBaseline; /** * 记下(或取回)某 id 在**本批开始时**的已提交 frontmatter。 * * `committedFm` = 这一次 I/O 真的从库里读到的已提交 frontmatter(没有行就传 `undefined`)。 * **首次触碰写入,之后只读** —— 同批里后面的 op 看到的必须还是批开始那一份,否则本批自己的写会把 * 基线推走(delete 之后 re-add 就又变成「库里没有 ⇒ 未标记」)。 */ export declare function rememberBatchCommitted(baseline: BatchCommittedBaseline, id: string, committedFm: MemoryEntryFrontmatter | undefined): MemoryEntryFrontmatter | undefined; /** * 未知 op 拼写的**前置**拒绝(codex R1-[high],验真后采纳;**既有**缺口,不是本批引入)。 * * 病:两支 SQL 后端的 `applyOne` 形状是「`if (op === "add") {…}` → 落到 update/delete 段」,而段内 * 的两道白洗守卫(repo_file provenance / 本模块的 origin 不可变律)都写成 `if (patch.op === "update")`。 * ⇒ 一个**词表外**的 op(`"upsert"` / 打错的字 / 将来 core 加的新词)会:跳过两道守卫 → 走到 update * 的 SQL → 把行改掉 → 在 `report.applied` 里报成一次 update。TypeScript 的联合型是**编译期**的, * 拦不住任何运行期调用方(sync 腿、嵌入方自建的调用点、future core)。 * * 今日可达性(如实):本仓唯一的调用点 `src/memory-sync.ts` 自己铸 op(基线有 ⇒ update、无 ⇒ add), * 词表外的值到不了。所以这条是**加固**而不是在修一次已发生的事故 —— 但它落在安全轴(白洗守卫的 * 有效性完全依赖「op 只有三种」这个未被检查的前提),按 CLAUDE.md [ref]「闭集 + 不可避免的运行期 * miss 臂必须说出来」补一道显式拒绝:未知 op **零写**,报 `malformed patch refused`(与本仓既有的 * id-mismatch 拒绝同族文案,消费方按同一条正则分类)。 */ export declare function unknownPatchOpRefusal(op: string): string | undefined; export declare function originWhitewashRefusal(op: "add" | "update", committedFm: MemoryEntryFrontmatter | undefined, next: MemoryEntryFrontmatter): string | undefined; /** * [ref](core 5.55.0 conformance c36/c37,[ref])—— **distilled 血统不可变律**的判据,与 origin * 律同族同基线同判位:一旦某 id 的已提交状态带 `frontmatter.distilled` 块(consolidation 蒸馏产物的 * 血统:planId/at/carrierRev/inputs),任何 update / 裸 add / guard add / 同批 delete+re-add 都必须把 * 它 DEEP-EQUAL 地带下去,否则拒 —— 合法出口只有已提交的墓碑(c36 逐字)。判序:**先于 rev CAS** * (c37:骑着 stale baseRev 的 strip 也必须答 malformed refusal,不是 rev mismatch)。 * 判据用 core 导出的 {@link committedDistilledOf} / {@link distilledEquals},不手抄(F1b);文案与 * core File backend 两处逐字同形,契约按 `/malformed patch refused/` 匹配。 */ export declare function distilledWhitewashRefusal(op: "add" | "update", committedFm: MemoryEntryFrontmatter | undefined, next: MemoryEntryFrontmatter): string | undefined; /** * [ref](conformance c38)—— **distilled 事实的头面携带**,与 {@link exposureCarriage} 同住同因: * `listHeaders` 与 `search` 两个头面都携带 `{ carrierRev, supersedes }` 投影(supersedes = inputs 中 * `superseded === true` 的行,形状与 core File backend 的 header 投影逐字同);无块条目两面都不携带。 * 蒸馏产物的「取代了哪些输入行」必须在检索面可见,否则被取代的旧行与蒸馏产物在读侧无从判序。 */ export declare function distilledCarriage(fm: MemoryEntryFrontmatter): Pick; //# sourceMappingURL=memory-origin-law.d.ts.map