评测、运营与增量项目

本节目标

运行一个离线、确定性的知识生命周期项目;从 41 项测试理解 canonical revision、source sequence、outbox、published pointer、查询时 ACL/正文完整性门禁、墓碑、内容清除、过期事件不物化和对账;最后为真实知识库建立分层评测与运营指标。

四层验收

  1. Ingestion:授权来源覆盖率、失败率、游标推进、新鲜度和删除发现延迟;
  2. Content:解析结构、schema、来源、ACL、版本和关键字段正确性;
  3. Retrieval:Recall@k、MRR/nDCG、过滤正确、零结果和过期候选;
  4. Answer:引用支持、完整性、无答案拒答、权限与安全。

只测最终回答难以定位是采集、解析、检索还是生成问题;只测相似度又不能证明用户任务成功。查询集要分层覆盖语言、专名、时间敏感、权限、无答案、长尾和来源冲突。

项目结构与能力

项目表:

表角色
documents每个 tenant/document 的 current、published、sequence、deleted 与访问阻断状态
revisions / revision_aclappend-only canonical 修订、状态 hash 与 ACL 快照
outbox与 canonical 事务一起写入的待投影事件和重试状态
search_revisions / search_acl可重建的教学搜索投影
tombstones删除序列、原因与应用层清除状态

它实现:来源/构建 state hash、no-op、checkpoint、旧事件忽略、同序列冲突、同序列删除原因冲突、pipeline 重处理、旧 published 版本保活、ACL 改变立即阻断、删除/复活顺序、投影失败重试、删除传播、应用层 canonical content 清空、输入/候选资源边界、outbox revision 身份绑定、过期事件不物化,以及查询时 canonical/projection 完整性复算。

明确未实现:真实 connector/身份提供方、向量/FTS 检索、分布式消息、并发 worker、超时/死信调度、数据库加密、备份/WAL 擦除、审计日志防篡改和合规证明。

运行项目

从项目根目录(同时包含 docs/ 与 .website/)使用 PowerShell 7:

$env:PYTHONDONTWRITEBYTECODE = '1'
py -3.11 -B -W error '.\docs\知识库构建\examples\knowledge_store.py'

程序依次:发布两份文档;更新一份内容;模拟一次投影失败并证明旧 revision 仍可见;重试发布新版;删除该文档;传播删除并清空 canonical content;最后输出 JSON 对账报告。

检查输出中的:

  • before_publish[0].revision_number == 1;
  • after_publish[0].revision_number == 2;
  • actions 包含 failed 后又成功投影(成功投影不单列入 actions);
  • pending_events、未发布/阻断 active 文档、跨身份指针、hash/ACL 不一致、孤儿 ACL 与删除残留投影都为 0;
  • purged_revisions == 2。

这只能证明应用表中的内容字段被清空,不能证明 SQLite 文件历史页、WAL、系统快照或备份已经物理擦除。

运行回归测试

$env:PYTHONDONTWRITEBYTECODE = '1'
Push-Location '.\docs\知识库构建\examples'
try {
    py -3.11 -B -W error -m unittest -v test_knowledge_store.py
    py -3.11 -B -O -W error -m unittest -v test_knowledge_store.py
} finally {
    Pop-Location
}

41 项测试覆盖:首次发布前不可见、ACL 组顺序规范化与上限、幂等 no-op、checkpoint 推进、旧事件、同序列状态/删除原因冲突、pipeline 重处理、失败投影保留旧版、ACL 收紧即时阻断、tenant 隔离、多组授权、删除即时隐藏、墓碑幂等、缺失对象删除、过时删除、受控复活、Unicode/换行规范化、输入/大小/查询候选门禁、未授权候选不消耗 limit、多个待发布 revision、未发布 revision 不物化、删除后待处理 upsert 不物化、outbox revision 跨身份篡改拒绝、无发布路径、跨 tenant 指针损坏的查询纵深防御、canonical/search 实际正文与 source/build state 重新哈希、投影 ACL 篡改、删除后内容清除和确定性主程序输出。

-O 会移除裸 assert;生产门禁使用显式异常和数据库约束,测试使用 unittest 断言。

对账不能只比较两个存储的 hash 字段:攻击者或故障若只改 search_revisions.content、保留旧 content_hash,字段间比较仍会误报一致。本项目分别从 revisions.content、canonical ACL/source/build state 与 search_revisions.content/search_acl 重算并交叉比较;任一不一致都会阻断 require_reconciled()。

更关键的是,在线 search_visible() 不要求调用者先记得运行后台对账:它把 published pointer 同时连接到 canonical revision 与搜索投影,先用 canonical ACL 做候选与 limit 硬过滤,再重算已授权候选的 canonical hash、投影正文 hash 和两侧 ACL。只篡改投影 ACL 以授予 guest,不会让该文档进入 guest 候选窗口,后台 reconcile() 会报告不一致;篡改已授权候选的投影正文或 ACL 则使查询直接失败关闭。这样未授权文档既不会返回,也不会挤占公开结果的 limit。

这些无密钥摘要证明的是当前可信代码下的内部一致性,不是数据库真实性。若攻击者可同时改写 canonical 正文、ACL、所有声明 hash 与查询代码,仍需受保护事实存储、MAC/签名、远端 attestation 或等价控制;本项目没有实现这些能力。

