第 20 周:接通 Capstone 数据与访问边界
直接答案: 第 20 周只做 Capstone M1:把 M0 批准的数据和角色边界落实到摄取、索引、检索与缓存,并证明未授权分块没有进入声明测试矩阵的候选集。本周不生成最终回答、不接写工具,也不毕业;第 21–24 周的 RAG、MCP、综合安全、试点和交接仍然保留。
为什么权限必须在生成之前成立
标题“为什么权限必须在生成之前成立”如果系统先取回所有文档,再让模型“不要引用无权内容”,敏感内容已经进入了不该进入的上下文。安全边界应更早生效:
已认证身份与角色 + M0 数据范围 + 语料版本、有效期与撤销状态 → 存储或检索查询层 ACL → 只产生允许的候选分块 → 按权限摘要与版本隔离的缓存 → 第 21 周才允许进入回答生成本周验收对象是原始候选集,不是最终回答文字。一个最终回答看似没有泄密,不能证明受限分块从未进入模型或缓存。
先看完成品:北辰双业务单元矩阵
标题“先看完成品:北辰双业务单元矩阵”以下文档、身份、租户和测试结果都是课程固定合成材料。北辰 M0 已明确要求 A、B 两个业务单元隔离,所以这个完成示例使用租户边界;你的 M0 没有该需求时,只实现已批准的角色或业务单元边界,不要为了展示技术强行增加多租户。
固定语料
标题“固定语料”| 文档 ID | 业务单元 | 角色 | 状态 | 内容用途 |
|---|---|---|---|---|
A-POLICY-01-v3 |
A | 普通客服 | 当前有效 | A 的普通退款政策 |
A-EXCEPTION-02-v2 |
A | 主管 | 当前有效 | A 的主管例外 |
B-POLICY-01-v5 |
B | 普通客服 | 当前有效 | B 的同名政策 |
A-NOTICE-OLD-v1 |
A | 普通客服 | 已过期 | 旧通知负向夹具 |
A-UNKNOWN-ACL-v1 |
A | 缺失 | 隔离 | 缺 ACL 负向夹具 |
固定身份
标题“固定身份”| 身份摘要 | 业务单元 | 角色 |
|---|---|---|
id-a-agent |
A | 普通客服 |
id-a-lead |
A | 主管 |
id-b-agent |
B | 普通客服 |
原始候选的预期结果
标题“原始候选的预期结果”| 查询身份与状态 | 允许进入候选 | 必须为 0 的候选 |
|---|---|---|
| A 普通客服 | A-POLICY-01-v3 |
A 主管、B、过期、缺 ACL |
| A 主管 | A 普通 + A 主管当前资料 | B、过期、缺 ACL |
| B 普通客服 | B-POLICY-01-v5 |
A、过期、缺 ACL |
| A 主管降权为普通客服 | 仅 A 普通资料 | 之前缓存的 A 主管资料 |
撤销 A-POLICY-01-v3 后 |
无该文档 | 已撤销版本 |
一条可检查的测试记录应同时包含输入、版本、预期候选和实际候选:
{ "case_id": "m1-a-agent-cross-unit", "identity_fixture": "id-a-agent", "corpus_version": "northstar-capstone-corpus-v1", "retrieval_version": "retriever-v1", "query_fixture": "refund-policy", "expected_candidate_ids": ["A-POLICY-01-v3"], "forbidden_candidate_ids": [ "A-EXCEPTION-02-v2", "B-POLICY-01-v5", "A-NOTICE-OLD-v1", "A-UNKNOWN-ACL-v1" ], "result_scope": "synthetic_fixed_matrix"}这是测试输入契约,不是假装已经运行过的结果。你必须用自己的实现保存实际候选并逐项比较。
第一步:从 M0 写出数据与访问合同
标题“第一步:从 M0 写出数据与访问合同”不要直接打开向量库。先把业务语言映射成系统可执行的边界:
# Capstone M1 数据与访问合同 v1
## 获准范围- 业务决定与任务:- 身份来源:- 角色 / 业务单元 / 租户:- 允许的数据分类:- 明确禁止的数据:
## 文档进入条件| 字段 | 必填? | 缺失动作 | 负责人 || --- | --- | --- | --- || source_id | 是 | 隔离 | 数据所有者 || owner | 是 | 隔离 | 知识负责人 || acl | 是 | 默认拒绝并隔离 | 安全/数据负责人 || effective_from / expires_at | 是 | 不进入可靠候选 | 知识负责人 || version | 是 | 拒绝构建 | 工程负责人 |
## 查询与缓存规则- ACL 在哪一层执行:- 身份和权限摘要怎样形成:- 缓存键包含哪些版本与权限字段:- 权限改变、文档撤销或语料更新怎样失效:- 原始候选怎样进入受限审计证据:
## M1 负向矩阵- 跨角色或跨业务单元;- 降权后复用缓存;- 缺 ACL;- 过期与撤销;- 旧语料版本或旧索引。如果 M0 没有租户概念,合同就不要出现租户字段。权限模型要来自问题证据,不来自作品集想展示的复杂度。
第二步:建立可重建、默认拒绝的语料入口
标题“第二步:建立可重建、默认拒绝的语料入口”摄取和索引必须满足:
- 每个文档和分块保留来源、所有者、版本、有效期、分类与 ACL;
- 分块不能丢失或放宽父文档权限;
- 缺 ACL、所有者或有效期的材料进入隔离区,不按公开处理;
- 同一事实来源、解析规则和分块版本可以重建相同语料快照;
- 索引不是唯一事实来源,能够从版本化原始源重建;
- 撤销、来源下线或有效期变化有刷新时限和硬停止规则。
语料 manifest 最小字段
标题“语料 manifest 最小字段”corpus_version: northstar-capstone-corpus-v1source_snapshot: synthetic-policy-fixture-v1parser_version: parser-v1chunking_version: chunker-v1access_policy_version: access-policy-v1document_count: 5quarantined_document_ids: - A-UNKNOWN-ACL-v1scope: synthetic_training_only这些字段是完成示例。你的报告记录自己的实际版本与计数,不要复制示例数字当运行证据。
第三步:把 ACL 下推到查询层
标题“第三步:把 ACL 下推到查询层”一次检索至少需要:
- 已验证的身份与角色;
- 若 M0 要求,则包含业务单元或租户;
- 权限策略版本;
- 语料与索引版本;
- 文档状态过滤:当前有效、未撤销、来源仍在线;
- 一个可追踪的
run_id。
实现可以是带过滤条件的数据库查询、搜索引擎过滤、向量检索 metadata filter,或先构建物理隔离索引。无论选哪一种,都必须能检查过滤后的原始候选 ID,并证明受限分块未进入后续上下文。
第四步:运行声明清楚的负向矩阵
标题“第四步:运行声明清楚的负向矩阵”至少覆盖:
| 测试 | 先做什么 | 必须观察什么 |
|---|---|---|
| 允许身份 | 查询当前普通政策 | 只返回允许且当前的候选 |
| 跨角色 | 普通角色查询主管资料 | 受限候选数为 0 |
| 跨范围 | 若 M0 有业务单元/租户边界,查询同名资料 | 其他范围候选数为 0 |
| 降权缓存 | 先以高权限查询,再降权复用同一问题 | 高权限候选不从缓存出现 |
| 缺 ACL | 摄取缺权限字段文档 | 文档被隔离,候选数为 0 |
| 过期 | 查询过期政策 | 不进入可靠候选,并返回可解释状态 |
| 撤销 | 查询后撤销,再在声明传播窗口后查询 | 已撤销版本不再命中 |
| 旧版本 | 切换语料或策略版本 | 缓存与 trace 不混用版本 |
一次泄漏就阻断第 21 周。不能说“生成阶段会过滤”,也不能把它留到第 23 周综合安全测试再修。
第五步:写一份不夸大的 M1 报告
标题“第五步:写一份不夸大的 M1 报告”报告至少分成四段:
- 测试范围: 固定语料、索引、身份、版本、负向矩阵与运行日期;
- 逐例结果: 预期与实际候选 ID、权限摘要、缓存状态和
run_id; - 失败与恢复: 泄漏、过期或缓存污染如何修复,修复后重跑哪些回归;
- 证据边界: 未覆盖角色、罕见缓存状态、并发权限变化和真实生产环境仍未知。
合格结论示例:
在
northstar-capstone-corpus-v1、三种固定身份与本报告声明的 8 类合成测试中,未授权分块进入检索候选的数量为 0。该结果只覆盖当前版本和矩阵,不证明未测试身份、并发权限变化或真实生产环境永不泄漏。
五天安排
标题“五天安排”| 学习日 | 建议时间 | 当天动作 | 离开前必须有的结果 |
|---|---|---|---|
| 第 1 天 | 1–2 小时 | 固定语料、身份、权限与预期候选矩阵 | 数据/访问合同与负向用例 |
| 第 2 天 | 2 小时 | 复用并调整摄取、隔离和可重建索引 | 语料卡、manifest 与隔离证据 |
| 第 3 天 | 2 小时 | 在存储或查询层实施 ACL 与缓存隔离 | 可检查原始候选的检索基线 |
| 第 4 天 | 1–2 小时 | 跑跨角色/范围、降权、过期、撤销和缺 ACL 测试 | 逐例实际结果与失败修复 |
| 第 5 天 | 1–2 小时 | 核对来源、版本和 run_id,完成三层演示 |
M1 报告与第 21 周入口清单 |
本周验收
标题“本周验收”- 在固定语料、身份、版本和声明矩阵中,未授权分块进入候选集的数量为 0;
- 缺失 ACL 默认拒绝并隔离;
- 文档分块没有丢失或放宽父文档权限;
- ACL 在存储或检索查询层生效,而不是生成后过滤;
- 缓存键包含权限摘要、语料版本和检索版本;M0 有租户边界时还包含租户;
- 降权、撤销和版本变化能使不再适用的缓存失效;
- 索引可从版本化事实来源重建,并记录解析与分块版本;
- 过期、撤销和来源下线有刷新或硬停止规则;
- 每次检索保存
run_id、身份摘要、策略与语料版本; - 任何泄漏都会阻断第 21 周;
- 结论明确限定于合成或授权数据、固定版本和已声明测试矩阵。
常见失败与修复
标题“常见失败与修复”| 失败 | 诊断信号 | 修复动作 |
|---|---|---|
| 检索后才过滤权限 | 原始候选含受限分块 | 把 ACL 下推到存储或查询层并重跑原始候选测试 |
| 缺 ACL 被当成公开 | 未知文档进入索引 | 默认拒绝、隔离并修复数据所有权 |
| 主管查询污染普通缓存 | 降权后仍命中主管内容 | 权限摘要进入缓存键,清旧缓存并加降权回归 |
| 索引成为唯一事实来源 | 无法说明来源或重建 | 从版本化原始源重建并对账摘要 |
| 撤销文档仍能命中 | 传播窗口后候选仍含旧版本 | 增加失效传播、陈旧上限和硬停止 |
| 只测试允许身份 | 报告没有负向候选 | 增加跨角色/范围、降权、撤销与缓存复用 |
| 为展示而增加多租户 | M0 没有租户证据 | 删除无依据边界,回到已批准角色模型 |
独立迁移:增加外包客服角色
标题“独立迁移:增加外包客服角色”在不改变北辰完成示例的前提下,独立增加一个“外包客服”角色。先写预期,再改数据:
- 它能看到哪些当前政策?
- 哪些 A 普通客服资料也必须拒绝?
- 角色切换和降权时缓存键怎样变化?
- 缺少外包范围标记的文档是公开还是隔离?
- 哪些测试失败会要求重开 M0,而不是继续补 if/else?
如果这个角色没有业务证据,只把它作为明确标注的权限练习,不要声称来自真实客户需求。
用同一份证据向三类人解释
标题“用同一份证据向三类人解释”- 老板版: 当前降低的是哪一类数据访问风险,哪项失败会立即停止项目,尚未证明什么业务价值。
- 一线版: 为什么某些资料不可见、过期或撤销时系统怎样说明、由谁负责更新或升级。
- 工程版: ACL 如何贯穿摄取、分块、索引、查询、缓存、撤销、版本和审计。
下一步:课程仍未结束
标题“下一步:课程仍未结束”第 20 周通过只建立了 M1 数据与检索候选基线。第 21 周只能消费固定版本的语料、ACL 检索接口和负向测试;语料或权限变化必须新建版本并重跑 M1。完整的第 21–24 周仍保留在24 周标准路线中:
- 第 21 周:Capstone 有引用问答、拒答、冲突/过期处理和失败分类;
- 第 22 周:MCP 预览、逐次审批、写入、幂等、对账与审计;
- 第 23 周:冻结盲测、威胁测试、负载/成本、故障演练与上线决定;
- 第 24 周:受控试点或明确模拟走查、最终交接、复盘和三听众答辩。
来源与事实边界
标题“来源与事实边界”- NIST SP 800-162 — Attribute Based Access Control:基于主体、对象、操作与环境属性实施访问控制的参考。
- OWASP Authorization Cheat Sheet:默认拒绝、每次请求验证权限和授权测试原则参考。
- NIST AI RMF Generative AI Profile:生成式 AI 风险识别与管理参考。
核验日期:2026-08-25。 北辰文档、身份、租户、矩阵和版本均为合成材料;示例 JSON 是输入契约,不是伪造运行结果。本周门槛是课程设计,不证明真实生产安全、合规、无泄漏、采用率、效率或 ROI,也不代表完成第 24 周毕业要求。