跳转至

SPEC-010 — 当前缺陷止损清单

0. 本文与 SPEC-009 的关系(先读这段)

🔴 本文不是方案设计,是缺陷清单。

SPEC-009(见下方位置说明) 本文 SPEC-010
建于 2026-08-25(worktree codex/phone-country-cloud-spec 2026-08-30
体量 1543 行 / 19 章 本文
回答 要建成什么样 — 数据模型、同步协议、验收 Fixtures、迁移、回滚 现在坏在哪 — 当前代码正在生效的破坏
状态 G1 BLOCK(2026-08-30 terra+dshf 双路终审) draft

两份工作互补,不是重复。 SPEC-009 的 G1 终审报出的 2 个 P0 全部出自本文的缺陷发现, 其正文对两者零提及:

  1. 旧 V1 云通道盲区 — 见 0091-sync-flag-not-reset-on-fast-paths
  2. 点分隔电话 cleaner 违反 SPEC-009 §3 第 2 条不变量「原文不可丢」 — 见 0089-dot-separated-phone-destroyed

🔴 SPEC-009 当前不在本仓库:它是 worktree codex/phone-country-cloud-spec (路径 ../02_laifaxin-chajian-ditu-g1-spec)里的未提交文件 docs/specs/active/SPEC-009-contact-normalization-cloud-ai.md。 因其尚未进入 git,本文不使用 wikilink 指向它,避免「声称即实存」。 它提交后应把本节的文本引用改回 wikilink。

SPEC-009 解除 G1 BLOCK 依赖本文的止损动作先落地。 本文的 §4「14 项裁决」与 SPEC-009 的 §4/§19 存在部分重叠,以 SPEC-009 为准; 本文独有的价值在 §5 缺陷清单与 §6 Phase 0 止损。

1. 结论

14 项拟议规则里:ACCEPT 4 项(#2/#5/#8/#12)、MODIFY 6 项(#1/#4/#6/#7/#11/#14)、REJECT 3 项(#3 规则层否决 + #9/#10 本轮不做)、改为复用评估 1 项(#13) = 14。 另有 4 个决策点必须 Tony 拍板(见 §8),未拍板前对应 Phase 不启动。

先修两个正在生效的数据销毁路径(0088-delete-no-email-filter-drops-all-rows / 0089-dot-separated-phone-destroyed),再谈规则升级。

2. 审查方法与证据等级

执行方 产出
1 Claude 主控 + 16 子 agent(7 sonnet 侦察 / 7 opus 证伪 / 2 opus 裁决) 初版 findings
2 主控逐条复核 file:line 推翻 1 条假阳性(MapTaskData.sync 因果链)
3 terra(openai/gpt-5.6-terra) + dshf(deepseek-v4-flash) 跨厂商双路盲化 推翻 1 条存在性结论、修正 4 处行号/表述、新增 1 条 P1、改 1 项裁决
4 同双路做落档前复审 两路均 NO-GO(terra 3.8 / dshf 7.5),提 4 条 MUST_FIX
5 同双路做落档后复审 两路均 NO-GO(terra 4.8 / dshf 5.5),提 5 条 MUST_FIX;X_UNFIXEDX_SELFREF_OK=NO 两家字符级一致
6 sonnet5(与主控同厂商)10 agent 五 lens 专攻三方共识 推翻共识 1 条(C3)、削弱 1 条(C1)、新增 2 个 issue(0090-manifest-host-permission-domain-typo / 0091-sync-flag-not-reset-on-fast-paths)、指出 Phase 0 门槛不可满足

双路读同一固定快照、独立 session、互不共享结论。轮 3 评分 terra 7.1 / dshf 6.8;轮 4 terra 3.8 / dshf 7.5。

3. 当前实现真实状态

🔴 as-of 声明:本节所有结论基于 commit f25b565(v0.10.124,2026-05-31)+ 工作区 SPEC-008 在制品, 核验日期 2026-08-30。Phase 0 起任何一次实施都会使本表部分失真 —— 每个 Phase 结束时必须重新核验本表, 不得把本表当作后续 Phase 的现状依据。

存在性结论按 capability-existence-proof 的三态标注:

能力 证据
DNS / NS / MX / A 查询 ABSENT 已查 5 类:库名(dns/doh)、API(chrome.dns/browser.dns/resolveMx/nameserver)、自研动词(resolveXxx/lookupXxx)、中文(解析/域名查询)、文件名(*dns*) — 三方各自复验零命中
libphonenumber / E.164 实现 ABSENT 5 类全零:库名(libphonenumber)、平台 API(无)、自研动词(toE164/formatE164/parsePhoneNumber/normalizePhoneNumber)、中文(号码格式化/国际格式/区号解析)、文件名(*e164*/*phone-format*) — 仅 scraper.ts:200 注释提及 E.164 15 位上限
Markdown 导出 PRESENT 入口 fetch-snapshot-list.tsx:103(按钮「导出 Markdown」)→ 副作用 :62 → 实现 fetch-snapshot.ts:231
HTML→Markdown 转换 ABSENT 5 类全零:库名(turndown/remark/html-to-md/html2md/showdown/marked)、平台 API(无)、自研动词(toMarkdown/htmlToMarkdown/convertToMarkdown/renderMarkdown/parseMarkdown)、中文(转.*Markdown/转换.*格式)、文件名(*markdown*/*turndown*) — 自研仅覆盖快照导出,不含 DOM 转换
截图 / OCR ABSENT 5 类全零:库名(html2canvas/dom-to-image/puppeteer)、平台 API(captureVisibleTab/tabCapture/toDataURL)、自研动词(takeScreenshot/captureScreen/grabImage)、中文(截图/识别)、文件名(*screenshot*/*capture*/*ocr*)
AI 调用 ABSENT 5 类全零:库名(openai/anthropic/openrouter)、平台 API(chat/completions)、自研动词(callLLM/askAI/promptModel/generateCompletion)、中文(智能分析)、文件名(*ai-*/*llm*/*prompt*)
业务单元测试 ABSENT 5 类全零:①库名(vitest/jest/@testing-library/mocha/chai — 裸 grep 的 jest/chai 经核实为子串误匹配 OranJESTad/writeCHAIn)、②平台 API(describe(/expect(/beforeEach 零命中;it( 裸 grep 76 次经逐条核实全为子串误匹配 —— clearTranExit(34 / split(32 / handleSubmit(10 等,加词边界后为零)、③自研动词(runTests/assertEqual/mockChrome 零命中)、④中文(测试 命中 5 处,全为业务文案:regex-tester.tsx:7,97 正则测试器 UI、settings-view.tsx:1605scraper.ts:111 的「测试号黑名单」,无测试设施)、⑤文件名(*.test.ts(x)/*.spec.ts(x)/vitest.config.*/jest.config.* 全零);package.jsontest:* 五项全为治理脚本自测
邮箱提取 PRESENT 三路:mailto scraper.ts:141-163、cf 反混淆 :226(定义)/:376-384(调用)、自由文本 :134+:350-374
电话提取 PRESENT 三层 Tier scraper.ts:202-208;塌缩成单值 scraper-executor.ts:30
国家识别 PRESENT(单一来源) 仅地图 addressInfo:parse-data.ts:40 / :121。无 JSON-LD / ccTLD / 语言推断 / 冲突检测
云端同步( SPEC-004 ContactPool 管线) PRESENT(客户端就绪,服务端未上线) enableCloudSync: false 默认(storage-data.ts:253);cloud-sync-panel.tsx:4 自陈服务端未上线;目标域名 cloud.laifaxin.com
云端同步( MapTaskData 通道) 🔴 PRESENT 且当前活着 cloud-data-sync.tsxmain-layout.tsx:429 无条件挂载pollingInterval: 10*1000 每 10 秒轮询;启动条件仅 uid && mapDataSync(服务端下发 + VIP),enableCloudSync 无关;打生产域名 https://ggdt.laifaxin.comapi/request.tsx:24-26)。详见 0091-sync-flag-not-reset-on-fast-paths
pnpm verify 17 段 && 切分实测;对纯函数正确性零证明力(无业务单测)

国家字段的可见性(Phase 3 依据,勿简化)countryCode 不在主表默认列 DEFAULT_FIELDSfields.ts:36-137), 但 CSV 导出列 exportFieldsfields.ts:181,title「国家」)。 ⇒ 国家已有出口,缺的是主表可见性与筛选能力,不是从零建。

4. 14 项裁决

# 拟议规则 裁决 Phase 依据
1 NS 作为邮箱域名存在性主判断 MODIFY 待拍板(§8-2)后单独立项,不在 Phase 0-6 DNS 为 ABSENT ⇒ 纯新建;DoH 隐私未决。Phase 1 只建状态字段与 rawValue 保留,不查 DNS
2 MX/A 只记录,不做 SMTP 探测 ACCEPT 同 1,随其一起 方向对,无独立价值
3 点号分隔电话一律判无效 REJECT 双路一致:点号是法/比合法格式。详见 §7 三层说明
4 电话字符白名单 MODIFY Phase 1 白名单可做,但必须放行点号tel: 来源(Tier 1)不过噪音过滤
5 一值一行 ACCEPT Phase 2 terra 论证:保留单 string 只改渲染层无法修复 P1-09 / P1-18 / P1-19。须建立「独立值 / 来源 / 规范化键 / 展示聚合」四层边界
6 国家未知进 PENDING_COUNTRY MODIFY Phase 3 国家已在导出列可见(fields.ts:181)但不在主表默认列 ⇒ 加状态字段须同时加主表可选列 + 筛选器,否则状态无出口
7 无加号号码国家推断 MODIFY Phase 1(修键)+ 待拍板(推断) 先修「取后 10 位」归一(P1-06,当前真 bug),推断逻辑依赖 §8-1
8 导入域名不强制选国家 ACCEPT Phase 3 双路一致支持。附加固条件:可选 hint 标 user-declared 且不计入证据链、导入后显示「N 行国家未知」、支持 CSV 带国家列
9 AI 只处理 PENDING/CONFLICT REJECT(本轮) ABSENT + 无后端 + key 归属未定 + 商店审查口径未评估
10 截图辅助 REJECT(本轮) ABSENT;无证据表明文本不足是瓶颈
11 持续上传云端 MODIFY(阻断方案本身) Phase 4 七个客户端缺陷是硬前置(P1-02 / 11 / 12 / 14 / 15 / 16 / 20),与 §6 Phase 4 一致。不阻断当前版本(默认关闭)
12 本地容量上限 ACCEPT Phase 4 三张表确无上限(P1-15),manifest 未声明 unlimitedStorage
13 Markdown 不整页存本地 改为复用评估 Phase 5 Markdown 导出为 PRESENT,先评估现有实现够不够,不新建
14 文档同步机器闸 MODIFY Phase 6 Docs-Impact 只验「路径存在 + staged」,不验内容真改

5. 阻断项

P0(当前生效,两路各自独立复现)

P1 完整清单(22 条)

编号 问题 位置
P1-01 「只保留企业邮箱」是死代码,条件在唯一调用点恒 false jsstore/search-data.ts:293
P1-02 writeCloudRecords 绕过全部邮箱质量防线(无 dedup / lowercase / broken 过滤) contact-pool.ts:209-233
P1-03 EMAIL_REGEX 无左右边界锚定,{1,64} 只截断不拒绝 ⇒ 产出外观合法的假邮箱 scraper.ts:134
P1-04 基线假绿:零 src 运行时单测,pnpm verify 17 段对邮箱/电话链路正确性零证明力 package.json verify
P1-05 PHONE_TEXT_REGEX 分隔符类不含括号,裸括号区号电话 Tier3 抓不到 scraper.ts:205-206
P1-06 电话归一取后 10 位 ⇒ 跨国误合并(+44 20 7123 4567 与美国 (207) 123-4567 同键,后到者被丢弃) scraper-executor.ts:36-39
P1-07 网址批量导入的商户行没有 countryCode 键,与 Maps 路径的空字符串是两种不同的「空」 task-manager.ts:201-205
P1-08 handleError 不区分永久性与瞬时错误,全部走同一条重试路径 cloud-sync-orchestrator.ts:349-366
P1-09 来源标签 (地图) 拼进持久化 phone 值,且 .join(', ') 多值拼串 scraper-executor.ts:61
P1-10 CSV「创建时间」导出 13 位毫秒戳;exportFieldsvalueType 使格式化分支成死代码 fields.ts:139-248 + export.ts:15-16
P1-11 整批无条件 markUploaded,忽略服务端逐条 accepted/rejected cloud-sync-orchestrator.ts:106-113
P1-12 匿名 clientId 与产品账号割裂,登出/切号不清空不轮换 auth-provider.tsx:80-84 vs cloud-sync-client.ts:18-19
P1-13 三张云端表无容量上限或过期清理,manifest 未声明 unlimitedStorage contact-pool.ts / contribution-ledger.ts / domain-state
P1-14 首次同步三路并发匿名注册竞态,clientId/token/userId 可落成互不匹配 cloud-sync-orchestrator.ts:292-296
P1-15 云端拉取覆盖本地待上传记录,被覆盖行永失上传资格 contact-pool.ts:209-235 vs :178
P1-16 ContributionLedger 被拒条目形成永久重投循环,长期占据 FIFO 队首 cloud-sync-orchestrator.ts:207-224
P1-17 脱敏正则漏过 tel: 链接的连续数字,sanitized:true 与实际内容不符 fetch-snapshot.ts sanitize
P1-18 云端表格 WhatsApp 深链用未拆分/未编码的合并多值 phone 拼 URL cloud-table.tsx
P1-19 详情抽屉 tel: 对合并多值串做纯字符过滤,多号码拼成一个乱码号 merchant-detail-drawer.tsx:396
P1-20 writeContactRecord 不与 existing 取并集,本地少量结果覆盖云端共识并重置为 pending contact-pool.ts:75-100

已升级移出(不占 P1 编号):坐标过滤误杀点分隔电话并删存量 → 已升 P0,见 0089-dot-separated-phone-destroyed

| P1-21 | 两条快路径漏重置 sync:0,补写的社媒/电话永不上传旧云通道 | scraper-executor.ts:148 / :163,见 0091-sync-flag-not-reset-on-fast-paths | | P1-22 | manifest host_permissions 域名拼写错误(潜伏,被 <all_urls> 掩盖) | wxt.config.ts:28,见 0090-manifest-host-permission-domain-typo |

(清单实为 22 条,编号连续。)

已降级(不计入)rows as any 双层 any(→P2,无已触发的错误形态)。

🔴 一条降级已被推翻:「忘重置 sync:0」曾被主控降为 P2,理由是「MapTaskData.sync 无云端消费方」—— 该理由错误:主控只查了新 SPEC-004 管线(cloud-sync-orchestrator.ts),漏了旧通道 cloud-data-sync.tsx:39,71。现已恢复为 P1-21。

P1-02 / 11 / 12 / 14 / 15 / 16 / 20 的定级口径:默认关闭(enableCloudSync: false)故当前 P1; 一旦服务端上线或用户填入自建地址,立即成为上线阻断

6. 实施计划

Phase 修改范围 进入下一阶段的门槛
0 ISSUE-0088;ISSUE-0089 的止损部分(停用存量销毁,见 §7);引入 vitest 见下方门槛说明
1 电话清洗:白名单放行点号(判据待 §8-1)+ 去重键改「完整 digits + countryCode」+ 保留 rawValue / rejectionReason + P1-05 括号 攻击样本全绿;P1-05 括号号码可抓
2 一值一行数据契约(四层边界),含 P1-09 / P1-18 / P1-19 tel: 与 WhatsApp 深链对多号码正确;无标签进数据值
3 国家:状态字段 + 主表可选列 + 筛选器 + 导入可选 hint + P1-07 键缺失 国家未知行可在主表筛出
4 云端七前置:P1-02 / 11 / 12 / 14 / 15 / 16 / 20 + 容量上限 P1-13 服务端上线前不合并;P1-12 身份绑定方案须先经 §8-4 拍板并有验收用例
5 AI / 截图不做;Markdown 走复用评估(现有 exportSnapshotsAsMarkdown 是否够用) 评估结论落档,不写代码
6 产品文档补邮箱/电话规则;Docs-Impact 增加机械判据校验(声明的文档路径在本次提交中 diff 非空),🔴 不做业务语义判断SPEC-008-doc-function-sync-governance §3.2 已明确否决「业务语义自动判断」与 diff-glob 推断,本 Phase 不得越过该边界 负向用例:声明了路径但该文档 diff 为空 ⇒ 拒绝

🔴 Phase 0 退出门槛(修正版) —— 原写「两个 P0 各有失败→通过的回归测试」对 ISSUE-0088 不可满足

P0 缺陷所在文件 可测性 门槛
0089 scraper.tsgrep -c '^import' = 0,纯函数模块) vitest 直接可测 必须有失败→通过的单测
0088 jsstore/search-data.ts(import wxt/storage + jsstore/base,后者用 workerInjector 真实 Web Worker + IndexedDB,jsdom 下无官方 mock) 单测不可行 改为:①对被抽出的纯判定函数做单测,或 ②扩展浏览器运行时手工验证并留存证据(截图 + 日志),二选一

⇒ 不得以「给 scraper.ts 写了测试 + pnpm verify 绿」宣告 Phase 0 完成 —— 那样 0088 会零测试覆盖通过。

Phase 0 不可修改:任何 schema / jsstore/base.ts / cloud-sync-* / 9 个 scanner baseline / verify 的任何一段。

每个 Phase 结束时必须重新核验 §3 的 as-of 表,并更新本文 updated 字段。

7. 点分隔电话的三层区分(避免误读)

这三件事不矛盾,是三个层次:

内容 状态
规则层 拟议规则第 3 项「点号一律判无效」 已裁决 REJECT — 采纳它等于把 ISSUE-0089 固化成规范
止损层 停止 cleanPollutedContactPool 继续销毁存量 Phase 0 立即做,不需拍板 — 停止破坏不等于选定新判据
判据层 新的坐标 vs 电话判别器往哪边偏 待 §8-1 拍板 — 这是产品市场取舍

⇒ Phase 0 只做止损层;判据层落地在 Phase 1,前置是 §8-1。

8. 待 Tony 拍板(未解除的 BLOCK)

  1. 点分隔电话收不收 — 收 = 放宽坐标判据、承担 GPS 噪音回流;不收 = 放弃法/比/瑞电话采集。双路审查均确认这是产品市场取舍。阻塞 Phase 1 的判据层
  2. 能否把客户的潜客域名送第三方 DoH — 属对客户的数据承诺范畴。阻塞裁决 1/2 的立项
  3. 一值一行迁移的不可恢复损失slice(0,5) 已截断的号码、(地图) 有损编码导致来源不可反推。阻塞 Phase 2 的迁移动作(非渲染改造)
  4. cloud-sync 匿名身份与产品账号的绑定方案阻塞 Phase 4

9. 非目标

  • 不建复杂邮箱评分系统
  • 不重写插件
  • 不建密码学审查系统
  • 不为「完整」而建无维护者的超大数据模型 — 每个新字段必须有明确读者