文件
hyb-oa/.trellis/tasks/08-29-reimbursement-expense-policy-invoice-guard/implement.md
T
wangsli和Claude Sonnet 5 e57ed0533b 回填e5685f1公网合成验收审计记录 [08-29-reimbursement-expense-policy-invoice-guard]
从孤儿 worktree(artifacts/isolated-worktrees/20260909-invoice-first)合并
回填 2026-09-09 部署 e5685f1 后从未提交的公网验收记录:

- 部署执行计划与 deploy-existing.sh 中途 exit=255 异常的只读排查(构建/
  迁移/健康检查实际均已完成,255 原因未定位但不代表发布未发生)。
- 公网止于草稿的合成验收:真实腾讯 OCR 队列、选票、分类编辑、跨端持久化
  全链路通过;清理工具认证问题(缺 x-device-fingerprint)排查与修正。
- 2026-09-10 最终 PASS:SCOPED_CLEANUP_PASS,测试数据精确清理确认。

无产品代码改动、无生产操作;仅补齐审计记录,使任务文档反映已发生的部署
与验收历史。

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-25 19:45:30 +08:00

43 KiB
原始文件 Blame 文件历史

实施清单:报销费用口径与票据明细门禁

当前接续点:已部署,公网草稿闭环验证完成

2026-09-10 公网草稿最终 PASS(本节替代前次样票阻塞状态)

  • 用户再次批准继续验证。未修改产品代码、权限、审批定义或生产配置,运行版本仍 e5685f1;本轮仅维护受控验收脚本和记录,无再次部署。
  • 生成新版全合成 PDF:采用表格化票面布局,明确标注不可报销/支付,无真实交易/账号/票据内容。每轮独立合成票号,不修改真实样票、不绕查重。先走部署环境已有 provider 检查,再经线上 UI 上传走真实队列及腾讯;最终样本摘要与 probe manifest、生产持久化发票逐一一致。
  • 最终命令:node artifacts/qa-flow/20260909-release-e5685f1/probe-synthetic.cjs && node artifacts/qa-flow/20260909-release-e5685f1/verify-live-draft.cjs,exit=0,证据 live-draft-result-v6.json=PASS_DRAFT_ONLY。专用 employee 真实浏览器登录1次 HTTP200,上传、OCR available、用户点击添加前零明细、一票一明细、批量分类、编辑说明、桌面刷新持久化均通过;同单转手机路径修改说明、保存刷新通过;真实 precheck POST 正常返回。submitRequests=0、blockedWrites=0,不是公网审批验收。
  • 只读最终回验:6 张本阶段合成票均 archived/manual 且无单据/明细/差旅绑定,标记测试草稿0,测试时间窗口内活动会话0。最后样票的哈希、合计1.00、税额0.00、日期2026-09-10、provider=tencent 均匹配;原始合成文件及OCR审计资料按正常保留机制留存,不能宣称物理删除。证据 live-draft-final-readback.json。
  • 中间失败保留而不抹掉:v2 桌面通过但拦截10次 precheck;核实该 POST 是只读预检查后放行,仅禁止提交等范围外写入。v3 分类后马上编辑的持久化断言失败,测试改为等待已分类视图并回读类型后再编辑,后续通过;这不证明所有快速点击竞态均无风险。v4/v5 手机说明定位超时;本地最小 HTML 复现 getByLabel('说明', exact=true) 对已有内容的 textarea 命中0,按 label 的 span + textarea 定位命中1,v6 补验通过,无产品修补。
  • 清理工具根因补验:Fastify 合成 DELETE 带 application/json 却没有 body,稳定返回400/FST_ERR_CTP_EMPTY_JSON_BODY;不发送多余 Content-Type 返回200。修正后各轮均经业务API删除草稿及归档合成票,不再直接写库。登录必须带设备指纹;logout 必须使用浏览器Cookie上下文,不把无Cookie返回200写成会话已撤销;最终数据库只读计数提供撤销证据。
  • 现已补上“公网真实腾讯→队列→选票→可编辑草稿”的同环境证据,不再沿用先前只能分层推断的缺口。边界仍是公网不提交、不审批、不付款;提交及三段审批沿既有隔离环境证据,不能扩成真实用户/全票种/整站全面验收。
  • 主线本阶段完成;下一阶段按已有顺序进入 09-04 退回删除修复,再做 09-08 付款文件名/下载理由/进度布局,复用原任务、不叠改当前产品。部署/验收记录与新增经验尚未提交,不自动归档整个历史任务。

