# ADR-001 角色权限模型:权限点目录、特殊权限、SoD 与六类数据范围 | 项 | 内容 | | --- | --- | | 状态 | 已接受(2026-11-03,H1 权限门用户确认) | | 决策人 | 产品 / 技术负责人(用户) | | 记录人 | 工程 Lead | | 关联 | 开发计划 V0.1 §3.4 H1 门、§6.3 A2、§7.2、§10.3、§13 DP-14;技术架构 V0.4 §5.5 / §6.1–6.3 / §7.1 / §8.7;PRD V1.6 §5.4.3、R-16~R-18 | | 影响范围 | `packages/shared` 权限点与数据范围契约、`identity` 模块、`prisma` 迁移与种子、`apps/web` 角色管理页、A3 客户模块、后续所有受保护端点 | ## 背景 A2「角色权限与数据范围」开卡前必须完成 H1 权限门(开发计划 §3.4):确认权限点目录、六类数据范围、SoD 互斥对、特殊权限授予规则,否则权限模型返工会影响全部页面。 现状与文档存在差异: - `packages/shared/src/permissions.ts` 是 A0 骨架,共 25 个 `read/write` 风格编码;缺少 `role:view`、`customer_field:apply`、`customer:transfer`、`customer_group:manage`、`asset:delete`。 - 开发计划附录 B 使用 `view/manage` 风格并列出更多编码;但其自身注明「最终以 `packages/shared` 为准」。 - PRD §5.4.3 的权限粒度更细(如图片生成 / 视频生成拆开),完整实现会显著增加权限点数量与配置成本。 - A3 客户模块尚未开发,而六类数据范围中的「指定客户组 / 负责客户 / 本人创建」依赖客户与客户组数据。 ## 决策 ### D1 权限点目录:保留现有编码,只增不改 以 `packages/shared` 现有 25 个编码为基线,补齐 A2 必需的 5 个编码: `role:view`、`customer_field:apply`、`customer:transfer`、`customer_group:manage`、`asset:delete`。 合计 **30 个权限点 / 10 个模块**。命名保持 `<模块>:<动作>` 与 `read/write` 动作词;后续新增权限点通过代码目录注册并同步 `permissions` 表,**不手写迁移**。 ### D2 特殊权限:M0 五项 6 编码 | 特殊权限 | 编码 | | --- | --- | | 导出 | `delivery:export` | | AI 配置 | `ai:configure` | | 审计查看 | `audit:read` | | 审计导出 | `audit:export` | | 行业包发布 | `industry:publish` | | 字段审批 | `customer_field:approve` | 规则:默认关闭、单独授予、不随角色复制带入、授予与撤销二次确认并写入审计。PRD 中的「跨客户复用授权」「批量删除」按开发计划 DP-14 留到 M2/M3,届时先回写技术架构并补编码,**A2 不实现**。 ### D3 SoD 三对互斥:按现有编码映射 | 互斥对 | 编码 | | --- | --- | | 脚本生成与审核 | `script:write` × `script:review` | | 行业包编辑与发布 | `industry:manage` × `industry:publish` | | 字段申请与审批 | `customer_field:apply` × `customer_field:approve` | 保存角色时校验,命中返回 `40304`;仅角色级校验(同一角色不可同时持有),超级管理员豁免。用户通过多个角色组合持有的情况不在 A2 校验范围(PRD 表述为「不可同属一个角色」)。 ### D4 六类数据范围:全链路实现,客户集合通过端口隔离 A2 建立 `data_scopes` 表、六类范围解析引擎、`ScopeContext` 注入链路与 Repository 客户维度强制参数;角色保存时持久化范围类型与目标(指定客户组可多个)。 客户集合解析通过 `CustomerDirectory` 端口完成: - A2 实现 `EmptyCustomerDirectory`,在 A3 客户模块落地前返回空集; - 测试使用 fake 客户目录覆盖非空场景; - A3 接入真实 `customers` / `customer_groups` 后替换实现,`ScopeContext` 与 Repository 契约不变。 失败处理:数据范围解析失败一律 403,**不降级为「全部客户」**。多角色取并集;任一角色为「全部客户」则结果为全部。 ### D5 超级管理员:隐式全集 `is_system = true` 的角色自动拥有全部权限点、全部特殊权限与全部数据范围,不依赖、也不可编辑 `role_permissions` / `data_scopes` 记录;仅显示名可改,不可删除、不可停用。 角色数量 > 20、超管人数 > 2 仅做非阻断提示(PRD 为「建议值」)。相关建议值当前以共享常量表达,A4 系统配置落地后迁入 `system_configs`(R-26 单一存放处)。 ### D6 变更审计:A2 结构化日志,A5 落库 角色变更(新建 / 编辑 / 复制 / 停用 / 删除 / 特殊权限授予)记录结构化日志:操作人、账号、IP、trace_id、目标角色、权限点前后值、特殊权限前后值、影响用户数。 同时写 `outbox_events`(`role.permissions.changed`,payload 含受影响用户 ID)作为「受影响用户通知」占位,消费方随 A5/A6 接入。审计表落库、检索与导出由 A5 完成;`@Audit` 装饰器签名保持不变。 ## 后果 ### 正面 - A1 已实现代码零迁移,A2 可在现有 25 编码上平滑扩展。 - 六类范围与 ScopeContext 一次建好,A3 只需实现 `CustomerDirectory` 与客户表,避免范围逻辑二次返工。 - 超管隐式全集避免新增权限点后超管缺权,减少权限事故。 - 权限目录代码化 + 启动同步,新增权限点无需手写迁移。 ### 代价与约束 - 权限点命名保留 `read/write`,与开发计划附录 B 的 `view/manage` 不一致;后续文档引用以 `packages/shared` 为准,附录 B 需在下一版修订时对齐(不改语义)。 - A2 的 `客户组 / 负责客户 / 本人创建` 三类范围在 A3 前无可选目标,UI 需提供空态;A2 验收以解析引擎与 fake 客户目录单测为准,不等同于真实客户隔离验收。 - 「审计查看」作为特殊权限后,普通角色默认无法查看审计;符合 PRD「审计单独授权」原则。 - 权限点数(30)与 PRD 操作级粒度仍有差距,图片 / 视频生成等细分待 M1 前按需补充,遵循「只增不改」。 ## 待办 / 回写 | 项 | 责任 | 时点 | | --- | --- | --- | | 开发计划附录 B 权限点表与 ADR-001 对齐(命名/增补) | 产品 / 架构 | A2 收口时 | | 技术架构 §6.3 补 `role:view`、`customer_field:apply` 等编码说明 | 架构 | A2 收口时 | | A3 实现 `CustomerDirectory` 真实客户目录并补范围验收 | 研发 | A3 | | DP-14 两项特殊权限编码与架构回写 | 架构 | M2/M3 前 | | 角色数量 / 超管数量建议值迁入 `system_configs` | 研发 | A4 | | 审计落库、检索、导出 | 研发 | A5 |