返回日报条目

资料节译

回放1,602个输出示例:17项稳定差异中仅一项是真实缺陷

阅读原文

402 响应不只是一份价格。在 extensions.bazaar 区块中,它还可以包含输入示例和输出示例——一个示例请求和一个示例答复。目录爬虫会将它们编入索引,智能体会用它们判断某个端点是否能满足需求,然后再决定是否付费。

这就让示例成了一项声明。如果示例答复与处理程序实际返回的内容不一致,智能体就只剩三个糟糕的选择:认定端点出了故障,认定平台在撒谎,或者花钱一探究竟。这些都不好;只要示例属实,这些情况本来都可以避免。

我们重新回放了自己的示例,看看其中有多少属实。

对于每个发布了输出示例的服务(共 1,602 个——其中一个端点的正确答复是空集合,因此未纳入统计),我们取其已发布的输入,通过回环接口发送给为其提供服务的处理程序,并用递归键子集检查比较实际响应与示例。数组按严格规则比较;嵌套对象允许响应包含额外键,但不能缺少示例中列出的键。

只比较一次还不够,因为许多端点的结果本来就会变化:价格、区块高度、AI 文本、随机值。这类不一致并非缺陷。因此,每个不匹配都用相同输入额外回放了两次。如果三次实际响应的字节完全相同,却仍与示例不一致,那就是稳定差异——也就是用户每次调用都会遇到的差异。

分类数量含义
MATCH1,444示例与响应一致
VARYING141响应本身每次运行都会变化
STABLE-DIFF17每次响应都相同,但仍不一致
EXEMPT8按定义属于时间戳的字段

这 17 个稳定差异可清楚地分为以下几类:

9 个由时间推移导致。“自 2026-01-01 起的天数”在录制示例时是 284,回放时是 289。证书的“剩余天数”只会朝一个方向变化。这些示例是快照,每天都会过时。

**7 个来自每天或每小时都会变化的上游数据。**外汇汇率、资金费率窗口、新闻源、预测市场交易量,以及实时悬赏列表。

**1 个真实缺陷。**一个返回 DNS 记录集的端点公布了一种 A 记录顺序,却有一半时间按另一种顺序提供记录。

向解析器用同一个问题查询两次,你会得到相同的记录集合,但顺序可能不同。这是正常的 DNS 行为。只有当处理程序原样传递数组,而 402 响应公布固定顺序时,它才会变成错误:调用两次并比较答案的智能体会看到一个从未真正发生的变化;而根据示例进行模式匹配的智能体,则会被告知它的正确答案是错的。

修复很小——在边界处排序,用一个共享辅助函数,不添加字段:

const { sortDns } = require('../lib/dnssort');
// ...
result[tt] = sortDns(records.map(format));

记录集合不变,答案顺序确定。我们连续调用同一个端点四次并比较响应字节,验证了这一点:四次完全相同。

这并非第一次排查。更早的一轮扫描已经在另外 18 个处理程序中发现了顺序问题,并完成修复。这一个被漏掉了——这个原因值得记下来,因为这种错误看起来像是成功。

修复对象是从当天的不匹配报告中选出的:检查碰巧与示例不一致的端点,再修复其中表现出相同特征的端点。但顺序会轮换。扫描当天,解析器碰巧按示例公布的顺序返回了 A 记录。因此,该端点没有出现在不匹配报告中,没有被纳入修复对象,也就一直带着缺陷——而所有防护检查都显示正常。

从碰巧失败的样本中选出的缺陷对象,并不是真正的缺陷对象范围。真正的范围是所有可能出现该缺陷的对象。

正确的对象范围应从源代码中确定:每个需要 DNS,并且调用 .resolve*() 或 .reverse() 的已部署处理程序。这里共有 21 个文件,而非 18 个;这个范围是通过机械方式推导出来的,不是从一堆症状中挑出来的。同一轮检查还发现了一个会在确认顺序前就拼接 TXT 记录的处理程序。

现在,我们每天都会对照已部署的字节运行这项普查。它执行三项检查:每个提供 DNS 服务的处理程序都必须对数组排序、自行排序,或将数组归约为标量;未列入清单的处理程序算失败;清单中没有对应处理程序的条目也算失败,因为一条无人执行的策略记录比没有记录更糟。

这 17 个稳定差异并非全是错误,我们也没有通过删除示例来“修复”它们。易变的端点仍然会公布快照——如实发布才是正确做法,而且目录需要示例。变化在于,我们现在清楚地知道哪些示例是快照,哪些是声明;回放后发现示例与处理程序不一致,就会亮起红灯,而不再被一笑置之。

唯一的真实缺陷已经修复。它藏身其中的对象范围,现在已从源代码中逐一列出,并每天检查。

本文中的所有数字均来自 2026-10-17 针对已部署处理程序字节进行的一次回放;方法和各端点的分类见代码库的 artifacts 目录。