feat: 完成错误原因合同与付款就绪提示(09-05-error-explainability-payment-readiness)
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

这个提交包含在:
2026-09-05 19:00:35 +08:00
父节点 ab82fe30c0
当前提交 a837912638
共修改 42 个文件,包含 2867 行新增和 876 行删除
@@ -0,0 +1,150 @@
# Implementation Plan:错误可解释性与付款就绪治理
## 0. 开工门
- [x] 用户复核并批准 PRD、design、implement。
- [x] 批准后运行 `task.py start`;inline 模式实施前加载 `trellis-before-dev`,不派 implement/check sub-agent。
- [x] 记录开工基线、工作区状态和当前 20 处旧字符串测试命中清单。
## 1. 原因码合同与 parity
1. [x] 新增原因码注册表和 reason-aware validation helper;保留顶层 `VALID_001` 与 legacy details。
2. [x] 新增客户端 reasonCode 映射与未知错误安全兜底,接入 `ApiError.requestId`。
3. [x] 建立 AST/结构化 parity 检查和版本化遗留指纹:基于规范化 AST 节点形状而非源码文本,纯格式化不得改变指纹;注册唯一性、服务端使用、客户端覆盖、孤儿映射、新增遗留、过期例外全部 fail closed。
4. [x] self-test 覆盖同 key 跨域、未注册、拼写、缺映射、一增一删、结构性指纹变化、纯格式化不变、到期和主链路试图加例外。
5. [x] 接入既有治理入口;报告只输出相对文件、原因码/域、计数和稳定 finding。
## 2. 首批主链路迁移
1. [x] 迁移报销提交/编辑/票据关联的用户可达校验。
2. [x] 迁移付款创建/复核/回单/登记的用户可达校验。
3. [x] 迁移请假提交、考勤修正提交的用户可达校验。
4. [x] 主链路达到零例外;其他存量按域记录 owner/reason/expiresOn,不使用永久目录豁免。
5. [x] 逐项处置 20 处旧字符串:
- 客户端 4 个 mock 改为 reasonCode,并断言具体提示/安全兜底;
- 主链路服务测试断言 exact reasonCode、status 和兼容 details;
- workflow utility、ERP procurement、budget 等逐项迁移或登记有效遗留;
- `failure_message`、批处理 reason 等持久化/内部摘要单独验证兼容,不与 UI 文案混同。
## 3. 付款 readiness
1. [x] 抽取共享 resolver,复用快照、部门 gate、账户、占用和 reviewer 事实;搬移 create 条件而非复制。
2. [x] 候选批量计算单条 readiness,避免 N+1;异常候选仍返回但不可选择。
3. [x] 增加多选组合只读 readiness API;严格输入,响应仅含安全 blocker。
4. [x] create 在锁和事务内复用 resolver;失败零 batch/item/成功 audit/notification 副作用。
5. [x] 覆盖资料或账户变化后的即时重算,不新增持久字段或长期缓存。
## 4. 付款页面
1. [x] 扩展 API types;候选行显示就近 blocker,正常行无解释噪音。
2. [x] 异常候选不可选择并具备可访问原因;有维护权限才显示维护入口。
3. [x] 多选变化查询组合 readiness;提示紧贴主按钮,按钮禁用、handler 和 create 二次拦截消费同一原因映射。
4. [x] 并发失败留在动作附近;toast 为补充;未知原因使用 request ID 安全兜底。
5. [x] PC 覆盖主路径;移动端保持现有能力边界且不显示技术错误。
## 5. 验证
- [x] `node scripts/qa/check-error-reason-contract.mjs --self-test` 与 strict 正常扫描。
- [x] Server:registry/helper、付款 readiness/route、报销/B2、请假、考勤 focused Vitest;涉及 workflow/状态机时做只读 review。
- [x] Client:error mapping 测试;付款 Playwright 覆盖正常、三类 0014 缺项、非建行、跨公司、占用、无 reviewer、并发、未知 request ID fallback、键盘/ARIA。
- [x] `cd server && npm run prisma:validate`;触达 JS 运行 `node --check`。
- [x] `cd client && npm run typecheck && npm run build`。
- [x] UI strict/self-test、Trellis governance、敏感信息扫描、`git diff --check`。
- [x] 核对 schema/migration/seed/flag/routing 和生产数据修改为零。
## 6. Spec、提交与部署
- [x] 实现验证稳定后,把“原因码 + 遗留指纹 + 主链路零例外”和“共享 readiness 多消费者”沉淀到现有 spec,不新建泛化设计文档。
- [x] spec 明确迁移一条遗留原因码的完整合同:服务端注册、客户端映射、具体内容断言三件缺一不可。
- [ ] 提交建议:A 原因码/parity;B 首批迁移;C 付款 readiness/UI;task 元数据另行回填。
- [ ] commit message 使用中文并引用 `[09-05-error-explainability-payment-readiness]`。
- [ ] commit、push、provenance、部署分别批准;切换前再次运行 UI strict。
- [ ] 部署保持 `SYNC_ENV=0`、existing 数据合同,无 migration/schema/seed/flag/routing/资料补录。
- [ ] 部署后核版本、公网文案、合成 readiness/错误投影、B2、maker-checker 和真人单据状态。
- [ ] 不自动创建 0014 批次、不复核、不登记付款;真实业务由授权人员完成。
- [x] 回滚无需数据库补偿;新客户端遇旧服务端未知码仍须显示可行动兜底。
## 7. 开工基线与 20 处旧断言处置
2026-09-05 恢复实施;开工 HEAD 为 `ab82fe3`,仅当前 task 目录未跟踪并已保留。
依据交接中的明确批准幂等执行 `task.py start`;本会话主会话实施/检查,没有派 implement/check agent。
原生 `update_plan` 未在工具集中提供,未创建替代 plan;继续以原已批准清单执行。
下表行号是开工前定位,不以当前字符串总数作为完成证据。兼容响应的原始 message
仍可以出现在测试 fixture;用户展示断言必须具体,不能用宽泛正则掩盖。
延期域均由 `shunli` 负责,遗留 AST 基线到期日为 **2026-10-05(UTC)**;未增添主链路例外。
| 编号 | 开工前测试定位 | 逐项处置 |
| --- | --- | --- |
| T01 | `client/tests/e2e/r103-cross-page-ui-consistency.spec.ts:544` | 采购附件 mock 增加 `purchase_attachment.unsupported_type`;对应服务端分支、固定中文断言和安全上传浏览器断言一并保留。 |
| T02 | `client/tests/e2e/r111-invoice-reimbursement-guard.spec.ts:448` | 提交 mock 改为 `reimbursement.items_invoice_item_guard_failed`;保留四类精确数量解释、异常字段安全兜底与双端就近提示断言。 |
| T03 | `client/tests/e2e/r111-invoice-reimbursement-guard.spec.ts:481` | 明细关联 mock 改为 `reimbursement.invoice_invalid_unlinked_item_state`;保留准确中文和原子关联断言。 |
| T04 | `client/tests/e2e/pc-finance-admin-visual-wave4.spec.ts:83` | 核验 mock 分别登记频控/次数上限原因码;服务端注册、客户端映射、固定内容和 route details 精确断言齐全;旧 `VALID_001:` UI 前缀断言改为无技术前缀的准确中文,不用正则。 |
| T05 | `server/tests/unit/utils/workflow.test.js:64` | workflow 配置工具延期;保留原准确消息断言,对应 `utils/workflow.js` 到期指纹。 |
| T06 | `server/tests/unit/utils/workflow.test.js:65` | workflow 空步骤配置延期;保留原断言,对应同文件独立调用指纹。 |
| T07 | `server/tests/unit/utils/workflow.test.js:87` | approval-rule 配置工具延期;保留原断言及到期指纹。 |
| T08 | `server/tests/unit/utils/workflow.test.js:94` | approval-rule 无效配置分支延期;保留原断言及到期指纹。 |
| T09 | `server/tests/unit/utils/workflow.test.js:112` | approval-rule 结构配置分支延期;保留原断言及到期指纹。 |
| T10 | `server/tests/unit/utils/workflow.test.js:331` | 缺少 assignee rule 配置延期;保留原断言及到期指纹。 |
| T11 | `server/tests/unit/utils/workflow.test.js:332` | 未知 assignee rule 类型延期;保留原断言及到期指纹。 |
| T12 | `server/tests/unit/services/leave.test.js:50` | 改为精确 `VALID_001`、422、`leave.leave_invalid_range` 与兼容 `leave: invalid_range`。 |
| T13 | `server/tests/unit/services/leave.test.js:98` | 改为精确 `VALID_001`、422、`leave.leave_non_working_range` 与兼容 `leave: non_working_range`。 |
| T14 | `server/tests/unit/routes/integrations.erp.procurement.test.js:49` | ERP procurement 非首批域延期;保留原消息断言与 integrations 模块到期指纹。 |
| T15 | `server/tests/unit/routes/integrations.erp.procurement.test.js:57` | ERP procurement 另一资格分支延期;保留原断言,不扩展集成能力。 |
| T16 | `server/tests/unit/routes/paymentBatches.reimbursement.test.js:2341` | 内部持久化 `failure_code/failure_message` 及 API 字段兼容断言保留;执行风险既有 JSON 仅附加注册原因码、刷新安全说明,不改风险状态;明细/风险卡经专用 helper 展示,历史与未知原因安全兜底,不直接展示原摘要。 |
| T17 | `server/tests/unit/routes/workflow.batch.test.js:377` | 内部批处理 reason 兼容断言原样保留;不把批处理摘要冒充用户恢复文案。 |
| T18 | `server/tests/unit/routes/workflow.permissions.test.js:172` | workflow 管理查询域延期;保留准确消息与 `routes/workflow.js` 到期指纹。 |
| T19 | `server/tests/unit/budgetPool.service.test.js:28` | budget scope 配置域延期;保留原断言与 `services/budgetPool.js` 到期指纹。 |
| T20 | `server/tests/unit/budgetPool.service.test.js:75` | budget pool 候选配置域延期;保留原断言及到期指纹,不变更预算规则。 |
## 8. 实施与只读复核记录
- 原因码合同接入既有 `run-release-gate.sh` static 阶段;主链路包含 invoice bind/unbind
与共享 item resolver,不借整目录永久豁免绕过。
- 候选、组合预检和创建事务复用 `loadPaymentCreationFacts` 与
`resolvePaymentBatchCreationReadiness`;候选关系、占用、公司账户、复核人批量读取。
部门 gate 仅在单次事实加载内复用底层 flag 查询,仍调用原判定实现;没有修改 flag。
- 只读 reviewer 首轮发现两项 P2:未选中时无法切换付款渠道、失效已选项无法取消。
已分别移出渠道选择器的选中条件、允许失效已选项取消,并补浏览器回归。
第二轮只读 review 确认这两项闭环及请求内 gate 查询复用;无新增高置信问题。
- 系统 Python 缺 `xlwt` 导致既有银行模板导出测试失败;使用现有隔离测试环境提供的
Python、通过 `PAYMENT_ORDER_PYTHON_BIN` 运行后恢复通过。未改依赖清单或生产配置。
- 首轮扩大浏览器运行另包含一个合同只读用例超时,未完成该项归因,未改合同产品逻辑或该断言;
付款、B2、采购与本轮核验反馈改为明确 focused 集重跑,不把无关失败标成通过。
- 已更新现有 backend/frontend spec;根因归类为跨层合同和传播缺失,而非真实资料补录。
- 本轮不提交、不 push、不部署、不归档;真实资料和 0014 的任何业务写入均未执行。
部署后 AC12 仍须独立批准与公网证据,不能用本地合成结果替代。
- 最终检查进一步发现两处付款明细直出旧失败摘要,已按 T16 补齐安全展示转换;
新原因只保存在既有执行风险 JSON 元数据中,未新增 schema、readiness 持久字段或日志机制。
追加只读 review 确认历史兼容、事务和风险状态边界,无新增高置信问题。
## 9. 本地验证结果(2026-09-05)
| 验证 | 结果与边界 |
| --- | --- |
| AST strict | PASS;142 注册原因、199 已迁移使用点、695 遗留调用、主链路例外 0、finding 0。遗留身份同时包含相对文件、operation 和规范化 AST 形状,不能靠移动操作或一增一删抵消。 |
| AST self-test | 20 项 PASS;正常跨域与纯格式化退出 0,其余拒绝场景退出 1,包括六类必查失败、主链路加例外、同数替换、别名、具体内容断言缺失、operation 移动、条件式通用码和不安全文案。 |
| 扩展服务端 focused | 23 文件 / 407 用例 PASS:报销、票据、请假、考勤、付款、readiness、registry/helper 和客户端投影。 |
| 后续兼容/新增边界 focused | 8 文件 / 309 用例 PASS:包含当轮 75 个付款 route 用例、149 个具体内容/安全投影用例、采购附件和延期域/内部摘要兼容测试;与上一行有交集,不合并虚报数量。 |
| 最终摘要投影 focused | 4 文件 / 244 用例 PASS:76 付款 route、150 内容/投影、16 readiness、2 helper;新增既有风险元数据更新与历史摘要保护,和前述测试有交集。 |
| Playwright focused | 18 个不同用例 PASS(原 17 项 + 最后追加的失败摘要保护 1 项):银行正常提交、现金渠道、三类缺项、非建行、跨公司、占用、无 reviewer、创建并发、移动未知原因排障编号、键盘/ARIA、渠道可达与失效项取消、B2 双端、采购附件、核验错误、PC/移动视口历史失败明细及风险保护。仅本地全合成 API mock。 |
| 客户端工程 | `npm run typecheck`、`npm run build` PASS;未部署构建产物。 |
| 服务端工程 | Prisma validate、触达 JS/MJS `node --check` PASS;没有执行 migration 或数据库重置。 |
| 治理 | UI strict/self-test、process baseline、docs registry、Trellis validate/governance、敏感扫描、`git diff --check` PASS。 |
| 只读 review | 首轮两项 P2 已修复;第二轮确认闭环;追加摘要投影与风险元数据复核无新增高置信问题。Reviewer 未写文件、未操作真实数据。 |
| 边界核对 | schema/migration/seed/flag 实现、前端 routing、真实资料与真实付款写入均为 0;0014 未操作。 |
复跑入口:`node scripts/qa/check-error-reason-contract.mjs --strict` 与 `--self-test`;
服务端测试中的银行模板导出需把 `PAYMENT_ORDER_PYTHON_BIN` 指向满足现有锁定依赖的隔离测试 Python。
浏览器 focused 文件为 `payment-reviewer-permissions.spec.ts`、`r111-invoice-reimbursement-guard.spec.ts`、
`r103-cross-page-ui-consistency.spec.ts` 和 `pc-finance-admin-visual-wave4.spec.ts`,筛选本表相关用例;
不得对公网或真实账户套用这些 mock 场景。
## 10. 独立复核、遗留验证事项与发布授权(2026-09-05)
- 用户独立复核 PASS:真实调用点注入未注册原因码时 strict 退出 1;向主链路新增例外时 strict 退出 1;确认 readiness 三处共用、20 项 self-test 及格式化指纹稳定。用户确认探针全部还原,基线校验与备份一致。
- 两个 task 已按完整文件物理分离;本目录仅保留错误可解释性与付款 readiness,Trellis 升级在独立 worktree 待单独审查。原治理文件均与开工 HEAD 一致,分离后原因码及治理门禁通过。
- 未解决验证事项:首轮扩大浏览器运行的合同只读用例超时,**未归因、不计入通过结果**,不是已确认的产品缺陷或可永久忽略的 flaky。后续合同域验证前必须单独复现,补充精确用例、失败步骤及环境证据后归类;本任务不得通过放宽断言、重试至绿或混入 focused PASS 关闭它。
- 用户明确批准本任务提交、push、部署;部署后核验 `/ready` 与 bundle marker、文案扫描零 findings、0014 候选禁用及缺项原因、创建接口 fail-closed、B2 保持 force_on + global。
- 生产核验只使用最小脱敏结果,不读取或修改真实银行卡资料;不补录 0014,不执行真实付款。若前置条件变化可能导致创建成功,停止强行创建探针。
- Task 保持 `in_progress`;独立复核通过不等于公网 AC12 完成。发布和公网证据待实际执行后回填,不提前标记 PASS,不归档 Trellis 升级任务。