Files
Neo-ZQYY/docs/_overview/04b-feedback/P1-1-schema-migration-risk.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

27 KiB
Raw Blame History

P1-1 维客线索 schema 迁移public → biz风险评估

日期2026-05-04 触发Neo 在 04b-conflicts-P1-detail.md § P1-1 反馈选 A迁移到 biz保证项目工程的规范性要求单独起任务调研风险评估 调研范围:测试库 test_zqyy_app + 仓库内全部代码 / DDL / FDW / SPEC / 后端 / 小程序 产出形态:调研 + 实施方案推荐,不动任何代码或 DB


一、依赖盘点结果

1.1 总览

按 grep 结果(member_retention_clue / retention_clue)统计涉及面:仓库内 85 个文件命中。其中会因为 schema 迁移真正受影响的"代码 + DDL + FDW"清单如下(文档 SPEC 仅需更新文字,不阻塞迁移)。

1.2 业务库 DDLdb/zqyy_app/

文件 关键内容 改动类型
db/zqyy_app/schemas/public.sql 序列 public.member_retention_clue_id_seq、表 public.member_retention_clue、3 个索引、1 个 PK 删除
db/zqyy_app/schemas/biz.sql 当前不含此表 新增表 + 序列 + 索引
db/zqyy_app/migrations/ 当前空v1 已归档) 新增迁移脚本
db/_archived/migrations_v1_merged/zqyy_app/2026-03-20__ns4_member_clue_is_hidden.sql 历史迁移(已归档),不动 不改(仅文档说明)

