OrderPin · ISO 白牌获客

Outreach 闭环看板

OrderPin 的 ISO 白牌获客外联系统——从找线索到 CRM 商务跟进共十一个环节,目标是把它跑成一条闭环。

外联实际是两套并行系统系统 A(Hermes run.pyiso_outreach_tracker.json)每个工作日 20:00 真的在发信系统 B(Supabase outreach_sequences)是本看板读的账本,冷发引擎当前停用。实测 tracker ⊂ Supabase,所以要解决的是状态同步方向,不是对齐两套数据。

本看板的数字若无特别说明均属系统 B;每次更新看板全部数字实查,不沿用上一版。

2026-09-04 23:18 HKT · 全部数字本版实查
(= 2026-09-04 15:18 UTC)
数据实查自生产库 · tracker · launchd · run.log
时间窗一律 current_date − N,本版边界 2026-08-03 00:00 UTC
cron 自动运行中
01

数据现状 · 本版实查

结论:三段都在跑,但没接在一起。唯一在真产出的是发送段,它靠一份存量名单,T1 口径还剩 6.8 个工作日(2026-09-04 22:24 HKT)。
建议:唯一队列已定 = Hermes tracker,DB 队列降为「候选供给态」;接线已做到 dry-run(候选 25 人),验证与写入两步等 Graham 批准 —— 之后加工段的产物才有地方去。
原因(全部本版实查):供给段断供 25 天(最后一条真线索 2026-08-08 10:00:16Z); 加工段 kp_finder 两条腿脚本层真实输入都是 0; 发送段真发送器只读 Hermes tracker,DB 队列零邮件消费者
⚠️ 本版把 18 条旧说法登记为已废(见 check_dashboard_consistency.pyBANNED), 其中 3 条是本会话自己先说错、后自查翻的:探针存量 592(实为 663 全集口径)、 「假数据在染绿流水线监控」(v_cron_last_success 今日才建,不可能有历史染绿)、 「kp_finder 在加工 64 条假数据」(脚本层过滤后真实输入是 0,不是垃圾,是空)。

⚠️ 本版有 3 组数字在实时移动,「绿」是看的时刻的函数。盖章时刻 = 2026-09-04 23:18 HKT
· 邮件正文入库 —— 回信扫描每 30 分钟增量跑,9/3 的 20 分钟内从 2,121 → 2,175 → 2,284;9/4 17:35 已到 2,329、22:34 已到 2,361
· 线索总行 / 待发 —— LinkedIn 在实时写入(近 2 小时 14:10 桶 2 行、14:20 桶 5 行)
· 批 3 → 批 2 迁移 + 已触达 —— Hermes 每工作日 20:00 那轮在发,人从「待发」变成「已发」。 9/2 晚 20 分钟内连跳两次(38→39→40 / 79→78→77 / 94→95→96);9/4 17:32 复算 批 2 60 / 批 3 57 / 已触达 116,22:34 再复算 74 / 43 / 130(今晚 20:00 那轮发了 30),合计 311 恒定 ⇒ 是迁移不是新增。
引用这些数必须带时刻--live 判据的作用不是「让它永远绿」,是让漂移当场可见

三段速率 · 实查(速率行为 2026-09-02 22:33 HKT · 探针已清;余粮/闭合行已按 2026-09-04 更新,各行自带时刻)

管道吞吐 = 最窄那一段。粗管接细管,中间那截还没焊上。

实测复算路径
供给段 断供 25 天。近 30 天真线索 32 条(cra_ncr 17 + tavily 15),但只落在 6 个自然日(08-01~08-08);08-09 起非测试新增 0;这 207 行窗口内带 contact_email0/207 max(created_at) where source in ('cra_ncr','tavily_discovery') = 2026-08-08 10:00:16Z
加工段 kp_finder 两条腿真实输入 0 / 0(谓词口径 62 / 5)。主查询那 62 条 website 全 NULL,被 kp_finder.py:1874 客户端过滤;兜底那 5 条 discarded_at 全非空 谓词加 coalesce(btrim(website),'')<>'' → 0;兜底加 discarded_at is null + ecs<>'confirmed' → 0
发送段 唯一在真产出的段。逐日实发 8/26=142 · 8/27=88 · 8/28=92 · 8/31=138 · 9/1=33(周末 cron 1-5 不跑)。T1 余粮 6.8 个工作日(205 ÷ 30,2026-09-04 22:24 HKT,仅 T1 口径;9/2 22:33 时为 295) grep "sent ->" run.log | cut -c1-10 | sort | uniq -c;余粮 = tracker 中 status='queued' = 205(22:24 HKT,今晚 20:00 那轮已发 30);分母见 run.sh:60

两个队列的真实关系(不是「对齐两套数据」,是状态同步方向)

口径说明
DB「可发」四层收紧1,017 → 900 → 895 → 671①有邮箱∧outreach_status='queued' ②排已发 t1 ③排 unsub/bounce/reply ④排 status∈(disqualified,dead,paused)
DB queued 三桶闭合(9/4 17:46 HKT)1,017 = 235 + 357 + 425tracker 也 queued 235 · DB 标 queued 但 tracker 已发/已回 357 · tracker 没有 425。425 里 status='new' 138 = 旧默认值 108 + 桥推 30;经正向放行 ∧ 无已知坏结果 ∧ 名字可用 后能导入发送器的只有 25 人(9/4 22:24 适配器 dry-run,全集 311 = 133 + 148 + 5 + 25)
真·上膛205(2026-09-04 22:24 HKT;9/2 22:33 为 295)tracker 里 queued 且三个 tN_sent_at 全 NULL,逐条验 295/295 同形
DB 比 tracker 多425这些人没有任何路径会被发出
DB 陈旧357(2026-09-04 17:46 HKT;9/2 为 297)DB 标 queued,tracker 里其实已发过 ⇒ 只能回填,不能相加
t1/t2/t3 已发tracker 571 / 465 / 386;DB 276 / 211 / 187发送器只读 tracker ⇒ tracker 是发送真相源,DB 仅为其 48% / 45% / 48%
发送总量三口径run.log 1,512(含自测)· tracker meta 1,425 · tracker 逐条 1,422(纯外发)三个都复算成立,差 ~90;引用时必须点名口径

🔴 本版最重的一条:一处权限缺陷已修复

结论:外审发现一处数据库函数权限配置缺陷,未认证请求可读到线索数据。 已于 2026-09-03 修复并用重放验证关闭(原攻击路径现返回 permission denied)。
建议:把「SECURITY DEFINER 函数不得对 PUBLIC/anon 开放」做成上线前的例行检查项 —— 本次是靠外审偶然发现的,不是靠判据抓到的。
原因:涉事函数是 SECURITY DEFINER 且源码内无鉴权, 而 Supabase 的默认是函数对 PUBLIC 可执行 —— 两者叠加才成洞。
⚠️ 范围经逐个核实收窄:全库看似有 ~65 个同类函数,但其中绝大多数自带内部鉴权, 真正无守卫的是 8 个签名,已撤 7 个(第 8 个被 13 条 RLS 策略引用,撤了会打断 RLS,故保留)。
复现细节与完整因果链不在本页 —— 见仓内 RLS_AUDIT_PROBE_ROOTCAUSE_20260902.md 与 migration 20260903_130000

流水线已恢复 · 那条挂了一天的埋点判据出结果了

判据结果说明
14:00 那次应真跑成立 2026-09-03 14:00:10 -- daily_leadgen started (hour=02 ET) --,16:03 跑完(11 passed / 2 failed)。自 8/31 以来第一次真跑。
15:00 那次应跳过⚠️ 不可观测 🔴 是判据本身写坏了:14:00 那轮跑了 2 小时,15:00 时它还在跑,第二个候选根本没触发。我当时假设两次独立触发。DST 双候选机制仍未验证,要等冬令时或一次短跑。
phone_backfill 埋点验证生效 从「从未」变成 2026-09-03 08:02,24h 内 23 个事件
kp_finder 埋点仍未验证 不是埋点坏,也不是没输入 —— 是第三种:这轮它有输入(website_backfill 补了 62 个网站后队列就有料),但每个站都撞 chrome 基建故障 (ERR_BLOCKED_BY_CLIENT) → DEFER → 一个 KP 都没找到 → 1800s 超时。

🔴 已坏 / 已停的回路(本版实查)

实测根因
com.grahamcrm.scan-replies已坏 5.97 天(最后成功 2026-08-27 19:22:39),err.log 287 行去重后仅 1 行脚本从未进 mainfd250a9(08-27 19:35) 把它提交到 feat/apk-kds-batch-send,切回 main 即消失 —— 13 分钟因果链。修法是「launchd 不许依赖 git 工作树路径」
LinkedIn 发送最后真发出 2026-08-16;此后 8/17→8/31 约 443 次尝试全部 sent=0非磁盘。8/30 governor 一次没挡、放行 50 次仍 0 ⇒ 真因在浏览器侧(no_editor / not_connected
apollo_email_backfill82 次 fail-closed,100% 被吞成 ✅(总 ✅ 138)判据落在退出码而非产出 —— 「拒绝花钱」与「真干完活」在日志上不可区分
冷发域COLDSEND_DAILY_TOTAL=0停机第 13 天恢复条件:域龄 60–90 天,run.sh 注释自陈 2026-09 中下旬可重测
LaunchAgents 体检53 个 plist,指向不存在文件的 = 1(仅 scan-replies);launchctl 非零退出 7 条⚠️ 体检必须用 plutil -convert jsonplistlib静默漏 6 个
pipeline-stale-alert9/3 09:30 已首跑,正确报出 5 个步骤长期无产出首跑报的 website_backfill 118 天 正是清掉 601 行探针之后的真值 —— 不删的话它第一次开口就会少报 3 个月
1,571
线索总数 · 1,442 家
129 行是同公司重复
274
已触达(系统 B 账)
系统 A 实发 1,425 封
1,297
待发 · 虚高
≥357 已被系统 A 发过(9/4 17:46) · 含 30 条无人读
33
已回复
另有 23 条漏标未计入
9
仅「已约会」未开会
原 11,2 条已升为已开会
5
已开会 · 5 家公司
日历实证 · 单位是公司不是人
2,361
邮件正文入库
2,383 封中 99.1% 有正文
20
退信
退信率 7.30%
回复分类人数说明
有意向13LLM 从正文判定,置信度均值 0.82
已拒绝8含明确退订
已约会(仅约,未开)6回复者中;全库含未回信的共 9
其他5竞品来信、无实质内容等
已开会19/2 日历实证后从 0 变成真值(另 4 家未回信但开过会)
合计33= 已回复人数,仍然对上。本表每行「人数 = 公司数」(13/13、8/8、6/6、5/5、1/1),无同公司重复计数

约会率此前是 21 ÷ 32——分子取全库、分母取回复者,不同源,没有业务含义。后拆成触达约会率与回信转化率,标签里写明分母。
2026-09-02 已重算:分母取 t1_sent_at ≥ 2026-07-01249 人(排除 27 条 t1_sent_at 早于外联启动的历史污染行),分子取日历实证且会议晚于首次触达3 人 ⇒ 触达→真开会率 = 1.2%,对照旧口径 21/276 = 7.6%。
⚠️ 这是下界——日历只能证明开过,不能证伪(电话/Zoom 会不产生纪要,取消事件 API 取不到)。另有 10 条无任何邮件/渠道佐证的标记已清除(走 apply_lead_status 唯一写入口)。

173
冷发若启用 · 会错发
默认 30 天窗口内 150
94
已触达 → 重发
tracker 已发,库侧仍 new
79
待发 → 与 Hermes 双发
两套系统各发一封
138
正常新线索
本该发,不算风险

窗口大小来自环境变量 COLD_EMAIL_MAX_AGE_DAYS(默认 30)——150 不是硬上界,173 才是。用一个可被改掉的默认值算出的风险数,方向对不等于数字对。

02

系统整体架构

三个子块:当下运行状态(谁真的在发)→ 目标闭环(该长成什么样)→ 当前断点(差在哪)。

2.1 当下运行状态 —— 谁真的在发信

这一轮查出的每件事都是同一个形状:台账说的和实测的不是一回事。所以这张表并置两列。

渠道台账怎么说实测状态
run.py
Hermes ISO 邮件
jobs.json 工作日 20:00 最早发送 2026-07-01,累计 1,422 次投递 在跑
engine.js
LinkedIn 好友邀请
jobs.jsonenabled=false
paused_at 2026-07-14
launchd 那一路从未关闭。日志 452 条发送记录,100% 发生在标记暂停之后;跨 49 天、27 个发送日、451 人 台账说停了
linkedin_outreach_v3
LinkedIn 私信跟进
代码默认 REAL_SEND=false .env:55 覆盖为 true,launchd 已调度。9/2 实测队列里有 61 人已到期runner_node 全部 = hermes-graham,与 .env:33 一致),current_step 分布 1→4,步进在推进。日志实证 8-17 sent=2/20 (mode=REAL) 在跑 · 61 待发
linkedin_reply_send_worker
审批过的回复自动发
代码默认 false 脚本 :91 硬编码 true,每 15 分钟一轮;已发 21 条,队列中 1 在跑
cold_email_engine 2026-06-25 恢复 daily_leadgen.sh:260 整行注释;注释写明「活库 contacted 行 = 0 = 事实空转」 已停
send_emails_direct.py 无调度,但零护栏:给个 JSON 名单就全发,不查抑制名单、不限量 误跑成本最高

9/2 新增一个同族陷阱daily_linkedin.out.log 里有两个任务名——linkedin_followups 每次都打「今日无需跟进的 LinkedIn 联系人」,而真正发 DM 的是 linkedin_outreachsent=2/20 (mode=REAL))。按任务名判断「followups 在不在发」会得出完全相反的结论。
上个会话查到过 linkedin-iso,判断「它跑的是 engine.js,根本不碰 tracker」就排除了——「不碰 tracker」≠「不发信」。按「它写不写我关心的那张表」筛发送器,会整条渠道地漏掉。查一个 job 停没停,判据是看它的产出state.jsonadded_today、日志末行),不是看配置里的开关。

2.2 目标闭环 + 2.3 当前断点

左侧三段分区(9/2 新增)供给段(来源 → raw_leads)近 30 天只产出 30 条真线索,Graham 已定换主线为 Apollo + LinkedIn Sales Navigator加工段(规则闸 → 邮箱验证)是本轮唯一还有价值的一段,下一步是把新供给接到它的入口上;待发队列是边界(「到待发队列前」指的就是它以上);发送段在本轮范围之外。
一条主路径 + 一条存量回流边,纵向读。分配同事是终点——交给销售后线索就离开外联系统。让它成环的是 company_leads 存量回流到闸前那条边(5,680 家可回流,见 §01 枚举明细),不是从销售末端往回流。当前之所以看着像两条,是因为 Apollo 的 --ingest 从最后一格直插待发队列,而回流边根本不存在。颜色是每个环节的真实状态,不是计划状态。9/2 重画——旧图有三处与实查矛盾:把 Reveal 标成「手动」(实际没有代码)、把邮箱验证标成已打通(实际未调度)、把 LLM 判 ISO 标成未接线(实际 icp_v2_reclass 每天在跑,没接的是 Apollo 那条 --review)。

多源入口 · 9 桶互斥且闭合 = 10,537 —— 枚举漏一个,闸就漏一条渠道 供给段 近 30 天 30 条 · 已换主线 加工段 —— 有价值的是这一段 新供给要接到它的入口上 边界 发送段 · 本轮范围之外 Google 抓取5,585 表格导入3,409 · 0 邮箱 展会名单740 其它单点427 POS 合作方45 Tavily244 测试垃圾654 source=null9 Apollo9 → 418 目标:全部收敛到 raw_leads(今天有 1,599 行没走这里) raw_leads10,166 🚪 规则闸零调用方 · 不在路径上 company_leads10,537 LLM 判 ISOicp_v2_reclass 每天在跑 Reveal 联系方式没有代码 · 且不可能全自动 邮箱验证有码 · 未调度 · 停 15 天 待发队列1,571 行 = 1,442 家 发信Hermes 工作日 20:00 回信扫描本轮修复 LLM 意图分类33/33 审核 + 日历第 5 信号源 流转 CRM通道不存在 分配同事无人写 · 终点 promote_raw 每天在跑 绕过闸;且上次真正产出是 2026-08-08 存量回流闸前 · 5,680 家 尚不存在(Graham 9/2 定的接法) Apollo --ingest 直插 · 418 条 绕过全部 4 张表和闸;lead_contact_id 100% NULL 9 个桶互斥且合计 = 10,537;其中 1,599 行绕过 raw_leads 直写 company_leads(Google 696 / Tavily 231 / 测试垃圾 663→62(RLS 探针 601 行已于 22:2x 删除,仅剩 __QA_PERF__ 62)/ Apollo 9 / 来源未知 9→0)。 cold call 存量 1,203 家散在各桶内(caller_stage 是另一个轴),其中 ask_to_send_email 61 家目前没有任何自动通路回到闸前。 分配同事是终点 —— 线索交给销售后就离开外联系统,那里没有回边。让它成环的是右侧那条「存量回流闸前」。 ⇒ 要补的四处:把闸挪进 company_leads · Reveal 保留人工环(账号计划所限) · 补流转 CRM 与分配同事 · 加存量回流边。再堵掉 Apollo 那条红线。

多源入口枚举 · 明细 —— 把分母定死

SEND_PATHS_ENUM 同一套纪律:入口没枚举全,闸就会被整条地绕过,而且是静默的。上图九个格是这张表的摘要。下表实查自生产库,「绕过 raw_leads」列精确闭合到 1,599(= from_raw_lead_id IS NULL 的行数)。

来源桶源数raw_leadscompany_leads绕过 raw闸能不能看见 / 备注
自动抓取 · Google
google_places / google_maps
25,4055,585696大部分经 raw_leads696 条直写——这批闸看不见
一次性表格导入
import_batch=wechat_20260620 · 5 个 sheet
53,4093,4090全经 raw0/3,409 有邮箱;source 名写 20260620 而实际导入是 2026-04-22online_ordering sheet 里 99 家名字像餐厅(终端商户,不是 ISO)
展会名单
tradeshow_linga/neaa/eta/discovery
47497400全经 raw
其它单点源
cra_ncr / linkedin / clover_fiserv / pax
55404270全经 raw
POS 厂商合作方
*_partner / *_reseller
1245450全经 raw
自动抓取 · Tavily
tavily_discovery
10244231🔴 每天在跑却几乎全部直写 company_leads(仅 13 条经 raw)
🔴 测试 / 审计垃圾
rls_audit 592 / qa_perf 62
20654654🔴 不是线索,回流时必须显式排除,且要单元测试锁住
Apollo
source='apollo'
0099🔴 company_leads 里只有 9 条;真正的 418 条走 --ingestcompany_leads 都没进
(source 为 null)1099🔴 来源未知的 9 条 —— 未知写入方,需查清
合计绕过 raw_leads1,599精确等于 from_raw_lead_id IS NULL 的行数 ⇒ 枚举闭合

⚠️ 还有两条不在这张表里的入口,因为它们根本不落 company_leads
Apollo --ingest 直插待发队列 418 条(图中红弧)——三张上游表一张没进;且它不是一次性验证:分 5 次入库(7/22 158、7/26 4、7/27 13、8/10 83、8/18 160),81 行 t1_sent_at 非空(已真发信),其中 44 行 status='disqualified'outreach_status='queued'、2 行判否后照样发了出去。
 🔴 比图上画的更隐蔽:这 418 行的 lead_source 全写成 linkedinlead_source ilike '%apollo%' 实查 0 行,只能靠 discovery_meta 认出来 ⇒ 队列里 lead_source='linkedin'533 行中 418 是 Apollo,真 LinkedIn 只有 115任何按 lead_source 做的枚举/闸/统计都会漏掉它。
cold call 存量 1,203 家——它们在 company_leads 里(已计入上表各桶),但 caller_stage 携带的人工信号(尤其 ask_to_send_email 61 家)目前没有任何自动通路回到闸前。
枚举漏一个,闸就漏一条渠道。LinkedIn engine.js 就是这么漏了 50 天。

🔴 流水线还在产出吗 —— 上次真正产出是 2026-08-08(25 天前)9/2 实查

结论:本机流水线除 US SOS 那条腿外,25 天没有产出过任何东西。近 14 天另有 6 天整天没跑(8/20 8/23 8/25 8/26 9/1 9/2),全被 governor 挡下、全静默。

建议:判据落在产出上,不是「进程起没起」。已建 v_cron_last_success(不限窗口取 MAX)+ 独立 launchd 告警。

步骤上次真正产出距今
kp_supplement(US SOS,不在本机)2026-09-010.9 天 ✅
leadgen_summary(raw_leads 任意)2026-08-284.3 天
promote_raw / tavily / cra_ncr / website_backfill / icp_v22026-08-0825 天
email_enrich2026-06-2569 天
gmaps_scrape(2026-05-25 停用)2026-05-22103 天
customer_match / pos_reseller_verify2026-04-16~20135+ 天

原因「跑了」≠「产出了」 —— 8/31 那次跑完了每一步,但 cra_ncr +0pos_reseller empty directoryphone_backfill 三次 serpapi 超时 ⇒ 净产出 0
🔴 告警装在被跳过的那段里面daily_leadgen.sh:47 governor 不过就 exit 0,而 leadgen_alert第 320 行 —— 流水线越是没跑,告警越不会响。且 check 的输出被 >/dev/null 2>&1 丢掉,连是哪一项资源都不知道。
⚠️ 此刻实测 governor 是 OK 的(disk 68.97 G / swap 0 / mem free 53%)⇒ 卡的是白天那两个时刻的内存,而一天只有两次机会。已改成 03/04/05 三次。

