Repository navigation
v1.2.0 头号项:加「反驳」一遍与「规模扫描」——算法复杂度与语义分裂现在整类都抓不到 #24
Description
v1.2.0 头号工作项:加「反驳」一遍与「规模扫描」
这不是修 bug,是补两个整类都抓不到的能力面。 来源是 Pulse 项目两轮深度审查里实际遇到的问题——那两轮用的是人,不是 skill,所以问题被挖出来了;换成 skill 审同样的代码,挖不出来。
先例见 #20(v1.1.0 票面)与 #23(v1.2.0 的 6 条能力边界)。本票比 #23 更靠前:#23 那 6 条是单点判据缺失,本票是判据的形态缺失——不补形态,补单点也补不上。
一、为什么现在抓不到(机制层面,不是「模型不够聪明」)
缺口 A:算法与复杂度——第 3 维的闸门把它挡在外面
第 3 维(实现缺陷)的成立条件是「给出『输入 → 走到哪行 → 错误结果』」。复杂度问题没有错误结果——它表现为随规模爆炸、慢、耗内存,不是算错。按这条闸门去卡,它永远不成立。
扩展维度里的「性能」也接不住:那一行写的是「N+1 查询、循环内 IO、无界分配/缓存、锁内做慢操作、重复序列化」,全是工程坏味道,不是算法复杂度。而且扩展维度「按改动性质取舍」,复杂度的老问题往往不在改动里,直接被跳过。
结论:不是判据不够细,是判据里根本没有「规模」这个维度。要报一个复杂度问题,你没法给出「输入 → 错误结果」,所以它连成立的机会都没有。
缺口 B:设计与架构——七维全是「和现有东西比」
| 维度 | 它问的是 |
|---|---|
| 1 设计:机制组合 | 现有机制能不能组合出来 |
| 2 方法契约 | 单个函数的参数与缺省值 |
| 6 重复 | 有没有第二个真相源 |
| 7 过度设计 | 删掉它今天损失什么 |
四问全是「和现有东西比」,没有一个要求质疑设计本身。
更具体地说,两类典型问题正好从缝里漏掉:
- 语义分裂(同一个动作两条路径行为不一致):第 6 维的成立条件是「哪条需求变了,这两处必须一起改?」——语义分裂的答案恰恰是「没有一条需求强制它们一起改」,所以按第 6 维判据直接判「形似,不报」。但它不是形似,是同一个名字下行为分叉。
- 跨层形状不一致(抽象层与实现层类型不是同一个形状):第 2 维只问单个函数的参数与缺省,不问「换个后端要不要改调用代码」。这不是编译错误,所以编译器也不会拦。
结论:需要一个独立动作去「先假设设计是错的」,而不是又一个维度条目。维度是「按清单扫」,动作是「逐条质问」,形态不同。
二、Pulse 那两轮实际挖出来的东西(改造的证据来源)
A 类 · 算法 / 复杂度
| 现象 | 实测 |
|---|---|
| mount-cycle 可达性搜索按挂载实例树展开而不是定义图(同一张子图挂多处是官方支持的合法用法) | 19 个 Graph 定义 / 36 条边 → 89.747 ms / 375,391,792 B;改后 2.668 µs / 3,432 B |
宽输出节点每次写都线性扫 Provides → O(m²) |
8000 输出 → 55.284 ms → 改后 1.134 ms |
| 检查点元数据枚举每层拷贝完整后代切片 | 1024 层 → 24.402 ms / 70,131,552 B → 改后 0.927 ms / 3,827,288 B |
| Mermaid 绑定反复线性查找声明 | 4096 绑定 → 68.216 ms → 改后 11.102 ms;但分配字节 8,746,368 → 10,095,640(内存变差,是取舍) |
B 类 · 架构 / 设计语义
| 现象 | 状态 |
|---|---|
| 同一个 Stop 行为三分:恢复出来的失败流直接返回原失败(不发可靠写)、本轮活跃失败流走可靠写(再写一次持久记录)、重复 Stop 静默返回 nil | 行为不一致 = 已确认缺陷(探针复现 FAIL);「Stop 该不该持久化」= 契约未定 |
根 Maintenance 接口与 distributed 专用 API 不是同一形状(字段类型、数量、返回类型都不同),调用方被迫写适配层 |
形状不一致 = 已确认缺陷;是否统一 = 争议 |
通用 JSON 编解码器服务带 omitempty 的领域结构 → nil/empty/null/"" 塌成同一个值 |
已确认缺陷(P1) |
这三条正好各对应一个判据缺口:A1/A2/A3/A4 是「规模」,B1 是「语义分裂」,B2 是「跨层形状」。
三、这一版改了什么
SKILL.md
- 执行顺序新增第 2 步「反驳」:对每个公开概念(类型、接口、入口、操作、状态名)问四句——它有几处实现?同一条业务动作在正常/异常/恢复路径上语义一致吗?统一调用方能用同一个类型吗?职责有没有被另一个抽象认领?每条质问必须落到「谁会因为这个不一致而写错、改错或被绊住」,指不出具体后果就删掉。
- 第 3 步七维之后新增「规模扫描」:输入规模 N、随 N 怎么长、有没有重复访问同一状态/每层拷贝完整结果/每次重新全量扫描。规模问题不需要有错误结果才算数,证据是规模关系 + 实测。
- 判定纪律加一条「不要默认设计是对的」。
- 分级加两条校准:规模按「N 到多少会出事」,语义分裂按「谁会被绊住」。
- 七维速查表第 2 维补跨层形状、第 6 维补语义分裂——速查表是模型第一眼看的东西,入口不能藏在细则里。
references/dimensions.md
- 第 6 维「重复」的成立条件从一条扩到两条,新增「语义分裂」:判据要给出两条路径、一个具体场景、两条路径上不同的那个结果。并补两条误报边界——差异必须体现在外部可见的副作用上;上层注释已承认这条差异是有意为之的,是已知取舍,进拍板不是缺陷。
- 第 2 维扫描动作加第 10 条「跨层形状」:抽象层与实现层/后端层类型不一致,能编译不等于形状一致;判据要并排列出两个签名。
- 新增「规模与复杂度」节:为什么七维管不到它、六类可识别的代码形状、热路径放大与锁内放大、成立条件与证据形式(规模曲线优于单点数字)、取舍要写明(改索引可能让内存变差)、定级与修法方向。
references/playbooks.md、references/output-template.md、README.md
- 批处理与 API/库两类补规模高发场景与跨层形状;明确规模扫描不被任何 playbook 覆盖。
- 覆盖表补「规模与复杂度」一行——它是有「跑了/没跑」之分的扫描面,不给证据位作者就分不清「扫了没问题」与「压根没扫」。
- README 两档模式、七维表、机制、证伪闸门、设计取舍同步。
四、评测:为什么必须有第二个样本
tiny-shop 测不出这个能力——它是 150 行的结算模块,里面没有指数级回溯,也没有分裂成两条路径的动作。所以新增样本 evals/fixtures/rule-engine/(约 370 行 Go,可编译可跑测试)与配套标准答案 evals/ground-truth-rule-engine.md:
刻意植入的缺陷(写样本时埋进去的):
| # | 植入的缺陷 | 实测 |
|---|---|---|
| R1 | 依赖图共享节点时可达性按路径重复展开(无已访问集合)+ 无环检测 | 菱形 depth 20 → 8,388,561 条 / 3.53 s;阶梯 depth 24 → 同一规则 ID 出现 121,392 次(规则集只有 24 条规则) |
| R3 | 每层深拷贝完整依赖链(且被拷贝的 child 随即丢弃,纯冗余) |
depth 25 → 33.6 KB;100 → 554.4 KB;200 → 2,228,800 B(深度翻倍分配 ×4) |
| R4 | 每次查折扣都重建整张索引表,且在 Apply 的内层循环里 |
README 上限(200 规则 × 200 行)下单请求 877 ms / 250.6 MB |
| R5 | Apply 与 ApplyChecked 做同一件事但行为不同 |
差异已被 godoc 写明(fast path / safe path)→ 按 skill 自己的判据是拍板项不是缺陷 |
| R6 | promo.Pricer 与 sqlstore.Engine.Apply 类型形状不一致,后端不可替换 |
编译期断言 var _ promo.Pricer = (*sqlstore.Engine)(nil) 失败 |
写样本时自然形成、被评测挖出来才补进标准答案的(这部分同时是「标准答案也得被评测校准」的证据):
| # | 缺陷 | 实测 |
|---|---|---|
| R7 | New 不拷贝入参,而 Engine 的 godoc 承诺「规则集在构造时固定、可并发共享」 |
构造后改 rules[0].Discount → 结算立刻按新值算;并发改 → -race 报 DATA RACE |
| R8 | ApplyChecked 把「这条规则不生效」当成错误 → 任何非空规则集必然返回 ErrUnknownSKU |
20 条规则 + 一行购物车(规则实际触发 0 of 20)→ 仍然报错 |
| R9 | Apply 就地回写调用方购物车且不可重入 |
同一购物车连调两次:total 400 → 300(扣两次钱) |
| R10 | Apply / ApplyChecked 两份几乎逐行相同的定价循环 |
三条需求任一变化都必须两处同改 |
外加 C1 并发确认点与 6 条「不该报」候选(测误报边界)。
标准答案本身被校准了 3 处(v1 → v2,依据是首轮两轮的实测,每处都附了依据):
- R2(
DuplicateLines的 O(n²))从「应当报出」移到「不该报」——README 写明一次请求最多 200 个商品行,实测 200 行 → 19,900 次比较 ≈ 0.5 ms,平方级在这个上限内不可观测。原标准答案把「复杂度形状」当成了「规模后果」,这正是要校准的东西。 - R4 级别 P2 → P1——README 上限下单请求 877 ms / 250 MB,按 skill 自己写的「现实规模下就拖垮 → P1」判轻了。
- R5 从 P1 缺陷改成拍板项,并拆出 R8 / R9 / R10——两条路径的差异已被 godoc 明说,按
references/dimensions.md第 6 维「上层注释已承认差异 = 已知取舍 → 进拍板,不是缺陷」,主体应是拍板项;但两条路径各自的后果里,有两条不在 godoc 覆盖范围内且确实有害。
另外把 R1 的成立条件收紧了:reachable 的 godoc 已经声明「按路径展开」,所以 R1 的成立点是规模后果,不是「重复展开」这件事本身。
两个样本分开跑,分数分开记,才能同时回答「旧的没退化」和「新的生效了」。tiny-shop 的 G1–G25 与 v4 基线原样不动,保证仍可比较。
五、归属建议:这是 v1.2.0,不是 v1.1.0
理由三条:
- 它改的是执行顺序——
SKILL.md的执行顺序一改,按evals/runbook.md的规则就必须重跑评测,v1.1.0 的发版基线(v1.1.0 发版票面:功能点/需求点清单 + 3 项待拍板 #20 里那套)当场作废。 - 它是新增能力,不是修复。v1.1.0 的定位是「修掉 13 条设计问题 + 口径修正 + 一份能复核的基线」,把一整类新能力塞进去,发版风险不可控。
- v1.1.0 的四个 PR 还没合(docs(evals): 记录首轮 A/B/C 评测基线与口径缺陷 #18 → fix(dimensions): 修硬编码判定自相矛盾 + 命名口味边界;GT 扩到 25 条 #19 → fix: 修 5 处执行文件口径不一致 + 1 处事实错误;lint 新增相对表述检查 #21 → docs(evals): 折进判分细则 + 记录 v4 轮基线(25 条口径,冻结提交 7c1c4ea) #22)。本票的分支叠在它们之上,顺序合即可。
建议的顺序:先把 v1.1.0 四个 PR 合掉、tag 发版;再合本票的分支,按本票重跑一轮评测,更新基线,然后发 v1.2.0。
六、验收标准
- rule-engine 样本 B+C 两轮跑完,评分表贴到 PR 里(
evals/baseline.md追加一节,不覆盖旧记录)。 - R1–R10 里计入分母的 9 条逐条记录命中与归类;误报必须为 0——「不该报」清单里的 6 条候选不能被报出来,尤其
cartTotal那种单次线性遍历、以及DuplicateLines的 O(n²)(README 上限 200 行实测 0.5 ms,平方级在该上限内不是缺陷)不能被当成规模缺陷。 - 规模类结论必须给数字(规模 N + 实测开销),只说「这里可能慢」的按计分要点最多记 0.5。
- tiny-shop 的 25 条口径必须重跑才能声称「没退化」——
SKILL.md的执行顺序变了,按evals/runbook.md的规则 v4 基线不能直接沿用。要给两轮分数的对比。
七、相关
| 事项 | 位置 |
|---|---|
| v1.1.0 票面 | #20 |
| v1.2.0 的 6 条单点能力边界 | #23 |
| v4 轮基线与判分细则 | evals/baseline.md、evals/rubric.md(PR #22) |
| 两个样本与两套标准答案 | evals/fixtures/、evals/ground-truth.md、evals/ground-truth-rule-engine.md |
| 重跑的操作手册 | evals/runbook.md |
Activity
Luo-root commentedon Oct 28, 2026
评测结果(2026-10-29,冻结提交
dad4adc)PR #25 已提(base
docs/eval-v3-baseline)。四轮评测已跑完,评分表在evals/baseline.md末尾的「rule-engine 轮 + tiny-shop 回归轮」一节。跑法
SKILL.md的执行顺序变了,按evals/runbook.md的规则必须重跑才能说「旧的没退化」。dad4adc。数字
验收标准逐条对账
evals/baseline.md追加一节,不覆盖旧记录)cartTotal与DuplicateLines的 O(n²) 两轮都明确驳回并写进「已查过没问题」三条要记下来的结论
① 新能力确实生效。 两轮都把「依赖图按路径重复展开 → 指数膨胀」报成 P1 规模缺陷并给了多档曲线(R1 命中 1/1);两轮都独立报了「依赖成环 →
fatal error: stack overflow整个进程死掉」。这类问题第 3 维按定义抓不到——它的成立条件是「输入 → 错误结果」,复杂度问题没有错误结果。② 新能力带出了新盲区(本票新增两条待办)。
Apply/ApplyChecked两份逐行相同的定价循环)两轮都漏。 模型把这两条路径认成「语义分裂」,就不再提「重复」了——语义分裂把真重复遮住了。ApplyChecked把「这条规则不生效」当成错误 → 任何非空规则集必然失败)两轮都漏。 C 轮甚至在「已查过没问题」里明确肯定了hasUnknownSKU的语义与 README 一致。语义分裂被识别了,错误语义这一层没被追问。这两条建议进 v1.2.0:第 6 维补一条「同一动作的多条路径要分三层看——路径分裂 / 错误语义 / 代码重复」,并在
references/dimensions.md的语义分裂一节补一条误报边界(「差异被识别 ≠ 差异被追问」)。③ 标准答案本身被推翻 3 处(v1 → v2,依据是首轮两轮的实测,每处都可复核):
DuplicateLines的 O(n²))从「应当报出」移到「不该报」——README 写明一次请求最多 200 个商品行,实测 200 行 → 19,900 次比较 ≈ 0.5 ms。原标准答案把「复杂度形状」当成了「规模后果」。Apply与ApplyChecked的差异已被 godoc 明说("fast path" / "safe path"),按第 6 维「上层注释已承认差异 = 已知取舍 → 进拍板,不是缺陷」,主体应是拍板项;但两条路径各自的后果里,有两条不在 godoc 覆盖范围内且确实有害。另外收紧了 R1 的成立条件:
reachable的 godoc 已经声明「按路径展开」,所以 R1 的成立点是规模后果,不是「重复展开」这件事本身。这轮挖出的 GT 之外真缺陷
四轮合计挖出 20 条标准答案未收录的真缺陷,全部经读码/实测复核为真(详见
evals/baseline.md)。其中最值得注意的三条:Apply完全忽略Rule.Depends→ 价格由切片顺序决定,README 写的依赖约束不生效Explain的返回值与引擎内部规则共享底层数组 → 调用方改一下解释结果就改了规则集这些是标准答案该补而没补的——当前 9 条分母仍只覆盖样本真实缺陷的一部分。已列进
evals/baseline.md,下一轮回填 GT。归属建议(不变)
本轮改造仍属 v1.2.0,不进 v1.1.0:
建议顺序:先合 v1.1.0 四个 PR → tag 发 v1.1.0 → 再合 #25 → 把 R8 / R10 两条盲区补进 skill → 重跑一轮 → 更新基线 → 发 v1.2.0。
Luo-root commentedon Oct 28, 2026
补充:独立复算交叉校验(含一处更正)
评分表由一个独立子 Agent 按同一份细则、不看本节重算了一遍,结果对照如下:
结论方向完全一致:四轮误报全 0;rule-engine C 轮命中与归类均不低于 B 轮;rule-engine 的证据强度高于 tiny-shop;tiny-shop 两轮 raw 都高于 v4 旧基线;R8 与 R10 是 rule-engine 两轮共同的薄弱点。
复算查出一处实质错误(已更正)
rule-engine C 轮其实没有确认「并发没问题」。 「引擎内部无共享可变状态」那句在校正前那一轮的报告里,我错带到了重跑轮——重跑轮的「已查过没问题」11 条里没有并发这一条(它的并发维度两条全是缺陷:
New别名与Explain返回值别名)。更正:表达分 23.5 → 23.0,raw 82.5 → 82.0。
其余差异全部落在已列明的口径分歧里
复算方与本基线在 11 处分歧上取了不同读法,主要几处:
这 11 处连同另一种读法的数字影响都写进了
evals/baseline.md的「口径待裁定」一节。逐条按「两种读法」对齐之前,本票里的数字应视为一个区间而不是一个点。复算方自己列出的 7 类口径分歧建议留在复算报告里,不入版本控制。