2026-09-23·by Sijie Wang#standmeet#architecture#design

acl-and-quota-granularity

ACL 与配额粒度

父级: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 落地,6395374b0backend/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:53backend/internal/access/entity/path_acl.go:143);冻结的作用域(授予 ∧ ¬代码侧语料拒绝 ∧ 公开身份只读已发布)随 RoleSnapshot(AllowsCorpus(uri, published) / CorpusScope())一起流转,raw://** 被硬性拒绝;作为一个不透明的 corpus_scope 整块通过 _meta 转发给外部化的检索插件。
  • Owner-MCP-server ACL: 已落地——外部 MCP 服务器通过 role_mcp_serversSetMCPServers 加所有权校验)按角色挂载;冻结进快照,作为 mcpServerIDs

实现细节:ACL=always 的能力

某些能力被标记为 ACL=always(retrieval、ask_visitor、summarize),不出现在 allowedTools。它们的拒绝在暴露关口由 RoleSnapshot.deniedCapabilities 把关,而不是查表。

验证

仓库中覆盖最重的端到端测试:

  • acl-capability-matrix:测试全局/角色/代码三层的所有组合。
  • acl-skill-matrix:测试技能 ACL 的隔离性。
  • acl-freeze-isolation:验证并发变更下冻结/实时的不对称性。
about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →