跳转至

[ISSUE-0071] SPEC-006 cascade 删命中 ISSUE-0008 历史回归

agent code review 在 v0.10.86-102 改动中发现的 🔴 严重 #1。 v0.10.102 SPEC-006 实施时没查历史文档,直接 removeByQuery({taskId: id}),命中 v0.10.2 ISSUE-0008 的老 DB 升级坑。

病灶

坑的历史

v0.10.2 ISSUE-0008(无独立 ISSUE 文件,search-data.ts:67-91 注释 明确):

jsstore where: { taskId } 在升级过的 DB 上 — 即使 row.taskId 有值 — 也容易给 0 行。 早期 DB schema 没该列,jsstore 升级后 enableSearch 索引不完整。

v0.10.102 漏查 + 踩坑

SPEC-006 实施时 2 处用了不安全写法:

  1. task-manager.ts:340 removeByQuery:

    if (payload?.deleteData === true) {
      await removeByQuery('MapTaskData', { taskId: id });  // ❌ 老 DB 删 0 行
    }
    

  2. task-delete-confirm-dialog.tsx:77 countByQuery:

    const n = await countByQuery('MapTaskData', { taskId: t.id });  // ❌ 老 DB 给 0
    

用户可见后果

场景 表现
老用户(v0.10.0 之前升级过) Dialog 显示"0 条商家数据"
用户选"同时删 N 条" 商家数据没删 → 孤儿
用户以为删干净 实际 TaskFilterPicker"已删任务"分组里还有 N 条

修(v0.10.103)

task-manager.ts — 改 select id 再 remove by id

if (payload?.deleteData === true) {
  const allRows = await selectByQuery('MapTaskData', { limit: 200000 }) as any[];
  const idsToDelete = allRows
    .filter(r => r && r.taskId === id)
    .map(r => r.id);
  if (idsToDelete.length > 0) {
    await removeByQuery('MapTaskData', { id: { in: idsToDelete } });  // ✅ id 主键安全
  }
}

Dialog — 改 countAllByTaskIds

import { countAllByTaskIds } from '@/utils/jsstore/search-data';

const countMap = await countAllByTaskIds(200000);  // ← search-data.ts 已有的安全 helper
const total = ids.reduce((sum, tid) => sum + (countMap[tid] || 0), 0);

附带优势:批量删 50 任务也只扫 1 次(之前串行 50 个 count,5s 卡顿 → 200-500ms)。

元教训

  1. CLAUDE.md 高频踩坑速查表里说"读源码顶部注释" — 我没读 search-data.ts 顶部就直接用 jsstore — 100% 踩坑路径
  2. 历史 ISSUE 没独立文件(v0.10.2 ISSUE-0008 只在注释里)→ 代码搜索难。所有踩坑应该写独立 ISSUE 文档 + audit_grep
  3. agent code review 必须做:本次纯靠自我审查会漏;agent 找到了
  4. SPEC 实施时:除了 SPEC 本身设计,应该 grep "相关字段"(taskId)所有历史使用,看是否有 anti-pattern 注释

audit_grep 防退化

audit_grep:
  - pattern: "removeByQuery\\([^,]*MapTaskData[^,]*,\\s*\\{[^}]*taskId:"
    description: "MapTaskData remove 不允许直接 where:taskId,必须先 select id + remove by id"
  - pattern: "countByQuery\\([^,]*MapTaskData[^,]*,\\s*\\{[^}]*taskId:"
    description: "MapTaskData count 不允许直接 where:taskId,必须用 countAllByTaskIds"

相关