归档(08-11-internal-oa-finance-backoffice-positioning-and-navigation-reduction): 关闭导航减法任务

这个提交包含在:
2026-08-13 22:35:39 +08:00
父节点 83405a7a4c
当前提交 29a33031aa
共修改 6 个文件,包含 3 行新增和 3 行删除
@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
@@ -0,0 +1,102 @@
# 财务后台定位与主导航减法:设计
## 1. 设计结论
本任务只有一个可交付阶段:**定位固化 + 三个固定 feature flag 的受控关闭**。`project_domain_v1` 原为唯一条件目标,但 2026-08-11 本地真实浏览器回归证明关闭后会让报销项目关联控件产生误导性降级,因此已从生产目标剔除。任务不引入导航层级,不修改产品代码,不部署候选。合同、采购、资产的“二级入口”是延后事项,不是本轮第二个实现阶段。
首次生产尝试前的“零代码”定义为:`client/src`、`server/src`、`scripts`、migration、依赖和部署配置零 diff;只允许 task、事实源、治理 spec 和必要的当前计划纠偏。生产变化由已部署功能管理页调用既有 batch API 完成。首次尝试因观察缺陷安全恢复后,用户另行批准一个最小本地例外:只可新增/修改 `scripts/qa` 纯状态机和对应单元测试,产品运行代码、API、schema、依赖与部署配置继续零 diff。
## 2. 产品边界模型
| 域 | 定位 | 当前动作 |
| --- | --- | --- |
| 报销、票据/OCR、审批、付款、AP | OA 主办且要求真实使用 | 保持开启;真实摩擦出现后才开小产品 task |
| 合同、采购、资产 | OA 低频台账 | 保持可用与冻结;暂留一级入口 |
| 认证、权限、审计、备份恢复、离职撤权 | 治理底线 | 不按使用频率削减 |
| 通讯录、请假、考勤、员工日常协作 | 企微主办、OA 留存/接管 | 不与企微争夺日常入口 |
| 公告、普通待办、周报 | 当前无使用证据、非核心 | 三个 flag `force_off`,代码与数据保留 |
| 项目 | 当前无使用证据,但报销等保留域会查询项目 | 本地跨域回归未通过,维持现状 |
| 绩效、薪资、ERP/集成、预算、Cutover | 超出当前规模的候选 | 只登记退役审查,不执行退役 |
## 3. 现有运行路径
```text
管理员 /system/feature-flags
-> GET /api/system/feature-flags(读取现场 version/effective)
-> 选择恰好三项 + force_off + 统一 note
-> POST /api/system/feature-flags/batch(一次)
-> 一个 Prisma transaction
-> 每项 expectedVersion 校验
-> 每项 updateMany version+1
-> 每项 FEATURE_FLAG_UPDATED audit
-> GET flags/navigation/changelog(只读验证)
```
batch 端点保留每项既有 `rolloutType` 和 `departmentIds`,任一 version conflict 会回滚整个事务。它比三次单项 PUT 更适合作为“严格一次”的配置写入。
## 4. 影响合同
| Flag | 导航消失 | 后端/聚合副作用 | 保留边界 |
| --- | --- | --- | --- |
| `announcements_v1` | 公告 | 公告 API(含写接口)disabled;Portal/搜索不再聚合公告 | 通知记录和通知中心不在此 flag 范围;这是关闭而非仅隐藏,临时发公告前须先重新开启 |
| `todo_board_v1` | 普通待办 | todos API disabled;Portal/搜索不再聚合普通事项 | `待我处理` 审批任务不是普通待办,必须保留 |
| `weekly_report_v1` | 周报 | weekly-reports API disabled;Portal/搜索不再聚合周报 | 历史表保留 |
| `project_domain_v1`(已排除) | 项目 | `/api/projects*` disabled;Portal/搜索和其他页面的项目候选为空/失败 | 本地证明未关联控件无关闭说明、已关联控件丢失当前项目并回落到“不关联项目”,不得进入生产 target |
三个最终目标的直接路由 disabled 是预期结果,因此这不是“仅 CSS 隐藏”。计划仍称为导航减法,是因为用户可见目标是收缩入口;技术实现同时冻结了三个无数据模块的后端入口,且完全可逆。项目 flag 明确保留开启,不属于本轮生产变化。
## 5. 执行门禁
### 5.1 本地门禁
- 只修改文档/spec/task,不运行产品构建或发布。
- 校正 `docs/产品口径.md` 中 OA 主办域及 P0-10,避免“项目仍是活跃 OA 主办主线”与新定位冲突。
- 替换 governance 中的 A/B 未决门为双轴结论:范围收缩、核心流程采用。
- 搜索当前非归档任务中的 R63–R66 前向引用,只纠正会驱动未来扩建的说明;不改历史归档材料。
### 5.2 项目 flag 本地准入
- 使用本地服务、真实路由和浏览器,不修改产品代码。
- 准备或复用两张本地报销:一张 `projectId=null`,一张关联现有项目;记录原始显示。
- 只在本地将 `project_domain_v1` 临时设为 `force_off`,验证两张详情:页面加载只是基础条件;还必须检查项目控件的可见/禁用/错误状态,以及已关联项目名称、编号和关联事实是否仍正确。
- 网络证据确认 `/api/projects` 的 disabled 响应如何被 UI 消费,不能只看 DOM 终态。
- 恢复本地 flag snapshot。任一可见错误、误导性空列表、已关联项目丢失或恢复失败,生产方案固定为三项。
### 5.3 生产 PRE(另批)
- 确认唯一 trialrun、运行 SHA 与已知已部署候选一致、migration 无 pending;本地文档领先不得误判为产品漂移。
- 复用已审查的只读普查能力现场确认三个最终目标模块仍为零数据/零真人活动;有效只读查询严格一次、零 retry。
- GET flags 保存三个最终目标的完整 snapshot 与审计基线;断言 target 恰好存在、effective on、无并发 version 变化。
- GET impact 断言返回的导航和后端影响与本设计一致。
- 审计粒度以实际 batch 路由为准:每个 target 各写一条 `FEATURE_FLAG_UPDATED`,没有独立 batch 审计;三个目标的预期全局增量是 3,不是 4。
### 5.4 生产 APPLY(再次明确批准)
- 使用既有管理员凭据现场登录;凭据不得进入 argv、env、历史、日志或证据。
- 在现有功能管理页选择恰好三个最终目标,批量“强制关闭”,填写统一说明;网络证据必须显示 `POST /api/system/feature-flags/batch` 恰好一次。
- 禁止单项保存、刷新后重试、workspace PATCH、其他配置或业务页面 mutation。
### 5.5 POST
- 三个最终 target 全部 effective off、reason `flag_force_off`、version 各 +1、audit 各 +1。
- 管理员 `/api/system/navigation` 的所有 section 均不含公告、普通待办、周报;项目及其他保留导航仍存在。
- GET 三个最终 target 的直达模块得到 409/disabled,不得 401、5xx 或意外业务数据。
- 只读打开 Portal、报销、合同、采购、资产;项目 flag 保持开启,报销等保留域的项目选项不得因本轮变化而降级;不允许页面整体失败或产生业务 mutation。
## 6. APPLY_UNCERTAIN 与回滚
- POST 通道中断、输出缺失、响应不可解析或本地观察器与响应矛盾:进入 `APPLY_UNCERTAIN`。在任何重试、回滚或补偿写入之前,第一动作必须只读 GET 三项当前 version、overrideMode 和 effective 状态,再用审计/changelog 交叉验证;未定性前所有配置 mutation 都被禁止。
- 最终 target 均 version +1 且 off:定性为已落地,继续 POST 检查,禁止重试。
- 最终 target 均保持 PRE snapshot:定性为未落地,停止并重新申请,不在现场重试。
- 出现部分更新:违反事务合同,记录为产品/运行发现并停止;不得用手工单项修补掩盖。
- 若生产批准明确包含回滚,且前向结果已经只读定性为已落地、随后保留域页面或项目关联显示合同真实失败,才可用一次 batch 将最终 target 恢复为 PRE 的 overrideMode;预授权不允许跳过写入定性。rollout/departments 本来未变,rollback note 明确原因。否则停下请求批准。
### 6.1 下一次前向写入的本地准入
- 修正执行器的观察范围:`page.on("request")` 只统计页面发出的请求,不能用于判断 `context.request.fetch` / `APIRequestContext` 的 batch 次数;要么让 batch 由页面真实发出,要么直接围绕调用函数、响应和服务端 readback 计数。
- 增加本地回归:batch 实际成功、response `count=3`、页面观察为 0 时,执行器必须进入只读定性;GET 为 POST 状态则继续验证,rollback 次数必须为 0。
- 同时覆盖响应缺失但 GET 为 POST、响应缺失但 GET 为 PRE、GET 失败、部分状态四条分支。该准入通过前不得申请新的生产前向写入。
## 7. 延后设计
合同、采购、资产二级入口需要改变扁平导航模型,是产品代码与部署工作。当前只有极低真人使用证据,引入层级的维护成本高于收益,因此不设计 schema、不建子 task、不列为本轮 AC。触发条件是:核心财务链有多名真人持续使用,并出现“一级导航过载”的明确反馈。
@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
@@ -0,0 +1,137 @@
# 财务后台定位与主导航减法:执行计划
## 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. 收口
- [x] 更新 task 证据,明确产品代码零改动、部署 0、最终 target=3、两轮 batch/恢复、审计增量、项目本地准入失败和二级入口延后。
- [x] 重跑 task/governance/process/docs/sensitive/diff 门禁。
- [x] 中文 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。
- [x] 重新执行生产 PRE,以现场 `version=49` 冻结三项目标和审计基线;不得沿用 `version=47`。身份、运行 SHA、migration、窗口、flag snapshot、审计基线和三模块零使用证据均已重新取得。
- [x] 形成新的生产批准包并等待明确批准;本次一次前向与一次恢复的批准均已消耗,新的 batch 尚未批准、尚未执行。
### 7.1 新一轮生产 PRE 的已确认部分(2026-08-13)
- 管理员只读现场:一次登录、正常 logout,配置 mutation=0、业务 mutation=0。三项仍恰好为 `force_on/global/departmentIds=[]/version=49/effective=true/reason=flag_force_on_global`;note 均非空且脱敏 hash 一致,未保存或输出原文。全局 `FEATURE_FLAG_UPDATED total=7`,三项目标 changelog 均为 2;公告、普通待办、周报和项目导航均存在,Portal 返回 200。
- 运行门禁:唯一生产身份 PASS;运行 build 精确等于 `origin/main` 已部署候选,本地提交相对该候选在 `client/src`、`server/src` 和部署 compose 上产品差异为 0;数据库身份 PASS,migration `pending=none`,回滚窗口仍为 `OPEN / terminatedRows=0 / basis=terminated_status_count`。
- 使用度首次查询:复用归档查询器严格执行一次,执行器报告该次 `queryExecuted=1 / zeroRetry=1`,内置合同确认只读事务、20 个模块齐全且全部 `queryStatus=PASS`。本地脱敏过滤器误用 `module` 而非真实字段 `moduleKey`,因此没有保留三个目标模块的当前聚合值;没有把该次成功查询伪写成“三模块仍为零”。
- 使用度 supplemental:用户另行批准后又执行一次、明确标注为 supplemental 的只读查询;它不是首次查询的 retry。三个目标 `announcements`、`todos`、`weekly_reports` 均为 `totalRows=0`,30/90 天活动、真人 actor/event 和真人审计 actor/event 全部为 0,`effectiveFlagState=on`、`queryStatus=PASS`。该次自身 `queryExecuted=1 / zeroRetry=1`;本轮合计发生两次只读 census 执行,不合并成一次。
- supplemental 脱敏过滤器按 `moduleKey` fail-closed:必须恰好命中三个唯一目标,完整数值字段必须为非负整数,query/flag 状态必须符合预期;少项、重复、字段异常或任一使用度非零均直接失败并停止。本次 `targetCount=3` 且全部通过。
- 新锁输入:三个目标恰好为 `announcements_v1@49`、`todo_board_v1@49`、`weekly_report_v1@49`;全局审计基线 7、各目标 changelog 2。正式写入前仍须读取现场值做 version lock;若任一项不再为 49、状态/审计漂移或使用度前提改变,停止而不是沿用本批准包。
### 7.2 第二次生产 APPLY 与恢复(2026-08-13)
- 批准边界:严格一次三目标前向 batch;只有“前向已通过现场 readback 确认落地”且“真实 POST 验收失败”同时成立,才允许最多一次恢复。观察器信号本身不能触发恢复。本轮没有单项 PUT、workspace PATCH、其他 flag 或业务数据写入。
- 写前门禁:身份、运行 build、数据库身份与 migration 再次 PASS,回滚窗口 `OPEN / terminatedRows=0 / basis=terminated_status_count`。同一执行现场确认三项目标仍为 `force_on@49`,全局审计 7、各目标 changelog 2,导航目标均存在。
- 前向结果:`executeFeatureFlagApplyAttempt` 发送计数严格为 1;batch response `count=3`,无重试。无条件 readback 确认三项统一进入 `force_off@50/effective=false/reason=flag_force_off`,因此前向确实已落地。页面 observer 没有观察 `APIRequestContext` 请求只形成 `observerMismatch`,没有直接触发恢复。
- 恢复触发:POST witness 报告两个门禁失败——合同台账列表 HTTP 400、Portal UI 失败。由于前向已确认落地且当时执行器把这两项判为真实 POST 失败,按获批分支严格一次恢复;恢复 batch response `count=3`,readback 为 `CONTINUE_POST`,三项统一回到 `force_on@51/effective=true/reason=flag_force_on_global`。配置 mutation 恰好为一次前向 + 一次恢复,业务 mutation=0。
- 失败定性:恢复后用全新浏览器/API context 只读复核,证明两项均是 witness 假失败,而非产品故障。合同列表端点不接受 `page/pageSize`,执行器错误附加分页参数才得到 400;正确 `GET /api/contracts-registry` 为 200。Portal 门禁错误地复用了整段会话累计的 `pageErrors`;全新分段观察 `/portal` 正常、正文存在、无新 page error、无 API 5xx。报销、合同、采购、资产、项目五个保留域均为 200。
- 审计与终态:全局 `FEATURE_FLAG_UPDATED` 从 7 到 13,增量 6;各目标 changelog 从 2 到 4,各增量 2,精确对应一次前向和一次恢复。导航已恢复,写后回滚窗口仍为 `OPEN / terminatedRows=0 / basis=terminated_status_count`,migration `pending=none`。
- 结论:生产安全,但“关闭三项并保持关闭”的业务目标再次未达成。本次前向与恢复批准均已消费;`expectedVersion=49` 已失效,任何后续写入必须以现场 `version=51` 重新锁定并另获明确批准,禁止在本轮第三次尝试。
## 风险与停止条件
- 三个最终 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 状态机改动引入。后续应独立定性运行依赖、模板门禁或产品行为,不得长期把该红灯当作可忽略背景噪音;本任务不修改付款业务代码或测试断言。
- `OPEN / TOOLING`:归档后的 `run-production-usage-census.sh` 仍按活动 task 相对层级计算仓库根,并引用归档前 query 路径;直接运行会在本地 source deployment contract library 前失败,远端查询为 0。本轮通过进程内只读路径替换复用既有 runner,未修改归档证据;后续若正式复用该入口,应让 runner 对归档移动稳定或提供受测的显式仓库根,不把本地路径漂移误判为生产故障。
- `OPEN / TOOLING`:第二次生产 APPLY 的恢复由两个 witness 假失败触发:合同列表探针使用了端点不支持的分页参数;Portal 门禁跨阶段累计 `pageErrors`。两者在全新只读 context 中均证明产品正常,但恢复已经执行。后续若再次尝试导航减法,必须先用本地用例固定“每个保留域使用真实查询合同”和“页面错误按单个导航阶段切片”,不得直接复用本次一次性 runner。
## Lifecycle Closeout
- 完成:财务与低频台账后台定位已进入事实源和治理 spec;项目 flag 因报销跨域降级被排除;三目标零使用与生产安全终态已有证据;QA 状态机 5/5;两轮前向和恢复的版本/审计链完整。
- 未完成:公告、普通待办、周报仍为 `force_on@51`,导航减法未保持生效;POST 终态相关 AC 保持未勾选。
- 停止原因:当前真人采用度过低,入口复杂度尚未形成真实摩擦;继续修建一次性生产 witness 的成本超过业务收益。两次成功写入被验证工具假失败撤销,说明下一步不应继续增加验证复杂度。
- 替代条件:不创建即时工程任务。下一轮真实报销由管理要求走 OA;只有多名真人持续使用且出现导航摩擦时才重新评估导航减法。届时验证只覆盖实际依赖和最小 `/portal` witness,失败默认保留已知低风险现场做只读诊断,确认产品回归后才恢复。
@@ -0,0 +1,120 @@
# 财务后台定位与主导航减法
## Goal
将 hyb-oa 的正式定位从“通用内部 OA”收敛为本公司 5–10 人使用的**财务与低频台账后台**:范围上只保留 OA 真正应主办的财务、台账和治理能力,采用力度上只要求团队在这些核心流程中真实使用,不与企业微信争夺请假、考勤和日常协作。随后利用现有运行时 feature flag,一次性、可逆地关闭公告、普通待办和周报;项目是否一并关闭,必须先通过本地跨域回归。
## Background
- `08-11-internal-oa-usage-census-and-simplification` 的生产只读普查覆盖 20 个模块:聚合 70 行(其中系统配置 26),13 个模块零行,近 90 天只有 13 条真人归属/创建记录证据;唯一可证明的业务链是“报销 → 审批 → 付款/AP”。
- 请假、考勤等日常员工流程按既有事实源由企业微信主办。零 OA 使用不代表这些业务没有发生,而是操作端在别处。
- 用户已正式选择“范围收缩 + 核心流程采用”的组合定位,不再沿用 A/B 二选一:
- OA 主办并推动真实使用:报销、票据/OCR、审批、付款批次、AP、必要财务交接。
- OA 保留冻结:合同、采购、资产等低频高失败成本台账,以及认证、权限、审计、备份恢复、离职撤权等治理底线。
- 企业微信主办:通讯录、请假、考勤及适合在企微完成的员工日常协作。
- 超出当前范围、仅列退役审查候选:绩效、薪资、ERP/集成、预算、Cutover 等;本任务不删除它们。
- 旧 R63–R66 产品排期中的通知中心、入职向导等扩建方向已被用户判定失效;不得再把它当后续产品主线。
## Confirmed Technical Facts
- `server/src/services/workspaceSettings.js:79-82` 将周报、待办、公告、项目分别绑定到 `weekly_report_v1`、`todo_board_v1`、`announcements_v1`、`project_domain_v1`;`getSystemNavigation` 在 flag 非 effective 时过滤导航项(`:310-357`)。
- 2026-08-11 生产快照中四个模块均为 0 行、近 90 天真人活动 0,且导航均为 `main`、effective flag 均为 on;执行前仍须现场复核,不能用该快照替代现场状态。
- `POST /api/system/feature-flags/batch` 使用单个数据库事务、逐项 `expectedVersion` 乐观锁、逐项 `FEATURE_FLAG_UPDATED` 审计;任一目标缺失或版本冲突会整批回滚(`server/src/routes/featureFlags.js:586-665`)。
- `force_off` 不只是隐藏导航,还会使对应后端能力返回关闭状态:公告 `/api/announcements`,周报 `/api/weekly-reports`,普通待办 `/api/todos`,项目主域 `/api/projects`;Portal 和全局搜索会停止聚合相应内容。项目 flag 关闭后,报销、合同和采购页面中的“项目选项”查询也会不可用,但 `project_linking_v1` 不在目标清单,合同台账 API 本身不得被关闭。
- 合同、采购、资产当前没有统一 feature flag 可单独变为“二级入口”;现有导航模型只有 `main/admin/backup` section 和扁平 item,不具备业务二级分组。本轮不新增导航层级。
## Scope
- 固化“财务与低频台账后台 + 核心流程真实采用”的正式产品定位,并纠正会继续驱动失效 R63–R66 扩建计划的当前前向口径。
- 在零产品代码、零部署前提下,先完成本地事实源与治理规则收敛。
- 在另获生产写入批准后,通过既有 batch feature flag 能力一次性关闭公告、普通待办和周报;`project_domain_v1` 只有本地证明报销详情项目关联不降级时才能加入同一批次。
- 合同、采购、资产二级入口及所有模块退役只登记延后条件,不实施、不创建子 task。
## Requirements
### R1. 固化正式产品定位
- 更新 `docs/产品口径.md` 和 `.trellis/spec/hyb-oa/project-governance.md`,用“财务与低频台账后台 + 核心流程真实采用”替换尚未决的 A/B 门。
- 明确 OA 主办、企微主办、低频保留和退役审查候选四个边界;强推报销走 OA 是管理动作,不实现强制提醒、渠道封堵、引导页或新通知功能。
- 只纠正仍会驱动未来工作的 R63–R66 前向引用;历史归档证据不批量改写。
### R2. 定位阶段保持零产品代码;恢复后允许最小 QA harness 修复
- 定位固化、项目准入和首次生产尝试阶段不修改 `client/src`、`server/src`、`scripts/`、migration、依赖、构建或部署配置,也不新增 task-scoped 可执行脚本。
- 首次生产尝试安全恢复后,用户明确批准在再次申请生产前先修复本地观察机制并补五个失败分支。该局部例外只允许新增/修改 `scripts/qa` 的纯状态机与对应本地单元测试;仍不得改产品运行代码、API、schema、依赖或部署配置。
- 允许修改当前 task、既有业务事实源、治理 spec,以及确有误导性的当前任务前向说明。
- 不构建、不部署、不改变 build SHA。生产只使用已部署的功能管理页面和既有 batch API。
### R3. 三个固定 flag 与一个条件 flag 的精确原子关闭
- 固定目标是 `announcements_v1`、`todo_board_v1`、`weekly_report_v1`,目标集合至少且恰好包含这三项;不得包含 `project_linking_v1` 或任何其他 flag。
- `project_domain_v1` 是唯一条件目标。实施前必须在本地关闭它,分别验证未关联项目和已关联项目的报销详情:页面无可见查询错误,未关联控件具有明确且不误导的状态,已关联单据仍显示原项目名称/编号和关联事实。任一项不满足即从生产 target 剔除,项目入口本轮继续保留。
- 本地准入结论已将目标固定为三项;生产变更方式固定为一次 batch mutation,将这三项 `overrideMode` 设为 `force_off`;保留各自既有 `rolloutType` 和 `departmentIds`,填写统一、可审计的定位说明。
- 执行前读取现场 version、override、rollout、effective 状态和三个目标模块当前计数;任一目标不唯一、版本变化、已有真人/业务数据、影响范围超出本合同或生产身份不清楚即停止。
- 生产登录会产生认证 session/audit;除这类认证状态、三条 feature flag 更新及三条对应审计外,不允许业务写入、workspace PATCH、数据清理或其他配置变更。
### R4. 分段批准与不确定性处理
- 当前批准只允许创建 task 和规划,不允许 `task.py start`、事实源改写或生产操作。
- 用户批准本规划并 `task.py start` 后,只先实施本地文档/spec 收敛与只读门禁;本地准入完成后确定的生产 batch(现已固定为三个 flag)仍需再次明确批准。
- 生产批准合同最多包含一次前向 batch;只有批准文本明确包含预定义回滚分支时,才允许在 POST 检查失败时执行最多一次回滚 batch。
- 若请求通道中断、响应缺失或 JSON 不可解析,进入 `APPLY_UNCERTAIN`:只允许重新 GET flag、导航和审计定性,禁止盲目重试。
- 本地观察器计数与 batch 响应或现场状态矛盾同样属于 `APPLY_UNCERTAIN`。未通过只读 GET 三项目标的 version/override/effective 并以审计交叉定性前,不得重试、回滚或执行任何补偿写入;回滚预授权不改变该顺序。
### R5. 写后验证与保留域保护
- 最终 target 均须满足 `overrideMode=force_off`、`effectiveEnabled=false`、`effectiveReason=flag_force_off`、version 相对 PRE 恰好 `+1`,且新增审计条数恰好等于 target 数、每项各一条。
- 管理员导航必须不再出现 `announcements`、`todos`、`weeklyReports`;只有 `project_domain_v1` 通过本地门禁并进入生产 target 时才要求 `projects` 消失。对应直达只读入口应得到明确的 disabled/409,而不是登录失效或 5xx。
- Portal、报销、合同、采购、资产的既有只读入口必须仍可加载,不得出现被动业务 mutation。若 `project_domain_v1` 入选,生产 POST 继续复核本地已证明的项目关联显示合同;“空下拉或隐藏错误”不算通过。
- `project_linking_v1`、合同/采购/资产数据、回滚窗口和其他 feature flag 的 snapshot 必须保持不变。
### R6. 阶段二明确延后
- 合同、采购、资产继续保留现有一级入口和最小能力,本轮不实现“二级入口”。
- 导航层级、分组或二级入口只有在核心财务流程出现更多真人用户、一级导航确有可观察摩擦时,才允许另建产品 task。
- 绩效、薪资、ERP/集成、预算、Cutover 本轮只标记退役审查候选,不关闭 flag、不删入口、不删代码、schema 或历史数据。
## Acceptance Criteria
- [x] AC1:产品口径和治理 spec 统一写明“财务与低频台账后台 + 核心流程真实采用”,不再保留 A/B 未决状态或 R63–R66 扩建主线。
- [x] AC2:git diff 证明产品运行代码、migration、依赖和部署配置零改动;首次恢复后的唯一代码例外是 `scripts/qa` 状态机及其 focused 单元测试,无构建、部署或 build SHA 变化。
- [x] AC3:本地关闭 `project_domain_v1` 后,分别验证未关联和已关联项目的报销详情;只有控件状态明确、已关联项目名称/编号仍正确且无可见错误才允许生产 target=4,否则固定 target=3。
- [x] AC4:生产 PRE 现场确认三个最终目标模块仍为零数据/零真人活动,三项 flag 目标恰好、effective on、版本已冻结,且完整记录原始 snapshot 与影响范围。
- [ ] AC5:仅在生产写入另获批准后,通过既有页面/API 严格执行一次最终 target batch `force_off`;无单项 PUT、无 workspace PATCH、无其他 flag 或业务数据写入。
- [ ] AC6:POST 证明最终 target 均 effective off、version 各 `+1`、审计增量等于 target 数;对应导航消失,直达得到可解释的 disabled 结果。
- [ ] AC7:Portal、报销、合同、采购、资产只读入口仍可加载;若项目 flag 入选,已关联项目显示仍正确;`project_linking_v1` 与其他 flag 不变,非认证业务 mutation 为 0。
- [x] AC8:阶段二和退役动作均未实施;合同、采购、资产继续保持可用,二级入口只作为事件触发的后续候选。
- [x] AC9:task validate、Trellis governance、process contract、docs registry、敏感扫描和 `git diff --check` 通过;生产证据不含账号、Secret、token 或原始业务 payload。
## Out of Scope
- 新功能、UI/导航层级代码、组件改造、路由删除、schema/data migration、模块物理退役。
- 用技术手段“强制”员工报销、禁用微信沟通、增加提醒/推送或制造生产业务记录。
- 关闭合同、采购、资产、付款、AP、票据、认证、权限、审计、备份恢复能力。
- 部署、公网候选发布、企业微信写入、真实付款或任何非本合同生产写入。
## Test Plan
- 静态核对三个最终目标 flag、已排除的项目 flag、batch transaction、后端影响范围和保留域依赖。
- 本地文档阶段运行 task validate、governance、process contract、docs registry、敏感扫描和 `git diff --check`;用 `git diff --name-only` 断言零产品运行代码。恢复后的 QA harness 阶段另跑 Node 语法与五分支 focused test,并继续断言 `client/src`、`server/src`、schema、依赖和部署配置零 diff。
- 本地先建立含项目关联和不含项目关联的报销样本,在 `project_domain_v1=force_off` 下用真实浏览器验证控件与显示;不以组件单测或“页面未崩”代替。
- 生产批准后:身份与运行 SHA 只读 PRE → 现场最终 target 模块计数和 flag snapshot → 管理员登录 → 一次 batch → 只读 flag/audit/navigation/direct-route/保留域 POST。
- 任一生产状态不确定进入 `APPLY_UNCERTAIN`;先只读定性,不连续提交。
## Approval State
用户已于 2026-08-11 完成只读复核并批准 `task.py start`,同时要求先本地验证 `project_domain_v1` 对报销项目关联的影响。该批准允许本地定位固化和合成数据验证,不包含生产 feature flag 写入。
## Local Admission Result
2026-08-11 本地真实浏览器 on/off/restore 验证已完成:`project_domain_v1=force_off` 时,未关联报销的项目控件仍启用但没有关闭说明;已关联报销的事实卡保留项目信息,项目控件却丢失当前选项并回落到“不关联项目”。因此 AC3 的条件未满足,生产 target 已固定为三个:`announcements_v1`、`todo_board_v1`、`weekly_report_v1`。项目 flag 继续保持现状,不允许在本轮生产 batch 中出现。
## Retry Admission After Restored Attempt
2026-08-12 获批的一次前向 batch 已成功落地,但本地页面观察器漏掉 `APIRequestContext` 写入并误触发预授权恢复;生产最终安全回到 `force_on@49`。下一次生产前向写入不是本次批准的延续:必须以现场 version 重新锁定、先在本地覆盖“写入成功但页面观察为 0”及全部不确定分支,并另获明确批准。本任务不得以修正文档为由自动再次写生产。
## Lifecycle Closeout
本 task 于 2026-08-13 按用户决定停止继续生产尝试并归档。正式产品定位、项目 flag 排除、本地不确定性状态机、两轮生产事实和安全终态均已完成记录;三项导航保持关闭这一原目标没有达成,AC5–AC7 保持未勾选。两轮前向 batch 都成功,随后分别因观察器和 POST witness 的假失败恢复,生产终态为三项 `force_on@51`、业务 mutation=0、回滚窗口 `OPEN/0`。
停止原因是成本收益不再成立,而不是产品无法关闭:当前仅有极低真人使用,三个多余入口没有可观察摩擦;继续建设验证装置的成本已经超过导航减法的即时价值。导航减法只在“多名真人持续使用且出现明确导航摩擦”时重新评估,并须创建或更新当时的 Trellis task,不沿用本 task 的版本锁或生产批准。当前下一步是管理动作——下一轮真实报销要求走 OA,用真人摩擦决定后续工程工作;在此之前不创建猜测性产品任务。
@@ -0,0 +1,33 @@
{
"id": "internal-oa-finance-backoffice-positioning-and-navigation-reduction",
"name": "internal-oa-finance-backoffice-positioning-and-navigation-reduction",
"title": "财务后台定位与主导航减法",
"description": "固化 OA 为财务与低频台账后台的正式定位,并在另获生产批准后使用既有 batch feature flag 能力可逆关闭公告、普通待办和周报;项目须先通过本地报销关联回归。产品运行代码零改动、无部署;恢复后仅补本地 QA 状态机与测试,不实现二级导航或模块退役。",
"status": "completed",
"dev_type": "configuration",
"scope": "task-docs,docs,spec,scripts-qa-local-harness,production-feature-flags",
"package": "hyb-oa",
"priority": "P1",
"creator": "shunli",
"assignee": "shunli",
"createdAt": "2026-08-11",
"completedAt": "2026-08-13",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [
"docs/产品口径.md",
".trellis/spec/hyb-oa/project-governance.md",
"server/src/services/workspaceSettings.js",
"server/src/services/featureFlags/index.js",
"server/src/routes/featureFlags.js",
".trellis/tasks/archive/2026-08/08-11-internal-oa-usage-census-and-simplification/research/production-usage-snapshot.json"
],
"notes": "生命周期关闭:财务与低频台账后台定位、项目 flag 排除、QA 不确定性状态机和生产证据已完成。两轮三 flag 前向 batch 均成功,分别因观察器和 POST witness 假失败恢复;生产安全终态为三项 force_on@51、全局审计 13、各目标 changelog 4、业务 mutation 0、窗口 OPEN/0。导航减法未保持生效,AC5-AC7 未完成。用户决定停止第三次尝试:当前极低真人采用下收益不支持继续投入;仅在多名真人持续使用并出现导航摩擦时重新评估。下一步是管理要求下一轮真实报销走 OA,不创建即时工程任务。",
"meta": {}
}