授权可扩展性——门票与工牌
读者
本文面向评估 Plexus 1.0 授权模型能否在无需破坏性重写的前提下扩展到任务级授权和企业授权的人,也面向日后扩展模型、需要了解哪些扩展点是有意预留的人。方案已作为 ADR-020 在 决策记录 中锁定;仓库中的唯一事实来源(SSOT)是 authz-extensibility.md。
一句话版本:1.0 交付完整的工牌和一张原型门票;完整的门票被刻意推迟——但它将来需要的每一个联结键、预留字段和收口点,如今都已存在,并在此处得到保证。
授权的两种单位——门票与工牌
用户考虑的是任务,网关则对每次能力调用执行授权检查。要求人对每次调用都作出任务级决定,就会造成审批疲劳:二十张一模一样的卡片,让每次决定都变成习惯性操作。Plexus 为此区分了两种授权工具:
- 工牌——你是谁。持久的、按 agent 独立的 PAT:长生命期的身份,可单独撤销,本身永远不构成权限(见安全模型)。
- 门票——你此刻可以做什么。以任务为界的授权同意:任务开始时一次批准,任务进行中生效,任务结束即关闭,并能作为一个完整故事被讲述("这个 agent 在这份授权下整理了这个 vault")。
把审批从每次调用改为每个任务,只有保留两项补偿性控制才安全:每次调用的进入和退出都记入审计日志;撤销只需一次操作,且立即生效,撤销 ticket 时,其所有成员也立即撤销。审批粒度、审计密度和撤销速度三者需要权衡;1.0 保持审计密度和撤销速度不变,才得以放宽审批粒度。
1.0 交付完整的工牌,门票则以原型形态交付:任务 bundle。 一个 bundle 是 N 条共享 bundleId 标签的普通常驻授权——一次批准、一次撤销、附带上下文,不构成新的权限类别。它尚未拥有的是生命周期:没有开票/关票,没有任务边界,没有门票级的叙述。那个对象被推迟了(ADR-020)——下面这些接缝就是它未来的建材。1.0 管理控制台暂不呈现 bundle 创建界面——端点与 grant-service 耦合作为保留的机制交付,bundle 成员就作为一条普通常驻授权显示——待门票生命周期落地后再把这层界面重新引入。
接缝——1.0 锁定了什么
S1 — 授权行删除后仍保留授权包关联键
撤销时会按设计删除授权行,确保刷新无法重新签发。哪项任务下授予了哪些权限,由审计日志持久记录,而非授权存储。因此,每个授权生命周期审计事件都会记录关联键 bundleId,并在删除授权行之前完成记录:
| 生命周期阶段 | 事件携带 |
|---|---|
| agent 请求一个具名 bundle | grant.pending + bundleId + bundleName |
| 人工批准待审批请求 | grant.allow + bundleId + bundleName |
| 成员在范围内的普通再铸请求 | grant.allow + bundleId(绝不悄悄脱离 bundle) |
| 管理员一步建 bundle | grant.allow + bundleId + bundleName |
| 按对 / 按 agent / 按 bundle 撤销 | grant.revoke + bundleId(删除前捕获) |
账本中该授权包的行全部删除之后,仍可仅凭审计日志回放任务授权包的完整过程:pend → allow → re-mint → revoke。审计日志采用仅追加的 JSONL 格式,默认保留 90 天;部署若需回放更久的历史,只需延长保留时间,其他部分无需改动。
S2——Attribution 预留了企业级的"谁/为何"字段
每个审计事件都可携带 Attribution:agent 表示谁执行了操作,principal? 表示代表谁执行(企业场景),grantRef? 表示哪项授权允许了这次操作,policyRef? 表示哪条策略规则作出了决定(企业场景)。这些字段全部可选,单网关 1.0 部署既不设置,也不读取它们。未来的工单/策略层可填入这些字段,无需改变事件结构。
S3 — 信任窗口可新增类型,保持兼容
TrustWindowKind 是一个封闭联合类型:once | 1h | 1d | 7d | until-revoked | custom,到期判断只有一个入口。将来新增类型(例如 until-task-closed,由事件而非时间戳决定到期),只需对协议作增量扩展、提升次版本号,再在一个函数中加一个分支。已有授权、协议客户端和管理界面都能继续正常工作:新类型在开始提供之前不会出现。
S4 — 授权器提供可替换的策略接口
授权决策由 Authorizer 接口的具体实现负责(ADR-007:“更换策略无需改动通信协议”)。1.0 提供两种实现:auto-approve 用于受信任的管理员路径,confirm-risky 是默认的待审批策略。未来,企业策略引擎可作为第三种实现接入,根据工单、角色或组织规则评估授权,并通过 policyRef(S2)标明作出决策的策略,通信协议无需任何改动。
S5——门票永远不能放大权限
无论未来的门票对象长成什么样,它都继承 bundle 的结构性规则:分组不赋予超出其成员的任何权限——每个成员都是一条走过普通审批闸门的普通常驻授权,整组随 connection-key 轮换一起失效,与任何授权无异。门票是一个同意与叙述对象,永远不是与授权、token、暴露并列的第四种权限类别。有效访问始终是已授权 ∧ 已暴露,按 capability 计,在管线里实时强制执行。
门票之内的 execute——已部分回答(ADR-023)
execute → once 最初被定为结构性限制:execute capability 不能使用长期授权,即使管理员设置了信任窗口也不例外(信任模型)。这与完整的 ticket 模型有冲突:"organize this vault" 可能合理地包含一次 claudecode.run 调用。如果每次 execute 仍需单独审批,ticket 就无法完整承载任务级授权;但悄悄放宽限制,又会留下安全漏洞。
ADR-023 回答了其中一部分:它把 execute → once 从绝对限制改成了可由所有者覆盖的默认规则——所有者可以为特定 agent 的特定 execute 能力启用长期授权(默认关闭,连接时需双重确认)。这项所有者覆盖机制满足了下文第一条约束所要求的严格、主动启用条件;ticket 对象本身仍未实现。未来的 ticket 方案仍须满足以下约束:
- agent 不能自行选择或提权获得长期 execute 授权,这也不是默认设置。只有 owner 在收到警告后,针对特定 agent 的特定 capability 主动开启,才允许长期授权(ADR-023)。默认行为不变:每次 execute 仍需等待审批。
- 任何放宽都必须按 capability 逐项自愿开启(由拥有者声明常驻 execute 对某一个 agent 可行),绝不是门票的通用特权。
- ticket 内的 execute 必须受有限时间窗口约束,不能通过
until-revoked超出这一期限;除非 owner 在针对该 capability 主动开启长期授权时,明确选择了until-revoked。
未来的门票对象会补上什么(草图,非规范)
未来可通过上述预留机制加入以下内容,无需迁移:
- 生命周期——开票(批准动作)→ 生效中 → 关票(用户动作、任务结束或窗口过期)。"关闭"是新增的状态;由 S3 作为一种窗口类型承载。
- 边界——任务的范围约束 + 上下文,bundle 今天已经把它们编成组。
- 呈现——Grants 标签页和审计视图按 ticket 分组:每个任务一张卡片,成员列在卡片内,完整调用历史通过 S1 串联。
这些都不在 1.0 的工作范围内。本页只保证:将来实现时,组合已有机制即可,无需重构。