1.3 FDW 反向映射(db/fdw/

文件 关键内容 改动类型
db/fdw/setup_fdw_reverse.sql CREATE FOREIGN TABLE fdw_app.member_retention_clue ... OPTIONS (schema_name 'public', table_name 'member_retention_clue') schema_name 'biz'
db/fdw/setup_fdw_reverse_test.sql 同上(指向 test_zqyy_app schema_name 'biz'
docs/database/ddl/fdw_reverse.sql 自动生成的 DDL 镜像 重新生成
docs/database/BD_Manual_fdw_reverse_retention_clue.md FDW 文档 全文更新 schema 引用

FDW 外部表名 fdw_app.member_retention_clue 不需改名,只需把 OPTIONS.schema_name'public' 改成 'biz',对 ETL 侧调用无破坏(外部表名不变)。

1.4 后端代码(apps/backend/app/)— 共 6 处 SQL 直引

文件 SQL 引用形态 改动
routers/member_retention_clue.py 29 / 68 / 87 无 schema 前缀(依赖 search_path biz. 显式前缀
routers/tenant_clues.py 67 / 197 / 235 / 267 / 298 public.member_retention_clue 硬编码 全改 biz.member_retention_clue
services/customer_service.py 271 / 278 public.member_retention_clue 硬编码 biz.
services/task_manager.py 1125 public.member_retention_clue 硬编码 biz.
ai/data_fetchers/page_context.py 241 无 schema 前缀,且字段名错为 created_at(应为 recorded_at biz. 前缀,附带修 created_at → recorded_at Bug
ai/dispatcher.py 574 / 588 App8 写入:无 schema 前缀DELETE + INSERT biz. 前缀

总计后端硬编码点6 文件 / 11 处 SQL(与冲突卡估计的 5-8 处一致,略多)。

1.5 后端 Pydantic / Schema 层

文件 关注点
apps/backend/app/schemas/member_retention_clue.py 无 schema 名硬编码;但枚举 ClueCategory 仍使用旧值 客户基础信息,与 BD 手册 2026-03-08 对齐后的 客户基础 不一致(独立 Bug登记不在本任务
apps/backend/app/schemas/tenant_clues.py grep 命中文档字符串,无 schema 引用
apps/backend/app/schemas/xcx_tasks.py / xcx_customers.py grep 命中文档字符串,无 schema 引用

1.6 小程序前端(apps/miniprogram/

仅命中 retentionClues / retention_clues 字段名,与 schema 无关

  • typings/api.d.tsL112 / L209API 响应类型
  • pages/customer-detail/customer-detail.ts:122pages/task-detail/task-detail.{ts,wxml}:页面渲染
  • utils/mock-data.ts:57MOCK 数据

结论:小程序零改动。

1.7 租户管理后台前端(apps/tenant-admin/

grep 命中 0 处。零改动。

1.8 ETL Connectorapps/etl/connectors/feiqiu/

grep 命中 0 处。ETL 侧零改动FDW 外部表名 fdw_app.member_retention_clue 由 FDW 文件维护)。

1.9 文档 / SPEC仅文字同步不阻塞迁移

文件 内容
docs/database/BD_Manual_member_retention_clue.md 9 处 publicbiz
docs/database/BD_Manual_fdw_reverse_retention_clue.md 5 处 schema 引用 + 数据流向图
docs/database/BD_Manual_biz_tables.md public.member_retention_cluebiz.member_retention_clue
docs/database/ddl/zqyy_app__public.sql / ..__biz.sql 重新生成(tools/db/gen_consolidated_ddl.py
docs/database/README.md schema 索引
docs/specs/tenant-admin-web/{requirements,design,tasks}.md 7 处
docs/specs/rns1-task-performance-api/*.md 4 处
docs/specs/rns1-customer-coach-api/*.md 4 处
docs/specs/05-miniapp-ai-integration/design.md 1 处
docs/prd/specs/00-数据依赖矩阵.md 3 处
docs/prd/specs/P10-tenant-admin-web.md 1 处
docs/prd/specs/P14-ai-dashscope-migration.md 1 处
docs/prd/specs/board-detail-gap-analysis.md 1 处
docs/prd/Neo_Specs/RNS1-split-plan.mdNS1NS2NS4review-audit/ 多处
docs/_overview/01-product-overview.md § 八冲突登记
docs/_overview/04b-conflicts-P1-detail.md 冲突卡
docs/contracts/openapi/backend-api.json 注释字段
docs/architecture/backend-architecture.md L118 1 处
docs/ai/ai_apps_feature_acceptance_spec.md 4 处(含 G20 RLS 缺失提醒)

二、表结构 / 索引 / RLS 现状

2.1 列定义11 列)

列名 类型 默认值 NOT NULL 备注
id bigint nextval('public.member_retention_clue_id_seq'::regclass) YES PK
member_id bigint YES 会员 ID
category varchar(20) YES CHECK 6 值
summary varchar(200) YES 摘要
detail text NO 详情
recorded_by_assistant_id bigint NO 助教 ID
recorded_by_name varchar(50) NO 助教姓名
recorded_at timestamptz now() YES 录入时间
site_id bigint YES 多门店隔离
source varchar(20) 'manual' YES manual / ai_consumption / ai_note
is_hidden boolean false YES 隐藏控制NS4

2.2 约束

  • PKmember_retention_clue_pkeyid
  • CHECKchk_retention_clue_category6 值枚举)
  • NOT NULL8 列
  • FK(既不引用其他表,也无表 FK 引用本表 — 经 information_schema.constraint_column_usage 查证)

2.3 索引4 个,含 PK

member_retention_clue_pkey         UNIQUE (id)
idx_retention_clue_member          (member_id)
idx_retention_clue_site            (site_id)
idx_retention_clue_category        (member_id, category)

2.4 RLS 策略

  • relrowsecurity = false / relforcerowsecurity = false
  • pg_policies 查询返回 0 行
  • 结论:当前未启用 RLS

注:ai_apps_feature_acceptance_spec.md G20 项已登记"全部 AI 表未启用 RLS"为已知差异,本次迁移不引入新 RLS保持现状与现行后端隔离方式一致tenant_clues 路由通过 site_filter_clause 强制 site_id 条件retention_clue 路由通过参数 site_id 限制)。

2.5 触发器

无非内部触发器。

2.6 序列

public.member_retention_clue_id_seqbigint需迁移到 biz


三、数据量评估

测试库 test_zqyy_app2026-05-04 实查):

总行数:        44
门店分布:      site_id=2790685415443269 → 44 行(单店)
来源分布:      ai_consumption → 44 行(全部 AI 写入,无 manual
分类分布:      消费习惯 23 / 玩法偏好 12 / 客户基础 9
时间范围:      2026-04-21 00:20 ~ 2026-05-01 01:53
biz schema:    存在
biz.member_retention_clue: 不存在

生产库未直连查询,估算依据:本表为 AI 写入主导App8 强幂等替换),单门店 10 天 44 行,按 4 门店、180 天线性外推约 3000-5000 行。即使生产库 10 倍于测试库,整表数据量在万行量级,迁移过程一次性 INSERT 即可完成(< 1 秒)。


四、风险等级矩阵

# 风险维度 等级 简述 缓解措施
R1 数据丢失 行数小(< 万),无并发写压力,可在事务内 INSERT INTO biz... SELECT FROM public... 事务 + 完成后 COUNT(*) 一致性校验;保留 public 表 7 天后再 DROP
R2 FK 破坏 无任何 FK 入/出
R3 FDW 外部表破坏 fdw_app.member_retention_clue OPTIONS 写死 schema='public'不改会读到旧表DROP 后报错) 迁移完成后立即 ALTER FOREIGN TABLE ... OPTIONS (SET schema_name 'biz')
R4 RLS policy 重建 当前未启用 RLS迁移不引入
R5 后端代码硬编码 schema 11 处 SQL 直引,遗漏一处即在生产报"relation does not exist" 全量替换 + grep 自动校验 + 后端单元测试 + staging 全链路验证
R6 后端 search_path 隐式依赖 routers/member_retention_clue.pydispatcher.pypage_context.py 共 5 处不带 schema 前缀,依赖 search_path 默认含 public 迁移后这 5 处必须显式加 biz. 前缀,否则会找到 public 旧表(如未删)或报错(已删)
R7 前端 mock / 类型 小程序仅引用字段名 retentionClues,与 schema 无关
R8 App8 写入路径 dispatcher.py L574/L588 是核心幂等写入DELETE + INSERT 在事务内运行,写错 schema 会让幂等失效 改 schema 同时保持 SQL 结构不变;测试库 dry-run 跑一次 App8
R9 AI prompt 硬编码 SQL App8 prompt 仅做 JSON 拼接,不含 SQL只在注释提及表名 注释同步改即可
R10 回滚成本 备份 public 表(保留 7 天)+ 代码 git revert + FDW OPTIONS 回退 完整 rollback runbook 见 § 五-A 第 7 步
R11 文档 / SPEC 不一致 25+ 个文档需要同步替换 public.member_retention_cluebiz.member_retention_clue 批量 grep + sed 风格替换;先做 DDL/代码,文档收尾
R12 序列权限 public.member_retention_clue_id_seq 需迁到 biz应用 INSERT 时使用 nextval() DDL 层 ALTER SEQUENCE ... SET SCHEMA biz 一并搬迁
R13 测试数据库与生产环境差异 测试库已观测无 RLS、无 FK生产库可能有手动加的索引或 grant 迁移前对生产库执行同样的 prescan SQL见 § 七验证锚点)
R14 多门店数据混入 测试库仅 1 site生产库未知INSERT INTO ... SELECT 不会造成混入 site_id 列原样复制

风险最高的 3 项R511 处 SQL 直引漏改、R8App8 幂等链路、R3FDW OPTIONS


五、推荐方案(三套对比)

方案 A — 一次性迁移(短停机 bigbang

适用条件:本任务 100% 满足

  • 数据量小(< 万行)
  • 无 FK 出入
  • 无 RLS 不需重建
  • 无生产小程序高并发写入(写入只来自 tenant-admin 手动 + App8 离线触发)
  • 单一 monorepo可同时部署后端代码 + DB DDL + FDW

步骤清单

  1. 预检5 min:在生产库执行 § 七 验证 SQL确认结构与测试库一致
  2. 公告 / 短停机10 min 内):通知所有 tenant-admin 用户,暂停 ai_trigger_jobs 调度
  3. DDL 迁移事务内执行1 min
    BEGIN;
    
    -- 5.1 先把 schema 从 public 搬到 biz含序列
    ALTER TABLE public.member_retention_clue SET SCHEMA biz;
    ALTER SEQUENCE public.member_retention_clue_id_seq SET SCHEMA biz;
    
    -- 5.2 PostgreSQL 12+ 会自动改 default 表达式中的序列引用,但稳妥起见显式重置:
    ALTER TABLE biz.member_retention_clue
      ALTER COLUMN id SET DEFAULT nextval('biz.member_retention_clue_id_seq'::regclass);
    
    -- 5.3 重命名约束(保持 chk_retention_clue_category 命名)— ALTER SCHEMA 已自动处理
    
    -- 5.4 索引随表迁移PostgreSQL 自动)
    
    COMMIT;
    
  4. FDW OPTIONS 切换(在 etl_feiqiu 中执行10 sec
    ALTER FOREIGN TABLE fdw_app.member_retention_clue
      OPTIONS (SET schema_name 'biz');
    
    测试库同样在 test_etl_feiqiu 执行。
  5. 后端代码部署(已经过 staging 全链路通过)
    • 6 文件 11 处 SQL 全改为 biz.member_retention_clue
    • 顺手修 page_context.py:243 created_at → recorded_at Bug
  6. 冒烟测试10 min
    • retention_clue CRUD 路由POST/GET/DELETE
    • tenant_clues 5 个端点
    • customer_service 客户详情接口CUST-1 retentionClues 块)
    • task_manager 任务详情接口
    • 触发一次 App8dispatch app8_consolidation确认幂等 DELETE + INSERT 落入 biz
    • page_context.build_page_text 维客线索拼接
  7. 回滚预案
    • DDL 层:ALTER TABLE biz.member_retention_clue SET SCHEMA public; ALTER SEQUENCE biz.member_retention_clue_id_seq SET SCHEMA public;
    • FDWALTER FOREIGN TABLE fdw_app.member_retention_clue OPTIONS (SET schema_name 'public');
    • 后端:git revert 单个 commit
    • 数据:原表整表搬迁未发生数据复制,零丢失
  8. 观察期7 天):保留 7 天观察告警,无异常后归档迁移脚本到 db/zqyy_app/migrations/

工作量预估

阶段 人时
代码改动6 文件 + 顺修 created_at Bug 2.0
DDL + FDW 脚本编写 + 测试库验证 1.5
测试库全链路冒烟(含 App8 dry-run 1.5
文档 / SPEC 批量同步25+ 文件) 2.0
审计记录 + DB 文档同步 1.0
生产部署 + 观察 1.0
合计 9 人时

风险等级 — 低-中

主要风险落在 R5漏改 SQL和 R8App8 链路),通过 staging 全链路验证可消除。


方案 B — 渐进迁移(双写 / 视图过渡)

思路:建 biz.member_retention_clue 实表 + 数据复制 → 在 public.member_retention_clue 替换为 biz 同名视图 → 后端代码逐文件切到 biz. 前缀 → 全部切完后删 public 视图。

步骤清单

  1. 在 biz 中 CREATE TABLE biz.member_retention_clue (LIKE public.member_retention_clue INCLUDING ALL) + 序列 + 索引
  2. INSERT INTO biz.member_retention_clue SELECT * FROM public.member_retention_clue
  3. 重置 biz.member_retention_clue_id_seq 到 max(id) + 1
  4. 事务内:DROP TABLE public.member_retention_clueCREATE VIEW public.member_retention_clue AS SELECT * FROM biz.member_retention_clue + 创建对应 INSTEAD OF 触发器(处理 INSERT/UPDATE/DELETE
  5. 后端代码逐文件 PR 切到 biz.11 处分多个 PR
  6. 全部切完后:删 public.member_retention_clue 视图 + 触发器
  7. FDW OPTIONS 切到 biz

工作量预估

20-25 人时(视图 + INSTEAD OF 触发器 + 多次部署)。

风险等级 —

  • 优点:后端代码可灰度切换,无停机
  • 缺点:双写期间复杂度高,触发器路径出 Bug 排查难;本任务无并发写压力,收益不匹配成本

方案 C — 暂缓 + 文档说明

思路:保留 public.member_retention_clue,在 db/CLAUDE.md01-product-overview.md § 八明确登记"维客线索为 public 例外,因 [理由]"。

何时选择

  • 当迁移工作量 > 收益时
  • Neo 已否决此选项(反馈选 A明确要"保证项目工程的规范性"

故方案 C 不再展开。


六、关键风险点详细分析

R5 — 后端代码 11 处 SQL 直引(高)

tenant_clues.py 是受影响最大的文件5 处显式 public. 前缀),其中 _get_clue_with_site_check 内嵌 SQL 同时带 site_filter_clause 拼装,改动需保持参数顺序和 f-string 拼接的占位符一致。

具体替换清单(文件:行 → 旧 → 新):

routers/member_retention_clue.py:29   "INSERT INTO member_retention_clue"           → "INSERT INTO biz.member_retention_clue"
routers/member_retention_clue.py:68   "FROM member_retention_clue"                  → "FROM biz.member_retention_clue"
routers/member_retention_clue.py:87   "DELETE FROM member_retention_clue WHERE..."  → "DELETE FROM biz.member_retention_clue WHERE..."
routers/tenant_clues.py:67            "FROM public.member_retention_clue"           → "FROM biz.member_retention_clue"
routers/tenant_clues.py:197           "FROM public.member_retention_clue"           → "FROM biz.member_retention_clue"
routers/tenant_clues.py:235           "UPDATE public.member_retention_clue"         → "UPDATE biz.member_retention_clue"
routers/tenant_clues.py:267           "DELETE FROM public.member_retention_clue"    → "DELETE FROM biz.member_retention_clue"
routers/tenant_clues.py:298           "UPDATE public.member_retention_clue"         → "UPDATE biz.member_retention_clue"
services/customer_service.py:278      "FROM public.member_retention_clue"           → "FROM biz.member_retention_clue"
services/task_manager.py:1125         "FROM public.member_retention_clue"           → "FROM biz.member_retention_clue"
ai/data_fetchers/page_context.py:241  "SELECT summary FROM member_retention_clue"   → "SELECT summary FROM biz.member_retention_clue"  ⚠ 同时修复 created_at → recorded_at
ai/dispatcher.py:574                  "DELETE FROM member_retention_clue"           → "DELETE FROM biz.member_retention_clue"
ai/dispatcher.py:588                  "INSERT INTO member_retention_clue"           → "INSERT INTO biz.member_retention_clue"

校验做法:迁移完成后,跑一次 Grep "FROM member_retention_clue|INTO member_retention_clue|UPDATE member_retention_clue|DELETE.+member_retention_clue|public\.member_retention_clue" --path apps/backend,应当 0 命中。

R8 — App8 幂等写入链路(高)

dispatcher.py:_write_retention_clue 是 App8 的强幂等核心DELETE 同源旧记录 + INSERT 新批,事务级别保证当天替换原子性。迁移后必须验证:

  1. 触发一次 App8target_id = 测试 member确认 biz.member_retention_clue 中该 (member_id, site_id, source='ai_consumption') 的记录被 DELETE 后 INSERT
  2. 检查事务回滚路径(人为制造 INSERT 失败DELETE 也要回滚

R3 — FDW OPTIONS 切换(中)

FDW 外部表 fdw_app.member_retention_clue 是独立对象,名字不变;只需把 OPTIONS 中 schema_name'public' 改成 'biz'遗漏后果ETL 库读到"对端 public 表已不存在",但当前没有 ETL 任务消费此外部表BD_Manual_fdw_reverse_retention_clue.md § 4 已注明 "当前无 DWS 任务直接消费"),故影响面其实更小,但仍需更新以避免未来踩坑

R6 — search_path 隐式依赖(中)

5 处不带 schema 前缀的 SQLmember_retention_clue.py 3 处、page_context.py 1 处、dispatcher.py 2 处)依赖 search_path 默认包含 public。迁移后 public 表被删,这些 SQL 会立刻报错;反向风险:如果 public 表保留过渡(如方案 BSQL 会写到 public 的视图(间接到 biz但 grep 看不出来,调试更难。结论:方案 A 在删 public 表的瞬间即暴露所有遗漏,更"硬"也更"快"。

R11 — 文档 / SPEC 不一致(中)

25+ 文档需同步。建议批量执行 sed -i 's/public\.member_retention_clue/biz.member_retention_clue/g'PowerShell 等价 (Get-Content ... ) -replace ...但要排除 _archived/ 目录和审计历史记录(保留历史原文)。

R13 — 测试库 vs 生产库结构差异(中)

测试库观测:无 RLS、无 FK、无触发器。但生产库可能存在

  • 手动加的额外索引DBA 可能为查询优化加)
  • 表级 GRANTapp_reader / etl_user / app_user
  • COMMENT

生产部署前必须执行

-- 在生产 zqyy_app 中
\d+ public.member_retention_clue
SELECT grantee, privilege_type FROM information_schema.role_table_grants
 WHERE table_schema = 'public' AND table_name = 'member_retention_clue';
SELECT obj_description('public.member_retention_clue'::regclass);

所有发现的额外属性GRANT / COMMENT / 额外索引),需在 ALTER SCHEMA 后逐项确认是否随表带过去PostgreSQL 12+:表级权限和注释会保留,索引随表迁移)。


七、推荐实施顺序(最小风险路径,方案 A

阶段 1 — 预检30 min

-- 在生产 zqyy_app 中执行
-- A1. 当前结构快照
\d+ public.member_retention_clue
-- A2. 数据量
SELECT COUNT(*) AS rows, COUNT(DISTINCT site_id) AS sites,
       MIN(recorded_at), MAX(recorded_at)
  FROM public.member_retention_clue;
-- A3. GRANT 现状
SELECT grantee, privilege_type FROM information_schema.role_table_grants
 WHERE table_schema = 'public' AND table_name = 'member_retention_clue';
-- A4. 是否有意外 RLS / 触发器(理论应为 0
SELECT relrowsecurity FROM pg_class WHERE oid = 'public.member_retention_clue'::regclass;
SELECT tgname FROM pg_trigger WHERE tgrelid = 'public.member_retention_clue'::regclass AND NOT tgisinternal;

阶段 2 — 测试库迁移演练2 h

  1. 在 test_zqyy_app 执行 ALTER TABLE ... SET SCHEMA biz + 序列同步
  2. 在 test_etl_feiqiu 执行 ALTER FOREIGN TABLE ... OPTIONS (SET schema_name 'biz')
  3. 部署后端代码改动11 处 SQL + page_context.py 顺修 Bug到本地或 staging
  4. 跑一遍后端单元测试 + 集成测试
  5. 触发一次 App8 dispatch确认 biz 表 DELETE + INSERT 正常
  6. 对照测试库写一份"产出报告":行数前后一致 / 序列衔接正常 / FDW 可读

阶段 3 — 文档批量同步1 h

按 § 一-1.9 文档清单,批量替换 public.member_retention_cluebiz.member_retention_clue排除 _archived/docs/audit/changes/ 历史审计

阶段 4 — 生产部署30 min 窗口)

按方案 A 步骤 1-7 执行;持续 7 天观察告警。

阶段 5 — 收尾审计30 min

  • docs/audit/changes/2026-05-XX__schema-migrate-retention-clue-public-to-biz.md
  • python scripts/audit/gen_audit_dashboard.py
  • 把迁移 DDL 脚本归档到 db/zqyy_app/migrations/2026-05-XX__retention_clue_to_biz.sql

总工作量:~9 人时(测试库演练 4h + 生产部署 1h + 文档 + 审计 4h


八、与其他 Wave 的关系

Wave 关系 建议
Wave 0已完成 04b-conflicts-P1-detail.md 第 P1-1 项即本任务的源 本评估即对 P1-1 反馈的响应
Wave 1小程序对齐 小程序零改动(仅引用字段名 retentionClues 不阻塞
Wave 2后端 API 对齐) 后端 6 文件 11 处 SQL 改动属于 Wave 2 范围 建议合入 Wave 2 一起做,避免后端分两次部署
Wave 3数据库 / Schema 治理) 本迁移属于"业务表归 biz"治理大方向 建议作为 Wave 3 的首发任务,先于其他迁移做(数据量最小、依赖最少,风险最低,可作为模板)
Wave 4AI / ETL App8 dispatcher 受影响,但本任务自带验证 在 Wave 4 启动前完成本迁移可以让 App8 路径在新 schema 下稳定 1-2 周
Wave 5tenant-admin 完整化) tenant_clues 路由 5 处改动属本任务范围 同 Wave 2

合并建议:把本任务和 Wave 2 后端 API 对齐合并为一个工作流(同一个分支同一次 PR避免后端 SQL 改动分两次。Wave 3 治理的其他业务表迁移(如果有)以本任务为模板。


九、给 Neo 的决策清单

请逐项确认(每项打 √ 或 ✗ + 备注):

# 决策项 默认 备注
D1 选择方案 A一次性迁移 数据量小、无 FK、无 RLS、无小程序写入压力方案 A 性价比最高
D2 接受 9 人时工作量预估? 含测试库演练 + 生产部署 + 文档 + 审计
D3 把本任务与 Wave 2 后端对齐合并到同一 PR 避免后端代码分两次部署
D4 顺手修复 page_context.py:243created_at → recorded_at Bug 同文件同 SQL零额外成本
D5 顺手修复 schemas/member_retention_clue.pyClueCategory.BASIC_INFO = "客户基础信息" 与 BD 手册 2026-03-08 对齐到 客户基础 不一致? 登记但不在本任务范围,建议另起 task。若一并修需评估对历史数据的兼容44 行测试数据是否有"客户基础信息"值需要迁移)
D6 接受迁移后 public.member_retention_clue 立即删除(非保留视图过渡)? 与方案 A 的"硬切"一致,所有遗漏 SQL 立即暴露
D7 迁移后保留旧表数据备份多久? 7 天 pg_dump -t public.member_retention_clue 单表备份,存到生产备份目录
D8 是否在迁移同步引入 RLSsite_id 过滤)? 本任务不引入新 RLS,保持 G20 已知差异RLS 治理另起任务(涉及全部 AI 表统一规划)
D9 是否需要先做"预检"小任务确认生产库无意外结构? 提交执行 § 七 阶段 1 SQL 后再决定动迁移
D10 生产部署窗口选择? 工作日 09:00 前 / 22:00 后 App8 调度、tenant-admin 用户访问最少时段

十、附录grep 校验脚本

迁移完成后用以下命令自检遗漏:

# 后端代码:应为 0 命中
grep -rEn "(public\.member_retention_clue|FROM member_retention_clue|INTO member_retention_clue|UPDATE member_retention_clue|DELETE.+member_retention_clue)" \
  apps/backend/app

# 文档(排除归档和审计历史):应为 0 命中
grep -rEn "public\.member_retention_clue" docs/ \
  --exclude-dir=_archived --exclude-dir=audit

# DDL应仅出现在 biz.sql 中)
grep -rEn "member_retention_clue" db/zqyy_app/schemas/

# FDW应仅 schema_name 'biz'
grep -En "schema_name" db/fdw/setup_fdw_reverse*.sql

十一、附:本评估自身的限制说明

  1. 未访问生产库:仅基于测试库 test_zqyy_app 推断;生产库结构差异通过 § 七 阶段 1 预检 SQL 排查
  2. 未跑 staging 全链路:本评估为只读调研,方案 A 阶段 2 必须在 staging 跑通后才能进入生产
  3. 未量化 App8 触发频率dispatcher.py 写入路径风险等级标"高"是基于"幂等性是核心特性"的保守判断;如果 App8 实际触发频率 < 1 次/天,迁移期间踩坑概率极低
  4. 未触及 RLS 引入:本评估遵循 Neo 反馈"保证规范性"的范围,仅做 schema 迁移,不并入 RLS 治理RLS 治理建议另起任务统筹 G20

结论:迁移 public.member_retention_cluebiz.member_retention_clue 工程上完全可行,推荐方案 A一次性迁移+ 与 Wave 2 后端对齐合并 PR。最高风险落在后端 11 处 SQL 直引,通过 grep 自检 + staging 全链路验证可控。预估 9 人时。