From 18970479f996bd3bb173baf09016ae2febd4603f Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sat, 26 Sep 2026 02:55:25 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=A4=8D=E6=A0=B8=20pi=205f3eb02d=20?= =?UTF-8?q?=E4=B8=89=E9=99=B7=E9=98=B1=20=E2=80=94=E2=80=94=20=E9=A3=8E?= =?UTF-8?q?=E9=99=A9=E5=8F=AF=E8=BE=BE=EF=BC=88=E5=AE=9E=E6=B5=8B=E9=9D=99?= =?UTF-8?q?=E9=BB=98=E4=B8=A2=2037=20=E8=A1=8C=E3=80=81(a)(b)=20=E5=85=A8?= =?UTF-8?q?=E7=BB=BF=EF=BC=89=EF=BC=8C=E4=B8=A4=E6=9D=A1=E7=90=86=E7=94=B1?= =?UTF-8?q?=E8=AE=A2=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ① ★★★ 陷阱1/3 成立且可达(真表名实测): 重建若为 DDL 里 4 条无 BEGIN 语句(migrate 逐条 Exec ⇒ 各自 auto-commit),DROP 后崩 ⇒ 次日启动建出空的新 PK 表、守卫见 PK 已新 ⇒ 跳过重建 ⇒ 实测 行=0/_new=3 ⇒ 判据 (a) PK ✓ (b) 索引 ✓ **全绿**而镜像为 0(生产 = 37 行静默消失)⇒ (c) 必需 ★ 我先用自造表名 aps 测,"次日启动"没跑真 DDL ⇒ 误报,换真表名后成立(模拟生产必须用真 DDL) ② ★★ 两条理由订正(结论对、机制错): ① "SQL BEGIN 不生效" ⇒ 实测 **BEGIN 生效**(BEGIN→DROP→ROLLBACK 表回来;崩溃后亦完好) 真因: SetMaxOpenConns(1)(db.go:91)⇒ 池里仅一条连接 ⇒ BEGIN 恰在同连接 ⇒ 巧合非保证(PG 分支 20)⇒ 用 BeginTx 对,理由应写"正确性依赖 MaxOpenConns(1)" ② "实际文本无空格" ⇒ 实际 PRIMARY KEY (agent_name, platform_id) **有**空格 失配真因是**模式里 , 后少空格** ⇒ 用 pragma 对,理由应写"LIKE 依赖空白/换行/引号形态" ③ ✅ pi 自撤"第二次 Migrate 撞 PK"复核为真(4 语句版幂等,run1/2/3 rc=0) ④ ✅ 附带: main.go 两次 Migrate ⇒ 必须幂等;REFERENCES agent_platform_sessions = 0 ⇒ 无需管 FK ⑤ ④ 最终措辞 + 收 pi"顺序依赖可消掉"(同事务内重建索引)+ 判据加 (c) 边界: 只读 + 临时 Go 探针(已删、status 已验干净);未改产品代码 --- docs/API.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/docs/API.md b/docs/API.md index a1c6fa4..658dae2 100644 --- a/docs/API.md +++ b/docs/API.md @@ -7456,3 +7456,61 @@ window.__AGENTMAIL_TOKEN__ = ''; // 省略则走 Cookie ⇒ ★ 记法: **"变化了"与"向违规变化了"是两个量** —— 只看"文件变了"会让任何无关修改都通过(**场景 B 就是这么漏的**)。 ``` + +--- + +- ★★★ **复核 pi `5f3eb02d`(把 ④ 当实现写时撞出的三陷阱):风险**成立且可达**(我实测复现"静默丢 37 行、判据(a)(b)全绿"),但其中**两条理由需订正**(结论对、机制错) + + ## (A) ★★★ 陷阱1/3: **成立且可达** —— 我用**真表名**实测复现 + ``` + ⚠️ 我先犯了一个测法错误: 第一次用自造表名 `aps` 测,"次日启动"那步**没跑真 DDL** ⇒ + 得不出孤儿态、误报 (a) 不绿。★ 换成真表名 `agent_platform_sessions` + 真 DDL 后成立。 + (教训: 模拟"次日启动"就必须**用真的那份 DDL**,否则模拟的不是生产路径。) + ⇒ 形态(若把重建写成 DDL 里 **4 条无 BEGIN 的语句**): + `migrate` 是**逐条 Exec**(`migrate.go:33`,注释明写"modernc 不接受多语句")⇒ 每条**各自 auto-commit** + ⇒ `DROP TABLE` 之后崩溃 ⇒ 实测: `agent_platform_sessions` 行=**0**、`_new` 行=**3** + ⇒ 次日启动: `CREATE TABLE IF NOT EXISTS` **建出空的新 PK 表** + `CREATE INDEX IF NOT EXISTS` 补索引 + ⇒ 守卫查"PK 是否已含 workspace" ⇒ **答"是"** ⇒ **跳过重建** ⇒ 数据**永远**躺在 `_new` 里 + ⇒ ★★★ 而判据 **(a)(b) 全绿**: (a) PK = `agent_name+workspace+platform_id+` ✓ ; (b) 索引存在 ✓ + ⇒ **镜像行数 = 0**(本表放生产 = **37 行会话静默消失**)⇒ **(a)(b) 抓不到** ⇒ pi 的 (c) **必需** ✓ + ``` + ## (B) ★★ 但 pi 的两条**理由**要订正(结论对、机制错) + ``` + ① pi: "migrate 逐条 Exec ⇒ SQL 里 BEGIN **不生效**,故必须用 Go 层 BeginTx" + ⇒ ★ 我实测 **BEGIN 生效**: `BEGIN`→`DROP`→`ROLLBACK` ⇒ 表**回来了**(行数不变); + 连**崩溃**(未 COMMIT 直接 `Close`)后再打开 ⇒ `aps` **2 行完好、`_new` 不存在** + ⇒ SQLite **回滚了未提交的 DROP**。 + ⇒ 真因: 本仓 SQLite 走 **`SetMaxOpenConns(1)`**(`db.go:91`)+ `Migrate` 用裸 `DB.ExecContext` + ⇒ 池里只有**一条**连接 ⇒ 裸 `BEGIN` 恰好落在同一连接 ⇒ **能用**。 + ⇒ ★ 但这是**巧合、不是保证**: 连接数只要 >1(**PG 分支就是 20**,`db.go:85`), + 跨连接的 `BEGIN` 就**不再构成事务** ⇒ 与 PG 侧 `DO $$…$$` 必须整体提交(`migrate.go:24-28`)同坑。 + ⇒ **结论**: 用 `BeginTx` **对**(显式、不依赖池大小);但**理由**应写成 + "**裸 BEGIN 的正确性依赖 `MaxOpenConns(1)`**",而不是"BEGIN 不生效"。 + ② pi: "守卫不能用 LIKE(我写 `'…PRIMARY KEY (…'` 带空格,而**实际无空格**)" + ⇒ ★ 我实测本仓实际文本 = `PRIMARY KEY (agent_name, platform_id)` —— **有**空格。 + ⇒ 它失配**真因**是**它模式里 `,` 后少了空格**(`'…agent_name,workspace%'` vs 实际 `'…agent_name, workspace'`): + 我两种模式都试 ⇒ 无空格模式**不命中**、带空格模式**命中**。 + ⇒ ⇒ 结论(该用 `pragma_table_info` 的 pk 列)**对**,但理由应写成 + "**LIKE 依赖 DDL 的空白/换行/引号形态,任一变即失配**"—— + 而不是"实际无空格"(那是把**自己模式的错**归给**被匹配的文本**;pi 这封自己也在讲同族坑)。 + ``` + ## (C) ✅ pi 自撤那条我也复核为真 + 两条附带事实 + ``` + · 它撤回"第二次 Migrate 会撞 PK 崩溃" ⇒ ★ 我实测 4 语句版**天然幂等**: run1/2/3 均 rc=0、行数不变 + (因 `RENAME aps_new→aps` 后 `aps_new` 名字**又空出来**)⇒ 撤回**正确** ✓ + ★ 真正的崩点是**陷阱1**(DROP 与 RENAME 之间崩),与它一致。 + · `main.go` **两次** `db.Migrate`(:39/:48)⇒ 重建**必须幂等** ✓ + · 全仓 `REFERENCES agent_platform_sessions` = **0** ⇒ 重建**不需要**处理 FK ✓ + (`PRAGMA foreign_keys` 在事务内本就是 no-op) + · `migrate` 内**无**任何 `RENAME TO`/`DROP TABLE`/`_new` ⇒ 今天**无**重建代码 ✓(与我早前结论一致) + ``` + ## (D) ★ ④ 的最终措辞(收 pi §四 的"顺序依赖可消掉") + ``` + ④ = 用 **Go 层事务**重建(`BeginTx`→新表→`INSERT SELECT`→`DROP`→`RENAME` + →**同事务内显式重建 `idx_platform_sessions_ws`**);守卫用 `pragma_table_info` 的 pk 列; + 并处理孤儿 `<表>_new`。 + ⇒ ★ 在同一事务内重建索引 ⇒ 与 DDL 批次前后**无关** ⇒ **顺序依赖消失**(pi 这点对,我收) + ⇒ 判据: **(a) 迁移路径断言 PK + (b) 断言索引存在 + (c) 断言无 `<表>_new` 残留** + ★ (c) 是**必需**而非可选 —— 否则 (A) 那个"37 行静默消失"的形态**无人发现**(我实测 (a)(b) 全绿) + ``` + ## (E) 边界: 只读 + 临时 Go 探针(均在 `internal/db/` 下建、**已删**,`git status` 已验干净);未改产品代码;本文件仅追加本段