关键实验

1. 内容更新与 ACL 更新不同

内容变化但 ACL 相同:canonical current 先更新,旧 published revision 继续服务,直到新投影成功。ACL 变化:access_blocked=1 立即阻断旧投影,新 ACL 投影完成才恢复。这一差异是项目最重要的安全结论。

这里的 allowed_groups 是上游已经解析好的扁平 allow 列表。项目没有实现文件夹/站点继承、deny 优先、ABAC、policy_revision、permission provenance 或解析时间;因此只能证明“扁平 ACL 生命周期”而不能证明企业权限解析器正确。真实系统应把可重算 permission snapshot 与 authorization decision 版本一并保存并测试父级收紧。

2. Sequence 与 state hash 共同判断

  • 同 sequence + 同 source/build state:noop;
  • 更高 sequence + 相同 state:只推进 checkpoint;
  • 同 sequence + 不同 source state:冲突;
  • 同 source state + 新 pipeline version:reprocessed;
  • 更旧 sequence:stale_ignored。

真实 connector 必须用其协议等价物替换教学整数序列。

3. 删除与内容清除

删除事务先写 deleted、access block、tombstone 和 outbox;查询立即不可见。worker 清除搜索投影后,purge_deleted_canonical_content() 才允许把 revision 内容置空。哈希、墓碑和备份如何保留必须另由治理政策决定。

内容更新或 ACL 收紧后的旧 revision 投影当前会保留在 SQLite 中;published pointer 与 access block 使它们不可查询,但项目没有 rollback-retention 到期清理或 stale_projection_rows 对账。这不等于撤权后的物理清除。生产实现需要明确旧投影保留窗、清理 worker、缓存/副本传播 SLO 与完成证据。

4. 过期 outbox 事件不应留下副本

source event 写入 outbox 后,worker 执行前可能已经有更新、删除或复活。项目先验证 event 的 tenant/document 与 revision 身份,并只为仍等于 current_revision_id 的 upsert 建立 search_revisions;较早的 upsert 会被消费但不物化。这样“先收到 v1、后收到 v2 或删除、worker 才开始处理”的路径不会留下从未发布的搜索内容。

这不是分布式顺序保证:多 worker、跨分区重排、消息确认与死信重放仍须在真实基础设施中验证。它只是把错误或篡改的跨身份 revision 指针变成显式故障,并缩小过期事件的保留面。

运营指标与告警

  • source lag、最后成功游标、全量/增量覆盖率;
  • 各阶段吞吐、错误率、重试与隔离队列年龄;
  • active/current/published/blocked/tombstone 数及差异;
  • pending outbox 年龄、最大对象 event lag、失败尝试数;
  • parser/chunker/model/index/policy version 分布;
  • ACL 缺失、跨 tenant 候选、撤权/删除传播延迟;
  • 零结果率、过期引用、gold query 指标和用户纠错。

阈值应由历史基线、风险和 SLO确定。总平均会掩盖小租户、长尾格式和高敏感文档,要按 tenant、来源、格式、语言和权限等级切片。

综合任务:加入 chunk 投影

在不把 chunk 变成 canonical 事实的前提下扩展项目:

  1. 新增 chunker_version 与 chunks(revision_id, chunk_id, order, text, hash);
  2. 相同 content + 新 chunker 触发 reprocess,不伪造 source revision;
  3. 新 revision 投影失败时旧 published chunks 继续服务;
  4. ACL 改变或删除时立即阻断旧 chunks;
  5. 删除传播后 chunk 数为 0;
  6. 设计 20 个 retrieval query、5 个无答案、10 个 tenant/ACL 越权用例;
  7. 用对账证明每个 published revision 的 chunk 顺序连续、ACL 完整、父指针真实。

完成定义:正常与 -O 测试通过;重复/乱序事件无副作用;故障注入后可重试;没有真实凭据或大型数据;明确未验证的分布式与物理删除边界。

完成后进入来源到引用证据链项目,把这里的 document-level 搜索投影升级为 element/chunk/index generation,并验证旧 snapshot、墓碑、授权版本与最终 citation 的同一条 lineage。

常见错误与排查

  • 测试只有 happy path:无法证明重试、乱序、权限和删除。
  • 只看 active 数量相等:可能 tenant/ACL、版本或父指针错误。
  • 把本项目搜索结果当检索评测:instr() 没有证明中文分词、相关性或规模性能。
  • 清空列就宣称合规删除:忽略存储页、日志和备份。
  • 把测试使用的组当身份认证:真实 subject groups 必须由可信身份/授权系统提供。

掌握检查

  • 我能解释项目所有表的事实/投影边界。
  • 我能从 source sequence、source state 和 build state 判定动作。
  • 我能证明失败发布不丢旧版,ACL 收紧却立即阻断。
  • 我能解释为什么授权查询必须在线复核 canonical/projection,而不能把后台 reconcile() 当成唯一安全门。
  • 我能说明为什么 event 已过期时仍可完成消费,却不能留下未发布的搜索投影。
  • 我能从墓碑到每个下游定义传播和完成证据。
  • 我会分开验收 ingestion、content、retrieval 与 answer。
  • 我能明确列出 SQLite 教学项目未验证的生产风险。

参考资料

来源获取日期:2026-07-22。返回 知识库构建学习目录;下一库是 Chunking 策略。