# P0-5 工程规范一致性全览

> 调研时间:2026-05-04
> 触发:Neo 在 P0-5 反馈"matching.py 直连 ETL 偏离规范"基础上提出 — **找到全项目类似情况,给个全览,并制定工程规范化和一致性的实施方案**
> 关联:`04a-feedback/P0-5-matching-evolution.md`(本调研为其下游)
> 性质:**只调研不实施**,产物为治理路线图

---

## 一、规范基线清单(共 14 条)

来源:CLAUDE.md(根 / 子模块) + 已知 spec / BD 手册 + 历史审计。

| # | 规范 | 来源 | 关键判定 |
|---|---|---|---|
| R1 | 业务库通过 FDW 只读访问 ETL,不直连 ETL 库 | `apps/backend/CLAUDE.md` "ETL 数据通过 FDW 映射的 app.v_* RLS 视图访问" | 后端不应直接 `psycopg2.connect` 到 `etl_feiqiu` |
| R2 | DWS / 取数禁用 `consume_money`,统一用 `items_sum`(`ledger_amount`) | feiqiu CLAUDE.md / DWD-DOC 规则 1 | SELECT / 计算 / 返回字段不应包含 `consume_money` |
| R3 | DWD/DWS 视图必须双 schema(原 schema + app schema 同步建) | `db/CLAUDE.md` RLS 双 schema 模板 | 后端可走 `app.v_*`,但 `dws.v_*` 必须存在等价镜像 |
| R4 | 后端响应统一通过 ResponseWrapperMiddleware 包装 | `apps/backend/CLAUDE.md` | 不应有路由用 `JSONResponse`/`response_class` 显式绕过 |
| R5 | JWT 三类 aud(`admin`/`miniapp`/`tenant-admin`)严格隔离 | `apps/backend/CLAUDE.md` JWT 双认证表 | 签发与校验都必须设置/校验 `aud` 字段 |
| R6 | AI 应用调用走 dispatcher 调度,不直接调 DashScope SDK | `apps/backend/CLAUDE.md` AI 集成 | 业务路由不应自行构造 `DashScopeClient` 调 `Application.call` |
| R7 | 配置分层 .env < .env.local < 环境变量 < CLI 参数,禁止生产代码硬编码主机/凭据 | 根 CLAUDE.md | 业务源码不应出现硬编码 IP/URL/密钥 |
| R8 | 测试不连生产库,用 `TEST_*_DSN`/`load_dotenv` 加载根 .env | 根 CLAUDE.md "测试与验证环境规范" | 测试不应引用 `PG_DSN`/`APP_DB_DSN` |
| R9 | 小程序参数命名 camelCase 对外、snake_case 内部 | 通用约定 | 后端 SnakeCase → CamelModel 自动转,前端取 camelCase |
| R10 | 审计记录全部归 `docs/audit/changes/`,不写入子模块 | 根 CLAUDE.md "文件归属规则" | 子模块下不应出现 `docs/audit/` |
| R11 | `_archived/` 目录禁止读取或参考 | 根 CLAUDE.md | 不应被 grep/read,不应被新代码引用 |
| R12 | `apps/demo-miniprogram/`(MOCK 标杆)禁改 | 根 CLAUDE.md | 30 天内不应有 commit 触及 |
| R13 | shared 包跨子项目共享 enums/money/datetime_utils | 根 CLAUDE.md / 推断 | 子项目不应重复定义同名工具 |
| R14 | DWD/DWS 字段命名约定:ETL 用 `tenant_member_id` / `site_assistant_id`,业务可用 `member_id` / `assistant_id`(已映射) | feiqiu CLAUDE.md `fdw_queries.py:48-58` | 跨层混用应有显式映射注释 |

---

## 二、偏离点全集(按规范分组)

### 2.1 R1 · FDW / 跨库访问

