文件

110 行
14 KiB
Markdown
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# 财务后台定位与主导航减法:执行计划
## 0. 规划与启动门禁
- [x] 用户批准创建 task 并进入规划。
- [x] 核验最初四个候选导航项、flag key、batch transaction、后端副作用和生产普查证据。
- [x] 明确本轮只有“定位固化 + 三个固定 flag 关闭”;原条件目标 `project_domain_v1` 已因本地准入失败排除,二级入口不实施。
- [x] 用户只读复核 `prd.md`、`design.md`、`implement.md` 并明确批准启动;已运行 `task.py start`。
## 1. 本地定位固化(启动批准后)
- [x] 更新 `docs/产品口径.md`:将 A/B 未决门替换为“范围收缩为财务/台账后台,核心 OA 主办流程要求真实使用”。
- [x] 对齐 P0-1/P0-3/P0-10 中 OA 主办域,明确项目主域当前冻结候选,合同/采购/资产仍为低频台账。
- [x] 更新 `project-governance.md` 的 Adoption Mode decision,禁止按失效 R63–R66 排期继续扩建。
- [x] 只纠正当前活跃任务中会把执行带回 R63–R66 的前向说明;未批量改归档历史。
- [x] `git diff --name-only` 断言没有 `client/src`、`server/src`、`scripts`、migration、依赖或部署配置;不新增执行脚本。
## 2. 本地验证与生产审批停点
- [x] 启动本地服务并复用两张本地草稿;一张保持未关联,另一张仅在验证窗口内临时关联现有合成项目;记录 flag on 基线。
- [x] 本地临时 `force_off project_domain_v1`,用真实浏览器验证页面、控件、错误提示、已关联项目名称/编号和 `/api/projects` 网络响应。
- [x] 恢复本地 flag 完整 snapshot 并复核;准入失败,生产 target 固定为 3,`project_domain_v1` 不进入本轮生产写入。
- [x] `python3 ./.trellis/scripts/task.py validate 08-11-internal-oa-finance-backoffice-positioning-and-navigation-reduction`
- [x] `node scripts/qa/check-trellis-governance.mjs`
- [x] `node scripts/qa/check-process-contract.mjs --baseline --json`
- [x] `node scripts/qa/audit-docs.mjs --check-registry`(退出 0;保留基线 warning:22 个未登记 docs 文件,本轮未新增 docs 文件)
- [x] 敏感扫描与 `git diff --check` PASS。
- [x] 形成生产批准包:PRE、三个固定 target、完整副作用、一次 batch、APPLY_UNCERTAIN、可选一次回滚;已在用户批准前停止。
### 2.1 本地真实浏览器证据(2026-08-11)
- 环境:本地 PostgreSQL、真实 Fastify/Vite、仓库已安装的 Playwright Chromium;无 `page.route`、无组件 mock、无产品代码或测试代码改动。Playwright CLI 因系统 Chrome 通道缺失且 CLI Firefox 构建不支持当前环境而未能启动,故只在浏览器驱动层回退到仓库 Playwright,验证对象和网络路径不变。
- 合成样本:本地原有 1 个项目;从原有草稿中取 2 张,一张保持 `project_id=null`,另一张临时关联该项目。窗口前后全库已关联报销数均为 0。
- 开启基线:两张详情的 `/api/projects` 均为 200;未关联控件有“不关联项目 + 现有项目”两项,已关联控件选中现有项目,项目名称和编号显示一致。
- 关闭观察:两张详情的 `/api/projects` 均为 409;页面无红色可见错误,但未关联控件仍启用且只剩“不关联项目”,没有“项目能力已关闭/不可用”的解释;已关联详情的事实卡仍显示原项目名称和编号,控件却丢失该项目选项并回落到“不关联项目”。这会把“关联事实仍存在”呈现成“当前未关联”,不满足明确且不误导的准入合同。
- 请求边界:关闭阶段自动出现 2 次 `POST /api/reimbursements/:id/precheck`,代码和现场均确认它是只读预检;其他业务 mutation 为 0。
- 恢复:flag 的 override/rollout/departments/note/version/创建与更新时间/更新人 snapshot 精确一致;已关联报销数恢复为 0;专用本地浏览器 session 已删除。本地第一次恢复尝试曾把 JSON `[]` 通过 raw 参数写成 `{}`,门禁立即识别并恢复为原数组;最终有效 witness 的恢复路径只改 `override_mode`,完整 snapshot PASS。
- 判定:`project_domain_v1` **不通过**本地准入,不得进入生产 batch。最终生产目标恰好为 `announcements_v1`、`todo_board_v1`、`weekly_report_v1`。
## 3. 生产 PRE(需单独批准)
- [x] 只读确认 trialrun identity、运行 SHA、migration 和当前产品差异;不因本地文档提交领先误报运行漂移。
- [x] 复用既有只读普查能力严格一次确认三个最终 target 模块仍为零数据/零真人活动;任一非零即 PAUSED。
- [x] 管理员登录一次,只读保存三个最终 target 的 override/rollout/departments/note/version/effective snapshot 和审计基线;不输出账号或凭据。
- [x] GET 三项 impact,确认副作用与批准合同一致。
- [x] 断言 `project_linking_v1` 与合同、采购、资产相关配置不在目标集合。
### 3.1 生产 PRE 证据(2026-08-12)
- 身份与运行:唯一 trialrun 身份 PASS;既有部署聚合体检 `PASS=13 FAIL=0`;运行候选与 `origin/main` 已部署提交一致,当前本地领先的 8 个提交在 `client/src`、`server/src` 和部署 compose 上零差异。数据库三方身份 PASS,migration `pending=none`,回滚窗口 `OPEN / terminatedRows=0 / basis=terminated_status_count`。
- preflight 执行边界:第一次 detached 临时 worktree 在 provenance 阶段因无 upstream 被拒,尚未进入迁移查询;随后使用跟踪 `origin/main` 的 clean 临时分支完成唯一有效 preflight。临时 worktree/branch 已删除。
- 使用度:复用已审查的只读事务查询器,通过 stdin 送入运行容器且不落盘;公告、普通待办、周报 `totalRows=0`,30/90 天真人 actor/event 与真人审计 actor/event 均为 0,`queryStatus=PASS`,`effectiveQueryExecutions=1`、`zeroRetry=true`。第一次本地路径拦截发生在 stdin 重定向前,远端查询执行数为 0。
- 管理员只读 API:一次登录、零 retry;三项均为 `overrideMode=force_on`、`rolloutType=global`、`departmentIds=[]`、`version=47`、`envEnabled=true`、`effectiveEnabled=true`、`effectiveReason=flag_force_on_global`。三项现有 note 均非空且 SHA-256 相同;未保存或输出 note 原文。正常 logout 已用于撤销本次 session。
- 导航与影响:公告 `/announcements`、普通待办 `/todos`、周报 `/weekly-reports` 当前均存在。公告 impact 为公告 API 与 Portal;普通待办 impact 为 todos API、Portal、搜索;周报 impact 为 weekly-reports API、Portal、搜索。公告是“关闭”而非仅隐藏:flag off 后公告 API(含写接口)硬拒,临时发公告前必须先重新开启;Portal/搜索消费者按条件省略,不构成项目 flag 的跨域矛盾。
- 审计基线:全局 `FEATURE_FLAG_UPDATED total=1`;三项目标 changelog total 均为 0。源码确认 batch 事务对每个 target 各写 1 条 `FEATURE_FLAG_UPDATED`,没有独立 batch 审计;因此预期写后为全局 `auditDelta=3`、三项 changelog 各 `+1`、batch response `count=3`,不得按 4 条判断。
- 最终写入输入:目标恰好为 `announcements_v1@47`、`todo_board_v1@47`、`weekly_report_v1@47`;`project_domain_v1`、`project_linking_v1` 及其他 flag 不在集合。生产 batch 尚未批准、尚未执行。
## 4. 生产 APPLY(需单独批准)
- [x] 使用既有 batch API 选择恰好三个固定目标;写前重新确认三项均为 `force_on/global/version=47/effective=true`,审计与回滚窗口基线未漂移。
- [x] 一次前向 batch 将三项设为 `force_off`,response `count=3`;单项 PUT=0、workspace PATCH=0、其他业务 mutation=0。
- [x] 执行器随后因只监听页面网络、没有监听同一 browser context 的 `APIRequestContext`,把已成功的 batch 误计为 0;该本地 witness 缺陷触发了用户预授权的最多一次恢复 batch。恢复 response `count=3`,没有重试前向 batch。
- [x] 请求通道和 JSON 响应始终明确,没有进入 `APPLY_UNCERTAIN`;实际配置 mutation 精确为一次前向 batch + 一次预授权恢复 batch。
### 4.1 生产写入与恢复证据(2026-08-12)
- 写前现场锁:三个目标仍为 `force_on/global/departmentIds=[]/version=47/effective=true`;全局 `FEATURE_FLAG_UPDATED total=1`、各目标 changelog `0`,回滚窗口 `OPEN/0`。
- 前向写入:batch HTTP 成功且 `count=3`,三项实际进入 `force_off@48`。执行器的页面级 request listener 没有观察到 `context.request.fetch`,本地计数断言错误报 `forward_batch_count_0`;这不是服务器拒绝或通道不确定。
- 预授权恢复:失败分支只读确认三项确已统一进入 `force_off@48` 后,严格一次 batch 恢复 `force_on`,response `count=3`;终态三项均为 `force_on/global/departmentIds=[]/version=49/effective=true/reason=flag_force_on_global`。
- 独立交叉验证:全局 feature-flag 审计从 `1` 到 `7`,增量 `6`;三个目标 changelog 均从 `0` 到 `2`,各增量 `2`,精确对应一次前向和一次恢复。公告、普通待办、周报及项目导航均已恢复,Portal 只读为 200,回滚窗口仍为 `OPEN / terminatedRows=0 / basis=terminated_status_count`。
- 边界与清理:没有单项 PUT、workspace PATCH、其他 flag 或业务数据写入;没有第二次前向 batch。CLI wrapper 权限位、系统 Chrome 缺失、一次缺设备指纹的 400 和一次导航响应形状误判均发生在配置写入之外;最终有效只读核验正常 logout,浏览器/API context 和 token 均销毁。导航响应形状误判的只读核验在 logout 前退出,留下 1 个无人持有的服务端 session;本任务不追加未批准的认证状态写入,记录为自然过期残留。
- 结论:生产安全回到 PRE 功能状态,但“关闭三项并保持关闭”的业务目标未达成。原 `expectedVersion=47` 已消耗,任何后续前向写入必须以现场 `version=49` 重新规划并再次获得明确批准,不能沿用本次批准。
## 5. POST 与回滚判断
- [ ] 最终 target 均 `force_off/effective=false/reason=flag_force_off`,version 各 +1;本次因预授权恢复而未达到该终态。
- [ ] 单次前向落地的审计增量恰好为 3、各 target +1;本次最终审计包含前向与恢复,实际为全局 +6、各 target +2。
- [ ] 导航所有 section 不含公告、普通待办、周报入口;本次恢复后这些入口按 PRE 状态重新出现。
- [ ] 三个最终 target 直达 GET 返回可解释 disabled;本次在进入完整 disabled/Portal witness 前被执行器计数断言中止并恢复,未形成可接受的保持关闭 POST 证据。
- [x] 恢复终态下其他 flag、`project_linking_v1`、导航、Portal 和回滚窗口保持 PRE 可用状态;业务 mutation 为 0。
- [x] 只执行用户预授权的最多一次恢复 batch;恢复后停止,没有重试前向写入。
## 6. 收口
- [ ] 更新 task 证据,明确产品代码零改动、部署 0、最终 target=3、batch 次数、审计增量、项目本地准入失败和二级入口延后。
- [ ] 重跑 task/governance/process/docs/sensitive/diff 门禁。
- [ ] 中文 commit 引用 task 名;不自动推送。
- [ ] 用户审查后归档,不创建二级入口或退役子 task。
## 7. 恢复后再次准入(任何新生产批准之前)
- [x] 新增纯本地 `feature-flag-apply-uncertainty.mjs`:真实发送次数在 `sendMutation` 包装层计数,`page.on("request")` 只作为可选辅助观察;响应和服务端 readback 分开记录,高层入口固定“发送至多一次 → 只读定性”,没有 rollback 回调入口。
- [x] 本地覆盖“batch 成功但页面观察为 0”:readback 为 POST 时返回 `CONTINUE_POST`,发送次数=1、前向重试=0、rollback=0。
- [x] 本地覆盖响应缺失但 GET 为 POST、响应缺失但 GET 为 PRE、GET 失败、部分状态;分别得到 `CONTINUE_POST`、`STOP_NOT_APPLIED`、`STOP_UNKNOWN`、`STOP_FOR_MANUAL_DECISION`。部分状态不自动继续或恢复,必须停止并申请人工决策。
- [x] `node --check scripts/qa/feature-flag-apply-uncertainty.mjs` PASS;focused Vitest `5/5` PASS。
- [x] `npm run prisma:validate` PASS。完整 server Vitest 发现既有付款单导出用例 1 条稳定失败(期望 200、实际 409);在干净 `HEAD` 临时 worktree 中单独运行同一标题仍失败,证明不是本次 QA 状态机/测试引入。本任务不修改付款业务代码,focused 状态机用例保持 5/5 PASS。
- [ ] 重新执行生产 PRE,以现场 `version=49`(或届时新值)冻结三项目标和审计基线;不得沿用 `version=47`。
- [ ] 形成新的生产批准包并等待明确批准;本次一次前向与一次恢复的批准均已消耗。
## 风险与停止条件
- 三个最终 flag 不是纯导航开关;若影响合同/采购/资产/报销页面整体可用性,停止并按已批准边界决定回滚。
- 现场出现任何业务数据或真人活动:停止,不关闭对应模块。
- 目标、version、运行身份或结果不确定:停止,禁止盲重试。
- 发现需要产品代码才能完成:返回规划并重新批准;不得把阶段二偷渡进本轮。
## Residual Register
- `OPEN / OUT_OF_SCOPE`:付款批次 BIFF8 导出单测“exports a reviewed CCB batch as BIFF8 and records the first export”稳定得到 409、预期 200;完整 server suite 与干净 `HEAD` 临时 worktree 的单标题复现结果一致,证明不是本任务 QA 状态机改动引入。后续应独立定性运行依赖、模板门禁或产品行为,不得长期把该红灯当作可忽略背景噪音;本任务不修改付款业务代码或测试断言。