#大模型#RAG#DevOps

RAG 权限元数据迁移:双写、覆盖证明与默认拒绝切换

把 role 字段改成 policy ID 不是普通 mapping 变更。本文用版本化 ACL schema、双写回填、覆盖账本和负向查询证明迁移期间没有权限空窗。

本文要点

  • 主题聚焦:大模型、RAG、DevOps
  • 适合人群:独立开发者、全栈工程师、AI 应用开发者
  • 核心价值:快速理解背景、实践路径、常见误区和工程建议
作者 @admin·发布于 2026年2月6日·更新于 2026年7月20日

早期知识库常把 roles: ["employee"] 直接写进 chunk,后来需要项目组、外部协作者和文档级例外,便想改成 tenantId + policyId + classification。如果 reader 先发布,旧 chunk 缺字段可能被当成公开;如果 writer 先停写旧字段,回滚 reader 又看不到新文档。这个问题不是 Embedding 迁移 的向量质量切换,而是任何一步出错都可能放宽访问范围的 schema 演进。

先冻结两个完整契约

为旧、新元数据定义不可变版本,不能靠字段是否存在猜测:

json
{
  "aclSchema": 3,
  "tenantId": "t_42",
  "resourceId": "doc_9",
  "policyId": "pol_71",
  "policyRevision": 12,
  "classification": "internal",
  "validFrom": "2026-07-18T00:00:00Z",
  "sourceVersion": 884
}

aclSchema、tenantId、resourceId 和 policyId 对 v3 都是必填;未知版本、空值、类型错误和租户不匹配一律不可检索。映射函数 v2 -> v3 固定代码 digest,并定义无法转换的状态 QUARANTINED,不能用 classification=public 填默认值。授权语义仍以 RAG 检索权限控制 为准,迁移只改变其可验证表示。

扩展、回填、切换、收缩

第一阶段先创建 v3 字段或新索引,确认向量库支持目标字段类型、数组和过滤运算。像 PostgreSQL ALTER TABLE 一样,把添加兼容表示与删除旧表示拆成不同发布;向量服务对 metadata 的相等、集合和组合过滤能力也要用真实查询探测,不能根据 SDK 类型推断。

第二阶段不能在源数据库事务里远程“双写”。事务为资源分配单调 materializationGeneration,提交包含 sourceVersion、policyRevision 和 payload hash 的完整 desired snapshot 与同一条 outbox;v2、v3 projector 仅在传入 generation 大于远端 generation 时条件写,不能分别比较两条版本轴。协调器读取两路投影,校验资源、generation、两条版本、ACL 与内容摘要一致后,写入绑定同一组值的 per-resource committed marker;任一路超时或响应丢失都不能使该代成为当前。删除墓碑也按更高 generation 生效,规则与 RAG 增量索引一致性 共用单调比较。

第三阶段 reader 先 shadow 执行 v2 和 v3 过滤。每个请求记录候选资源 ID 集合的摘要、允许差异、拒绝原因和 schema 覆盖,不把正文写进差异日志。只有预期的策略变化可以产生集合差异;v3_allow - v2_allow 中任何未批准资源都是安全阻断项。正式切换按租户固定路由,绝不能同一请求在 v3 空结果后自动无过滤查询 v2。

最后才停止 v2 写入并经过完整回滚窗口后删除旧字段。旧 reader、旧 writer 和旧回填任务都应被版本准入拒绝,避免半年后重放任务重新制造 v2-only chunk。

覆盖账本必须能逐项复算

总文档数相等不是证明。一次覆盖运行先冻结 source CDC watermark S、ACL CDC watermark A 与候选 index generation G,再按 source、tenant、classification 和 policy 记录资源数、chunk 数、缺失 v3、隔离、孤儿及排序后 (resourceId, sourceVersion, policyId, chunkHash) 的摘要。报告必须绑定 S/A/G,从源、授权库和索引按同一水位生成可定位差集,不能把不同时间的三个“当前值”相减。

alias CAS 前重新读取 projector offsets、candidate generation 和未提交 marker:v2、v3 各自 lastSourceApplied >= S 且 lastAclApplied >= A、G 未变、fence 内 desired revision 全部 committed 才可切换。fence 后的新变更仍走连续双投影,只有自己的 marker 完成才可被 reader 选为当前;水位或 generation 改变就废弃报告并重跑。

对固定权限矩阵执行负向检索:无租户、伪造 tenant、已移除成员、跨项目用户、过期外部账号、未知 schema、缺 policy、旧 policy revision。每个 query 不只断言最终答案没有秘密,还断言向量候选、rerank 输入、trace 和引用都未出现未授权 resource ID。MCP 多租户隔离 中的 cache key 回归也要重跑,因为 schema 版本与策略 revision 已成为新的缓存影响因素。

故障注入比顺利回填更重要

在预生产注入 v3 条件写成功但响应丢失、projector 崩溃、(source=2, policy=1) 与 (source=1, policy=2) 交叉乱序、删除迟到、空字段、旧 mapping shard、offset 停滞和 alias CAS 冲突。硬不变量是:没有匹配 generation 的 committed marker 或 aclSchema != 3 的投影都不能进入 v3 生产上下文;可用性下降可以暂停,不能关闭过滤。

发布门禁可明确为:当前资源覆盖率 100%,未知 schema 为 0,孤儿 chunk 为 0,所有负向样本泄露为 0;允许集合差异全部有审批单;P95 过滤延迟与空结果率在预算内。抽样召回和答案质量只是附加门槛,不能替代集合证明。

回滚 reader,不回滚授权事实

切换前保留 v2 reader、v2 metadata 和上一个索引别名。若 v3 误拒绝或延迟失控,把受影响租户固定路由回 v2,同时保持双写并冻结 schema 收缩;回滚动作本身记录 operator、manifest digest 和租户集合。若发现 v3 意外放行,先关闭该版本的生产查询并默认拒绝,再修映射,不能为了恢复回答率回到已知不安全路径。

回滚不能撤销已经发生的权限变更,也不能把已删除文档从旧索引复活。恢复后重新运行覆盖账本、负向矩阵与 模型服务金丝雀发布 式的分层切流。权限 schema 迁移真正完成的标志,不是 v3 索引建完,而是每个当前 chunk 都能证明自己属于一个已知、当前且默认拒绝的授权契约。

延伸阅读

Related

相关推荐