**关键背景**:H2(2026-03-20)规范变更后,业务后端"直连 ETL 库 + RLS 视图"已成为**事实标准**(`fdw_queries._fdw_context()` 模式)。R1 当前的写法"业务库通过 FDW 访问 ETL"在 `apps/backend/CLAUDE.md` 中实际已过期,应在治理时同步修订。但即便按事实标准衡量,仍存在多处偏离。

#### 偏离点 1 — `apps/backend/app/services/matching.py:62`(P0-5 主体)

- 现状:用 `_fdw_context(None, site_id)` 直连 ETL,**未传入业务库 conn**,`bd_str` 降级为系统今天而非 RuntimeContext.business_date
- 偏离类型:绕过 RuntimeContext 透传(P0-5 选项 B 主张"补 FDW 外部表"消除直连)
- 影响:沙箱模式下日期上界裁剪失效(若 site 启用沙箱,匹配读到未来助教/员工)

#### 偏离点 2 — `apps/backend/app/database.py:175 get_etl_write_connection()` + `services/task_generator.py:1107`

- 现状:后端**写入** ETL 关系指数表(`get_etl_write_connection()` 是可写连接,无 RLS,task_generator 直接执行 INSERT)
- 偏离类型:**严重违反 R1**(规范明确"FDW 只读访问 ETL")
- 影响:业务后端越权写 ETL 库,绕过 ETL Loader 流程,数据所有权混乱

#### 偏离点 3 — `apps/backend/app/database.py:109 get_etl_global_readonly_connection()` + `routers/etl_status.py:44`

- 现状:全局只读连接,**不设置 `app.current_site_id`**,直连 ETL 库读全局状态
- 偏离类型:绕过 RLS 隔离(规范前提"直连必带 RLS")
- 影响:潜在跨门店数据泄露(若被业务路由误调)

#### 偏离点 4-7 — H2 改造遗漏的"伪 FDW"代码(实际必坏)

| 文件 | 行号 | 现状 | 严重度 |
|---|---|---|---|
| `routers/tenant_users.py` | 425, 450 | `etl_conn = get_etl_readonly_connection(site_id)` 后 SQL 写 `FROM fdw_etl.v_dim_assistant/v_dim_staff` | **P0 — 必失败** |
| `routers/tenant_excel.py` | 390, 407 | 同上,SQL 写 `fdw_etl.v_dim_assistant/v_dim_staff` | **P0 — 必失败** |
| `routers/tenant_clues.py` | 113, 119 | 同上,SQL 写 `fdw_etl.v_dim_member` | **P0 — 必失败** |

**致命点**:这些查询连接的是 `etl_feiqiu` 库,而 `fdw_etl` schema 只存在于 `zqyy_app` 业务库 → 必报 `schema "fdw_etl" does not exist` → 当前被 `try/except` 静默吞,接口表面正常但**永远返回空列表**。这是 P0-5 同类(H2 改造遗漏)的高危偏离。

---

### 2.2 R2 · `consume_money` 字段

#### 偏离点 8 — `apps/backend/app/services/fdw_queries.py:1173/1208/1226`

- 现状:`get_member_consumption_orders()` SELECT 仍包含 `sh.consume_money`,GROUP BY 也带,字典返回 `"consume_money": float(...)` 给前端
- 偏离类型:R2"DWS 取数禁用 consume_money"违反
- 用途反查:`customer_service.py:385` 用于"原价/折扣价"对比展示(原价 = consume_money,实付 = total_amount)
- 评估:**业务有真实需求**(展示"划线价"),但取自 DWD 原始 consume_money 仍属于规范偏离 → 应在 BD 手册更新口径或加 view 层封装

#### 偏离点 9 — `apps/etl/connectors/feiqiu/tasks/utility/dws_build_order_summary_task.py:23`

- 现状:用 `(COALESCE(sh.consume_money, 0) = 0 AND COALESCE(sh.pay_amount, 0) > 0) AS recharge_order_flag` 识别充值订单
- 偏离类型:R2 在判定逻辑中使用 consume_money(非金额计算,但仍是该字段)
- 评估:**判定语义合理**(充值单消费金额必为 0),但应文档化为"R2 例外:仅作零值判定,不参与金额计算"