🔴 监控视图自己是坏的 —— 16 行里 5 行不可能变绿 9/2 实查

结论v_cron_health 有 3 个信号指错了表,另有 2 个从来没有过发射方

建议:3 个信号已在 migration 084 指对;2 个埋点已补,下次成功跑完应变绿 —— 这是可证伪的验收判据

步骤视图查的实际写的
tavily_discoveryraw_leads0company_leads 244
kp_supplementraw_leads source LIKE 'sos%'0lead_contacts 331
icp_v2_reclassmeta.reclass_source0stage_name='icp_passed' 3,160
kp_found🔴 从来没有过发射方 —— 只有一次性回填脚本 funnel_event_tracker.py(零调用方)在 2026-04-24 写过一次
phone_validated🔴 同上

原因一个永远红的监控和一个永远绿的一样不携带信息 —— 这就是它建好之后没人读的原因。唯一的消费方 cron_health_check.py 在本机没有任何调度

🔴 测试脚本在往生产线索表写 —— 占近 30 天新增的 85% 已挡一半

结论company_leads 近 30 天新增 200 行,其中 170 行是测试数据rls_audit 108 / qa_perf 62,9/1 还在写),真线索只有 30 条。存量测试垃圾 654 行。

建议:已加 CHECK ... NOT VALIDqa_perf(只对新写入生效)。rls_audit 待 Graham 定

原因:🔴 不能用约束挡 rls_audit —— 那 108 行来自 RLS 审计的 INSERT 探针(故意插一行验 RLS 拦不拦得住)。挡了它,探针会因「约束」失败,而那个失败长得和「RLS 生效了」一模一样 —— 等于用数据卫生把一个安全控制变成假绿。它的正确修法是修它自己的 cleanup。
⚠️ NOT VALID 期间「约束存在 ⇒ 数据干净」是错的推论:存量 654 行仍在,清干净后才能 VALIDATE

🚪 闸该落在哪:company_leads 的写入口,不是绿线 promote_raw(9/2 实查定的)。
实查:promote_raw 近 30 天 promoted17 条(最后一次 2026-08-08 10:00Z,25 天前),同期 disqualified 4,004——它在跑,但几乎不放行;而 company_leads 近 30 天新增 200 家里 183 家(91.5%)from_raw_lead_id IS NULL(近 90 天 585 里 512)。
⇒ 把闸挂在绿线上只管得住今天入口的 8.5%,另外 91.5% 照样从旁边走过去——正是本页反复说的那个形状。

已打通并验证 能用但不自动 不存在 / 未接入 虚线箭头 = 靠人搬运,无代码衔接
03

下一步计划

排序:重要 > 高产出 > 紧急。新需求按此插队,不按提出时间。每项按结论 → 建议 → 原因写,全部基于实查。

🔴 重要

I-0 · 唯一队列已定 = Hermes tracker;接线做到 dry-run,两步等批准 (9/4 决定并实现)

结论唯一发送队列 = Hermes trackeroutreach_sequences 降为「候选供给态」(不是账本);导入审计 jsonl 是「导入事实与幂等记录」。依据:真发送器 run.py 只读 tracker、零 DB 客户端;DB 侧零邮件消费者(冷发引擎自 2026-07-16 起停用);MERGE_PLAN v8.3 七轮外审已把「DB 当队列」否掉。方向经 4 路 AI 评审 + 三家外审两轮(3/3 出票)认可。

建议:接线已实现(openclaw db_queue_to_stage.py 只读 DB → 批次快照 → 既有导入器一次原子写 tracker;153 单测;真库 dry-run 闭合),剩两步等 Graham:A 验证 25 人(≤25 credit)→ B 按准入清单写 tracker(冻结窗外)。同时要定:排程化、catch_all 队列(73 人 = 本轮产出 3 倍)、双队列日落日期。

原因:桥推的 30 人 0/30 进 tracker;DB 侧 425 人无发送路径、357 人 DB 标 queued 而 tracker 已发过(17:46 HKT)。收益上限很小 —— 候选 25 × 验证通过率 ≈ 16 人 ≈ 半个发送日;价值在「接通」本身:之后每一批放行都有去处。剩余风险(并发写保护依赖人工维护窗)已在内部 plan 登记,不在公开页展开。

I-1 · 把新供给接到加工段入口 (降到 I-0 之后)

结论:供给段已断供 25 天(最后一条真线索 2026-08-08 10:00:16Z);08-09 起非测试新增 = 0。方案已写并过外审 3/3 出票(方向获背书,但三家均给 P0)。

建议:取双写不取改路——Apollo 保持现有两条输出不动,增写 company_leads + lead_contacts但排在 I-0 之后:双写买不到一封邮件。

原因:Apollo 走 --ingest 只写 outreach_sequencescompany_leadssource ilike '%apollo%' 全表仅 9 行、近 30 天 0 行 ⇒ 额度与库存完全脱钩。且 Apollo 今天对 10,537 家存量零 CRM 侧去重_dedup_guardphone=None 调用,控制流必达 return (True,"ok",None))。

I-2 · 反向抽样:1,660 家「肯定类目 ∧ 摘要不足」

结论:其中 274 家已进外联队列,可能正在给非 ISO 发信。

建议:抽 100 家测误肯定率,排在任务 8 之前。

原因:任务 8 只查否定方向(4,772 家)。1,660 占肯定类目 73%。假阴 = 机会成本;假阳 = 实际支出 + 域名声誉。

I-3 · 闸落在 company_leads 写入口

结论:已挡 qa_perfCHECK ... NOT VALID);rls_audit 待 Graham 定

建议:先定 rls_audit 怎么办,再谈存量 654 行清理与 VALIDATE。规则 6a 已拍板,配额闸本身可接但名额为 0。

原因:挡 rls_audit 会让 RLS 审计的 INSERT 探针因「约束」失败,而那个失败长得和「RLS 生效了」一模一样。它的正确修法是修自己的 cleanup。

I-4 · 合并方案阶段 1.5 的四项前置

结论:四轮外审均判「不建议直接真写」,已停止外审循环。

建议:做隔离库演练,不再加文档。前置:① D7 冻结 Hermes 的可执行命令(阻塞)② 演练 ③ ck_lead_status_source'tracker' ④ 清 3 家 opt-out(规则 6a 已拍板,不再是阻塞项)。

原因:36 条 ≥2 家命中已全修(v8.3),但命中数 12→9→8→7 未趋近 0,且后三轮最严重的缺陷均来自上一轮的修复动作

I-5 · rotate_keys.sh 枚举所有消费方

结论:9/1 轮换只更新了一处,openclaw/.env 的 key 死了两天。

建议:轮换后逐个消费方实拉一行,401 即报错退出。

原因:脚本自述「orderpin-iso.env 是所有脚本唯一的引用点」,而 daily_leadgen.sh:15 source 的是 openclaw/.env

📈 高产出

H-1 · Apollo 线 A:按合格供给 reveal,不追「把 2,021 用完」

结论:链路全通,但额度用不完不是问题 —— 瓶颈是合格线索供给。同一批源页面上,公司层已被挡掉的 657 人里 42.9% 公司名含 payment/merchant,net-new 2,215 人只有 1.9%密度差 22 倍(放宽到 pay|pos|iso 仍差 6 倍)。

建议有多少合格线索就 reveal 多少。先全量 --review 量通过率;不够时从筛选条件代价表入手,不要拿同样条件补跑搜索。

原因:在这个池子上花掉 2,021 credit 比让它作废更糟——等于往队列灌 2,000 个非 ISO,代价是发信支出 + 域名声誉。上周期也作废约 70%(~1,759/2,510),当时定性同样是「主因是没用完」——同一件事第二次发生。--review 首批 24 条 KEEP=0,判出的 GoTab/Pomodo(POS 软件)、Decorative Paint(油漆厂)就是这个池子的真实成分。
⚠️ 0/24 是 cands[:N] 前缀不是随机,不能外推。⚠️ 密度差用 scripts/check_dashboard_consistency.py --pool 每次重算;上一版的 17.7 倍 → 22 倍,见 §08。

H-2 · ask_to_send_email ∧ 被判不合格的 11 条

结论:不烧 credit、不烧 LLM 配额。

建议:排在任务 8 之前。

原因:电话中对方明确说「发邮件给我」,机器判不合格 —— 每条自带一条独立的人类正向信号,信息量大于随机抽 200 家。

H-3 · 任务 8 阶段 A(200 家重判)

结论:样本已固定落盘且可复现。

建议:等 I-2 的反向抽样结论一起看。

原因:两个方向的误判率放在一起,才能判定「判定器能不能信」。icp_category 实有 13 个值,任务 8 只取 non_icp+unknown

H-4 · 邮箱验证接到新供给主线上

结论:MillionVerifier 不是「有码未调度」——它是 CSV→JSONL 的离线工具,不读库也不写库,已在 Apollo phase-2 链上被 promote_phase2_to_tracker.py 消费。

建议不要接进 daily_leadgen.sh;随 I-1 一起长在 Apollo 主线上。

原因:接进 daily 会打破 Graham 2026-07-29 定的「按文件解耦、Hermes 代码零改动」边界。这是我先前判断错的一条,已更正。

⏱ 紧急

U-1 · 首批 30 接进真发送器

结论:30 条全部 status='new't1_sent_at 全 NULL、进 tracker 的 0/30 → 9/4 适配器 dry-run:25 条候选(5 条单 token 名字留人工),全部未验证,等批准 A/B。

建议:批 A(验证)→ 批 B(写 tracker)→ 下一个工作日 20:00 起真发。

原因:晋升口写 outreach_sequences,发信器读它自己的 tracker。本项目第四次「建好了、过了外审、没接线」。

U-2 · 3 家 opt-out 清理(规则 6a 已拍板)

结论:规则 6a Graham 9/2 拍板取「接受名额 0」——闸照接,槽 2 只对以后走闸进来、带 function 的新公司开放;存量 1,442 家维持现有 1 个联系人。剩下的阻塞只有 3 家 opt-out 冲突。

建议:清 3 家 opt-out,然后按「闸落在 company_leads 写入口」接线。

原因:6a =「占位者职能查不出 ⇒ 拒第 2 人」,是 B3「必须换职能」的 fail-closed 实现。实查 discovery_meta->>'function'classifier_version 非空各 0 行;1,571 个占位者里能摸到 title 的只有 26(1.7%)。另两条路已排除:回填存量职能要花 Apollo credit(plan §不做);关掉 B3 在 v7 已封死。

U-3 · 修 rls_audit 自己的 cleanup