2026-09-10 公网止于草稿的实测结果

  • 用户批准公网只验上传/OCR/选票/分类编辑/保存刷新,不提交审批。定位到既有仓库外 live-smoke.env(0600),仅在进程内读取 employee 凭据;此前“常规 env 无配置”不等于专用凭据不存在。生产只读前置确认唯一 active、未锁定、无需改密且角色精确 employee 的 smoke 账号,未改账号或生产配置。
  • 执行 node artifacts/qa-flow/20260909-release-e5685f1/verify-live-draft.cjs:真实浏览器登录一次 HTTP 200,创建标记草稿,上传一份 Chromium 生成的中文 PDF,显式标注“合成测试样张、不可报销、无支付效力”。未使用用户真实票据、未改 provider 或注入 OCR 结果。上传创建成功,轮询终态 ocr_failed,结果 STOPPED/SYNTHETIC_OCR_NOT_AVAILABLE。
  • 这证明登录、草稿创建、上传及识别失败终态路径;不能证明腾讯能识别该合成版式,不据此认定生产 OCR 配置坏了,也不记自动建明细/分类/编辑刷新通过。submitRequests=0;浏览器安全拦截了 6 个未列入本次写入白名单的请求,具体类型未记录,不谎称全部请求均正常。未产生审批任务或付款。
  • 验收脚本自动清理未成功,后续仅清理用途的 API 登录也未通过;已核实该清理请求缺少生产要求的 x-device-fingerprint,不能记为账号密码失败,不继续重试登录。原脚本 loggedOut=true 仅表示无 Cookie logout 返回成功,不证明会话撤销,该结论撤回。
  • 随后只读确认恰有本次前缀标记草稿 1 张(draft、明细0)与合成文件名票据1张(ocr_failed、未绑定)。按已获准清理范围执行专用 Serializable 事务:锁单/票后重验唯一数量、smoke精确角色、owner=submitter、标记、状态、无明细及无绑定;只删除该草稿、归档该失败合成票、撤销按该次建单/上传时间窗口唯一定位的测试会话。任一条件不符全事务拒绝。
  • 最终 SCOPED_CLEANUP_PASS:草稿删除回读0、合成票归档、测试会话撤销。合成 PDF 与 OCR/审计记录按既有保留机制留存,不声称物理文件已删除。未清理其他账号、单据、发票或会话;未做真实业务写入。
  • 证据在既有发布目录:live-draft-result.json 保留原失败,live-draft-cleanup.json 保留清理API失败,live-draft-cleanup-final.json 记录最终定点清理。仅测试工具与本记录变化,无产品改动、未再部署。
  • 后续缺口:需要可被腾讯识别且不会占用真实报销票号的专用测试样票,或另行批准真实样票在确认无业务占用/查重风险后的受控用途;不能以随意排版的合成 PDF 冒充已验证发票样本。先修正测试器认证/会话清理与失败诊断,再安排下一次受控验证,不交给用户手工测试,不反复消耗账号登录。

公网合成业务验收前置(用户已授权,停在写入前)

  • 用户已明确授权专用测试账号的合成报销上传/OCR/选票/分类编辑/提交审批及测试数据清理;不包含真实报销、真实付款、修改真人账号或生产审批路由。
  • 本轮只读查询运行容器的账号状态及当前报销工作流,事务/连接强制 read only,仅回传测试别名、角色与状态。四个既有 smoke 候选:两个 active employee+manager(其中一个 must_change_password=true),一个 active employee,一个 archived employee+finance;启用 smoke 角色集合只有 employee、manager,没有 ceo。不得将已归档财务视为可直接登录,也不以管理员替代财务验收。
  • 当前活动报销工作流 assignee_rule 为 role=ceo,不是直接经理步骤。即使测试员工有测试直接经理,也不能据此保证提交不会派给真人;目前缺少仅限本次测试的审批隔离路径。不能为了跑通而给测试账号增加 ceo(会影响全局角色路由)、修改活动工作流或擅自启用财务。
  • 当前执行进程、常规本地 env 与生产容器未配置 LIVE_SMOKE 凭据键。这不证明仓库外凭据不存在,也未尝试密码、重置账号或复制生产身份。获准的此前真实样票只做过 provider 检查,不能直接占用为合成报销材料或绕过真实票据查重。
  • 本次 loginAttempts=0、businessMutationCount=0、workflowSubmitCount=0,无新建单据/票据/会话需清理;没有尝试完整公网闭环,不能记 PASS。已部署健康检查结论不受此影响。
  • 建议调整验收边界:公网用获准专用凭据及腾讯可识别的测试样票验到可编辑草稿,不提交进入真人待办;提交审批继续沿已通过的隔离数据库/真实认证/队列/浏览器三段审批证据。若要求公网全链独立通过,先另审仅测试数据的审批隔离方案和财务启停窗口,不在本轮临时改生产规则。

e5685f1 已部署与后置检查补齐(2026-09-09)

  • 生产实际运行 e5685f18b233901cffc9a12c9c277cc5c642725c。原 deploy-existing 调用最终 exit=255,日志仅回传到 build-init,不能改写为原脚本 exit=0;255 原因尚未定位,不凭退出码断言产品失败或发布完全没发生。
  • 中断后只读核实已完成构建、切换与迁移,未盲目重发整次部署或回滚。运行源码摘要与候选一致;init 日志顺序为 validate_config → migrate → check_required_configuration,INIT_PASS/existing,无 seed 阶段。
  • 单独补跑生产身份/迁移后置预检 PASS,pending=none;数据库只读校验费用类型列 nullable,金额/日期仍 NOT NULL,本迁移成功记录一条、当时 null 分类明细为 0。该计数会随用户操作变化,不构成以后随时切旧镜像安全的保证。
  • 单独执行既有 external-deps smoke 与 runtime smoke 均 exit=0;公网 ready/home/主脚本 HTTP 200,buildSha=e5685f18b233;既有公网 bundle-copy 扫描 106 项、focused=2、buildMarker PASS、findings=0。
  • 受控身份 probe/refresh 均 PASS,最终回读 backupContractBuildMatches=true。本次属于应用已部署后补齐后置检查,不把原 exit=255 回执覆盖成成功。未新增产品修复、未执行数据库恢复。
  • 发布前正式备份完成于 15:33:41 UTC,属于 b87cc55,manifest/数据库/附件大小与摘要匹配;旧 b87cc55 镜像保存为独立文件,14 层摘要通过,未演练 load/恢复。刷新备份合同不等于已产出 e5685f1 新版本备份;旧备份及回滚制品保留。发布后可用空间 43.55 GiB,无追加清理。
  • 公网 Chromium 打开站点后进入登录页,登录内容可见;未登录业务账号、未创建真实或合成报销。两条控制台资源错误均为 favicon.ico 404,本任务未改图标,记录为非阻断观察,不擅自扩修。首次 CLI 默认 Chrome 缺失,改用已安装 Chromium 后通过;首次 /ready 检查误读顶层 buildSha,按既有 data 包装修正检查后通过,均非产品修复。
  • 证据入口仍为 artifacts/qa-flow/20260909-release-e5685f1:原 deployment-result.json、备份与独立制品回执、recovery-smoke.json、post-preflight-result.json、final-postcheck-result.json、public-check.json、浏览器快照。原日志受控留存,不输出原始环境或票据。
  • 当前结论:已部署、发布健康及结构检查完成;不是公网带账号的报销完整业务验收。下一步由执行端取得专用测试账号及线上合成单据写入/清理的明确授权后,验证上传→识别→选票→分类编辑→提交审批,不让真实员工代测,不以此授权真实付款。退回删除、付款体验两个 task 仍未实施,整个 task 保持 in_progress。

