[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 全字典对齐