多端系统的权限判定通常由三处分别实现:前端界面以条件分支硬编码菜单与按钮的显隐; 后端接口以中间件配合令牌与角色判断是否放行;面向 AI 的工具注册表同样以条件分支硬编码可调用的操作。
三处代码各自演进,由此产生两类可复现的缺口:界面已隐藏入口,而接口未加对应保护; 接口返回拒绝,而工具清单仍向调用方建议该操作。两类缺口的共同点在于判定依据不止一套, 差异只在被哪一处先行暴露。
EAMX 的处理方式是把判定依据收敛为一处:packages/core/src/ability.ts
导出同一个能力对象类型 AppAbility,前端、接口与工具注册表均以该类型为准。
判定规则不随调用位置变化。
权限判定一处定义、三处消费:定义在 ability.ts,消费在界面、接口与工具清单。
三处之间的差异由此从实现问题转为配置问题。
能力定义由两个基本量组成:动作(Action)表示可以执行的操作,资源(Subject)表示操作所指向的对象。
当前版本的 ability.ts 定义 15 个动作与 26 类资源,两者的组合构成权限的上限范围,
共 15 × 26 计 390 种。
| 分组 | 动作 | 覆盖的操作 |
|---|---|---|
| 记录操作 | read · create · update · delete | 对记录本身的读取与增删改 |
| 通用管理 | export · manage | 数据导出与配置管理 |
| 派发与开工 | grab · dispatch · start | 抢单、指派执行人、开工 |
| 完工与关闭 | complete · accept · close | 完工报验、验收、关闭 |
| 指派与审批 | assign · approve | 指派责任人与审批处理 |
| 对话能力 | use | 使用受权限约束的对话式能力 |
动作的划分并不止于增删改查。工单的生命周期被拆分为抢单、派工、开工、完工、验收、关闭等独立动作,
其作用是让权限粒度足以区分「查看全部工单」与「从公池抢单」:前者为 read:workorder,
后者为 grab:workorder,两者可以分别授予同一角色。该区分由动作定义承担,不依赖界面处理。
资源一侧覆盖工单、故障报告、设备、备件、库存、采购申请、技术标准、保养计划、对话记录, 以及部门、用户、角色、租户等组织与账号对象,共 26 类。
依据:packages/core/src/ability.ts 的类型定义(Actions 与 Subjects)。
角色在配置层以权限字符串保存,格式为「动作 : 资源」,资源键为小写。
能力判定使用 PascalCase 资源类型,两者之间的映射由 SUBJECT_KEY_MAP 维护。
左侧为配置层存储形式,右侧为构建能力对象时产生的规则。 两者的对应关系集中在单一映射表中,新增资源类别只需在该表登记一次。
解析由 resolvePermission 完成:以冒号切分字符串取出动作与资源键,再经
SUBJECT_KEY_MAP 映射为资源类型;映射失败的权限字符串返回空值,在构建阶段被丢弃。
这一处理方式决定了配置层的安全性:写错的权限字符串会失效,而不会因无法识别被放宽为通配。
依据:resolvePermission 与 SUBJECT_KEY_MAP 的实现。
运行时由 buildAbilityFromPermissions 把权限列表构建为能力对象,
入参为权限列表与可选项:调用方是否为平台超管,以及权限对应的数据条件。
can("manage", "all"),不再逐条解析权限列表。resolvePermission 得到动作与资源类型。permissionConditions 中有对应条目时,以带条件的形式构建规则;无对应条目时以无条件形式构建。AbilityBuilder 汇总全部规则后返回 AppAbility。该文件的长度为 132 行,承担两件事:类型定义决定判定可用的动作与资源范围, 构建过程决定请求上下文中的实际规则集合。同一份权限列表在三处消费时得到同一结果, 因此界面显示与接口放行之间不存在配置差。
角色在系统内的授权记录只有一份。 界面、接口与工具清单由同一份记录生成,三者之间的差异只可能来自数据条件的取值,不可能来自判定口径。
能力判定的依据为五个维度:组织、角色、资源类型、动作类型、数据范围。 同一套判定在三条通道上适用,判定结果不因请求来自界面、内嵌助手或外部 Agent 而不同。
此处需要说明一处口径差异。源文以「三维数据范围」描述平台范围、部门范围与本人范围三档, 指的是「数据范围」这一维度的三种取值。本文按站内其余页面的口径,把五个判定维度与数据范围的三档取值分开表述: 前者是判定的依据,后者是其中一项依据的取值。两种说法描述的是同一套设计,不构成冲突。
| 取值 | 可见范围 | 规则形式 | 典型角色 |
|---|---|---|---|
all | 平台范围内的全部数据 | can("manage", "all") | 平台超管 |
department | 本部门范围内的数据 | can("read", "WorkOrder", { departmentId }) | 部门管理员 |
self | 本人负责的数据 | can("read", "WorkOrder", { responsibleUserId }) | 普通维修工 |
数据范围以 CASL 的条件权限实现:授予以数据字段为条件的规则,判定时以记录自身的字段值与条件比对。 部门管理员的规则以本部门标识为条件,普通维修工的规则以本人标识为条件, 由此同一动作对不同角色的可见数据集自然不同,无需为每类角色单独编写查询分支。
该机制带来一处需要留意的实现差别:在缺少数据上下文的纯前端环境中,带条件的判定不成立;
条件本身在后端被取出并注入数据库查询,形成 SQL 的 WHERE 子句。
前端由此只承担显隐,实际放行以后端注入条件后的查询结果为准。规则来源相同,两端承担的角色不同。
依据:rulesFor 的返回结构与后端查询注入的实现记录;三档取值分别对应平台超管、部门管理员与普通员工。
以上结构得出一个可以直接陈述的结论:权限判定在统一执行管道内部完成。
executeAction 是唯一的写入口,参数校验、权限裁决、审批校验与审计写入均为该入口内部的环节,
与调用方是 Web 控制台还是外部 Agent 无关。这是「同构」一词在本系统中的确切含义。
由此,「Agent 拥有特权」与「Agent 可绕过审批」两种情况在结构上不成立: 外部 Agent 没有独立的判定路径,也没有可以跳过的环节。它进入的是与人工操作相同的管道, 适用相同的能力对象与相同的判定维度。
| 通道 | 使用者 | 身份来源 | 权限判定 |
|---|---|---|---|
| Web 控制台 | 业务人员 | cookie JWT | 同一套能力对象,五维度判定 |
| 内嵌 AI 助手 | 系统内的对话式操作 | 请求级上下文 | 同一套能力对象,五维度判定 |
| MCP Server | 外部 Agent | API Key | 同一套能力对象,五维度判定 |
三条通道的身份来源不同,进入管道后适用同一套规则。身份来源的差别决定「以谁的名义执行」, 不决定「可以执行什么」。API Key 在系统内按角色签发,权限随该角色确定,集团与各单位的可见范围按上述维度分别计算。
「Agent 不绕过审批」在本系统中并非一项需要事先约定的承诺,而是架构结果:不存在可绕过的路径。 权限即契约,系统内没有第二套判定口径。
依据:executeAction 管道的环节划分与 AppAbility 的三处共用方式。通道与接入方式的完整说明见 AI 原生与 Agent 接入。
同一份能力对象在三处消费。消费方式各有差别,判定依据相同。
| 层级 | 消费方式 | 实现位置 |
|---|---|---|
| Web 控制台 | ability.can(action, subject) 决定按钮与菜单的显隐 | 共享的 AppAbility 实例 |
| 后端接口 | 中间件以同一能力对象裁决,并按规则注入数据条件 | API Server |
| 工具清单 | getAuthorized(ability) 按权限裁剪工具清单 | Tool Factory |
工具清单的裁剪方式值得单独说明。工具注册表提供按能力过滤的方法: 工具在注册时标注所需的动作与资源,取清单时逐条比对; 未标注权限要求的工具默认向全部调用方开放。
因此工具清单在服务端按账号权限生成:调用方拿到的清单即为服务端的授权结果。 生成时未列入的动作不会出现在调用方的可调用集合中,也就不存在调用时的拒绝。 这一差别决定了权限边界的位置:界面隐藏入口属表现层处理,绕过界面仍有试探空间; 清单裁剪属结构性限制,客户端没有可试探的对象。
口径:截至本文发布,动作契约共 247 条,覆盖 35 个业务域;演示账号可见的工具由端点实时返回。该清单可由演示环境实时导出并核对。
说明上述结构之后,需同时说明本文的范围。以下三项属系统自身的约束与不作出的承诺, 在能力说明之前列出。
| 事项 | 说明 |
|---|---|
| 哪些动作不得自主执行 | 改账、报废、权限变更等高风险动作在动作定义上标注需人工确认,未携带确认的调用一律拒绝执行 |
| 只读角色不可见什么 | 写入类动作不在该角色的清单内。边界由清单生成时的裁剪划定,不依靠调用时的拦截 |
| 关于标准与认证 | 本页所述为系统自身的设计约束,不构成对任何标准或认证的符合性认定。认证对象是组织,而非软件 |
另有一项属性能范畴,本文不下结论:识别与判断的准确率,以及无人值守的程度。 本文所述内容集中于权限判定的结构与边界,与上述两项指标无关。
packages/core/src/ability.ts(132 行)中的类型定义与构建过程;本系统的权限与审计设计实现记录五个判定维度、工具清单的裁剪方式、审计口径的同一性——上述各项均无需采信本文陈述, 可由演示环境当场核对,或由贵方自有客户端报告可见工具数量。