e5685f1 发布执行计划(2026-09-09,用户明确授权部署)

  • 固定已推送候选 e5685f18b233901cffc9a12c9c277cc5c642725c;使用干净发布 clone,不将本记录或其他未提交内容同步生产。证据目录 artifacts/qa-flow/20260909-release-e5685f1。
  • 只读前置已通过:运行版本 b87cc55、最低配置 39/3/1;待迁移仅 20260909210000_reimbursement_draft_expense_type;磁盘可用 45.52 GiB。预检 rollbackWindow=OPEN 仅是 terminated 状态统计,不是数据兼容或恢复演练结论。
  • 发布前保存当前 b87cc55 独立镜像并核归档层摘要,触发既有正式备份服务,再核同运行版本的 manifest/数据库/附件完整性。失败停止,不绕权限、不清理旧制品。
  • 沿 deploy-existing.sh:existing、SYNC_ENV=0,预检待迁移显式匹配,执行本次 DROP NOT NULL,不运行 seed、不重建数据库容器;随后核迁移无 pending、版本、ready、依赖/runtime smoke、公网资源及合同 refresh。
  • 此次仍无完整候选门禁通过证据,附件/应用恢复未演练;分层腾讯与本地流程证据不等于公网完整报销验收。用户已授权准备并部署,不记录为完整门禁通过。
  • 回退目标为 b87cc55,不是此前 f223bfb。迁移后若未生成 null 分类,旧应用兼容仍需核验;若存在 null,不自动切旧镜像、不逆迁移、不猜分类回填。失败先确定应用切换和数据库阶段,保全证据并报告;数据库恢复及可能丢失恢复点后数据不自动执行。
  • 上线自动化由执行端承担;不处理真实报销、不真实付款。本次未额外授权线上合成单据写入,先做既有发布健康/smoke与只读版本/结构检查,不能以此冒称已走通公网报销全链路。

用户复核与提交授权(2026-09-09)

  • 用户回复“通过”,接受本次复核材料;这是用户复核结论,不改写为执行端独立 reviewer 已运行。
  • 用户随后明确授权仅本 task 的 28 个文件提交并推送,冻结新候选;未授权部署、生产迁移或线上写入验证。
  • 提交前 fetch 核对远端 main 仍与执行副本基线 b87cc55 一致。本次以独立副本的提交快进推送 main,不切换或覆盖主工作树的其他未提交任务;不强推、不新建分支。
  • 下文“未提交/待独立复核”为此前阶段记录。提交与推送结果以 Git 对象及远端回读为准;发布仍待后续明确批准,task 保持 in_progress。

2026-09-09 最终收口与复核入口

  • 执行副本:artifacts/isolated-worktrees/20260909-invoice-first,基线 b87cc55,官方 current 指向本 task;基线不是包含本次修复的候选 SHA。当前产品增量未提交,主工作树其他任务不得整体合入。
  • 范围为 28 个文件(21 个 tracked 修改、7 个新增文件)。复核必须同时读取 git diff HEAD 和 git ls-files --others --exclude-standard 列出的新文件,尤其真实 HTTP 测试脚本、迁移、分类服务与共享组件;只看 diff stat 会漏新增文件。此记录是复核入口,不是已冻结的哈希清单。
  • 本轮核对服务端事务、提交拦截、双端选票/批量分类和最新合同,纠正 PRD/design 的历史状态冲突:assets 无现行硬门禁,不能声称保留了不存在的校验;可空迁移已批准本地实施,不再列为方案未定。产品代码未因这次文档收口变化,沿用下方同输入的分层验证结果,不重复累加测试数量。
  • 本地验证已覆盖:可空迁移及反向约束、同单幂等/跨单竞争/失败回滚/绑定解绑锁顺序、真实认证与队列、桌面/手机上传到审批完成。provider mock 的端到端与真实腾讯单次样票验证分开记录;付款、企业微信 OAuth、真实腾讯与候选队列浏览器同一次整链仍未覆盖。
  • 待独立只读复核:权限与参与人、草稿/退回状态、提交前分类和说明阻断、null 与省略的区别、所有报销 bind/unbind 先单后票、批量分类原子范围、OCR 取消/目标选择不被回调覆盖、隔离测试不接触共享数据库及退出清理。当前 inline 未有独立 reviewer,不记独立 PASS。
  • 发布前置仍需:复核后的明确文件集合提交并冻结新 SHA;重新核生产运行版本/变化、migration 状态、证据时效、备份和独立回滚制品。备份完整性不等于恢复能力;本次一旦产生空分类,旧镜像可能不兼容,不能承诺仅镜像回退。若需恢复 NOT NULL,先确认 null 为零,禁止猜分类回填。
  • 下一动作由执行端提供本入口供只读复核并处理 findings;复核后按明确授权提交/推送,再提出发布方案。生产迁移、部署及线上合成写入验证另批。上线验证仍由执行端承担,不把真实员工试错当验收。
  • 退回删除与付款中文文件名/下载理由/进度布局继续留在原 task,本阶段未实施,不归档本 task,也不叠加第三笔工作。