结论:存量测试垃圾 654 行,近 30 天还在增(rls_audit 108)。

建议:修它的 cleanup,不要在写入口挡它(见 I-3)。清干净后 VALIDATE CONSTRAINT

原因:脚本有 cleanup 代码但显然没清干净;它不在任何调度里 —— 是人工跑审计时留下的。

04

规划里有、目前没有

按你最初描述的闭环逐项对照,这些是缺口。

A1
Apollo 主动搜索 + 条件复用

系统按关键词自动搜、LLM 优化搜索条件。现在全是人工在 Apollo 后台筛完导 CSV。

不存在
A2
Reveal 前的 LLM 判断闸 + 月度 credit 预算

在消耗 credit 取联系方式之前先判是否 ISO 目标客户。这是你定的硬约束,目前没有自动执行的通路。

不存在
A3
LeadsGen → 外联队列的晋升口

已更正我此前写「上游没接进来」是错的——我查到没有叫 leads 的表,就当成没有这件事。实际上游用的就是同一个库raw_leads 10,166 → company_leads 10,537 → lead_contacts 3,480,每天 10:00 自动跑。

真正缺的是 lead_contacts → outreach_sequences 这一节没有外键,只能靠邮箱字符串对;晋升标准也散在 10 个脚本里没有单一定义。方案见 LEADSGEN_BRIDGE_DESIGN_20260901.md

🔴 2026-09-02 实查:桥建好了,但通向一个没人读的队列。首批晋升的 30 人全部 status='new' / outreach_status='queued't1_sent_at 全为 NULL,进 Hermes tracker(真正在发信的那套)的是 0 / 30。晋升口写 outreach_sequences,而发信器读它自己的 tracker。

⇒ 「等一周看首批 30 的回复率」等的是一封永远不会发出的信。这是本项目第四次「建好了、过了外审、没接线」(前三次:sync.py 调度、shadow_ingest 从未运行、iso_send_sink 零引用)。

建好了没接线
D1
开过会 → 流转 CRM 商务跟进

你定的规则是开完一次会就转入 CRM 销售跟进。目前没有这条通道,也没有数据格式对齐方案。

不存在
D2
分配给同事

assigned_to 字段存在但无人写入。后续两位同事的邮箱接进来后,分配是必需的。

不存在
D3
自动生成回复 / 主动跟进

正文入库后现在具备了上下文条件,但生成侧还没做。

未开始
05

难点

这几条不是「还没做」,是做起来有真实阻力的。

01 · 「已开会」取不到 9/2 已解 · 换了信号源

此前判「无解法」的结论是对的,但只对邮件这条路Meeting Notes 文件夹 354 封邮件里匹配到线索的是 0 封——会议纪要是内部文档,收发件人都不是客户。

换成 Google 日历 API 就直接有了:参会人邮箱是干净的联结键。主日历 2026-05→09 共 52 个事件、58 个外部参会邮箱、35 个带 Gemini 纪要。取不到不是因为数据不存在,是因为一直在错的地方找。详见 §09。

02 · 「在 Appointment 文件夹里」不等于「约到了会」 已绕过

该文件夹同时装着我发出的 Invitation:、对方的 Accepted:Declined:。直接按文件夹归属,会把两个 LLM 以 0.95 置信度判为「拒绝」的人翻成「已约会」。

→ 修了三轮才对:第一轮不过滤、第二轮在邮件层面否决(排除了 21 封但一个人都没防住,因为他们还有别的邮件在同一文件夹)、第三轮改到联系人层面才正确。

03 · 三个写入者抢同一个字段 已实现

lead_status 同时被手工标记、文件夹检测、LLM 分类写入,彼此没有优先级约定。

→ 实查交叉表:33 个回复者里两边一致 23、一方有信息另一方无信号 10、真正互相矛盾 0 条。已落成 apply_lead_status():手工永远生效、弱结论(other/auto_reply)不覆盖强结论、真冲突标记 status_conflict原状态不动。三个写入者全部接线,实测六个用例符合设计。

04 · 验证判据本身会说谎 已加护栏

本轮反复栽在这上面:退出码 0 是管道里 tail 的;「各类之和 == 扫描总数」在 0 == 0 时恒成立,于是扫到 0 封也打印「✓ 无静默丢弃」;连跑两次看 inserted 是否为 0 这个判据,会被确定性 message_id 掩盖成假绿。

→ 现有三道防线:seen==0 退 4;去重失效退 3(触发条件 inserted>20 且命中率 <10%,由 100/5% 收紧而来);另加一级只警示不拦的「插入量异常」。三者都用端到端测试验过真实退出码,不是只测布尔表达式。

05 · ICP 判定器在没有输入时也会下否定结论 新发现

icp_v2_reclass.py 的注释写明「不重新爬 website(省 LLM/HTTP 配额),直接用旧摘要判」。实查 10,537 家公司,57.8% 的 website_summary 不足 80 字;被判 non_icp 的 4,081 家里 83% 摘要不足,unknown 的 942 家里 99% 不足。

→ 人工抽样 20 家疑似误判 8 家(40%)。Graham 手工核了三家全部坐实:Nextera POS 的库存域名已死、COCARD 的摘要抓自错误域名、Coastal 的官网首页明写在代理 KwickPOS。证据就在网站上,判定器选择了不去看。否定结论会让公司永远进不了队列,且无任何报警。方案见 TASK8_ICP_RECLASSIFY_DESIGN_20260901.md

07 · 字段本身不能直接当判据 已换判据

reply_detected 漏标 41%;stage_entered_at 26 家挤在一小时内(录入时刻);t1_sent_at 有两条早于外联系统启动;created_at ≠ 首次接触(TWC 的往来在 2024 年,却落在 30 天冷发窗口内)。

→ 判「约没约过会」最强的信号是日历,不是任何布尔字段。9/2 再升一级:从「日历邮件」(Accepted: / Invitation:)升到日历 API 的 Meet 抄本时长——邮件只能证明「约了」,抄本时长能证明「开了,开了多久」。任何「时间戳」在用作判据前都该先看它的分布。

08 · 发信路径上没有「客户阶段闸」 影响 1 人

_is_closed_customer() 是 Apollo 拉线索时的入口闸,不在 Hermes 发信路径上。run.pycompute_queue 只有三道闸(tracker 状态、tracker 抑制名单、booking_suppress),全都不查 CRM 阶段。

→ 一个联系人先进 tracker、后在 CRM 被标成拒绝,入口闸就再也拦不住它。实测影响面 1 人(已单独止血,2027-01-15 自动放行)——入口闸基本有效,不是系统性泛滥。结构性的出口闸要改生产发送器,未做。

06 · Apollo credit 是硬约束 未解

判断闸必须在 reveal 之前,否则每验证一个不合格线索都在烧钱。这决定了上游自动化不能简单地「先取数据再筛」。

→ 需要先证明 reveal 前的信息足够 LLM 做判断,再谈自动化。

06

两套系统的合并方案 · v8.3

目标:让 Supabase 成为统一观测账本(不是「单一事实源」——阶段 2 后 tracker 仍是发送决策的权威源)。九轮外审,每轮都出实质 P0——而且后三轮里,最严重的那条都是上一轮的修复动作造出来的。🛑 第九轮后已停循环,详见 §11。

