Skip to content

2026-09-20 迭代 — 验收用例(TC) ​

依据 PRD 原文 与 技术方案。PRD 定义产品行为,技术方案定义实现契约; 本文只把两者转成可重复执行、可留证据的判定条件,不另行扩大范围。 本文与 PRD、技术方案一同评审;预跑与证据归档由 T01(lamolabs-docs#77) 执行, 预跑完成后在 §8 回填结果与证据链接。

任务追踪:权限平台总 PRD nexo-im-pc#58 · 组织 Project

1. 执行规则 ​

  • 编号含义:P = 端到端主路径,B = 边界与安全,N = 非功能硬阈值,MTG = 上会用例子集。 编号一经发布不得复用或重排,后续任务的「测试追踪」按本编号引用。
  • 会上只执行 §3 的 8 条,每条判定时间不超过 3 分钟;其余用例在 2026-09-19 预跑阶段完成。
  • 一条用例的全部判定条件均满足才算通过;部分满足、无证据或只能口头说明都记为不通过。
  • 因环境、资产或评审结论缺失而无法执行时记为阻塞,不允许把"未测"写成"通过"。
  • 涉及时间的用例以服务端时间为准;毫秒级阈值(N01/N03)用服务端毫秒时间戳差计算,不用人工秒表。
  • 三项硬阈值必须实测取数,不得估算;数据由预跑阶段采集,会上只复核证据。
  • 鉴权判定类用例必须同时断言 allow 与 denyReason 两个字段,只看 allow 不算通过。
  • 允许用缩短周期的验收配置(如 PERM_RECONCILE_INTERVAL_SECONDS=10)模拟兜底场景,但必须同时证明生产默认值(300)仍正确配置。

1.1 本期冻结的验收基线 ​

项目验收值
鉴权判定顺序① 主体在职 → ② 显式拒绝 → ③ 权限点/命名空间/组织状态 → ④ 直接授权 → ⑤ 角色路径;任一前置失败即拒,不进后续
拒绝原因枚举subject_inactive / deny_hit / pp_invalid / no_grant / exception,SDK 原样透传
鉴权性能p99 < 10ms(服务端计时;压测数据集 100 主体 × 50 角色 × 500 权限点,连续 1000 次混合请求)
判定超时PERM_CHECK_STATEMENT_TIMEOUT_MS=200,超时按 exception 拒绝
组合深度上限PERM_COMPOSITION_MAX_DEPTH=5,创建时强拒不截断
定时兜底周期PERM_RECONCILE_INTERVAL_SECONDS=300(≤5min)
SDK 连接层兜底连接错误/超时/非 2xx/畸形响应 → {allow:false, denyReason:'exception'},不抛异常;SDK 超时 PERM_CHECK_TIMEOUT_MS=2000
单有效行UNIQUE(subject_type, external_id) WHERE active;epoch 首建 0,停用不递增,启用/调岗新建行递增
通用 deny 清除作废旧有效行(停用/调岗)同事务清除该用户通用权限显式拒绝,删除快照进 audit_outbox
审计事件4 类(grant_change/check_deny/pp_change/role_change)100% 产生 outbox 行;只记 deny 不记 allow;deny 类异步投递不阻塞主流程
不可变字段权限点 scope/org_id、角色 org_id、app 主体 bound_namespace_id:应用层错误码 IMMUTABLE_FIELD + DB 触发器双层拒改
错误码以技术方案 §9.1 错误码表为准,用例断言具体 code 而非只看 HTTP 状态

2. 环境前置 ​

  • nexo-perm-api(test 环境,3002)+ nexo_perm 库迁移与初始化脚本已执行,超管(PERM_SUPERADMIN_USER_ID)可登录管理台
  • nexo-perm-console 已部署并经壳(nexo-app)入口可达;独立访问方式亦可登录
  • nexo-account test 实例可用(供主体创建校验与状态通知联动);具备可自由停用/启用的测试账号 ≥3 个
  • 造数与压测脚本随证据归档到 evidence/scripts/,结构化取数落 evidence/logs/,管理台截图落 evidence/console/
  • 只读 psql 访问 nexo_perm(断言 subjects.epoch、outbox、inbox 用)

3. 上会用例(MTG 子集,每条 ≤3 分钟) ​

P01 P03 P05 B01 B03 B06 B09 N01 共 8 条;其余在 09-19 预跑完成,会上只复核证据。

4. 主路径用例(P01—P10) ​

P01 业务方接入全链路(组织→命名空间→权限点→角色→授权→鉴权) ​

  • 前置条件:超管已登录管理台;nexo-account 有 ACTIVE 测试账号 u_t1。
  • 操作步骤:① 建组织 org-a;② 建命名空间 ns-task 与 app 主体(绑 ns-task);③ 在 ns-task 注册组织权限点 task.close(scope=org,绑 org-a);④ 建角色 任务操作员(org-a)并关联 task.close;⑤ 建 user 主体 u_t1(主组织 org-a)并分配角色;⑥ 以 app 主体身份调 /internal/check {userId:'u_t1', permissionPoint:'task.close'}。
  • 预期结果:⑥ 返回 {allow:true}(无 denyReason 字段);①-⑤ 每步写操作在 audit_outbox 各有对应事件行(pp_change/role_change/grant_change);subjects 中 u_t1 行 epoch=0, active=true。
  • 证据采集:各步请求/响应 JSON;audit_outbox 按 dedupe_key 的 psql 断言输出;check 响应原文。
  • 对应:PRD FR-1/2/3/5/6/7 · 上会 MTG-01

P02 直接授权链路与有效期到期失效 ​

  • 前置条件:P01 环境;u_t1 未经角色持有 ns-task 下另一权限点 task.export。
  • 操作步骤:① 注册 task.export(org 权限,org-a);② 给 u_t1 直接授权 task.export,expiresAt = now+7d;③ check → 记录;④ psql 将该行 expires_at 改为 now-1s;⑤ 再 check。
  • 预期结果:③ {allow:true}(④ 直接授权命中,不经角色);⑤ {allow:false, denyReason:'no_grant'}(过期行在判定中不生效,无需物理清理)。
  • 证据采集:两次 check 响应;direct_grants 行前后对比。
  • 对应:PRD FR-6/7 · 总 PRD §4.4

P03 显式拒绝优先于放行 ​

  • 前置条件:P01 完成(u_t1 经角色已有 task.close)。
  • 操作步骤:① check 确认 allow;② 给 u_t1 设置 task.close 显式拒绝;③ check;④ 解除显式拒绝;⑤ check。
  • 预期结果:③ {allow:false, denyReason:'deny_hit'}(角色路径存在仍拒);⑤ 恢复 {allow:true};②④ 各产生 grant_change outbox 行,其中解除事件为异步投递口径(delivered_at 可后置但行必存在)。
  • 证据采集:三次 check 响应;outbox 两行断言。
  • 对应:PRD FR-6/7 · 上会 MTG-02

P04 角色组合动态聚合与实时生效 ​

  • 前置条件:org-a 下有角色 R-base(关联 task.close)与空角色 R-comp。
  • 操作步骤:① R-comp 组合 R-base;② 给 u_t2(org-a)分配 R-comp;③ check task.close;④ 给 R-base 新关联 task.export;⑤ 立即 check task.export(不做任何刷新/等待);⑥ 调 GET /api/roles/{R-comp}/effective-permissions。
  • 预期结果:③⑤ 均 {allow:true}(来源变更自动跟随,实时生效);⑥ 聚合预览含两个权限点且 via:'composed' 标注来源 R-base,与判定结果一致。
  • 证据采集:check 响应(带服务端时间戳证明④⑤间隔 <5s);聚合预览响应。
  • 对应:PRD FR-4 · 总 PRD §4.2

P05 账号停用联动(通知路径) ​

  • 前置条件:u_t1 为 ACTIVE 且 P03 后持有 task.close 权限与一条通用权限显式拒绝(先建通用权限点 platform.demo 并对 u_t1 设 deny)。
  • 操作步骤:① 记录 subjects 当前行(id/epoch);② 在 nexo-account 停用 u_t1;③ 等通知消费(≤5s)后 check task.close;④ psql 断言主体行与 explicit_denies。
  • 预期结果:③ {allow:false, denyReason:'subject_inactive'};④ 原行 active=false 且 epoch 不变、无新行;u_t1 的通用权限 deny 行已删除(org 权限 deny 保留);outbox 含 deny 清除快照事件与联动告警事件。
  • 证据采集:停用前后 subjects/explicit_denies 对比;check 响应;inbox 行(event_type='disabled', processed_at 非空)。
  • 对应:PRD FR-9 · 上会 MTG-03

P06 重新启用(复职)走新建行 ​

  • 前置条件:P05 后 u_t1 处于失活。
  • 操作步骤:① 在 nexo-account 重新启用 u_t1;② 消费后 psql 查 subjects 全历史;③ check task.close。
  • 预期结果:② 出现新行 active=true, epoch=旧+1,旧行保持 active=false;③ {allow:false, denyReason:'no_grant'}——旧行上的角色关联不复活,需重新分配(重新分配后 check 恢复 allow,作为附加断言)。
  • 证据采集:subjects 历史行输出;两次 check 响应。
  • 对应:PRD FR-9 · 总 PRD §4.7

P07 主组织变更(调岗)后旧组织权限失效 ​

  • 前置条件:u_t3(org-a)经角色持有 org-a 组织权限点,且有一条通用权限显式拒绝;组织 org-b 已建。
  • 操作步骤:① 触发 org_changed 通知(orgId=org-b);② 消费后 psql 断言;③ check 旧 org-a 组织权限点。
  • 预期结果:② 新行 org_id=org-b, epoch+1,旧行失活;通用 deny 已清除;③ {allow:false}(no_grant:新有效行组织与 pp 归属不符,且旧行角色关联已脱钩)。
  • 证据采集:subjects 历史、explicit_denies 前后、check 响应。
  • 对应:PRD FR-9 · 总 PRD §4.1/§4.7

P08 权限点下线与同名重建(激活原实体) ​

  • 前置条件:task.close 上有一条对 u_t2 的显式拒绝。
  • 操作步骤:① 下线 task.close;② u_t2 check;③ 同名重新激活(POST .../activate);④ u_t2 check。
  • 预期结果:② {allow:false, denyReason:'pp_invalid'};③ 为同一实体(id 不变)翻回 active;④ {allow:false, denyReason:'deny_hit'}——原显式拒绝保留永效、继承命中;outbox 含两条 pp_change。
  • 证据采集:权限点 id 前后一致的断言;两次 check 响应。
  • 对应:PRD FR-2 · 总 PRD §4.3

P09 硬删除连删与同名重建(全新实体) ​

  • 前置条件:task.export 下存在显式拒绝 1 条、直接授权 1 条、角色关联 1 条。
  • 操作步骤:① 调 impact 接口记录 N/M/K;② 带 confirmCode 硬删;③ psql 断言三关联表;④ 同名重新注册 task.export;⑤ 原被 deny 用户 check。
  • 预期结果:① N/M/K 与实际一致(1/1/1);③ 三表对该 pp 零残留(CASCADE);④ 新实体(新 id);⑤ {allow:false, denyReason:'no_grant'}——旧 deny 不继承(不是 deny_hit);outbox 含 deny 快照事件(异步口径)。
  • 证据采集:impact 响应;删除前后三表计数;新旧 id 对比;check 响应。
  • 对应:PRD FR-2 · 总 PRD §4.3

P10 管理台主流程走查(F02—F08) ​

  • 前置条件:test 环境管理台可达;壳内与独立访问均已登录。
  • 操作步骤:按 F02 组织/命名空间 → F03 权限点 → F04 角色与组合 → F05 授权 → F06 主体 → F08 我的权限顺序,每页完成一次主操作闭环;构造一次后端业务拒绝(如跨组织分配)观察提示。
  • 预期结果:每页主操作成功且列表即时刷新;业务拒绝显示明确中文提示(非透传英文 code/500);F08 页面无任何写操作入口;壳内切换到 IM 再切回状态不丢。
  • 证据采集:每页操作后截图(evidence/console/);拒绝提示截图。
  • 对应:PRD F 系列任务 · F07 壳挂载

5. 边界与安全用例(B01—B14) ​

B01 判定顺序短路(第一步优先) ​

  • 前置:失活主体 u_x 同时存在:显式拒绝 1 条 + 未过期直接授权 1 条。
  • 步骤:check 该用户任一相关权限点。
  • 预期:{allow:false, denyReason:'subject_inactive'}——不是 deny_hit(证明①先于②执行)。
  • 证据:check 响应 + subjects/explicit_denies/direct_grants 三表状态断言。
  • 对应:总 PRD §4.5 · 上会 MTG-04

B02 组合成环三型强拒 ​

  • 步骤:分别尝试建:① A 组合 A(自引用);② 已有 A→B 再建 B→A;③ 已有 A→B→C 再建 C→A。
  • 预期:三次均 HTTP 4xx,code='COMPOSITION_CYCLE';role_compositions 无新增行;outbox 各有 role_change rejected 事件。
  • 证据:三次响应 + 表计数 + outbox 断言。
  • 对应:PRD FR-4

B03 深度上限强拒(不截断) ​

  • 前置:PERM_COMPOSITION_MAX_DEPTH=5;已构造深度恰为 5 的链 R1→R2→R3→R4→R5,R5 关联权限点 pp_deep,用户经 R1 可判 pp_deep allow(先断言此点)。
  • 步骤:尝试建 R0 组合 R1(使深度变 6)。
  • 预期:创建被拒 code='COMPOSITION_DEPTH_EXCEEDED';既有 5 层链判定不受影响(pp_deep 仍 allow)——证明"强拒新建"而非"截断存量"。
  • 证据:拒绝响应;前后两次 pp_deep check。
  • 对应:PRD FR-4 · 上会 MTG-05

B04 同组织校验矩阵(四拒一放) ​

  • 步骤:分别尝试:① org-b 用户分配 org-a 角色;② org-b 角色关联 org-a 组织权限点;③ org-b 角色组合 org-a 角色;④ org-b 用户直接授权 org-a 组织权限点;⑤ org-b 角色关联通用权限点。
  • 预期:①-④ 均拒 code='CROSS_ORG';⑤ 成功(通用权限跨组织可关联)。
  • 证据:五次响应汇总表。
  • 对应:PRD FR-3/4/6 · 总 PRD §5.3

B05 不可变字段双层拒改 ​

  • 步骤:① API 层分别 PATCH 权限点 scope、角色 org_id、app 主体 bound_namespace_id;② psql 直连各执行一条对应 UPDATE。
  • 预期:① 三次均 code='IMMUTABLE_FIELD';② 三条 UPDATE 均被触发器 RAISE EXCEPTION 'IMMUTABLE_FIELD' 拒绝——应用层与 DB 层双层防线各自独立成立。
  • 证据:API 响应 + psql 报错原文。
  • 对应:design §7.2

B06 跨业务方探测防护(命名空间推导覆盖自报) ​

  • 前置:业务方 A 的 app 主体(绑 ns-a);ns-b 下有权限点 b.secret,某用户在 B 侧对其有授权。
  • 步骤:以 A 的 HMAC 身份调 check,permissionPoint:'b.secret'(载荷无命名空间字段,无从自报;如实现允许多余字段则附带伪造 namespace:'ns-b' 再试一次)。
  • 预期:{allow:false, denyReason:'pp_invalid'}——按 A 的 bound_namespace 推导查 (ns-a, 'b.secret') 不存在;伪造字段被忽略;outbox 有 check_deny 事件。
  • 证据:check 响应;outbox 事件行。
  • 对应:总 PRD §4.5 · 上会 MTG-06

B07 组织停用联动禁用角色 ​

  • 前置:org-c 下 3 个 active 角色,用户 u_c 经其一持有组织权限点 c.op。
  • 步骤:① 停用 org-c(记录响应 disabledRoleCount);② psql 断言角色状态;③ u_c check c.op。
  • 预期:① disabledRoleCount=3;② 三角色同事务全部 disabled(中途构造失败场景则整体回滚,可用事务注入验证一次);③ {allow:false, denyReason:'pp_invalid'}(组织停用在③步拒)。
  • 证据:响应、角色状态、check 响应。
  • 对应:PRD FR-1 · 总 PRD §4.5 第三步

B08 命名空间停用全局拒绝 ​

  • 步骤:停用 ns-task 后对其下任意权限点 check。
  • 预期:{allow:false, denyReason:'pp_invalid'}。
  • 证据:check 响应。
  • 对应:总 PRD §4.5 第三步

B09 fail-closed:断库与超时(服务端 + SDK 双层) ​

  • 步骤:① 暂停 nexo_perm 容器后经 SDK 调 checkPermission;② 恢复库,注入慢查询(pg_sleep 于判定路径或将 PERM_CHECK_STATEMENT_TIMEOUT_MS 调至 1ms)再调;③ 全程观察 SDK 是否抛异常。
  • 预期:① SDK 返回 {allow:false, denyReason:'exception'} 不抛异常;② 服务端按 exception 拒绝(statement_timeout 生效);③ 业务方进程无未捕获异常;恢复正常配置后 check 回归 allow。
  • 证据:SDK 调用日志;服务端错误日志(脱敏);恢复后 check 响应。
  • 对应:PRD FR-7 / B12 · 总 PRD §5.2 · 上会 MTG-07

B10 定时兜底双方向防错 ​

  • 前置:验收配置 PERM_RECONCILE_INTERVAL_SECONDS=10(同时截图生产配置=300)。
  • 步骤:复活方向——psql 将在职用户 u_r 的 perm 主体行手工置 active=false(模拟漏通知),等兜底发现;在兜底核对完成后、消费前(暂停消费循环制造窗口),把 nexo-account 侧 u_r 停用;恢复消费。失活方向——构造核对快照后该用户被 enabled 事件替换新行的时序(先采快照、后投 enabled 事件、再放行兜底校正消费)。
  • 预期:复活方向:消费时复查 account 发现已 DISABLED → 跳过复活,u_r 保持失活(无新行);失活方向:epoch 与快照不一致 → 跳过校正,新行保持 active。两方向均在日志留痕 skip 原因。
  • 证据:inbox 行(reconcile_fix 与 skip 日志);subjects 历史断言。
  • 对应:PRD FR-9 · 总 PRD §4.7

B11 状态通知幂等 ​

  • 步骤:同一 eventId 的 disabled 通知连投 3 次;随后同一 eventId 的 enabled 通知连投 3 次。
  • 预期:inbox 仅各存 1 行;disabled 仅一次翻转(epoch 不变),enabled 仅新建 1 行(epoch 恰 +1,无 +2/+3);三次 HTTP 响应均 202。
  • 证据:inbox 计数;subjects 历史行。
  • 对应:design §12

B12 平台接口自身鉴权与鸡生蛋闭环 ​

  • 步骤:① 无 platform.* 权限的普通用户调建组织接口;② 禁用某 app 主体后以其身份调 check;③ 以超管(仅初始化数据)完成:建组织管理员角色 → 分配给普通用户 → 该用户成功执行本组织管理操作、且对他组织操作被拒。
  • 预期:① code='FORBIDDEN' 附 denyReason:'no_grant';② FORBIDDEN 附 subject_inactive;③ 闭环成立,行权范围过滤生效(本组织成功/他组织 FORBIDDEN)。
  • 证据:各步响应;超管初始化数据 psql 断言。
  • 对应:PRD FR-8 · design §13

B13 硬删除影响面与双重确认 ​

  • 步骤:① impact 接口返回与 psql 实数对比;② 管理台硬删弹窗:不输入 confirmCode 直接提交;③ 输错 code 提交;④ 输对提交。
  • 预期:① N/M/K 完全一致;②③ 提交按钮不可用/被拒;④ 成功。前端展示含"同名重建为全新实体,不继承旧显式拒绝"提示文案。
  • 证据:impact 响应与 psql 对比;三种状态截图。
  • 对应:PRD FR-2 / F03 · 上会(并入 P09 复核)

B14 失活主体授权拦截 ​

  • 步骤:对失活用户分别尝试:分配角色、直接授权、设置显式拒绝、重复创建主体。
  • 预期:前三者 code='SUBJECT_INACTIVE';重复创建 code='SUBJECT_EXISTS'(失活行禁翻活,提示走状态联动)。
  • 证据:四次响应。
  • 对应:PRD FR-5/6 · 总 PRD §4.7

6. 非功能硬阈值(N01—N03) ​

N01 鉴权延迟 p99 < 10ms ​

  • 取数方式:造数脚本生成 100 主体 × 50 角色(含 3-5 层组合链)× 500 权限点;压测脚本连续 1000 次 check,请求构成 allow:五类 deny ≈ 6:4 混合(各 deny 类型均覆盖);服务端毫秒时间戳差计 p50/p95/p99,脚本与原始数据归档。
  • 阈值:p99 < 10ms(记录 p50/p95 供基线回归)。
  • 对应:PRD §6 · 上会 MTG-08

N02 停用回收时效 ​

  • 取数方式:nexo-account 停用操作时间戳 → perm 主体行 updated_at(通知路径);另测通知被人为丢弃场景下由兜底闭合的时延(验收配置 10s,按比例换算生产 300s 上界成立)。
  • 阈值:通知路径联动生效 ≤ 1s;丢通知场景 ≤ 兜底周期上界。
  • 对应:PRD FR-9 · 总 PRD §3.1

N03 deny 审计不阻塞判定 ​

  • 取数方式:在投递 worker 注入 2s 延迟(或暂停 worker 使 outbox 积压),以 N01 同口径重跑 1000 次 check,对比 p99。
  • 阈值:与 N01 基线差异 < 1ms;积压期间 check 全部正常返回。
  • 对应:PRD FR-10 · 总 PRD §4.6

7. 证据目录约定 ​

  • evidence/scripts/:造数、压测、时序构造(B10)脚本,可重复执行
  • evidence/logs/:结构化取数(JSON),文件名按用例编号(如 n01.json、b10-reconcile.json)
  • evidence/console/:管理台截图,文件名按页面/用例(如 p10-f03-harddelete-confirm.png)
  • 敏感信息脱敏:HMAC 密钥不入库,token 只留前 8 位

8. 预跑结果(T01 于 09-19 回填) ​

预跑报告见 tc-evidence-preflight。预跑时 27 条:通过 22 / 不通过 1 / 阻塞 4。 后续复验:B09 已解除(修复 1b3c25b,复验证据 evidence/logs/b09-failclosed-reverify.json)。 2026-09-20 管理台上生产后补跑 P10 与 B13 的界面部分,两条均通过(证据在 evidence/console/)。 现为 通过 25 / 不通过 0 / 阻塞 2(B10、N02 的兜底方向,两者同因:需真实 nexo-account test 实例)。 下表 B09 行同时保留预跑原始结论与复验结果。 判定环境为集成分支 integration/2026-09-20(真实服务 3002 + 真实 nexo_perm 库), 判定接口与状态通知走真实 HTTP + 真实 HMAC,管理接口走真实 Bearer JWT(经 nexo-auth 登录链路); nexo-account 以 stub 扮演(仅影响 §2.2 所列面)。环境不允许的部分一律记为阻塞,未把「未测」写成「通过」。

编号结果证据备注
P01通过evidence/logs/p01-p03-b01-b05-b13.json、evidence/logs/e2e-integration-2026-09-20.txt①–⑤ 真实 HTTP 写链路;⑥ {allow:true};⑦ 三类审计事件落 audit_outbox;⑧ epoch=0, active=true。账号校验经 stub
P02通过evidence/logs/t01-cases.json未过期 → allow;expires_at 改到过去 → no_grant 且行仍在(无需物理清理)
P03通过evidence/logs/p01-p03-b01-b05-b13.json设 deny → deny_hit;解除 → 恢复 allow,explicit_denies 无残留
P04通过evidence/logs/p04-effective-permissions.json组合链 allow;来源新关联后 10ms 即时生效;聚合预览 via='composed'
P05通过evidence/logs/p05-p06-p07.jsonsubject_inactive、epoch 不变无新行、通用 deny 清除 / org deny 保留、inbox 已处理。账号侧发事件由脚本扮演
P06通过evidence/logs/p05-p06-p07.json新建行 epoch+1、旧行失活;旧角色不复活 → no_grant;重新分配后恢复 allow
P07通过evidence/logs/p05-p06-p07.json新行 org_id=org-b, epoch+1、旧行失活、通用 deny 清除;查旧组织权限点被拒。账号侧发事件由脚本扮演
P08通过evidence/logs/t01-cases.json下线 → pp_invalid(无 deny 用户);activate 回原实体(id 不变);原 deny 永效 → deny_hit。用例问题见报告 §4.4
P09通过evidence/logs/p09-impact.jsonimpact 1/1/1 与实际一致;硬删 204 且三关联表零残留;同名重建新 id、旧 deny 不继承 → no_grant
P10通过(含 1 项未执行步骤)evidence/console/f02-org-created.jpg、f02-namespace-created.jpg、f03-permission-point-registered.jpg、f04-composition-prefilled.jpg09-20 于生产管理台(nexo-perm-console.pages.dev → nexo-perm.snailuu.cn,perm-api 0.1.3-alpha.1)以平台管理员走查六页,每页主操作闭环成功且列表即时刷新:F02 建组织/命名空间、F03 注册组织权限点(scope 联动出「所属组织」必填、「范围一经声明不可改」提示在位)、F04 建两角色并组合(编辑器正确回填已有组合,依赖新增的 GET /api/roles/:id/compositions,见 nexo-perm-api#29)、F05 权限全景 8 点带「角色路径」来源、F06 建 app 主体(「bound_namespace 一经创建不可变」提示在位)、F08 只读视图无任何写入口。「构造跨组织分配观察拒绝提示」这一步未执行:管理台在选择阶段即按组织过滤,跨组织角色不出现在下拉中,UI 上无法构造该拒绝——属前端防呆,非缺陷,但该步骤需改写,见下方「用例问题」
B01通过evidence/logs/p01-p03-b01-b05-b13.json失活 + deny + 未过期直授 → subject_inactive(①先于②)
B02通过evidence/logs/t01-cases.json自引用 / 两节点 / 三节点回路三型均 COMPOSITION_CYCLE,无新增行
B03通过evidence/logs/t01-cases.json5 层链 allow;建第 6 层强拒 COMPOSITION_DEPTH_EXCEEDED;存量链仍 allow(强拒非截断)
B04通过evidence/logs/b04-matrix.json四拒一放:分配角色 / 关联组织权限点 / 组合 / 直接授权 → CROSS_ORG;通用权限点 → 成功
B05通过evidence/logs/p01-p03-b01-b05-b13.json角色 orgId、app boundNamespaceId 经 HTTP 改 → IMMUTABLE_FIELD;权限点无 PATCH 端点(404)。DB 触发器层见报告 §4.3
B06通过evidence/logs/p01-p03-b01-b05-b13.json跨业务方探测 → pp_invalid(按 X-Nexo-App 推导命名空间);伪造字段被 strip;check_deny 审计行存在
B07通过evidence/logs/p01-p03-b01-b05-b13.json停用组织 → disabledRoleCount=3、三角色全 disabled;其下权限点判定 → pp_invalid
B08通过evidence/logs/t01-cases.json停用前 allow(反证非恒拒)→ 停用 ns 后 pp_invalid → 恢复后 allow
B09通过(复验;预跑为不通过)evidence/logs/b09-failclosed-reverify.json(复验);evidence/logs/b09-failclosed.json、evidence/logs/b09-crash.log(预跑失败原始记录)预跑:停库 / statement_timeout=1ms 时服务进程直接退出(Unhandled 'error' event on BoundPool,src/db/index.ts 缺 pool.on('error')),未返回 {allow:false, denyReason:'exception'};期望「HTTP 200 + exception」,实际「连接被拒、/health 不可达」。复验(修复 1b3c25b 后):基线 / 停库 / 暂停容器 / 恢复 / statement_timeout=1ms 五档全通过,SDK 全程不抛异常,服务进程 restarts=0。附带发现:describeError 想单独带出 SQLSTATE 但 drizzle 把 code 包在 .cause,日志里 57014 从未出现(P3 可观测性,见复验证据 extraFinding)
B10阻塞evidence/logs/p01-p03-b01-b05-b13.json(部分)已证生产默认周期 = 300s。双方向时序未测:需要真实账号中心可变在职状态作核对对端,固定状态 stub 构造不出「核对快照后账号侧变化」。解除:提供可用 nexo-account test 实例(可变状态测试账号 ≥3)
B11通过evidence/logs/b11-idempotency.json同 eventId 的 disabled 连投 3 次:inbox 仅 1 行、响应均 202、仅一次翻转;enabled 连投 3 次:仅新建 1 行、epoch 恰 +1
B12通过evidence/logs/b12-closure.json无平台授权时建组织 → 403 FORBIDDEN;对照:恢复后同一请求 201;超管建组织管理员角色 → 分配成功。「第二个真实用户」维度受本地账号限制(报告 §4.5.2),以主体停用隔离
B13通过evidence/logs/p01-p03-b01-b05-b13.json(API 部分)、evidence/console/b13-confirm-empty.jpg、b13-confirm-mismatch.jpgAPI 侧通过:impact 计数与库内实数一致、confirmCode 错 → VALIDATION_FAILED。管理台三态 09-20 补齐:① 未输确认码 → 提交按钮置灰;② 输错 → 仍置灰并给出中文提示「请输入完全一致的权限点标识:」+ 该权限点 code;③ 输对 → 删除成功、列表即时刷新。弹窗含 impact 计数(N/M/K)与要求文案「同名重建为全新实体,不继承旧显式拒绝」
B14通过evidence/logs/t01-cases.json失活主体:分配角色 / 直接授权 / 设显式拒绝 → SUBJECT_INACTIVE;重复建主体 → SUBJECT_EXISTS
N01通过evidence/logs/n01-t01-rerun.jsonp99 2.711ms(阈值 10ms,余量 3.7×);p50 1.228 / p95 1.885;期望不符 0/1000。B08 原读数 p99 2.248ms 同档
N02阻塞evidence/logs/n02-latency.json(通知路径)通知路径 最大 14ms ≤ 1s,通过;丢通知 → 兜底闭合方向未测(同 B10:需真实 account 状态作核对对端)。解除:同 B10,并把 PERM_RECONCILE_INTERVAL_SECONDS 调到验收值归档原始时间戳
N03通过evidence/logs/n03-audit-backlog.json积压态(check_deny 新增 36 行未投递)p99 2.424ms vs N01 基线 2.711ms,差 −0.287ms < 1ms;积压期间 1000/1000 正常返回