---

### 2.3 R3 · DWS 双 schema 视图

#### 偏离点 10 — DWS schema 仅暴露 4 个视图,app schema 暴露 50+(规模性偏离)

`db/etl_feiqiu/schemas/dws.sql` 中 `dws.v_*` 仅 4 条:
- `dws.v_dws_coach_area_hours`(L1206)
- `dws.v_dws_finance_area_daily`(L1227)
- `dws.v_dws_finance_board_cache`(L1267)
- `dws.v_member_recall_priority`(L1292)

`db/etl_feiqiu/schemas/app.sql` 中 `app.v_dws_*`/`app.v_dwd_*`/`app.v_dim_*`/`app.v_cfg_*` 共约 **50 条**。

DWS 基表共有 38 张(L46-L1027),意味着 ~34 张表只在 app schema 暴露 RLS 视图,违反 R3"双 schema"。

- 偏离类型:**R3 规模性违反**
- 影响:运维直查 `dws` schema 时只能看到原始基表(无门店过滤),易出错;新建 RLS 视图必须双 schema 的规则反复被忽视
- 历史标记:`memory/MEMORY.md` 提到"踩坑 2026-03-29 DWS RLS 双 schema 强制规则"已存在,但执行不严

---

### 2.4 R4 · ResponseWrapperMiddleware

#### 评估:无显式偏离,中间件设计良好

`app/middleware/response_wrapper.py:33-37` 中间件已自动跳过:
- `text/event-stream`(SSE)
- 非 `application/json`
- 非 2xx

`xcx_chat.py:344` / `tenant_excel.py:954` 用 `StreamingResponse`(SSE / 文件下载),由中间件自动跳过包装,**不算偏离**。

---

### 2.5 R5 · JWT aud 严格隔离

#### 偏离点 11 — `apps/backend/app/auth/jwt.py:42-50` admin/miniapp 签发**完全不带 `aud` 字段**

```python
payload = {
    "sub": str(user_id),
    "site_id": site_id,
    "type": "access",
    "exp": expire,
}  # 无 aud!
```

只有 `tenant_auth.py:69/93` 显式设置 `"aud": "tenant-admin"`。意味着:
- admin-web 与小程序 token 在 payload 层**无法区分**
- `auth/dependencies.py:124 decode_access_token()` 也**没校验 aud**
- 结果:小程序 token 理论上可被用于 admin 端点(若仅依赖 `decode_access_token` + roles 检查,roles 分类不严就破防)

- 偏离类型:**P0 — 安全性偏离 R5**
- 影响:跨端横向越权风险
- 修复成本:中(需小程序/admin-web 同步重新签发 token + 后端 verify 接受多 aud)

---

### 2.6 R6 · AI 应用走 dispatcher

#### 偏离点 12 — 多处直接构造 `DashScopeClient` 调用

| 文件 | 行号 | 用途 |
|---|---|---|
| `app/main.py` | 145 | lifespan 内全局单例(合理,作为 dispatcher 注入源) |
| `app/services/chat_service.py` | 734 | `_get_dashscope_client()` 工厂 + `:644` 直接 SSE 流式调用百炼 |
| `app/services/note_service.py` | 71 | 单点客户端实例化(走 App6 直接调用) |
| `app/routers/xcx_chat.py` | 408 | 同上,`xcx_chat.py:203` SSE 直接调 |

- 偏离类型:R6 部分违反
- 评估:**SSE 流式回复天然不能走 dispatcher 的 run_step**(dispatcher 设计是同步整段 reply),目前实现合理。但 `note_service` 直调 App6 应改走 dispatcher
- 影响:绕过熔断/限流/预算追踪,但 client 内部已有部分保护
- 修复成本:小(note_service 改走 dispatcher;chat_service SSE 路径标记为合理偏离)

---

### 2.7 R7 · 配置硬编码

#### 偏离点 13 — `apps/etl/connectors/feiqiu/config/defaults.py:30`

