Skip to content

Commit 3705c67

Browse files
authored
test(app-showcase): configuration-surface test round — admin position UI, position→permission-set mapping, developer authoring chain (#2915)
Second round of ADR-0090 permission-model testing covering the configuration surfaces (checklist sections N/O/P): - N: admin configures positions via Setup UI (list, create form, related lists, non-admin denial, built-in anchor delete protection) — 6 cases - O: position→permission-set mapping end-to-end (bind via junction, assign holder, live grant/revoke observed from the assignee's session) — 9 cases - P: developer authoring chain (definePermissionSet → build → boot reprojection, FLS end-to-end with visual proof, access-matrix snapshot gate, package-set 403 write-through, runtime-set lifecycle, seed idempotency) — 7 cases New e2e/permission-model-admin.spec.ts (14 tests, all green; full permission suite 25/25) + 11 evidence screenshots + report §4 configuration-surface details and two new observations (cookie-less FLS render fail-open, lookup column shows target API name).
1 parent 607aaf4 commit 3705c67

17 files changed

Lines changed: 478 additions & 13 deletions

examples/app-showcase/docs/test/permission-model-test-checklist.md

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,61 @@
139139
- agent 主体交集规则(需 MCP OAuth 通道;运行时已实施,showcase REST 无法直接构造)。
140140
- 层级深度 `own_and_reports/unit/unit_and_below`(企业版 hierarchy-security;开放版 fail-closed)。
141141
- 到期授权/职责分离/环境晋升(文档 §10 明确"planned")。
142-
- Access-matrix 快照门(已由 `os compile` 与仓库 CI 覆盖,见 access-matrix.json)。
142+
143+
---
144+
145+
# 第二轮:配置面测试(管理员配岗位 / 岗位映射权限集 / 开发者定义权限集)
146+
147+
> 第一轮(A~L)测的是**运行时行为**:给定配置,权限层序是否按设计裁决。
148+
> 本轮(N~P)测的是**配置链路本身**:三种角色各自怎么把配置放进系统、配置如何生效。
149+
>
150+
> | 角色 | 配置面 | 通道 |
151+
> |---|---|---|
152+
> | 系统管理员 | 岗位、岗位↔权限集绑定、用户指派 | Setup 应用 UI(Access Control 组)/ REST `sys_*` |
153+
> | 应用开发者 | 权限集、岗位声明、共享规则 | 代码 `definePermissionSet`/`definePosition``defineStack` → 构建产物 |
154+
> | 引擎 | 声明→运行时投影 | `bootstrapDeclaredPositions` 种子;ADR-0094 write-through 投影 |
155+
156+
## N. 管理员配置岗位(Setup UI,视觉实测)
157+
158+
入口:`/_console/apps/setup/sys_position`(Setup 应用 → Access Control → Positions;应用级门 `setup.access`)。
159+
160+
| # | 用例 | 步骤 | 预期(截图) |
161+
|---|---|---|---|
162+
| N1 | 岗位列表页 | admin 打开 Positions | 网格列 Display Name/API Name/Default Position/Updated At;视图 tabs Active/Default/Custom/All;New 按钮 + Edit inline;含 7 个 showcase 岗位 + 内置 platform_admin/org_* + everyone/guest 锚点行 |
163+
| N2 | 新建岗位表单 | 点 New | 弹窗字段:Display Name*、API Name*(机器名)、Description、Permissions(JSON,遗留)、Active、Default Position(自动指派新用户)、Delegatable(ADR-0091 D3 说明文字) |
164+
| N3 | UI 建岗位 | 填 qa_lead / QA Lead → Create | 列表出现 QA Lead;REST 查 `managed_by` 非 system |
165+
| N4 | 岗位详情 overlay | 点击 Contributor 行(`?recordId=…`) | overlay 含基本信息 + **Holders**(sys_user_position related list,Assign user 按钮)+ **Permission Sets**(sys_position_permission_set related list)两个 tab |
166+
| N5 | 非管理员负例 | ada(无 setup.access)访问 `/_console/apps/setup/sys_position` | 无 Setup 入口 / 页面拒绝(截图) |
167+
| N6 | 锚点行保护 | admin DELETE everyone 岗位行 | 拒绝(managed_by:system 不可删) |
168+
169+
## O. 岗位→权限集映射 + 指派端到端(管理员配置生效链)
170+
171+
绑定表 `sys_position_permission_set`(唯一键 position_id+permission_set_id,无独立导航,经岗位详情 related list 或 REST);指派表 `sys_user_position`(唯一键 user_id+position+organization_id,position 存**机器名**)。
172+
173+
| # | 用例 | 步骤 | 预期(截图) |
174+
|---|---|---|---|
175+
| O1 | 前置对照 | newbie 登录 console 看询价列表 | 空(仅 everyone 基线,private OWD)——复用 L4 |
176+
| O2 | 建岗位+绑集 | admin POST sys_position{qa_lead} → POST sys_position_permission_set{qa_lead↔showcase_auditor} | 两行创建成功;岗位详情 Permission Sets tab 出现 Showcase Auditor |
177+
| O3 | 指派用户 | admin POST sys_user_position{newbie, position:'qa_lead'} | 创建成功;`/auth/me/permissions`(newbie)positions 含 qa_lead,permissionSets 含 showcase_auditor |
178+
| O4 | 能力即时生效(UI) | newbie 刷新询价列表 | **全量可见**(showcase_auditor 的 viewAllRecords 经新岗位到达)——与 O1 截图前后对比 |
179+
| O5 | VAMA 只读边界 | newbie PATCH 他人询价 | 仍拒绝(viewAllRecords ≠ modifyAllRecords) |
180+
| O6 | 重复绑定 | 再 POST 同一 qa_lead↔showcase_auditor | 唯一约束拒绝 |
181+
| O7 | 解除指派→回收 | admin DELETE sys_user_position 行 | newbie 询价列表回到空;`/auth/me/permissions` 不再含 qa_lead |
182+
| O8 | 锚点门(引用) | 绑 VAMA 集到 everyone → 403 | H3 已测,配置面视角复核 |
183+
| O9 | 清理 | 删绑定行、qa_lead 岗位 | 环境还原 |
184+
185+
权限集详情页(`/_console/apps/setup/sys_permission_set?recordId=…`)预期:OWNING PACKAGE / MANAGED BY(package|user|system)、OBJECT/FIELD/SYSTEM/RLS/TAB 权限计数 +“Design in Studio”链接、**Assigned Users** related list(显示 `via position <name>` 的间接指派)。
186+
187+
## P. 应用开发者配置权限集(代码 → 构建 → 运行时投影)
188+
189+
authoring 面:`src/security/permission-sets.ts`(`definePermissionSet` from `@objectstack/spec`)、`src/security/positions.ts`(`definePosition`)、`objectstack.config.ts``positions:`/`permissions:`/`sharingRules:` 三键;构建产物 `dist/objectstack.json` 顶层 `positions[]`/`permissions[]`
190+
191+
| # | 用例 | 步骤 | 预期 |
192+
|---|---|---|---|
193+
| P1 | 声明结构走查 | 检查 showcase 8 集 7 岗位的 authoring 源码与 defineStack 接线 | 与设计文档 §3/§4 对齐(FLS/RLS/adminScope/isDefault 全形态覆盖) |
194+
| P2 | 构建产物 | `pnpm build` 后查 dist/objectstack.json | 顶层 positions/permissions 数组含全部声明 |
195+
| P3 | FLS 改动端到端 |`showcase_project.spent` editable:false→true → build → 重启 → ada 打开编辑弹窗 | spent 从灰显变可编辑(截图前后对比);`/auth/me/permissions` fieldPermissions 同步变化;**测后恢复源码** |
196+
| P4 | access-matrix 快照门 | 给某集加对象能力(如 auditor+allowDelete)→ `objectstack compile` | 矩阵漂移 → 构建失败;`--update-access-matrix` 重新生成后通过;**测后恢复** |
197+
| P5 | ADR-0094 投影(package 集) | admin REST PATCH showcase_contributor(managed_by:package) | 环境门编辑被拒/被重置为声明体(package 所有权保护,ADR-0086/0094) |
198+
| P6 | ADR-0094 投影(runtime 集) | admin REST POST 新权限集 → PATCH → DELETE | write-through 到元数据层;行 managed_by:'user';删除即元数据删除 |
199+
| P7 | 声明种子幂等 | 重启 dev 服务器两次 | sys_position/sys_permission_set 行数不变、不重复 |

examples/app-showcase/docs/test/permission-model-test-report.md

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88
## 1. 结论摘要
99

1010
- **REST 全量套件:49/49 断言通过**([scripts/perm-test.sh](./scripts/perm-test.sh),覆盖清单 A~K 节)。
11-
- **Playwright 套件:11/11 通过**([e2e/permission-model.spec.ts](../../e2e/permission-model.spec.ts):REST 代表用例 6 条 + console UI 实测 5 条,UI 全部留证截图)。
11+
- **Playwright 套件:25/25 通过**(行为面 [e2e/permission-model.spec.ts](../../e2e/permission-model.spec.ts) 11 条 + 配置面 [e2e/permission-model-admin.spec.ts](../../e2e/permission-model-admin.spec.ts) 14 条,UI 全部留证截图)。
12+
- **第二轮配置面测试(清单 N/O/P 节)全部通过**:管理员经 Setup UI 配岗位、岗位↔权限集映射端到端生效/回收、开发者代码定义→构建→运行时投影、access-matrix 快照门、ADR-0094 write-through。
1213
- 实测发现并修复 **2 个引擎级缺陷**(见 §3):写时 RLS check 漏判岗位适用域;`/auth/me/permissions` 身份解析漂移导致 console 假只读。修复后全套件复验通过。
13-
- 另有 6 项行为观察 / 测试注意事项(见 §4),均为符合设计或可解释的行为,记录备查。
14+
- 另有 8 项行为观察 / 测试注意事项(见 §5),均为符合设计或可解释的行为,记录备查。
1415

1516
## 2. 执行结果(按清单分节)
1617

@@ -27,8 +28,11 @@
2728
| I | guest 匿名 | ✅ 3/3 | 匿名数据 API 401;公共表单通道可提交 |
2829
| J | 委托管理(D12) | ✅ 6/6 | 子树 + 白名单 + 锚定三重校验全部生效 |
2930
| K | explain 引擎 | ✅ 2/2 | 逐层解释;查他人需管理员 |
30-
| L | console UI 实测 | ✅ 5/5 | **L5 首测失败 → 引擎缺陷②,修复后通过**;截图见 §5 |
31+
| L | console UI 实测 | ✅ 5/5 | **L5 首测失败 → 引擎缺陷②,修复后通过**;截图见 §6 |
3132
| M | 范围外 || 按设计文档标注跳过(planned / 企业版) |
33+
| N | 管理员配置岗位(Setup UI) | ✅ 6/6 | 列表/新建表单/详情 related lists/非管理员负例/锚点保护,截图 N1~N5 |
34+
| O | 岗位↔权限集映射端到端 | ✅ 8/8 | UI 建岗位→绑集→指派→能力生效→回收,前后对比截图 O1/O4/O7 |
35+
| P | 开发者权限集定义链路 | ✅ 7/7 | FLS 改动端到端(截图 P3a/P3b)、快照门、ADR-0094 投影、种子幂等 |
3236

3337
## 3. 实测发现的引擎缺陷(均已修复)
3438

@@ -46,16 +50,45 @@
4650
- **修复**:`resolveCtx` 整体替换为委托 `resolveAuthzContext`(与 REST 服务器、runtime 调度器同源),返回 shape 对齐 rest-server;`hono-plugin.test.ts` 增加 2 条回归测试(岗位授予进入权限集解析、匿名返回 `authenticated:false`)。
4751
- **复验**:ada `/auth/me/permissions` 返回 `positions:[org_member, contributor, everyone]``showcase_contributor` 权限集、`showcase_project.allowEdit:true` 且 budget FLS 仍锁定;L5 编辑弹窗 name/status/日期可编辑、budget/spent 灰显禁用(截图 L5)。
4852

49-
## 4. 行为观察与测试注意事项
53+
## 4. 配置面实测详情(第二轮,清单 N/O/P 节)
54+
55+
### 4.1 系统管理员怎么配置岗位(N 节,Setup UI)
56+
57+
- 入口:Setup 应用(应用级门 `setup.access`)→ **Access Control** 组 → Positions(`/_console/apps/setup/sys_position`)。
58+
- 列表页含 New / Edit inline / 视图 tabs(Active/Default/Custom/All);14 行 = 7 个 showcase 声明岗位 + 内置身份岗位(platform_admin/org_owner/org_admin/org_member)+ everyone/guest 锚点(截图 N1)。
59+
- 新建表单字段:Display Name*、API Name*(机器名)、Description、Permissions(JSON,遗留通道)、Active、Default Position(自动指派新用户)、Delegatable(ADR-0091 D3)(截图 N2);实测经 UI 创建 `qa_lead` 落库为普通行(截图 N3)。
60+
- 岗位详情 overlay 是配置枢纽:**Holders** tab(`sys_user_position`,Assign user 按钮)+ **Permission Sets** tab(`sys_position_permission_set`,Bind permission set 按钮)(截图 N4)。两张 junction 表无独立导航,这里是唯一 UI 入口。
61+
- 负例:ada(无 `setup.access`)访问 Setup URL 回落到默认应用壳,侧边栏无 Access Control 组,网格数据加载被数据面拒绝(截图 N5)。
62+
- 锚点保护:everyone/guest 行 `managed_by:system`,DELETE 被拒(N6)。
63+
64+
### 4.2 岗位怎么映射到权限集并生效(O 节,端到端)
65+
66+
完整链路实测:**UI 建岗位 qa_lead → REST 绑 showcase_auditor → 指派 newbie → 能力立刻生效 → 解除立刻回收**
67+
68+
- 绑定后岗位详情 Permission Sets tab 显示 `showcase_auditor` 行(截图 O2)。
69+
- 指派后 newbie 的 `/auth/me/permissions` 即含 `qa_lead` 岗位与 `showcase_auditor` 集;console 询价列表从****(截图 O1)变**7 条全量**(截图 O4)——viewAllRecords 经新岗位到达,无需重登。
70+
- VAMA 只读边界仍成立(newbie 改他人询价 → 4xx);重复绑定被唯一约束拒绝;解除指派后列表回空(截图 O7),证明授予与回收都是即时的。
71+
72+
### 4.3 应用开发者怎么配置权限集(P 节,代码 → 构建 → 运行时)
73+
74+
- **authoring**:`src/security/permission-sets.ts`(`definePermissionSet`)+ `positions.ts`(`definePosition`)→ `objectstack.config.ts``positions:`/`permissions:`/`sharingRules:` 三键;构建产物 `dist/objectstack.json` 顶层数组(P1/P2)。
75+
- **FLS 改动端到端(P3)**:把 `showcase_project.spent``editable:false→true``pnpm build` → 重启 → ada 的 `/auth/me/permissions` 立即反映;编辑弹窗中 spent 从灰显(截图 P3a)变可编辑、budget 仍锁(截图 P3b);REST 写入持久化。改回后行为复原——**声明是权威源,boot 重投影覆盖运行时行**(ADR-0094)。
76+
- **access-matrix 快照门(P4)**:FLS 改动不驱动矩阵(矩阵只含对象级能力);给 auditor 加 `allowDelete``pnpm build` 失败:`✗ Access matrix drift (1 change) — 'showcase_auditor' gains delete on 'showcase_inquiry'`;`objectstack build --update-access-matrix` 重新生成,git diff 恰为一行 `"delete": false→true`(diff 即评审工件,ADR-0090 D6)。
77+
- **ADR-0094 write-through(P5/P6)**:admin PATCH package 所有的集(showcase_contributor)→ **403**,报错明示 ADR-0086 两门分离(“change it by editing its package and re-publishing”);REST 新建运行时集 → 行 `managed_by:'user'`,PATCH/DELETE 全生命周期经元数据层 write-through 正常。
78+
- **种子幂等(P7)**:两次重启后 sys_position=13、sys_permission_set=15,不重复。
79+
80+
## 5. 行为观察与测试注意事项
5081

5182
1. **共享规则是写时物化**:条件 sharing 在记录写入/更新时物化到 `sys_record_share` 并重算;改规则后需 touch 记录才生效。测试共享用例要先写一次记录。
5283
2. **岗位↔权限集绑定是管理员显式配置**:岗位本身不携带权限,必须有 `sys_position_permission_set` 绑定行;漏绑时用户静默落到 everyone 基线(无报错)。
5384
3. **能力门先于记录范围**:对象级能力未授予时直接 403,OWD/sharing/VAMA 完全不参与 —— 与设计文档 §2 的层序一致。
5485
4. **guest 孤儿行**:公共表单通道创建的记录 `owner_id=null`,private OWD 下无深度/VAMA 的用户(包括提交者)都不可见、不可改删,只有 org 深度或 VAMA 持有者能处理 —— 询价场景正好合理,但建业务对象时要留意。
5586
5. **scope 层拒绝的响应形态**:记录范围层拒写返回 **HTTP 400 + body `code:FORBIDDEN`**(非 403);能力门层才是 403。断言时按层区分。
5687
6. **审批锁与权限测试相互作用**:`budget>100000 && changed` 的审批规则会锁记录(RECORD_LOCKED),**管理员恢复现场的写入同样触发**,需由待批人 reject/approve 解锁后再继续。权限用例应避开会触发审批的字段组合,或测试后显式清理审批请求。
88+
7. **console FLS 渲染依赖 cookie 会话**:只向 localStorage 注入 bearer token(无 better-auth cookie)时,编辑表单的 FLS 禁用态不生效——全字段渲染为可编辑(UI 层 fail-open;数据面仍强制,写入照样被剥离)。真实登录总带 cookie,影响限于 token 注入类自动化;写 UI 测试必须像 spec 的 `signIn` 一样把 cookie 一并注入。
89+
8. **绑定 related list 显示权限集 API name**:岗位详情 Permission Sets 网格的 lookup 列解析目标对象 `name` 字段,显示 `showcase_auditor` 而非 display label(与"网格 lookup 只认 name 字段"的既知行为一致),断言/截图预期按 API name 写。
5790

58-
## 5. UI 实测截图索引([screenshots/](./screenshots/))
91+
## 6. UI 实测截图索引([screenshots/](./screenshots/))
5992

6093
| 截图 | 用例 | 证明 |
6194
|---|---|---|
@@ -65,8 +98,19 @@
6598
| [L3b-audrey-private-notes-vama.png](./screenshots/L3b-audrey-private-notes-vama.png) | L3 | audrey 可见他人 private_note(VAMA 越过 private OWD) |
6699
| [L4-newbie-inquiries-empty.png](./screenshots/L4-newbie-inquiries-empty.png) | L4 | newbie 询价列表为空(private OWD,无深度/共享/VAMA) |
67100
| [L5-ada-project-budget-fls-locked.png](./screenshots/L5-ada-project-budget-fls-locked.png) | L5 | ada 编辑弹窗:budget/spent 灰显禁用(FLS),status/日期可编辑 |
68-
69-
## 6. 复跑方法
101+
| [N1-admin-positions-list.png](./screenshots/N1-admin-positions-list.png) | N1 | Setup→Positions 列表:14 岗位(声明+内置+锚点)、New/视图 tabs |
102+
| [N2-position-new-form.png](./screenshots/N2-position-new-form.png) | N2 | 新建岗位表单:label/name/description/permissions/active/is_default/delegatable |
103+
| [N3-position-qa-lead-created.png](./screenshots/N3-position-qa-lead-created.png) | N3 | UI 创建的 QA Lead 出现在列表 |
104+
| [N4-position-detail-related-lists.png](./screenshots/N4-position-detail-related-lists.png) | N4 | 岗位详情 overlay:Holders + Permission Sets related lists、Assign user |
105+
| [N5-nonadmin-positions-denied.png](./screenshots/N5-nonadmin-positions-denied.png) | N5 | ada 无 setup.access:无 Access Control 导航,网格加载被拒 |
106+
| [O1-newbie-inquiries-before.png](./screenshots/O1-newbie-inquiries-before.png) | O1 | 指派前:newbie 询价列表空(Nothing here yet) |
107+
| [O2-qa-lead-bound-showcase-auditor.png](./screenshots/O2-qa-lead-bound-showcase-auditor.png) | O2 | qa_lead 详情 Permission Sets tab 显示 showcase_auditor 绑定 |
108+
| [O4-newbie-inquiries-after-assignment.png](./screenshots/O4-newbie-inquiries-after-assignment.png) | O4 | 指派后:同一 newbie 看到 7 条全量询价(viewAllRecords 经岗位到达) |
109+
| [O7-newbie-inquiries-revoked.png](./screenshots/O7-newbie-inquiries-revoked.png) | O7 | 解除指派后:列表回空(即时回收) |
110+
| [P3a-ada-spent-fls-locked-before.png](./screenshots/P3a-ada-spent-fls-locked-before.png) | P3 | 改声明前:spent/budget 灰显禁用 |
111+
| [P3b-ada-spent-editable-after.png](./screenshots/P3b-ada-spent-editable-after.png) | P3 | 改声明+重建+重启后:spent 可编辑、budget 仍锁 |
112+
113+
## 7. 复跑方法
70114

71115
```bash
72116
# 1. 启动 showcase(dev,自动种子 + seed admin)
@@ -80,9 +124,11 @@ docs/test/scripts/perm-setup.sh # BASE 默认 http://localhost:3777
80124
# 3. REST 全量套件(49 断言)
81125
docs/test/scripts/perm-test.sh
82126

83-
# 4. Playwright(REST 代表用例 + UI 截图;spec 自我供给,亦可独立于 2/3 运行)
127+
# 4. Playwright(行为面 + 配置面两个 spec 共 25 条;自我供给,亦可独立于 2/3 运行)
84128
PERM_BASE_URL=http://localhost:3777 \
85129
pnpm exec playwright test --config playwright.permission.config.ts
86130
```
87131

88-
Playwright 配置不设 `PERM_BASE_URL` 时会自行拉起 :3000 后端(CI 模式)。该 spec 已从默认 smoke 配置排除(`playwright.config.ts``testIgnore`),仅经上述专用配置显式运行。
132+
Playwright 配置不设 `PERM_BASE_URL` 时会自行拉起 :3000 后端(CI 模式)。两个 spec 已从默认 smoke 配置排除(`playwright.config.ts``testIgnore: 'permission-model*.spec.ts'`),仅经上述专用配置显式运行。
133+
134+
P 节的构建链路用例(P3/P4)是破坏性的(改源码、重建、重启),不进 spec,按 §4.3 手工复跑:改 `src/security/permission-sets.ts``pnpm build`(观察快照门)→ 重启 → 验证 → 还原。
-1.37 KB
Loading
129 KB
Loading
103 KB
Loading
129 KB
Loading
99.4 KB
Loading
69.2 KB
Loading
62.8 KB
Loading
102 KB
Loading

0 commit comments

Comments
 (0)