轮次被打中什么形状
1 · 内部6 条攻击命中 5基本功
2 · Graham推翻根本定位——「不迁移发送器」是我自己设的非目标,不是查出来的约束定位
3 · 外审把「两组读方不重叠」当优点,实际是缺陷:不干扰 = 不生效观察对、推论反
4 · 外审历史 99 人没有任何阶段会修复他们数字对、机制对、接不上
5 · 外审待发的 79 人会被双发;且刚立的「唯一权威」规矩自己就没执行问题框窄了
6 · 外审三家全部否决直接执行,12 条 ≥2 家命中(含 staged 会被 run_promote() 放回 new没审下游读方
7 · 外审三家仍不建议执行,9 条 ≥2 家命中——最重的两条是我修第六轮时亲手造的修复动作本身是缺陷来源
8 · 外审三家第三次不建议执行,8 条 ≥2 家命中——paused 一个值背三种语义、coalesce 把假阴翻成假阳,又都是我修出来的同上,且静态检查抓不到
9 · 外审三家第四次不建议执行,7 条 ≥2 家命中——最重的一条是 L3 只改 Supabase paused,根本挡不住 Hermes 发信🔴 把发送侧的问题装在了账本侧

v7 最硬的一条约束 · 前五轮外审全没发现

阶段 1.5 要把 94 人从 new 改成 contacted,而这 94 人100% 是 lead_source=linkedin,正好撞上 linkedin_outreach_v3 的取件条件(status=eq.contacted,真发 flag 已开)。

→ 目前靠 next_action_at / runner_node 为 NULL 挡着,那是巧合不是设计——冷发引擎发完就会同时写这两个字段。所以回填不得写它们。外审看不出这条,因为它只看文档,看不到 .env 和 launchd

v7.1 更正 · 上面那条的复验判据本身是坏的 9/2 实查

v7 写的复验是表级的:「lead_source=linkedin ∧ status=contacted ∧ 两字段非空 的行数必须为 0」。实查动手前就已经是 61,不是 0。原文那个「0 人」是对 94 人回填 cohort 的口径,被复述成了表的现状。

→ 两种错法都致命:照原样验 ⇒ 回填后看到 61 行会错误归因成「我造出来的」;反过来把它「修」成 0 ⇒ 打断一条合法的在跑队列(A3 是设计内的 accept 后跟进)。
正确形式必须限定在回填 cohort 内:where id = any(:backfill_ids) and ...——缺这一行就是判据落在了被测对象之外。另立一条表级基线不变量(回填前后该数必须都等于 61)防止顺手污染。

共同形状:每一轮都在一个更大的范围上漏掉了东西,而每一轮我都以为这次齐了。暴露面 89 → 99 → 178 → 173,对象从 tracker 的 866 人改成库侧全部 311 人。v7→v8.2 已连送第六至第九轮外审,四轮共 36 条 ≥2 家命中、全部修完(现为 v8.3)。🛑 第九轮后停止循环——不收敛,改做演练。「这次应该干净了」这个念头本身就是信号——第七轮最重的两条正是修第六轮时新造的。阶段 1.5 已按证据强度分三批:批 1(56 人,库内自证、零外部依赖)可先跑,实测与日历零重叠、与那 61 行也零重叠

07

第 5 个信号源 · Google 日历

Graham 9/2 提的:「已经开过会的联系人,日历上可以和冷发列表交叉对比,尤其是带 meeting note 的,这种应该是最准确的吧」。方向成立,但「有内容」的定义必须改,否则会产生系统性漏判。

52
主日历事件
2026-05-04 → 09-01
58
外部参会邮箱
34 个公司域(已剔 free-mail)
35
带 Gemini 纪要
17 个事件无纪要
19
对方自助预约
描述含「Booked by」
7
命中活跃队列 ·
8 行 = 7 家(BPS 占 2 行)
0
即时重发风险
tracker 侧 8/8 已终态

不能用「Gemini Summary 有没有内容」做判据 会全队漏判

实读两份纪要正文,两份的 Summary 都是空的,原文一字不差:“A summary wasn't produced for this meeting because there wasn't enough conversation in a supported language.” 会是中英混说的,Gemini 直接拒绝出摘要。

→ 真正有内容的是 Transcript 时长:Competitive Technology 00:08:38、Carolina PayPoint 00:09:34判据落在时长上,不是摘要上——否则这批中文团队的会议会被系统性判成「没开过」。

证据分级(供 v7 终态判据用)

L1
抄本时长 > 0 说话人 ≥ 2

证明对方到场并发言。本轮未达到——没统计说话人数,可见片段里只有 Graham 一个人。

未做
L2
抄本时长 > 0

证明 Meet 真的开了并在录音。本轮已达到这一级,8 人的结论都建立在这里。

本轮达到
L3
事件存在 + 外部方 responseStatus='accepted'

对方答应了,无到场证据。全部外部参会者里 accepted 54 / needsAction 31 / tentative 3 / declined 1。

弱证据
L4
事件存在,needsAction

没有任何证据。约了而已——这正是 appointment_scheduled 这个字段一直在混淆的那一层。

不算证据

交叉比对 · 会过面却还在发送队列里的 8 行 = 7 家公司

证据公司库侧 status / outreach_statustracker.status
本人参会Business Payment Systemsstaged / donedone 已挡
本人参会Carolina PayPointstaged / donereplied 已挡
本人参会Competitive Technology Solutionsstaged / queued ← 库里以为待发done 已挡
本人参会Diversified Paymentsstaged / donedone 已挡
同公司Advanced Merchant Servicesstaged / donedone 已挡
同公司BAMS Holding Groupstaged / donedone 已挡
同公司Business Payment Systemsstaged / donedone 已挡
同公司Premier Merchant Servicesstaged / donedone 已挡

8/8 在 tracker 里是终态,Hermes compute_queue 会跳过 ⇒ 即时重复触达风险 = 0。但这层保护是巧合不是设计done 的语义是「三封序列跑完了」,不是「我们见过面了」,任何一次重新导入都会绕过它——与 §07 那条 LinkedIn 情形同形
顺带机器化解决了一个挂起的待办2ee19f8b Competitive Tech 原记着「需人工看那封 Re: Invitation: 正文,机器判不了」。日历直接判了——5/29 有会议实体、对方 accepted、抄本 8 分 38 秒、OrderPin 侧三人在列。

未做 / 未证实(不猜)

· 说话人数未统计 ⇒ 停在 L2,没到 L1
· Recording 附件未采集——我给采集写的字段规格是「title 含 Notes」,把 2 个录像附件滤掉了(Invoteq 6/03、Gobble 7/29)。录像是比纪要更硬的证据,被我自己的过滤条件排除了
· 取消的事件取不到(API 无 showDeleted)⇒ 无法回答「约了但取消了」
· 窗口只到 2026-05-01。邮件实证最早到 2023-12-21(EatNnet),「历史上开过会」的完整问题需要把窗口拉到 2023
· LinkedIn 已邀请的 436 人无邮箱,无法与日历做邮箱联结

另:58 个外部参会邮箱里只有 5 个在 outreach_sequences——大部分会议来自对方自助预约(19 场描述含「Booked by」),不是外联打出来的。这从一个新角度再次印证了 §02 那条「触达约会率的分子被污染」。

08

Apollo 配额 · 9/16 归零

Graham 说的是「用完本月配额」,实查计费周期是 2026-08-16 → 09-16T15:04Z不是自然月。按月底算会多给自己 14 天。

2,021
待用 lead credit
Graham 独立确认
14
距归零天数
9/16,不是 9/30
140
需日均 reveal
2021 ÷ 14.5 天
489
本周期已消耗
8/24 后一分没花
0
direct dial 余额
2,500 已 100% 耗尽
9-10
Graham 定的开工死线
留 6 天缓冲

9/2 实跑:额度用不完不是问题 —— 瓶颈是合格供给,不是 credit 推翻上一版结论

结论:好线索已被捞走。同一批源页面里,公司层已被挡掉的 657 人中 42.9% 公司名含 payment/merchant;net-new 2,215 人只有 1.9%——密度差 22 倍(放宽到 pay|pos|iso 仍差 6 倍)。

建议不要追「把 2,021 用完」。额度的正确用法是「有多少合格线索就 reveal 多少」。真问题是搜索条件为什么产出 97% 无关线索——那是供给质量问题,应从已实测的筛选条件代价表入手,而不是用同样条件补跑更多搜索。

原因:① 在这个池子上花掉 2,021 credit 比让它作废更糟——等于往外联队列灌 2,000 个非 ISO 联系人,代价是发信支出 + 域名声誉。② 上个周期也作废了约 70%(~1,759 / 2,510),当时定性同样是「主因是没用完,不是用在低质量线索上」——同一件事第二次发生。③ --review 首批 24 条 KEEP=0,判出的 GoTab / Pomodo(POS 软件,A 类)、Decorative Paint(油漆厂,D 类)就是这个池子的真实成分。
⚠️ 0/24 不能外推--samplecands[:N]前缀不是随机。但它与整池的关键词密度一致(前 24 条含支付关键词 0 家)。
⚠️ 这次 0/24 不是已知的两个假 0:两家独立同意分类且理由具体。对照 2026-07-26 那次假 0——20/24 名字字面写着 Merchant Services 却被判「无证据」,那是 rubric 自我否决。
⚠️ 上一版的密度差 17.7 倍 → 22 倍:旧口径拿「1,340 人中 633 家」当分母,而 counts.filtered=1,340 里只有 657 落进了 prefilter_20260902.json,另外 683 人(已 reveal/已在库/台账)根本没落盘 ⇒ 那组数复算不出来。现口径只用落盘文件里能枚举的两群,判据 --pool 每次重算。

panel 的 codex 腿超时 · 且它没有 extreview 的回退

结论:codex 两批全部 300s 超时,panel_produced = {codex:0, deepseek:24, qwen:24},共识自适应降为 ≥2/2(两家必须都同意)。

建议:已钉死 Xtoken/gpt-5.6-sol(实测真实尺寸 prompt 52s / rc=0 / 合法 JSON,远低于 300s 上限)。Graham 已定不加回退

原因apollo_netnew_leadpull.py:1595 裸调 codex exec不传 --model ⇒ 吃 ~/.codex/config.tomlXTokenClaude/claude-fable-5 @ model_reasoning_effort=xhigh。而 extreview:411 显式覆盖成 Xtoken/gpt-5.5 并带回退——同一个中转站,只有 extreview 绕开了 fable-5。中转站实测在跑(:10100 回 200,供 23 个模型,含 Xtoken/gpt-5.6-sol)。
⚠️ codex 是这个 panel 里唯一不走 mods 的腿;它缺席时 DeepSeek+千问共用同一个二进制与 role prompt,输入侧缺陷会让两票一起错且照样出票

四个字段里三个是「无法失败的判据」 读错就等于没有闸

响应里有四个看着像配额的字段。此刻已消耗 489,但:

字段会动吗
num_credits_remaining2021✅ 唯一会动 8/24 是 2059
effective_num_lead_credits2510恒定
num_lead_credits_used0恒定 —— 实际已用 489
total_unified_credits_used0恒定

→ 仓里已经栽过一次:曾用 effective_num_lead_credits 的「2510 → 2510 零 delta」证明「搜索免费」。那个字段本来就不动,零 delta 不构成任何证据——结论碰巧对,证据是假绿。读错字段 = 做一个永远不会跳闸的闸。

通道实测 · 一个 query 参数决定结论相反 已更正

先前结论是「REST 拿不到 credit,只能走 MCP」。错在探测判据写窄了——REST 请求漏传了 include_credit_usage=true(MCP 那边传了)。

端点结果
/v1/users/api_profile?include_credit_usage=true200 含余额 ← 脚本用这条
/v1/users/api_profile(不带参数)200,仅 6 个字段,无 credit
/v1/users/api_profile + ?api_key=422
/v1/usage_stats/credit_usage404 ← 仓里注释说的是这条
MCP apollo_usage_stats_credit_usage_stats含周期起止 ← 只有这条给归零日

闸可以纯脚本实现,不依赖 MCP(余额够用)。但归零日 REST 拿不到——已定 B+C 组合,A(写死常量)不实现,见下方实现块。写死一个会过期又不报警的常量不可接受,那正是本项目反复栽的形状。

🔴 9/2 晚实现时查出:花钱路径上是三重阻断,实时闸不是 9/16 的瓶颈 改变结论

写完「.env 的 key 是死的」之后继续追「就算换好 key,钱花得出去吗」——花不出去,而且另外两层是别人刻意设的

#阻断实证能不能改
1.envAPOLLO_API_KEY 无效/auth/healthis_logged_in=false/email_accounts·/labels·/typed_custom_fields 三个免费端点全 401。与 Keychain 里的 master key 指纹不同可修(换 key)
2REST search/match 被 plan-gateapollo_netnew_leadpull.py:39-47 模块自述:/mixed_people/search403 API_INACCESSIBLErun_pull():2013 因此是返回 2 的 stub账号计划 · 改代码没用
3DB ledger 对 apollo 硬封实查 pg_procreserve_quota() 里明写 IF ... lower(btrim(p_provider)) = 'apollo' THENRETURN FALSE(migration 076b)刻意封的

→ 链条实证:_reserve_credit():327guard("apollo",N)reserve_quota 对 apollo 恒 FALSE → 抛 QuotaHalt一分不花。⇒ 无论实时闸开不开、.env 的 key 修不修,apollo_email_backfill 这条路都花不出去钱。

只验读余额那把 key 是不够的:闸读余额用 master key,花钱却用另一把。只验读的那把,就会做出一个「余额 2,021、充足、放行」→ 然后每次 reveal 全 401 的闸——钱一分没花、额度一个没救回来,而日志看起来一切正常。已加 verify_spend_key() 在读余额之前先验花钱那把。

⇒ 给 Graham 的是两件独立的事,别混成一件 B 才是 9/16 的那件

决定什么需要 Graham 做什么
A启用实时闸——以后花 Apollo 钱时按实时余额判,读不到就停 + 邮件APOLLO_LIVE_QUOTA_GATE=1 + APOLLO_CREDITS_PER_MATCH,并处理 .env 的死 key
B🔴 按合格供给 reveal——9/16 前能过 --review 多少就 reveal 多少,不以用完 2,021 为目标排期:先跑全量 --review 量通过率;通过的才走 MCP bulk_match → 落盘 → --ingest。⚠️ --ingest 现在写的 lead_source='linkedin'lead_contact_id 全 NULL,接之前先修(见 §01 注 ①)

A 不做也能做 B;A 做了也不能替代 B。我一开始把 A 当成了 B 的前置条件——那是错的,而且是本项目的典型错法:把一个架构目标摆在一个有硬死线的业务目标前面

实时闸已实现 · 默认关闭 9/2 完成 启用需 Graham 点头

scripts/leadgen/apollo_quota_live.py + 判据自检 + pytest。开关 APOLLO_LIVE_QUOTA_GATE 未设 ⇒ 生产行为一字不变

验证项(全部实跑,零 credit 消耗)结果
REST 读余额num_credits_remaining=2021
MCP 交叉验证lead_credit.left_over=2021 与 REST 一致
8/24 记录反推2059 − 38 = 2021 精确对上
判据自检(9 条喂已知坏样本验它会红 + 1 条干净样本)10/10
变异测试 A:余额回退到恒定字段自检第 1 条变红
变异测试 B:读不到就放行自检第 4/5/6 条变红
变异测试 C:去掉 24h 去重自检第 7 条变红
既有 guardrail 测试 / 新增 pytest50 passed 无回归 + 8 passed
Python 3.9 与 3.11 双跑(cron 有 3.9 回退路径)都 10/10
生产 env 下 checkallow=False, reason='gate_disabled' 开关确实是关的

归零日选了 B+C 组合,A(写死常量)不实现:B = MCP 读到的周期落盘 + fetched_at 过期告警(已落 2026-09-16T15:04:08Z,还剩 14.8 天);C = 余额单调下降,一次 ≥100 的上跳即判重置,且能证伪 B(自检第 9 条专测这个)。A 被否的理由不是「不准」,是「它不准的时候没有任何人会知道」——与看板 §08 判据纪律里那几条同族。

读不到时必须同时做两件事 已实现

① fail-closed 拒绝花钱(money path 永不 fail-open,本仓已立的规矩)
② 邮件通知 Graham(他 9/2 明确要求)

两件必须同时做。只做 ① 就是静默停摆——配额会被悄悄拖到 9/16 作废,而这正是本仓最常见的失效形状;只做 ② 就是 fail-open 花钱。
当前之所以一分没花,就是 ① 在生效而 ② 不存在:APOLLO_INCLUDED_MONTHLY_CREDITS 未设 ⇒ run():606 显式 return,日志 8/29–8/31 每天打 refusing to spend没有任何人被通知

09

已完成(8/29 – 9/2)

起因是「面板数字一直不准」。查下来不是 SQL 分类条件写错,是更上游的东西坏了。

9/2 让流水线「有没有在产出」变成能报警的事实 已上线

governor 拦截原因写进日志(原来被 >/dev/null 2>&1 丢掉)+ TG 通知; 新建 pipeline_stale_alert.py + 独立 launchd 09:30,与 daily_leadgen 解耦(原来告警在被跳过的那段里面); 调度 10:00/11:00 → 03/04/05

migration 084 v_cron_last_success(不限窗口取 MAX,回答「上次好是什么时候」);kp_found/phone_validated 两个从来没有过的埋点; migration 085 挡 qa_perf 写进生产线索表。

判据自检 5 条全过(含「视图整行消失也必须红」);约束用坏样本实证:qa_perf 被挡、正常来源放行、探针行零残留。openclaw workspace 604ff03

9/2 三项拍板 已定

③ 供给主线换成 Apollo + LinkedIn Sales Navigator MCPGoogle Places 不恢复——它当年找的主要是 merchant(终端商户),而现在要找的是 ISO。这同时解释了库里那批「名字像餐厅」的线索是怎么来的。

① 规则 6a 取「接受名额 0」:配额闸照接,槽 2 只对以后走闸进来、带 function 的新公司开放,存量 1,442 家维持 1 个联系人。备选两条已排除——回填存量职能要烧 Apollo credit(plan §不做),关掉 B3 在 v7 已封死。

② 闸落在 company_leads 写入口,不挂在 promote_raw 上。实查:绿线近 30 天只放行 17 条,而 company_leads 新增的 91.5% 根本没走 raw_leads。

根因:邮件正文从未入库 已修

扫描脚本第 62 行取了正文,第 74 行拼输出时没带上它。2,354 行 body_text 全空 → LLM 只能看标题 → 每次回「内容为空,无法判断」→ 全判 other。

过去两天反复改 SQL 分类条件,改的都是症状。修复后 2,106/2,132 行有实质正文interested 从恒为 0 变成 13。

扫描器重写 已验证

改为直读 ~/Library/Mail/V10/**/*.emlx,一并修掉四个坑:locale 日期被解析成 12193 年、PostgREST 1000 行截断、不匹配就静默丢弃、sent_at 写当前时间。

立「各类之和 == 扫描总数」等式断言,全量 16,940 封实测成立。

状态口径统一 已上线

八值枚举 + DB CHECK 约束;Python 与 TypeScript 各一份共享常量;统计函数重写为单一数据源。回复分类合计 = 已回复人数,首次自洽

数据清理 已完成

删除 1,496 行脏数据(日期最远到 2057、94% 未关联线索)、去重 1,586 行冗余、修正 19 行方向错误。每步删除前完整备份落盘。

安全止血 权限已收

凭据从 8 处硬编码收敛到单一 0600 文件;1.5 MB 含 1,467 处明文邮箱的 SQL 移出仓库(历史干净);移除一个每 30 分钟失败、plist 里带明文 API key 的定时任务。

收回了 anon 对线索表的读写与 5 个 SECURITY DEFINER RPC 的执行权(其中一个会把 33 条真实客户邮箱返给任何持 anon key 的人)。双边验证:anon 401、authenticated 仍读到 1,528 行。

密钥本身仍未轮换——见下一步第 1 条。

数据库不变量 外审采纳

外审指出「用应用层护栏补偿数据库不变量属复杂度错配」——这条对,而且建索引时当场炸出 19 个重复:一条直接 SQL UPDATE 把 19 行方向翻转,正好撞上已有的 19 行,当时无人复查。

现在 uq_email_threads_natural_key 唯一索引是最终保证,应用层去重降为优化。去重预检也从写入后移到了写入前

终态判据改为四信号源 9/2

原判据是 unsubscribed ∨ bounced ∨ reply_detected。实测 reply_detected 漏标 41%——56 个有 inbound 邮件的线索里 23 条标着 false,Alt POS 收了 13 封仍标未回复。

现加入「存在非自动回复的 inbound 邮件」,并排除 Automatic reply:——只看「有没有 inbound」同样是单信号源。据此补处置 4 条漏筛的真回复。

「明确拒绝」6 个月窗口落地 9/2

COLDSEND_HALT §6-② 卡了两周,因为「不确定有没有可靠的拒绝时间戳」。查清了:stage_entered_at 不可用(30 家里 26 家挤在 2026-04-16 一小时内,是录入时刻不是拒绝时刻),last_follow_up 可用。

放行 5 家(付款/已转出仍永久排除,无时间戳 fail-closed)。真实数据验证:改动前挡 125 家,正好对上代码注释里的「应挡 125 家」。

外发路径全枚举 9/2

把「谁会向外发信」完整列了一遍——此前从没列过,所以每轮的暴露面都在扩大(89 → 99 → 178 → 173)。查出 engine.js 在标记暂停后又发了 49 天。

口径一致性做成可执行判据 9/2

合并方案在「改了一段没改引用同一结论的其它段落」上连续犯了 14 次——立规矩没用,因为规矩只约束将来写的字,不动已经写下的复述。

现落成 scripts/check_plan_consistency.py,有退出码,按章节区分历史区与当前区。不再每次现写 grep。

定时任务真正跑通 9/1 实证

三次交付三次失败:cron 读不到 token、哈希跨进程随机导致去重失效、时间戳两侧格式不一致。第四次加上完全磁盘访问权限后,seen: 16,940,首次自动运行成功。

以下为过程记录(log)

判据纪律、外审逐轮台账、内部 debate 记录。这些是「怎么走到今天」的证据链,不是当前要做的事 —— 读当前状态请看 §01–§03。

10

这轮攒下的判据纪律

都是真栽过的,写在这里是为了下一轮不再栽。

看起来是实际是
退出码 0 = 成功那可能是管道里最后一个命令的退出码。判据要落在数据上
cron 装了 = 在跑112 次 traceback、0 次进 main。要看日志内容,不是文件存在
交互式 shell 测通 = 能部署环境变量、PATH、磁盘权限全不同。用 env -i 模拟
切了配置 = 新配置在工作TCC 授权对已运行进程不生效,要重启进程
护栏没报警 = 一切正常0 == 0 恒成立。护栏自己会说谎,要构造反例验它
404 和 401 差不多404 是表不存在,401 是有表没权限。含义相反
分布统计随手查一下PostgREST 默认只返 1000 行,采样让 linkedin 从 533 缩成 155
文档写了「已验证」要有那次运行的证据。声称验证过 ≠ 验证过
它不碰我关心的那张表 = 不发信按「写不写我关心的表」筛发送器,会整条渠道地漏掉。LinkedIn 那条就这么漏了 50 天
配置里写着 paused = 停了同一个 job 名下可能有两套调度。查停没停要看产出,不是看开关
文档说「现在是永久排除」= 规则如此那可能是在陈述实现缺陷。规则是「6 个月后可再发」。别拿实现当规则
字段名叫什么就是什么reply_detected 漏标 41%;stage_entered_at 是录入时刻。时间戳用作判据前先看分布
我 grep 过了,干净了连续 14 次漏改。grep -v 的排除条件会把要查的行一起滤掉;grep -c 无匹配时 exit 1。判据本身要先用已知答案验一次
数出来 8 条就是 8 个单位错了:那 8 是联系人行,实际是 7 家公司(Business Payment Systems 占 2 行)。而且里面 4 行是「同公司但本人没到场」,被算成了已开会。「已开会」的计数单位必须是公司,报数前先问:一行代表一个什么
会议纪要「有内容」= 有 Gemini Summary实读两份,Summary 全空——「conversation not in a supported language」。会是中英混说的,Gemini 直接拒绝出摘要。真证据是 transcript 时长(8:38 / 9:34)。用 summary 筛 = 中文团队会议全漏
纪要挂在这个事件上 = 这次会的纪要同一份文档(自身日期 8/4)同时挂在 8/6 和 8/18 两个事件上——改期系列会继承前序附件。要用文档自己的日期,否则一次会算成三次
同域名 = 同公司域名联结初值 38 行,绝大部分是 gmail.com 撞的。剔掉免费邮箱域后是 8 行。规模先估大又发生了一次
日历里没有 = 没开过会只能证明开过,不能证伪:取消的事件被 API 服务端过滤(无 showDeleted),电话/Zoom 会不产生纪要(52 个事件里 17 个没有)
任务名叫 followups = 它在管跟进它每次都打「今日无需跟进」,真发 DM 的是另一个叫 linkedin_outreach 的任务。按名字找会得出完全相反的结论
customers 表里 = 是现有客户367 行里 won_at 大多为空。判成交要看 current_stage / won_at,且终结态记在两个字段里

截至 2026-09-02 日历轮,grahamcrm 本地 main 领先 origin/main 53 个 commit(9/2 深夜实查);openclaw workspace 在 feat/tier3-ingestion-hardening 分支上,领先它自己的远端分支 75 个(此前记的「2 个」是那一轮的新增数,不是未推总数——我第一次量它时还量错了对象,去查了一条没人在动的 main,得到 0)。均未推远端。生产数据本轮动了 18 行(8 行 status、10 行 lead_status),每次都重跑了该表全套不变量。参与方:Claude (Opus 5) 实现与复核、Kiro/codex 独立复核、外审三家(Codex / DeepSeek / 千问)、Graham 执行需要人工权限的操作与最终拍板。

11

外审第六 → 第九轮 · v8.3 · 已停循环

Codex「不建议直接执行」/ DeepSeek「不具备安全执行条件」/ 千问「有条件不通过」。12 条被 ≥2 家独立命中。A7 上轮已修,9/2 晚修完剩余 11 条并裁决了三家互相矛盾的 4 处,文档升 v8。⚠️ 其中 A6 / A10 各有一半不成立——三家都说的话,也可能是三家都只看了文档。

#条目命中状态
A1全文人数口径不平(56+38+79+140=313 ≠ 311,实查已确认差 2)3 家已修
A2冷发引擎「硬关闭」仍是要求不是机制(无 flag 名 / 默认值 / 失败行为)3 家已修
A3阶段 1.5 缺备份 / 事务边界 / 回滚 —— PostgREST 无事务,「整批回滚」无可执行路径3 家已修
A4静默失败无补偿闭环,阶段 3 只发现不修复3 家已修
A5自动回复正则漏检,且 subject 为 NULL 被三值逻辑误伤(需 coalesce3 家已修
A6邮箱映射未封死,一邮箱多 sequence 会写错人3 家已修
A7§4 表级归零判据与正文 v7.1 的 cohort 级修正打架2 家✅ 已修
A8未来新入队人群双发防护缺失(只修了存量)2 家已修
A9staged/contacted 下游读方未做条件级审计2 家已修
A10阶段 1 判据仍以 297 面板口径为基线2 家已修
A11apply_lead_status() 终态白名单与 manual 优先级未定义2 家已修
A12批 2(38 人)仅 tracker 佐证,直接改 contacted 会造新假账2 家已修

A7 是我自己的漏改 —— 而且是同一个毛病的第 15 次

我在 §3 阶段 1.5 把 LinkedIn 复验判据改成 cohort 级,§4 判据表忘了跟着改,仍写表级「必须 = 0」——而表级实际是 61 条合法的在跑队列。后果双向:误判回填失败,或为了归零把 61 条合法队列清掉。

关键在于我的一致性脚本为什么没抓到:它只查过期数字,而这次漏改的是判据类型——数字一个都没错。已给检查器加第 2 类判据(表级归零断言必须带 cohort 限定),并双向验过:构造回归样本 exit=2 精确定位到行,修好的文档 exit=0一个从没红过的检查等于没有检查。

🛑 第九轮之后停止外审循环 —— 依据不是「审够了」,是四轮数据显示它不收敛 仍不是「通过了」

≥2 家命中其中由上一轮修复动作引入
612—(首轮)
79🔴 2 条,且是最严重的两条
88🔴 2 条,且含最严重的那条
97🔴 2 条(D1/D3),且 D1 是最严重的那条

→ 命中数在降(12→9→8→7),但没有趋近 0,且新缺陷的严重度没有下降每轮修 N 条、引入约 2 条,而引入的那 2 条一直排在最严重的位置。同时三家在第八、九两轮都独立建议先做预生产演练而不是继续审文档,第九轮三家还都把「文档过大、执行路径被历史台账淹没」列成了问题——这份文档现在 1,800+ 行,相当一部分是外审台账本身。

决定:不再进入第十轮文档外审,下一步是隔离库演练。一句话:文档审查已经审到了它自己会制造缺陷的区间;而演练能发现的东西,文档审不出来。

→ ⚠️ 这不是「通过了」。三家仍明确判定「不建议直接进入生产真写」,该结论保持有效。四项硬前置不变:① D7 阻塞项(查清冻结 Hermes 发送器的可执行命令)② 隔离库演练跑通「快照→分块写→复验→撤销→复验」③ ck_lead_status_source'tracker' ④ 3 家 opt-out 清理 + 规则 6a 处置。恢复外审的条件是拿演练产出去审,不是拿文档。

第九轮最重的一条:我连着三轮把发送侧的问题装在账本侧 D2

§07 白纸黑字:「tracker 仍是发送决策的权威源,Supabase 是发送后的派生账本」。我却连着三轮用 status 值去解决「别再发这个人」。

status 不会让 Hermes 少发一封信,它只是让账本看起来像挡住了。L3 的阻断已改走 tracker 抑制名单compute_queue 真正查的三道闸之一)。paused 从此只剩两种原因,C1 为了兜住多义性而发明的 paused_reason 机制整个删掉

当一个修法需要发明新机制来兜住它自己引入的歧义时,先怀疑被修的那个设计。

→ 另一条(D3):v8.1 说每日 guard「只是补丁」说轻了,v8.2 说第二道闸是「唯一硬闭环」说重了——两次都为了让结论干净而说过头闸和不变量的差别不是强度,是能不能被绕过而不被发现。

🔴🔴🔴 三轮下来的信号,比任何单条意见都重要 修复动作本身是缺陷主要来源

最严重的那条它是遗留缺陷还是我修出来的
6staged 会被 run_promote() 放回 new遗留
7paused 没有出口🔴 我修第六轮时造的(裁决①)
8paused 一个值背三种语义🔴 我修第七轮时造的(B1 + B6 撞在一起)
8coalesce 把假阴翻成假阳🔴 我修第六轮时造的(A5)

连续三轮,最严重的缺陷都来自上一轮的修复动作。§08 那张「同一个毛病第 N 次」记的是漏改;这张记的是修改本身。后者更难防:漏改可以靠脚本查,而「改 A 的时候在 B 上开了个洞」没有任何静态检查能抓到。

→ 已落成合并方案 §2b 的第三条写作规矩:每修一条外审意见,必须显式回答「这个改动新增了哪些状态 / 路径 / 语义?谁会读它们?它们怎么离开?」三条实证教训同一个根 —— 状态机既要看入边也要看出边(B1);一个值承载多种原因,转换规则必然对其中至少一种是错的(C1);修一个方向的偏差前先问它会不会把偏差翻到另一个方向(C5)。我在改一个点,而它是一张图上的节点。

第八轮 · 三家第三次全部不建议直接执行 8 条 ≥2 家命中 已修

#条目命中处置
C1🔴 paused 一个值背三种语义(批3 / queued guard / L3 待审),释放规则按「有没有发信」判 ⇒ 静默清空人工队列3 家已修 原因落 outreach_log,释放按原因分叉
C2硬关闭只挡首封:496),t2/t3(:784)与 LinkedIn DM(:1678)走另外的谓词,flag 关着照发2 家已修 四个取件点全列
C3批 2 抽样 10/38 不足3 家改判 38/38 全验
C4每日 queued → paused guard 是定时补丁不是结构性防护3 家已澄清 硬闭环本来就在 A2 第二道闸
C5coalesce(subject,'')假阴翻成了假阳:空主题 inbound 被当成真回复1 家(成立且是我造的)已修 空主题路由 L3,两边都不站
C6outreach_status 值域/判据口径矛盾2 家已实查钉死 CHECK 允许 8 值,在用只有 2 个
C7补偿式撤销有 crash 窗口,并发下不可靠3 家已修 快照先写后改 + manifest + A4 兜底
C8冻结窗口漏掉了真正在发信的那个 —— Hermes 调度器2 家已补

