框架: 升格抽取改为可恢复的两阶段短事务

This commit is contained in:
zizi 2026-07-23 10:40:07 +08:00
parent f1e1de581a
commit c54d188887
4 changed files with 2214 additions and 1401 deletions

View File

@ -58,11 +58,13 @@ disable-model-invocation: true
**窗行陷阱(放量首日实测)**:`--window` 参数变化后重切,旧窗行会按 from_order 占位,新的大窗被「已有大纲跳过」→ 中间章域永远漏出卡(验收期 1–3 章小窗占住 from_order=1,放量 1–34 章大窗被跳过)。**换窗参数重切前必须先删该书全部窗行**(窗行是可再生中间产物;卡挂「窗起」,cards 重出时按窗软删重出)。
**作品面升格执行器 `scripts/parse_upgrade.py`(与上面范式拆书管线并行的另一条线,命令 `windows`/`run`/`status`)**:把参考书正文按窗抽成「会随剧情长大的实体卡」(升格卡,`source_type=upgrade_book`),设计见 `docs/2026-07-16-升格卡改造设计.md`。里程碑除真实章号外,模型输出必须临时携带所标章节正文短引;系统机械核验后删除证据,错章、缺证据或改写证据均拒收入库并留审计。模型给出的顶层出场章也必须由实体规范名或合法别名在对应章节正文中的实际出现机械证明;合法别名集合同时取卡内 payload 与独立 alias 表,单字规范名禁作章证据,规范名和别名均禁止通用称谓/关系称呼(如队长、舰长)作证据,常规二至四字专名保持精确子串命中。未命中章不得参与立卡、登场兜底或既有卡追加,无实证章时不补登场里程碑;机械登场兜底只允许用 debut 章正文存在性与实体名称/型构造中性台阶,禁止读取跨章摘要。既有卡每窗新增出场章必须审计完整旧值,redo/undo 精确恢复后再按新正文重算。任何送入模型的既有卡 payload 必须同步绑定读取时 revision;写事务锁行后必须仍为 `pending + upgrade_book` 且 revision 一致,否则整窗失败/重试,禁止以旧模型结果覆盖外部改版。关系 prompt 的每个参与角色也必须先读取 expected revision、再锁定并复验 status/source/revision,只有锁后 payload 可送模型,新建和更新关系都只能引用通过该门禁的角色。显式 redo **仅允许当前 active 末窗**:执行前机械校验窗口号连续、章域合法且首尾相接,并校验目标窗逐章正文齐全且非空;任一失败必须在快照、undo 与任何写入前非零退出。历史窗修正必须全书前滚重建;后缀级联重算属于 P1,当前不支持。合法末窗 redo 仍须在撤销事务中清除所有 active `upgrade_book` 卡的本窗章域,保留窗外章并为历史无审计数据补旧值审计;redo 前建立完整恢复点(卡 payload/status/revision/deleted、别名、presence、水位、审计与窗状态),首次失败重新清理再试,最终失败完整恢复且重试初建卡软删。`--max-calls` 不得截断已经开始的即时重试,非显式 redo 不做该全局清理。`run` 默认**不发嵌入**;加 `--semantic-dedup` 开语义判重(治改名/跨型漏并)时,每窗按**读→算→写三段式短连接**跑——观察/近邻召回/M3 终判都在**无长连接**段发 LLM 与嵌入 HTTP(不再持窗级连接跨调用存活),写段只查预判结果落库;实体卡与关系卡的新建/更新都必须同步 state、逐字段审计并进入 touched;并**边抽边嵌**:本窗新建/更新卡在窗事务提交后增量嵌入落库(软删旧向量+upsert 新行),**后窗即可语义召回前窗刚长成的卡**,不再依赖"同书须预先全量 embed"。普通嵌段服务失败只告警;确定性 owner 冲突先持久化 durable `compensation-failed` marker,再由普通窗按 touched fence、显式 redo 按整书六域全域 fence 补偿,任一外部漂移都禁止覆盖。marker 窗下次 run 禁止自动 undo。
**作品面升格执行器 `scripts/parse_upgrade.py`(命令 `windows`/`run`/`status`)**:正文按窗抽取 `upgrade_book` 实体卡。每窗采用“两阶段短事务”:模型/嵌入调用期间不持有业务连接;实体写入和关系写入各自提交 `processing` marker;最终嵌入必须完整成功,才与 `done` 在同一短事务提交。窗口输入摘要绑定作品标题、章/块 ID、标题、状态、revision、类型、正文、active schema 合同和当前脚本 SHA。启动时会恢复可精确撤销的 `processing`;本次窗口第二次失败立即以非零退出并停止当前作品,保留 `retryable-clean` 断点,人工再次运行才继续。
`recover-legacy-failed --preview/--execute` 是 fence 引入前 failed 窗的唯一恢复入口:preview 只读输出窗口、六类本窗产物计数、processing 数和确认 SHA;execute 需确认旧进程已结束、同书锁内重算并 exact 匹配,且无 processing、**阻断产物计数为 0**,才保持 `failed` 并标为 `retryable-clean`。计数只豁免能机械证明来源的全书 reset 墓碑:draft 必须同时 `deleted=true`、`updater='upgrade-reset'`、`status='pending'`;embedding 还必须 `entity_id IS NULL` 且自身 `deleted=true`;alias 必须 `deleted=true` 且 `updater='upgrade-reset'`。presence 没有 reset 来源标记,任何本窗行(包括软删墓碑)都阻断;其他软删、活跃行、已有 entity owner,以及任何 card_state/audit 行也都阻断。完整 `stateSha` 仍覆盖所有 deleted 行,二者不能混淆。不调用模型/嵌入。`--redo-window` 已禁用,历史修正走人工 backup/reset/rebuild。
**登场中性台阶名称边界**:名称只能取 debut 章正文唯一实际命中的合法规范名或别名;若只命中旧别名就用旧别名,多个合法名称同时命中、仅命中通用称谓或无法唯一确定时退化为“人物登场/物件登场”等仅类型描述,禁止泄漏未来才形成的规范名。`new_card` 规范化名称后必须把实际 canonical 与合法 aliases 立即登记到本窗判重索引,不能继续使用模型原始括号名。
**嵌入唯一键冲突失败关闭**:前述“嵌段失败只告警”仅指普通网络或服务异常;owner 预检必须在 `embed_texts` 前覆盖全部 todo hash。写连接第一条 SQL 必须按固定顺序以 `ROW EXCLUSIVE` 锁 `muse_knowledge_draft, example_knowledge_embedding`;随后按 did 锁 draft 行,机械确认 tenant、deleted=false、status=pending,并以当前 payload+MODEL 重算 hash 与 ready hash 一致,再按 did 锁全部活向量、按 hash 以 `FOR UPDATE` 二次校验 owner。若 HTTP 期间 payload 漂移,`(tenant_id, content_hash, model)` 唯一键被两个活跃 draft 的当前 payload 同时声明,唯一行 `entity_id` 非空,或同 draft 任一旧活向量已有 `entity_id`,必须在任何向量写入前硬停并令命令非零;禁止迁移 entity owner、清空 `entity_id`、软删实体向量或打印窗口完成。硬停后先以窗口 `done/null/upgrade` CAS 独立提交 durable `compensation-failed` marker;marker 失败不得继续补偿。普通窗随后锁定并 exact 复验 touched draft 的 status/source/revision/payload hash,通过后才允许同事务 `undo→最终 failed`;显式 redo 则按固定顺序阻写 draft/alias/presence/card_state/audit/window 六域,exact 复验 redo postcommit 全域快照(draft 含 payload/status/revision/updater/deleted),通过后才恢复 pre-redo 全域与原窗状态。任一外部确认、删除、改版或辅助域漂移都禁止 undo/restore,保留 marker;marker 窗下次 run 必须非零阻断,禁止自动 undo。仅当唯一行没有 entity owner 且命中旧软删 draft 时,才把行迁到当前 draft并刷新当前嵌入语义列;冲突更新及旧 hash 软删条件都必须保留 `entity_id IS NULL` 防并发竞态。
**嵌入唯一键冲突失败关闭**:owner 预检必须覆盖全部 todo hash;写入前锁 draft、全部活向量和 hash owner,复验 `pending + upgrade_book`、revision、payload hash 与 `entity_id` 归属。普通嵌入异常、owner 冲突或 payload 漂移都令窗口失败,不打印完成;最终向量写入和 `done` 必须同事务。任何外部确认、删除、改版或辅助域漂移都禁止补偿覆盖。
**同书命令互斥**:`parse_upgrade.py run/windows`、`reset_upgrade_work.py` 的预览/执行,以及 `backup_upgrade_work.py backup/rehearse/restore` 均须先取得 `scripts/upgrade_work_lock.py` 的同租户同作品 PostgreSQL session advisory lock;失败必须在任何业务 SQL、文件 verify/写入、嵌入或 LLM 调用前非零退出。锁由独立 autocommit 连接持有到命令结束,该连接只执行加锁/解锁 SQL;`status` 只读且不取锁。所有调用方必须导入同一个 `upgrade_work_lock(...)` context manager,禁止另造不兼容锁键。

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -244,6 +244,39 @@ flowchart TB
## 九、风险与验证办法
### 9.1 批量抽取的连接与恢复合同(2026-07-22 补充)
升格抽取不得在模型或嵌入调用期间持有业务数据库连接。一次窗口只保留一个可恢复的中间提交点,避免把每个更新批次都变成独立恢复协议:
1. **无连接计算**:读取正文、既有卡和 revision 后立即关闭连接;完成观察、证据修复、语义判重和全部实体更新输出。此阶段失败时没有知识写入,只把窗口留成干净失败断点。
2. **实体短写事务**:先复验模型输入快照,再复验模型所见 revision,原子写入新卡、别名、留档和实体更新;把窗口置为 `processing`,并在同一事务记录本书受控域的完整状态摘要。进程此后崩溃,续跑必须先复验摘要,再精确撤销本窗。
3. **无连接关系计算**:实体阶段提交后重新读取人物与关系快照,关闭连接,再生成或重整关系。卡片已更新后的状态仍能进入关系判断,但模型调用期间没有长事务。
4. **关系短写事务**:复验 `processing` 摘要、输入快照、人物 revision 和关系集合,写关系与剩余出场章;窗口仍保持 `processing`,原子刷新状态摘要。
5. **严格嵌入与完成事务**:在无业务数据库连接状态下计算本窗 touched 卡向量,再用短事务复验摘要和向量 owner 后落库;只有嵌入成功且摘要再次一致,窗口才置 `done`。普通嵌入失败也按窗口失败处理,不允许后窗在缺向量状态下继续语义判重。
```mermaid
flowchart LR
R[短读: 正文/卡/revision] --> C1[无连接: 观察/证据/判重/实体更新]
C1 --> W1[短写: 实体阶段<br/>processing + 完整摘要]
W1 --> R2[短读: 更新后人物/关系]
R2 --> C2[无连接: 关系生成/重整]
C2 --> W2[短写: 复验摘要<br/>关系/出场章/刷新摘要]
W2 --> E[无连接: 计算增量向量]
E --> W3[短写: 复验摘要/owner<br/>落向量/done]
E -->|失败或 owner 冲突| X[锁内复验完整摘要<br/>通过才撤销]
```
恢复与并发纪律:
- 普通错误只允许同窗完整撤销后重试一次;第二次仍失败立即停止当前作品,保留断点,不跳窗、不换作品内顺序。
- `processing` marker 固定为 `upgrade-fence:v1:<stage>:<inputSha256>:<stateSha256>`;`stage` 只允许 `entity` 或 `relation`。非法、缺失或版本不认识的 marker 一律写 durable 断点并停止,不能猜测恢复。
- `inputSha256` 绑定实际送模输入:作品标题、窗口边界、每章 `id/title/status/revision`、每个 block 的 `id/order/type/revision/content_text`,以及实体 schema active version/字段合同、抽取合同和当前 `parse_upgrade.py` SHA-256。按章拼接正文的规则必须与送模文本一致;实体阶段写入前与关系阶段写入前都要重算,章域不完整、无 block 或拼接正文为空直接失败关闭。
- `stateSha256` 覆盖本书卡、别名、留档、水位、审计、向量和全部窗口不变元数据;窗口自身可变的状态/错误文本不参与自引用 hash。`processing` 恢复和失败补偿都要在固定顺序的七域锁内复验它。
- 任何外部确认、改版、删除或辅助域漂移都写 `compensation-failed` 并停止,禁止覆盖外部状态。嵌入落库与 `done` 在同一最终短事务完成,消除 `done` 已提交但向量尚未落库的崩溃缝隙。
- 当前数据库没有所有写方共同遵守的 work 级互斥键。为保证摘要无幻读,七个受控域使用**只跨短写事务**的固定表锁;它会短暂串行化不同作品的写阶段,但绝不跨模型或嵌入计算。待真实耗时证明成为瓶颈后,再单独设计全系统统一的 work 级写锁,不能在本次修复里用弱锁换假安全。
- fence 引入前的 `legacy failed` 窗不能自动猜测。`recover-legacy-failed` 是唯一恢复入口:preview 输出窗口、七域本窗产物计数与确认 hash;execute 必须取得同书锁、精确匹配 hash、确认无 `processing` 窗且**阻断产物计数为 0**,才可标成 `retryable-clean`。计数只豁免能机械证明来源的全书 reset 墓碑:draft 必须同时 `deleted=true`、`updater='upgrade-reset'`、`status='pending'`;embedding 还必须 `entity_id IS NULL` 且自身 `deleted=true`;alias 必须 `deleted=true` 且 `updater='upgrade-reset'`。presence 没有 reset 来源标记,任何本窗行(包括软删墓碑)都阻断;其他软删、活跃行、已有 entity owner,以及任何 card_state/audit 行也都阻断。完整 `stateSha` 仍覆盖所有 deleted 行,不能混淆。操作人仍须先确认旧进程与数据库 backend 已结束,工具不得把“查不到已提交行”误当成“旧事务不存在”。
- 显式 `--redo-window` 需要把 redo 前完整快照持久化到进程外,单靠内存快照无法承受崩溃。该持久快照协议另立设计前,命令机械拒绝;历史修正走已有全书 backup/reset/rebuild,不提供不完整的 redo。
### 风险
1. **条目从字符串变对象,合并逻辑改动面不小**。`parse_upgrade.py` 现有一大套针对字符串条目的守卫(剥前缀、剥尾残、拦垃圾、拆粘连、窗序归位),全要跟着改造成处理里程碑对象;存量卡还是字符串,迁移要兼容。→ 缓解:分两步落,先补字段和提示词(新卡受益),再做存量迁移;对象结构压到三字段降低崩坏面。
@ -251,6 +284,7 @@ flowchart TB
3. **语义判重增加调用量和跨型误并风险**。→ 缓解:0.60 阈值(实测校准) + 同型才自动并 + 跨型仅提示;判重近邻可像 v6 说的那样批量做、不逐条烧。
4. **存量迁移精度损失**:无内嵌章号的条目只能靠窗映射到 12 章宽区间。→ 接受为存量固有局限,新卡不受影响。
5. **改 schema 后必须重跑种子**,否则库里字段合同和 YAML 不一致,抽取会按旧合同把新字段裁掉。→ 实施清单里钉死"改 YAML 后重跑 `seed_schemas.py`"这一步。
6. **受控域表锁会压缩多作品写入并发**。→ 模型调用仍可并行,表锁只覆盖短写和摘要复验;实施后记录阶段耗时与锁等待,若写阶段成为瓶颈,再以统一 work 级锁替换,禁止局部脚本自创弱互斥。
### 验证办法(实施后怎么确认真治好了)
@ -260,6 +294,8 @@ flowchart TB
- **语义判重召回**:造一个改名样例(影杀者 / IV 代纯机械机甲·影杀者),确认语义判重能召回并由 M3 判为同一实体或前身后继。
- **章号无窗号残留**:迁移后全库扫一遍,确认演变类字段里不再有 [窗N] 前缀。
- **章号证据真绑定**:用跨章窗口重抽验证,错标章节的里程碑必须拒收;正确章节原文短引通过后,正式卡只保留章 / 台阶 / 周期,不残留证据字段。
- **连接红线**:离线门禁覆盖观察、证据修复、判重、实体更新、关系生成/重整和嵌入;每次外部调用时业务数据库连接数必须为零。
- **真实数据库恢复冒烟**:在回滚事务内验证公共 `example_upgrade_window` 完整行的 `processing` marker 写入、复验和回滚;同事务把一张 `upgrade_book` draft 的 revision 加一,确认 marker 复验明确拒绝,最后用新连接确认窗口完整行和 draft revision 均未改变。不满足 pending 窗或无可用 draft 时明确失败。纯 fake 连接测试不能替代这项。
---