- 现状:`"base_url": "https://pc.ficoo.vip/apiprod/admin/v1"` 硬编码飞球 API
- 评估:作为 default 合理,允许 .env 覆盖;**不算严格偏离**

#### 偏离点 14 — `apps/etl/connectors/feiqiu/scripts/refresh_json_and_audit.py:22` / `full_api_refresh_v2.py:27`

- 现状:`API_BASE = "https://pc.ficoo.vip/apiprod/admin/v1/"` 模块顶层硬编码,无 env 读取分支
- 偏离类型:R7 违反
- 影响:迁移到飞球新域名/沙箱时改不动;一次性脚本性质风险低
- 修复成本:小

#### 偏离点 15 — `apps/etl/connectors/feiqiu/orchestration/flow_runner.py:255`

- 现状:`backend_url = os.getenv("BACKEND_API_URL", "http://127.0.0.1:8000")` — fallback 到本机
- 评估:**合理**(开发期默认),且有 env 覆盖

#### 偏离点 16 — `apps/admin-web/vite.config.ts:11` 与 `playwright.config.ts:21`

- 现状:proxy target / playwright baseURL 硬编码 `localhost:8000` / `localhost:5173`
- 评估:**合理**(开发工具配置,非生产代码)

---

### 2.8 R8 · 测试不连生产库

#### 偏离点 17 — `apps/backend/tests/tests/unit/test_backfill_script.py:29`

- 现状:`with patch.dict("os.environ", {"APP_DB_DSN": "postgresql://test:test@localhost/test"})` — 用 `APP_DB_DSN` 而非 `TEST_APP_DB_DSN`
- 偏离类型:R8 违反(变量名错误,虽然值是假 DSN)
- 影响:命名混淆,易让人以为生产 DSN 也能用;真跑会因 patch 顺序问题潜在风险
- 修复成本:极小

#### 偏离点 18 — 测试 `load_dotenv` 检查

`apps/backend/tests/tests/integration/` 下两个 e2e 测试 import `load_dotenv`,实际加载根 `.env`(包含 PG_DSN/APP_DB_DSN),依赖 cwd 决定加载哪个 .env。如果集成测试在 backend cwd 跑,会加载 `apps/backend/.env.local`(其内 `CORS_ORIGINS=http://localhost:5173` 但**未必有 TEST_*_DSN 覆盖**) → 风险:误连生产库
- 偏离类型:R8 潜在违反
- 修复成本:中(需 conftest 强制注入 TEST_*_DSN 校验)

---

### 2.9 R9 · 命名风格

#### 偏离点 19 — 小程序 `task-list.ts:586,628,647` 内部混用 camelCase / snake_case

```ts
const memberId = (target as any).memberId ?? (target as any).member_id  // L586
const userId = (authUser as any).userId  // L628
```

后端 CamelModel 应自动转成 camelCase 输出,小程序兜底读 snake_case 表明历史上**后端字段未走 CamelModel**。需逐字段确认:
- 是否后端 `xcx_*` 路由全部走 `CamelModel.model_dump(by_alias=True)`?
- 现状部分 service 直接 `dict` 返回(如 `tenant_users.py:441 .model_dump(by_alias=True)` 写了,但 `routers/xcx_*` 是否一致?)

- 偏离类型:R9 部分违反(已知 P1-9 反馈)
- 修复成本:中

#### 偏离点 20 — `member_id` vs `tenant_member_id` 混用

ETL 库 DWD 字段名 `tenant_member_id`(`fdw_queries.py:48` 注释明确),业务 API 对外是 `member_id`。
fdw_queries.py 内 SELECT/JOIN 都正确映射(`tenant_member_id = %s` → 返回 `member_id`),小程序 API 用 `memberId`。
- 评估:**已有显式映射**,符合 R14(DWD 字段命名约定),不算偏离;但映射散落在 200+ 行,易遗漏

---

### 2.10 R10 · 审计文件归属

#### 偏离点 21 — `apps/backend/tests/tests/_archived/`

