全部 AC 完成,status 已是 review。用户确认口径:本任务范围是"产出可执行 的准备包"(0014 观察清单草稿、三张 rejected 单据收口规则草稿、三项只读 检查结果、G1-G10 备份可用性预检),清单里的实际运营动作(真人交接、 财务收口)是后续独立工作,不卡本任务完成判定。 Co-Authored-By: Claude Sonnet 5 <[email protected]>
22 KiB
执行与只读证据
已批准范围和基线
- 基线 HEAD:66f25b1;origin/main:492920c;两笔 Trellis 本地提交不在本轮推送范围。
- 用户批准第一批准备产物;当前 task 与上一 Trellis 升级 task 分开,不归档或改写上一任务。
- 产品代码、生产数据和全局配置只读;仅本 task 文档及忽略的隔离证据写入。允许在临时目录使用合成 fixture,不执行真实写接口。
执行状态
- 核实 rejected 现有动作与历史取舍,完成观察清单/收口草稿。
- 先确认 G1–G10 改前备份、批准方案、当前内容入口可用。
- 主体存量只读汇总盘点。
- 合同只读超时具体用例、失败步骤与环境归因。
- IM 注册表/环境配置/运行时有效值核对。
- 汇总待人工决定项,完成治理/敏感检查,准备交用户审阅;独立复核尚待用户进行。
证据口径
每项分别记录源码事实、环境实测、结论和未验证范围;不将读取配置等同于通知送达,不把 403 原因诊断等同于财务业务验收,不把普通本地测试绿等同于历史超时已归因。
1. 业务草稿与 rejected 动作事实
可交给观察人填写的清单和三单 A/B 草稿在 design.md。先确认观察分工与清单,再另行授权付款;现场结果只按实际发生填写,补资料前先记录实际可见的缺项提示。记录角色、入口、获知方式和是否人工催促,不补造时间、不为了取样制造退回。
源码核对:
server/src/routes/reimbursements.js:648的参与人判定仅为 owner/submitter;10 个维护调用点经过该判定,admin/finance 角色本身没有豁免。server/src/routes/reimbursements.js:2816删除仅限 draft;:4386撤回仅限 reviewing,撤回回到 draft,不是作废。- 报销 cancelled 枚举不提供可执行的作废入口;不能把工作流任务的取消误认成报销本体作废。
- 已接受的取舍见
.trellis/tasks/08-28-reimbursement-nonparticipant-readonly-projection/design.md:73;本轮不是重开该权限缺陷。
业务出口仍由负责人选择:A 本人在既有权限内限期修改重提;B 新运营动作另立 task,明确原因、权限、状态和审计后审批。A 到期未履行不会自动关闭,必须作 B 的需求决定或如实记录未解决,不能写“财务代处理”。
本次汇总显示 3 张 rejected 归 1 个 owner,其登录为 active、任职非 inactive。未读单号、人员标识或正文,不将汇总强行匹配到旧三单;更不将 active 等同于可联系或能执行。业务仍需逐单确认。
2. 生产只读通道与身份核验
正式汇总快照:2026-09-06 12:51:02(Asia/Shanghai),运行中的应用 build 为 f223bfb72fb2。
- 按现有部署 spec 的身份消歧合同,选择唯一符合非 root、冻结部署目录及环境文件数字 UID 归属、Docker 访问条件的既有 SSH 身份;不新增通道、不修改 SSH 配置。
- 对照冻结备份 unit 的 Compose project 定位运行中 server/db。容器内解析应用连接目标,仅返回布尔值及数据库名摘要:应用 DB host 解析到所选 db 容器;端口与 schema 符合检查前提;应用数据库名与查询数据库名摘要一致。没有把默认 POSTGRES_DB 未经核验当作应用生产库。
- 随后在同一只读探针内重新核验身份并重做汇总。连接设置
default_transaction_read_only=on,事务为REPEATABLE READ READ ONLY,实际确认transaction_read_only=on;statement timeout 10 秒、lock timeout 1 秒,最后 ROLLBACK。 - SQL 只取计数、状态分组和通知 routing 指定字段,不取员工标识、卡号、开户行、合同正文、通知内容或密钥。账号状态只读,不登录生产业务身份或修改账号。
脱敏复核入口(均在忽略的证据目录内):
artifacts/qa-flow/payment-observation-readonly-preparation/readonly-inventory.sqlartifacts/qa-flow/payment-observation-readonly-preparation/production-readonly-results.jsonartifacts/qa-flow/payment-observation-readonly-preparation/application-database-identity.json
最初的默认库汇总未立即发布结论;正式结果以上述应用身份匹配后的重查为准。读取配置及 SELECT 不等于任何写入授权。
3. 唯一付款主体盘点
| 口径 | 本次只读结果 |
|---|---|
| 主体总数/启用且未归档 | 1/1 |
| 员工记录总数 | 8 |
| 未归档且登录 active 的员工记录 | 7 |
| 上述记录缺 legal_entity_id | 7/7 |
| 上述记录绑定失效主体 | 0;不能把未绑定算成绑定有效 |
| 在职生命周期记录(probation/active/offboarding,未归档) | 7,全部缺主体 |
| 已归档员工记录 | 1,也缺主体,不与在用范围混算 |
| approved 报销/owner 数 | 1/1;该 owner 缺主体,登录未停用 |
判断:不是只针对单个员工的防御性补丁。 当前全部 7 个未归档 active 员工记录都存在主体缺项,主体生命周期 PRD 可立即使用本盘点作为输入,不必等 0014 完成。
口径限制:这里是员工记录数,未按真人与 smoke 身份拆分;不是“7 位真人都已触发付款失败”。approved 汇总未排除组批占用,也未执行完整 readiness,因此不冒充付款候选总数;没有读取单号,不能直接把该唯一 approved 记录认定为 0014。未检查真实银行资料、付款账户可用性或指定应该绑定哪个主体。
三段合同不变:新增时按经批准的唯一主体规则显式落库;存量先盘点、再获单独回填批准;付款阶段仍严格报错,不静默推导。当前 1 个启用主体只是快照,不授权立即回填,也不替代未来写入时重新检查唯一性。
4. 合同只读超时归因
精确复现
- 环境:本地 Vite、单 worker、无重试、API mock 与合成角色/合同;不访问生产合同。
- 用例:
client/tests/e2e/payment-reviewer-permissions.spec.ts:380,合同只读人员看不到新增编辑续约和归档动作。 - 实际结果:1 failed,180000ms test timeout。没有调整超时或断言,没有重试至绿。
执行命令(工作目录 client;本轮启动的 Vite 已停止):
E2E_MODE=local E2E_BASE_URL=http://127.0.0.1:5189 CI=1 \
node node_modules/@playwright/test/cli.js test \
tests/e2e/payment-reviewer-permissions.spec.ts \
--grep '合同只读人员看不到新增编辑续约和归档动作' \
--workers=1 --retries=0 --reporter=line --trace=on \
--output=../artifacts/qa-flow/payment-observation-readonly-preparation/contract-baseline
因果链与分类
- 合成身份登录、进入合同页、合同行可见,以及“新增合同/编辑不可见”前置断言已经过。
payment-reviewer-permissions.spec.ts:397等待该行名为“更多”的 button,直至超时;DOM/trace 中该行只有“查看详情”链接,没有“更多”按钮。- 当前
client/src/pages/ContractsRegistryPage.tsx:313的行操作已是详情入口。测试在5d0a5f9(2026-07-18)引入,列表在2185177(2026-08-08)改为详情工作区;对应设计见.trellis/tasks/archive/2026-08/08-05-contract-detail-workspace/design.md:80。
分类为测试契约漂移:用例仍操作已移除的列表菜单,不是靠增大等待时长可解决的慢启动。这解释了本次对既有超时用例的复现,不把未保存的历史运行环境也声称逐次复现。
后续应按真实详情入口重建只读权限回归,继续具体断言新增、编辑、续约和归档不可用;不能只删 click 或只断言详情可见。本轮仅归因,测试仍失败、修复未实施,不能计入通过结果;超时后的续约/归档断言尚未执行。
证据在 artifacts/qa-flow/payment-observation-readonly-preparation/contract-baseline/:失败状态、DOM 摘要、截图、视频和 trace,均为合成数据。没有真实合同截图或正文。
5. IM routing 三层核对
代码层、环境层与有效路由层
| 层级 | 实测/源码证据 | 不代表什么 |
|---|---|---|
| 代码注册 | 生产的 NOTIFICATION_TYPES 与 IM_SUPPORTED_NOTIFICATION_TYPES 均包含三种 returned;注册表及 imRouting 源码摘要与本地 HEAD 一致 | 不代表配置放行或已送达 |
| 环境配置 | 冻结环境文件 IM_NOTIFIER_ENABLED=true;未用示例文件冒充现状 | 不单独证明运行中容器值 |
| 运行中配置 | server 中实际 getConfig().imNotifierEnabled=true | 不代表某个收件人有有效企微绑定 |
| 应用 DB routing | 1 条启用 wecom;allowedTypes 为 7 项,domainScopes 仅 workflow/reimbursement,roleCodes 为空 | 没有读取 channel 密钥/模板或发送内容 |
| 有效路由计算 | 将只读提取的 routing 交给生产容器现有纯函数 matchesNotificationChannel,使用注册表解析的实际域 | 这是当前配置匹配结果,不是通知入队或送达验收 |
本次 allowedTypes:workflow.task_assigned、reimbursement.approved、reimbursement.rejected、reimbursement.returned、reimbursement.paid、payment_batch.review_requested、payment_batch.execution_requested。
| 类型 | 代码注册/IM 支持 | allowedTypes | domainScopes | 当前匹配启用 channel 数 |
|---|---|---|---|---|
| reimbursement.returned | 是/是 | 包含 | reimbursement 包含 | 1 |
| purchase_request.returned | 是/是 | 不包含 | procurement 不包含 | 0 |
| travel_request.returned | 是/是 | 不包含 | travel 不包含 | 0 |
| reimbursement.rejected(区分旧三单状态所需) | 是/是 | 包含 | reimbursement 包含 | 1 |
旧“三种 returned 都未放行”的前提已被现状推翻。 当前需讨论的路由缺口是采购、出差两类,而且要同时处理类型和业务域,不能只往 allowedTypes 加名称。若后续批准变更,应保留其余既有路由,并用双层差异交复核;本轮未生成或应用生产写入命令。
空 roleCodes 表示不额外按角色过滤,不是全部拒绝。imEnqueue 每次从应用 DB 查询启用渠道后调用匹配函数,不把环境文件中的总开关当作 allowlist。
Worker 与送达边界
当前没有独立 running worker 容器,但 server 的 jobsEnabled=true、jobDriver=pgboss。server/src/app.js:407 调 startJobs,server/src/jobs/jobSystem.js:14 支持内嵌 pg-boss,server/src/jobs/pgBossJobs.js:310 与 :400 注册和调度 integration outbox。因此“没有独立 worker 容器”不能单独判为通知服务缺失。本轮只核对配置和启动代码,不声称队列健康或每次任务已执行。
没有读取真实通知 payload、收件人企微信息、历史 outbox 送达记录,也没有发送探针消息。报销 returned/rejected 当前匹配不能证明旧三单当时曾入队或送达;采购/出差当前不匹配也不能解释三张报销未回复。历史配置、事件是否产生、送达和本人是否看到,均保持未验证。
6. G1–G10 可用性预检
可开展后续完整只读复核,不因备份缺失阻塞;本轮没有替代该复核。
| 私有备份 ID | 归档 | 可读性与条目数 |
|---|---|---|
| hyb-oa-rules-stage2-FcbImknm | global-before.tar.gz | 可读,118 条;14 个 SKILL.md 和配置入口 |
| 同上 | project-before.tar.gz | 可读,92 条 |
| 同上 | duplicate-policy-before.tar.gz | 可读,2 条 |
| hyb-oa-global-five-VmWeZjgM | skills-before.tar.gz | 可读,29 条;5 个 SKILL.md |
- 批准方案
.trellis/tasks/09-05-trellis-upgrade-rule-cleanup/design.md的 G1–G10 条目全部存在,各项改前文件与当前可读入口均能对应。 - 10 个全局 Trellis 后备入口存在;find-skills 的两个当前入口仍解析到同一目标;G6–G10 五个当前技能文件可读。
- 原 discovery/validation JSON 可解析;本轮计算归档摘要并记录可用性,不将旧日志当作独立内容复核通过。
- 未展开或输出用户级配置秘密,未解包覆盖任何当前文件,未修改技能、配置、hooks 或其发现策略;没有验证真实客户端加载状态。
脱敏回执:artifacts/qa-flow/payment-observation-readonly-preparation/global-skills-availability.json。备份位置保留在既有私有状态目录,正文只记备份 ID,不复制完整路径或配置内容。
7. 下一步人工关口与未启动项
- 用户逐项审阅本准备包;业务指定 0014 观察人与真实财务身份,确认清单后再另行授权补资料/付款。非建行停止,不伪填。
- 业务逐单定位旧三单、确认参与人能否操作、决定 A/B 和明确期限,通知到人;没有“财务代处理”的伪出口。
- 合同测试修复、唯一主体 PRD、IM 两类差异方案及 G1–G10 全量复核均已有前置输入,不必等付款;本轮不自动开下一 task 或进入实施。
- 执行端收到脱敏现场记录后才能整理真实交接与自然样本;仅统一待办的具体设计等待这些证据。未发生的样本不算覆盖,管理员代验不算财务验收。
- 所有 push、归档、生产配置、账号启停、回填及付款继续分别等待明确授权。当前没有后台自动推进中的工作。
8. 本轮检查与交付状态
- 合同精确用例:失败已归因,保持 1 failed,不计入 PASS。
python3 .trellis/scripts/task.py validate .trellis/tasks/09-06-payment-observation-readonly-preparation:PASS;inline 下两个 manifest 各 0 条,未为未来可能的 sub-agent 模式伪造上下文。node scripts/qa/check-trellis-governance.mjs:PASS,0 errors/0 warnings,1270 个 Trellis 文件纳入敏感检查。node scripts/qa/check-sensitive-content.mjs:PASS,0 findings;git diff --check:PASS。新增 task 文件另做 no-index 空白检查,避免仅检查 tracked diff 漏掉新文件。- 本 task 仅准备证据与草稿,没有修改产品运行时代码、spec、权限、状态机、部署工具、hooks 或全局技能。没有修复测试、触碰真实银行资料、提交、push、归档或部署。
- 旧 Trellis task 的提交追溯及全局技能完整审查未被本轮标成完成。本 task 由 start 记录的 branch=base_branch=main 保持原状,不借只读准备擅自创建分支或归档。
- 原准备包已获用户独立复核通过,可交业务;追加的门禁只读追溯等待增量复核,状态保持 review。不表示业务操作、测试修复或后续 PRD 已获批准;HEAD 保持 66f25b1,origin/main 本地跟踪引用保持 492920c。
9. 用户复核后的门禁完整性只读追溯(2026-09-06)
修正定性与调查边界
用户指出该超时位于 acceptanceSpecs,并会令本地候选门禁失败。原报告只定位到测试契约漂移,漏掉了它的门禁影响,应由执行端纠正:这是候选验收与发布阻断未关联的完整性问题,不再按普通测试债排队。 本轮先查调用链和历史留痕,没有修改测试、放宽断言、跑完整候选门禁或访问生产。
机器调用链已经证实
scripts/qa/route-manifest.json:23登记旧合同只读用例所在 spec;scripts/qa/run-local-candidate.sh:84将全部 acceptanceSpecs 与 witness 合并,full/focused 均不按本次产品改动缩减。- runner 检查 Playwright 与结果验证器的退出状态;失败时 exit 1,只有 full 的成功分支能声明 LOCAL_CANDIDATE_PASS。没有发现该 runner 吞掉失败退出码的证据。
.trellis/spec/hyb-oa/deployment-trialrun.md:800定义本地候选合格的范围;.trellis/spec/hyb-oa/frontend-react-vite-design.md:2686与:2708要求受管验收实际执行且零 failed/skipped。声明某个局部测试通过不能替代它。- 实际发布链为
production-identity-bootstrap.mjs → deploy-existing.sh → deploy.sh。deploy-existing.sh:70检查来源,随后检查生产目标、迁移等前置;这条链没有调用本地候选 runner,也没有消费绑定当前候选提交的本地验收回执。 deploy/trialrun/check-source-provenance.mjs:48检查工作树、提交、分支与上游发布状态;source provenance 通过并不包含“浏览器候选验收通过”。scripts/qa/run-release-gate.sh同样不调用本地候选 runner。其 full-local 间接执行另一套npm run test:e2e:full(全部 Playwright),不能与 run-local-candidate 的 full 混为一谈;static 模式也不是浏览器验收。
因此必须区分:规约中的受管候选验收失败,会阻断本地候选 PASS;但现有发布入口不以该结果为必要输入,所以不能自动阻断部署。 这不是数据库或页面自己恢复了,也不是清洁工作树证明所有门禁都过了。
历史证据:不只读旧结论,核对工具调用与返回
检查范围为本机可索引的 18 份本项目历史会话,核对 2026-08-08 至 2026-09-05 的工具事件;时间表使用 Asia/Shanghai。区分实际执行、读源码、写文档和会话引用,不把源码中打印出来的 PASS 字符串当作执行回执。
| 时间/来源 | 已证实事实 | 证明上限 |
|---|---|---|
| 2026-07-18、2026-08-01 的既有 task 记录 | 分别记录 full 44/44、focused 50/50 通过 | 早于合同列表迁移;本轮未重跑,不可复用成后续候选的通过证据 |
| 2026-08-08 01:21 工具调用 | 批量本地检查中实际请求了 run-local-candidate.sh list | 只解析;本轮未取得该调用完整结束回执,不将它记成 full/focused 通过 |
| 2026-09-04 23:03 工具返回 | 旧合同只读用例等待“更多”超时,明确 180000ms;该次返回 1 failed、4 passed | 确认旧用例在 09-05 前已经报红;不是 full runner 的返回 |
| 2026-09-05 13:14–13:19 工具调用/返回 | 未加 grep 的 payment-reviewer-permissions spec 被执行,返回中再次出现该合同用例红行 | 已见失败进度;本轮没有将该进程完整最终退出码标成已验证 |
| 2026-09-05 13:31–13:42 工具调用/返回 | 后续直接 Playwright 使用明确的 --grep 选择付款/B2/采购等用例,返回 17 passed、exit 0;筛选不包含合同只读用例 | 这是定向回归,不是 run-local-candidate.sh focused,也不是合同已修复 |
| 2026-09-05 19:01–19:04、21:07–21:09 工具调用/返回 | 两次非只读 bootstrap 发布进程均返回 exit 0;对应 task 分别记录 a837912、f223bfb 的生产发布 | 证明发布入口在上述已知失败存在后仍可完成;不等于它曾接收并忽略一个 full runner 的 exit 1 |
直接回执定位保存在 artifacts/qa-flow/payment-observation-readonly-preparation/candidate-gate-history-audit.json,仅含会话标识、调用标识、时间、测试计数和已验证信号,不附会话原文、完整账号、生产连接信息或原始业务响应。
原任务笔记与工具留痕相符:.trellis/tasks/09-05-error-explainability-payment-readiness/implement.md:112 已写扩大测试出现合同超时、随后改为定向集合;第 10 节明确失败未归因、不计通过,然后记录用户允许发布。因此“跑了失败却完全没留记录”至少不适用于这一次;留下了失败记录,但没有识别并报告它令受管候选验收不合格的后果。
回答“为什么没有阻断”与仍不能断言的事
能够证实的链条是:
- 新合同详情已实现并做局部回归,但旧受管合同验收仍指向已移除的菜单。
- 最近两轮真实工具留痕表明旧用例确实报红;09-05 的后续定向集合排除了它,并将未归因失败留作合同域后续事项。
- 发布使用来源/生产前置检查和获批的独立部署入口,不要求提供该候选的完整本地验收结果;因此“定向绿+来源前置通过+发布获批”仍能走到部署完成。
这是执行选择与机器依赖两层断开:执行端把受管验收失败当作任务外事项,而部署链没有把完整候选结果设为前置。普通发布批准与已披露的一条测试失败,不能被倒写为用户明确豁免完整候选门禁;本轮没有找到该种专门豁免记录。
本机上述范围内未找到漂移后 full/focused 候选执行的可核回执。但这不证明整个月从未运行:不覆盖其他 worktree、未索引客户端、外部 CI/机器和已清理记录。不能以未找到记录代替每次部署的完整调查,更不能把最近两次的处理方式套用到全部历史候选。
另有留痕缺口:run-local-candidate.sh 使用临时 JSON 与 stderr 文件,cleanup 在成功或失败后均删除它们;脚本本身没有持久化带候选提交、模式和结果的验收回执。它解释了为何仅扫描 artifacts 不足以证明“从未跑过”,但不能证明某条丢失的回执曾经通过。
优先级与交接
- 先按门禁完整性问题另行确定修复范围:实际部署入口如何关联当前候选的必需验收、如何留存结果、合法例外如何明确批准并留痕;不能只把单条测试改绿就宣布完整性收口。本轮不立新开发 task、不改治理或发布脚本。
- 合同用例后续仍须按详情入口具体验证只读身份的新增、编辑、续约、归档边界;当前旧用例超时后的断言保持未验证。先查清完整性再批准修复,不先删 click、增加 timeout 或缩减 acceptanceSpecs。
- 原业务准备包已经用户复核通过,不因本追溯重新挂起;业务优先匹配 3 张 rejected 是否就是旧三单。观察清单补充付款提醒/本人自行确认到账的可选尾端记录,严格区分通知、登记付款与银行入账。
- 本节为主会话新增只读证据,尚待用户增量复核;没有把原准备包的 PASS 延伸为本节独立复核通过。