版本、删除与权限

本节目标

用 current/published 分离实现可回滚更新;让 ACL 收紧和删除 fail closed;理解墓碑、逻辑删除、内容清除、缓存/备份传播和授权查询边界。

多条版本轴

知识库常同时存在:

  • source revision/sequence:来源内容或权限状态;
  • canonical revision:本库保存的规范修订;
  • schema/pipeline version:解析、规范化、切块配置;
  • model/index version:embedding 模型和检索索引结构;
  • policy version:授权、保留和分类规则;
  • published generation:线上查询正在读取的组合。

内容未变但 pipeline 升级,应重算受影响的派生产物,不应伪造来源内容变更。ACL 变化即使 content hash 相同,也必须产生可追踪状态变化。

Current 不等于 Published

current_revision_id 表示 canonical 最新已接收状态;published_revision_id 表示在线检索已经完整投影并通过门禁的版本。

source event → canonical current + outbox(同一事务)
             → 构建派生投影 → 验收 → 原子切换 published pointer

普通内容更新时,新投影失败可以继续服务旧 published revision;重试成功后再切换。不要先删旧索引,再慢慢构建新索引。

但“旧版继续服务”有安全例外:

  • ACL 收紧:旧投影可能对过多主体可见,应立即 access_blocked,新 ACL 投影成功后解除;
  • 删除/撤权:canonical 状态立即标记 deleted,查询 fail closed,后台再清除投影;
  • 首次发布/删除后复活:没有安全旧版,必须等新投影完整发布。

本库项目用这些规则区分可用性与保密性,避免“一致性最终会恢复”成为短暂泄密的借口。

墓碑与删除传播

删除来源可能是:源对象消失、权限撤销、用户请求、保留期到期、许可变化或治理决定。墓碑至少记录 document ID、来源序列、事件版本、原因类别、run ID 和各下游传播状态;它防止旧批次或全量重扫把对象意外复活。

传播清单通常包括:

  • canonical/current 状态与 revision 内容;
  • 解析元素、chunks、embeddings、关键词/向量/图索引;
  • 查询、答案、prompt、CDN 等缓存;
  • 失败队列、导出、分析副本;
  • 备份、WAL/日志与供应商保留策略。

软删除适合短期回滚和审计,但不自动满足物理删除义务。把数据库字段设为 NULL 也不能证明旧页、WAL、快照和备份已擦除。本项目的 purge_deleted_canonical_content() 只验证应用层传播顺序;真实清除要按存储介质、备份周期和法律/组织政策提供证据。

哈希本身也可能成为个人数据或可关联标识,不能因“不可逆”就永久无条件保留。

授权在候选生成前执行

OWASP 授权指南强调最小权限、默认拒绝、每个请求验证权限和授权测试。NIST SP 800-162 将 ABAC 描述为依据 subject、object、operation 与环境属性评估规则。对应到检索:

  1. 身份系统认证主体并提供可信 tenant/组/属性;
  2. 查询服务把这些属性作为授权上下文;
  3. metadata/lexical/vector 候选阶段执行 tenant 和 ACL/ABAC filter;
  4. 只有授权候选才能进入 reranking、prompt、日志和缓存;
  5. 返回前可做防御性复核,但不能替代候选前过滤。

先召回机密 chunk,再提示 LLM “不要说”,越权数据已经进入处理、日志或缓存。向量不可读也不是访问控制。

ACL 快照、继承与缓存

权限可能来自租户、站点、文件夹、文档和行级属性。可在 revision 上保存可执行快照,同时保留 policy version 和来源关系以便重算。权限变化的传播 SLO要单独监控,不能只看内容新鲜度。

快照还要能回答“何时、按哪条规则、针对哪份上游授权事实作出的决策”。因此生产记录通常需要 entitlement/ACL 的观察时间、有效期或同步水位、policy/connector version 和决策原因;上游 scope 或父级权限无法确认时,应阻断发布而不是沿用过期 allow 结果。

缓存键至少包含 tenant、主体授权摘要、policy version、query 和检索配置;权限变更时要失效。不要把用户 A 的候选或答案缓存直接给用户 B。

公开内容也建议使用显式 public/everyone policy,而不是把空 ACL 当公开;空 ACL 默认拒绝更安全。

顺序、冲突与复活

旧来源事件不能覆盖新状态。若来源提供单调序列:

  • sequence 小于已知值:记 stale_ignored;
  • sequence 相同且状态相同:幂等 noop;
  • sequence 相同但状态不同:冲突并隔离;
  • 删除后只有更高 sequence 的明确 upsert 才能复活。

若真实来源不保证单调序列,应使用 ETag 条件写、变更 feed 语义或来源特定冲突策略。不能把本项目整数模型机械套用。

常见错误与排查

  • 只改 documents 表:旧 chunk/embedding 仍可被搜索。
  • 删除等同 active=false:缓存、投影、备份没有传播证据。
  • ACL 更新等下一次全量:安全撤权窗口过长。
  • 先召回再在回答层过滤:已经发生越权处理。
  • 缓存不含授权上下文:跨主体复用泄漏结果。
  • 把 SQLite NULL 当物理擦除:忽略文件页、WAL 和备份。
  • 相同 cursor 最后写入获胜:隐藏来源协议冲突。

练习

  1. 画出“ACL 撤销后立即不可检索、5 分钟内完成所有在线投影传播”的状态与确认信号。
  2. 为 canonical、chunk、embedding、向量索引、缓存、日志和备份分别定义删除动作与完成证据。
  3. 设计一个内容更新失败仍服务旧版、ACL 更新失败却立即阻断的测试矩阵。
  4. 为 tenant、group、classification、effective time 写一条 ABAC 决策,并列出 8 个拒绝用例。

自测

  • 我能区分 current、published 与各类 pipeline/model/policy 版本。
  • 我知道何时保留旧版,何时必须 fail closed。
  • 删除有墓碑、传播、对账和保留/清除证据。
  • tenant/ACL 在候选生成前执行,空授权默认拒绝。
  • 我不会把应用层清空字段误报为合规物理擦除。

参考资料与下一步

来源获取日期:2026-07-22。下一步:索引与增量更新。