Files
Neo-ZQYY/docs/_overview/04a-feedback/P0-7-runtime-context-todos.md
Neo 509cf43284 chore(docs): Wave 0 调研产出 + P0/P1/P2 反馈调研
建立项目级标杆文档 docs/_overview/ 作为产品全景索引,
解决"PRD 零碎、文档膨胀、跨子系统调研无入口"的问题。

主要内容:
- 00-index 总索引 + 维护协议 + 与 CLAUDE.md 关系
- 01-product-overview 产品全景脑图(6 角色 / 6 子系统 / 数据流 /
  7 业务概念 / 8+1 AI 矩阵 / 22 术语)
- 02a-miniprogram-page-matrix 小程序 21 页业务指纹
- 02b-adminweb-page-matrix admin-web 19 路由业务指纹
- 03-test-spec 测试规范 (L1-L5 分层 + 走查模板 + 75-95 case 估算)
- 04-doc-conflicts 39 条冲突索引(P0×8 / P1×13 / P2×13 + 5 子项)
- 04a/b/c-conflicts-*-detail 业务故事卡(7 字段:关联/逻辑/影响/选项/判定)
- 05-orphan-pages-cleanup admin-web 6 孤儿页面处置(1 归档 + 4 保留)
- WAVES-MASTER-PLAN.md 全 Wave 主计划(0-5,共 22-32 工作日)
- WAVE-1-KICKOFF.md Wave 1 实施 kickoff
- GLOBAL-DECISION-DASHBOARD.md 全局决策仪表板

反馈调研产物:
- 04a-feedback/ P0 两轮反馈(8+8 项决策 + D-1/2/3 + F-1/2 子代理产出)
- 04b-feedback/ P1 两轮反馈(13+1+5 项 + E-1/2/3/4 + G-1/2 子代理产出)
- 04c-feedback/ P2 反馈(13 项 + 5 子项 + H-1/2/3 子代理产出)
- NEO-DECISIONS-LOG 累积决策记录

关键追加发现 8 处 D Bug(原蓝本 0):
- P0-3 看板沙箱接入(Wave 1 W1-T1)
- P0-5 致命 1 (4 处 fdw_etl 残留, 已修 commit 17f045a)
- P0-5 致命 2 (JWT aud 缺失, 已修 commit 17f045a)
- P0-6 clearAllTasks 守卫 (Wave 3)
- P0-8 DBViewer 黑名单漏 (已修 commit 17f045a)
- P1-3 task-detail 跳转传 task_id 而非 customer_id
- P2-7 board-finance 隐式 null
- 2 个独立 Bug (page_context.created_at + ClueCategory 字典)

参考: docs/_overview/00-index.md
2026-05-04 07:38:28 +08:00