2026-09-09 真实认证与 pg-boss 补验(最新)

  • 已移除业务请求的 x-synthetic-actor 身份注入,改为真实 auth routes、auth plugin、密码哈希、登录接口发 JWT、Cookie 会话和浏览器启动时的 refresh;账号和密码只在隔离环境合成。员工/经理/CEO/财务各自登录,业务权限从真实 employee_roles 读取,不在测试中直接授予审批权限。
  • 无令牌/伪造令牌访问被 401 拒绝,token_version 增加后旧令牌也被 401 拒绝。浏览器测试通过真实登录接口获取 Cookie,未伪造 has_session 或 accessToken;个人信息/导航/通知展示壳仍是测试适配,企业微信 OAuth 和登录表单交互不据此声称已测。
  • RUN_ISOLATED_DB_TEST=1 RUN_BROWSER_TEST=1 node scripts/qa/test-invoice-first-http.cjs:真实认证+手动 worker 模式 14 组 PASS。
  • RUN_ISOLATED_DB_TEST=1 RUN_BROWSER_TEST=1 RUN_JOB_QUEUE=1 node scripts/qa/test-invoice-first-http.cjs:真实认证+真实 pg-boss 模式 14 组 PASS;真实 startPgBossJobs 注册实际 OCR 队列和消费者,上传路由自动 enqueue,未手动调用 worker;独立查询队列中对应 invoiceId 的 ocr.invoice job 已 completed,且发票 available。桌面/手机均完成添加、分类、保存刷新、提交、同单三段审批及只读回读。
  • 队列测试只连接一次性库 synthetic_jobs schema;非本次关注的周期任务设置年度时间避免在测试期间执行,验的是 OCR 即时投递/消费,不宣称生产 cron 时序已验。provider 仍为 mock,不复制腾讯凭据、不使用真实票据;先前指定样票的真实腾讯 PASS 保留为独立证据。
  • 两种模式均已清理 pg-boss、连接池、HTTP/preview、浏览器、临时上传目录和数据库容器。JS 语法、git diff --check、Trellis 治理 PASS;本轮只扩测试,无新增产品修复,未提交、推送或部署。
  • 本地实现与分层验证可进入只读复核准备;独立 reviewer 仍未执行(当前 inline 模式不派发 implement/check 子代理),不把主会话验证记作独立通过。发布候选需包含本次可空分类 migration 及回滚约束,继续另批,不复用旧部署授权。

2026-09-09 桌面/手机串联验证接续(最新)

  • 在既有隔离脚本增加 RUN_BROWSER_TEST=1:使用当前候选的 client/dist、Vite localhost preview 和 Chromium;业务 API 透传至真实 Fastify/Prisma/一次性 PostgreSQL,未拦截伪造报销详情、上传、状态轮询、分类、保存、提交或审批响应。
  • 认证/个人信息/导航/通知壳使用合成适配器,权限来自实际 ROLE_PERMISSIONS。OCR worker 使用真实 runOcrForInvoiceId、存储读取、领取锁、结果落库与 available 状态转换,但 provider=mock 且手动触发 worker;不能据此宣称腾讯网络、pg-boss/cron 调度或真实登录的同环境整链通过。
  • 合成空白 PDF 由脚本生成,桌面/手机分别新上传不同哈希,轮询识别完成默认勾选,添加前真实数据库明细数为 0;一次添加后批量分类、编辑说明、等待 PATCH 200、查库并刷新核对,再从页面提交生成经理待办。双端到待办阶段已通过。
  • 测试开发失败均如实保留原因:Vite CJS 导入形状、未限定明细区域的“编辑”/隐藏文本定位、手机按钮带数量、保存未等响应即刷新;未因这些测试适配问题修改产品合同或放宽断言。现在同时验保存响应、真实数据库值与可见明细内容。
  • 同一张浏览器提交单的经理/CEO/财务三段 HTTP 审批及审批完成后双端只读回读最终通过;真实库状态 approved、页面不再出现添加发票入口。复现命令(先确保 client/dist 来自当前候选构建):RUN_ISOLATED_DB_TEST=1 RUN_BROWSER_TEST=1 node scripts/qa/test-invoice-first-http.cjs。最终 12 组检查 PASS;测试数据库容器、合成文件目录、浏览器、preview/HTTP 服务和连接池均已清理。JS 语法、Trellis 治理及 diff 检查 PASS。保留 Node punycode/Vite CJS 弃用提示,不扩依赖升级。
  • 此前真实腾讯样票 PASS 仍是独立证据,不将其与本节合成 provider 串联结果拼成一次真实云端整链。未重用真实票据、未连接生产、未提交或部署。独立复核和正式调度/认证边界仍待收口。

