建立项目级标杆文档 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
18 KiB
P0-5 matching.py 直连 ETL 演进史调研
日期:2026-05-04 触发:Neo 在 04a-conflicts-P0-detail 反馈,倾向选项 B(补全 FDW 外部表),要求先调研当时为何偏离工程规范 关联文件:
apps/backend/app/services/matching.py、apps/backend/app/services/fdw_queries.py关联审计:2026-03-18__rns1-e2e-fdw-direct-connect-bugfix.md、2026-03-20__h2-fdw-to-direct-etl-unification.md、2026-03-20__rns1-ai-autonomous-decision-risk-audit.md
一、当前实现状态
1.1 是否真的"直连 ETL 库"
是,但不是经 get_connection() 直连。matching.py 通过 app.services.fdw_queries._fdw_context(None, site_id) 上下文管理器获取游标,该上下文内部调用 app.database.get_etl_readonly_connection(site_id) 直连 etl_feiqiu (生产) / test_etl_feiqiu (测试)。
# apps/backend/app/services/matching.py:21
from app.services.fdw_queries import _fdw_context
# apps/backend/app/services/matching.py:62
with _fdw_context(None, site_id) as cur:
candidates.extend(_query_assistants(cur, phone))
candidates.extend(_query_staff(cur, phone, employee_number))
_fdw_context 在 fdw_queries.py:80 实现:
- 第一参数
conn仅用于读取业务库 RuntimeContext (业务日 / sandbox 模式),不参与 SQL 查询 - 内部
from app.database import get_etl_readonly_connection新建到 ETL 库的直连 SET LOCAL app.current_site_id = <site_id>+SET LOCAL app.current_business_date = <bd>(两条 GUC 在 ETL 库连接上设置)- yield 出 cursor 给调用方
- 退出时 commit / close (owned 时)
1.2 当前查询的视图
| 视图 | schema | 列名约定 |
|---|---|---|
app.v_dim_assistant |
etl_feiqiu | scd2_is_current = 1 (integer) |
app.v_dim_staff |
etl_feiqiu | scd2_is_current = 1 (integer) |
app.v_dim_staff_ex |
etl_feiqiu | scd2_is_current = 1 (integer) |
1.3 文件头注释真实意图
L1-4 的 AI_CHANGELOG 明确写出了改动原因:
2026-03-20 | Prompt: H2 FDW→直连ETL统一改造 | FDW 外部表(fdw_etl.)改为直连 ETL 库,查询 app.v_ RLS 视图。原因:postgres_fdw 不传递 GUC 参数,RLS 门店隔离失效。
L58 / L122 处的 # CHANGE 2026-03-20 | H2 锚点同步标注了 SQL 表名映射 fdw_etl.v_dim_staff/v_dim_staff_ex → app.v_dim_staff/v_dim_staff_ex,以及列类型映射 scd2_is_current TRUE → 1。
二、Git 历史时间线
| 日期 | commit | 作者 / 真实改动日 | 摘要 | 是否变更 dim_staff 访问方式 |
|---|---|---|---|---|
| 2026-02-26 | b25308c |
Neo / 2026-02-26 | feat: P1-P3 全栈集成 — 数据库基础 + DWS 扩展 + 小程序鉴权 + 工程化体系 | 首次引入 matching.py,走 fdw_etl.v_dim_assistant/v_dim_staff/v_dim_staff_ex (FDW 外部表) |
| 2026-02-27 ~ 2026-03-19 | 79f9a0e |
Neo | feat: gift-card-breakdown / batch update | matching.py 未变 |
| 2026-03-18 | (合并入 beb88d5) |
AI / 2026-03-18 21:38~21:47 | RNS1.1 E2E 测试发现 postgres_fdw 不传 GUC,自行把 fdw_queries.py 47 个函数改为直连 ETL,未改动 matching.py (审计 H2 / 已知遗留清单第 4 项) |
否 (但已建立"直连模式" precedent) |
| 2026-03-20 | (合并入 beb88d5) |
AI + Neo / 2026-03-20 | H2 修复:用户确认方案 A (全部统一直连 ETL),把 4 个残留文件 (matching.py / task_generator.py / recall_detector.py / task_manager.py) 一并改为 _fdw_context(None, site_id) + app.v_* |
是,核心变更点 |
| 2026-03-20 | beb88d5 |
Neo / 2026-03-20 09:02 | feat: chat integration ... 累积提交,把 H1/H2/RNS1.1~1.4 全部代码合并入仓 | matching.py 在此提交 diff 中体现完整变更 |
| 2026-04-06 | 6f8f123 |
Neo / 2026-04-06 00:03 | feat: 累积功能变更 — 聊天集成 / 触发器调度 / Kiro→Claude Code 整理 | 仅追加 @trace_service 装饰器,未改 SQL 路径 |
| 2026-05-02 | caf179a |
Neo / 2026-05-02 | feat: AI 重构 + Runtime Context + DWS 修复 | _fdw_context 增加 app.current_business_date GUC,matching.py 行为不变 |
2.1 关键 commit diff 摘要 (beb88d5)
- from app.database import get_connection
+ from app.services.fdw_queries import _fdw_context
- conn = get_connection()
- conn.autocommit = False
- with conn.cursor() as cur:
- cur.execute("SET LOCAL app.current_site_id = %s", (str(site_id),))
+ with _fdw_context(None, site_id) as cur:
- FROM fdw_etl.v_dim_assistant WHERE mobile = %s AND scd2_is_current = TRUE
+ FROM app.v_dim_assistant WHERE mobile = %s AND scd2_is_current = 1
- FROM fdw_etl.v_dim_staff s
- LEFT JOIN fdw_etl.v_dim_staff_ex ex
+ FROM app.v_dim_staff s
+ LEFT JOIN app.v_dim_staff_ex ex
注:本仓的 git 日期与"会话日期"有约 17 天偏差 —
beb88d5commit date 是 2026-03-20,但里面打包了 2026-03-18~20 的 76 个 RNS1 系列 session。
三、审计记录追溯
3.1 2026-03-18__rns1-e2e-fdw-direct-connect-bugfix.md (起点)
- 触发:RNS1.1 端到端测试中,API 调用
fdw_etl.v_*时,远端 ETL 库的 RLS 视图执行current_setting('app.current_site_id')失败报错(GUC 未在远端连接生效) - 根因:
postgres_fdw不传递自定义 GUC 参数到远端连接,即使本地SET LOCAL app.current_site_id = ...也只对本地连接生效 - 当时方案:AI 未询问用户,自行将
fdw_queries.py改为通过get_etl_readonly_connection(site_id)直连 ETL 库,在同一连接上SET LOCAL后查app.v_* - 遗留清单:
matching.py/task_generator.py/recall_detector.py/task_manager.py4 个文件仍走fdw_etl.*,被显式标注为"已知遗留,不在本次 E2E 测试范围"
3.2 2026-03-20__rns1-ai-autonomous-decision-risk-audit.md (定性)
H2 项被列为高风险 8 项之一,标题为"E2E 测试中自行决定架构级修改:FDW → 直连 ETL"。审计原文:
AI 发现
postgres_fdw不传递自定义 GUC 参数(app.current_site_id),尝试修复失败后,自行将fdw_queries.py从"通过业务库的 FDW 外部表查询"改为"直连 ETL 库查 RLS 视图"。AI 说"最干净的方案是让 fdw_queries.py 内部自己获取 ETL 直连",没有询问用户就直接实施。
系统性问题 2:架构级决策未经用户确认。审计建议:涉及数据库连接模式 / DDL / FDW 映射变更必须先展示方案对比。
3.3 2026-03-20__h2-fdw-to-direct-etl-unification.md (修复)
- 决策方式:用户确认方案 A (全部统一直连 ETL) 后执行
- 改造范围:matching.py / task_generator.py / recall_detector.py / task_manager.py 4 个文件
- 关键说明:
- "RNS1.1 期间,AI 自行将
fdw_queries.py改为直连 ETL,但其他 4 个文件仍使用旧 FDW 模式,造成架构不一致" - "FDW 外部表 DDL 暂不清理(用户确认保留)" — 这是当前 fdw_etl schema 仍然存在但不被后端使用的根因
- "RNS1.1 期间,AI 自行将
- 回滚方案 (审计原文):"将 4 个文件中的
_fdw_context调用改回get_connection()+fdw_etl.*查询,恢复 FDW 外部表列名。注意:回滚后 RLS 门店隔离将再次失效。"
3.4 2026-03-20__rns14-chat-fdw-filter-audit.md
聊天模块的 FDW 过滤审计,涉及 dim_member 但不直接影响 matching.py,作为佐证:RNS1 系列中"RLS 跨库失效"是普遍问题,不是 matching 单点。
四、历史 Session 追溯
| Session 文件 | 时间 | 上下文摘要 |
|---|---|---|
docs/ai-env-history/sessions/claude/6d607893-25c1-400e-82a2-325c4e968232.md |
2026-04-07 23:10~04-08 07:38 | Fix-13 召回事件回滚 / recall_detector.py 中也保留了 # AI_CHANGELOG 2026-03-20 H2 FDW→直连ETL 注释。是 H2 改造后的使用现场,非决策原始 session。 |
| 原始决策 session (审计指向) | 2026-03-18 21:38~21:47 | 路径 18/44_9a92e7af/.../main_01_66b06ed6.md (审计中引用,但不在当前 ai-env-history 目录中) |
| 原始决策 session (H2 修复) | 2026-03-20 (具体时段未在审计中标注) | 用户确认方案 A 后批量改 4 文件,session 路径未在审计中明列 |
说明:
docs/ai-env-history/sessions/claude/仅保留了 2026-04-07 之后的 claude session 摘要,2026-03-18~20 的 Kiro session 应保存在docs/audit/_archived/或本机原 Kiro 工作目录。审计记录已经把这两个 session 的关键决策点提炼出来,无需再读原始 jsonl。
五、FDW 现状
5.1 已建外部表清单 (db/fdw/setup_fdw.sql)
-- L42-44
IMPORT FOREIGN SCHEMA app
FROM SERVER etl_feiqiu_server
INTO fdw_etl;
关键事实:setup_fdw.sql 用 IMPORT FOREIGN SCHEMA app 批量导入,理论上 etl_feiqiu.app schema 中所有视图都会自动映射到 zqyy_app.fdw_etl,包括 v_dim_staff / v_dim_staff_ex / v_dim_assistant。
这意味着:Neo 在 04a-conflicts 中说的"FDW 外部表没建 dim_staff"很可能是误解 — 不是没建,而是建了但不被代码使用(且 RLS 在 FDW 路径上失效,即便用了也是错的)。
5.2 dim_staff / dim_staff_ex 是否在内
通过 IMPORT FOREIGN SCHEMA 自动包含:
fdw_etl.v_dim_staff(镜像 etl_feiqiu.app.v_dim_staff,见 db/etl_feiqiu/schemas/app.sql:195)fdw_etl.v_dim_staff_ex(镜像 app.sql:219)fdw_etl.v_dim_assistant(镜像 app.sql:121)
无显式"已删除 / 已废弃"注释。db/fdw/setup_fdw.sql 也未做白名单/黑名单。
5.3 etl_feiqiu.app.v_dim_staff / v_dim_staff_ex 现状
db/etl_feiqiu/schemas/app.sql:
- L195
CREATE OR REPLACE VIEW app.v_dim_staff AS SELECT staff_id, staff_name, ... - L219
CREATE OR REPLACE VIEW app.v_dim_staff_ex AS SELECT staff_id, avatar, ... - L121
CREATE OR REPLACE VIEW app.v_dim_assistant AS SELECT assistant_id, user_id, ...
均为标准 RLS 视图,WHERE 包含 current_setting('app.current_site_id') 过滤(在 ETL 库内生效)。
六、Q1 推测:为什么从 FDW 改为直连?
推测 1 (高置信度) — 修复真实的 RLS 失效 Bug
证据:
- 审计 H2 直接定性:"
postgres_fdw不传递 GUC 参数到远端连接,导致 RLS 视图的current_setting('app.current_site_id')在远端未设置而报错" - 这是 PostgreSQL 已知行为(
postgres_fdw只透传连接选项,不透传 session GUC) - 不修就是生产事故:RLS 门店隔离失效或查询直接 500
结论:这个改动有真实必要,不是 over-engineering。Neo 倾向选项 B (回到 FDW)时必须考虑:不解决 GUC 透传问题,RLS 在 FDW 路径上是失效的。
推测 2 (高置信度) — 时间压力下选了"最干净的方案"
证据:
- 审计原文:AI 在 2026-03-18 21:38~21:47 (10 分钟内) 自行做出架构决策,理由是"最干净的方案"
- 当时正在 RNS1.1 E2E 测试阶段,所有 API 都在跑 500,需要快速止血
- 替代方案 (修 FDW GUC 透传 / 用 dblink / 改用 RLS function-based) 都需要深入调研 + 跨数据库改 DDL + 与运维确认连接配置
结论:这是"凌晨 22 点测试 100% 红的紧急修复",不是规范设计。但事后 H2 审计补救把方案对比推迟到了 2026-03-20 (用户确认方案 A 全部统一直连)。
推测 3 (中置信度) — 后续没回头是因为已成既定事实
证据:
- 2026-03-20 已经把 47 + 4 共 51 个文件全切到直连
_fdw_context()上下文管理器已经成为 etl 查询的标准入口- 后续 RNS1.4 / Fix-13 / Runtime Context (2026-05-02) 都基于这个抽象继续叠加
- FDW schema 保留是"用户确认保留",但没有任何代码再走 fdw_etl.*
结论:即便后期想回归 FDW,沉没成本和回归测试成本都已经很高。
七、Q2 推测:为什么没有规范设计?
场景分析
当时的开发场景 (2026-03-18 21:38):
| 维度 | 状况 |
|---|---|
| 项目阶段 | RNS1.1 端到端测试,正准备给业务上线 |
| 时间 | 晚上 21:38 (深夜 / Kiro Autopilot 模式) |
| 失败规模 | 全部 RNS1.1 API 报 RLS 错误 (绩效 / 任务 / 助教全挂) |
| 发现路径 | E2E 跑完才发现,而非设计阶段就识别 |
| 决策者 | AI 在 Kiro Autopilot 模式下(无人值守) |
根本原因 (按"系统性问题"框架)
- 架构 review 缺失:首次设计
fdw_queries.py走 fdw_etl 模式时,没人对 RLS + FDW 的兼容性做技术验证。这是 02-26 P1-P2-P3 全栈集成期就埋下的雷,直到 03-18 E2E 才爆。 - AI 自主权过大 (Kiro Autopilot):H2 审计原文 "AI 自行决定架构级修改"。当时的工作流允许 AI 在测试失败时直接改架构,缺少 "GUC 透传 → 用户确认 → 方案对比" 的强制中断点。
- MVP 思维优先于规范:RNS1 系列 spec 由 AI 基于 design.md 生成,design.md 假设 RLS 会自动跨 FDW 生效(这是错的),没有在 spec 阶段验证数据库 schema(审计"系统性问题 1:AI 信任文档胜过信任数据库")。
- 回滚方案存在但被刻意保留:H2 审计末尾的"回滚方案"章节明确写了"回滚后 RLS 门店隔离将再次失效",所以当时已经知道要回到 FDW 必须先解决 GUC 问题,只是没人去做。
- FDW DDL 不清理是技术债标记:H2 审计"未变更项"明确写"FDW 外部表 DDL 暂不清理(用户确认保留)" — 是有意保留作为未来可能回归的备份,但没有任何后续动作让它真正可用。
八、推荐 step2 实施方案
基于调研,Neo 倾向选项 B (统一走 FDW),但必须先解决 GUC 透传问题,否则回到 FDW 就是回到 RLS 失效的 Bug 状态。
方案对比
| 方案 | 描述 | 工作量 | 风险 | 是否需要测试库回放 |
|---|---|---|---|---|
| B-1 立即补 FDW 回退代码 | matching.py 改回 fdw_etl.v_dim_*,不解决 GUC |
0.5h | 极高 — 直接重新引入 RLS 失效 Bug | 必须,且会跑红 |
| B-2 渐进式补 FDW | (1) 在 fdw_etl 已有外部表上,(2) 在业务库写新的 SECURITY DEFINER function 包装 GUC,(3) 后期切代码 | 4-8h | 中 — 需验证 SECURITY DEFINER + RLS 组合行为 | 必须,需 RLS 跨库验证 |
| B-3 修 GUC 透传 + 切回 FDW | 修改 user mapping 的 options,通过 set_config 在 FDW 连接初始化时透传 site_id;切回 fdw_etl.* |
6-10h | 中高 — postgres_fdw 11+ 才支持 pre-connection options,且写法 finicky |
必须,需多 site 并发回放 |
| C 维持现状 + 补充文档 | 保留直连 ETL,在 docs/architecture / 后端 CLAUDE.md 增补"为什么 4 个文件偏离 FDW 模式"的明确说明,把当前不一致变成"已知设计选择" | 1-2h | 低 — 仅文档 | 否 |
| D 折中:matching.py 单点统一 | 不动其他 3 个 (task_generator/recall_detector/task_manager),只把 matching.py 也用 SECURITY DEFINER 包装走 FDW;反而加剧不一致 | 3-5h | 高 | 必须 |
推荐顺序
- 首选 C (维持现状 + 补充文档) — 因为:
- "符合工程规范"的成本很高 (B-2/B-3 需要 6-10h + RLS 跨库测试)
- 当前架构是有真实根因的合理选择,不是技术债 — 它解决了 postgres_fdw 不透传 GUC 的真实问题
- 把"为什么直连"的设计决策写进 backend CLAUDE.md / docs/architecture/backend-architecture.md,后续读代码不会再困惑
- 把 fdw_etl schema 的真实角色 (FDW 备份 / 当前不被后端代码使用) 也写明
- 次选 B-2 — 如果 Neo 坚持工程一致性大于成本,推荐 B-2 (写 SECURITY DEFINER function)。但需要:
- 用 1 周时间在 test_zqyy_app + test_etl_feiqiu 上做完整 RLS 跨库回放
- 验证 4 个文件全切回 FDW 的正确性
- 用 e2e_test_rns1.py 全跑通
- 不推荐 B-1 / B-3 — B-1 是直接回退到 Bug;B-3 修 postgres_fdw GUC 透传方案在 PG14+ 才稳定,且配置复杂。
推荐方案 C 的具体动作清单
| # | 动作 | 文件 |
|---|---|---|
| 1 | 在 backend CLAUDE.md 的"数据库访问"段落补充直连 ETL 的根因 + GUC 不透传的事实 | apps/backend/CLAUDE.md |
| 2 | 在 docs/architecture/backend-architecture.md 增加 "为什么 4 个 service 不走 fdw_etl" 一节 | docs/architecture/backend-architecture.md |
| 3 | 在 db/fdw/setup_fdw.sql 顶部加注释:"当前 fdw_etl schema 作为只读备份保留,后端代码通过 get_etl_readonly_connection 直连 ETL 库 + RLS GUC,详见 backend-architecture.md" | db/fdw/setup_fdw.sql |
| 4 | 在 app/services/matching.py / task_generator.py / recall_detector.py / task_manager.py / fdw_queries.py 的 docstring 引用 "为什么直连" 的统一说明位置 |
5 个文件 |
| 5 | 把"FDW + RLS GUC 透传"列入 docs/_overview/04-doc-conflicts.md 或单独的"已知工程债务"清单,标注未来回归条件(运维允许 / postgres_fdw GUC 透传方案验证通过) | docs/_overview/04-doc-conflicts.md |
九、调研中发现的其他问题
fdw_queries.py模块名是历史包袱:文件名叫 fdw_queries 但实际不走 FDW,与文件功能严重不符。后续重构时应改名为etl_queries.py/etl_views.py。低优先级。_fdw_context函数名同样误导:它是 ETL 直连 + GUC 设置的封装,不是 FDW 上下文。改名建议:_etl_rls_context/_etl_session_with_site。db/fdw/setup_fdw.sql仍是生产部署脚本之一:运维如果按文档跑了这个脚本,得到的 fdw_etl schema 当前完全不被后端使用,但表占用元数据空间。运维侧需要明确"这是备份,不影响功能"。- 审计文件证据链非常完整:这次调研能在 1 小时内还原决策 — 全靠
2026-03-18/2026-03-20三份审计的明确记录。这是 NeoZQYY 审计文化的正面案例,值得维持。 - gitignore 提到的
_archived/没污染调研 — 没有读取任何 archived 文件即可还原历史,符合 CLAUDE.md 的强制规则。
十、给 Neo 的最终建议
Q1 推测核心:改动有真实根因 — postgres_fdw 不透传 GUC 导致 RLS 失效,2026-03-18 RNS1.1 E2E 测试时全 API 跑红,AI 紧急止血后 2026-03-20 经用户批准统一推广到 4 个文件。
Q2 推测核心:当时是"测试红 / 深夜 / Kiro Autopilot / 缺架构 review"四重叠加,AI 自主决策后审计才补救;FDW 是 02-26 集成期的初始设计,没人事先验证 RLS + FDW 兼容性。
推荐方案:选 C (维持现状 + 补文档)。当前架构是有根因的合理选择,不是债务。把"为什么直连"写进文档,把 fdw_etl 标注为"备份/未使用",后续读代码就不会再产生疑问。如果坚持选 B,推荐 B-2 但成本 4-8h + 完整回归测试。不要选 B-1(直接回退会重新引入 RLS 失效 Bug)。