C3 是一次裁决改判:第七轮三家方向相反(Codex「不够且没必要」/ 千问「要 38/38」/ DeepSeek「样本量不足」)构成 1:1:1 分裂,按规则没自行拍板;第八轮 Codex 改口后 ≥2 家一致,分裂消解 ⇒ 主会话拍板。理由不是「多多益善」:总体只有 38,抽样省下的 28 次核对,换来的是一个永远无法消除的推广风险,而写入不可逆。总体小到可以全查时,抽样不是节省,是自找的不确定性。

C4 的处置是「承认它不是防护」而不是「把它升级成防护」:防双发的硬闭环本来就在 A2 第二道闸({status='new' 邮箱} ∩ {tracker 866} == ∅ 不满足则冷发拒绝启动)。guard 挂了、跑晚了、顺序错了都不会导致双发 —— 因为冷发根本没启动。外审问的「调度顺序怎么定」,前提就不成立。

→ 🔴 三家一致的一条流程建议已采纳为执行前置:真写前必须在隔离库完整跑一遍「快照 → 分块写 → 复验 → 撤销 → 复验」并落盘两次复验输出。这也是对「第八轮还在出 P0」最诚实的回应:文档已经审到收益递减的区间,而演练能发现的东西,文档审不出来。

🔴 第七轮:三家仍然不建议直接执行 —— 而最重的两条是我修 v7 时亲手造的 9 条 ≥2 家命中 已修