- 现状:子模块测试目录下有 `_archived/`(test_ai_app2.py / test_ai_apps_prompt.py / test_ai_clue_writer.py)
- 偏离类型:**R11 + R10 违反**(子模块下不应有 `_archived/`,此为旧 Cursor 时代的产物)
- 修复成本:小(整体移到根 `_DEL/` 或者评审后删除)

#### 偏离点 22 — `apps/backend/tests/tests/` 嵌套异常

- 现状:`apps/backend/tests/` 与 `apps/backend/tests/tests/` **同时存在并各有重复测试文件**(test_auth_jwt.py 出现在两层)
- 偏离类型:**目录结构混乱 / 历史迁移残留**
- 影响:pytest 重复执行 / 修改时不知改哪个版本
- 修复成本:小(取舍后删一份)

#### 偏离点 23 — `apps/etl/connectors/feiqiu/tests/unit/unit/`

- 现状:`tests/unit/unit/` 三层嵌套,任务测试文件有 task_test_utils.py 同名重复
- 偏离类型:同 22
- 修复成本:小

#### 评估:`apps/etl/connectors/feiqiu/scripts/audit/`

- 现状:模块内 `scripts/audit/` 是审计**工具**(scanner.py / inventory_analyzer.py 等),不是审计记录
- 评估:**不算偏离**(R10 限定的是审计记录,工具放模块内合理)

---

### 2.11 R11/R12 · `_archived/` 与 `demo-miniprogram/`

#### 偏离点 24 — demo-miniprogram 30 天内被修改

git log 显示:
- `f2e0de8`(2026-05-02 反向迁移) — 改 `apps/demo-miniprogram/AGENTS.md`
- `81e4173`(2026-04-29) — 改同文件
- `2a7a5d6`(2026-04-15~20) — 改 `project.miniapp.json` / `project.private.config.json`
- 偏离类型:**R12 违反**(MOCK 标杆原则上禁改)
- 评估:`AGENTS.md` 是 AI 环境文件,反向迁移期间清理可理解;`project.miniapp.json` 改动需要审视

#### `_archived/` 目录读取

`pre_read_archived_block.py` hook 已强阻断,无新违反

---

### 2.12 R13 · shared 包共用

未发现明显偏离。`packages/shared/` 提供 enums/money/datetime_utils,各子项目正常 import,无重复定义。

---

## 三、偏离点严重度矩阵

| # | 偏离点 | 规范 | 原因 | 影响范围 | 修复成本 | 优先级 | 风险 |
|---|---|---|---|---|---|---|---|
| 1 | matching.py 不传 conn | R1 | 历史 H2 改造速度优先 | 单点 | 小 | Wave 1 | 中 |
| 2 | get_etl_write_connection + task_generator 写 ETL | R1 | 紧急修复(关系指数回写) | 模块 | 中 | Wave 2 | 高 |
| 3 | get_etl_global_readonly_connection 无 RLS | R1 | 设计偏好(系统监控) | 单点 | 小 | 长期 | 低 |
| 4-7 | tenant_users/excel/clues 残留 fdw_etl.* SQL(必坏) | R1 | H2 改造遗漏 | 模块 | 小 | **立即(P0)** | **高** |
| 8 | fdw_queries 返回 consume_money 给前端 | R2 | 业务划线价需求 | 单点 | 中 | Wave 3 | 中 |
| 9 | dws_build_order_summary 用 consume_money 判定 | R2 | 充值单零值判定 | 单点 | 极小 | 文档化 | 低 |
| 10 | DWS schema 30+ 视图缺双 schema 镜像 | R3 | 不知规范 / 历史遗留 | **全局** | 大 | Wave 5 | 中 |
| 11 | jwt.py 不带 aud,decode 不验 aud | R5 | 设计偏好(单 secret) | **全局** | 中 | **立即(P0)** | **高** |
| 12 | note_service 直调 DashScopeClient | R6 | 紧急功能 | 单点 | 小 | Wave 2 | 低 |
| 13-14 | 飞球 API 地址硬编码 | R7 | 一次性脚本 | 单点 | 极小 | 长期 | 低 |
| 17 | test_backfill_script 用 APP_DB_DSN | R8 | 命名错误 | 单点 | 极小 | 立即 | 低 |
| 18 | 集成测试 .env 加载顺序 | R8 | 配置不严 | 模块 | 中 | Wave 1 | 中 |
| 19 | 小程序 camelCase/snake_case 混用兜底 | R9 | 历史 P1-9 已知 | 模块 | 中 | Wave 3 | 低 |
| 21 | tests/tests/_archived/ | R10/R11 | 历史迁移残留 | 单点 | 极小 | 立即 | 低 |
| 22 | tests/tests/ 嵌套重复 | R10 | 历史迁移残留 | 模块 | 小 | 立即 | 中 |
| 23 | feiqiu tests/unit/unit/ 嵌套 | R10 | 历史迁移残留 | 模块 | 小 | 立即 | 低 |
| 24 | demo-miniprogram 30 天内改动 | R12 | AGENTS.md 迁移 | 单点 | 0 | 文档化(可接受) | 低 |