2026-09-09 用户指定样票的真实腾讯 OCR 验证(最新)

  • 用户指定部署服务器 OCR 环境及一份 PDF 样票,授权本次受控识别;材料仅以 SAMPLE-PDF-INVOICE-FIRST 记录,不保存原文件名、票面内容、账号或完整路径。
  • 先做只读前置核验:运行 build b87cc55aca0003130cf8a5bbe38d0dd15528389c;OCR_PROVIDER=tencent、腾讯凭据存在。远端既有 check-ocr-provider-samples.mjs、OCR index/tencent 三文件摘要与执行副本逐一一致,识别相关代码本 task 无差异。
  • 指定 PDF 经 SSH/stdin 传入现有容器中的 check-ocr-provider-samples.mjs --alias=SAMPLE-PDF-INVOICE-FIRST,使用现有环境凭据,调用一次腾讯接口;不把凭据复制到本地、不生成服务器样票副本、不调用业务上传/写库/队列接口、不改配置、不部署。
  • 结果 PASS,transportMode=pdf_native,elapsedMs=826,documentCategory=full_vat_invoice,documentSubtype=VatElectronicInvoiceFull,documentCount=1。号码、日期、金额、税额、合计、销售方、购买方字段均命中;invoiceCode 未命中但不是全电发票必需字段;missingRequiredFields 为空、errorCode=null。
  • 此证据证明指定样票通过真实腾讯调用、归一化与必填字段检查;没有人工逐字核对识别精度,也不证明所有票种或实际费用分类正确。该票种不提供可靠费用类型推断,仍按既定规则集中补分类,不从商户名称猜用途。
  • 真实 OCR 专项前置缺口已关闭;下节“待指定环境/样票”是历史状态。剩余是候选版本上传→队列识别→浏览器选票→提交审批的同环境串联验收,以及独立只读复核,不能将分层 PASS 相加成整链通过。未提交、推送、部署、写生产业务数据或真实付款。

2026-09-09 上传与审批链补验(最新)

  • RUN_ISOLATED_DB_TEST=1 node scripts/qa/test-invoice-first-http.cjs 最终 10 组检查通过,真实 PostgreSQL/Prisma/HTTP;本轮只扩测试,未改审批产品代码。
  • 真实 multipart 上传合成空白 PDF:文件落入专用临时目录,发票保存 pending_ocr 且未绑定;再次上传同文件复用原记录,不新增票。此用例只验证上传/落盘/待识别状态,不启动 OCR worker,也没有把空白 PDF 当作能识别出票面字段的样票。
  • 使用真实角色权限常量、合成经理/CEO/财务账号与三级规则:提交人自审 403;return 回 draft 后可修改重提;reject 回 rejected 后可修改重提;重提创建新实例;经理→CEO→财务逐步审批最终 approved,重复审批 409,无遗留 pending 任务,不标为 paid。
  • 测试设计初次把 return 当作 rejected、把 CEO/财务角色待办当作个人待办,产生断言失败;核对既有实现后按 draft/角色分派分别断言,保留 reject 独立测试,未为通过测试修改生产状态或审批合同。
  • 所有数据库容器、HTTP 服务、连接池及合成上传目录已清理。认证仍是合成测试适配器,真实登录、腾讯识别、队列消费与真实付款不在这次通过结论内。
  • 真实 OCR 前置:当前进程没有注入腾讯凭据;仅检查主工作树常规本地 env 的键是否存在,结果未配置,不读取回传密钥值,不据此推断生产关闭 OCR。专用执行副本没有已确认获准样票;现有 check-ocr-provider-samples.mjs 空输入演练返回 HOLD/sample_empty,不是实际腾讯调用。下一步需指定获准 OCR 测试环境及脱敏 PDF 样票别名,不从生产复制凭据或擅自发送真实票据。
  • 仍未提交、推送、部署;独立只读复核待完成,旧“仅到派单”是上一阶段证据,已被本节三级审批补验扩展,但不等于上传→腾讯 OCR→浏览器→审批的完整集成验收。

2026-09-09 真实隔离数据库 / HTTP 验证

  • 使用 RUN_ISOLATED_DB_TEST=1 node scripts/qa/test-invoice-first-http.cjs:启动一次性 PostgreSQL 16(仅随机 localhost 端口、tmpfs),执行历史 SQL 与本次迁移,使用独立 Prisma 客户端和真实 Fastify HTTP;不读取共享 DATABASE_URL,不连接生产。结束关闭 HTTP/连接池并删除容器。
  • 已验证空单从已识别合成票添加、未知类型持久化、类型/说明缺失提交阻断、HTTP 批量分类与编辑、正式 submit 路由创建 reviewing 状态和经理待办;重复提交不重复生成实例。认证是合成测试适配器,不冒充真实登录验证;OCR 输入是合成持久化字段,不是腾讯调用证据。
  • 同单 4 请求并发只生成 1 条明细,重试不覆盖人工修改;跨单抢票只有一方成功且无孤立明细;数据库触发器注入绑定更新失败后,明细/占用/总额完整回滚,解除故障重试成功。
  • 实测发现:外部锁屏障下,createItem 重试先锁单后锁票,旧 unbind 先锁票后锁单,两者并发返回 [200,500],不能用顺序模拟测试证明混合路径安全。修复为所有报销绑定先锁单;解绑先读关联快照、锁单再锁票,取得票锁后关联变化则 409,不操作新关联。修复后的真实 HTTP 并发为 [200,200]。
  • 扩展路由/服务回归 18 文件 349/349 通过(随后新增一条解绑快照变化反例,单文件另验);JS 语法、错误码 strict 合同与 diff 检查同步执行。初次集成测试漏配合成账号的 expense.edit_own 导致断言失败,修正测试权限后通过,未放宽产品权限。
  • 当前只证明到提交/派单,不证明经理实际审批、付款、真实 OCR 或上传到 OCR 队列全链路完成。独立复核仍待完成,当前 inline 主会话无独立 reviewer。未提交、推送、部署或真实数据写入。

最终补测:真实 DB/HTTP 脚本 8 组检查全部通过(含同金额票不误合并、已有明细加票不改金额、混入异单 ID 的分类整批回滚);解绑快照变化反例加入后 cd server && npm test -- tests/unit/routes/invoicePool.travel-bind.test.js 20/20 通过。此前扩展 349 用例与这 20 条有重叠,不累加成独立总数。所有自建数据库容器、HTTP 服务和连接池已关闭;没有真实业务数据参与。

