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 残留, 已修 commit17f045a) - P0-5 致命 2 (JWT aud 缺失, 已修 commit17f045a) - P0-6 clearAllTasks 守卫 (Wave 3) - P0-8 DBViewer 黑名单漏 (已修 commit17f045a) - 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
This commit is contained in:
146
docs/_overview/04a-feedback/P0-7-runtime-context-todos.md
Normal file
146
docs/_overview/04a-feedback/P0-7-runtime-context-todos.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# 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_engine(task_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 | 自动化 PASS(max 截断) | 低 |
|
||||
| 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;后端代码降级 live(fail-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 + app8(P1-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`
|
||||
Reference in New Issue
Block a user