**统计**:24 个偏离点,其中 **P0 立即** 4 类(偏离 4-7、11、17、21、22、23),Wave 协同 5 类(1、2、8、12、18、19),长期 2 类(3、10、13-14),可接受文档化 3 类(9、20、24)。

---

## 四、统一治理方案

### 4.1 立即治理项(D-Bug 类,数据/安全风险)

#### 治理 A · 修复 4 处必坏的 fdw_etl.* 残留(对齐 P0-5 主体)

| 文件 | 行 | 改法 |
|---|---|---|
| `routers/tenant_users.py:431` | `fdw_etl.v_dim_assistant` | → `app.v_dim_assistant` |
| `routers/tenant_users.py:456-457` | `fdw_etl.v_dim_staff` / `v_dim_staff_ex` | → `app.v_dim_staff` / `app.v_dim_staff_ex` |
| `routers/tenant_excel.py:394` | `fdw_etl.v_dim_assistant` | → `app.v_dim_assistant` |
| `routers/tenant_excel.py:411` | `fdw_etl.v_dim_staff` | → `app.v_dim_staff` |
| `routers/tenant_clues.py:119` | `fdw_etl.v_dim_member` | → `app.v_dim_member` |

后续应改用 `_fdw_context()` 而非裸 `get_etl_readonly_connection`(统一拥有 business_date GUC)。

#### 治理 B · JWT aud 标准化(R5)

1. `auth/jwt.py:42` payload 增 `"aud": "miniapp"`(小程序签发)
2. 新建 `auth/admin_jwt.py` 给 admin-web 签发,设 `"aud": "admin"`
3. `dependencies.py:124 decode_access_token` 接受 `audience` 参数,FastAPI 依赖按路由前缀分流
4. 测试覆盖:miniapp token 调 admin 端点必须 401,反之亦然

#### 治理 C · 测试目录单轨化

| 操作 |
|---|
| `apps/backend/tests/tests/_archived/` → 评审后删除或移到根 `_DEL/` |
| `apps/backend/tests/tests/` 与 `apps/backend/tests/` 合并(保留较新版本) |
| `apps/etl/connectors/feiqiu/tests/unit/unit/` 同上 |
| `test_backfill_script.py:29` 把 `APP_DB_DSN` 改为 `TEST_APP_DB_DSN` |

---

### 4.2 Wave 协同项

| Wave | 关联偏离 | 治理措施 |
|---|---|---|
| Wave 1(matching FDW 重建) | 偏离 1、18 | P0-5 选项 B 实施时,顺带把 `_fdw_context` 改为 `must` 接 conn,集成测试 conftest 强制注入 TEST_*_DSN |
| Wave 2(关系指数 / AI 流水线) | 偏离 2、12 | task_generator 关系指数回写 ETL → 改走 ETL 内置 task(由 ETL 主动消费 biz 的关系信号);note_service 改走 dispatcher |
| Wave 3(对外 API 一致性) | 偏离 8、19 | 引入 `view_dwd_order_with_orig_price` 封装 consume_money 划线逻辑,前端字段统一 camelCase 强校验 |
| Wave 5(数据库治理) | 偏离 10 | 编写 `tools/db/check_dws_dual_schema.py` 扫描 dws 基表 vs `dws.v_*` 视图差集,补齐 30+ 镜像;CI 加守护测试 |

