diff --git a/docs/superpowers/specs/2026-04-26-multi-user-rbac-skill-governance-design.md b/docs/superpowers/specs/2026-04-26-multi-user-rbac-skill-governance-design.md new file mode 100644 index 0000000..9ab1cb3 --- /dev/null +++ b/docs/superpowers/specs/2026-04-26-multi-user-rbac-skill-governance-design.md @@ -0,0 +1,414 @@ +# Boss 多用户权限与 Skill 治理设计 + +目标:把当前偏单管理员视角的 Boss 控制台升级为多用户、多设备、多权限的协作控制平台,让超级管理员可以安全地把不同电脑、项目线程、主 Agent 能力和 Skill 分配给不同子账号。 + +## 背景与现状 + +当前 Boss 已经具备几块可复用基础: + +- 登录会话与账号体系已经存在,角色包含 `member / admin / highest_admin`。 +- 设备模型已有 `account` 字段,可表达设备归属。 +- 会话、项目、线程、设备和 Skill 清单都已经在 `BossState` 里统一持久化。 +- 设备 Skill 已可由 `local-agent` 从本机 `~/.codex/skills` 上报到 Boss。 +- 主 Agent 已能读取设备、项目、线程、记忆和任务状态,并通过本地节点执行对话与开发任务。 + +当前缺口不是“新增一个用户列表页面”,而是缺少正式的授权模型: + +- API 多数只校验是否登录,没有统一过滤当前用户可见设备和项目。 +- 子账号是否能看某台电脑、某个项目、某条线程,当前主要靠设备 `account` 粗略判断。 +- Skill 只有设备上报清单,没有“谁可以使用哪个 Skill”的授权层。 +- 主 Agent 构造上下文时还没有强制按当前用户权限裁剪,存在未来多用户场景下越权总结的风险。 +- 设备执行任务时还没有把 `userAccount + deviceId + projectId + skillId` 作为统一授权输入。 + +## 产品目标 + +### 超级管理员 + +超级管理员可以: + +- 创建、禁用、修改子账号。 +- 分配子账号可见和可控的电脑。 +- 查看所有电脑上的项目、线程和进展。 +- 通过主 Agent 汇总任意授权范围内的项目状态。 +- 给用户或设备分配可用 Skill。 +- 查看权限变更、Skill 执行、Computer Use 等高风险操作的审计日志。 + +### 子账号 + +子账号只能: + +- 看到自己被授权的电脑。 +- 看到这些电脑下被授权的项目和线程。 +- 在授权范围内与线程或主 Agent 对话。 +- 使用被授权的 Skill。 +- 在获得 `computer.control` 权限时,才能通过主 Agent 或线程触发电脑控制能力。 + +### 主 Agent + +主 Agent 必须: + +- 只看当前用户有权访问的设备、项目、线程、Skill 和进展事件。 +- 在用户权限范围内回答问题、汇总进展或协调开发。 +- 不能因为超级管理员存在就默认给普通子账号展示全局上下文。 +- 在任务执行前带上当前用户身份,方便服务端和 local-agent 双重校验。 + +## 角色与能力模型 + +继续保留三类角色,但角色只定义默认上限,真实能力由授权决定: + +- `highest_admin`:全局最高权限,可管理所有账号、设备、项目、Skill 和审计日志。 +- `admin`:范围管理员,只能管理被分配范围内的设备、项目和成员。 +- `member`:普通成员,只能使用被授予的能力。 + +第一阶段定义以下权限能力: + +- `device.view`:查看设备状态。 +- `device.manage`:修改设备名称、执行模式、备注和授权。 +- `project.view`:查看项目、线程、项目目标、版本记录和进展。 +- `thread.chat`:向授权线程发送消息。 +- `master_agent.ask`:向主 Agent 提问,让主 Agent 在授权范围内总结或答疑。 +- `master_agent.takeover`:让主 Agent 接管授权线程或项目。 +- `computer.control`:触发 CLI、GUI、Browser、Computer Use 等电脑控制能力。 +- `skill.view`:查看设备上的 Skill 清单。 +- `skill.use`:在授权范围内调用指定 Skill。 +- `skill.manage`:给设备安装、更新或撤销 Skill。 +- `account.manage`:管理子账号和授权。 +- `audit.view`:查看权限与执行审计日志。 + +权限默认规则: + +- `highest_admin` 默认拥有全部能力。 +- `admin` 默认没有全局能力,只能管理授权范围内资源。 +- `member` 默认无设备、项目、Skill 权限,必须显式授权。 +- 用户被授权某台设备后,默认继承该设备下项目和线程的只读可见权。 +- 线程聊天、主 Agent 接管、电脑控制、Skill 调用必须显式授权,不随只读可见自动开启。 + +## 授权数据模型 + +第一阶段继续使用 `data/boss-state.json` 文件存储,不立即引入数据库迁移。新增模型放入 `BossState`: + +```ts +type BossPermission = + | "device.view" + | "device.manage" + | "project.view" + | "thread.chat" + | "master_agent.ask" + | "master_agent.takeover" + | "computer.control" + | "skill.view" + | "skill.use" + | "skill.manage" + | "account.manage" + | "audit.view"; + +interface AccountDeviceGrant { + grantId: string; + account: string; + deviceId: string; + permissions: BossPermission[]; + grantedBy: string; + grantedAt: string; + expiresAt?: string; + note?: string; +} + +interface AccountProjectGrant { + grantId: string; + account: string; + projectId: string; + deviceId?: string; + permissions: BossPermission[]; + inheritFromDeviceGrant?: boolean; + grantedBy: string; + grantedAt: string; + expiresAt?: string; + note?: string; +} + +interface AccountSkillGrant { + grantId: string; + account: string; + skillId: string; + deviceId?: string; + projectId?: string; + permissions: BossPermission[]; + grantedBy: string; + grantedAt: string; + expiresAt?: string; + note?: string; +} + +interface SkillCatalogEntry { + skillId: string; + name: string; + description: string; + sourceType: "gitea" | "skillhub" | "local"; + sourceUrl?: string; + version?: string; + checksum?: string; + category?: string; + updatedAt: string; +} + +interface PermissionAuditLog { + auditId: string; + actorAccount: string; + action: + | "grant.created" + | "grant.updated" + | "grant.revoked" + | "skill.assigned" + | "skill.revoked" + | "task.authorized" + | "task.denied"; + targetAccount?: string; + deviceId?: string; + projectId?: string; + skillId?: string; + permissions?: BossPermission[]; + detail?: string; + createdAt: string; +} +``` + +## 权限判断规则 + +新增统一权限模块,例如 `src/lib/boss-permissions.ts`,所有 API、投影、主 Agent 和任务路由都调用它: + +- `isHighestAdmin(session)`:最高管理员直接通过。 +- `canAccessDevice(state, session, deviceId, permission)`:判断设备权限。 +- `canAccessProject(state, session, projectId, permission)`:判断项目/线程权限。 +- `canUseSkill(state, session, skillId, { deviceId, projectId })`:判断 Skill 使用权限。 +- `filterDevicesForSession(state, session)`:设备列表过滤。 +- `filterProjectsForSession(state, session)`:会话和项目列表过滤。 +- `buildAuthorizedMasterAgentScope(state, session)`:主 Agent 上下文裁剪。 + +项目继承规则: + +- 如果用户有某设备的 `device.view`,则默认可见该设备下项目。 +- 如果某项目绑定多个设备,只要用户有其中一台设备的 `device.view`,即可看到该项目的基础摘要。 +- 多设备群聊项目中,用户只能看到自己授权设备相关的参与线程和消息;全量群聊汇总需要项目级 `project.view`。 +- `thread.chat` 不从 `device.view` 自动继承,必须来自设备授权或项目授权里的显式能力。 + +## API 改造范围 + +第一阶段优先改这些接口,保证后端权限真的生效: + +- `GET /api/v1/devices` + - 最高管理员返回所有设备。 + - 子账号只返回授权设备。 + +- `GET /api/v1/conversations` + - 最高管理员返回所有会话。 + - 子账号只返回授权项目/线程。 + +- `GET /api/v1/projects/[projectId]` + - 无 `project.view` 返回 403。 + - 多设备项目按权限裁剪参与线程。 + +- `POST /api/v1/projects/[projectId]/messages` + - 需要 `thread.chat` 或 `master_agent.ask`。 + - 如果消息触发接管或电脑控制,还需要额外权限。 + +- `GET /api/v1/devices/[deviceId]/skills` + - 需要 `skill.view`。 + - 返回当前用户可见 Skill,而不是设备全部 Skill。 + +- `POST /api/v1/master-agent/tasks/claim` + - local-agent 领取任务前校验任务绑定设备和用户授权。 + +- `POST /api/v1/master-agent/tasks/[taskId]/complete` + - 写回消息时保留 `requestedByAccount`,便于审计和前台展示。 + +新增管理接口: + +- `GET /api/v1/admin/access/accounts` +- `POST /api/v1/admin/access/accounts` +- `GET /api/v1/admin/access/grants` +- `POST /api/v1/admin/access/grants` +- `PATCH /api/v1/admin/access/grants/[grantId]` +- `DELETE /api/v1/admin/access/grants/[grantId]` +- `GET /api/v1/admin/skills/catalog` +- `POST /api/v1/admin/skills/catalog/sync` +- `POST /api/v1/admin/skills/assignments` +- `DELETE /api/v1/admin/skills/assignments/[grantId]` +- `GET /api/v1/admin/audit-logs` + +这些管理接口第一阶段只允许 `highest_admin` 调用。后续再开放给具备 `account.manage` 或 `skill.manage` 的范围管理员。 + +## 主 Agent 改造 + +主 Agent 的输入上下文必须从“全局状态”改成“授权范围视图”: + +- `highest_admin` 仍可看到所有设备、项目、线程、Skill。 +- 子账号只注入授权设备、授权项目、授权 Skill、授权进展事件。 +- 主 Agent 回复里如果用户要求查看未授权资源,应明确说“当前账号没有权限查看这台电脑或项目”。 +- 主 Agent 发起任务时必须写入: + - `requestedByAccount` + - `authorizedDeviceIds` + - `authorizedProjectIds` + - `authorizedSkillIds` + - `requiredPermissions` + +任务执行前的双重校验: + +- 服务端创建任务前校验当前用户权限。 +- local-agent 领取任务时再校验该任务是否发给本设备,以及 Skill 是否被授权给请求用户。 + +## Skill 治理方案 + +不把 SkillHub 作为第一阶段权限核心。第一阶段采用: + +- Skill 源:Gitea 私有仓库,例如 `krisolo/codex-skills`。 +- 安装位置:各电脑本机 `~/.codex/skills`。 +- 上报方式:local-agent 周期扫描并上报 `DeviceSkill[]`。 +- 授权位置:Boss 的 `AccountSkillGrant`。 +- 执行校验:Boss 和 local-agent 双重检查 `skill.use`。 + +SkillHub 接入作为第二阶段: + +- 抽象 `SkillSource`,支持 `gitea / skillhub / local`。 +- 如果启用 SkillHub,Boss 从 SkillHub 拉取 Skill catalog。 +- Boss 仍负责账号、设备、项目和 Skill 授权。 +- SkillHub 只做 Skill 注册、版本、搜索和包分发,不直接决定 Boss 用户权限。 + +这样即使将来替换 SkillHub,也不会影响 Boss 的权限模型。 + +## Android 产品改造 + +第一阶段 Android 只做必要闭环,不做复杂企业后台: + +- 我的页面新增或改造“账号与权限”入口。 +- 超级管理员可进入: + - 子账号列表 + - 设备授权 + - Skill 授权 + - 审计日志 +- 子账号看到: + - 当前账号角色 + - 已授权设备 + - 已授权 Skill + - 无权限时的清晰提示 +- 会话页和设备页只显示服务端返回的授权内容,不在前端自行拼全量数据。 +- Skill 页面从“按设备查看本机 Skill 清单”升级为“按授权查看可用 Skill”。 + +## Web 产品改造 + +Web 第一阶段作为管理端主入口: + +- `/me/access`:账号与权限总入口。 +- `/me/access/accounts`:子账号管理。 +- `/me/access/grants`:设备/项目/线程授权。 +- `/me/access/skills`:Skill 分配。 +- `/me/access/audit`:审计日志。 + +权限管理 UI 不追求复杂,优先做可理解、可验证: + +- 选择账号。 +- 勾选设备。 +- 勾选权限能力。 +- 可选限制到项目或 Skill。 +- 保存后立即写审计日志。 + +## 数据迁移策略 + +为了不破坏现有单用户使用: + +- 首次升级时自动给 `highest_admin` 全量权限,不需要写入显式 grant。 +- 当前已有设备保持原样。 +- 当前已有子账号如果没有 grant,则只能看到自己 `account` 匹配的设备。 +- 新增授权模型后,旧的 `device.account === session.account` 作为兼容 fallback。 +- 等多用户授权稳定后,再逐步把旧 fallback 收敛为显式 grant。 + +## 安全与审计 + +必须记录审计日志的动作: + +- 创建、修改、禁用账号。 +- 授予或撤销设备权限。 +- 授予或撤销项目/线程权限。 +- 授予或撤销 Skill。 +- 触发 `computer.control`。 +- 主 Agent 因权限不足拒绝请求。 +- local-agent 因权限不匹配拒绝任务。 + +敏感数据规则: + +- Skill 仓库 token、API Key、SSH 密钥不进入 `boss-state.json`。 +- 设备 token 继续只用于设备写入认证。 +- Skill 的安装源可以存仓库地址,但不能存私钥。 +- 审计日志不得记录完整 API Key、密码、Cookie 或系统提示词。 + +## 测试策略 + +后端单测: + +- 最高管理员可见所有设备和项目。 +- 子账号只能看到授权设备。 +- 设备授权自动带来项目只读可见。 +- 没有 `thread.chat` 时不能发线程消息。 +- 没有 `skill.use` 时不能调用 Skill。 +- 主 Agent 上下文不会包含未授权项目。 +- 权限变更写入审计日志。 + +Android 单测: + +- 子账号会话列表只渲染授权会话。 +- 无权限页面显示明确提示。 +- 超级管理员可以看到账号与权限入口。 +- 普通成员不能看到授权管理入口。 +- Skill 页面只显示授权 Skill。 + +集成验证: + +- 用超级管理员创建子账号。 +- 给子账号只授权一台设备。 +- 子账号登录后只能看到这台设备和相关项目。 +- 子账号问主 Agent 全局状态时,主 Agent 只总结授权范围。 +- 子账号尝试控制未授权设备时返回 403。 +- 授权 Skill 后,子账号能看到并使用;撤销后立即不可用。 + +## 分阶段交付 + +### 第一阶段:权限底座可用 + +- 新增权限数据结构。 +- 新增 `boss-permissions` 模块。 +- 改造设备、会话、项目、消息、Skill 相关 API 的权限过滤。 +- 主 Agent 上下文按授权范围裁剪。 +- Android 和 Web 显示不越权。 + +### 第二阶段:授权管理 UI + +- Web 端完成账号与权限管理。 +- Android 端完成基础查看和简单授权入口。 +- 审计日志可查看。 + +### 第三阶段:Skill 分发治理 + +- Gitea 私有 Skill 仓库同步。 +- Boss Skill catalog。 +- 按用户/设备/项目分配 Skill。 +- local-agent 执行前校验 Skill 授权。 + +### 第四阶段:SkillHub 可选接入 + +- 增加 SkillHub source adapter。 +- 同步 SkillHub catalog。 +- 保持 Boss 权限为最终裁决。 +- 支持 Gitea 与 SkillHub 并存。 + +## 不做范围 + +第一阶段不做: + +- 组织架构树、部门、复杂审批流。 +- PostgreSQL 迁移。 +- 完整 SkillHub 私有部署。 +- 多租户账单。 +- 外部 SSO。 +- 细到文件级的代码仓库权限。 + +这些可以后续接入,但不能阻塞当前多用户设备控制模型落地。 +