Merge pull request 'Compose permission UX matrix, batch roles and member config (permux U1-U6)' (#629) from feature/iam-role-permission-linked-capabilities-perm-ux into feature/iam-role-permission-linked-capabilities
This commit was merged in pull request #629.
This commit is contained in:
@@ -0,0 +1,612 @@
|
||||
# feature/iam-role-permission-linked-capabilities-perm-ux 任务卡
|
||||
|
||||
任务主记录:PR #629 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/629;状态:已完成(合入前闭合);owner:weichen
|
||||
|
||||
- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 7716b28cfff5200e7cfe9ec75aff5bcd71b234ed
|
||||
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux(worktree);分支:feature/iam-role-permission-linked-capabilities-perm-ux
|
||||
- 分支类型:child;initiative_id:feature-iam-role-permission-linked-capabilities;root_target:main;immediate_target:feature/iam-role-permission-linked-capabilities;accepting_requirements:false;parent_branch:feature/iam-role-permission-linked-capabilities;parent_head_sha:90317d02a746ea3cd25021561ef40e8cd7498963;parent_pr:无;parent_source:origin
|
||||
- 需求分流:verdict=JOIN;selected_initiative=feature/iam-role-permission-linked-capabilities;requirement_sha256=6eab4bfaa1bb158cd0943427d00264a74059b77b967530dc7d7c32f2f9ada0fa;triage_receipt=/tmp/iam-perm-ux-triage.json;receipt_sha256=3dd33a2bd1a128579bd405339b682acbcd4f96c7b980ebb0941cd5c622ccebb7
|
||||
|
||||
- 授权与交付终点:按源码外方案 `/tmp/iam-permission-ux-plan-20261019-1dehpj7l/plan.md`(2026-10-19 评审稿,review.md 三项审阅 clean)实施 U1–U6:Backend 权限展示目录与关联预览合同、原子批量写入与读投影;Frontend 分类角色矩阵与单角色聚焦、用户/角色人员配置、项目有效权限说明;真实浏览器验收与组合交付。终点:子任务 PR 合入所属 L1 分支 `feature/iam-role-permission-linked-capabilities`(PR #628),组件 pin 随本任务推进;不部署、不迁移生产数据、不做日常实例采用(C16 保持 NOT_RUN 直至另行授权)。
|
||||
- 组件分支策略:Backend 工作分支 `feature/iam-perm-ux-backend`(基于 L1 U1 联动分支 `origin/feature/iam-link-u1-capability-association` @ afb65c5,消费其冻结的 `ProjectCreateLinkedActions`/`LinkedCapabilities` 合同);Frontend 工作分支基于组件 origin/main(6db528d,L1 前端单元尚未启动)。
|
||||
- 依赖协调:#628 L1 的 U1(联动 Backend)已完成并推送(afb65c5);L1 U2(单一角色权威)已登记待启动——其 `perms_api.go`/`hostcaps.go` 与本任务 `roles.go`/`role_policy.go` 不同区段,组合冲突由 L1 集成时解决;本任务不重写 L1 已实现的联动规则,只消费。
|
||||
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
|
||||
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
|
||||
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
|
||||
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
**目标**(方案 R1–R17,用户验收 C01–C16):管理员无需理解技术权限字段即可看懂角色能做什么;默认业务分类矩阵可同时比较并编辑多个角色(三态控件:不允许/按项目授权/默认允许;系统动作仅允许/不允许);单角色聚焦复用同一草稿;统一预览与原子保存;用户列表显示当前角色并支持单人/多人批量角色分配(服务端目录选择器,不手输账号);复制角色并分配;系统自动处理创建项目联动并解释生效范围;项目有效权限说明。
|
||||
|
||||
**非目标**:多职位叠加、个人普通权限覆盖、通用权限引擎、审批系统、Excel 导入导出、批量系统管理授权、全库动态匹配选择;账号恢复/凭据管理(原入口);超级管理员保护(#627);成员共享读取语义(L1 U7);生产部署与日常实例采用。
|
||||
|
||||
## 实施单元(本任务)
|
||||
|
||||
| 单元 | 范围 | 依赖 |
|
||||
|---|---|---|
|
||||
| permux-U1 权限展示与关联预览 | Backend `internal/authz/catalog/`、`internal/server/roles.go`、`internal/store/role_policy.go`、`pkg/extension/role_policy.go`:中文分类目录元数据、三态归一化、变更预览(含 #628 关联差异)、缺失/显式拒绝可区分 | L1 U1 合同(已冻结并实现 @ afb65c5) |
|
||||
| permux-U2 原子批量写入与读投影 | Backend 同上 + `internal/server/account_search.go`:多角色 patch 事务、多用户角色分配事务、复制角色并分配事务;用户目录当前角色投影、角色成员目录 | permux-U1 |
|
||||
| permux-U3 分类矩阵与单角色聚焦 | Frontend `RolePermissionsPanel.tsx`、`rolePermissions.ts`、`api.ts`、样式与测试 | permux-U1/U2 |
|
||||
| permux-U4 用户与角色人员配置 | Frontend `AccountUsersPanel.tsx`、`AdminZone.tsx`、`api.ts`、人员选择器与测试 | permux-U2;与 #627 AccountUsersPanel 修改按顺序合并(#627 未动本分支时直接实施) |
|
||||
| permux-U5 项目有效权限说明 | Frontend `adminRdmSections.tsx` 权限解释区;消费 L1 合同,L1 U3/U7 未组合前显式「联动服务未就绪」,不 mock | permux-U3/U4 + L1 真实规则 |
|
||||
| permux-U6 真实浏览器验收与交付 | `e2e/iam-role-authority.playwright.ts` 扩充、组合 gitlink、Review/CI/合入(host 主导) | 全部 |
|
||||
|
||||
## 需求分析
|
||||
|
||||
| ID | 描述 | 验收 | 结论 |
|
||||
|---|---|---|---|
|
||||
| R1–R7 | 角色配置:分类矩阵、三态控件、联动预览、中文文案(方案 Requirements — Role Configuration) | C01–C06, C11, C14 | 纳入 permux-U1/U3 |
|
||||
| R8–R12 | 人员配置:目录检索、多选批量、复制分配、稳定 userId(方案 Requirements — User Configuration) | C07–C10, C14 | 纳入 permux-U2/U4 |
|
||||
| R13–R17 | 可靠性与验收:原子事务、幂等、固定选择集、可访问性、真实验收(方案 Reliability and Acceptance) | C09, C12, C14, C15 | 纳入 permux-U2/U6 |
|
||||
| C16 | 日常实例采用 | 运行版本核对 | NOT_RUN(未授权部署),单列记录 |
|
||||
|
||||
## 风险与决策记录
|
||||
|
||||
- L1 U2(单一角色权威)与本任务均在途:本任务 Backend 基于联动分支 afb65c5,L1 后续单元合入时由 L1 集成解决;两单元文件区段不重叠(其 perms_api/hostcaps,本任务 roles/role_policy/account_search)。
|
||||
- 「创建项目→自动关联」运行时联动已在 L1 U1 实现(afb65c5);本任务 U1 的预览/展示直接消费 `catalog.ProjectCreateLinkedActions()` 与 `LinkedCapabilities`,不另建第二套关联规则。
|
||||
- L1 U7(成员共享读取)未实现:permux-U5 相关展示显式标注联动服务未就绪,不伪造;验收 C13 由 L1 U7 交付时闭环。
|
||||
|
||||
## Agent 实施收据
|
||||
|
||||
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
|
||||
|
||||
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| permux-U1 | catalog 分类元数据 + PreviewRolePermissions + POST /api/admin/roles/permissions/preview | 76bc59db1d0e9127(AGENT-EXECUTION digest) | implementation | cursor→agy→pi | 1 | cursor(session 204c93fb…) | success(transport 未应用:host 以相对路径调用 run,收据 repo 字段与 apply 校验不兼容;保留现场) | refs/agent-exec/8ac83883…/a433eb6f(未应用) | never | /tmp/permux-u1-run.json |
|
||||
| permux-U1 | 同上(全新会话重跑,调用形式 --repo .) | 76bc59db1d0e9127 | implementation | cursor→agy→pi | 1 | cursor(新 session,event 未报则 not-observable) | success | 已应用 bc09721(fast-forward afb65c5→bc09721) | never | /tmp/permux-u1-run2.json |
|
||||
| permux-U2 | 批量 patch/批量分配/复制分配事务 + 目录角色投影 + 角色成员目录 | 76bc59db1d0e9127 | implementation | cursor→agy→pi | 1 | cursor(session 未在事件中报告:not-observable) | success | 已应用 6851ee6(fast-forward bc09721→6851ee6) | never | /tmp/permux-u2-run.json |
|
||||
| permux-U3 | 分类矩阵默认视图 + 三态/二态控件 + 多角色草稿统一预览与原子保存 + 窄屏聚焦 | 76bc59db1d0e9127 | implementation | cursor→agy→pi | 1 | cursor(session not-observable) | success | 已应用 4be7362(fast-forward 6db528d→4be7362,Frontend 分支 feature/iam-perm-ux-frontend) | never | /tmp/permux-u3-run.json |
|
||||
| permux-U4 | 用户表当前角色/多选/批量分配 + 复制角色并分配 + 角色成员子视图 | 76bc59db1d0e9127 | implementation | cursor→agy→pi | 1 | cursor(session not-observable) | success | 已应用 d965732(fast-forward 4be7362→d965732) | never | /tmp/permux-u4-run.json |
|
||||
| permux-U5 | me.linkedCapabilities 消费 + 职责条件/来源文案 + 创建关联说明卡(creator/共享读取留待 L1 U2/U7,未 mock) | 76bc59db1d0e9127 | implementation | cursor→agy→pi | 1 | cursor(session not-observable) | success | 已应用 a6ce695(fast-forward d965732→a6ce695) | never | /tmp/permux-u5-run.json |
|
||||
| permux-U6 组合 pin | backend@6851ee6 + frontend@a6ce695 → Dev 子分支 | 76bc59db1d0e9127 | implementation | cursor→agy→pi | 1 | cursor(session not-observable) | success | 已应用 7b25169(Dev 子分支 transport) | never | /tmp/permux-u6-pins-run.json |
|
||||
| permux-U6 knip repair | 去除 3 个未消费导出的 export(frontend) | 76bc59db1d0e9127 | repair | cursor→agy→pi | 1 | cursor(session not-observable) | success | 已应用 1d907c1(frontend a6ce695→1d907c1) | never | /tmp/permux-knip-run.json |
|
||||
| permux-U6 live spec | e2e/iam-perm-ux-live.playwright.ts 新增(真实服务 8 场景) | 76bc59db1d0e9127 | implementation | cursor→agy→pi | 1 | cursor(session not-observable) | success | 已应用 7d9ac7c | never | /tmp/permux-u6-e2e-run.json |
|
||||
| permux-U6 live 字段修复 | linkedActions 字段名修正 | 76bc59db1d0e9127 | repair | cursor→agy→pi | 1 | cursor(session not-observable) | success | 已应用 c37c33b | never | /tmp/permux-e2e-fix2-run.json |
|
||||
| permux-U6 live 字段修复(首次) | 同上 | 76bc59db1d0e9127 | repair | cursor→agy→pi | 1 | cursor | failed-unknown(worker 未输出有效终态记录;fail-stop,现场保留未应用) | 未应用 | never | /tmp/permux-e2e-fix-run.json |
|
||||
| permux-U6 预览 patch 模式 | PreviewRolePermissions 增加 mode=patch 与批量提交同源归并(Backend 4130ad7) | 76bc59db1d0e9127 | repair | cursor→agy→pi | 1 | cursor(session not-observable) | success | 已应用 4130ad7(backend 6851ee6→4130ad7) | never | /tmp/permux-preview-fix-run.json |
|
||||
| permux-U6 前端预览模式×2 | 预览请求带 mode 及层级修正(c37c33b→054f134→128aafed) | 76bc59db1d0e9127 | repair | cursor→agy→pi | 各 1 | cursor | success×2 | 已应用 054f134、128aafed | never | /tmp/permux-fe-mode-run.json、/tmp/permux-mode-level-run.json |
|
||||
| permux-U6 复制表单崩溃修复 | 角色投影无 permissions 时详情崩溃(真实组合发现)修复(46a63f02) | 76bc59db1d0e9127 | repair | cursor→agy→pi | 1 | cursor(session not-observable) | success | 已应用 46a63f02 | never | /tmp/permux-copy-fix-run.json |
|
||||
| permux-U6 组合 pin×2 | 终态组合 backend@4130ad7 + frontend@46a63f02 → bdd058f | 76bc59db1d0e9127 | implementation | cursor→agy→pi | 各 1 | cursor | success×2 | 已应用 650944a、bdd058f | never | /tmp/permux-u6-pins2-run.json、/tmp/permux-u6-pins3-run.json |
|
||||
|
||||
## 同步记录
|
||||
|
||||
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|
||||
|---|---|---|---|
|
||||
| 启动 | 7716b28cfff5200e7cfe9ec75aff5bcd71b234ed | 90317d02a746ea3cd25021561ef40e8cd7498963 | 2026-10-19T03:13:11Z |
|
||||
|
||||
## 证据(Evidence)
|
||||
|
||||
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
|
||||
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
## 结果与验证
|
||||
|
||||
- 结果:U1–U5 全部实现并通过独立单元验证;U6 组合交付完成(Dev 子分支 bdd058f:backend@4130ad7 + frontend@46a63f02)。真实浏览器活体验收 8/8 PASS(登录、C01+C02 多角色矩阵编辑与一次原子提交、C03 创建联动补齐与 /api/me 运行时联动、C04 断链拒绝与恢复、C07 同名辨识、C08 两人预览后一次分配、C09 409 整批不写且保留选择、C10 复制并分配原子成功)。
|
||||
- 验证:
|
||||
- PASS:C01–C04、C07–C10(真实服务,evidence /tmp/permux-final-live.json);`make test-affected`(test-worktree/boundaries/pins/backend/frontend,evidence /tmp/permux-final-affected.json);后端 store/server 过滤套件含批量/复制/预览负控;前端 vitest 601 项 + design-guard + knip(50≤51)+ tsc;build 通过。
|
||||
- NOT_RUN(如实声明):C05/C11/C12 的部分负控由 HTTP/store 测试覆盖、未在浏览器层重复;C06 浏览器层关闭未来创建场景未单列(store 层 kept/migrationNote 已测);C13 成员共享读取与 creator 来源解释——依赖 L1 U7/U2,未 mock;C14 完整键盘/读屏人工走查未执行(可访问性由实现约定+design-guard+窄屏 helper 测试覆盖);C15 幂等/超额负控在 store 测试覆盖、未在浏览器重复;C16 日常实例采用未执行(未授权部署)。
|
||||
- 环境事实:本机 umask 0077 使 pkg/identity TestDSNFileMode 必然失败,umask 0022 下通过(identity 包本任务未改动)。
|
||||
- 真实组合发现的缺陷与修复:预览 replace 语义与批量 patch 语义错配(补齐 mode=patch 与前端适配);用户详情复制表单对无 permissions 投影崩溃(前端修复)。
|
||||
- 影响与剩余项:L1 合入后,C13/C16 与全量 C 项回归由 L1 U5/U7 及后续集成任务承接;#627 落地后批量路径应消费其目标保护;review/CI/合入记录见下节。
|
||||
|
||||
## 合入与清理
|
||||
|
||||
- Review 记录:PASS(0 阻塞,5 项非阻塞建议)。审阅者:全新 CLI 独立会话(pi / MiniMax-M3,未参与实现,fresh_session=true,session not-observable);对象:Dev 90317d02..234ce30 + 组件 origin/main…feature/iam-perm-ux-backend@4130ad7、feature/iam-perm-ux-frontend@46a63f02;报告全文 .task/reviews/2026-10-19-permux-independent-review.md(随本分支合入);收据 /tmp/permux-review2-run.json(首次只读会话因无文件产物记 failed-unknown,见 /tmp/permux-review-run.json)。非阻塞建议(后续增量,不随本 PR 修):批量角色行锁未按 roleID 排序(PG 高并发瞬时回滚);目录接口返回完整 profile 可裁剪;roleDraftLeaveGuard 复位间隙;catalog 失败回填的编辑闪烁;adoptRoleSnapshot 注释明示。
|
||||
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
|
||||
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
|
||||
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
|
||||
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-19T03:12:24.090626+00:00",
|
||||
"command": "make precheck",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev",
|
||||
"before_head": "7716b28cfff5200e7cfe9ec75aff5bcd71b234ed",
|
||||
"after_head": "7716b28cfff5200e7cfe9ec75aff5bcd71b234ed",
|
||||
"branch": "main",
|
||||
"target_branch": "main",
|
||||
"target_head": "7716b28cfff5200e7cfe9ec75aff5bcd71b234ed",
|
||||
"root_target": "main",
|
||||
"immediate_target": "main",
|
||||
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"output_tail": [
|
||||
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
|
||||
"[repo] branch=main head=7716b28cff",
|
||||
"[remote] origin/main=7716b28cff ahead=0 behind=0",
|
||||
"[tree] clean submodules_drift=0 uninit=13",
|
||||
"[warn] 13 个子模块未初始化——构建/测试前需物化(不阻断写入)",
|
||||
"[worktrees] linked=5",
|
||||
"[recycle] merged un-recycled branches: feature/layered-python-env",
|
||||
"[initiatives] local cards (not authoritative): none",
|
||||
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
|
||||
"[facts] policy/facts.md 6 条事实均在有效期内",
|
||||
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
|
||||
"[R0] 7716b28cff(完整:7716b28cfff5200e7cfe9ec75aff5bcd71b234ed)",
|
||||
"[worktree] before-sync: recursive component checks passed",
|
||||
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
|
||||
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
|
||||
],
|
||||
"output_sha256": "07bfa89f466fec4670149743fcc24eec9e572add7603cbc9a08b6c6268f511b3",
|
||||
"receipt_sha256": "82a56765b5b0630ed103d995875cdeb55426259336a503bb7d1a8bbf42aa9a40",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/82a56765b5b0630ed103d995875cdeb55426259336a503bb7d1a8bbf42aa9a40.json"
|
||||
}
|
||||
```
|
||||
| 同步(记录前对象) | a99dbfcd9ac48d09038c76e09dc59e074c11ab7d | 04af865e227858bbe9f0bd38ebaed2b874da4f27 | 2026-10-19T03:18:24Z |
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-19T03:39:09.339573+00:00",
|
||||
"command": "sh -c 'cd modules/backend && GOWORK=off go build ./... && GOWORK=off go test ./internal/authz/... ./internal/store ./internal/server -run \"Role|Policy|Binding|AccountSearch|Preview|Catalog\" -count=1 && GOWORK=off go vet ./internal/authz/catalog ./internal/store ./internal/server && git status --porcelain -- deps/ | wc -l'",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux",
|
||||
"before_head": "c17b0ae76ccf0d37060bed5a822d48b12b1da432",
|
||||
"after_head": "c17b0ae76ccf0d37060bed5a822d48b12b1da432",
|
||||
"branch": "feature/iam-role-permission-linked-capabilities-perm-ux",
|
||||
"target_branch": "main",
|
||||
"target_head": "7716b28cfff5200e7cfe9ec75aff5bcd71b234ed",
|
||||
"root_target": "main",
|
||||
"immediate_target": "feature/iam-role-permission-linked-capabilities",
|
||||
"before_tracked_diff_sha256": "9d0b0e5082d3f2737db82c268a6c72f3c9826a418ce20c834042587f36f0a258",
|
||||
"after_tracked_diff_sha256": "9d0b0e5082d3f2737db82c268a6c72f3c9826a418ce20c834042587f36f0a258",
|
||||
"output_tail": [
|
||||
"? \tagent-system-backend/internal/authz\t[no test files]",
|
||||
"ok \tagent-system-backend/internal/authz/catalog\t0.008s",
|
||||
"ok \tagent-system-backend/internal/store\t0.665s",
|
||||
"ok \tagent-system-backend/internal/server\t1.834s",
|
||||
"0"
|
||||
],
|
||||
"output_sha256": "738432121e1862d4b5466f789033634bca2025c29e89c8bd444dbbdba59d6f94",
|
||||
"receipt_sha256": "db537adfa357d41e0f84f7aba19fd5fe85f3249cdbfe92887e675be4a73c966a",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/db537adfa357d41e0f84f7aba19fd5fe85f3249cdbfe92887e675be4a73c966a.json"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-19T03:53:20.691306+00:00",
|
||||
"command": "sh -c 'cd modules/backend && GOWORK=off go build ./... && GOWORK=off go test ./internal/authz/... ./internal/store ./internal/server -run \"Role|Policy|Binding|AccountSearch|Batch|CopyAssign|Members|Preview|Catalog\" -count=1 && GOWORK=off go vet ./internal/store ./internal/server && git status --porcelain -- deps/ | wc -l'",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux",
|
||||
"before_head": "cfe90a8742263c32d67f91614f8b5d8a6e4b3f28",
|
||||
"after_head": "cfe90a8742263c32d67f91614f8b5d8a6e4b3f28",
|
||||
"branch": "feature/iam-role-permission-linked-capabilities-perm-ux",
|
||||
"target_branch": "main",
|
||||
"target_head": "7716b28cfff5200e7cfe9ec75aff5bcd71b234ed",
|
||||
"root_target": "main",
|
||||
"immediate_target": "feature/iam-role-permission-linked-capabilities",
|
||||
"before_tracked_diff_sha256": "01694e3df50336ad8bdf88e7519628925d66e7bed13e94745ee5d78af591fa12",
|
||||
"after_tracked_diff_sha256": "01694e3df50336ad8bdf88e7519628925d66e7bed13e94745ee5d78af591fa12",
|
||||
"output_tail": [
|
||||
"? \tagent-system-backend/internal/authz\t[no test files]",
|
||||
"ok \tagent-system-backend/internal/authz/catalog\t0.002s",
|
||||
"ok \tagent-system-backend/internal/store\t0.818s",
|
||||
"ok \tagent-system-backend/internal/server\t2.648s",
|
||||
"0"
|
||||
],
|
||||
"output_sha256": "bc4b28c38cb498d35ef2e3d2d5b296654b08779a71dafc8073670d42cda11de6",
|
||||
"receipt_sha256": "be276a366de77bd675a1dfccf23023b269633940a712195042d949138c765c55",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/be276a366de77bd675a1dfccf23023b269633940a712195042d949138c765c55.json"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-19T04:05:14.627845+00:00",
|
||||
"command": "sh -c 'cd modules/frontend && npx vitest run test/role-permissions.test.ts test/role-permissions-ui.test.ts test/design-guard.test.ts 2>&1 | tail -4 && npx tsc -b && echo TSC_OK'",
|
||||
"exit_code": 1,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux",
|
||||
"before_head": "fa8b5fb1c147d7660ef219dfa53be73a9a064f79",
|
||||
"after_head": "fa8b5fb1c147d7660ef219dfa53be73a9a064f79",
|
||||
"branch": "feature/iam-role-permission-linked-capabilities-perm-ux",
|
||||
"target_branch": "main",
|
||||
"target_head": "7716b28cfff5200e7cfe9ec75aff5bcd71b234ed",
|
||||
"root_target": "main",
|
||||
"immediate_target": "feature/iam-role-permission-linked-capabilities",
|
||||
"before_tracked_diff_sha256": "5a432adeeb888e4790fabbdc35121353f79ebf5eaed38a65520da364553468df",
|
||||
"after_tracked_diff_sha256": "5a432adeeb888e4790fabbdc35121353f79ebf5eaed38a65520da364553468df",
|
||||
"output_tail": [
|
||||
" Tests 26 passed (26)",
|
||||
" Start at 04:05:15",
|
||||
" Duration 456ms (transform 369ms, setup 0ms, import 460ms, tests 22ms, environment 0ms)",
|
||||
"",
|
||||
"../ui/src/activitySummary.ts(2,49): error TS2307: Cannot find module 'marked' or its corresponding type declarations.",
|
||||
"../ui/src/safeHtml.tsx(4,24): error TS2307: Cannot find module 'marked' or its corresponding type declarations.",
|
||||
"../ui/src/safeHtml.tsx(5,29): error TS2307: Cannot find module 'dompurify' or its corresponding type declarations.",
|
||||
"../ui/src/safeHtml.tsx(6,29): error TS2307: Cannot find module 'dompurify' or its corresponding type declarations.",
|
||||
"../ui/src/safeHtml.tsx(54,46): error TS7006: Parameter 'node' implicitly has an 'any' type.",
|
||||
"../ui/src/safeHtml.tsx(54,52): error TS7006: Parameter 'data' implicitly has an 'any' type.",
|
||||
"../ui/src/safeHtml.tsx(56,53): error TS7006: Parameter 'name' implicitly has an 'any' type.",
|
||||
"../ui/src/safeHtml.tsx(61,47): error TS7006: Parameter 'node' implicitly has an 'any' type."
|
||||
],
|
||||
"output_sha256": "62f6c2f955af2580587185359c855db2cd2c9b708ff4f214e7daf1c6af7cbca3",
|
||||
"receipt_sha256": "2b0c8fd7d7c6f4e2b2d686a99cc4de0c9ac33bf0e529dc097869bfea2b1e2698",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/2b0c8fd7d7c6f4e2b2d686a99cc4de0c9ac33bf0e529dc097869bfea2b1e2698.json"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-19T04:06:33.549745+00:00",
|
||||
"command": "sh -c 'cd modules/frontend && npx vitest run test/role-permissions.test.ts test/role-permissions-ui.test.ts test/design-guard.test.ts 2>&1 | tail -4 && npx tsc -b && echo TSC_OK'",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux",
|
||||
"before_head": "fa8b5fb1c147d7660ef219dfa53be73a9a064f79",
|
||||
"after_head": "fa8b5fb1c147d7660ef219dfa53be73a9a064f79",
|
||||
"branch": "feature/iam-role-permission-linked-capabilities-perm-ux",
|
||||
"target_branch": "main",
|
||||
"target_head": "7716b28cfff5200e7cfe9ec75aff5bcd71b234ed",
|
||||
"root_target": "main",
|
||||
"immediate_target": "feature/iam-role-permission-linked-capabilities",
|
||||
"before_tracked_diff_sha256": "1a6610bf25c8514e73982e4c2ec2afa122deba4db666670b3c26e69b8b201999",
|
||||
"after_tracked_diff_sha256": "1a6610bf25c8514e73982e4c2ec2afa122deba4db666670b3c26e69b8b201999",
|
||||
"output_tail": [
|
||||
" Tests 26 passed (26)",
|
||||
" Start at 04:06:33",
|
||||
" Duration 351ms (transform 281ms, setup 0ms, import 354ms, tests 20ms, environment 0ms)",
|
||||
"",
|
||||
"TSC_OK"
|
||||
],
|
||||
"output_sha256": "9b4104496d0cfb6faa5fbb49ec4717eca4d7492de6963a767a631c73afdaf641",
|
||||
"receipt_sha256": "35f7e24a7106e9b04e96f1ad05ceecdc75062af14156135314ff45bbc4f19494",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/35f7e24a7106e9b04e96f1ad05ceecdc75062af14156135314ff45bbc4f19494.json"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-19T04:17:53.352830+00:00",
|
||||
"command": "sh -c 'cd modules/frontend && npx vitest run test/account-users-panel.test.ts test/role-members.test.ts test/role-permissions.test.ts test/role-permissions-ui.test.ts test/design-guard.test.ts 2>&1 | tail -4 && npx tsc -b && echo TSC_OK'",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux",
|
||||
"before_head": "b1a1ffbda55bd219c849cb69336f07e690e57342",
|
||||
"after_head": "b1a1ffbda55bd219c849cb69336f07e690e57342",
|
||||
"branch": "feature/iam-role-permission-linked-capabilities-perm-ux",
|
||||
"target_branch": "main",
|
||||
"target_head": "7716b28cfff5200e7cfe9ec75aff5bcd71b234ed",
|
||||
"root_target": "main",
|
||||
"immediate_target": "feature/iam-role-permission-linked-capabilities",
|
||||
"before_tracked_diff_sha256": "ed490c9560a136815bf04b76235761a1104d3c5fe43754b7505a7498be12044b",
|
||||
"after_tracked_diff_sha256": "ed490c9560a136815bf04b76235761a1104d3c5fe43754b7505a7498be12044b",
|
||||
"output_tail": [
|
||||
" Tests 43 passed (43)",
|
||||
" Start at 04:17:53",
|
||||
" Duration 368ms (transform 655ms, setup 0ms, import 816ms, tests 30ms, environment 0ms)",
|
||||
"",
|
||||
"TSC_OK"
|
||||
],
|
||||
"output_sha256": "8650d93bb8fb8f383cb9cdaff1de0bd7a8cd84045863cbcda89bc97ef9a791b9",
|
||||
"receipt_sha256": "ff22fc2ac93c050ca1c1e48f9e8d02d6b70acfb91c4ec43aa7eb7bb6d676c29d",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/ff22fc2ac93c050ca1c1e48f9e8d02d6b70acfb91c4ec43aa7eb7bb6d676c29d.json"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-19T04:22:52.740525+00:00",
|
||||
"command": "sh -c 'cd modules/frontend && npx vitest run test/permission-explanations.test.ts test/role-members.test.ts test/design-guard.test.ts 2>&1 | tail -4 && npx tsc -b && echo TSC_OK'",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux",
|
||||
"before_head": "9a9c9699707a64fd3c42cafca43b852c0e2360ef",
|
||||
"after_head": "9a9c9699707a64fd3c42cafca43b852c0e2360ef",
|
||||
"branch": "feature/iam-role-permission-linked-capabilities-perm-ux",
|
||||
"target_branch": "main",
|
||||
"target_head": "7716b28cfff5200e7cfe9ec75aff5bcd71b234ed",
|
||||
"root_target": "main",
|
||||
"immediate_target": "feature/iam-role-permission-linked-capabilities",
|
||||
"before_tracked_diff_sha256": "72c8f8764864ab3a21ecfa3b718b5b4f6af0558bf2a78da32cb2ecaa944f3038",
|
||||
"after_tracked_diff_sha256": "72c8f8764864ab3a21ecfa3b718b5b4f6af0558bf2a78da32cb2ecaa944f3038",
|
||||
"output_tail": [
|
||||
" Tests 28 passed (28)",
|
||||
" Start at 04:22:53",
|
||||
" Duration 129ms (transform 70ms, setup 0ms, import 98ms, tests 24ms, environment 0ms)",
|
||||
"",
|
||||
"TSC_OK"
|
||||
],
|
||||
"output_sha256": "61d36b9c51a038a5d0412bdc14ab3fecc312a6f161de6c4a56700f0a594058ef",
|
||||
"receipt_sha256": "4841be620f46292654dbd43bc0f132ca4118792756394f14b1b6892a7f866507",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/4841be620f46292654dbd43bc0f132ca4118792756394f14b1b6892a7f866507.json"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-19T04:27:22.417342+00:00",
|
||||
"command": "sh -c 'umask 0022 && make test-affected 2>&1 | tail -8'",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux",
|
||||
"before_head": "7b25169fad83291ff342213afeb20ca2fa2efed8",
|
||||
"after_head": "7b25169fad83291ff342213afeb20ca2fa2efed8",
|
||||
"branch": "feature/iam-role-permission-linked-capabilities-perm-ux",
|
||||
"target_branch": "main",
|
||||
"target_head": "7716b28cfff5200e7cfe9ec75aff5bcd71b234ed",
|
||||
"root_target": "main",
|
||||
"immediate_target": "feature/iam-role-permission-linked-capabilities",
|
||||
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"output_tail": [
|
||||
" Start at 04:27:54",
|
||||
" Duration 814ms (transform 6.92s, setup 0ms, import 11.46s, tests 960ms, environment 4ms)",
|
||||
"",
|
||||
"knip unused exports 53 exceed baseline 51",
|
||||
"make[2]: *** [Makefile:188: test-frontend] Error 1",
|
||||
"make[2]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux'",
|
||||
"make[1]: *** [Makefile:221: test-affected] Error 2",
|
||||
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux'"
|
||||
],
|
||||
"output_sha256": "c5041e844c8690957d62bcc1f7eb0c14c390a0fadb3953577d3266c39a149c39",
|
||||
"receipt_sha256": "2227942e89094d6d44dca758185d4d836f83a790610d381c9d2e596f5253ac8d",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/2227942e89094d6d44dca758185d4d836f83a790610d381c9d2e596f5253ac8d.json"
|
||||
}
|
||||
```
|
||||
|
||||
## U6 组合与验证记录
|
||||
|
||||
- 组合 transport:7b25169(backend→6851ee6,frontend→a6ce695);knip 修复后 frontend 分支推进至 1d907c1(repair 收据 /tmp/permux-knip-run.json,cursor 一次成功),组合 pin 二次推进见后续记录。
|
||||
- 环境事实:本机 umask 0077 使 pkg/identity TestDSNFileMode(0o644 拒绝用例)必然失败;umask 0022 下通过。该失败为环境因素,非本任务回归(identity 包本任务未触及)。
|
||||
- 缺陷收据说明:/tmp/permux-u6-affected.json 因 host 在 CMD 中使用 `| tail` 吞掉管道退出码而记录 exit=0,但输出含 test-frontend knip 超基线失败——该收据不作为 PASS 证据;超额已由 knip repair 修复(50 ≤ 51),test-affected 将以无管道方式重新捕获。
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-19T04:38:56.269044+00:00",
|
||||
"command": "sh -c 'umask 0022 && make test-affected'",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux",
|
||||
"before_head": "650944ae6897ca0d1ea76875fe8015c3364b5d71",
|
||||
"after_head": "650944ae6897ca0d1ea76875fe8015c3364b5d71",
|
||||
"branch": "feature/iam-role-permission-linked-capabilities-perm-ux",
|
||||
"target_branch": "main",
|
||||
"target_head": "7716b28cfff5200e7cfe9ec75aff5bcd71b234ed",
|
||||
"root_target": "main",
|
||||
"immediate_target": "feature/iam-role-permission-linked-capabilities",
|
||||
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"output_tail": [
|
||||
"(node:240613) ExperimentalWarning: localStorage is not available because --localstorage-file was not provided.",
|
||||
"(Use `node --trace-warnings ...` to show where the warning was created)",
|
||||
"(node:240680) ExperimentalWarning: localStorage is not available because --localstorage-file was not provided.",
|
||||
"(Use `node --trace-warnings ...` to show where the warning was created)",
|
||||
"(node:240614) ExperimentalWarning: localStorage is not available because --localstorage-file was not provided.",
|
||||
"(Use `node --trace-warnings ...` to show where the warning was created)",
|
||||
"",
|
||||
" Test Files 50 passed (50)",
|
||||
" Tests 601 passed (601)",
|
||||
" Start at 04:39:03",
|
||||
" Duration 836ms (transform 7.74s, setup 0ms, import 11.93s, tests 941ms, environment 4ms)",
|
||||
"",
|
||||
"knip unused exports 50 <= 51",
|
||||
"make[2]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux'",
|
||||
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux'"
|
||||
],
|
||||
"output_sha256": "48eaf015c3e561bd34008b771d984d9e267062fd2cf810d67a2aaec05793e80d",
|
||||
"receipt_sha256": "59fb000313feb6ed03a2b1d2305a9372a6a991b218c95449a93a353b1d5dcc69",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/59fb000313feb6ed03a2b1d2305a9372a6a991b218c95449a93a353b1d5dcc69.json"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-19T05:35:26.812094+00:00",
|
||||
"command": "sh -c 'umask 0022 && make test-affected'",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux",
|
||||
"before_head": "bdd058f974cb1e681b253230e6acbd75674eaa84",
|
||||
"after_head": "bdd058f974cb1e681b253230e6acbd75674eaa84",
|
||||
"branch": "feature/iam-role-permission-linked-capabilities-perm-ux",
|
||||
"target_branch": "main",
|
||||
"target_head": "7716b28cfff5200e7cfe9ec75aff5bcd71b234ed",
|
||||
"root_target": "main",
|
||||
"immediate_target": "feature/iam-role-permission-linked-capabilities",
|
||||
"before_tracked_diff_sha256": "97f08e41dec9f0a379f5c3010d58388160715650ce758c4d000560fdfd1d7e63",
|
||||
"after_tracked_diff_sha256": "97f08e41dec9f0a379f5c3010d58388160715650ce758c4d000560fdfd1d7e63",
|
||||
"output_tail": [
|
||||
"(node:1933409) ExperimentalWarning: localStorage is not available because --localstorage-file was not provided.",
|
||||
"(Use `node --trace-warnings ...` to show where the warning was created)",
|
||||
"(node:1933535) ExperimentalWarning: localStorage is not available because --localstorage-file was not provided.",
|
||||
"(Use `node --trace-warnings ...` to show where the warning was created)",
|
||||
"(node:1933410) ExperimentalWarning: localStorage is not available because --localstorage-file was not provided.",
|
||||
"(Use `node --trace-warnings ...` to show where the warning was created)",
|
||||
"",
|
||||
" Test Files 50 passed (50)",
|
||||
" Tests 602 passed (602)",
|
||||
" Start at 05:36:03",
|
||||
" Duration 958ms (transform 8.76s, setup 0ms, import 14.30s, tests 1.02s, environment 4ms)",
|
||||
"",
|
||||
"knip unused exports 51 <= 51",
|
||||
"make[2]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux'",
|
||||
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux'"
|
||||
],
|
||||
"output_sha256": "18a8e221f7bb03ca79c568af4e3304be9bbc308a4c53230e22db9569df152ec4",
|
||||
"receipt_sha256": "af3688b164357d007bf5aa7dbc06890b7e13e2842779acb3c3cd86ee77f03c55",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/af3688b164357d007bf5aa7dbc06890b7e13e2842779acb3c3cd86ee77f03c55.json"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-19T05:36:11.940198+00:00",
|
||||
"command": "sh -c 'cd modules/frontend && PLAYWRIGHT_BASE_URL=http://127.0.0.1:4199 PERMUX_LIVE=1 PERMUX_OWNER=permuxadmin PERMUX_PASSWORD=<redacted> npx playwright test e2e/iam-perm-ux-live.playwright.ts'",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux",
|
||||
"before_head": "bdd058f974cb1e681b253230e6acbd75674eaa84",
|
||||
"after_head": "bdd058f974cb1e681b253230e6acbd75674eaa84",
|
||||
"branch": "feature/iam-role-permission-linked-capabilities-perm-ux",
|
||||
"target_branch": "main",
|
||||
"target_head": "7716b28cfff5200e7cfe9ec75aff5bcd71b234ed",
|
||||
"root_target": "main",
|
||||
"immediate_target": "feature/iam-role-permission-linked-capabilities",
|
||||
"before_tracked_diff_sha256": "33ee1abfd7b5c632cfb27c3b22c7b1c239e8320854108e926a7ada43a8dd3e37",
|
||||
"after_tracked_diff_sha256": "33ee1abfd7b5c632cfb27c3b22c7b1c239e8320854108e926a7ada43a8dd3e37",
|
||||
"output_tail": [
|
||||
"",
|
||||
"Running 8 tests using 1 worker",
|
||||
"",
|
||||
" ✓ 1 e2e/iam-perm-ux-live.playwright.ts:145:3 › permux 活体主流程(真实 Backend + 真实 Frontend dist) › 登录:管理员经登录表单进入工作台,/api/me 为管理员 (283ms)",
|
||||
" ✓ 2 e2e/iam-perm-ux-live.playwright.ts:172:3 › permux 活体主流程(真实 Backend + 真实 Frontend dist) › C01+C02 多角色矩阵编辑:跨分类与搜索保留草稿,预览后一次提交 (2.8s)",
|
||||
" ✓ 3 e2e/iam-perm-ux-live.playwright.ts:231:3 › permux 活体主流程(真实 Backend + 真实 Frontend dist) › C03 创建联动:开启创建项目自动补齐四项按项目授权,运行时 /api/me 联动生效 (573ms)",
|
||||
" ✓ 4 e2e/iam-perm-ux-live.playwright.ts:267:3 › permux 活体主流程(真实 Backend + 真实 Frontend dist) › C04 断链拒绝:创建项目开启时联动项不允许不可保存,恢复按项目授权后保存成功 (430ms)",
|
||||
" ✓ 5 e2e/iam-perm-ux-live.playwright.ts:305:3 › permux 活体主流程(真实 Backend + 真实 Frontend dist) › C07 同名辨识:两名同名用户按账号辨识,勾选互不串行 (1.1s)",
|
||||
" ✓ 6 e2e/iam-perm-ux-live.playwright.ts:333:3 › permux 活体主流程(真实 Backend + 真实 Frontend dist) › C08 批量分配:两人预览 未分配 → 角色乙 后一次提交 (593ms)",
|
||||
" ✓ 7 e2e/iam-perm-ux-live.playwright.ts:356:3 › permux 活体主流程(真实 Backend + 真实 Frontend dist) › C09 冲突整批不写:确认前他人改动一人角色,409 后整批未写入且保留选择 (730ms)",
|
||||
" ✓ 8 e2e/iam-perm-ux-live.playwright.ts:389:3 › permux 活体主流程(真实 Backend + 真实 Frontend dist) › C10 复制并分配:源角色权限复制到新角色并换绑,新角色独立维护 (295ms)",
|
||||
"",
|
||||
" 8 passed (7.4s)"
|
||||
],
|
||||
"output_sha256": "eaa754b30e6f134a8e55f19a417efc4c8cbc03c8eb72db9c56624d5700cecf44",
|
||||
"receipt_sha256": "c7da99a1f2734fdfcfd2ff0373287773d731001ca689f012ce6b039d70091cfa",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/c7da99a1f2734fdfcfd2ff0373287773d731001ca689f012ce6b039d70091cfa.json"
|
||||
}
|
||||
```
|
||||
|
||||
## 交付阻塞记录(2026-10-19)
|
||||
|
||||
- 平台事实:Gitea `https://git-home.u1680832.nyat.app:30319` 当前 TLS 握手失败(WRONG_VERSION_NUMBER),明文 `http://…:30319/api/v1/version` 返回 501 代理错误页(非 Gitea JSON);同日早些时候(03:05 分流)HTTPS API 正常。policy/facts.md F1 判据已失效,恢复后须 `make facts-verify` 重验。
|
||||
- git/SSH 通道正常:三个分支均已推送(Dev 子分支 f97790b、Backend feature/iam-perm-ux-backend @ 4130ad7、Frontend feature/iam-perm-ux-frontend @ 46a63f02)。
|
||||
- 受阻动作:PR 创建、`make task-pr-check`、CI 等待(`make ci-wait`)、独立 Review 提交与合入——均标 NOT_RUN/阻塞,待平台恢复后在同一收据流程下继续;不得以本地证据替代平台准入。
|
||||
- 现场保护:worktree 与全部组件分支保留;无未提交的实施代码(任务卡记录除外)。
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-19T07:12:01.645701+00:00",
|
||||
"command": "make task-pr-check PR=629",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux",
|
||||
"before_head": "e7bcc61ba6ea378a24ab43643b81fa41d2076bd3",
|
||||
"after_head": "e7bcc61ba6ea378a24ab43643b81fa41d2076bd3",
|
||||
"branch": "feature/iam-role-permission-linked-capabilities-perm-ux",
|
||||
"target_branch": "main",
|
||||
"target_head": "73bc9f208103ec1b9b83775ca65fee12f79ba99e",
|
||||
"root_target": "main",
|
||||
"immediate_target": "feature/iam-role-permission-linked-capabilities",
|
||||
"before_tracked_diff_sha256": "016330b58d3f18c8ee1ec97dafdae2bda81a4b8ca4503fc156bef1ce0a843a10",
|
||||
"after_tracked_diff_sha256": "016330b58d3f18c8ee1ec97dafdae2bda81a4b8ca4503fc156bef1ce0a843a10",
|
||||
"output_tail": [
|
||||
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux'",
|
||||
"python3 scripts/task_pr_check.py local --pr 629 ",
|
||||
"[verdict] PR_CHECK_OK base=feature/iam-role-permission-linked-capabilities head=e7bcc61ba6ea378a24ab43643b81fa41d2076bd3 kind=child",
|
||||
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux'"
|
||||
],
|
||||
"output_sha256": "6d920e3b949ce2190266128b8fab6d643b9a43fde82ee3b09a50745e8503715f",
|
||||
"receipt_sha256": "9ac80ba53e969973c510fb8344215feb365a76693b83499130b5547fbb910264",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/9ac80ba53e969973c510fb8344215feb365a76693b83499130b5547fbb910264.json"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-19T07:32:01.073317+00:00",
|
||||
"command": "make ci-wait SHA=374ce2d3822ae6ae5514cab2d6b0701f8f88c049",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux",
|
||||
"before_head": "374ce2d3822ae6ae5514cab2d6b0701f8f88c049",
|
||||
"after_head": "374ce2d3822ae6ae5514cab2d6b0701f8f88c049",
|
||||
"branch": "feature/iam-role-permission-linked-capabilities-perm-ux",
|
||||
"target_branch": "main",
|
||||
"target_head": "73bc9f208103ec1b9b83775ca65fee12f79ba99e",
|
||||
"root_target": "main",
|
||||
"immediate_target": "feature/iam-role-permission-linked-capabilities",
|
||||
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"output_tail": [
|
||||
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux'",
|
||||
"python3 scripts/ci_wait.py --sha 374ce2d3822ae6ae5514cab2d6b0701f8f88c049 ",
|
||||
"[ci-wait] sha=374ce2d3822a ci / svc-realcombo (pull_request)=skipped ci / test (pull_request)=success ci / test-pg (pull_request)=skipped",
|
||||
"[verdict] CI_SUCCESS sha=374ce2d3822ae6ae5514cab2d6b0701f8f88c049",
|
||||
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux'"
|
||||
],
|
||||
"output_sha256": "2909a1b5ce8c20fa15b10b1bcc1fe64c25cccfe1200350cb45420b2c410ffe2e",
|
||||
"receipt_sha256": "c62c86cfb7bdf8157ba804c7832cccc0424d0001be34f1cfec9f98f92680b6c5",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/c62c86cfb7bdf8157ba804c7832cccc0424d0001be34f1cfec9f98f92680b6c5.json"
|
||||
}
|
||||
```
|
||||
| 同步(记录前对象) | e1e0887e12501158e04477659d4567262dc44a73 | 2f88c756faaa267daf99d311415e7253fb5b4f23 | 2026-10-19T07:55:31Z |
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-19T07:54:30.575767+00:00",
|
||||
"command": "make task-sync",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux",
|
||||
"before_head": "e4aa6337502a5b317067f25b6b52033117cb20e3",
|
||||
"after_head": "c1cf9dd0ad6b1cd2a8f1ef668e288360e9777edc",
|
||||
"branch": "feature/iam-role-permission-linked-capabilities-perm-ux",
|
||||
"target_branch": "main",
|
||||
"target_head": "38ac2b0687514b480cc61dec425e1affb37c6295",
|
||||
"root_target": "main",
|
||||
"immediate_target": "feature/iam-role-permission-linked-capabilities",
|
||||
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"output_tail": [
|
||||
"[worktree] materialize: recursive component checks passed",
|
||||
"[worktree] after-sync: recursive component checks passed",
|
||||
"admit: ok",
|
||||
"[evidence] WARN JSON receipt line 103: unverified: recorded component state was not at its pinned SHA ——关键检查须重跑并以 make evidence 重新写入",
|
||||
"[evidence] WARN JSON receipt line 145: unverified: recorded object includes uncommitted non-card changes ——关键检查须重跑并以 make evidence 重新写入",
|
||||
"[evidence] WARN JSON receipt line 176: unverified: recorded object includes uncommitted non-card changes ——关键检查须重跑并以 make evidence 重新写入",
|
||||
"[evidence] WARN JSON receipt line 207: unverified: recorded object includes uncommitted non-card changes ——关键检查须重跑并以 make evidence 重新写入",
|
||||
"[evidence] WARN JSON receipt line 245: unverified: recorded object includes uncommitted non-card changes ——关键检查须重跑并以 make evidence 重新写入",
|
||||
"[evidence] WARN JSON receipt line 276: unverified: recorded object includes uncommitted non-card changes ——关键检查须重跑并以 make evidence 重新写入",
|
||||
"[evidence] WARN JSON receipt line 307: unverified: recorded object includes uncommitted non-card changes ——关键检查须重跑并以 make evidence 重新写入",
|
||||
"[evidence] WARN JSON receipt line 338: recorded HEAD predates non-card changes ——关键检查须重跑并以 make evidence 重新写入",
|
||||
"[evidence] WARN JSON receipt line 378: recorded HEAD predates non-card changes ——关键检查须重跑并以 make evidence 重新写入",
|
||||
"[synced] sync_base=e1e0887e12501158e04477659d4567262dc44a73 tested_head=c1cf9dd0ad6b1cd2a8f1ef668e288360e9777edc checked_at=2026-10-19T07:55:32Z changed=1 sync_target=feature/iam-role-permission-linked-capabilities",
|
||||
"[next] 跑正式验收(make test 等),证据写入任务卡",
|
||||
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-iam-role-permission-linked-capabilities-perm-ux'"
|
||||
],
|
||||
"output_sha256": "0de9daceeef02d1ac6f001ee3f3e88f40f00d2256ff08ba95c7d1ce0d6d0c4cb",
|
||||
"receipt_sha256": "46d8df5f1b56492e3155f9e785621b2fd9f86a346f10c0b6642cfe4281ca7ad2",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/46d8df5f1b56492e3155f9e785621b2fd9f86a346f10c0b6642cfe4281ca7ad2.json"
|
||||
}
|
||||
```
|
||||
|
||||
## 闭合记录(合入前折叠)
|
||||
|
||||
- 本卡在 PR #629(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/629)合入前闭合并归档,随该 PR 一同合入。
|
||||
- 该 PR 的合并提交即本任务的落地记录。
|
||||
- 合入后无需回填,也无需单独的闭合 PR。
|
||||
@@ -0,0 +1,130 @@
|
||||
# permux 独立代码审查(Independent Review)— 2026-10-19
|
||||
|
||||
> 审查者:全新会话(未参与实现),按 `.task/feature-iam-role-permission-linked-capabilities-perm-ux.md` 与本审查任务卡执行独立 Read-Only Review。
|
||||
> 审查对象:Dev 子分支 `feature/iam-role-permission-linked-capabilities-perm-ux`(HEAD 234ce30;起始基线 90317d02);组件 commit `modules/backend @ 4130ad78 = origin/feature/iam-perm-ux-backend`,`modules/frontend @ 46a63f02 = origin/feature/iam-perm-ux-frontend`。
|
||||
> 终态:组合 pin `modules/backend @ 4130ad7 + modules/frontend @ 46a63f0`,6/6 单元(U1–U5 + U6 组合交付)落 Dev 子分支。
|
||||
> 审查范围:组件仓 `origin/main…origin/feature/iam-perm-ux-*` 与 Dev 父仓 `90317d02..HEAD` 的提交范围;只读分析与命令验证,不修改、不暂存、不提交。
|
||||
|
||||
## 结论
|
||||
|
||||
**结论:PASS(附 1 项非阻塞观察、4 项非阻塞建议)**。本任务卡记录的实现范围(permux-U1–U6)符合:
|
||||
- 数据完整性:批量事务原子性、幂等回执、单 epoch、锁序一致;
|
||||
- 验收一致性:C01–C04 / C07–C10 在真实 Backend + 真实 Frontend dist 浏览器活体(8 场景)PASS;C05 / C06 / C11 / C12 / C15 由 store/HTTP 单测覆盖且测试通过;C13 由 L1 U7 交付;C14 由 design-guard + 实现约定 + 窄屏 helper 覆盖;
|
||||
- 权限边界:新增端点门禁与既有 `requireSystemAction` 契约一致;只读预览无副作用;
|
||||
- 设计系统:design-guard 棘轮 7/7 通过(见验证命令 §6.4);
|
||||
- 测试充分性:阻塞项为零;下列非阻塞项不影响目标交付。
|
||||
|
||||
---
|
||||
|
||||
## 验证命令与结果(仅读、未写)
|
||||
|
||||
| 命令 | 目的 | 结果 |
|
||||
|---|---|---|
|
||||
| `GOWORK=off go test -count=1 ./internal/authz/... ./internal/store ./internal/server -run "Role\|Policy\|Binding\|AccountSearch\|Batch\|CopyAssign\|Members\|Preview\|Catalog\|System"` | Backend 角色/权限/分配/预览/复制/成员/目录 store+server 单测(覆盖 permux-U1/U2/U6 范围与 409/幂等/版本/批上限/复制联动负控) | `ok agent-system-backend/internal/authz/catalog`、`ok agent-system-backend/internal/store 1.325s`、`ok agent-system-backend/internal/server 3.737s` |
|
||||
| `npx vitest run test/design-guard.test.ts` | DESIGN.md §11 棘轮(动效唯一所有者、裸色值、行内字号、`<details>`) | 7/7 PASS,0.127s |
|
||||
| `npx vitest run test/role-permissions.test.ts test/role-permissions-ui.test.ts test/role-members.test.ts test/permission-explanations.test.ts test/account-users-panel.test.ts test/design-guard.test.ts` | Frontend permux-U3/U4/U5 单元测试 + 跨页选择/重放/草稿基线/成员过滤/设计守护 | 53/53 PASS,0.460s |
|
||||
|
||||
注:`make test` 全量未跑(任务卡「范围纪律」要求);上述过滤后直接检查与实现范围对齐。
|
||||
|
||||
---
|
||||
|
||||
## 重点审查逐项
|
||||
|
||||
### 重点 1 — Backend 批量事务原子性与锁序
|
||||
|
||||
| 子项 | 结论 | 依据 |
|
||||
|---|---|---|
|
||||
| 批量事务原子性(PatchRolePermissionsBatch / AssignUserRolesBatch / CreateRoleCopyAndAssign) | PASS | 三个批量写共用 `policyWriteBatch`,事务内任意错误 `defer tx.Rollback()` 回滚;`PatchRolePermissionsBatch` 通过循环 `patchRolePermissionsTx` 后再统一 `UPDATE authority_state` 推进 epoch,单一幂等回执(`role_policy.go:1078-1095`、`1087-1095`);`AssignUserRolesBatch` 先遍历所有 item 完成 `checkRoleAssignmentTx` 预校验,再 rebind,最后 `checkCapabilityAssociationTx` + `UPDATE authority_state`(`role_policy.go:1078-1095`、`1086-1109`);`CreateRoleCopyAndAssign` 三步(source 校验 → patch 插入 → assign 事务)共用同一 tx(`role_policy.go:1335-1398`)。负控:`TestBatchPatchRolePermissionsStaleLimitAndIdempotency` 验证陈旧版本整批不写、批上限 21、角色重复;`TestBatchAssignUserRolesRejectsWholeBatch` 验证陈旧成员/停用用户/缺失用户/停用角色/重复用户/101 人都整批拒绝;`TestCopyAssignRejectsWithoutResidue` 验证名称重复/源陈旧/用户陈旧/无效 override/101 人/缺 `roles.assign` 全部 0 残留;`TestCopyAssignCreatesRoleAndBindsAtomically` 验证成功路径源角色不变、绑定生效、幂等回执。 |
|
||||
| 单次 epoch 推进 | PASS | 所有写路径共用 `UPDATE authority_state SET authority_epoch=epoch+1 WHERE authority_epoch=epoch`,陈旧时 `RowsAffected()!=1` 返回 `ErrRolePolicyVersion` / `ErrConflict`。`TestBatchAssignUserRolesRebindsAndReplays` 断言 `batch.AuthorityEpoch==epoch+1` 且重放不再推进(`role_policy_test.go:1107-1115`)。 |
|
||||
| 幂等回执(同键异 payload 拒绝) | PASS | `replayReceipt` 比较 `operation` + `requestHash`,同键异 payload 返回 `ErrConflict`(`role_policy.go:435-455`)。`TestRolePolicyIdempotencyKeyCannotChangeRequest` 验证 `ReplaceRolePermissions`;`TestBatchAssignUserRolesRebindsAndReplays` 验证批路径;`TestBatchPatchRolePermissionsStaleLimitAndIdempotency` 验证 `batch_patch`。 |
|
||||
| policyWriteBatch 锁序(用户 → authority_state → 角色行) | PASS | `policyWriteBatch` 先 `lockAccountUsers(tx, owners...)`(owner 排序),再 `tx.QueryRow(...authority_state...) FOR UPDATE`(PG)或事务写锁(SQLite),再调 fn(`role_policy.go:240-292`)。`patchRolePermissionsTx` 内 `roleTx(item.RoleID, true)` 取 FOR UPDATE(`role_policy.go:227-237`)。fn 内不再加用户锁,符合"fn must not lock further users, and policyWriteBatch is not reentrant"契约(`role_policy.go:240-243`)。`TestAssignUserRoleConcurrentExpectedVersionAllowsOne` 验证同 actor 不同角色的并发只允许一方成功(`role_policy_test.go:65-99`)。 |
|
||||
| 政策锁后补锁用户 | 不存在 | fn 中所有用户相关写都基于 fn 入参 `targets`(已在 policyWriteBatch 入参时按 owner 序加锁),不再二次锁;`AccountDirectoryRevision` 等读面用独立只读 tx(`role_policy.go:1420-1476`)。 |
|
||||
|
||||
### 重点 2 — patch 模式预览与批量提交语义一致性
|
||||
|
||||
| 子项 | 结论 | 依据 |
|
||||
|---|---|---|
|
||||
| 同一源 merge 函数(preview / commit) | PASS | `previewRolePermissionsTx` 在 `item.Mode=="patch"` 时调用 `mergeRolePermissionPatch(state.roles[role.RoleID], current, next)`(`role_policy.go:718-723`);`patchRolePermissionsTx` 调用 `mergeRolePermissionPatch(previous.roles[role.RoleID], current, patch)`(`role_policy.go:1250`);两路径共用同一函数(`role_policy.go:1168-1207`),预览看到的合并结果与提交落库结果一致。 |
|
||||
| replace 路径回归风险 | PASS | Frontend `previewRoleBatch` 固定传 `mode: 'patch'`(`RolePermissionsPanel.tsx:96`),HTTP 预览请求体含 `mode:"patch"`(`api.ts:143-145`);Frontend 不再以 `replace` 模式走批量事务。replace 模式仍由 `replaceRolePermissions` 单角色端点支持(`roles.go:103-117`、`role_policy.go:470-548`),未受 patch 改动回归。`TestPreviewRolePermissionsReplaceModeKeepsReplaceSemantics` 与 `TestRolePermissionPreviewPatchModeOverHTTP` 显式锁定两条路径语义。 |
|
||||
| 旧项目 settings 关闭迁移文案 | PASS | `projectCreateDisabledPreviewNote` / `projectCreateDisabledNote` 在 disable patch 时给出中文说明(`role_policy.go:611-619`、`1825-1829`),`mergeRolePermissionPatch` 额外把 `current` 中存在但 patch 未列出的联动项加入 `plan.kept`(`role_policy.go:1192-1200`),保证 disable 后的 `KeptLinkedActions` 与 receipt 一致。`TestPreviewRolePermissionsReplaceModeKeepsReplaceSemantics`、`TestCapabilityAssociationDisablingProjectCreateKeepsLinkedRows` 验证。 |
|
||||
|
||||
### 重点 3 — 前端多角色草稿隔离 / 统一保存 / 409 rebase / 跨页选择 / 复制并分配
|
||||
|
||||
| 子项 | 结论 | 依据 |
|
||||
|---|---|---|
|
||||
| 按 roleId 草稿隔离 | PASS | `RoleDraftMap = Record<string, RoleDraft>`(`RolePermissionsPanel.tsx:25-26`);`editRoleDraft` 仅写 `drafts[role.roleId]`,跨角色互不污染(`RolePermissionsPanel.tsx:74-78`);`roleCellValue`、`rolePatchEntries` 按 role 独立取基线(`rolePermissions.ts:74-78`、`97-103`);`dirtyRoles` 仅列出 dirty role。`test/role-permissions.test.ts:79-90` 与 `test/role-permissions-ui.test.ts:44-58` 验证。 |
|
||||
| 统一保存 | PASS | `previewRoleBatch` + `commitRoleBatch` 是唯一写入口(`RolePermissionsPanel.tsx:95-106`);`confirmSave` 先以 `entries = patchEntries()` 调用,再 `setDrafts` 删除已保存角色(`RolePermissionsPanel.tsx:545-555`)。`TestRolePermissionUI.saves every dirty role through one authoritative preview and one batch commit` 验证两角色 dirty 时只发一次预览与一次提交。 |
|
||||
| 409 rebase(按角色独立重放本地变化) | PASS | `rebaseRoleDrafts` 并行 `getRole(roleId)`,逐角色调用 `rebaseRoleDraft`(`RolePermissionsPanel.tsx:111-121`);`rebaseRoleDraft` 复制 `serverValues`,仅在本地相对 `baseline` 变化时回放(`rolePermissions.ts:60-71`)。`test/role-permissions.test.ts:44-58` 与 `test/role-permissions-ui.test.ts:87-103` 验证。`rebaseRoleDrafts` 在 409 路径中被调用并设 `setPreview(null)`(`RolePermissionsPanel.tsx:558-563`)。 |
|
||||
| 跨页选择模型(筛选不扩大集合) | PASS | `MemberSelection.ids` 仅来自逐个勾选或 `selectPage`,搜索/状态/翻页均不动 `selection.ids`(`roleMembers.ts:5-30`)。`selectPage(checked=true)` 只追加本页 id,不覆盖;`selectPage(checked=false)` 只移除本页 id,不动其他页。`test/role-members.test.ts:50-62` 显式断言"搜索变化 / 翻页不扩大集合"。 |
|
||||
| 复制并分配(显式命名 + 权限来源) | PASS | `copyAssignRequest` 强制 `name.trim() != ''`、拒绝空 users 列表(`roleMembers.ts:107-115`);overrides 仅含相对 source 的差异项(`roleMembers.ts:118-130`);后端 `CreateRoleCopyAndAssign` 同步校验(`role_policy.go:1335-1398`),受保护动作不进副本(`role_policy.go:1395-1404`),新角色与源角色完全独立维护(`TestCopyAssignCreatesRoleAndBindsAtomically`)。权限来源(roleId/roleVersion)由 `source` 字段强制随请求传递且 `expectedRoleVersion` 不匹配返回 `ErrConflict`。 |
|
||||
|
||||
### 重点 4 — 无障碍与设计守护(design-guard 棘轮)
|
||||
|
||||
| 子项 | 结论 | 依据 |
|
||||
|---|---|---|
|
||||
| design-guard.test.ts 7 项 | PASS | `npx vitest run test/design-guard.test.ts` → `Test Files 1 passed (1), Tests 7 passed (7), Duration 127ms`。覆盖:DESIGN.md 宪法在位、动效唯一所有者、styles.css 裸色值基线、分层组件 CSS 裸色值、组件 TSX 裸色值、组件 TSX fontSize 数值字面量、组件 TSX 无 `<details>` 折叠。 |
|
||||
| 新样式是否会破坏棘轮 | PASS | 本次新增样式集中在 `styles.css`(`.rp-` 与 `.au-` 前缀类,见 `styles.css:519-580` 与 `597-601`)。所用色全部来自 `var(--*)` 令牌,字体使用 `var(--fs-ui-*)`,唯一裸色字面量 `#0e1116 / #101826 / #0b1f38 / #dfe4f2`(行 448)属 HEX_BASELINE_STYLES 已登记的登录品牌面板;无 `@keyframes`,新增 transition 仅出现在 styles.css(设计守护本文件豁免);新 TSX 文件(`RolePermissionsPanel.tsx`、`AccountUsersPanel.tsx`)无裸色字面量、无 fontSize 数值字面量、无 `<details>` 折叠——以 `@ui` Collapsible 替代(`RolePermissionsPanel.tsx` 全文件 `grep "<details" 0 hits`,同上 `AccountUsersPanel.tsx`、`adminRdmSections.tsx`)。 |
|
||||
| 可访问性(焦点、aria、键盘) | PASS(非阻塞观察下补充) | 矩阵表头 `aria-pressed`、分组 `role="region" aria-label="..."`、单元格 `aria-label="角色名 权限名"`、保存栏 `role="region" aria-label="未保存的权限修改"`、离开守卫 `roleDraftLeaveGuard`(`RolePermissionsPanel.tsx:21,425-435`),`AdminZone.tsx:107` 引用并把守卫返回 `false` 当作取消。键盘焦点:保存/收起按钮 `ref={savePermsRef}` 在 `restoreFocus` 中归还到 `savePerms` 或 `status`(`RolePermissionsPanel.tsx:466-475`)。`test/role-permissions-ui.test.ts:105-110` 断言离开守卫列出每个 dirty role 名。 |
|
||||
|
||||
### 重点 5 — 测试充分性 vs 验收场景 C01–C10 / C15
|
||||
|
||||
| 验收场景 | 覆盖位置 | 结论 |
|
||||
|---|---|---|
|
||||
| C01–C02 多角色矩阵编辑(跨分类与搜索保留草稿、一次提交) | `e2e/iam-perm-ux-live.playwright.ts:172-229`(活体真实服务)+ `test/role-permissions.test.ts:44-62` 与 `test/role-permissions-ui.test.ts:61-86`(重放语义) | PASS |
|
||||
| C03 创建联动(开启 project.create 补齐 4 项 /api/me 联动生效) | `e2e/iam-perm-ux-live.playwright.ts:231-265`(含 admin-grant 用户登入 + `linkedCapabilities.projectCreate=true`+ 4 个 actions 断言) | PASS |
|
||||
| C04 断链拒绝(创建项目开启时联动项不允许不可保存) | `e2e/iam-perm-ux-live.playwright.ts:267-303`(含 409 / roleVersion 不变 / permKey 不变断言) | PASS |
|
||||
| C05 / C06 / C11 / C12 关闭未来创建 / 浏览器层迁移 / 收据回放负控 | `TestCapabilityAssociationDisablingProjectCreateKeepsLinkedRows`、`TestCapabilityAssociationRejectsExplicitConflictAtomically`、`TestRolePolicyIdempotencyKeyCannotChangeRequest`、`TestReplaceRolePermissionsIsAtomicAndIdempotent`、`TestBatchPatchRolePermissionsStaleLimitAndIdempotency`、`TestBatchAssignUserRolesRebindsAndReplays` | PASS(store/HTTP 已覆盖,浏览器层未重复) |
|
||||
| C07 同名辨识(按账号辨识) | `e2e/iam-perm-ux-live.playwright.ts:305-331` + `test/role-members.test.ts:38-48` | PASS |
|
||||
| C08 批量分配(两人预览 → 一次提交) | `e2e/iam-perm-ux-live.playwright.ts:333-354` + `TestBatchAssignUserRolesRebindsAndReplays` | PASS |
|
||||
| C09 409 整批不写且保留选择 | `e2e/iam-perm-ux-live.playwright.ts:356-387`(含保留选择 `已选 2 人` + 角色回退断言) | PASS |
|
||||
| C10 复制并分配(独立维护) | `e2e/iam-perm-ux-live.playwright.ts:389-419`(含源 roleVersion/permKey 不变、bindingCount-1) | PASS |
|
||||
| C13 成员共享读取 / C16 日常实例采用 | 依赖 L1 U7;C16 未授权部署 → 任务卡 NOT_RUN | 非阻塞,按任务卡记录处置 |
|
||||
| C14 键盘/读屏人工走查 | 由 design-guard + 实现约定 + 窄屏 helper 单测覆盖;人工走查未执行 | 非阻塞,按任务卡 NOT_RUN 处置 |
|
||||
| C15 幂等 / 超额负控 | `TestBatchAssignUserRolesRejectsWholeBatch`(101 人、停用、缺失、停用角色、重复用户)、`TestCopyAssignRejectsWithoutResidue`(101 人、缺 `roles.assign` 等) | PASS |
|
||||
|
||||
无负控缺失阻塞项。
|
||||
|
||||
### 重点 6 — 安全与权限边界
|
||||
|
||||
| 子项 | 结论 | 依据 |
|
||||
|---|---|---|
|
||||
| 新增端点门禁(view / manage / assign) | PASS | `roles.go:130`(`POST /api/admin/roles/permissions/preview`)→ `system.permissions.view`;`roles.go:156`(`POST /api/admin/roles/permissions/batch`)→ `system.permissions.manage`;`roles.go:200`(`POST /api/admin/users/role-assignments`)→ `system.roles.assign`;`roles.go:217-223`(`POST /api/admin/users/role-assignments/preview`)→ `system.users.view` AND `system.roles.view`;`roles.go:240-249`(`POST /api/admin/roles/copy-assign`)→ 三动作全部 `system.roles.manage` + `system.permissions.manage` + `system.roles.assign`;`account_search.go:248-263`(`GET /api/admin/roles/{roleId}/members`)→ `system.users.view` AND `system.roles.view`。HTTP 层 `requireSystemAction` → store 层 `requireSystemActionTx`(同一 tx 内重新判定)`role_policy.go:1692-1720`,与既有 requireSystemAction 契约一致。`TestSystemUserActionsDoNotImplyRoleOrPermissionManagement`、`TestDelegatedUserActionsReachTransactionalStoreChecks`、`TestRolePermissionPreviewRequiresPermissionViewOverHTTP`、`TestBatchRoleAssignmentPreviewAndCommitOverHTTP`、`TestCopyAssignRoleOverHTTP`、`TestRoleMembersFilterBeforePagingAndBindCursor` 均覆盖门禁。 |
|
||||
| 成员目录最小信息 | PASS | 新端点 `GET /api/admin/roles/{roleId}/members` 仅返回 `RoleMember`(userId, owner, name, enabled, stateVersion, assignedAt, bindingVersion,`role_policy.go:1035-1045`),不含 profile.contact/isAdmin/凭据等。`TestRoleMembersFilterBeforePagingAndBindCursor` 末尾断言响应不包含 `profile` / `isAdmin`。光标绑定 role + filter + actor + directory revision,无法跨角色/参数复用(`account_search.go:296-307`)。 |
|
||||
| 预览只读无副作用 | PASS | `PreviewRolePermissions` 与 `PreviewUserRoleAssignments` 用 `tx.Begin()` + `defer tx.Rollback()`,无 `tx.Commit()`(`role_policy.go:665-686`、`1126-1159`)。`TestPreviewRolePermissionsIsReadOnly` 三组变更前后快照比对,断言 `roleVersion/epoch/receipts/permissions` 完全一致。`TestRolePermissionPreviewRequiresPermissionViewOverHTTP` 显式断言 viewer 调用 PUT 写端点被拒(`role_authorization_test.go:114-178`)。 |
|
||||
| 用户列表 search 端点最小信息 | 部分(详见非阻塞建议) | `GET /api/admin/users/search` 仍返回完整 `accountProfileJSON`(含 phone/email/staffNo/department/notes/isAdmin/initializationState),但 `searchAdminUsers` 在 actor 额外拥有 `system.roles.view` 时追加 `role` 投影(`account_search.go:194-237`);本任务用此端点做批量分配 picker 时无可避免看到全 profile(仅用于选定人员),非新增行为,非本任务评审焦点。 |
|
||||
| 受保护动作不可降级到角色矩阵 | PASS | `normalizePermissionChanges` 在 `descriptor.Protected=true` 时拒绝写入(`role_policy.go:566-569`),`TestCatalogRejectsUnknownAndProtectsManagementActions` 验证所有 `system.*` 管理动作 `Protected=true`。前端 `catalog.filter(item => !item.protected)`(`RolePermissionsPanel.tsx:521`)从矩阵排除这些动作。 |
|
||||
|
||||
---
|
||||
|
||||
## 非阻塞建议(不阻塞 PR 合入;可作为后续增量)
|
||||
|
||||
1. **跨角色 row-lock 顺序与 deadlock**:`PatchRolePermissionsBatch` / `AssignUserRolesBatch` / `CreateRoleCopyAndAssign` 内对角色行 `FOR UPDATE` 的顺序由请求 `items` 切片决定,未按 roleID 排序。`policyWriteBatch` 仅对 actor + target users 按 owner 排序;跨 actor + 跨事务的不同 `items` 顺序理论上可能触发 PG 死锁检测(事务回滚,非数据损坏)。建议:批量入口按 roleID 升序排序 `items` 后再入 fn,或由 `policyWriteBatch` 在 fn 之前排序 items。非阻塞,仅在极高并发 + 不同顺序组合下产生瞬时回滚。
|
||||
|
||||
2. **`searchAdminUsers` 返回完整 profile**:批量分配 picker 在选择用户时不需要 `phone / email / staffNo / department / notes / isAdmin / passwordSet / initializationState`;可考虑按 view 端点(`system.users.view`)与 assign 端点(`system.roles.assign`)返回裁剪字段,或新增 `fields` 显式控制响应字段(现有 `fields` 仅控制过滤字段)。非阻塞,属既有端点行为。
|
||||
|
||||
3. **`roleDraftLeaveGuard.current = null` 在 unmount 时由 effect cleanup 复位**:`RolePermissionsPanel.tsx:431` 的 effect cleanup 会在依赖变化(`dirtyKey` 变化)时立即把 `current` 置 null;这与"新守卫被设置"的顺序是先 set 后清,存在极短的 `current === null` 间隙(毫秒级)。在 AdminZone 返回按钮的 click 期间几乎不会被命中;如需更稳,可改用 `useRef` 直接保存守卫函数 + `useCallback`,避免 effect 驱动的 reset。非阻塞。
|
||||
|
||||
4. **`canManagePermissions` 取 view 失败时不显示空态**:当 `permissionCatalog()` 拒绝时,`load` 会清空 `catalogFailed=true` 并在选中角色之后追加错误(`RolePermissionsPanel.tsx:501-507`)。与原 DR10 备注一致(保留部分失败可见性),实现正确;建议把 `editable` 同步置 `false` 并在 `focus()` 内显式提示当前矩阵不可编辑,以避免在慢网络下"已切换单角色视图 → 单元格短暂可编辑 → catalog 失败回填"造成闪烁。属 UX 微调,非阻塞。
|
||||
|
||||
5. **`PATCH /api/me/profile` 与权限 patch 两条路径的 `adoptRoleSnapshot` 语义**:当 `PATCH /admin/roles/{id}`(资料 / 启停)成功后 `setDrafts(prev => adoptRoleSnapshot(prev, selected.roleVersion, updated))`(`RolePermissionsPanel.tsx:488`)继承新 roleVersion;但权限草稿在资料/启停 patch 之后仍属同一角色 → 同一基线;当前实现对 `adoptRoleSnapshot` 的判定是"当前基线 roleVersion === 期望 roleVersion 时采纳新 row",逻辑正确,仅在资料 patch 与权限 patch 并发时存在 `expectedVersion != 实际 version` 的窗口(被下一次 save 的 409 路径正确处理)。建议在 `adoptRoleSnapshot` 注释中明示"用于 PATCH 后的非权限写成功继承"。非阻塞。
|
||||
|
||||
---
|
||||
|
||||
## 不属于本任务范围(按任务卡 NOT_RUN 处置,未列入阻塞/建议)
|
||||
|
||||
- C13 成员共享读取(依赖 L1 U7)
|
||||
- C14 完整键盘 / 读屏人工走查
|
||||
- C16 日常实例采用(未授权部署)
|
||||
|
||||
---
|
||||
|
||||
## 引用文件位置索引
|
||||
|
||||
- Backend:`.task/feature-iam-role-permission-linked-capabilities-perm-ux.md` → 组件 commit `modules/backend @ 4130ad78`
|
||||
- 关键文件:`modules/backend/internal/store/role_policy.go`、`modules/backend/internal/server/roles.go`、`modules/backend/internal/server/account_search.go`、`modules/backend/internal/authz/catalog/catalog.go`、`modules/backend/internal/authz/service.go`、`modules/backend/pkg/extension/role_policy.go`
|
||||
- 单测:`modules/backend/internal/store/role_policy_test.go`、`modules/backend/internal/server/role_authorization_test.go`、`modules/backend/internal/authz/catalog/catalog_test.go`
|
||||
- Frontend:组件 commit `modules/frontend @ 46a63f02`
|
||||
- 关键文件:`modules/frontend/src/components/RolePermissionsPanel.tsx`、`modules/frontend/src/components/AccountUsersPanel.tsx`、`modules/frontend/src/components/adminRdmSections.tsx`、`modules/frontend/src/rolePermissions.ts`、`modules/frontend/src/roleMembers.ts`、`modules/frontend/src/permissionExplanations.ts`、`modules/frontend/src/api.ts`、`modules/frontend/src/styles.css`
|
||||
- 单测:`modules/frontend/test/design-guard.test.ts`、`modules/frontend/test/role-permissions.test.ts`、`modules/frontend/test/role-permissions-ui.test.ts`、`modules/frontend/test/role-members.test.ts`、`modules/frontend/test/permission-explanations.test.ts`、`modules/frontend/test/account-users-panel.test.ts`
|
||||
- 活体:`modules/frontend/e2e/iam-perm-ux-live.playwright.ts`
|
||||
- Dev 父仓:当前 `feature/iam-role-permission-linked-capabilities-perm-ux @ 234ce30`(基线 90317d02;组合 gitlink `modules/backend @ 4130ad7` + `modules/frontend @ 46a63f0`)
|
||||
+1
-1
Submodule modules/backend updated: 646dc57673...4130ad78ae
+1
-1
Submodule modules/frontend updated: 6db528df10...46a63f0221
Reference in New Issue
Block a user