Files
Neo-ZQYY/docs/_overview/04a-feedback/P0-5-matching-evolution.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

280 lines
18 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.
# 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` (测试)。
```python
# 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)
```diff
- 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 天偏差 — beb88d5 commit 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.py` 4 个文件仍走 `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 仍然存在但不被后端使用的根因
- **回滚方案 (审计原文)**:"将 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`)
```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 模式下(无人值守) |
### 根本原因 (按"系统性问题"框架)
1. **架构 review 缺失**:首次设计 `fdw_queries.py` 走 fdw_etl 模式时,**没人对 RLS + FDW 的兼容性做技术验证**。这是 02-26 P1-P2-P3 全栈集成期就埋下的雷,直到 03-18 E2E 才爆。
2. **AI 自主权过大 (Kiro Autopilot)**:H2 审计原文 "AI 自行决定架构级修改"。当时的工作流允许 AI 在测试失败时直接改架构,缺少 "GUC 透传 → 用户确认 → 方案对比" 的强制中断点。
3. **MVP 思维优先于规范**:RNS1 系列 spec 由 AI 基于 design.md 生成,**design.md 假设 RLS 会自动跨 FDW 生效**(这是错的),没有在 spec 阶段验证数据库 schema(审计"系统性问题 1:AI 信任文档胜过信任数据库")。
4. **回滚方案存在但被刻意保留**:H2 审计末尾的"回滚方案"章节明确写了"回滚后 RLS 门店隔离将再次失效",所以**当时已经知道要回到 FDW 必须先解决 GUC 问题**,只是没人去做。
5. **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 | 高 | 必须 | (不推荐) |
### 推荐顺序
1. **首选 C (维持现状 + 补充文档)** — 因为:
- "符合工程规范"的成本很高 (B-2/B-3 需要 6-10h + RLS 跨库测试)
- 当前架构是**有真实根因的合理选择**,不是技术债 — 它解决了 postgres_fdw 不透传 GUC 的真实问题
- 把"为什么直连"的设计决策写进 backend CLAUDE.md / docs/architecture/backend-architecture.md,后续读代码不会再困惑
- 把 fdw_etl schema 的真实角色 (FDW 备份 / 当前不被后端代码使用) 也写明
2. **次选 B-2** — 如果 Neo 坚持工程一致性大于成本,推荐 B-2 (写 SECURITY DEFINER function)。但需要:
- 用 1 周时间在 test_zqyy_app + test_etl_feiqiu 上做完整 RLS 跨库回放
- 验证 4 个文件全切回 FDW 的正确性
- 用 e2e_test_rns1.py 全跑通
3. **不推荐 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` |
---
## 九、调研中发现的其他问题
1. **`fdw_queries.py` 模块名是历史包袱**:文件名叫 fdw_queries 但实际不走 FDW,**与文件功能严重不符**。后续重构时应改名为 `etl_queries.py` / `etl_views.py`。低优先级。
2. **`_fdw_context` 函数名同样误导**:它是 ETL 直连 + GUC 设置的封装,不是 FDW 上下文。改名建议:`_etl_rls_context` / `_etl_session_with_site`
3. **`db/fdw/setup_fdw.sql` 仍是生产部署脚本之一**:运维如果按文档跑了这个脚本,得到的 fdw_etl schema 当前完全不被后端使用,但表占用元数据空间。运维侧需要明确"这是备份,不影响功能"。
4. **审计文件证据链非常完整**:这次调研能在 1 小时内还原决策 — 全靠 `2026-03-18` / `2026-03-20` 三份审计的明确记录。这是 NeoZQYY 审计文化的正面案例,值得维持。
5. **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)。