### 4.3 长期治理项

#### L1 · 修订 R1 文字(`apps/backend/CLAUDE.md`)

事实上 H2 后已统一直连 ETL,规范文字应改为:

> ETL 数据通过 `_fdw_context(conn, site_id)` 直连 ETL 库 + RLS 视图访问;`fdw_etl.*` 外部表已废弃,新代码禁用。

#### L2 · 编写 `scripts/audit/check_engineering_consistency.py`

零 token 静态扫描脚本,检查项:
1. grep `fdw_etl\.` 在 `apps/backend/app/` 下应零结果(白名单注释除外)
2. grep `audience=` 在 jwt 签发函数应非空
3. grep `consume_money` 在 backend service / ETL 任务中应有 BD 手册引用
4. 双 schema 视图差集(对比 `dws.v_*` 与 `app.v_dws_*`)
5. 测试目录嵌套 / `_archived/` 子模块违规
6. `localhost`/`127.0.0.1` 在生产源码白名单外

接入 `scripts/audit/prescan.py`,合并到 `/audit` 流程

#### L3 · DWS 双 schema 历史补齐

约 30+ 个 `app.v_dws_*` 视图需补 `dws.v_dws_*` 镜像,生成迁移文件 `db/etl_feiqiu/migrations/2026-XX-XX__dws_dual_schema_backfill.sql`,逆序 DROP 写入回滚。

### 4.4 可接受偏离项(文档化归档)

| 偏离 | 理由 | 文档化位置 |
|---|---|---|
| 9 (`dws_build_order_summary` 用 consume_money 零值判定) | 仅作零值标记,不参与金额计算,业务语义清晰 | `apps/etl/connectors/feiqiu/CLAUDE.md` 增"R2 例外清单" |
| chat_service SSE 直调 DashScopeClient | 流式 reply 无法走 dispatcher run_step 抽象 | `apps/backend/CLAUDE.md` 增"R6 例外:SSE 流式直连" |
| 20 (member_id ↔ tenant_member_id 映射) | 已在 fdw_queries.py 集中映射,无散点违反 | 已在 `fdw_queries.py:48-58` 注释 |
| 24 (demo-miniprogram AGENTS.md) | 反向迁移产物,与代码逻辑无关 | 在 `CLAUDE.md R12` 加"例外:AGENTS.md / project config 文件可随迁移调整" |

---

## 五、CI / 自动化校验建议

### 5.1 静态规则(零 token)

| 检查 | 工具 | 失败动作 |
|---|---|---|
| `fdw_etl\.` 在 `apps/backend/app/` | grep + CI | 拒绝合并 |
| 签发 JWT 必带 `aud` 字段 | `tools/audit/check_jwt_aud.py` | 拒绝合并 |
| dws 基表 vs `dws.v_*` 视图差集 | `tools/db/check_dws_dual_schema.py` | warning(>0 个差集) |
| 测试 `from app.config import APP_DB_DSN` 等违规导入 | grep | 拒绝合并 |
| `_archived/` 子模块嵌套 | find / glob | 警告 |

### 5.2 提交期 hook

新增 `.claude/hooks/pre_commit_engineering_consistency.py`:
- pre-commit 阶段调上述静态扫描脚本
- 命中即阻断并打印偏离点 + R 编号

### 5.3 周期性体检

`scripts/audit/weekly_consistency_report.py`:
- 每周生成本表(同此文档结构)
- 输出到 `docs/audit/engineering_consistency_<YYYY-MM-DD>.md`
- diff 比上周新增/消除的偏离

---

## 六、给 Neo 的决策清单