2026-09-09 双端面板接续

  • 桌面/手机“我的发票”上传和 OCR 完成只默认勾选;点击添加才提交,整单传 createItem,已有明细入口传 linkedItemId,目标可修改。
  • 返回报销单不占用、不删除池票;关闭清理本次选择。已成功添加从选择中移除,部分失败保留失败项重试。
  • 双端接入共享批量分类组件:只更新用户选中的明细,未知类型集中提示,已分类项可编辑,刷新详情与移动列表缓存。
  • 补桌面添加成功后的目标清理,避免从明细入口添加后,下次整单入口仍指向旧明细。
  • 真实 Prisma + HTTP + 隔离 PostgreSQL 验证同单幂等、跨单竞争、事务失败回滚与空单提交/派单;真实 OCR/上传和完整审批仍按上节留缺口。
  • 获准合成样票的真实腾讯 OCR 验证;不发送真实业务票据,不以 mock 证明 OCR 可用。
  • 独立只读复核(当前 inline 执行未由独立 reviewer 验证);未提交、未推送、未部署,不交真实员工补测。

本节为最新状态,下文“尚未接入页面”等表述保留为历史阶段记录,不代表当前实施状态。退回删除和付款体验仍未启动,继续保持任务隔离。

本轮浏览器与治理证据(均在 invoice-first 执行副本):

  • 最终页面输入 cd client && npm run typecheck && npm run build 通过,2422 模块构建完成;保留 Browserslist 数据老旧提示,不扩依赖升级。自建本地 Vite 验证服务已停止。
  • E2E_BASE_URL=http://127.0.0.1:5187 npx playwright test tests/e2e/r111-invoice-reimbursement-guard.spec.ts --workers=1 --reporter=line --timeout=90000:12/12 通过。
  • 同命令追加 --grep '已有明细入口|多票添加':4/4 通过,包含补充的多票部分失败仅重试失败票,以及成功后重开整单不残留旧目标。
  • 同命令追加 --grep '选票优先空单':2/2 通过,补充分类响应后的界面更新、刷新保留断言。三次有用例重叠,不计作 18 个独立业务场景。
  • 使用真实浏览器和合成 API 拦截,覆盖桌面、手机与企业微信布局、空单上传不绑定、退出留池、一次选票添加、已有明细目标、刷新与局部重试。未覆盖真实腾讯 OCR、真实 Prisma 事务并发或完整审批链。
  • node scripts/qa/check-trellis-governance.mjs、node scripts/qa/check-error-reason-contract.mjs --strict、git diff --check 通过;node scripts/qa/audit-docs.mjs --check-registry errors=0,保留既有 22 个未注册文件 warning。

接续阶段:选票创建与批量设类服务端

  • 明确 createItem: true 的新绑定意图,和 linkedItemId 互斥;门禁开/关均允许用户显式创建,旧绑定合同保留。
  • 新动作先锁单再锁票,权威金额/日期、可靠类型或 null、未知说明空字符串;新明细+占用+总额重算在事务内,不按相同金额或已有唯一明细匹配。
  • 同单重试经权限/状态/归属重验复用已占用明细,拒绝跨单占用/非参与人/缺金额日期,不重复写入。
  • 新增批量设类接口,只改选中 ID 的类型和更新人;异单/失效 ID、停用类型、非参与人或不可编辑状态整批拒绝。保留批准依据,不宣称既有 assets 路径存在硬阻断。
  • 空说明草稿允许编辑;预检/提交按明细集中阻断,编辑器明确传空字符串。当前 OCR 没有票面项目,不以销售方/文件名伪造支出用途。
  • 下一步先接 PC/移动“我的发票”面板:上传/OCR 完成只勾选,不自动添加;整单入口传 createItem,已有明细入口传 linkedItemId。接批量分类控件,再做真实后端并发与空单浏览器回归;本阶段不发布。

本轮验证:服务端新绑定/草稿分类/费用门禁/错误码镜像 4 文件 219/219 通过;扩展报销路由组前一轮为 12 文件 311/311(之后仅增加空说明预检/提交及相应测试,相关组已重跑)。测试均为合成 Fastify 注入+模拟 Prisma,不据此声称已验证真实锁/事务回滚。旧一组 SQL 隔离验证只覆盖结构迁移,不覆盖本轮 API 并发。未调用腾讯真实 OCR、未改生产、未提交推送;独立复核及完整前端验收待后续。

本轮最终扩展回归:cd server && npm test -- tests/unit/routes/invoicePool.*.test.js tests/unit/routes/reimbursements.*.test.js tests/unit/services/invoiceExpenseClassification.test.js tests/unit/scripts/error-reason-client.test.js 为 18 文件 349/349 通过。cd client && npm run typecheck、错误码 strict 合同、Trellis 治理检查、JS 语法与 git diff --check 均通过。本轮未重跑浏览器或生产构建,不继承上一阶段构建为新页面验收(新页面尚未接入)。