147 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Runtime Context 深入调研 / 测试 / 收口建议
> 日期2026-05-04 / 触发Neo 反馈"涉及内容多,远未做到完成和收口的地步"
> 关联 SPEC`docs/prd/specs/P20-runtime-context-sandbox.md`
> 关联审计:`docs/database/changes/2026-05-01__runtime_context_sandbox.md` 与 5 份 `2026-05-02__sandbox_*.md`
---
## 一、调研中发现的"未完成 / 未验证"项
按优先级分类。**P0 = 上生产前必须收口****P1 = 灰度期间必须验证****P2 = 长期改进**。
### P0上生产前必须收口
1. **生产库迁移未执行**`db/zqyy_app/migrations/20260501__runtime_context_sandbox.sql``db/etl_feiqiu/migrations/20260502__rls_views_business_date_upper_bound.sql` 仅在 `test_*` 库执行;`zqyy_app` 生产库与 `etl_feiqiu` 生产库未跑。回滚 SQL 已附在迁移末尾,但生产环境的回滚演练未做。
2. **多门店并行 sandbox 未跑过验证**:架构支持,但端到端验证脚本只覆盖单 site`2790685415443269`。site A 切 sandbox + site B 保持 live 时,下列点未验证:
- site B 用户登录看到的 `business_date` 是否仍是真实日
- site B 的 ETL `_fdw_context` 查询是否未受 site A GUC 污染GUC 是连接级,但连接池复用是否带来串扰未测)
- site B 的 AI cache 是否未读到 site A 的 `sbx_*:` 命名空间数据
3. **BD_Manual 与现状不一致未修订**`docs/database/BD_Manual_runtime_context_sandbox.md` § 3.3 / § 4 仍写 "切沙箱按 site_id 暂停 `biz.trigger_jobs``paused_by_sandbox`";代码已移除此逻辑(见 `admin_runtime_context.py``biz_triggers_unchanged` step。文档与代码冲突会误导运维。
4. **Steps key 与文档不一致**`2026-05-02__sandbox_admin_web_manual_checklist.md` 第 1 节列出的期望步骤 key`runtime_context_upserted` / `pending_jobs_cancelled` / `runtime_cache_purged`)与代码实际产出的 key`cancel_etl_processes` / `cancel_task_queue` / `cancel_ai_runtime` / `cancel_ai_jobs` / `biz_triggers_unchanged` / `apply_context`)不匹配,手工清单需要修订。
5. **`coach_service._build_task_groups` 是否带 site_id + runtime_filter 未确认**`2026-05-02__sandbox_no_future_data_plan.md` 标注 "550-566 未带 site_id 过滤 + runtime filter",状态为 "✅ 已实施",但 grep `task_runtime_filter``coach_service.py` 中未匹配到此函数路径。需重新核对。
### P1灰度期间必须验证
6. **小程序覆盖不全**`board-finance.ts` / `board-customer.ts` / `board-coach.ts` 是沙箱"不看未来"主受益方,但 grep `runtime-clock` 在这 3 个文件未匹配到。设计文档要求改 `isCurrentMonthFilter`,未在代码层确认。
7. **AI App8 consolidate 未确认**grep 显示 App2/2a/3-7 都接入了 `as_runtime_business_now_str`,但 `app8_consolidate_prompt` 未在结果中。如果 App8 在 sandbox 模式输出"今天"是真实日,会破坏一致性。
8. **`ai/dispatcher.py``ai/admin_service.py` 接入状态模糊**plan 文档 § B1 提到 `dispatcher.py` 259/330 去重键应改为 `as_runtime_today_param``admin_service.py` 86-87 等 AI 调用统计窗口标 "需产品确认"。grep 结果未确认是否落地。
9. **跨页时间漂移**:小程序 `runtime-clock.ts` 60s in-memory 缓存。从 page A 切到 page B 时,是否一定 force=true 重拉?切沙箱后切回 live 时,缓存是否被 `clearBusinessClockCache` 主动失效grep 未发现任何调用 `clearBusinessClockCache` 的位置。
10. **`task_generator` 部分 SQL 业务日上界未单测覆盖**plan 文档 § B1 标 873 行 "`dwd.dim_assistant` 直连,非 RLS 视图,需切换 `app.v_dim_assistant`"status 是否落地未单测。
11. **`page_context.py` 7 处直连 ETL 依赖 GUC 兜底**:未单独传 `ref_date`;如果 `_fdw_context` 之外的连接路径调用了 `page_context`GUC 不会下发。
12. **AI cache 命名空间清理逻辑**sandbox 模式下 `target_id``sbx_*:` 前缀,但是否有清理脚本删除已结束沙箱实例的 cache 行未实施。长期使用会膨胀 `biz.ai_cache`
13. **沙箱写入数据归零脚本缺失**`coach_tasks` / `coach_task_history` / `recall_events` / `ai_cache` / `ai_run_logs` / `ai_trigger_jobs` 在 sandbox 长期使用后会积累大量 `sandbox_instance_id` 行;清理脚本(按 `runtime_mode='sandbox' AND sandbox_instance_id=...` 限定未实施BD_Manual § 6 仅警告"不要直接清空"。
### P2长期改进
14. **DIM SCD2 维度沙箱回放**`v_dim_assistant / v_dim_member / v_dim_member_card_account / v_dim_staff / v_dim_staff_ex / v_dim_table` 保留 `scd2_is_current=1` 当前快照;如需"sandbox 当时维度状态"需把 WHERE 改为 `scd2_start_time <= bd AND (scd2_end_time > bd OR is null)`。但这会让一行变多行,影响 JOIN需重构。
15. **配置表 SCD2 时间戳"未来"边界**`v_cfg_assistant_level_price` 等用 `effective_from <= bd AND effective_to >= bd` 双向夹住,但 `effective_to` 默认值是否合理(`9999-12-31` vs NULL未在 SPEC 中固化。
16. **`utils/time.ts` 相对时间是否按沙箱算**plan § B2 明确不改;但 sandbox 演示"3 天前"按真实日推算可能出现不直观的标签(如 sandbox=2025-09-01真实"3 天前"是 2026-05-01。需确认是否影响演示体验。
17. **`ai_mode` 字段实际未使用**:表与代码都保留 `ai_mode='live'`CHECK 约束限定只能 `live`。如未来要做"沙箱不真实调 DashScope改返 mock"需移除 CHECK + 实现 mock dispatch预留扩展点未明确。
18. **业务日跨日切换边界**`RuntimeContext.business_date` 包含 "凌晨小于 `BUSINESS_DAY_START_HOUR` 算昨天" 的逻辑sandbox 模式下也按这个规则推算 `business_now`。是否符合演示意图?演示者通常希望 sandbox_date 全天 24h 都是同一天。需评估。
19. **RuntimeContext 表无审计日志**:切换历史只能通过 `updated_by` + `reason` + `updated_at` 看最后一次状态,无完整 transition log 表。如需追溯"该 site 历史上哪天进过 sandbox",无数据。
20. **测试用例缺位**:仓库内未发现 `tests/` 下 runtime_context 相关 pytest 文件。当前所有验证依赖 `tools/db/verify_sandbox_end_to_end.py` 一次性脚本,未纳入 CI。
---
## 二、跨模块影响清单(可能漏处理的地方)
| 模块 | 是否读 RuntimeContext | 测试覆盖 | 风险 |
|---|---|---|---|
| 后端 task_enginetask_manager / task_generator / task_expiry | 是 | 自动化 PASS | 低 |
| 后端 board_service / coach_service / customer_service | 是 | 自动化 PASS部分 | 中:`coach_service._build_task_groups` 路径未确认 |
| 后端 fdw_queries | 是GUC + helper | 自动化 PASS | 低 |
| 后端 ai/cache_service / run_log_service | 是 | 自动化 PASS | 低 |
| 后端 ai/dispatcher / admin_service | 不确定 | 未测 | 中:去重键、统计窗口 |
| 后端 ai/prompts/app2 / app2a | 是 | 自动化 PASS | 低 |
| 后端 ai/prompts/app3-7 | 是 | 自动化 PASS仅 current_time | 低 |
| 后端 ai/prompts/app8_consolidate | 不确定 | 未测 | 中 |
| 后端 ai/data_fetchers/page_context | 是(部分依赖 GUC | 自动化 PASS | 中:连接路径外的边界 |
| 后端 routers/tenant_users | 是 | 未测 | 低 |
| 后端 routers/admin_runtime_context | 是 | Playwright PASS | 低 |
| 后端 routers/xcx_runtime_clock | 是 | 自动化 PASS | 低 |
| ETL 库 39 个 RLS 视图 | C 方案 GUC | 自动化 PASSmax 截断) | 低 |
| ETL `task_engine` / `flow_runner` | 否(直接读 ODS/DWD | — | 低(设计有意) |
| admin-web `RuntimeContext.tsx` | 是 | Playwright PASS | 低 |
| admin-web 其他页AIRunLogs / TriggerManager / AIDashboard | 间接 | Playwright PASS | 低 |
| admin-web 其他 AI 页AIOperations Card 1 / 4 | 间接 | 未在 UI 触发(成本高) | 中 |
| 小程序 `performance` / `performance-records` / `task-list` / `customer-records` / `customer-service-records` | 是 | grep PASS | 低 |
| 小程序 `board-finance` / `board-customer` / `board-coach` | 不确定 | 未确认 | **高**:沙箱主受益面 |
| 小程序 `customer-detail` / `chat` / `utils/time.ts` | 否(设计共识) | — | 低 |
| tenant-admin | 否 | — | 低(不展示业务数据) |
| MCP server | 不确定 | 未测 | 低(只读,但若读到 sandbox 数据需评估) |
---
## 三、Wave 1 走查时必测的场景
1. **live → sandbox 单门店切换流程**admin-web 切到 sandbox=2025-09-01确认 6 个 step 全 success表格更新DB 状态正确。
2. **sandbox → live 还原**:切回后 `sandbox_date / sandbox_instance_id` 恢复 NULL写入恢复 `('live','live')`
3. **小程序业务时钟一致性**:登录小程序,进入 5 个已接入页面,确认 `getBusinessClock()` 返回的 `business_date = 2025-09-01`;切回 live 后再进同一页,业务日变回真实日。
4. **小程序板看不看未来**:进入 `board-finance` / `board-customer` / `board-coach`,确认看到的最大日期 ≤ `sandbox_date`**这是当前覆盖率最低的部分**)。
5. **AI 应用在 sandbox 模式输出**admin-web AIOperations 触发一个 App3成本最低等待执行后到 AIRunLogs Drawer 看 `current_time` 字段是否为 `2025-09-01 HH:MM`
6. **AI cache 命名空间隔离**:触发同一 member_id 的 App7 在 sandbox + live 各一次DB 查 `biz.ai_cache.target_id` 应有两行sandbox 行带 `sbx_*:` 前缀。
7. **任务表 runtime 维度共存**:手动触发 `task_generator`DB `SELECT runtime_mode, sandbox_instance_id, COUNT(*) FROM biz.coach_tasks GROUP BY 1,2`,确认 live 与 sandbox 两套并存。
8. **触发器调度不暂停**`/triggers?tab=all` 12 条触发器全 enabled`paused_by_sandbox`
9. **AIDashboard 真实日期**:切沙箱后 AIDashboard "今日" 仍然是真实日(如 0 调用,因为今天确实未触发),不被拉到 `sandbox_date`
10. **多门店并行site A sandbox + site B live**:用 site B 用户登录小程序,确认 `business_date` 仍是真实今天,业务数据未被截断。
11. **沙箱期 ETL 跑批不污染演示**sandbox 模式下让 ETL 跑一批真实数据进 DWS写真实最新日小程序板看 max 日期仍是 `sandbox_date`(因 RLS 视图截断)。
12. **回滚演练**:在 test 库跑迁移末尾的回滚 SQL确认 7 张表的列被 DROP、唯一索引恢复旧版、`site_runtime_context` 表 DROP后端代码降级 livefail-soft
---
## 四、推荐补充测试用例(给 Wave 1 用)
| # | 场景 | 测试方式 | 期望 |
|---|---|---|---|
| T1 | sandbox 边界:"今天"切 sandbox 到今天 | `PATCH` `mode=sandbox, sandbox_date=today` | 422 拒绝("sandbox_date 不能晚于真实今天"含等号?需确认;当前代码 `>`,等号允许) |
| T2 | sandbox 切到极早历史日2020-01-01 | `PATCH` 切换 | 接受;验证视图无数据(早于 ETL 范围) |
| T3 | reset_sandbox=false 沿用实例 | 第二次切沙箱不重置 | `sandbox_instance_id` 保持不变 |
| T4 | 未审核小程序用户访问 `/api/xcx/runtime/clock` | curl with limited token | 403 / 401 |
| T5 | 非 super_admin 访问 admin runtime API | curl | 403 |
| T6 | `_fdw_context` GUC 串扰 | 同连接池循环切 site | 每次 SET LOCAL 不溢出 |
| T7 | runtime-clock 缓存失效 | 切 sandbox 后立即在小程序内观察 5 个页面 | 60s 内可能仍是旧值;切换流程是否需要主动 push 给小程序未实现 |
| T8 | AI dispatcher 去重键 | 同 member_id 在 live + sandbox 各触发一次 App3 | 不应去重,两个独立 trigger_job |
| T9 | 沙箱产生大量数据后清理 | INSERT 1000 条 sandbox 任务,运行清理脚本 | 仅 sandbox 行被删live 不受影响(**清理脚本待实施** |
| T10 | `app.business_date_now()` 在多事务并行下的 STABLE 行为 | 并发查询 39 视图 | 同事务内值固定,不同事务独立 |
| T11 | `effective_from / effective_to` 双向夹住后 v_cfg 视图返回 0 行边界 | sandbox=2020-01-01 看 `v_cfg_assistant_level_price` | 期望返回当时生效档位(依赖测试库种子数据) |
| T12 | DWS 物理跑批进 sandbox 期 | sandbox=2025-09-01 时让 ETL 跑入 2026-05-04 数据 | 视图侧仍截断到 2025-09-01 |
---
## 五、收口路径建议
```
step1 完成本次 SPEC 起草(已落 docs/prd/specs/P20-runtime-context-sandbox.md
step2 修订 BD_Manual + manual_checklist 以匹配代码现状P0-3 / P0-4
step3 跨模块缺口扫描:补 board-finance/customer/coach + dispatcher + admin_service + app8P1-6/7/8
step4 多门店并行 sandbox 端到端脚本P0-2
step5 沙箱写入清理脚本 + cache 命名空间清理P1-12 / 13
step6 Wave 1 走查(按 §三 12 项 + §四 12 用例)
step7 走查发现 bug → 修 bug → SPEC § 13 「已知冲突」更新 → todos 项标 done
step8 生产库灰度执行迁移(先 test_etl_feiqiu / test_zqyy_app 二跑,再 zqyy_app + etl_feiqiu
step9 SPEC 进入 v1.1,关掉 P0-7 反馈
```
---
## 六、关联资产
- SPEC`docs/prd/specs/P20-runtime-context-sandbox.md`
- 主迁移:`db/zqyy_app/migrations/20260501__runtime_context_sandbox.sql`
- ETL RLS 视图迁移:`db/etl_feiqiu/migrations/20260502__rls_views_business_date_upper_bound.sql`
- BD_Manual`docs/database/BD_Manual_runtime_context_sandbox.md`**待修订**
- 6 份 changes`docs/database/changes/2026-05-01__runtime_context_sandbox.md` + `2026-05-02__sandbox_*.md`
- 端到端验证:`tools/db/verify_sandbox_end_to_end.py``tools/db/verify_admin_web_sandbox.py`
- 后端核心:`apps/backend/app/services/runtime_context.py`
- admin-web 入口:`apps/admin-web/src/pages/RuntimeContext.tsx`
- 小程序入口:`apps/miniprogram/miniprogram/utils/runtime-clock.ts`