Codex「仍不建议直接执行」/ DeepSeek「仍存在会导致方案执行失效或产生新的静默风险的内部矛盾」/ 千问「不要直接进入生产真写」。3 家出票,无降级。

#条目命中处置
B1🔴 paused 没有出口——批 3 被 Hermes 发送后永远停在 paused3 家已修 矩阵前置 newnew 或 paused
B2🔴 口径漏改第 16 次(判据表仍写批3→staged、§2b 指针行仍 175/96、§5 风险表仍旧值、窗口内仍 89)3 家已修 4 处 + 三条检查器缺口全堵
B3冷发第二道闸只看得见批 1 的 56 人,批 2/批 3 对它完全隐形3 家已修 改成 {new 邮箱} ∩ {tracker 866} == ∅
B4A8 的 queued → paused 没有触发机制——sink 只在发完信后跑,而 queued 的人恰恰没发过2 家已修 移到独立每日任务
B5批 4「不动」/ 终态体检归零 / 311→138 三者互相矛盾2 家已修 改成 311 → 138−k,k 由 dry-run 给出
B6L3「不改 status」= 疑似回过信的人在等裁决期间照常收冷发2 家已修 new → paused
B7apply_lead_status(...,'tracker') 未迁移即被设计依赖2 家已修 升为带验证查询的有序前置
B8补偿式撤销覆盖不了已发生的副作用;执行期间未冻结写方3 家已修 写明边界 + launchd 冻结窗口
B9run_promote() 本身的无差别 promote 缺陷未修2 家单独立项 写明本方案不修的理由