已批准字段调整:本地阶段

  • 用户明确批准仅费用类型可空的本地实施与隔离验证,生产迁移和部署仍未授权。
  • 新增可空字段迁移,API 在 draft/rejected 接受明确 null;PATCH 省略类型保留原值,明确 null 清空类型,不调用空 ID 查询。
  • 报销提交及该单预检在财务计算前拒绝缺失/停用分类并返回明细 ID;保留身份与状态检查。补齐前不启动预算占用或审批。
  • DTO 与 PC/移动编辑器保留待补值,可保存其他字段,不因打开编辑器默认选中第一类型;金额、日期约束未放宽。
  • 一次性 PostgreSQL 16、无网络、tmpfs 库执行历史 SQL 建立基线,再验证新迁移:旧分类不变、空分类持久化、外键及金额/日期非空约束保留;存在空值时逆迁移拒绝、合成记录明确设类后可恢复非空。测试退出删除专用容器,不连接共享开发库或生产;这是 SQL/约束测试,不是 Prisma migrate deploy 或完整业务 E2E。
  • 接续实现显式选票建明细事务、批量设类 API 与面板,完成真实后端/浏览器验证。当前仍不是可发布的完整发票流程;暂不叠加退回删除和付款改动。
  • 只读独立复核待完成:当前 inline 模式仅由主会话检查,没有虚报独立 reviewer。

本阶段最终验证:

  • cd server && npm test -- tests/unit/routes/reimbursements.*.test.js tests/unit/services/invoiceExpenseClassification.test.js tests/unit/scripts/error-reason-client.test.js:12 文件,295/295 通过;详情测试初次漏传合成认证头产生 401,补齐测试头后单文件 37/37 及全组通过,不曾放宽产品认证。
  • prisma:validate、独立依赖目录中的 prisma:generate 通过。生成 schema 与 TypeScript unchecked-create 输入确认类型可空、金额/日期必填。最初使用 DMMF 的旧 isRequired 元数据断言不适用于当前客户端,未将该失败归为产品缺陷,改为核验实际生成产物后通过。
  • cd client && npm run typecheck && npm run build 通过;Browserslist 数据老旧提示保留,不混入依赖升级。
  • RUN_ISOLATED_DB_TEST=1 bash scripts/qa/test-reimbursement-draft-migration.sh 通过;仅使用现有本地 Postgres 镜像与合成记录,无网络专用库退出已清理。
  • node scripts/qa/check-error-reason-contract.mjs --strict、node scripts/qa/check-trellis-governance.mjs、git diff --check、改动 JS 的 node --check 与新脚本的 bash -n 通过。
  • 路由测试使用 Fastify 注入与模拟 Prisma;SQL 测试使用真实隔离 PostgreSQL。二者互补,但不等于真实 Prisma+HTTP+浏览器端到端验收。实际选票/OCR/批量分类仍未接通,独立复核及桌面/窄屏浏览器验收待后续阶段,不能据本阶段发布。

2026-09-09 首批代码验证

  • 已新增纯分类服务和 10 条合成单元测试,尚未接入 API/页面。仅认可持久化铁路电子客票与启用火车票类型,覆盖未知 VAT、销售方/文件名误导、类型停用/缺失、历史 provider 默认值、客户端字段冒充及输入不变性。

  • 测试先运行得到预期模块缺失失败;实现后 cd server && npm test -- tests/unit/services/invoiceExpenseClassification.test.js tests/unit/services/invoiceDocumentMeta.test.js tests/unit/services/ocr.tencent.test.js 为 3 文件、22/22 通过。node --check src/services/invoiceExpenseClassification.js 与 git diff --check 通过。

  • OCR 测试为合成单元测试,不是腾讯真实调用证据;未测完整浏览器流程、真实数据库并发或生产 OCR。未独立复核、未提交、未部署。

  • 当前需确认具体持久化范围:只放宽明细费用类型列以保存待补草稿,提交前强制补齐;已将跨层兼容及旧镜像回滚风险补入 design。分类逻辑不要求用户每条手填,也不把本次开始实施视为生产迁移授权。

  • 用户采纳“一事一单、自动分类为主、同类批量设置、只处理例外”,同步本 task PRD/design,不新建任务。

  • 核定自动类型的可靠输入与批量更新接口,审计草稿保存、刷新及提交约束;不再以逐条手选分类作为数据库非空字段的默认解决方案。

  • 在空单流程回归中加入混合类型、同类批量设类、未知项集中处理、用户编辑保留与停用类型拒绝。

  • 代码、发布、业务验收:本轮仅更新需求和验收合同,未实现分类功能、未部署、未宣称业务验证通过。

  • 下一步由执行端完成最小技术方案和针对性回归,不等待员工代测;如确需迁移,应给出具体差异和兼容风险,不能复用早先概括性迁移建议。

2026-09-09 后续阶段清单(已进入实施前核查)

后续实施授权:用户明确“自动填充的明细不需要用户二次确认,但允许再次编辑”,并批准按上述方案执行。该取向已补入 PRD,可继续同范围的最小技术设计、代码实施与本地验证,不因补录合同重复索取授权;产品代码尚未实施。迁移或范围/风险发生实质变化仍先报告,提交、推送、部署、生产配置与真实票据发送按既有单独授权边界执行。

  • 按用户确认更新 PRD/design:选票一次触发 OCR 辅助生成明细及自动关联;上一版手工关联默认方案撤回,不新建 task。
  • 从已发布 b87cc55 建立独立执行副本 artifacts/isolated-worktrees/20260909-invoice-first,通过官方 task start/current 对齐本 task;仅承接本 task 三份规划文件。
  • 核查上传/OCR/绑定/费用类型和 schema,记录不可空类型与 OCR 缺票面项目的实际约束;旧自动匹配不能作为新入口实现。
  • 按已采纳分类交互补齐最小接口事务设计与稳定类型判定规则,再审计具体持久化影响;不擅自新增迁移或改成强制先填分类。
  • 核实真实 OCR 启用及测试样票/调用权限的可用条件;仅回传脱敏状态,不读回或暴露密钥,不自动修改生产配置。
  • 建立空单、已有明细、多选和异常重试的失败用例;实现用户明确选票触发的自动建明细,不调用旧静默金额匹配作为替代。
  • 实施双入口面板与后端单票原子事务/重试幂等,完成与删除阶段的释放协议检查;不伪造必填字段、不批量修真实单据。
  • 完成真实后端角色 E2E、腾讯 OCR 专项、浏览器桌面/窄屏及反例,保留每层验证与缺口。
  • 只读独立复核与规则文档同步;按明确授权提交、推送、部署和专用线上合成检查,旧发布授权不复用。

