ACL 与配额粒度
核心模型
符号约定:
X= 被检查访问权限的某项能力或工具
暴露判定是三层的纯 AND 收窄:
exposed(X) = global_enabled(X) ∧ role_grants(X) ∧ ¬code_denies(X) ∧ connector_deps_met ∧ quota_ok
上层没有授予的东西,下层无法打开。每一层都必须通过,访问才能成功。
冻结/实时的不对称
访问控制的执行效果取决于每一层何时被求值(confusables):
- 全局层:实时。 存储在
capability_settings中,在请求时求值。翻转一个全局能力会立即杀死所有正在使用它的运行中会话。 - 角色层:签发时冻结。 在代码创建时快照进 role-snapshot-frozen。角色变更不会追溯影响已存在的代码。
- 代码层:签发时冻结。 拒绝表
code_capability_denials/code_skill_denials/code_corpus_denials在代码创建时锁定。过去的代码永远看不到未来的代码侧拒绝。
三态的消除
早先的三态 code_capability_overrides(allow/deny/inherit)已被杀死,现在替换为稀疏拒绝表:
code_capability_denials:存在一条记录即表示该能力被拒绝;没有状态列。code_skill_denials:存在一条记录即表示该技能被拒绝;没有状态列。code_corpus_denials:(code_id, uri_pattern)——这个代码从角色的正向语料列表里收回的一个 glob(第三类,2026-07-16 落地,6395374b0;backend/db/schema.sql)。冻结进快照作为deniedCorpusURIs,在匹配时检查,而不是从授予列表里减掉(glob 减不掉一个列表项)。
一个代码只能做减法,永远不能重新授予。 这从构造上消除了"代码复活某项能力"这一类权限提升的边角情形。
V1 范围
管理平面阶段落地了全局层。V1 覆盖范围:
- 能力/工具 ACL: 已完全实现。
- 技能 ACL: 已完全实现。
- 语料 glob ACL: 已落地——
allowsCorpusEntry(scope, genre, path, published)→access.AllowsCorpusEntry(scope, CorpusEntryRef)在每次语料读取时都会强制执行(backend/internal/corpus/usecase/corpus_lister_pg.go:53、backend/internal/access/entity/path_acl.go:143);冻结的作用域(授予 ∧ ¬代码侧语料拒绝 ∧ 公开身份只读已发布)随 RoleSnapshot(AllowsCorpus(uri, published)/CorpusScope())一起流转,raw://**被硬性拒绝;作为一个不透明的corpus_scope整块通过_meta转发给外部化的检索插件。 - Owner-MCP-server ACL: 已落地——外部 MCP 服务器通过
role_mcp_servers(SetMCPServers加所有权校验)按角色挂载;冻结进快照,作为mcpServerIDs。
实现细节:ACL=always 的能力
某些能力被标记为 ACL=always(retrieval、ask_visitor、summarize),不出现在 allowedTools 中。它们的拒绝在暴露关口由 RoleSnapshot.deniedCapabilities 把关,而不是查表。
验证
仓库中覆盖最重的端到端测试:
acl-capability-matrix:测试全局/角色/代码三层的所有组合。acl-skill-matrix:测试技能 ACL 的隔离性。acl-freeze-isolation:验证并发变更下冻结/实时的不对称性。