跳转至

[ISSUE-0056] 第 11 轮 agent — v0.10.75 撤拦截后仍 4 个 quickFilter 漏

v0.10.75 commit 后立即第 11 轮独立 agent 审查。Agent 直击: "撤拦截 + saveParams 只透传 taskId/hasEmailOnly = quickFilter 5 个里漏 4 个!"

真 bug 列表

🔴 #1 saveParams 漏 4 个 quickFilter → 全表删风险

saveParams.filters = filters(仅 state.filters,不含 quickFilter 转译)。 而 dataRun 走的 mergedFilters = [...filters, ...quickFilterAsFilters(quickFilter)] 含 5 个 quickFilter 转译。

链路: - quickFilter='has-website' → quickFilterAsFilters 返回 [{property:'website',operator:'notNull'}] - saveParams.filters = [] (空,遗漏) - 用户选"全部" → selectOption='all' + saveParams 给 resolveTargetRows - needsClientFilter = !!taskId || !!hasEmailOnly = false - → baseQuery = exViewFilterToQuery([], '', 'and') = {} ← 空 query - → deleteByQuery({}) → jsstore native clearData(MapTaskData) 全表 22w 删!

修法: 1. saveParams.filters = [...filters, ...quickFilterToFilters(quickFilter)] 2. helper quickFilterToFilters 提前到 saveParams 上方(避免 TDZ) 3. getTableData 也复用同款 helper(去掉重复定义)

🔴 #B assertNonEmptyQuery 兜底(防线 + 1)

即便 saveParams 错了,server 端最后一道防线:

function assertNonEmptyQuery(query: any, selectOption: string): void {
  if (selectOption === 'current') return;
  if (!query || typeof query !== 'object') return;
  if (Object.keys(query).length === 0) {
    throw new Error('拒绝执行:批量删除/导出的 server where 为空 — 见 ISSUE-0056');
  }
}

apiLocalDataDelete + apiLocalDataExport 调用 deleteByQuery / getListByQuery 前 assert。 未来任何 caller 漏传 filter → 抛错而非删全表。

🔴 #2 export selectTotal cap

const cap = Math.min(selectTotal || HAS_EMAIL_SCAN_LIMIT, HAS_EMAIL_SCAN_LIMIT);
const { list } = await getListByQuery(jsstoreQuery, 1, cap, sort);

selectTotal 可能 = 22w → 单次拉 22w 行 → 主线程秒级阻塞 / 可能 OOM。

cap 在 50k(与 hasEmailOnly 同款上限),> 50k 数据用户分批导出。

🔴 #3 resolveTargetRows 'front' + computed sort

用户按 quality desc 看前 100 → 选"前 100" → 旧版传 sort 给 jsstore → jsstore 不识别 quality 静默忽略 → 拿到的是 id 默认序前 100(不是 quality 前 100)!

修法:'front' + hasComputedSort 时走全表 + applyClientSort + slice 与 List 路径一致。 两条路径都加: - needsClientFilter=false 时(无 taskId/hasEmailOnly) - needsClientFilter=true 时(scanned 已在内存,applyClientSort 后再 slice)

元洞察(agent 直击)

"v0.10.75 假定 taskId/hasEmailOnly 是仅有的 client filter,但 quickFilter 5 选项里其它 4 个也是 client filter 的不同形态。修补策略仍是逐字段添加。真正治本:saveParams 把整个生效的展示集筛选统一编码(filters merged with quickFilter),让 resolveTargetRows 只看 filters。"

本轮按 agent 推荐做了治本(merge filters),且加 assert 兜底(defense in depth)。未来再加新 quickFilter 时必须同时改 quickFilterToFilters(不再有遗漏点)。

未做(agent #4 / 边缘)

  • #4 getListByTaskId 100k 硬编码:task > 10w 数据时漏后段。低优先级,下轮 ISSUE-0057 上。
  • #5 setMode useEffect 第一帧旧:UX 轻微,无害。
  • #6 双重 page reset:无害。
  • #7 client mode delete optimistic update:UX 改进。
  • keyword client vs server 行为差:v0.10.75 已知遗留。

audit_grep

- pattern: "saveParams\\s*=\\s*\\{[\\s\\S]{0,500}filters:\\s*filters\\s*[,}]"
  description: "saveParams.filters 必须 merge quickFilterToFilters(quickFilter),不能裸传"

严重度评估

  • v0.10.75 未收敛 — has-website/high-rating/scraped/pending 任一 chip + 选全部 = 删全表
  • v0.10.76 收敛 + 双保险:filter merge + assertNonEmptyQuery 兜底
  • 数据安全级别从 v0.10.74(拦截保护)回到 v0.10.75(部分撤销)再到 v0.10.76(治本+兜底)

相关

  • [[0055-saveparams-refactor-data-safety|0055-saveParams治本-数据安全长期重构]] — 上轮,撤拦截但漏 4 个 quickFilter
  • [[0054-round10-agent-a2-6-bugs|0054-第10轮agent-A2-6处真bug]] — A2 hybrid 引入这一系列
  • 修bug全字典扫描 — 5 个 quickFilter 全字典对齐