完成五人团队正式内用最小收口(07-18-small-team-internal-readiness)

这个提交包含在:
2026-07-18 15:46:14 +08:00
父节点 58c8decef9
当前提交 5d0a5f9a38
共修改 134 个文件,包含 10790 行新增和 1413 行删除
+77
查看文件
@@ -16,3 +16,80 @@ task before modifying approval behavior.
not drift because rules changed later.
- Reviewer review is required after changes touching workflow, status machine,
approval rules, permissions or migrations.
## Scenario: System Operator Is Not A Business Approver
### 1. Scope / Trigger
- Trigger: adding or changing OA account administration, role assignment,
approval eligibility, payment review or emergency-superuser behavior.
### 2. Signatures
- Daily responsibility roles: `manager`, `ceo`, `finance`, `system_admin`.
- Emergency role: `admin` (`*`), hidden from ordinary assignment UI.
- System permissions: `system.accounts.manage`, `system.roles.manage`.
- Payment reviewer permission: `payment.batch.review`.
### 3. Contracts
- `system_admin` manages ordinary accounts and system configuration only; it
does not imply leadership, finance, payroll, personnel-sensitive access or
business approval authority.
- A payment reviewer must have both `payment.batch.review` and an explicit
`manager` or `ceo` role. `admin` or `system_admin` alone is not a normal
reviewer.
- `manager` does not imply that every employee reports to that account;
reporting scope remains in `direct_manager_id`.
- A non-emergency system administrator cannot edit their own roles or grant,
revoke or import `admin` / `system_admin`. Role changes remain audited.
- Employee aggregate endpoints must not treat `system.settings` as personnel-
sensitive authority. Salary summaries require `payroll.manage`; performance
summaries require `system.users_sensitive`; unauthorized branches issue no
salary/performance query and return only the non-sensitive summary shape.
### 4. Validation & Error Matrix
- System administrator changes own roles -> `403 PERM_001` before role writes.
- System administrator changes a protected role -> `403 PERM_001`, zero role
and audit writes.
- Reviewer has permission but lacks `manager|ceo` -> `403 PERM_001`.
- Batch creator attempts review -> deterministic `409 VALID_001`.
- `system_admin` opens ordinary employee statistics -> salary is null,
performance is empty and both sensitive query families remain untouched.
### 5. Good/Base/Bad Cases
- Good: one person may explicitly hold both `manager` and `system_admin`, while
each route evaluates the responsibility required for that action.
- Base: `system_admin` creates or disables an ordinary account without seeing
payroll, bank, identity or employee-contract content.
- Bad: treat `admin` as the default company leader or use `system_admin` as an
approval fallback because the team is small.
### 6. Tests Required
- Role matrix for employee, manager, CEO, finance, system administrator and
emergency administrator.
- Negative tests for self-role change, protected-role assignment/import and
permission-only payment review.
- Negative employee-statistics test using the real `system_admin` permission
set, plus positive tests for explicit payroll/personnel-sensitive authority.
- Reviewer read-only review after changing role or approval semantics.
### 7. Wrong vs Correct
#### Wrong
```js
if (hasPermission(actor, "*")) allowPaymentReview();
```
#### Correct
```js
if (!hasPermission(actor, "payment.batch.review")
|| !actor.roleCodes.some((role) => role === "manager" || role === "ceo")) {
throw forbidden();
}
```
@@ -383,3 +383,82 @@ const currentSession = await tx.authSession.findUnique({ where: { id: sessionId
if (!currentSession || currentSession.revoked_at) return invalidRefresh();
await rotateSession(tx, currentSession);
```
## Scenario: Small-Team Payment Batch Consistency
### 1. Scope / Trigger
- Trigger: changing payment-batch creation, review, execution, retry,
cancellation, receipt evidence or the legacy direct-payment path.
### 2. Signatures
- Candidate API: `GET /api/payment-batches/candidates?q=&page=&pageSize=`.
- State APIs: `POST /api/payment-batches/:id/review`,
`POST /api/payment-batches/:id/execute`,
`POST /api/payment-batches/:id/cancel` with `{ reason }`.
- Receipt invalidation accepts `{ reason }` and retains the record.
- DB: `payment_batches.cancelled_reason TEXT NULL`.
- Isolated concurrency gate:
`bash scripts/qa/run-web-gates.sh payment-concurrency-local`.
### 3. Contracts
- Normal flow is finance maker/executor plus one independent `manager|ceo`
reviewer; the maker cannot review their own batch.
- Lock the batch before state checks, then lock source entities in stable order
and recheck status, amount and reservation inside the same transaction.
- When `payment_batch_v1` is enabled, an approved reimbursement can enter only
one active batch and the legacy direct-payment route must not form a second
write path.
- Retry processes only `pending|failed` items. Existing `executed` items never
repeat payment writes, follow-ups or notifications. Persisted execution
summary is recomputed from every final item state; the audit separately
records the current attempt.
- Cancellation requires a 2-500 character reason. Receipt evidence is voided
with audit history, not physically deleted.
### 4. Validation & Error Matrix
- Missing review, maker-reviewer conflict or terminal state -> `409 VALID_001`.
- Concurrent loser after a valid winner -> deterministic `409`, zero duplicate
state, audit or notification writes.
- Missing/invalid cancellation or void reason -> `400 VALID_001`.
- Source already paid, amount changed or reserved by another active batch ->
`409 VALID_001` and no partial batch mutation.
### 5. Good/Base/Bad Cases
- Good: one executor wins, a concurrent cancel or second executor receives
`409`, and the final payment/audit/notification counts remain one.
- Base: a failed batch retries only failed items and reaches `executed` with an
all-item final summary.
- Bad: count only the retry attempt in `execution_summary_json`, or allow the
direct route to pay while the same source is in a valid batch.
### 6. Tests Required
- Focused route tests for candidate authority, maker-checker, retry summary,
cancellation, receipt void and zero-side-effect refusals.
- Real disposable PostgreSQL races for double execute, execute/cancel, double
create and direct-payment/batch competition; assert unique audits and
notifications and zero residual test databases.
- Prisma validate, clean empty-database migration application and a read-only
reviewer after payment-state changes.
### 7. Wrong vs Correct
#### Wrong
```js
summary.successCount = successesInThisRetry;
batch.status = failuresInThisRetry ? "failed" : "executed";
```
#### Correct
```js
const finalSummary = summarizeFinalItemStatuses(finalItemStatuses);
batch.execution_summary_json = finalSummary;
batch.status = finalSummary.failedCount > 0 ? "failed" : "executed";
```
@@ -442,3 +442,77 @@ validate_isolated_target_before_writes
run_mutating_auth_regression
drop_owned_database_or_fail
```
## Scenario: Repository Local Candidate Gate
### 1. Scope / Trigger
- Trigger: declaring a checkout ready for internal deployment planning without
deploying it or using public, production or real-account evidence.
### 2. Signatures
- Full gate: `bash scripts/qa/run-local-candidate.sh full`.
- Browser-only rerun: `bash scripts/qa/run-local-candidate.sh focused`.
- Parse-only check: `bash scripts/qa/run-local-candidate.sh list`.
- Route contract: `scripts/qa/route-manifest.json`.
- Result verifier: `scripts/qa/verify-local-candidate-results.mjs`.
### 3. Contracts
- `full` alone may print `LOCAL_CANDIDATE_PASS`; `focused` prints
`FOCUSED_BROWSER_PASS`, and `list` prints `LIST_ONLY`.
- The gate accepts only loopback HTTP origins and canonical TCP ports. It uses
local synthetic fixtures, excludes `manual/live` specs and does not consume
historical Issue CSV as a candidate prerequisite.
- Every registered route is classified. Every core bundle has one stable,
source-bound witness; candidate spec basenames are unique.
- Every selected Playwright result must pass. Missing, duplicate, unknown,
skipped, multi-tag or wrong-source witnesses and any failed/skipped
acceptance test make the gate fail.
- The script's full pass proves only its local scope. A task changing payment
concurrency also requires the isolated PostgreSQL payment gate; deployment,
public checks and real-device behavior remain separate states.
### 4. Validation & Error Matrix
- Non-loopback URL, credentialed URL, path/query target or invalid port -> fail
before Vite/Playwright use.
- Unclassified/orphan route or missing core behavior/visual witness -> non-zero
scanner result.
- Playwright suite error, unexpected, failed or skipped test -> non-zero result
gate with basename-only redacted diagnostics.
- `focused` succeeds -> useful rerun evidence, but not a full candidate claim.
### 5. Good/Base/Bad Cases
- Good: full base checks pass, all source-bound witnesses and acceptance specs
execute, and the runner prints one full-pass label.
- Base: use `list` before a long run to prove assets parse, while recording that
no behavior has run.
- Bad: reuse a prior browser pass after parser changes, or report focused/list
output as `LOCAL_CANDIDATE_PASS`.
### 6. Tests Required
- Negative tests for route gaps, manual/live evidence, duplicate basenames,
invalid loopback inputs, witness result/source drift and mode labels.
- Current-checkout `full` run with zero skipped/unexpected results.
- For payment-state changes, run the disposable PostgreSQL gate and prove
cleanup left no matching temporary databases.
### 7. Wrong vs Correct
#### Wrong
```bash
bash scripts/qa/run-local-candidate.sh list
echo LOCAL_CANDIDATE_PASS
```
#### Correct
```bash
bash scripts/qa/run-local-candidate.sh full
# Only this successful branch emits LOCAL_CANDIDATE_PASS.
```
@@ -938,6 +938,69 @@ validation prevent repeated re-entry and reduce support/debugging cost.
- Focused Playwright coverage for draft save, submit validation, unsaved-change
protection and server-validation failure paths when changing form behavior.
## Scenario: Data-Router Unsaved-Change Protection
### 1. Scope / Trigger
- Trigger: a React form can lose unsaved work through in-app navigation,
browser back/forward, reload, closing its panel or switching records.
### 2. Signatures
- Router: `createBrowserRouter(...)` rendered through `RouterProvider`.
- Navigation blocker: React Router `useBlocker(isDirty)`.
- Reload/close guard: `beforeunload` while `isDirty === true`.
- Shared UI contract: one unsaved-change dialog with continue-editing and
discard actions.
### 3. Contracts
- Block navigation before the route or selected record changes; do not restore
old UI state after navigation has already occurred.
- `blocker.reset()` keeps the current route and form values. `blocker.proceed()`
is called only after explicit discard confirmation.
- Record switching and panel closing use the same pending-action contract as
route navigation. Successful save or explicit discard clears dirty state.
- Register `beforeunload` only while dirty and remove it during cleanup.
### 4. Validation & Error Matrix
- Clean form navigation -> continue without a dialog.
- Dirty in-app navigation or browser back -> block and show one dialog.
- Continue editing -> URL, record selection and values remain unchanged.
- Discard -> execute exactly one pending navigation/selection and clear dirty.
- Reload/close while dirty -> browser-level unload warning remains active.
### 5. Good/Base/Bad Cases
- Good: reimbursement, procurement, contract and asset forms reuse one guard
while keeping their own save and dirty-field logic.
- Base: a short non-editable filter form does not register the guard.
- Bad: listen to history after navigation, then push the previous URL back; the
user can see state flicker or lose form data before confirmation.
### 6. Tests Required
- Playwright covers link navigation, browser back, reload and record switch.
- Assert cancel preserves URL and values; discard changes target once; save
removes the warning.
- Frontend typecheck and build after router or shared guard changes.
### 7. Wrong vs Correct
#### Wrong
```ts
window.addEventListener("popstate", () => navigate(previousUrl));
```
#### Correct
```ts
const blocker = useBlocker(isDirty);
// reset to keep editing; proceed only after explicit discard.
```
## Convention: Detail Evidence Layout
**What**: Detail pages use one evidence-first structure so users can understand
@@ -0,0 +1,8 @@
{"file":".trellis/spec/hyb-oa/project-governance.md","reason":"验证任务合同、复审、提交和归档边界"}
{"file":".trellis/spec/hyb-oa/backend-fastify-prisma.md","reason":"验证权限、事务、迁移与服务端回归"}
{"file":".trellis/spec/hyb-oa/frontend-react-vite-design.md","reason":"验证页面一致性、响应式和浏览器证据"}
{"file":".trellis/spec/hyb-oa/approval-workflow.md","reason":"验证审批角色、自审和 workflow 约束"}
{"file":".trellis/spec/hyb-oa/docs-and-evidence.md","reason":"验证证据、finding 生命周期和文档事实源"}
{"file":".trellis/spec/hyb-oa/security-privacy.md","reason":"验证敏感字段、日志、附件与安全摘要"}
{"file":".trellis/spec/hyb-oa/deployment-trialrun.md","reason":"验证本地候选结论没有越界为已部署"}
{"file":".trellis/tasks/archive/2026-07/07-16-system-multidimensional-audit/research/findings.md","reason":"逐项核对已承接 finding 的状态和证据"}
@@ -0,0 +1,99 @@
# 五人团队正式内用最小收口设计
## 实施顺序
1. **角色与敏感资料**:先完成 enum/权限常量、账号与角色边界、员工 DTO、工资/银行/证件/合同授权及矩阵测试。
2. **付款一致性**:在新角色合同稳定后完成候选查询、行锁状态机、取消原因、回单作废、前端选择器及 PostgreSQL 并发验证。
3. **页面与门禁**:完成共享表格、dirty guard、移动路由、权限显隐、路由 manifest 和 `local-candidate` runner,最后回填旧标题任务。
每阶段先跑聚焦测试再扩展到全量。权限、状态机、workflow 和迁移在实现后由只读 reviewer 复核,不让 reviewer 修改代码。
## 角色与权限设计
### 角色模型
- `employee` 是所有日常账号基础角色。
- `manager`、`ceo`、`finance`、`system_admin` 是可兼任职责。
- `finance_mgr`、`hr`、`administration` 保留兼容,不自动迁移。
- `admin` 仍映射 `*`,仅现有超级管理员/初始化流程可用;普通创建和普通角色编辑不提供该选项。
### system_admin 授权
允许:账号创建/启停/凭据重置、为其他用户分配普通业务角色、系统设置、集成、通知渠道、脱敏审计查看。
拒绝:修改自身角色、授予或撤销 `admin/system_admin`、人员完整档案、工资、银行/证件、员工合同、业务审批、付款制单/执行/复核。
普通角色修改继续使用既有 endpoint,但服务端按操作者角色和目标用户实施额外约束;UI 限制只做体验,不作为安全边界。角色变更审计只记录操作者、目标用户 ID、前后角色代码和结果,不记录凭据或员工敏感字段。
### 敏感资料数据流
```text
角色/对象判断 -> 选择最小 Prisma select -> DTO 字段省略 -> 前端按权限渲染
-> 脱敏审计(成功访问/写入)
```
- 业务员工目录固定字段:`id / employeeNo / name / title / department / avatar / status`。
- 完整 profile:本人或人员资料管理权限。
- 银行资料:finance 只读;admin/明确管理权限可写。
- 证件、员工合同:仅明确管理权限;内部路径、原始正文和不必要文件名不进入 DTO/审计。
## 付款设计
### 状态机
```text
draft -> reviewed -> executed
-> failed -> executed(仅失败项)
-> cancelled(仅未执行项)
-> cancelled
```
- `executed`、`cancelled` 终态;已执行 item 永不回滚。
- review 需要 `payment.batch.review` 且角色代码包含 `manager` 或 `ceo`;创建者自审返回 409。
- 50000 元阈值只影响警示/确认文案,不增加状态或第三复核人。
### 锁与并发
- review/execute/cancel/付款元数据更新/回单作废:同一事务内先 `SELECT ... FOR UPDATE` 锁批次。
- create:稳定排序并锁定源报销,复查 `approved`、未付款且未进入非 cancelled 批次。
- execute:稳定排序锁定开放 item 对应源单,再复查状态、金额和回单门槛。
- 竞争失败统一返回 409 业务码;失败重试按所有 item 最终状态重算汇总。
- `payment_batch_v1` 启用时旧直接付款端点返回兼容性 409;关闭 flag 可回滚旧代码路径。
### API / 数据
- Prisma 加法迁移:`RoleCode.system_admin`;`payment_batches.cancelled_reason TEXT NULL`。
- `GET /api/payment-batches/candidates?q=&page=&pageSize=` 返回业务展示字段,entity ID 仅用于后续提交。
- 取消 body:`{ reason }`;DTO 增 `cancelledReason`。
- 回单作废:`{ reason }`;复用现有治理/归档字段,不物理删除。
- 旧 DELETE/直接付款仅作为 feature flag 回滚路径;新版开启时不得产生第二写路径。
### 迁移与回滚
- 只提交加法 migration,不运行 migrate deploy,不修改真实数据。
- 代码回滚时关闭 `payment_batch_v1` 并回退应用提交;enum 和 nullable 列保留,避免破坏已写数据。
- 一旦部署后曾授予 `system_admin`,回退到不识别该枚举值的旧应用前,必须先在兼容版本中撤销或转换该角色,或保留可识别该值的兼容解析;不得让旧 Prisma client 直接读取未知枚举值。
- 不修改迁移历史,不执行 migrate reset。
## 页面与门禁设计
- 表格修复优先调整共享容器:表格自身保留最小宽度,容器 `overflow-x:auto`,document 不横溢;五个代表页做命中和尺寸断言。
- dirty guard 使用共享 hook/provider,统一拦截站内导航、浏览器返回/刷新和记录切换;保存成功或显式放弃后清除 dirty。
- `/mobile/attendance` 复用现有响应式考勤页;移动 resolver、首页和通知目标统一使用该路径,避免 `/attendance -> /mobile` 自循环。
- 移动归档只处理长字段换行/局部 overflow 和底栏安全区,不扩展移动信息架构。
- 路由 manifest 每条注册路由必须显式分类;`core` 必须有可实际执行的行为 spec,标记 `visualRequired` 的路由还必须有视觉 spec;manual/live 文件不计本地候选证据。
- `local-candidate` 组合基础门禁、路由/流程可信扫描和核心本地浏览器测试,不读取公网或生产环境变量。
## 验证矩阵
- **权限**:employee / manager / ceo / finance / system_admin / admin 正负矩阵;字段省略;拒绝时零 DB/file side effect;角色自提升负例。
- **付款**:候选筛选、POST 权威拒绝、maker-checker、高额单次复核、取消原因、回单作废、审计、失败重试和权限负例。
- **PostgreSQL 并发**:双 execute、execute vs cancel、双 create 同一报销、direct pay vs batch execute;使用隔离合成数据库并清理。
- **浏览器**:375×812 移动考勤/归档;768×1024 与 1024×768 五类表格;四类 dirty form;危险动作 0/0/1 mutation;三处权限按钮 count=0。
- **门禁反例**:unclassified、manual-only、core behavior missing、visualRequired missing 均非零退出。
## 证据与状态
- 截图和临时浏览器产物放 `output/playwright/07-18-small-team-internal-readiness/`,只保留合成/脱敏内容。
- 原 finding 台账只更新当前修复状态和证据引用,不改发现时严重度;未部署项继续保持部署/外部验证待定。
- 本地完成结论固定为 `LOCAL_CANDIDATE_PASS`。
@@ -0,0 +1,93 @@
# 五人团队正式内用最小收口证据
## 证据身份与边界
- 发现基线:`bbb5c89`;规划基线:`58c8dec`。
- 复核对象:本文件所在工作提交;提交前所有结果仅归属于当前受控工作树,提交后以本文件所在提交为 `reviewedCommit`。
- 允许环境:回环 HTTP、回环 PostgreSQL、合成角色/业务数据、无网络外部适配器。
- 未执行:部署、push、公网、生产配置、共享或真实数据库、真实账号、企业微信真机、真实 OCR、真实付款、银行流水和 ERP。
- 状态上限:`LOCAL_CANDIDATE_PASS`;不得据此告知用户线上可测试。
## 最终门禁摘要
| 证据 | 结果 | 关键结论 |
| --- | --- | --- |
| 权限与敏感资料只读复审 | PASS,聚焦 107/107;统计权限 4/4 | `system_admin` 与经理/CEO/财务/超级管理员分离;工资/绩效汇总使用独立敏感权限,拒绝路径先于敏感查询或文件读取。 |
| 付款状态机只读复审 | PASS,聚焦 36/36;重试回归 27/27 | maker-checker、锁顺序、直付关闭、失败项重试、取消/回单审计均无 P1。 |
| 候选 workflow 只读复审 | `PASS_WITH_FOLLOWUPS`,follow-up 已修复 | witness 来源、结果 parser、loopback、模式标签与无历史 Issue CSV 依赖均有反例。 |
| migration 只读复审 | PASS,无 P1/P2 未结项 | 两项加法 migration;`system_admin` 位于 `admin` 前;可空取消原因;回滚前置已记录。 |
| 服务端全量 | PASS,161/161 文件、985/985 测试 | 包含用户导入 8/8、付款批次 27/27、员工统计权限 4/4。 |
| 前端静态门禁 | PASS | typecheck 与 Vite production build 通过;仅既有 Browserslist 提示。 |
| 默认完整基础门禁 | PASS,exit 0 | `bash scripts/check-minimal.sh` 的历史流程基线、984 项服务端测试、前端 build 与功能矩阵全部通过;本机 stub 使用回环权限。 |
| 路由与候选反例 | PASS,52/52 | 71 条路由全部分类;10/10 核心流程束、10/10 witness、7/7 acceptance、0 blocking gap。 |
| 隔离 PostgreSQL | PASS,exit 0 | 当前 61 项 migration 从空库应用;五组付款竞态 PASS;审计/通知唯一;临时库残留 0。 |
| Playwright focused | `FOCUSED_BROWSER_PASS`,44/44 | 0 failed、0 skipped、0 unexpected;10 个 witness 均来自指定 spec。 |
| Playwright/full 候选 | `LOCAL_CANDIDATE_PASS`,exit 0 | 2026-07-18 13:27 CST 完成;44/44,0 failed、0 skipped、0 unexpected;基础层与结果门禁同时通过。 |
默认门禁第一次在禁止回环监听的沙箱内运行时,两个本机 stub 测试未能产生完整结果;允许本机-only stub 后聚焦 12/12 通过,随后原命令完整 exit 0。该诊断未访问公网,也未作为 PASS 替代原命令。
## PostgreSQL 竞态与迁移证据
命令:`bash scripts/qa/run-web-gates.sh payment-concurrency-local`。
- 双执行:一个成功、一个确定性 409;付款/执行审计和内部通知各唯一。
- 执行与取消:本轮执行成功、取消 409;无重复状态或副作用。
- 双创建:一个 201、一个 409;仅一个有效批次/项及一条创建审计。
- 直接付款与批次付款:范围外均拒绝且零副作用;范围内直付 409、批次执行成功。
- 缺失制单人:复核 409,批次保持 draft,零复核审计。
- 空库 migration 后只读确认枚举相邻顺序为 `system_admin` 后接 `admin`。
- runner 与独立检查结束后,`hyb_oa_payment_%` 临时数据库计数为 0。
## 十个核心流程 witness
每项均断言最终路径、唯一 H1、合成角色、允许/禁止动作,以及 `unhandled API / page error / 非白名单 console error = 0`。
| 流程束 | 最终路径 | H1 | 角色 fixture | 允许动作 | 禁止动作 |
| --- | --- | --- | --- | --- | --- |
| 登录退出 | `/login` | 海音达 OA | employee | 登录 | 应急超级管理员 |
| 报销 | `/reimbursements` | 我的报销 | employee | 新建报销 | 导出 |
| 审批 | `/workflow/tasks` | 待我处理 | manager | 处理 | 通过 |
| 票据 | `/invoice-pool` | 票据 | employee | 上传票据 | 作废 |
| 采购 | `/purchase-requests` | 采购 | employee | 新建采购 | 通过 |
| 项目/合同 | `/contracts-registry` | 合同 | manager | 新增合同 | 执行付款 |
| 付款/AP | `/payment-batches` | 付款 | finance | 新建批次 | 复核批次 |
| 人员/审计 | `/system/users` | 系统用户管理 | system_admin | 新建用户 | 应急超级管理员 |
| 考勤/归档 | `/mobile/attendance` | 考勤 | employee | 修正申请 | 提交应急修正 |
| 移动工作台 | `/mobile` | 今天待办 | employee | 新建报销 | 财务 |
## 浏览器视口与截图
- 移动:375×812,覆盖移动考勤、归档空态、长字段详情、换行、底栏遮挡与页面横溢。
- PC:768×1024、1024×768,覆盖项目、资产、合同、付款、报销五类表格;局部横滚可用、右侧操作可命中、document 横溢不超过 2px。
- 截图均使用合成身份和合成业务记录,已人工复核未见真实资料、内部 Secret、完整账号、银行资料或原始 payload。
- 产物目录为 `output/playwright/07-18-small-team-internal-readiness/`;最终 full 复跑后的 SHA-256 如下。
| 截图 | SHA-256 |
| --- | --- |
| `assets-1024x768.png` | `d3d0ff0d9dd413521a068ca1c87ad6978d12d08e836d6fe3c1048559f013014b` |
| `assets-768x1024.png` | `1a5c9b07617479cfa14fa9a4525bbdf32fac6eb74635d8dbd7641e319871abbc` |
| `contracts-registry-1024x768.png` | `f318e1637f3eb65eb853f3d0436c1825efca4fcc9336b80a6cccb6ab7236b3b9` |
| `contracts-registry-768x1024.png` | `5ce2b667aef2fd24cdc918729fe998327991246417477d6301f8314406bde7dd` |
| `desktop-archive-1024x768.png` | `9d099e825e897f37383ee7d40622322946356a9dd89b8893d9531120da6750f6` |
| `mobile-archive-detail-375x812.png` | `6a1359c4bae284cd68872faa148755e2b6fc53b7be41b1739ae2aff0090a9436` |
| `mobile-archive-empty-375x812.png` | `9b6fb325287e4436de7a71f80cf5b75c3a4e5d4f183b8919af5ffa59cf1070ca` |
| `mobile-attendance-375x812.png` | `01f75c88be01b4e4ab0696bb1de39f211eaaee402377757bf7384a278c197a7d` |
| `payment-batches-1024x768.png` | `0d8f69b360a7fbfcf5a0774aa031a0c6845dac6f468a28486c72a575db0a8db1` |
| `payment-batches-768x1024.png` | `72554a7d3a6778547a69732a5a2c1cf49bfdb2e1394acb3f465782becd559ef1` |
| `projects-1024x768.png` | `cf925dede4124e1780c8a9e67e82bf596e96278131fd03c1a115e6d45dd22eba` |
| `projects-768x1024.png` | `da5dd4b0842b407d715b91e5025f8b4e6c68cb7d7b5a87c0e460630a95f40308` |
| `reimbursements-1024x768.png` | `30726bbdba0d8dc0e80d8aa269c57996d72ce0564c84c333b7cb8a97e832e096` |
| `reimbursements-768x1024.png` | `46a146af17f1f69069492b1232071de4ae55f81f73a36ed95d5672f56e887621` |
## Finding 生命周期边界
- 原 finding 台账已按本证据更新本地修复状态,未改写发现时严重度、置信度或发现状态。
- `05-31-unified-page-header-actions` 的权限显隐、资产旧 E2E、七页面标题动作及项目/资产视觉验收已闭合,并归档至 `archive/2026-07/`。
- 本地验证通过的条目只更新 `当前修复状态=LOCAL_FIX_VERIFIED`。
- 未部署的运行时条目继续 `DEPLOYMENT_PENDING`;企业微信/角色/公网行为继续 `EXTERNAL_BEHAVIOR_PENDING`。
- 纯测试/门禁条目使用 `NOT_APPLICABLE` 部署状态;不需要外部行为的条目使用 `NOT_REQUIRED`。
- 移动通知定位只闭合考勤入口增量;其他对象定位不在本任务范围,`MOB-TARGET-001` 整体保持开放。
## 后续授权边界
真实账号职责分配、超级管理员托管、内部地址部署、migration 执行、备份恢复、部署来源、公网角色、企业微信真机和新 Day 1 连续试运行均由后续获批 Trellis task 承担。
@@ -0,0 +1,8 @@
{"file":".trellis/spec/hyb-oa/project-governance.md","reason":"任务、审批、迁移、复审和提交边界"}
{"file":".trellis/spec/hyb-oa/backend-fastify-prisma.md","reason":"Fastify、Prisma、事务、迁移和服务端测试规则"}
{"file":".trellis/spec/hyb-oa/frontend-react-vite-design.md","reason":"React/Vite 页面、权限显隐、响应式和视觉验证规则"}
{"file":".trellis/spec/hyb-oa/approval-workflow.md","reason":"经理、公司负责人、自审和审批链合同"}
{"file":".trellis/spec/hyb-oa/reimbursement-invoice-ocr.md","reason":"报销、付款、附件和回单业务边界"}
{"file":".trellis/spec/hyb-oa/security-privacy.md","reason":"员工敏感数据、审计脱敏和附件安全边界"}
{"file":".trellis/spec/hyb-oa/deployment-trialrun.md","reason":"LOCAL_CANDIDATE 与部署/公网结论分离"}
{"file":".trellis/tasks/archive/2026-07/07-16-system-multidimensional-audit/research/findings.md","reason":"本任务承接 finding 生命周期与控制目标"}
@@ -0,0 +1,105 @@
# 五人团队正式内用最小收口
## Goal
在不部署、不连接真实数据、不进行公网或企业微信真机验证的前提下,把当前代码收敛为适合约 5 人公司内部使用的本地候选版本。角色上明确分离业务管理、财务和 OA 系统运维;业务上保证付款状态、敏感资料和高影响操作可控;工程上建立可信的本地候选门禁。
本任务承接用户在 2026-07-17 批准的 `07-17-small-team-internal-readiness` 方案。由于任务实际创建日期为 2026-07-18,Trellis 目录按规则使用 `07-18-small-team-internal-readiness`。
## Scope
- Backend:Fastify 权限、员工/账号/工资/合同和付款批次路由,Prisma 加法迁移,聚焦与并发测试。
- Frontend:角色管理、敏感资料与付款页面、共享表格/dirty guard、移动考勤与归档、聚焦 E2E/视觉断言。
- QA / governance:路由分类、流程覆盖扫描、`local-candidate` runner、当前 task、必要 Trellis spec/现有手册及 finding 生命周期。
- 只使用本地合成数据和隔离测试资源;不改变生产配置或真实数据。
## Requirements
### 角色、权限与敏感资料
- 所有账号保留员工基础能力;日常职责可显式兼任 `manager`、`ceo`、`finance` 和新增 `system_admin`。
- `manager` 与员工 `direct_manager_id` 分开维护;经理身份不自动建立汇报关系。
- 新增 `RoleCode.system_admin`。现有 `admin` 继续作为通配超级管理员,但只用于初始化和应急,不作为普通预设。
- 普通用户管理界面显示员工、直属经理、公司负责人、财务、OA 系统管理员;`finance_mgr/hr/administration` 保留为高级兼容职责;普通界面不可授予 `admin`。
- `system_admin` 可管理其他普通账号、业务角色、系统配置、集成和审计;不得修改自己的角色,不得授予或撤销 `admin/system_admin`,所有变更写脱敏审计。
- `system_admin` 不获得工资、银行、证件、员工合同、付款执行或业务审批权限。
- 付款复核要求显式具有 `manager` 或 `ceo`,不能仅凭 `admin` 或 `system_admin` 进入正常审批路径。
- 新增细分权限:`system.accounts.manage`、`system.roles.manage`、`payroll.manage`、`employee.bank.read`、`employee.bank.manage`、`employee.identity.manage`、`employee.contract.manage`、`payment.batch.review`。
- `/employees` 对业务选择调用者只返回窄 DTO;完整员工档案仅本人或人员资料管理员可读,权限拒绝发生在敏感查询或文件读取前。
- 财务只读银行字段,不读取证件或修改敏感资料;未授权字段从响应中省略。
- 员工合同列表、元数据、附件下载和写操作使用独立权限;DTO 不返回内部路径,成功访问和变更写脱敏审计。
- 前端按权限隐藏动作;后端仍为最终授权边界。
### 付款一致性与最小审计
- 固定“财务制单/执行 + 经理或公司负责人复核”;创建者不得复核自己的批次。
- 所有金额只需一次独立复核;总金额达到 50000 元时增加警示和强确认,不增加第三人审批。
- 批次状态为 `draft -> reviewed -> executed|failed|cancelled`;`failed` 只重试失败项或取消剩余项;`executed/cancelled` 为终态。
- 创建、复核、执行、取消、付款元数据更新和回单作废在事务内锁定批次;执行再按稳定顺序锁定源单并复查状态和金额。
- 并发竞争返回确定性 409,不重复更新付款状态、审计或通知。
- 正式范围只接受已审批、未付款且未进入其他有效批次的报销;`payment_batch_v1` 启用时直接报销付款端点不得形成第二付款路径。
- 新增付款候选查询,页面按业务编号、人员、金额和日期选择,不要求粘贴内部 UUID;创建接口继续权威复查。
- 取消必须填写 2–500 字原因;取消失败批次只终止未付款项,不表示退款或冲正。
- 回单采用有理由的留痕作废,不物理删除;已复核后的付款元数据变更写 before/after 审计。
### 核心页面与本地候选门禁
- 项目、资产、合同、付款、报销表格在 768×1024 和 1024×768 下操作可达;容器可控横滚,页面整体不横向溢出。
- 报销、采购、合同、资产长表单共享未保存保护,覆盖站内跳转、记录切换、浏览器返回和刷新。
- 高敏附件删除和付款回单作废显示对象、后果和理由确认;普通可恢复动作不升级为强确认。
- 新增 `/mobile/attendance` 并消除移动考勤自循环;修复 375×812 下移动归档长字段裁切和底栏遮挡。
- 完成付款、合同、票据的权限显隐;修正资产旧 E2E,补齐项目/资产标题动作视觉覆盖;通过后回填并归档 `05-31-unified-page-header-actions`。
- 所有注册路由分类为 `core`、`supported-noncore`、`deferred` 或 `frozen`。未分类、核心行为测试缺失、仅由 `manual/live` 测试覆盖时门禁非零退出。
- 本地核心矩阵覆盖:登录退出、报销、审批、票据、采购、项目/合同、付款/AP、人员/审计、考勤/归档、移动工作台。
- 建立仓库内可移植的 `local-candidate` 门禁,不依赖公网、生产配置、历史 Issue CSV 或个人绝对路径。
## Acceptance Criteria
- [x] Prisma enum 包含 `system_admin`,加法迁移可验证且有回滚说明;既有角色与历史账号不自动迁移。
- [x] 角色矩阵证明业务管理、财务、系统管理员和超级管理员职责分离,系统管理员不能自我提权。
- [x] 员工目录、完整档案、银行、证件、工资和员工合同权限满足字段级与对象级边界;拒绝路径无敏感 DB/file 副作用。
- [x] 付款批次创建、复核、执行、失败重试、取消和回单作废满足状态机、maker-checker、原因与审计合同。
- [x] 合成本地 PostgreSQL 并发验证覆盖双执行、执行与取消、双创建、直接付款与批次付款竞争,结果唯一且不重复审计/通知。
- [x] 付款页面使用业务候选选择器,不要求普通用户输入 UUID;高额仅增加警示和确认。
- [x] 五类表格、四类未保存表单、不可逆动作、移动考勤和移动归档通过聚焦浏览器验证。
- [x] 付款、合同、票据权限显隐与后端拒绝一致;`05-31-unified-page-header-actions` 剩余测试和视觉证据闭合后才归档。
- [x] 路由清单无未分类项,门禁反例对 unmapped、manual-only、核心缺失行为/视觉均能失败。
- [x] 十个核心流程束在本地合成环境实际运行,记录最终 URL、H1、角色、主操作和页面错误断言。
- [x] 完整本地质量门禁通过;权限、状态机、workflow 和迁移变更完成独立只读复审。
- [x] 收口结论只写 `LOCAL_CANDIDATE_PASS`,不写已部署、可公网测试或企业微信真机通过。
## Definition of Done
- 代码、迁移、测试、现有规范/手册和 finding 生命周期同步更新,未新建重复审查报告。
- `bash scripts/check-minimal.sh`、Prisma 校验、服务端聚焦及全量测试、前端 typecheck/build、聚焦 Playwright、task validate、Trellis governance、敏感扫描、文档 registry 和 `git diff --check` 通过。
- 三个实施阶段分别复核,最后以中文 commit 引用本任务;不部署、不 push。
- `05-31-unified-page-header-actions` 仅在其剩余验收确实闭合后归档;当前任务完成后按 Trellis finish-work 归档并记录 journal。
## Test Plan
- 静态与单元:Prisma validate、触及服务端文件 `node --check`、权限/DTO/审计/付款路由 Vitest、前端 typecheck/build。
- 集成:隔离合成 PostgreSQL 应用已提交 migration,运行付款四类并发竞争,结束后清理数据库;不连接共享开发库或生产库。
- 浏览器:本地 Vite dev 与合成 fixture 实际运行十个核心流程;PC 768×1024/1024×768 和 WeCom UA 375×812 记录 URL、H1、角色、错误及溢出指标。
- 门禁可信度:为 unclassified、manual-only、core behavior missing、visualRequired missing 提供反例测试并断言非零退出;Playwright `--list` 只证明资产可解析。
- 全量收口:`bash scripts/check-minimal.sh`、task validate、Trellis governance、敏感扫描、文档 registry、`git diff --check`;权限/状态机/workflow/迁移另做只读 reviewer 复核。
## Decision (ADR-lite)
**Context**:公司约 5 人,需要低配置成本,但系统维护者不一定是领导,付款与高敏资料仍需要最小职责分离。
**Decision**:保留现有多角色引擎和业务职责,新增受限 `system_admin`,保留隐藏的应急 `admin`;付款使用数据库行锁和 maker-checker,不建设通用权限平台、事件平台或多级审批系统;发布只增加本地候选模式。
**Consequences**:日常界面简单且允许一人兼任多个职责;数据库仅做加法迁移;至少两个不同账号才能闭环付款;真实账号分配和内部环境验证必须在后续授权任务完成。
## Out of Scope
- 部署、公网检查、生产配置、真实账号分配、真实数据库写入、迁移 deploy、数据库重置、备份恢复演练。
- 企业微信真机、真实 OCR、真实付款、银行直连/流水、退款冲正、ERP/总账、差旅付款恢复。
- 通用 RBAC 平台、权限表达式设计器、全局幂等平台、事件总线、分布式锁、可配置多级金额审批。
- 全量可访问性、搜索直达、复杂离线数据生命周期、所有通知精准定位、非核心路由视觉重做。
## Technical Notes
- 规划基线:`58c8dec`;实现开始前若 HEAD 变化,更新 task notes 并重跑全部门禁。
- 发现来源:归档任务 `07-16-system-multidimensional-audit` 中 `DATA-EMP-001`、`DATA-HR-ATT-001`、`PAY-STATE-001`、`PAY-AUDIT-001`、`PAY-UX-001`、`UX-SAFE-001`、`UX-FORM-001`、`MOB-ATTEND-001`、`RESP-TABLE-001`、`REL-GATE-001`、`UI-PERM-001`、`MOB-ARCHIVE-001`。
- 页面可靠性沿用现有设计语言,不做新视觉风格裁决;当前会话没有可用的 `frontend-design` skill。
@@ -0,0 +1,26 @@
{
"id": "small-team-internal-readiness",
"name": "small-team-internal-readiness",
"title": "五人团队正式内用最小收口",
"description": "面向约 5 人公司,分离业务管理与 OA 系统运维,收敛敏感资料、付款一致性、核心页面和本地候选门禁。",
"status": "in_progress",
"dev_type": null,
"scope": "fullstack",
"package": null,
"priority": "P1",
"creator": "shunli",
"assignee": "shunli",
"createdAt": "2026-07-18",
"completedAt": null,
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "承接用户批准的 07-17 方案;实际创建日期为 2026-07-18,规划基线 58c8dec。2026-07-18 当前工作提交通过 full 本地候选门禁(44/44 浏览器、10/10 witness、0 skipped/failed/unexpected)、服务端 985/985、隔离 PostgreSQL 61 migrations 与五组竞态、权限/付款/workflow/migration 只读复审;复审发现的员工统计敏感汇总权限漏口已修复并以 system_admin 零敏感查询回归验证,结论为 LOCAL_CANDIDATE_PASS。本轮未部署、未 push、未访问公网/生产/真实数据、未做企微真机或真实付款;后续内部部署另行授权。",
"meta": {}
}
@@ -79,7 +79,7 @@
- [x] 规则写入前端设计规范,后续页面可复用。
- [x] 标题、筛选、列表列、空状态、详情/编辑操作区至少完成一致性审计,形成后续改造清单。
- [x] 前端 typecheck/build 通过。
- [ ] 聚焦 Playwright/视觉复检覆盖主要业务页。
- [x] 聚焦 Playwright/视觉复检覆盖主要业务页。
## Test Plan
@@ -114,3 +114,11 @@ node scripts/qa/check-trellis-governance.mjs
- `HeaderActionGroup` 目前只统一布局,不约束动作数量;缺少七页面统一标题动作合同测试,现有截图缺项目和资产列表。
上述 finding 需要独立修复 task;在修复并补齐视觉/回归覆盖前,本任务保持 `in_progress`,不得虚报完成或归档。
## Final Closeout Verification (2026-07-18)
- 承接任务 `07-18-small-team-internal-readiness` 已闭合上述 follow-up:付款复核角色看不到新建批次,应付账款导出按权限隐藏,合同只读角色看不到新增/编辑/续约/归档,票据无上传权限时 fail-closed;对应合成 Playwright 5/5 纳入最终候选门禁。
- `assets-flow.spec.ts` 和历史 manual 资产用例已改为通过标题区 `更多 -> 资产分类`;资产流程 spec 可解析,当前菜单行为由实际浏览器回归覆盖。
- `r103-cross-page-ui-consistency.spec.ts` 扩展为 8 项:七个页面标题区严格执行“一主操作 + 最多一个更多”,并覆盖项目/资产代表视觉、资产“更多 -> 资产分类”及核心页面动作合同。
- 当前 checkout 的 `bash scripts/qa/run-local-candidate.sh full` 最终为 44/44 passed、0 skipped/failed/unexpected;项目、资产、合同、付款、报销双断点截图及权限显隐 spec 均包含在候选 acceptance 集合。
- 本任务仅完成本地前端验收并归档;未部署、未公网复检,不能据此宣称线上可测。
@@ -3,7 +3,7 @@
"name": "unified-page-header-actions",
"title": "统一页面右上角操作区规划",
"description": "统一 PC 页面标题右上角操作区规划,收敛主操作、更多、筛选、导出、刷新等按钮位置。",
"status": "in_progress",
"status": "completed",
"dev_type": null,
"scope": "frontend",
"package": null,
@@ -11,7 +11,7 @@
"creator": "shunli",
"assignee": "shunli",
"createdAt": "2026-05-31",
"completedAt": null,
"completedAt": "2026-07-18",
"branch": null,
"base_branch": "main",
"worktree_path": null,
@@ -21,6 +21,6 @@
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "2026-07-16 收口验证:typecheck/build 和 r103 3/3 通过,标题迁移 6 项验收已证实;仍有权限显隐、资产旧测试和项目/资产视觉覆盖 finding,需独立修复 task 后才能归档。",
"notes": "2026-07-18 由 07-18-small-team-internal-readiness 闭合剩余项:付款/合同/票据权限显隐合成 Playwright 5/5,资产旧 E2E 改用更多菜单,r103 扩展为 8 项并覆盖七页面标题动作及项目/资产视觉;最终 local-candidate full 为 44/44、0 skipped/failed/unexpected。本任务仅本地验收,未部署或公网复检,可归档。",
"meta": {}
}
@@ -3,7 +3,7 @@
## 台账口径
- 本文件是当前任务唯一的分级与生命周期事实源。分域报告保留发现证据,但不再各自决定当前严重度或状态。
- 发现基线为 `bbb5c89`;当前本地增量目标为 `8f8606c`。发现时严重度不因后续修复而改写,收敛校准会在单元格中显式注明。
- 发现基线为 `bbb5c89`;原审查收口增量目标为 `8f8606c`。后续修复任务 `07-18-small-team-internal-readiness` 的规划基线为 `58c8dec`,其本地验证以该任务 `evidence.md` 所在工作提交为准。发现时严重度不因后续修复而改写,收敛校准会在单元格中显式注明。
- 发现状态、当前修复状态、部署状态、外部验证状态彼此独立。
- `NO_FIX_TO_DEPLOY` 表示尚无修复产物,不等于待部署;`NOT_APPLICABLE` 仅用于非运行时代码/纯测试合同。
- `ACTIVE_TASK_OPEN` 表示该项仍开放但已由现有活动 task 承载;本轮只做去重,不另建 task。`FOLLOW_UP_RECOMMENDED` 表示持续治理建议,不是已修复。
@@ -15,24 +15,24 @@
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `SEC-AUTH-001` | 认证 / 代理信任 | P0 | 高 | `CONFIRMED` | `LOCAL_FIX_VERIFIED` | `DEPLOYMENT_PENDING` | `EXTERNAL_BEHAVIOR_PENDING` | `business-security-review.md#bsr-sec-001`;归档任务 `07-17-auth-challenge-trusted-proxy-hardening` | 认证边界不足可能导致账号被未授权控制。 | 挑战单次有效、并发一致、来源可信且可审计。 | 当前 HEAD 聚焦负路径、完整服务端与隔离并发验证通过。 | 另行授权:部署与公网复核 |
| `SEC-ATT-001` | 业务附件 / 浏览器隔离 | P0 | 高 | `CONFIRMED` | `LOCAL_FIX_VERIFIED` | `DEPLOYMENT_PENDING` | `EXTERNAL_BEHAVIOR_PENDING` | `business-security-review.md#bsr-sec-002`;归档任务 `07-17-purchase-attachment-xss-hardening` | 主动内容可能破坏 OA 同源会话与业务数据边界。 | 实际内容验证、重新授权/审计、非执行下载。 | 当前 HEAD 策略/路由、完整服务端与聚焦浏览器验证通过。 | 另行授权:部署与公网复核 |
| `DATA-EMP-001` | 员工敏感数据授权 | P1 | 高 | `CONFIRMED` | `OPEN` | `NO_FIX_TO_DEPLOY` | `EXTERNAL_BEHAVIOR_PENDING` | `business-security-review.md#bsr-authz-001` | 广泛能力可能读取超出岗位需要的员工敏感字段。 | 字段级授权与人员范围同时最小化。 | 只读跨层检查确认权限粒度不足,未读取真实资料。 | WP-1 员工敏感数据范围授权 |
| `PAY-STATE-001` | 付款状态机 / 并发 | P1 | 高 | `LIKELY` | `OPEN` | `NO_FIX_TO_DEPLOY` | `EXTERNAL_BEHAVIOR_PENDING` | `business-security-review.md#bsr-data-001` | 并发状态变化可能造成重复结果或证据覆盖。 | 条件更新/锁、幂等和不可覆盖审计保证单一结果。 | 静态事务检查发现保护证据不足,未连接数据库或执行付款。 | WP-2 付款状态一致性与审计 |
| `DATA-HR-ATT-001` | 员工合同附件 | P1 | 高 | `CONFIRMED` | `OPEN` | `NO_FIX_TO_DEPLOY` | `EXTERNAL_BEHAVIOR_PENDING` | `business-security-review.md#bsr-priv-001` | 高敏合同资料可能受过宽权限控制且缺访问用途审计。 | 独立最小权限、对象级授权、安全 DTO 和下载审计。 | 只读路由/测试合同确认缺口,未读取真实合同。 | WP-3 员工合同附件权限与访问审计 |
| `DATA-EMP-001` | 员工敏感数据授权 | P1 | 高 | `CONFIRMED` | `LOCAL_FIX_VERIFIED` | `DEPLOYMENT_PENDING` | `EXTERNAL_BEHAVIOR_PENDING` | `business-security-review.md#bsr-authz-001`;任务 `07-18-small-team-internal-readiness/evidence.md` | 广泛能力可能读取超出岗位需要的员工敏感字段。 | 字段级授权与人员范围同时最小化。 | 当前工作提交的窄目录 DTO、字段/对象权限、员工统计敏感汇总独立授权、零敏感副作用矩阵与全量服务端门禁通过;未读取真实资料。 | WP-1 员工敏感数据范围授权 |
| `PAY-STATE-001` | 付款状态机 / 并发 | P1 | 高 | `LIKELY` | `LOCAL_FIX_VERIFIED` | `DEPLOYMENT_PENDING` | `EXTERNAL_BEHAVIOR_PENDING` | `business-security-review.md#bsr-data-001`;任务 `07-18-small-team-internal-readiness/evidence.md` | 并发状态变化可能造成重复结果或证据覆盖。 | 条件更新/锁、幂等和不可覆盖审计保证单一结果。 | 空隔离 PostgreSQL 应用 61 项 migration,五组竞态、审计/通知唯一性和临时库零残留通过;未执行真实付款。 | WP-2 付款状态一致性与审计 |
| `DATA-HR-ATT-001` | 员工合同附件 | P1 | 高 | `CONFIRMED` | `LOCAL_FIX_VERIFIED` | `DEPLOYMENT_PENDING` | `EXTERNAL_BEHAVIOR_PENDING` | `business-security-review.md#bsr-priv-001`;任务 `07-18-small-team-internal-readiness/evidence.md` | 高敏合同资料可能受过宽权限控制且缺访问用途审计。 | 独立最小权限、对象级授权、安全 DTO 和下载审计。 | 当前工作提交的独立权限、无内部路径 DTO、内容复验、审计先于响应及拒绝零文件读取测试通过;未读取真实合同。 | WP-3 员工合同附件权限与访问审计 |
| `DATA-EXPORT-001` | 统一导出 | P1 | 高 | `CONFIRMED` | `OPEN` | `NO_FIX_TO_DEPLOY` | `NOT_REQUIRED` | `business-security-review.md#bsr-sec-003` | 导出内容可能在表格软件中形成非预期活动行为。 | 全部 CSV/表格出口统一安全编码并有合成负例。 | 静态检查确认缺少统一中和合同,未使用真实导出。 | WP-5 客户端数据生命周期与统一导出 |
| `PRIV-OFFLINE-001` | 客户端数据生命周期 | P1 | 高 | `CONFIRMED` | `OPEN` | `NO_FIX_TO_DEPLOY` | `EXTERNAL_BEHAVIOR_PENDING` | `business-security-review.md#bsr-priv-002` | 退出或换账号后残留身份、审批或草稿可能被后续使用者读取。 | 先确认离线必要性,再按账号隔离、最小缓存并可靠清除。 | 静态检查确认持久化和清理未闭合,未检查真实设备。 | WP-5 客户端数据生命周期与统一导出 |
| `DATA-DOWNLOAD-001` | 下载凭证 / 日志面 | P2 | 中高 | `LIKELY` | `OPEN` | `NO_FIX_TO_DEPLOY` | `EXTERNAL_GATED` | `business-security-review.md#bsr-sec-004` | 短期访问凭据可能进入常规浏览器或边缘记录面。 | 使用当前认证上下文下载并最小化 URL、日志和缓存暴露。 | 代码存在风险可能性;真实边缘日志未获授权检查。 | WP-5 客户端数据生命周期与统一导出 |
| `DATA-ACL-001` | 受限资料规则 | P2 | 中高 | `LIKELY` | `OPEN` | `NO_FIX_TO_DEPLOY` | `NOT_REQUIRED` | `business-security-review.md#bsr-authz-002` | 已保存但未执行的规则会造成受限资料边界误判。 | 产品明确其为 ACL 或废弃元数据;保留则由后端强制执行。 | 静态检查确认规则与执行证据不一致。 | WP-5 客户端数据生命周期与统一导出 |
| `PAY-AUDIT-001` | 付款取消审计 | P2 | 高 | `CONFIRMED` | `OPEN` | `NO_FIX_TO_DEPLOY` | `NOT_REQUIRED` | `business-security-review.md#bsr-data-002` | 取消责任与前后状态难以完整还原。 | 记录原因、操作者、前后状态与并发条件。 | 静态路由/模型检查确认缺口,未执行真实状态变化。 | WP-2 付款状态一致性与审计 |
| `PAY-AUDIT-001` | 付款取消审计 | P2 | 高 | `CONFIRMED` | `LOCAL_FIX_VERIFIED` | `DEPLOYMENT_PENDING` | `NOT_REQUIRED` | `business-security-review.md#bsr-data-002`;任务 `07-18-small-team-internal-readiness/evidence.md` | 取消责任与前后状态难以完整还原。 | 记录原因、操作者、前后状态与并发条件。 | 可空取消原因 migration、2–500 字原因、保留已执行项、回单留痕作废及 before/after 审计回归通过。 | WP-2 付款状态一致性与审计 |
| `WEB-DEFENSE-001` | 浏览器防御纵深 | P2 | 高 | `CONFIRMED` | `OPEN` | `NO_FIX_TO_DEPLOY` | `EXTERNAL_BEHAVIOR_PENDING` | `business-security-review.md#bsr-sec-005` | 缺少内容执行限制会降低未知缺陷的纵深保护。 | 基于资源清单分阶段收紧 CSP 并保持附件独立沙箱。 | 本地配置确认当前未启用全站策略;未推断具体利用路径。 | WP-5 客户端数据生命周期与统一导出 |
| `UI-PERM-001` | PC 写动作显隐 | P2(收敛校准;原分域报告 P1/P2 不一) | 高 | `CONFIRMED` | `ACTIVE_TASK_OPEN` | `NO_FIX_TO_DEPLOY` | `NOT_REQUIRED` | `page-static-review.md#既有-finding-复核与去重`;`BSR-UX-001`;活动任务 `05-31-unified-page-header-actions` | 后端虽拒绝写入,PC 用户仍可能进入最终失败的流程。 | PC 显隐与后端拒绝共享同一能力合同,后端保持权威。 | 付款、合同、票据三处既有 PC finding 完成只读去重;移动导航由 `MOB-PERM-001` 单列。 | WP-6 页面权限显隐及旧标题任务收口 |
| `PAY-UX-001` | 付款业务可用性 | P1 | 高 | `CONFIRMED` | `OPEN` | `NO_FIX_TO_DEPLOY` | `NOT_REQUIRED` | `page-static-review.md#page-p1-001` | 财务人员难以凭业务信息选择待付对象,可能错选或无法创建。 | 以待付池和业务编号/人员/金额选择对象,内部标识仅由系统传递。 | 静态页面与 E2E 合同确认普通流程依赖内部标识。 | WP-2 付款状态一致性与审计 |
| `UX-SAFE-001` | 不可逆证据 / 资金动作 | P1(收敛后边界) | 高 | `CONFIRMED` | `OPEN` | `NO_FIX_TO_DEPLOY` | `NOT_REQUIRED` | `page-static-review.md#page-p1-002` | 误触可能删除不可逆证据或改变资金结果。 | 强确认展示对象、后果、可逆性与必要理由。 | 静态检查确认代表性高影响动作在 mutation 前缺少强确认。 | WP-6 页面权限显隐及旧标题任务收口 |
| `UI-PERM-001` | PC 写动作显隐 | P2(收敛校准;原分域报告 P1/P2 不一) | 高 | `CONFIRMED` | `LOCAL_FIX_VERIFIED` | `DEPLOYMENT_PENDING` | `NOT_REQUIRED` | `page-static-review.md#既有-finding-复核与去重`;`BSR-UX-001`;归档任务 `05-31-unified-page-header-actions`;任务 `07-18-small-team-internal-readiness/evidence.md` | 后端虽拒绝写入,PC 用户仍可能进入最终失败的流程。 | PC 显隐与后端拒绝共享同一能力合同,后端保持权威。 | 付款、合同、票据正负显隐合成 Playwright 5/5 与后端拒绝测试通过;旧标题任务已完成本地验收并归档。 | WP-6 页面权限显隐及旧标题任务收口 |
| `PAY-UX-001` | 付款业务可用性 | P1 | 高 | `CONFIRMED` | `LOCAL_FIX_VERIFIED` | `DEPLOYMENT_PENDING` | `NOT_REQUIRED` | `page-static-review.md#page-p1-001`;任务 `07-18-small-team-internal-readiness/evidence.md` | 财务人员难以凭业务信息选择待付对象,可能错选或无法创建。 | 以待付池和业务编号/人员/金额选择对象,内部标识仅由系统传递。 | 当前工作提交的候选接口权威复查及合成浏览器业务选择器通过,普通页面不要求粘贴内部 UUID。 | WP-2 付款状态一致性与审计 |
| `UX-SAFE-001` | 不可逆证据 / 资金动作 | P1(收敛后边界) | 高 | `CONFIRMED` | `LOCAL_FIX_VERIFIED` | `DEPLOYMENT_PENDING` | `NOT_REQUIRED` | `page-static-review.md#page-p1-002`;任务 `07-18-small-team-internal-readiness/evidence.md` | 误触可能删除不可逆证据或改变资金结果。 | 强确认展示对象、后果、可逆性与必要理由。 | 合成浏览器证明高敏附件删除与回单作废展示对象、后果和理由;取消或理由不足时零 mutation。 | WP-6 页面权限显隐及旧标题任务收口 |
| `UX-SAFE-002` | 可恢复管理动作 | P2(由原 PAGE-P1-002 拆分校准) | 高 | `CONFIRMED` | `OPEN` | `NO_FIX_TO_DEPLOY` | `NOT_REQUIRED` | `page-static-review.md#page-p1-002` | 普通启停等可恢复动作存在误触和反馈不清。 | 使用轻确认或可撤销反馈,按可逆性分级。 | 只读检查确认部分可恢复动作仍直接执行。 | WP-6 页面权限显隐及旧标题任务收口 |
| `UX-FORM-001` | 核心长表单可靠性 | P1 | 高 | `CONFIRMED` | `OPEN` | `NO_FIX_TO_DEPLOY` | `NOT_REQUIRED` | `page-static-review.md#page-p1-003` | 采购、报销、合同、资产等长输入可能因离开页面丢失。 | 共享 dirty-state 合同覆盖路由离开、刷新和记录切换。 | 静态检查未找到未保存保护实现。 | WP-6 页面权限显隐及旧标题任务收口 |
| `MOB-ATTEND-001` | 移动考勤入口 | P1(由原 PAGE-P1-004 拆分) | 高 | `CONFIRMED` | `OPEN` | `NO_FIX_TO_DEPLOY` | `EXTERNAL_BEHAVIOR_PENDING` | `page-static-review.md#page-p1-004` | 企业微信/触控小屏考勤入口会回到原移动首页。 | 提供明确移动镜像或允许只读响应式页,不形成自循环。 | 静态路由解析确认自循环;真机点击待另行授权。 | WP-6 页面权限显隐及旧标题任务收口 |
| `MOB-TARGET-001` | 移动通知定位 | P2(由原 PAGE-P1-004 拆分校准) | 高 | `CONFIRMED` | `OPEN` | `NO_FIX_TO_DEPLOY` | `EXTERNAL_BEHAVIOR_PENDING` | `page-static-review.md#page-p1-004` | 部分通知不能定位原业务对象,用户需重新查找。 | 每种通知保留稳定对象标识并验证最终 URL/内容。 | 静态链接映射确认目标参数不完整;真实通知待授权。 | WP-6 页面权限显隐及旧标题任务收口 |
| `UX-FORM-001` | 核心长表单可靠性 | P1 | 高 | `CONFIRMED` | `LOCAL_FIX_VERIFIED` | `DEPLOYMENT_PENDING` | `NOT_REQUIRED` | `page-static-review.md#page-p1-003`;任务 `07-18-small-team-internal-readiness/evidence.md` | 采购、报销、合同、资产等长输入可能因离开页面丢失。 | 共享 dirty-state 合同覆盖路由离开、刷新和记录切换。 | data router / `useBlocker` 共享保护在报销、采购、合同、资产中覆盖站内离开、返回/刷新和记录切换,合成浏览器通过。 | WP-6 页面权限显隐及旧标题任务收口 |
| `MOB-ATTEND-001` | 移动考勤入口 | P1(由原 PAGE-P1-004 拆分) | 高 | `CONFIRMED` | `LOCAL_FIX_VERIFIED` | `DEPLOYMENT_PENDING` | `EXTERNAL_BEHAVIOR_PENDING` | `page-static-review.md#page-p1-004`;任务 `07-18-small-team-internal-readiness/evidence.md` | 企业微信/触控小屏考勤入口会回到原移动首页。 | 提供明确移动镜像或允许只读响应式页,不形成自循环。 | `/mobile/attendance` 的 375×812 最终 URL、H1、角色、动作、错误和溢出断言通过;真机点击待另行授权。 | WP-6 页面权限显隐及旧标题任务收口 |
| `MOB-TARGET-001` | 移动通知定位 | P2(由原 PAGE-P1-004 拆分校准) | 高 | `CONFIRMED` | `OPEN` | `NO_FIX_TO_DEPLOY` | `EXTERNAL_BEHAVIOR_PENDING` | `page-static-review.md#page-p1-004`;任务 `07-18-small-team-internal-readiness/evidence.md` | 部分通知不能定位原业务对象,用户需重新查找。 | 每种通知保留稳定对象标识并验证最终 URL/内容。 | 考勤目标增量已实现并验证;资产/盘点等其他对象定位未在本任务闭合,整体保持开放,真实通知待授权。 | WP-6 页面权限显隐及旧标题任务收口 |
| `SEARCH-NAV-001` | 全局搜索导航 | P2(收敛校准;原报告 P1) | 高 | `CONFIRMED` | `OPEN` | `NO_FIX_TO_DEPLOY` | `NOT_REQUIRED` | `page-static-review.md#page-p1-005` | 点击部分搜索结果只到列表,不能确认命中对象。 | 对象型结果提供稳定详情或定位/高亮合同。 | 静态 href 与现有单实体 E2E 确认覆盖不完整。 | WP-6 页面权限显隐及旧标题任务收口 |
| `RESP-TABLE-001` | 中间断点表格 | P1 | 高 | `CONFIRMED` | `OPEN` | `NO_FIX_TO_DEPLOY` | `NOT_REQUIRED` | `page-static-review.md#page-p1-006`;`browser-review.md` | 共享表格隐藏超出宽度,多个核心列表的右侧操作不可达。 | 操作列可见或容器可控横滚,页面本身不溢出。 | 768 下容器缺 58px、1024 下缺 106px 且 `overflow-x:hidden`;代表页面操作命中失败。 | WP-6 页面权限显隐及旧标题任务收口 |
| `RESP-TABLE-001` | 中间断点表格 | P1 | 高 | `CONFIRMED` | `LOCAL_FIX_VERIFIED` | `DEPLOYMENT_PENDING` | `NOT_REQUIRED` | `page-static-review.md#page-p1-006`;`browser-review.md`;任务 `07-18-small-team-internal-readiness/evidence.md` | 共享表格隐藏超出宽度,多个核心列表的右侧操作不可达。 | 操作列可见或容器可控横滚,页面本身不溢出。 | 项目、资产、合同、付款、报销在 768×1024 与 1024×768 下局部横滚、右侧操作命中、document 无横溢断言与脱敏截图通过。 | WP-6 页面权限显隐及旧标题任务收口 |
| `A11Y-FORM-001` | 表单可访问性 | P2(收敛校准;原报告 P1) | 高 | `CONFIRMED` | `FOLLOW_UP_RECOMMENDED` | `NO_FIX_TO_DEPLOY` | `NOT_REQUIRED` | `page-static-review.md#page-p1-007` | 辅助技术用户较难识别字段和动态错误。 | 共享字段/错误组件提供名称、关联、错误状态和 live feedback。 | 静态样例确认语义缺口;无行为证据证明核心流程完全不可完成。 | WP-6 页面权限显隐及旧标题任务收口 |
| `NAV-META-001` | 路由 / 标题元数据 | P2 | 高 | `CONFIRMED` | `OPEN` | `NO_FIX_TO_DEPLOY` | `NOT_REQUIRED` | `page-static-review.md#page-p2-001` | 标题、最近访问和浏览器 tab 语义漂移。 | 统一 typed route manifest 与页面可控覆写。 | 静态映射确认缺失、误匹配与固定浏览器标题。 | WP-6 页面权限显隐及旧标题任务收口 |
| `ERR-RECOVERY-001` | 错误恢复 | P2 | 高 | `CONFIRMED` | `OPEN` | `NO_FIX_TO_DEPLOY` | `NOT_REQUIRED` | `page-static-review.md#page-p2-002` | 403/404/渲染错误可能把用户带向隐藏旧入口。 | 所有恢复动作回到稳定工作台或安全上一页。 | 静态链接确认仍指向旧路径。 | WP-6 页面权限显隐及旧标题任务收口 |
@@ -40,13 +40,13 @@
| `UX-ERROR-001` | 普通页面错误文案 | P2 | 高 | `CONFIRMED` | `OPEN` | `NO_FIX_TO_DEPLOY` | `NOT_REQUIRED` | `page-static-review.md#page-p2-004` | 用户先看到内部码/枚举,难以理解原因与下一步。 | 普通界面展示自然语言和恢复动作,诊断码仅进受控区域。 | 静态样例确认普通页面直接组合技术信息。 | WP-6 页面权限显隐及旧标题任务收口 |
| `A11Y-TABLE-001` | 表格键盘可达 | P2 | 高 | `CONFIRMED` | `FOLLOW_UP_RECOMMENDED` | `NO_FIX_TO_DEPLOY` | `NOT_REQUIRED` | `page-static-review.md#page-p2-005` | 键盘用户无法等价选择部分表格记录。 | 使用真实按钮/链接和明确选中状态。 | 静态样例确认行点击缺少键盘等价入口。 | WP-6 页面权限显隐及旧标题任务收口 |
| `A11Y-VISUAL-001` | 对比度 / 减弱动效 | P2 | 高 | `CONFIRMED` | `FOLLOW_UP_RECOMMENDED` | `NO_FIX_TO_DEPLOY` | `NOT_REQUIRED` | `page-static-review.md#page-p2-006` | 低视力与减弱动效用户体验下降。 | 共享 token 达到既定对比度并尊重 reduced motion。 | 静态 token/样式检查确认当前合同不足。 | WP-6 页面权限显隐及旧标题任务收口 |
| `QA-VISUAL-001` | 视觉证据可信度 | P2 | 高 | `CONFIRMED` | `OPEN` | `NOT_APPLICABLE` | `NOT_REQUIRED` | `page-static-review.md#page-p2-007` | 报告可能遗漏真实路由/中间断点却给出过强信心。 | 显式路由计划、最终 URL/标题/错误页断言和代表断点。 | 静态矩阵确认通配、旧路由和断点缺口。 | WP-4 发布门禁可信化 |
| `QA-VISUAL-001` | 视觉证据可信度 | P2 | 高 | `CONFIRMED` | `LOCAL_FIX_VERIFIED` | `NOT_APPLICABLE` | `NOT_REQUIRED` | `page-static-review.md#page-p2-007`;任务 `07-18-small-team-internal-readiness/evidence.md` | 报告可能遗漏真实路由/中间断点却给出过强信心。 | 显式路由计划、最终 URL/标题/错误页断言和代表断点。 | 71 条路由分类、10 个 source-bound witness、7 个 acceptance spec、代表断点和结果 parser 反例均通过。 | WP-4 发布门禁可信化 |
| `MOB-PERM-001` | 移动导航权限 | P2 | 中高 | `CONFIRMED` | `OPEN` | `NO_FIX_TO_DEPLOY` | `NOT_REQUIRED` | `page-static-review.md#page-p2-008` | 最小权限角色看到无效入口或进入 403。 | 移动入口统一复用能力谓词,未授权默认隐藏。 | 静态导航与守卫对照确认不一致。 | WP-6 页面权限显隐及旧标题任务收口 |
| `QA-ASSET-001` | 资产 E2E 漂移 | P2 | 高 | `DUPLICATE` | `ACTIVE_TASK_OPEN` | `NOT_APPLICABLE` | `NOT_REQUIRED` | `page-static-review.md#page-dup-p2-001`;活动任务 `05-31-unified-page-header-actions` | 自动化仍按旧交互执行,不能证明当前页面。 | 测试跟随当前“更多”菜单交互,不为旧测试恢复旧按钮。 | 只读测试/页面对照确认重复项。 | WP-6 页面权限显隐及旧标题任务收口 |
| `QA-HEADER-001` | 标题动作合同 / 视觉覆盖 | P2 | 高 | `DUPLICATE` | `ACTIVE_TASK_OPEN` | `NOT_APPLICABLE` | `NOT_REQUIRED` | `page-static-review.md#page-dup-p2-002`;活动任务 `05-31-unified-page-header-actions` | 标题区可能继续堆叠动作且现有回归不报警。 | 可测试的主操作/更多槽位及项目、资产代表性视觉断言。 | 静态组件与测试矩阵确认重复项。 | WP-6 页面权限显隐及旧标题任务收口 |
| `QA-ASSET-001` | 资产 E2E 漂移 | P2 | 高 | `DUPLICATE` | `LOCAL_FIX_VERIFIED` | `NOT_APPLICABLE` | `NOT_REQUIRED` | `page-static-review.md#page-dup-p2-001`;归档任务 `05-31-unified-page-header-actions`;任务 `07-18-small-team-internal-readiness/evidence.md` | 自动化仍按旧交互执行,不能证明当前页面。 | 测试跟随当前“更多”菜单交互,不为旧测试恢复旧按钮。 | 资产 E2E 已改为“更多→资产分类”,spec 可解析且实际菜单行为/代表视觉由候选浏览器回归通过。 | WP-6 页面权限显隐及旧标题任务收口 |
| `QA-HEADER-001` | 标题动作合同 / 视觉覆盖 | P2 | 高 | `DUPLICATE` | `LOCAL_FIX_VERIFIED` | `NOT_APPLICABLE` | `NOT_REQUIRED` | `page-static-review.md#page-dup-p2-002`;归档任务 `05-31-unified-page-header-actions`;任务 `07-18-small-team-internal-readiness/evidence.md` | 标题区可能继续堆叠动作且现有回归不报警。 | 可测试的主操作/更多槽位及项目、资产代表性视觉断言。 | 七页面“一主操作+最多一个更多”合同、项目/资产代表视觉和资产菜单行为纳入 44/44 候选回归。 | WP-6 页面权限显隐及旧标题任务收口 |
| `QA-COVERAGE-001` | 风险型测试覆盖 | P2 | 高 | `CONFIRMED` | `OPEN` | `NOT_APPLICABLE` | `NOT_REQUIRED` | `engineering-review.md#测试资产与实测覆盖率` | 全量用例通过仍不能防止关键 worker/脚本覆盖退化。 | 对认证、资金、worker、迁移/恢复入口设置分层防回退门禁。 | 旧基线 coverage 与入口清单确认关键运行面无阈值。 | WP-4 发布门禁可信化 |
| `REL-GATE-001` | 发布门禁可信度 | P1 | 高 | `CONFIRMED` | `OPEN` | `NOT_APPLICABLE` | `NOT_REQUIRED` | `engineering-review.md#流程覆盖扫描可信度`、`#项目级发布门禁可执行性`;`current-head-delta.md` | 标准门禁不可移植且会把大量未映射路由报告成无高风险缺口。 | 仓库内可执行、显式映射/豁免、排除 manual/live、缺口非零退出并自测反例。 | 当前 HEAD 扫描 68 路由仍有 39 未映射却无 gap;标准 runner 依赖失效外部前置。 | WP-4 发布门禁可信化 |
| `MOB-ARCHIVE-001` | 移动归档详情响应式 | P2 | 高 | `CONFIRMED` | `OPEN` | `NO_FIX_TO_DEPLOY` | `EXTERNAL_BEHAVIOR_PENDING` | `baseline.md#移动路由静态合同`;`browser-review.md` | 长标识使详情内部内容被隐藏,用户无法横向找回。 | 保持路由/卡片/底栏基线并让详情长字段安全换行或可控滚动。 | 375×812 的 URL、H1、空态、列表和底栏通过;长详情 panel 351/457,约 105px 被 `overflow-x:hidden` 裁切。 | WP-6 页面权限显隐及旧标题任务收口 |
| `REL-GATE-001` | 发布门禁可信度 | P1 | 高 | `CONFIRMED` | `LOCAL_FIX_VERIFIED` | `NOT_APPLICABLE` | `NOT_REQUIRED` | `engineering-review.md#流程覆盖扫描可信度`、`#项目级发布门禁可执行性`;`current-head-delta.md`;任务 `07-18-small-team-internal-readiness/evidence.md` | 标准门禁不可移植且会把大量未映射路由报告成无高风险缺口。 | 仓库内可执行、显式映射/豁免、排除 manual/live、缺口非零退出并自测反例。 | `full` 门禁在仓库内 exit 0;71 路由、10/10 witness、7/7 acceptance、52 个反例与 44/44 浏览器结果通过,且 full/focused/list 标签分离。 | WP-4 发布门禁可信化 |
| `MOB-ARCHIVE-001` | 移动归档详情响应式 | P2 | 高 | `CONFIRMED` | `LOCAL_FIX_VERIFIED` | `DEPLOYMENT_PENDING` | `EXTERNAL_BEHAVIOR_PENDING` | `baseline.md#移动路由静态合同`;`browser-review.md`;任务 `07-18-small-team-internal-readiness/evidence.md` | 长标识使详情内部内容被隐藏,用户无法横向找回。 | 保持路由/卡片/底栏基线并让详情长字段安全换行或可控滚动。 | 375×812 的空态、记录、长详情、底栏与 document 横溢断言及脱敏截图通过;企业微信真机仍待授权。 | WP-6 页面权限显隐及旧标题任务收口 |
## 不进入 Finding 的观察与外部补证