主题
44 · RVH 回执:删扫描页 → cloze 行仍在(doc 23 §7.3 / 待验项 C)
方向:RVH → RB 回的是:
docs/cross-end/23-rb-ocr-cloze-context-decision.md§7.3(验证建议第 3 条) 在 relay 台账里的编号:待验项 C,docs/verification/cross-end-cloze-pool-2026-08.md收尾时记为「🚫 未验」——阻塞原因见 §1。 日期:2026-08-28 · RVH HEAD517179d· 真机 Xiaomi 24094RAD4C / Android 16 账号:<测试账号 A>·user_id = <test-user-id-A>(与 relay 同一个)
0. 一句话
C 成立:删扫描页不会带走它贡献的 cloze 行(本地 33 条、云端 33 条,deleted_at 全为 null)。
但先修后验 —— relay 收尾时 C 被标成「不排期」,理由是 RVH 的删页当时是硬删 + FK CASCADE,那种状态下跑 C 会把坏行为记成「既有行为」。该缺陷已于同日修复(软删 + 手工级联,红线 #6e/#6f/#6g),本次是在修复后的实现上验的。
1. 为什么之前不能验
relay 台账 §0 把 C 标为「🚫 本轮不排期」,原文理由成立,这里补上代码位置:
reading_page_datasource.dart:131 deleteReadingPage db.delete ← 硬删
:580 deleteReadingPages db.delete ← 硬删(批量)
01_create_tables.sql:377/403 word_page_links.reading_page_id ... ON DELETE CASCADE在那个实现下,「cloze 行仍在」这个观测会成立,但成立的理由是错的:页行被物理删掉、 删除信号根本不上行,同时 CASCADE 把 word_page_links 的墓碑一并抹掉。 把这种状态记成「§3.1 第 1 条的既有行为,别当 bug 报」,等于给一个真缺陷发了通行证。
修复见 RVH commit 517179d(软删 + 同事务手工级联子树 + deleted_at/updated_at 同值), 13 例回归 + 四处注入缺陷实证。
2. 夹具
刻意不用 relay 的 cave,也不碰 B 轨的 silence。 新造一页地质题材的渲染页, 经相册导入走完整 OCR → 收割链路:
| 项 | 值 |
|---|---|
| 笔记 | CROSSEND-C-FIXTURE(45df2428-e6ed-49df-8f52-3551206ff69d) |
| 页 | 1f77e68e-2567-4800-929a-fb8aa54b1e63(Scan 2026-08-28 11:50 #1) |
source_ref | NULL ← doc 23 §3.1 第 1 条的根 |
| 收割 | Cloze: 33 词入库,33 词通过闸门,新增 33 条语境 |
33 条 cloze 的 source_url | 33/33 全为 NULL(doc 23 §3 裁定:不造合成 URL) |
删除前已 sync 到静默(pushed=95, pulled=34 → 连续两轮 pushed=0), 云端该页 deleted_at = null、server_updated_at = 2026-08-28T03:51:35.279338+00:00。
3. 动作与观测(原始输出)
动作:来源图片列表里点垃圾桶删除该页(reading_page_list_page.dart:112 那条 UI 路径)。 确认弹窗文案:Delete "Scan 2026-08-28 11:50 #1"? Associated word links will also be removed.
3.1 本地(删除后、sync 前)
page_row_exists 1 ← 行还在(软删,不是物理消失)
page_deleted_at 2026-08-28T04:24:08.457491Z
page_updated_at 2026-08-28T04:24:08.457491Z ← 与 deleted_at 逐位相同(#6g W)
links_rows 33
links_tombstoned 33 ← CASCADE 下这 33 行会整个消失
cloze_still_active 33 ← ★ C 的判据
learning_entries 17/17 活(抽查 20 个词) ← 词仍在生词本,只失去这个出处3.2 sync 两轮
Sync complete: pushed=0, pulled=0, syncStartAt=2026-08-28T04:23:53Z ← 删除前
Sync complete: pushed=34, pulled=34, syncStartAt=2026-08-28T04:24:53Z ← 1 页 + 33 link 墓碑
Sync complete: pushed=0, pulled=0, syncStartAt=2026-08-28T04:25:53Z ← 无回声环3.3 云端(Supabase)
user_reading_pages / id=eq.1f77e68e-…
deleted_at 2026-08-28T04:24:08.457491Z
updated_at 2026-08-28T04:24:08.457491Z
server_updated_at 2026-08-28T04:24:54.422534+00:00
source_ref null
user_word_page_links / reading_page_id=eq.1f77e68e-… , deleted_at=not.is.null
count = 33,33 行的 deleted_at 全部 = 2026-08-28T04:24:08.457491Z
user_word_cloze_contexts / created_at=gte.2026-08-28T03:50:40 , deleted_at=is.null
count = 33 ← ★ 一条没少,source_url 全为 null
behind / beneath / boundary / carry / downstream / drill / explore / far / fine /
geology / glacier / grind / ancient / ice / imagery / leave / mark / measure / news /
powder / range / retreat / satellite / science / sediment / shape / stake / summer /
surface / thin / track / valley / whole判定:✅ C 成立。
4. 🔴 一处订正:现在「为什么仍在」的理由变了
doc 23 §3.1 第 1 条给的理由是:
级联键是
source_url,NULL 匹配不上任何行 ⇒ 在 RVH 删掉某张扫描页不会带走它贡献的 cloze 行。
那句话描述的是 RB 侧页级清理(notes.rs:1010-1014 按 source_url = reading_pages.source_ref 精确等值)。RVH 侧的理由不是这个 —— RVH 的删页路径压根不碰 word_cloze_contexts:
deleteReadingPage 事务里只有两条 UPDATE:
① UPDATE reading_pages SET deleted_at = ?, updated_at = ? WHERE id = ?
② UPDATE word_page_links SET deleted_at = ?, updated_at = ? WHERE reading_page_id = ?两个理由都指向同一个观测,但来源不同、失效条件也不同:RB 那条会在 source_url 恰好 等于某个 source_ref 时改变行为(这正是 doc 23 §3 禁止造合成 URL 的原因); RVH 这条是结构性的,与 source_url 取值无关。记下来是因为日后若有人给 RVH 的删页加 「按来源清语境」,读 doc 23 会以为 NULL 就万事大吉。
5. 🆕 需要 RB 确认的一件事(本次修复带来的新暴露面)
修复前 RVH 的删页不上行;现在会了。 于是 RB 会开始收到来自 RVH 的 user_reading_pages 墓碑——这是 relay 期间不存在的输入。
请 RB 确认:pull 到一条页墓碑时,会不会触发那条按 source_url 的页级级联清理?
- 按 RVH 的读码理解,那条清理长在 RB 的本地删除命令里(
notes.rs), pull 路径不会调它 ⇒ cloze 行在 RB 侧同样保留,与 C 的结论一致。 - 但这是 RVH 读 RB 代码得出的推断,没有实测,且 RVH 这侧的 cloze 行
source_url恒为 NULL、匹配不上任何source_ref,就算真调了也应当无事发生。 - ⇒ 判断是「双保险,应该没问题」,但请 RB 自己核一遍,不要采信我的读码结论。
顺带:RVH 侧 word_page_links 的墓碑现在也会随删页上行(本次 33 条已实测到云端)。 按红线 #6f,RB 拉到后应把对应本地 link 打成墓碑,不需要额外动作。
6. 遗留
✅ 夹具已清理(2026-08-28,用户确认后执行)。顺带又是一次 #6e 的实证:
reading_notes deleted_at = updated_at = 2026-08-28T05:19:13.928966Z ← 逐位相同(#6g W) reading_pages 墓碑 1 / 活 0 learning_entries 33/33 仍在生词本 ← #6e 语义:词仍在,只失去这个出处 word_cloze_contexts 33 条仍活跃 ← 删笔记同样不带走语境 Supabase 笔记墓碑 server_updated_at = 05:19:16.546447+00:00(删后约 3 秒) 之后连续 4 轮 pushed=0 ← 无回声环确认弹窗文案与 #6e 语义一致:
Its reading sources and word links are removed too. The words stay in your notebook.33 个词(glacier/sediment/moraine…)作为正常收割产物保留在生词本。 推到设备相册的三张测试图也已删除。doc 23 §7.3 可以标为已验;relay 台账已停止更新,故结论落在本文件。
7. 相关
- doc 23 §3.1 / §7.3 —— 本文件回的就是它
- relay 台账
~/reading-browser/docs/verification/cross-end-cloze-pool-2026-08.md(已收尾) - 红线 #6e / #6f / #6g —— 修复所依据的三条
- RVH commit
517179d(软删修复 + 13 例回归);backlog 对应条目已标 ✅