DIP1 三循环验证反复空转问题 — 复盘与详解(AI 专家分析 + 实际处置对照)¶
日期:2026-08-10 场景:线下讨论(BO 郭总 / SPO 曾总 / TL 司徒) 参考文档:DWLG-20260809-04 Code Agent 迁移/Seed/Ready 验证记录、sys.py、migration 0001、migration 0003 文档编号:DLG-66(WLG 线下讨论记录)
一、背景:什么是「三循环验证」?为什么会卡?¶
1.1 三循环验证是什么(面向非技术)¶
类比:装修前先反复验收图纸
| 步骤 | 技术操作 | 装修类比 |
|---|---|---|
| 第 1 步 | alembic downgrade base |
拆到只剩空地(清空所有表) |
| 第 2 步 | alembic upgrade head |
按图纸完整重建一遍(建 34 张表 + 种子数据) |
| 第 3 步 | 再 downgrade base → upgrade head 循环 2 次 |
拆了建、建了拆,反复 3 轮,确保图纸没有「建完就拆不动」的漏洞 |
目的:确保 DIP1 上线后,万一数据库出问题需要重建或迁移,每一步操作都是可靠、可逆的。
1.2 Code Agent 卡住的现象¶
用户提供的过程信息显示:
● Need to run seed again. But also the seed uses `DEV_ADMIN` from data.py.
● Grep (mds_scl_categories|mds_scl_sub_categories) · 158 matches
● Grep (sys_role_permissions|seed_rows|PERMISSIONS_21) · 58 matches
Error: [loop.max_steps_exceeded] Turn exceeded maxSteps=100
Code Agent 触发了 100 步强制上限,工作流停在"反复 grep 分析种子数据来源"阶段,没有执行任何实际操作。
二、第一轮分析:AI 技术专家的判断(DeepSeek 输出)¶
本节保留 2026-08-09 23:00 DeepSeek 收到 DWLG-03 + CLI 片段后的原始分析,标注正确点与遗漏点。
2.1 AI 专家判断的核心观点¶
| # | AI 专家观点 | 判断 | 说明 |
|---|---|---|---|
| A1 | Agent 在「迁移验证」和「模型修复」之间陷入死循环 | ✅ 正确 | 方向判断完全吻合实际 |
| A2 | 根本原因是 role_id 模型定义 String(16) vs 数据库 ENUM 类型不一致 | ✅ 基本正确(是 bug 链第一环 B0) | 经 Code Agent DWLG-04 一-3 条确认:SysRole.role_id / SysRolePermission.role_id 初始代码确实是 String(16),已由 Code Agent 改为 ENUM。A2 准确命中了"修改→验证→发现新问题→再修改"循环的触发源。后续在 B0 之后又叠加了 B1~B3 三个 ORM 层 bug(AI 专家未能预判,因为这些需要完整代码访问权限+实际运行才会暴露)。 |
| A3 | 迁移脚本 0001 已经包含种子数据,Agent 未意识到 | ✅ 正确 | 准确命中 B4(种子数据双来源分析循环) |
| A4 | 建议先全局修复模型 → 清空 DB → 跑迁移 → 确认种子来源 → 三循环 | ✅ 正确但不够具体 | 方向完全对,后续实际执行时还需要补充:① migration 0001 漏建的 updated_at 列(B3)② sys_user_roles association table 注册(B1)③ foreign_keys 参数(B2)④ downgrade CASCADE + 显式 DROP TYPE(Code Agent 第二轮追加,见 §九 A.2) |
2.2 完整 bug 链总览(AI 专家命中 vs 未命中)¶
B0 由 Code Agent 自行修复(所以日志里看起来 sys.py 里已经是 ENUM 了),B1~B3 需要 TL 介入修复(这三个是导致 Code Agent 最终 100 步耗尽仍未跑通 seed CLI 的直接原因),B4 由人类理清边界:
| # | 真实问题 | 类型 | AI 专家命中 | 修复人 | 对空转的影响 |
|---|---|---|---|---|---|
| B0 | role_id 模型定义 String(16) vs 数据库 ENUM 类型不一致 | 技术 bug | ✅ §A2 准确命中 | Code Agent 自行 | 触发"修改模型→再验证→再改模型"的局部循环,消耗约 30 步 |
| B1 | sys_user_roles 关联表未在 ORM 层注册为 Table 对象 | 技术 bug | ❌ 未提及 | TL 介入 | B0 修复后立刻报错,seed CLI 无法启动 |
| B2 | relationship 多 FK 路径歧义未显式指定 foreign_keys | 技术 bug | ❌ 未提及 | TL 介入 | B1 修复后立刻再报错,seed CLI 仍无法跑 |
| B3 | migration 0001 系统性漏建 7 张表的 updated_at 列 | 技术 bug | ❌ 未提及 | TL 介入 | B1+B2 修复后 seed CLI 执行到 SysRole 立刻报 UndefinedColumnError |
| B4 | migration seed vs seed CLI 职责重叠造成分析循环 | 设计疑惑 | ✅ §A3 命中 | 人类理清边界 | 不影响功能正确性,但反复 grep 消耗约 40 步(是 100 步耗尽的最大单一因素) |
三、第二轮处置:TL 实际执行 + 4 个技术 bug 详解(B0~B3)¶
本节对应 DWLG-20260809-04 第一、三节的技术细节展开,便于 BO/SPO 理解"为什么 Agent 修了 B0 之后还是卡、卡在了哪三步"。
补充说明:B0 是由 Code Agent 在 100 步耗尽之前自行修复的(DWLG-04 一-3 条有记录),所以后面我们看 sys.py 时它已经是 ENUM 了,容易误以为 A2 判断不准;B1~B3 是 B0 修复后依次暴露的 3 个 ORM 层 bug,由 TL 介入修复。
3.0 Bug 0(B0):role_id String(16) vs ENUM 类型不一致¶
本 bug 由 Code Agent 自行修复,对应 AI 专家 A2。
3.0.1 为什么会有这个 bug?¶
- Migration 0001 的
create_table("sys_roles", ...)中,role_id 列定义是_enum("app_enum__sys_role_6", create_type=True)(PostgreSQL 原生 ENUM 类型) - 但 sys.py ORM 模型初始写的是
role_id: Mapped[str] = mapped_column(String(16), primary_key=True)(普通字符串,长度 16) - 结果:
alembic upgrade head建出来的数据库列是 ENUM,但 ORM 查询/插入时 SQLAlchemy 会把它当作 VARCHAR(16) 来绑定参数,在 strict 模式下会报类型不匹配错误
3.0.2 修复方式(Code Agent 自行完成)¶
DWLG-04 一-3 条记录:
- SysRole.role_id、SysRolePermission.role_idORM 模型字段从String(16)改为ENUM(..., name="app_enum__sys_role_6"),与数据库类型对齐
见当前 sys.py#L28-L31(改完后的版本)与 sys.py#L66-L69(SysRolePermission 同字段)。
3.0.3 为什么 B0 修完还会空转?¶
Code Agent 修完 B0 后,想"修完了,跑一下 seed CLI 验证吧",结果立刻遇到了 B1,B1 修完又遇到 B2,B2 修完遇到 B3。一轮一轮修复 + 跑验证消耗步,同时 B4 种子数据双来源的反复 grep 也在并行消耗步数,最终 100 步到顶,强制终止。
3.1 Bug 1(B1):sys_user_roles 关联表未注册(字符串引用 vs Table 对象)¶
3.1.1 技术现象¶
InvalidRequestError: expression 'sys_user_roles' failed to locate a name
seed CLI 刚启动,ORM 模型加载就失败了。
3.1.2 根因图解¶
SQLAlchemy many-to-many 有两种写法:
写法①(字符串引用,❌ 本项目初始代码):
SysUser.roles = relationship(..., secondary="sys_user_roles")
问题:字符串引用依赖 SQLAlchemy "名称自动查找表对象"机制,
但 sys_user_roles 表只有 migration DDL,没有对应 Table 对象
注册到 Base.metadata → 名称查找失败。
写法②(显式 Table 对象 + 变量引用,✅ 修复后):
1. 先创建 Table 对象并注册到 Base.metadata:
sys_user_roles = Table("sys_user_roles", Base.metadata, Column(...), ...)
2. relationship 直接引用变量
SysUser.roles = relationship(..., secondary=sys_user_roles)
为什么 Code Agent 容易卡在这一步?
- Agent 需要同时理解:migration DDL 已经创建了表 → 但 ORM 层还需要一个 Table 对象 → 两者结构必须逐列对齐(数据类型、长度、FK 级联动作、ENUM 名称)
- 任何一列不一致(比如 ENUM 名少写一个字母、FK ondelete 写成 RESTRICT 和 migration 不一致),后续 downgrade/upgrade 循环就会报错
- 这是一个"局部修改 + 全局一致性约束"的任务,Code Agent 在单步 100 限制下容易反复验证而无法完成
3.1.3 修复结果(代码引用)¶
见 sys.py#L12-L20:显式定义 sys_user_roles = Table("sys_user_roles", Base.metadata, …),包含 4 列(user_id、role_id、assigned_at、assigned_by)+ 3 个 FK(两个 CASCADE + 一个 SET NULL),逐字对齐 migration 0001#L198-L208。
3.2 Bug 2:AmbiguousForeignKeysError — 多 FK 路径歧义¶
3.2.1 技术现象¶
Bug 1 修复后立刻出现第二个错误:
AmbiguousForeignKeysError: Could not determine join condition between parent/child tables
on relationship SysUser.roles - there are multiple foreign key paths linking the tables
via secondary table 'sys_user_roles'.
3.2.2 根因图解¶
sys_user_roles 表有 两个 FK 指向 sys_users 表:
sys_user_roles:
├─ user_id ──────FK──→ sys_users.user_id (用户本身,CASCADE 删除)
├─ assigned_by ──FK──→ sys_users.user_id (谁分配的角色,SET NULL)
└─ role_id ──────FK──→ sys_roles.role_id
SQLAlchemy 看到 secondary 表里有 2 条路通往 sys_users,就困惑了:
"这个 many-to-many 到底要走哪条路当 join 条件?是通过 user_id 还是通过 assigned_by?"
于是必须由人显式告诉它:走 user_id + role_id,别管 assigned_by。
3.2.3 修复方式¶
在 relationship 中增加 foreign_keys 参数(显式列出参与 join 的列):
roles: Mapped[list[SysRole]] = relationship(
secondary=sys_user_roles,
foreign_keys=[sys_user_roles.c.user_id, sys_user_roles.c.role_id], # ← 新增这行
lazy="selectin",
)
为什么 Code Agent 自己修容易循环? - Agent 能理解"多 FK 歧义"的定义,但它需要先准确识别出「哪几个 FK 指向了同一张表」 - 修复后又需要重新跑 seed CLI 验证,一轮验证 = 10-15 步消耗,多次反复就触发上限
3.3 Bug 3:migration 0001 系统性漏建 7 张表的 updated_at 列¶
3.3.1 技术现象¶
B1+B2 修复后,seed CLI 终于能跑起来了,执行到第一条 INSERT INTO sys_roles 时:
UndefinedColumnError: column sys_roles.updated_at does not exist
3.3.2 根因图解:Base 类定义与 migration DDL 脱节¶
项目设计(base.py#L20-L28)要求:所有 ORM 模型自动拥有 created_at + updated_at 两列,由 Base 类统一提供。
但是 migration 0001 在手写 create_table DDL 时,有 7 张表漏写了 updated_at 列定义,甚至其中 1 张表(cpt_orders)只建了触发器、漏建了对应的列(等于触发器在找一个不存在的列,更隐蔽):
| 漏 updated_at 的表 | 触发器是否已建? | 说明 |
|---|---|---|
| sys_roles | ❌ 无触发器 | 角色表 |
| cpt_orders | ⚠ 有触发器但漏列 | 订单主表(隐蔽性最高,后续 UPDATE 会触发但列不存在) |
| cpt_order_stage_data_l7 | ❌ 无触发器 | L7 验收表 |
| ops_rewards | ❌ 无触发器 | 营销奖励表 |
| push_delivery_logs | ❌ 无触发器 | 推送投递日志 |
| qsv_param_versions | ❌ 无触发器 | 报价参数版本 |
| sys_dlq_messages | ❌ 无触发器 | 死信队列 |
这是整个空转事件中最严重的 bug:
- 如果后续有任何代码对这 7 张表执行 UPDATE,触发器 trg_xxx_updated_at 会报错(因为要 SET NEW.updated_at = now(),但列根本不存在)
- 同时 Base 类会在 ORM 查询时要求返回 updated_at 列,但 SELECT 查不到,也会报 UndefinedColumnError
3.3.3 修复方式:新建 migration 0003(幂等补丁迁移)¶
不能直接修改 0001(版本历史不可变),所以创建补丁迁移 0003_fix_updated_at.py,结构如下:
upgrade():
对 7 张表循环执行:
① ALTER TABLE xxx ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP ... DEFAULT now()
(幂等:列存在就跳过,不会重复加)
② DROP TRIGGER IF EXISTS trg_xxx_updated ON xxx;
CREATE TRIGGER trg_xxx_updated BEFORE UPDATE ... EXECUTE app_set_updated_at();
(先删后建:处理 cpt_orders 这种"触发器已存在但列不存在"的半残状态)
downgrade():
反向操作:DROP TRIGGER IF EXISTS → DROP COLUMN IF EXISTS
(同样 IF EXISTS 保证幂等)
为什么 Code Agent 自己修不了? - 需要同时懂:Alembic migration 历史不可变原则、PostgreSQL DDL 幂等写法(IF NOT EXISTS / DROP IF EXISTS)、触发器函数先删后建的顺序 - 更关键的是:需要先准确找出哪 7 张表漏了 — 这需要对比 "Base 子类全部模型有 updated_at" 与 "migration 0001 中所有 create_table DDL 哪些写了 updated_at",这是一次 diff 运算,Code Agent 在步数限制下容易反复 grep 而无法形成完整清单
3.4 B4(非 bug,设计选择):种子数据职责重叠¶
3.4.1 问题现象¶
Code Agent 在种子数据阶段反复 grep 下面这些关键词:
- mds_scl_categories|mds_scl_sub_categories:158 次匹配
- sys_role_permissions|seed_rows|PERMISSIONS_21:58 次匹配
它在反复核对:"migration 里写了一份种子,seed CLI 又写了一份,到底以哪个为准?会不会冲突?重复插入会报错吗?"
3.4.2 最终确认的职责边界表(给所有人的标准答案)¶
| 数据类 | 来源 1:migration 0001 bulk_insert | 来源 2:seed CLI insert on_conflict | 结论 |
|---|---|---|---|
| sys_roles(6 角色) | ✅ 含 is_builtin=true | ✅ 缺 is_builtin(但 migration 已默认 true) | 不冲突:on_conflict 跳过 |
| sys_role_permissions(21×6=126) | ✅ 含 allowed=false 全矩阵 | ✅ 仅 allowed=true 子集 | 不冲突:on_conflict 跳过 |
| mds_scl_categories(8 品类) | ✅ 全量 8 品类 | ✅ 同样的 8 品类 | 不冲突:on_conflict 跳过 |
| sys_users(DEV_ADMIN 账户) | ❌ 未 seed | ✅ 唯一增量 | seed CLI 必须执行(核心价值) |
3.4.3 为什么这样设计是合理的?¶
- migration seed 放"系统内置不可变"数据:角色、权限矩阵、品类目录 — 这些数据和"建表"是强绑定的,表一建出来就必须存在,所以和 create_table 写在同一个 migration 里
- seed CLI 放"环境相关可变"数据:开发管理员账户(只允许开发/测试环境有,生产环境不能有)— 所以不能写在 migration 里(migration 生产环境也要跑)
- on_conflict_do_nothing 保证幂等:seed CLI 跑 1 次和跑 100 次结果完全一致,不会重复插入也不会覆盖
四、全链路验证最终执行结果(TL + Code Agent 两轮,实际可复现)¶
完成 B0~B3 修复 + 理清 B4 边界 + Code Agent X1~X5 加固后,实际执行结果如下:
4.1 Alembic 三循环验证(TL 执行,基于 X1+X2 加固后的 migration 源码)¶
| 循环轮次 | 操作序列 | 耗时 | EXIT 码 | 说明 |
|---|---|---|---|---|
| 第 1 轮 | downgrade base | ~8s | 0 | 从 0003 → 0002 → 0001 → base,34 表全部删除,仅剩 alembic_version |
| upgrade head | ~15s | 0 | base → 0001 → 0002 → 0003,34 表重建成功 | |
| 检查 migration seed | ✅ | roles=6,perms=126,categories=8 | ||
| 第 2 轮 | downgrade base | ~8s | 0 | 同第 1 轮,无报错(CASCADE + DROP TYPE 生效,无需手动排序) |
| upgrade head | ~15s | 0 | 同第 1 轮,无报错(TYPE 无残留,create_type=True 不再冲突) | |
| 第 3 轮 | downgrade base | ~8s | 0 | 同第 1 轮,无报错 |
| upgrade head | ~15s | 0 | 同第 1 轮,无报错 | |
| alembic current | 0003_fix_updated_at (head) ✅ |
说明:三循环 3 轮 EXIT 0 是基于「TL 手动修完 migration downgrade 逻辑」跑通的;Code Agent 第二轮的 X1+X2(把 CASCADE + DROP TYPE 写回 migration 源码)保证了这个结果将来可在任何机器上无需手动改代码即可复现。
4.2 seed CLI 幂等性验证¶
| 执行次数 | EXIT | 结果 |
|---|---|---|
| 第 1 次(创建 DEV_ADMIN) | 0 | ✅ 种子数据写入完成(X3 已将 emoji 改为 ASCII,Windows Git Bash 无 Unicode 错误) |
| 第 2 次(重复,测试幂等) | 0 | ✅ 种子数据写入完成(on_conflict 全部跳过,无重复) |
4.3 初始数据完整性抽样检查¶
| 检查项 | 期望 | 实际 | 状态 |
|---|---|---|---|
| DEV_ADMIN 用户名 | admin | admin | ✅ |
| 开发管理员显示名 | 开发管理员 | 开发管理员 | ✅ |
| sys_roles 角色数 | 6 | 6 | ✅ |
| 权限矩阵行数 | 21×6=126 | 126 | ✅ |
| TL 角色权限数 allowed=true | 21(全部权限) | 21 | ✅ |
| FL 角色权限数 allowed=true | 1(仅 cpt:list:self) | 1 | ✅ |
| SCL 品类数 | 8 | 8 | ✅ |
| MdsSclCategory.category_id ULID server_default | DB 端 app_gen_ulid('CAT') | 已由 X5 Code Agent 补 server_default | ✅ |
4.4 应用层探针验证(Code Agent 第二轮 X4)¶
三循环 + seed 跑通 ≠ 服务能正常提供。应用层探针验证确保 FastAPI 启动、连接池、DB 查询链路完整可用。
| 验证点 | 期望 | 实际 | 状态 |
|---|---|---|---|
GET /ready HTTP 状态码 |
200 | 200 | ✅ |
ready 字段 |
true | true | ✅ |
checks.db 字段 |
"ok" | "ok" | ✅ |
| 应用启动耗时 | < 10s | < 5s | ✅ |
五、非技术解读:为什么 Code Agent 会空转?人+AI 应该如何配合?¶
本节面向 BO(郭总)/ SPO(曾总),不需要技术背景。
5.1 类比:装修 vs 装修验收¶
把 DIP1 比作"装修一套房子": - Code Agent = 装修学徒工:会刷墙、会铺砖,但遇到"墙歪了同时瓷砖也切错了"这种连环问题时,容易陷入"先修墙→发现贴不了砖→先切砖→发现尺寸还是不对→又去修墙"的循环 - TL(司徒)= 工头:有全局视角,能一眼看出"先打水平线修垂直度 → 再统一按新尺寸切砖 → 一次性完工" - 三循环验证 = 消防验收:不是装修过程,是"验收图纸可不可靠",要求拆了能建、建了能拆、反复 3 次不出问题
5.2 这次空转暴露出的 Code Agent 能力边界¶
| Code Agent 擅长(未来继续用) | Code Agent 不擅长(需要人介入) |
|---|---|
| ① 明确的单一任务:写一个函数、写一个接口、补一个单测 | ① "先全局扫描再局部修改"类任务:需要先列完整清单、再统一修改 |
| ② 有样板代码的重复性工作:8 个品类的 CRUD、10 个 DTO 定义 | ② "连环 bug + 多步修复顺序约束":需要先 A 才能 B 才能 C,错一步就要回头 |
| ③ 已有明确标准答案的实现:按接口文档写后端、按原型写页面 | ③ "设计选择类"问题:两个都对但只能选一个(比如种子数据写哪边、migration 版本怎么管理) |
| ④ 文档润色、格式调整、命名规范对齐 | ④ "隐蔽系统性 bug":7 张表漏了同一列、一个 FK 导致全链路歧义这类需要 diff/对拍才能发现的 |
5.3 后续人+AI 协作建议(落地到 DIP1 执行)¶
- C-A0 环境基线:TL 已 100% 完成并固化为脚本,Code Agent 不再碰环境配置问题
- 开发流程调整:Code Agent 在推进 §B(后端业务域)和 §C(RN 双端)时,遵循"先把要改的文件清单发给 TL 确认 → 再动手改 → 改完跑单测/集成测试 → 结果回传"的模式,避免局部修改循环
- 遇到 ≥2 个关联 bug:立即触发 SFM ESCAL(子项目反馈机制 Critical 级别),由 TL 先确定修复顺序,Code Agent 再按序执行
- 三循环验证这类强一致性检查:由 TL 主导一次性执行并固化结果,Code Agent 后续代码改动跑单测/集成测试即可,不反复跑完整三循环
六、给 Code Agent 的明确指令(可直接粘贴)¶
如 Code Agent 再次在 §A.2 或 seed 阶段空转,可直接发送以下指令:
请立即停止循环分析。以下是已核实的明确结论,请按顺序执行:
1. 模型与 migration 一致性:sys.py、migration 0001~0003 全部修完,不要做任何改动。
- B0(ENUM 对齐):SysRole.role_id / SysRolePermission.role_id 已由 Code Agent 从 String(16) 改为 ENUM
(见 sys.py#L28-L31、sys.py#L66-L69)
- B1(association table):sys_user_roles 已用 Table 对象注册
(见 sys.py#L12-L20)
- B2(foreign_keys 参数):SysUser.roles relationship 已加 foreign_keys 显式指定 join 列
(见 sys.py#L54-L58)
- B3(updated_at 列):7 张表缺列由 migration 0003_fix_updated_at.py 补齐,含触发器
- X1+X2(downgrade 加固):migration 0001/0002 downgrade 已用 CASCADE + 显式 DROP TYPE,
三循环可逆无需再手动排序
2. 种子数据职责边界(不要再分析"到底用哪边种子"):
- migration 0001 已 seed:sys_roles(6) + sys_role_permissions(21×6=126) + mds_scl_categories(8 品类)
- seed CLI 唯一增量:DEV_ADMIN 账户(USR-DEV-ADMIN-000001)
- 两者通过 on_conflict_do_nothing 幂等共存,直接运行 seed CLI 即可
3. 三循环验证 + 探针验证已由 TL + Code Agent 通过:
- alembic downgrade base → upgrade head × 3 轮 EXIT 0
- seed CLI × 2 次幂等性验证 EXIT 0
- FastAPI GET /ready 返回 {"ready":true,"checks":{"db":"ok"}}
你不需要再重复跑三循环。
4. 下一步按优先级推进:
① QSV 报价域持久化集成测试(DB 已就绪,34 表 + 种子数据完整)
② 认证接口集成测试(用户名 admin / 密码 admin123)
③ RN 双端页面实现(§C 阶段,按 DIP1-PROTO V2.0)
任何报错立即上报带完整 traceback 的 SFM REQ/CROSS。
七、结论:空转问题已闭环¶
7.1 最终根因(修正 V2.1)¶
本次反复空转是一条串行的 4 技术 bug 链 + 1 个设计选择疑惑的组合效应:
触发源 B0(ENUM 不一致)
↓ Code Agent 自行修复 String(16)→ENUM
种子数据前置分析 B4 并行消耗 40 步
↓ 跑 seed CLI 验证 → 依次暴露
B1(association table 未注册)→ InvalidRequestError
↓ TL 修复 B1
B2(多 FK 歧义 foreign_keys)→ AmbiguousForeignKeysError
↓ TL 修复 B2
B3(7 表缺 updated_at)→ UndefinedColumnError
↓ TL 修复 B3(migration 0003)
B4 边界人类理清
↓
三循环 + seed CLI 幂等性 + 探针验证全部通过 ✅
AI 专家 A2 判断完全正确:B0(ENUM 不一致)确实是这条 bug 链的触发源;只是 Code Agent 修好 B0 之后,还没来得及往下走就遇到 B1/B2/B3(3 个需要完整代码权限+对拍才能发现的 bug),再加上 B4 种子数据双来源的 grep 循环消耗了约 40 步,最终一起触发了 100 步上限。
7.2 完成清单¶
- ✅ B0 由 Code Agent 自行修复(role_id String(16)→ENUM)
- ✅ B1~B3 由 TL 修复 + migration 0003 新建
- ✅ B4 职责边界由人类理清,整理为 §3.4.2 标准答案表
- ✅ Code Agent 第二轮追加:downgrade CASCADE + 显式 DROP TYPE(三循环不再失败)、emoji→ASCII(Win 编码问题)、category_id ULID server_default、/ready 探针验证
- ✅ 三循环验证 3 轮全部 EXIT 0 + seed CLI 幂等性验证通过 + 初始数据完整性抽检通过 + FastAPI /ready DB ok
- ✅ 本文档形成非技术解读 + 给 Code Agent 的明确指令(§六),避免后续再空转
八、后续行动项¶
| # | 行动 | 负责人 | 截止 | 状态 |
|---|---|---|---|---|
| 1 | Code Agent 按 §六 指令恢复推进:QSV 持久化集成测试 → 认证接口集成测试 → RN 双端页面 | Code Agent / TL | 即时 | 可启动 |
| 2 | 本文档 DLG-66 同步写入 docs/工作日志/README.md 索引 | DT | 2026-08-10 | 🟡 待处理 |
| 3 | DWLG-20260809-04 待办项 6(Docker CLI PATH 持久化) | TL / DT | 2026-08-09 | ✅ 已完成 |
| 4 | Code Agent 第二轮加固工作(X1~X5):CASCADE/显式DROP TYPE/emoji→ASCII/.env//ready 探针 | Code Agent / TL | 2026-08-09 | ✅ 已完成 |
九、附录 A:完整事件时间线 + Code Agent 第二轮追加处置¶
本附录将「用户最初提供的 Agent 异常片段」「DWLG-20260809-04 Code Agent 执行记录」「TL 处置」按实际时间先后顺序串起来,便于将来审计或复盘。
A.1 全链路 bug 链时间线(时间顺序)¶
| T+时间 | 阶段 | 事件 | 触发错误 | 消耗步数 | 处置人 |
|---|---|---|---|---|---|
| T+0 | 开始 | Code Agent 接任务:§A.2 Alembic 三循环验证 | — | 0 | Code Agent |
| T+~15 步 | 分析阶段 | Code Agent 检查 sys.py,发现 B0 role_id 是 String(16) 与 migration ENUM 不一致 | — | 累计 ~15 | Code Agent |
| T+~25 步 | B0 修复 | 编辑 sys.py,SysRole.role_id / SysRolePermission.role_id String(16)→ENUM;顺带修了 mds.py 的 category_id ULID server_default |
(修复无报错) | 累计 ~28 | Code Agent |
| T+~40 步 | 种子数据分析(B4) | Code Agent 开始核对种子数据:grep mds_scl_categories\|... 158 次 + grep sys_role_permissions\|... 58 次 |
(无报错,但纯分析消耗步数) | 累计 ~70 | Code Agent |
| T+~85 步 | 执行 seed CLI | 跑 python -m app.infrastructure.db.seed.cli,第一次尝试跑 |
❌ B1 InvalidRequestError: sys_user_roles 未注册 |
累计 ~90 | Code Agent |
| T+~95 步 | 修 B1 未果 | Code Agent 尝试修 sys_user_roles,改到一半,需要再次跑 seed CLI 验证;途中还顺手补了 seed CLI 的 from sqlalchemy import select, text(原缺 text import) |
— | 累计 ~98 | Code Agent |
| T+100 | 强制上限 | Error: [loop.max_steps_exceeded] Turn exceeded maxSteps=100,工作流终止在「刚准备加 text import、尚未重新跑 seed CLI」阶段 |
— | 100 满 | — |
| T+100+ | TL 介入 ① | 人类接手 B1:补 sys_user_roles Table 对象注册(sys.py#L12-L20) | B1 修完 | — | TL |
| TL 介入 ② | 立即跑 seed CLI → 暴露 B2 AmbiguousForeignKeysError;加 foreign_keys 参数(sys.py#L54-L58) |
B2 修完 | — | TL | |
| TL 介入 ③ | 再跑 seed CLI → 暴露 B3 UndefinedColumnError: sys_roles.updated_at;新建 migration 0003 给 7 张表补列 + 触发器 |
B3 修完 | — | TL | |
| TL 介入 ④ | 理清 B4 种子数据职责边界:migration seed = roles/perms/categories,seed CLI = 仅 DEV_ADMIN,两者 on_conflict 共存 | B4 理清 | — | TL | |
| TL 验证 | 三循环 3 轮 EXIT 0 + seed CLI 幂等性×2 + 初始数据抽检 7 项全通过 | ✅ 无错误 | — | TL | |
| Code Agent 第二轮追加 | 修 0001/0002 downgrade CASCADE + 显式 DROP TYPE + emoji→ASCII + category_id ULID + FastAPI /ready DB ok | ✅ 全通过 | — | Code Agent |
A.2 Code Agent 第二轮追加处置(DWLG-20260809-04 一-1~2 + 一-3 后半段 + 五)¶
B0~B4 全部理清后,Code Agent 又在 TL 主导下完成了以下 4 项加固工作(这是 AI 专家 A4 建议里也没有提及的、属于"跑通三循环后才会发现"的细节):
| # | 追加工作 | 对应 DWLG-04 | 为什么需要? |
|---|---|---|---|
| X1 | downgrade 使用 DROP TABLE ... CASCADE |
一-1 条 | 0001/0002 表间存在 cpt_orders <-> ops_leads 等双向 FK,人工排序删表极其脆弱;CASCADE 由 PostgreSQL 自动解依赖,可逆健壮。 |
| X2 | 显式 DROP TYPE IF EXISTS 清理所有枚举 |
一-1 + 一-2 条 | SQLAlchemy create_type=True 在 DROP TABLE CASCADE 场景下不会自动回收 TYPE,必须在 downgrade 末尾逐个删。否则第一周目 downgrade→upgrade 没问题,第二周目 upgrade 会报 "TYPE 已存在"。 |
| X3 | seed CLI 输出去掉 emoji 改为 ASCII | 一-3 条 | Windows Git Bash 默认 GBK 编码,✅ 这类 emoji 会引发 UnicodeEncodeError;改为纯文本输出避免编码错误。 |
| X4 | FastAPI /ready 探针验证 |
五 | 三循环 + seed 跑通 ≠ 服务能正常启动;探针验证保证:应用启动 → 连接池获取连接 → 真正执行一条 SQL(如 SELECT 1)并返回 {"ready":true,"checks":{"db":"ok"}},这是部署前最后一道关卡。 |
| X5 | 创建本地 .env 开发配置 |
一-4 条 | 避免 Code Agent 每次启动都要手动配环境变量(数据库 URL、Redis URL、JWT secret 等),脚本化配置。 |
X1+X2 的重要性说明:TL 执行的三循环(migration 0001→0002→0003)是基于"TL 手动改了 migration 0001 的 downgrade 逻辑"才通过的,但 Code Agent 的 X1+X2 工作把这套逻辑写回到了 migration 源码里,保证将来任何环境跑 alembic downgrade base → upgrade head 都能直接通过,不需要 TL 再手改。这是"验证通过 → 代码固化"的关键一环。
A.3 用户最初提供的 Agent 异常片段(原始现场,存档用)¶
以下为用户最初提供的、Code Agent 100 步耗尽前最后几行的日志片段(原样保留,仅包代码块格式化,便于将来审计对照):
● Used Read (backend\app\infrastructure\db\seed\cli.py) · 63 lines
● Add seed_admin_role. Need `text` import.
● Used Edit (…\dip1\backend\app\infrastructure\db\seed\cli.py) · +1 -1
1 - from sqlalchemy import select
1 + from sqlalchemy import select, text
2 from sqlalchemy.dialects.postgresql import insert as pg_insert
──────────────────────────────────────────────────────────────────────
Todo
● §A.2 Alembic 0001+0002 迁移三循环验证
○ §A.3 seed CLI 写入角色/权限/管理员/品类
○ §A.4 后端 /ready 探针验证 DB 连通
○ §B CPT 订单跟踪域核心实现
○ §B MDS 主数据域核心实现
… +2 more
──────────────────────────────────────────────────────────────────────
Error: [loop.max_steps_exceeded] Turn exceeded maxSteps=100
注:用户终端输出里还夹杂了一段 PowerShell 报错「属性或类型文本末尾缺少 ]」——这段与 Code Agent 无关,是 Trae/PowerShell 在渲染 Agent 输出框时自身的脚本解析失败,不影响 Agent 内部的代码修改效果(实际 sys.py / seed cli 的修改已落盘成功),属于已知 Trae 终端显示 bug,无需在代码侧处置。
修订记录¶
| 版本 | 日期 | 修订人 | 修订内容 |
|---|---|---|---|
| V1.0 | 2026-08-09 | DeepSeek(AI 技术专家) | 第一轮分析:ENUM 不一致 + 种子数据职责重叠 + 正确流程建议 |
| V2.0 | 2026-08-10 | DT(听写,基于 TL 实际处置) | 增补:① AI 专家分析对错判定表;② B0~B3 四个技术 bug 详解(含 B0 Code Agent 自行修复的 ENUM 不一致);③ B4 职责边界最终确认表;④ 三循环实际执行结果完整表格;⑤ 非技术解读(装修类比)+ 人+AI 协作建议;⑥ 给 Code Agent 的可粘贴指令;⑦ 结论与后续行动 |
| V2.1 | 2026-08-10 | DT(听写) | 修正误判:经 Code Agent DWLG-04 一-3 条证实,A2(String(16)→ENUM)判断准确,B0 是 bug 链第一环,bug 链扩展为 B0~B3 四技术 bug;新增 §3.0 B0 详解 + §九 完整时间线 + Code Agent 第二轮 X1~X5 加固说明;§六 给 Code Agent 的指令增补 B0+X1~X2;§七 根因 ASCII 流图说明;§八 行动项补充 |
本记录为线下讨论成果整理,第一轮分析来自 DeepSeek AI 技术专家,第二轮实际处置复盘由听写根据 TL 处置全过程撰写。