下文为历史 B1/B2 已完成阶段,不把其勾选继承为本次自动建明细功能已实现或已验证。

1. 规划门禁

  • 固定 B1+B2 同包开发、分级启用;B2 默认关闭。
  • 选择 assets “事前批准依据”字段方案,不在本轮关联 PurchaseRequest。
  • 用户批准以 2,000 元/条明细作为办公用品/办公电子设备选类提示阈值,并明确不等同于会计固定资产标准。
  • 财务负责人为会计科目映射回填责任角色;应在 B1 生产启用前提供,目标日期 2026-09-05。届时仍无正式科目表则保持 null,不得由开发猜测。
  • PRD、design、implement 经用户审查并明确批准实施。

2. Schema 与主数据

  • 新增 ExpenseType.requires_invoice 和 ReimbursementItem.asset_prior_approval_basis migration,验证既有数据默认值。
  • 逐条实现改名、停用和新建类型;同步 seed、API DTO、管理页和历史兼容测试。
  • 为费用类型补员工可读 description;将 2,000 元/条明细阈值和多件同类分开记载要求写入办公用品/办公电子设备提示。

3. B1 表单与服务端合同

  • PC/移动端展示材料要求和 assets 事前批准依据字段。
  • 服务端按权威费用类型校验 assets 字段,覆盖新增、编辑、类型切换后的清理和提交复核。
  • 基于服务端权威费用类型与金额生成 2,000 元办公用品非阻断提示,PC 员工端、移动端和财务审核视图保持一致。
  • 两项补助保持无票但需要说明/算式的现有业务合同;flag 开关两种状态均覆盖纯补助无票基线。

4. B2 双闸和关联

  • 新增 env config、feature key、DB 初始记录和诊断/审计测试,默认 hard-off/force-off。
  • flag 开启时要求发票显式选择明细并关闭自动新建;关闭时保持兼容路径。
  • 提交事务按明细聚合有效票面金额,仅对 requires_invoice=true 执行缺票/超额门禁。
  • 错误返回可定位到明细并使用员工可理解文案;财务保留差异和风险核验信息。

5. 验证与上线

  • 执行 PRD Test Plan 和 Trellis quality gate,做权限/状态/历史草稿/flag 回退专项复核。
  • B1 部署与真实单据处置顺序已调整:先部署 B1、隔离验证并启用 B2,再由真实财务统一退回 BX-20260828-0014/0016/0017/0018,避免员工在无系统指引时重复返工。
  • 已只读核对生产存量:除审批中的 4 张单据外,没有 draft/rejected assets 明细会被 B1 新校验拦住;不得批量伪造事前批准依据。
  • 首次部署只启用 B1;B2 保持关闭,完成 owner 基线和无票补助验证。
  • B2 开启必须单独获得生产配置批准;先专用账号/部门灰度,再按证据决定全局。
  • 关闭 B2 后复核旧提交路径恢复且关联数据未被删除或解绑。

6. 交接 B3

  • 汇总真实关联、差额和错误分布的脱敏结论,作为 B3 一页式信息结构输入。
  • 明确交接财务行内费用标准提示、票据—明细对应关系和差额解释,不在 B1 中提前重做审批页面。

7. 本地实施与验证记录(2026-08-29)

  • 业务实现提交:6e999d5。
  • Schema、B1、B2 代码均已实现;B2 env hard gate 和 DB 初值仍为关闭,未执行任何生产配置写入。
  • prisma validate、prisma generate、generated client 检查通过;只读 prisma migrate status 正确识别待应用 migration 20260829120000_reimbursement_expense_policy_invoice_guard。本地回归库未获 migration 写入批准,因此未执行 migrate deploy;existing-data/default/backfill 由静态 migration 合同测试覆盖。
  • 后端聚焦回归 59/59 通过;使用固定 BIFF8 Python 依赖的全量 Vitest 为 183/183 files、1329/1329 tests 通过。系统 Python 直接运行会产生既有的 3 个付款指令环境型失败,不属于本次 diff。
  • 前端 typecheck、production build 通过;r102 普通报销回归 6/6,r111 B1/B2/PC/移动/企业微信回归 3/3 通过。
  • Trellis task validate、governance、process contract、docs registry audit 和 git diff --check 通过;docs audit 保留既有的 22 个未注册文件 warning,exit 0。
  • 权限、migration、feature flag、历史兼容和 seed 科目保护已完成三轮只读专项复核,最终 PASS。复核发现并推动修复:pending OCR 关联丢失、flag-off 纯无票补助服务端门禁、PC 无票补助主操作投影。
  • 未部署 B1、未应用生产 migration、未开启 B2、未做真实数据写入;本 task 保持 in_progress,上线与 B3 交接项不得提前勾选。
  • 生产只读审计把业务处置范围扩大为 4 张审批中单据:0014 为 5 个发票日期压成 1 条明细且 B2 无法自动拦截;0016/0017/0018 存在未关联票据,0017 另有票面差额。最终顺序改为先部署 B1、隔离验证并全局启用 B2,再由真实财务统一退回,日期粒度继续依赖明确退回说明与人工复核。部署与灰度合同见子任务 08-30-reimbursement-expense-policy-invoice-guard-deploy。