按优先级倒序请 Neo 决策:

### 决策 D1(立即 / 高风险)

- [ ] **是否同意 P0 治理 A**:修复 4 处 `fdw_etl.*` 残留(`tenant_users/excel/clues`),改为 `app.v_*`?**这是真 bug,生产环境必坏,目前被 try/except 静默吞**。
- [ ] **是否同意 P0 治理 B**:JWT aud 标准化(jwt.py 加 `aud`,decode 加 audience 校验)?需后端+小程序+admin-web 三端协同发版。
- [ ] **是否同意 P0 治理 C**:删除 `apps/backend/tests/tests/_archived/`、合并 `tests/tests/` 嵌套?

### 决策 D2(随 Wave 协同)

- [ ] Wave 1 P0-5 选项 B 实施时,是否一并把 `_fdw_context(None, ...)` 改为强制传 conn?(避免 business_date 降级)
- [ ] Wave 2:task_generator 写 ETL 关系指数表 → 改为"ETL 主动消费 biz 信号"还是保留 `get_etl_write_connection`?后者需在 R1 中明确"例外清单"。

### 决策 D3(长期治理)

- [ ] DWS 双 schema 30+ 视图历史补齐 → 何时启动?(估计需要 1-2 个工作日)
- [ ] 是否增加 `.claude/hooks/pre_commit_engineering_consistency.py` 提交期阻断?
- [ ] 周期性体检报告 → 接入 `/audit` 还是独立 weekly?

### 决策 D4(规范文字修订)

- [ ] R1 在 `apps/backend/CLAUDE.md` 是否改写为"H2 后规范"(直连 ETL + RLS,弃用 fdw_etl.*)?
- [ ] R2 是否在 feiqiu CLAUDE.md 增"零值判定例外清单"?
- [ ] R6 是否增"SSE 流式直连例外"?

---

## 附录 A · 调研覆盖范围与方法

| 维度 | 覆盖工具 | 命中数 |
|---|---|---|
| FDW / 跨库 | grep `get_etl_*_connection`、`fdw_etl\.`、`_fdw_context` | 19 文件 |
| 响应包装 | grep `JSONResponse` / `response_class` / `StreamingResponse` | 2 文件(SSE 合理) |
| JWT aud | grep `audience=` / `aud['"]` / `verify_token` | 4 文件 |
| AI 调用 | grep `DashScopeClient(` / `dashscope.` | 4 直调点 |
| RLS 双 schema | grep `^CREATE.*VIEW (dws|app)\.` | dws 4 / app 50 |
| consume_money | grep | 9 处 |
| 配置硬编码 | grep `localhost` / `127.0.0.1` / `https?://` | 18 行 |
| 测试连库 | grep `PG_DSN` / `APP_DB_DSN` / `TEST_*_DSN` | 1 单测违规 |
| 命名混用 | grep 小程序 `member_id` / `memberId` / `userId` | 20 文件 |
| 审计/归档 | ls 子模块 docs/audit、`_archived/` | 3 处嵌套违规 |
| demo-miniprogram | git log 30 天 | 4 commits |

未覆盖维度(后续补):
- shared 包重复定义检查(R13)
- xcx_* 路由是否全部走 CamelModel(R9 深度抽样)
- ETL DWD 12 条强制规则(feiqiu CLAUDE.md)逐项审计

---

## 附录 B · 与既有反馈交叉引用

| 本文偏离 | 关联反馈 | 关联文件 |
|---|---|---|
| 偏离 1 | P0-5 反馈主体 | `04a-feedback/P0-5-matching-evolution.md` |
| 偏离 11 | (新发现 / 未在 P0/P1 列表) | — |
| 偏离 19/20 | P1-9 散落 memberId | `04b-feedback/P1-12-scattered-memberid.md` |
| 偏离 4-7 | (新发现 / 高危) | — |
| 偏离 10 | 已知"DWS RLS 双 schema 踩坑"(memory 2026-03-29) | `MEMORY.md` |

---

(全文 ~570 行)
