fix: 用专用权限收窄经理 AP 访问(09-07-manager-ap-access-scope)
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

这个提交包含在:
2026-09-08 07:21:37 +08:00
父节点 99167127b5
当前提交 58dcee08f7
共修改 18 个文件,包含 410 行新增和 8 行删除
+50
查看文件
@@ -18,6 +18,56 @@
redacted form.
- `scripts/qa/check-trellis-governance.mjs` must pass before commit.
## Scenario: AP Full-Ledger Reading Is Not Payment Review
### 1. Scope / Trigger
AP financial summary and reimbursement ledger reads, not general entity visibility.
### 2. Signatures
- Permission: `finance.ledger.view` in both server and client role maps.
- Reads: `GET /api/ap-operations` and `GET /api/ap-operations/reimbursement-ledger`.
- Separate export: `POST /api/exports/ap-operations`.
### 3. Contracts
Grant ledger reading to `ceo`, `finance`, `finance_mgr`, not `manager` or
`system_admin`. Preserve emergency `admin` wildcard semantics. Both reads require
the dedicated permission before data queries; query filters cannot confer access.
Frontend route and navigation projections use the same permission. Keep manager
payment review and multi-role permission unions intact; never grant CEO payment
management to preserve read access. Export keeps its existing independent
`payment.batch.manage` / `risk.review.manage` rule.
### 4. Validation & Error Matrix
Feature enabled: missing ledger permission returns 403 / `PERM_001`, with no
financial query. Feature disabled: the existing 409 refusal may occur first.
CEO/finance/finance manager and manager+finance retain reading. Old payment or
risk permissions alone no longer imply ledger reading.
### 5. Good / Base / Bad Cases
- Base: manager keeps payment review but cannot enter AP.
- Good: CEO reads AP without acquiring export or payment-write rights.
- Bad: hiding a menu while leaving either backend read unrestricted.
### 6. Tests Required
Route injection checks both reads, filters and zero queries on refusal; role and
navigation tests cover CEO and mixed roles. Browser checks cover manager direct
refusal and preserved payment entry, plus allowed AP readers. Export and payment
review regressions remain separate from ledger authorization.
### 7. Wrong vs Correct
Wrong: `payment.batch.review` implies all-company financial visibility, or a CEO
role exception is added to AP handlers. Correct: use `finance.ledger.view` in role
maps and read guards. This AP-only change does not alter the manager's six
`*.view_all` permissions: reimbursement-list all-record visibility remains an
explicit deferred decision, not a completed scope restriction.
## Scenario: Bank Account Snapshots And Payment-Order Export
### 1. Scope / Trigger
@@ -0,0 +1,33 @@
# AP 全量读取最小收窄方案(专用权限修订已批准)
## 1. 读取授权
获批修订:新增专用权限 `finance.ledger.view`,仅授予 ceo、finance、finance_mgr;两个 AP GET 及前端入口统一只检查此权限。manager 不授予;不存在 CEO 角色特判,旧付款管理、复核、风险和支付权限均不再独立授予 AP 读取。既有应急 admin 通配符语义不变。
前后端各有静态 ROLE_PERMISSIONS,需要同步三类角色和相关测试,不是只改服务端三行。无数据库权限迁移,不授予 CEO 写权限,不按人员账号特判,不采用 manager deny-list。AP 导出仍使用原有独立权限。
## 2. 接入范围
- `server/src/routes/apOperations.js`:两个 GET 共用收紧后的守卫,数据查询之前拒绝。功能门禁先后顺序保持现状。
- `client/src/auth/permissions.ts`:集中表达 AP 全量读取判据;不修改通用付款复核判据或其他角色权限集合。
- `client/src/router/index.tsx`、AP 页面入口及服务端 `workspaceSettings`:只同步 AP 投影,核对移动和桌面入口,不能全局替换相同的旧 permission 数组而误伤付款路由。
- `server/src/routes/exports.js`:现有导出不含复核授权,本轮不更改;验证拒绝和已有正例。
- 不在 AP 中另造“经理相关单据子集”;经理继续通过既有审批/付款复核路径处理职责内工作。若核实必须借 AP 才能完成复核,报告导航依赖并交审,不默认扩充数据范围。
## 3. 验证矩阵
| 身份 | AP 汇总/台账 | 付款复核 | AP 导出 |
| --- | --- | --- | --- |
| employee | 拒绝 | 按现有权限拒绝 | 拒绝 |
| manager,仅复核职责 | 拒绝 | 保留原业务条件 | 拒绝 |
| ceo | 保留全量读取 | 保留原业务条件 | 不新增 |
| finance / finance_mgr | 专用权限允许 | 不改变角色和独立复核条件 | 按既有两项权限 |
| manager+已授权财务角色 | 允许 | 原条件不变 | 按财务授权 |
| 仅系统管理员 | 不新增 | 不新增 | 不新增 |
| 仅持旧付款管理/复核/风险/支付权限 | 拒绝 | 按原权限 | 按既有两项权限 |
API 验证包括数据查询未发生、筛选不绕过;前端验证包括直连、菜单和复核入口。现有经理 AP 拒绝断言保留,必要时拆分用例使后续角色断言真正执行,不以局部通过冒充整个权限审计通过。
## 4. 安全与交付边界
不查询生产日志,不回填历史访问结论。不触碰既有门禁交审文件;若实施必须修改已交审测试,先保存对应增量基线,单独交本 task diff,不混报旧包。实现后独立只读复核,提交、推送和部署继续另行授权。
@@ -0,0 +1,57 @@
# 执行计划
## 2026-09-08 独立复核与提交授权
- 用户已独立复核通过本 task 的 18 文件包,包括单权限守卫、原有获准角色无误伤、经理复核保留、前后端正反测试与 AP-only 边界;不是主会话自检替代独立复核。
- 用户本轮授权独立提交 AP 修复,并与门禁包及已有两个 Trellis 提交一起推送;不包含部署或归档。本节之后的“待复核/未提交”表述保留为上轮交审时的历史状态。
- 推送不是发布;尚未授权生产部署,不能宣称生产 AP 权限已经收窄。经理其他 view_all 仍保留,不能宣称全域只看相关单据。
- 后续若准备未通过完整候选验收的发布方案,必须另行明确批准合同、验证缺口、候选、回滚及验收条件;本次提交推送授权不是整道候选门禁的豁免。
## 当前状态
- 2026-09-07:方案获批并修订为专用 finance.ledger.view,已 start;inline 实施。已保存全仓起始快照于本 task 的 artifacts,旧门禁包不得改写。
- 已读取 AP 两个 GET、导出守卫、角色权限和前端路由/页面判据,形成 PRD 与设计。
- 门禁任务不关闭、不归档、不自动提交;本轮实现及验证已完成,等待用户独立只读权限复核。
## 执行结果
- 已同步前后端静态角色表:仅 ceo、finance、finance_mgr 新增 finance.ledger.view;manager/ceo 的复核权限和六个 view_all 不变。应急 admin 通配符不变。
- AP 两个 GET、前端路由、本地 fallback 导航、服务端导航注册表只使用专用读取权限;没有 CEO 角色分支。导出与付款复核实现未修改。
- 新增 AP 路由矩阵;角色/导航/导出单测补反例;既有浏览器经理 AP 可访问测试按获批口径改为直连拒绝,新增 CEO、finance、finance_mgr、manager+finance 读取正例,实际切换财务台账;合同用例正文未修改、未运行。
- 后端五文件 123/123 PASS(含 76 条付款批次报销回归);前端六条定向浏览器 6/6 PASS、零重试、trace。不是 full-local,不抵扣旧 58 条清单。
- typecheck、独立 artifacts 目录 build、Node 语法、diff 检查和 Trellis governance PASS;未覆盖 client/dist。build 保留既有 Browserslist 数据陈旧提示,没有更新依赖。
- 首轮扩展付款回归有 BIFF8 导出失败,默认 Python 缺 xlrd;使用已存在的隔离 Python(固定 BIFF8 依赖)后 123/123 通过。原始失败日志保留,不宣称任意环境都具备该依赖。
- 浏览器前置原生 online=true、无 cookie、登录表单可见、refresh=0;本次是本地 Vite + 合成 API 浏览器测试,不冒充真实后端浏览器闭环或部署验证。
- 旧门禁 33 文件逐一摘要核对全部不变。未提交、push、部署或操作生产;独占 Vite 服务已停止。独立只读复核待用户,不派 implement/check 代理,不以主会话自检冒充独立复核。
## 验证命令与证据
证据目录:`artifacts/qa-flow/manager-ap-access-scope/implementation-01/`。
```bash
cd server
PAYMENT_ORDER_PYTHON_BIN=<已存在的隔离BIFF8解释器> npm test -- tests/unit/routes/apOperations.permissions.test.js tests/unit/constants/permissions.test.js tests/unit/workspaceSettings.navigation.test.js tests/unit/routes/exports.apOperations.test.js tests/unit/routes/paymentBatches.reimbursement.test.js
cd ../client
npm run typecheck
E2E_MODE=local E2E_BASE_URL=http://127.0.0.1:23117 ./node_modules/.bin/playwright test tests/e2e/payment-reviewer-permissions.spec.ts --grep '只有付款复核职责|AP 专用读取权限' --retries=0 --trace=on --reporter=list --output=../artifacts/qa-flow/manager-ap-access-scope/implementation-01/browser-final-results
./node_modules/.bin/vite build --outDir ../artifacts/qa-flow/manager-ap-access-scope/implementation-01/build
cd ..
node scripts/qa/check-trellis-governance.mjs
git diff --check
```
日志:`server-tests-final.log`、`browser-tests-final.log`、`browser-preflight.json`、`typecheck-final.log`、`build.log`、`governance-final.log`。局部 6 条来源 spec 与 grep 如实记录,不能称整个 payment-reviewer-permissions spec 通过。
## 原批准步骤(保留顺序记录)
1. 加载权限、后端、前端相关稳定规约,冻结既有工作树和本 task 写入面。
2. 先补 AP 权限矩阵测试及 CEO/多角色/经理复核保留反例。
3. 最小收紧 AP 后端读取与前端入口投影,不改其他 view_all 或付款复核授权。
4. 执行针对性后端测试、前端类型检查及获批的本地合成浏览器验证;不默认重跑 full-local 或生产操作。
5. 对照 AC 审核并交独立只读权限复核;发现新的数据范围或正常复核依赖问题先报告,不扩修。
6. 获批后更新现有权限规约,交付明确保留的 view_all 开口。提交、推送、部署均不随实施授权自动进行。
## 待独立复核
- 复核代码、正反权限结果和范围;本轮不请求或执行生产变更。task 保持 in_progress。
- 明确保留:经理仍可从报销列表查看全部单据,本轮只收窄 AP 入口,未实现所有业务域“只看相关单据”。
@@ -0,0 +1,59 @@
# 经理 AP 财务台账与付款汇总访问收紧
## Goal
仅收窄 AP 财务台账与付款汇总的全量读取入口:只有经理/付款复核职责的账号不得借付款复核权限访问全公司 AP 数据;保留 CEO 全量读取、既有财务读取及经理正常审批和付款复核能力。
2026-09-07 用户已批准 AP-only 方案实施,附修订:使用 finance.ledger.view 专用权限,不使用 CEO 角色分支。仅授予 ceo、finance、finance_mgr;前后端同步。当前 in_progress;不查询生产、不部署。
## 已确认事实与边界
- 用户确认系统管理员、经理、财务、员工是不同业务角色;授权按角色和权限表达,不硬编码个人账号。
- `server/src/constants/permissions.js` 中 manager 和 ceo 均有 `payment.batch.review`;manager 另有六个 `*.view_all`。本轮不调整这些全量查看权限。
- `server/src/routes/apOperations.js` 的两个 GET 路由共用四选一读取守卫,其中包含 `payment.batch.review`;台账调用 `listFinanceLedger` 时只传 Prisma 与查询条件,没有传当前员工范围。
- `client/tests/e2e/permission-direct-access.spec.ts` 留有经理 AP 读取预期拒绝、实际返回 200 的本地合成测试证据。业务现已明确拒绝经理仅凭复核职责获取 AP 全量数据;不将此失败改成接受 200。
- 当前 AP 导出使用独立的 `payment.batch.manage` / `risk.review.manage` 守卫,不包含 `payment.batch.review`。本轮保留既有导出边界,不为 CEO 或经理新增导出权限。
- 未证明实际生产部署与当前源码一致,未证明经理访问过无关数据,不宣称发生过泄露。
## Scope
- 范围:AP 汇总、报销财务台账两个后端读取入口;对应前端直连保护、导航与入口投影;相关正反权限测试和既有权限规约。
- AP 导出仅作旁路负例和既有授权不变验证,不重构导出流程。
## Out of Scope
- 不修改报销、请假、考勤、采购、资产、项目的 `view_all`,不设计本人/部门/审批链可见范围,不重构付款批次自身的数据范围。
- 明确保留的开口:本轮完成后,经理仍可从报销列表查看全部报销。这是待业务另行决定的权限范围,不得在交付中宣称“经理只能看相关单据”已经全局实现。
- 门禁 33 文件包不变,58 条不逐条修,合同阶段 B、R1、场景 8 继续暂停。业务包接收确认独立待办,不属于本任务交付。
## Requirements
- R1:仅持经理/付款复核职责、没有其他 AP 全量授权的账号访问两个 AP GET 接口必须被后端拒绝,不返回汇总、台账或计数;不得仅靠隐藏前端菜单。
- R2:CEO、finance、finance_mgr 通过专用权限保留两个接口的全量读取;旧付款/风险权限码本身不再隐含读取。不得通过授予 CEO 付款管理等写权限实现读取,也不得删除经理的 `payment.batch.review`。
- R3:经理原有正常审批、付款复核及独立复核约束不变。前端提供既有付款复核入口,不以恢复 AP 全量访问解决导航问题。
- R4:多角色按显式授权并集合成;manager 与已授权财务角色组合不得因包含 manager 被一票否决。系统管理员身份不自动等同财务权限。
- R5:查询参数、日期、关键词、分页不能绕过 AP 读取守卫。导出保持原权限,不将 CEO 全量读取等同导出或写入授权。
- R6:实施与部署分别批准;实现后须只读独立权限复核,主会话验证不能冒充独立复核。
## Acceptance Criteria
- [ ] 普通员工和仅经理角色对两个 GET 均被拒绝;功能开启时为 403,关闭时可保留既有无数据 409。拒绝路径不调用财务数据查询。
- [ ] CEO、finance、finance_mgr 正例通过;manager+财务组合保留其财务授权;仅系统管理员不新增授权。
- [ ] 日期、状态、关键词等查询组合不能改变拒绝结果;经理 AP 导出仍拒绝,既有导出角色不退化。
- [ ] 经理付款复核正例及原有独立复核负例通过;不扩大其付款执行、组批或其他写权限。
- [ ] 前端 AP 直连被阻止、导航不暴露不可用入口,经理付款复核入口仍可达;CEO 和财务 AP 入口保留。
- [ ] 六个 view_all 未修改,交付明确“仅 AP 收窄”,不冒充所有业务单据范围收窄。
- [ ] 针对性验证与只读独立复核有记录;无部署、生产查询或写入,除非另获明确授权。
## Test Plan
- Fastify inject:两个 AP GET、筛选组合、旧权限码反例、专用权限正例、feature-off、无权时零财务查询。
- 角色与导航单测:经理、CEO、财务、财务经理、系统管理员、多角色;保持经理付款入口。
- 既有 AP 导出及付款复核回归:保留独立授权、制单/复核分离及 CEO 无新增导出权。
- 本地合成浏览器:经理直连 403 和零 AP 请求、CEO/财务/财务经理/混合角色进入 AP 和台账、经理付款页保留;零重试、trace,排除合同用例及其他红项。
- 前端 typecheck、隔离目录 build、语法、diff、治理检查。扩大付款回归需既有固定 BIFF8 Python 依赖,不改产品逻辑绕过环境缺项。
- 独立只读权限复核待用户;inline 主会话自检不冒充独立复核。
## Notes
- 本文及 design.md 的专用权限修订已获实施批准;验证结果见 implement.md,不是生产状态证明。
@@ -0,0 +1,39 @@
{
"id": "manager-ap-access-scope",
"name": "manager-ap-access-scope",
"title": "经理 AP 财务台账与付款汇总访问收紧",
"description": "使用 finance.ledger.view 收紧 AP 全量读取,保留 CEO 财务读取与经理复核;其他 view_all 和导出授权不变,不部署",
"status": "in_progress",
"dev_type": null,
"scope": "AP-only 专用读取权限、对应前后端投影和权限验证",
"package": null,
"priority": "P2",
"creator": "shunli",
"assignee": "shunli",
"createdAt": "2026-09-07",
"completedAt": null,
"branch": "main",
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [
"server/src/constants/permissions.js",
"server/src/routes/apOperations.js",
"server/src/services/workspaceSettings.js",
"client/src/auth/permissions.ts",
"client/src/router/index.tsx",
"client/src/components/navigation/NavLinks.tsx",
"client/tests/e2e/payment-reviewer-permissions.spec.ts",
"server/tests/unit/routes/apOperations.permissions.test.js",
"server/tests/unit/constants/permissions.test.js",
"server/tests/unit/workspaceSettings.navigation.test.js",
"server/tests/unit/routes/exports.apOperations.test.js",
".trellis/spec/hyb-oa/security-privacy.md"
],
"notes": "",
"meta": {}
}