ACL 与配额粒度

更新于 · 在 sijie.xyz 查看原条目 ↗

父级:access-control

核心模型

符号约定:

  • 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:验证并发变更下冻结/实时的不对称性。

相关笔记