B1 的教训值得单独记:裁决①本身没错(staged 确实会被 run_promote() 放回 new),错在换一个状态值 = 换一整套下游语义,而我只审了「谁会读它」,没审「它怎么离开这个状态」状态机既要看入边,也要看出边。staged 有出边(方向错了);paused 一条出边都没有——我把「有一条坏出边」换成了「没有出边」,并把它当成了改进。

B4 是同族的另一面:把一条规则写进「状态转换矩阵」,会让人默认「有个东西会执行它」。但矩阵是被动的,它只描述「事件来了怎么办」,不产生事件没有事件源的矩阵行 = 一条永远不执行的规则,而它在文档里长得和其它行一模一样。

B2 的根因:检查器的三条互不相同的结构性缺口 全堵上 + 各钉一条回归自检

漏掉的位置为什么没报堵法
§2b 的权威指针行整节豁免太粗:§2b 在历史节名单里,但它装着唯一权威指针表——那是当前内容带「唯一权威」标记的行即使在历史节里也必须查
§5 风险表那一行🔴 行级标记连坐(本轮新形状):行尾一句合法的「v6 曾写…」把同一行行首的当前结论一起豁免了标记只豁免它自己所在的从句 + 紧邻它前面的那个从句(括号注的是它前面那段)
批 3 目标值 / 窗口内 89staged目标值名不是数字(第 1 类看不见);89 从没登记进 STALE补登记 + 台账节纳入历史豁免

→ 中间那条与 v7 那次「grep -v '§0d' 把要修的行一起过滤掉了」是同一族豁免规则的粒度比缺陷的粒度粗,豁免就会盖住缺陷。

→ ⚠️ 放松豁免规则之后必须回来验它仍会红,否则就是「改到绿为止」——本仓最常见的自欺形状。已对三条缺口各钉一条回归自检(喂当初真漏掉的那三行原文)+ 一条反向控制(纯历史叙述不许误报)。自检 4 条 → 9 条,全过

本轮收益最高的一条:A9 的读方条件级审计直接推翻了 v7 的批 3 方案 差点装错

v7 让批 3 的 79 人写 staged,语义写的是「已进入外联流程、由 Hermes 负责发」。实查发现 staged 在本仓的真实语义是反的

apollo_netnew_leadpull.py:2086 run_promote()
  取件谓词 status = eq.staged(默认不带 lead_source 过滤)
  动作     sb_patch(..., {"status": "new"})  ← staged → new

→ 那等于给正在修的「双发」装了一个单条人工命令(--promote --apply)就能复位的开关,而执行那条命令的人看不到任何提示说这 79 人是 Hermes 的。⇒ 裁决①:批 3 改写 paused——零自动写方、已在 OCCUPYING_STATUSES 内、且不宣称已触达。

三家互相矛盾的 4 处 · 主会话裁决

#分歧裁决一句话理由
批 3 怎么处置(扩展 / 改矩阵 / 暂缓)改矩阵:写 pausedstaged 会被 run_promote() 一条命令放回 new
文档还能不能出现硬编码人数可以,但必须可复算常量的问题不是「可能不准」,是「不准时没人会知道」;挂到会报警的载体上就能留
终态处置的自动化程度分 L1/L2/L3,单信号一律不自动终态不可逆且静默,而假阴(漏标 41%)与假阳("Away from Desk")两个方向都已实证
「整批回滚」是实现它还是降级承诺降级承诺 + 实现补偿式撤销承诺一个做不到的机制比承认做不到更危险——执行者会按「反正能回滚」的胆量去跑

A1 的口径已闭合,并把「加总平衡」做成了退出码 第 3 类判据

真库 + tracker 快照复算:311 = 56 + 38 + 79 + 138(v7 写的 140 是错的,四批加总 313≠311)。

总数30 天窗口内风险
批 1 · 库内自证5647重发
批 2 · 仅 tracker 知道3838重发
批 3 · tracker queued7965双发
批 4 · 不在 tracker138110正常新线索
合计 = 库侧 status='new'311260

前两类判据都抓不到 A1:第 1 类查过期数字,可 56/38/79 三个数全是对的;第 2 类查判据类型,与人数无关。漏的是几个正确数字之间的算术关系——每个单独看都「实查过、带日期」,加起来才露馅。⇒ 新增 第 3 类判据(加总平衡,离线,带 --selftest)+ recount_plan_headcounts.py(连真库 + tracker 复算,实跑与文档完全一致)。

本轮我自己撞到的两个坑(不是外审提的)

① 我先断言「这三列是纯数据列,没有触发器」——实查有 2 个。outreach_sequences_anchor_n1email/linkedin_url/discovery_meta 上触发且会抛异常,正好挡住我原本打算在 A12 里写的 discovery_meta.backfill_provenancetouch_updated_at 每次写都刷 updated_at直接推翻了「撤销时比对快照时刻」这个 CAS 基准——用快照时刻当基准,撤销脚本会一行都改不了却看起来很安全。

② A6 / A10 各有一半不成立。实查重复邮箱 0 组(A6 说的「一邮箱多 sequence」今天不存在,真洞是 267 行根本没有 email,其中 62 行已 staged/contacted);297 = 571 − 274 今天仍精确成立(A10 错在它被写成裸数字、且混淆了存量与日增)。

外审看不到库。不实查就照单全改,会把方案改坏在一个不存在的问题上。

12

线索流水线计划 v2 · 内部 debate 命中 5/5

Graham 9/2 定的新主线:把「Apollo 搜索产出 → 邮箱验证」这段打通并验证成全自动。 v1 草案 9/2 晚跑了内部对抗 debate,5 条攻击命中 5 条,其中一条推翻了标题目标本身。计划落在 .handoff/LEAD_PIPELINE_AUTOMATION_PLAN.md,已送外审。

⚔️ 攻击 5(最重):「全自动」在 S4 上做不到,而且原因在仓库外 推翻标题目标

三重阻断里有两重改代码绕不过去(详见 §10):REST search/match 被 plan-gate 回 403 API_INACCESSIBLEreserve_quota()provider='apollo' 硬 RETURN FALSE(migration 076b,刻意封的)。

→ reveal 只能走 Apollo MCP,而 MCP 要在 Claude 会话里调。目标改成「全自动除了 S4 这一个有名有姓的人工环」。这条要写在最显眼的地方——否则下一个人会花几天去「打通 S4 的自动化」,而那几天的产出必然是 0。本项目已有四次「建好了、过了外审、没接线」,这次的形状更糟:接线本身不可能成功,而失败原因在仓库外。

⚔️ 攻击 3:计划的范围和 Graham 的死线互斥 优先级排错了

死线是「任务 7 9/10 开工」+「2,021 credit 9/16 归零」。而计划内容是改数据库层闸 + 收敛 9 个入口 + 存量清理 + 回流 5,680 家——8 天落不了地,落地了也不产生一个 reveal

→ 劈成两条互不阻塞的线:线 A(抢额度,9/16 硬死线)= 每天一次 MCP bulk_match → 落盘 → --ingest已验证跑通、不需要任何新架构线 B(管道收敛)无硬死线。线 A 是排期问题,不是工程问题。

⚔️ 攻击 1:闸的位置从 raw_leads 移到 company_leads

草案第 1 条论证的是(DB vs 应用),架构图画的是位置raw_leads 之后)——草案把这两件事当成了一件

闸装在哪入口覆盖率代价
raw_leads86%(漏 1,599 行 + 4 个源)
company_leads100%(所有入口的汇聚点)需处理 10,537 行存量
outreach_sequences100% 但太晚reveal 已经花过钱了,违反 Graham 的硬约束

→ 架构图里 raw_leads入口之一,不是统一入口。Graham 说的「统一入口」在语义上是对的(目标态),但把现状画成目标态,下一个人会以为已经收敛了

⚔️ 攻击 2:加约束会当场炸在 10,537 行存量上——草案只引用了先例的一半

草案拿 uq_email_threads_natural_key「当场炸出 19 个重复」证明「应用层去重会静默失败」。但那个先例同时证明:在有存量的表上加约束,约束会立刻对存量生效并失败。

ALTER TABLE ... ADD CONSTRAINT ... NOT VALID(毫秒级、不锁表、对新写入立即生效)→ 存量清干净后再 VALIDATE CONSTRAINT先止血,再清创。⚠️ 但 NOT VALID 期间「约束存在 ⇒ 数据干净」是错的推论——这条必须写进注释,否则它就是下一个假绿。

⚔️ 攻击 4:回流 5,680 家 = 把一个已知不可靠的判定器放大 5,680 倍

判定器两个方向都已实证会错:否定类目 ∧ 摘要不足 4,772 家;肯定类目 ∧ 摘要不足 1,660 家(占肯定类目 73%),其中 274 家已进外联队列

阶段对象家数前置条件
R0ask_to_send_email ∧ 被判不合格11 每条自带一条独立的人类正向信号
R1ask_to_send_email ∧ 有邮箱50R0 的结论
R2可回流 ∧ 有邮箱468判定器误报率已量化
R3全部可回流5,680R2 的 ROI 为正 ∧ 误报率 < 阈值

→ 排除集必须写死并单元测试锁住加总闭合(五桶 = 10,537):已判不合格 4,739 / 测试垃圾 663→62(RLS_AUDIT% 那 601 行已删,级联带走 lead_stage_events 3,024 + lead_contacts 63;仅剩 __QA_PERF__% 62,已由 migration 085 挡住新增)/ 已在销售流程 33 / opt-out 16。与 §11 的第 3 类判据同款——加总闭合是判据,不是注释。

五个环节的自动 / 人工分界(debate 后定稿)

环节自动说明
S1 Apollo 搜索人工Graham 明确接受手动触发
S2 规则闸可全自动确定性规则,可测
S3 LLM 判 ISO可全自动误报率必须先量化(攻击 4)
S4 Reveal🔴 不可能全自动账号计划限制,不是选择——必须有 Claude 会话调 MCP
S5 邮箱验证可全自动MillionVerifier 已通
晋升进外联队列人工现状就是 --promote --apply 的人工闸门,保持

9/2 已拍板:接上确实只能开出 0 个名额(不是此前估的 143)——outreach_sequences 1,571 行里 discovery_meta->>'function' 非空 0classifier_version 非空 0 ⇒ 规则 6a 对 100% 的存量公司触发。Graham 取「接受名额 0」:闸照接,槽 2 只对以后走闸进来、带 function 的新公司开放,存量 1,442 家维持 1 个联系人(见 §09)。另外硬阻塞比文档记的宽:实查 3 家 opt-out 冲突(Focus Merchants done / Cervion queued / Alt POS queued),load_company_occupancy() 撞到即 abort。