一份案例,跑两个实现,判决必须一致。不一致的地方,要么实现有洞,要么断言在空转。
我们给被测实现注入了 72 种谎话——谎报成功、伪造账单、冒充别人、偷偷挪走别人的订单。 适用的 46 种里,断言当场抓住 44 种;漏掉的 2 种写在台账里,不在脚注里。 (怎么验证的 · 哪些抓不到 · 打开能点的判决页 ↗)
当前版本 0.78.3 · 21 种断言 · 33 条命令 · 72 个变异体
这一行是算出来的:版本取自 package.json,断言种类取自 KINDS,
命令数与变异体数取自代码本身。scripts/status-line.mjs 对不上就红,CI 在跑。
在线演示 ↗ · 三十秒判断 · 六十秒上手 · 三种判决 · 六个域 · 命令 · 边界 · 文档地图
如果你的 Agent 测试长这样,这个工具帮不上忙:只有一份实现、断言靠模型看输出、失败时靠人读日志。
如果长这样,它就是给你写的:
| 你的现状 | 这里对应什么 |
|---|---|
| 有 mock 实现,也有真实后端,但没人证明两者行为一样 | compare:同一份案例跑两遍,判决必须逐条一致 |
| 测试挂了,你分不清是代码坏了还是环境没起来 | 三值判决:unknown 是一个独立结论,不是失败的别名 |
| 断言写了很多,但不知道哪条真的在起作用 | certify + 变异体:故意写坏实现,看哪条断言抓得住 |
| 想让 Agent 自己改代码,但不敢让它自己判断改对没有 | 断言机器可判,禁 LLM judge;selftest 13 条标签由机制产出 |
| 手上有接口但没测试,不知道从哪开始 | discover:输入网址,生成能跑的适配器和案例草稿 |
一句话:你在为「Agent 改的代码到底对不对」找一个不依赖信任的答案。
不适合的场景也说清:它不做性能测试、不做 UI 测试、不做安全扫描。它只回答一个问题——某次业务操作的行为,是否符合一份写死的、机器能判的预期。
第一条命令不需要任何准备,也不用装依赖。从仓库跑:
git clone https://gitee.com/liu-everything_impossble/agent-web.git
cd agent-web
node bin/agent-web.mjs selftestnpm 包尚未发布。 包名
agent-case已定好(仓库与命令都叫 agent-web,同一份东西),package.json、bin双命令名、files白名单都就位,npm pack装到干净目录里跑过--version与selftest。 卡在发布凭据(本机npm whoami仍是 need auth,registry 上agent-case现在返回 404)。 所以下面这行现在跑会 404,发布完成后它才成立——届时本段与docs/LIMITS.md的对应条目一并删掉:npx agent-case selftest # 发布前会 404
selftest 用内置内存实现跑内置案例:pass、fail、unknown 三种判决各出现一次。
fail 的那条会直接告诉你「订单 1001 当前 MAKING,不允许直接变成 PICKED」——不是「测试失败」四个字。
手上已经有一个能跑的系统?看你想省哪一步:
./quickstart.sh http://127.0.0.1:8080 # Windows: .\quickstart.ps1 那个地址
# 只读接口 → 直接生成能跑的适配器 + 案例草稿 + NEXT.md 施工单
# 有流量可录 → 用 tap + learn,适配器、孪生、案例一起掉出来(见下两节)quickstart.sh 就是 discover 加三条「接下来做什么」的提示;它不猜业务规则,
猜不出来的会写在 NEXT.md 里,而不是悄悄编一条。
想看它动起来:
52 秒介绍片(判决三种、输入网址、录流量、断言准入、出题批改):docs/media/promo.mp4
Agent 改代码改得很快,但「改对了没有」经常只能靠人肉点一遍。单元测试管不到跨请求的状态流转,端到端测试又慢、还不稳。
agent-web 的做法:把一次真实业务操作写成案例(做什么 + 结果该是什么),然后拿它同时打两个实现——一个内置的内存实现,一个你真实的系统。两边判决必须一致。
一致,说明案例可移植、断言没写死实现细节。不一致,就有一处要修,而且你能立刻知道是哪一处。
- pass:所有断言成立。
- fail:有断言不成立,并指出哪一条、期望什么、实际什么。
- unknown:缺观测、环境故障、案例还没人确认。留在分母里,不算通过。
可判定率 = (pass + fail) / N。unknown 不回收,所以这个数字只会因为观测被修好而上升,不会因为掩盖而上升。
没有适配器也能起步。discover <网址> 探根路径、试 8 个常见 OpenAPI 位置,然后:
- 只读接口 → 一个能跑的只读适配器(写能力一律
unsupported) - 每个资源 → 一条案例草稿,断言只有
unchanged:读不该改变任何东西 - create 接口(
--writes)→ 往返案例:建一条,断言count_delta +1 - 一份
NEXT.md施工单:要接写操作,改哪四处、每步怎么验收
它不猜三件事:写操作该保证什么、带参数接口的路径、没有 OpenAPI 时的路由。 猜出来的 404 会变成假失败,而假失败会让工具失去信任。
写适配器是这套东西最贵的一步。这一节讲怎么把它压到十分钟。
agent-web tap --target=http://127.0.0.1:8199 --port=8197 --out=.tmp-tap/rec
# 另开一个终端,正常点几下:登录、建三条任务、完成一条、看列表
agent-web learn .tmp-tap/rec/traffic.jsonl --out=.tmp-tap/learned --name=tasks
四样东西一起掉出来:
| 产物 | 是什么 |
|---|---|
adapter.yml |
声明式适配器,不写代码 |
twin.json |
回放孪生:这五分钟里应用答应过什么 |
cases/*.json |
案例草稿,断言只有从观测直接算得出来的 |
unclassified.jsonl |
认不出的形状,一条不藏 |
twin.json 是白送的第二个实现。同一批案例同时打真实系统与孪生:
agent-web compare .tmp-tap/learned/cases --adapters=.tmp-tap/learned/adapter.yml,.tmp-tap/learned/twin.json
跨适配器判决一致率 100.0% (1/1)
视图契约
learned/adapter.yml 与 learned/twin.json:兼容(2 个集合逐字相同)
然后是最要紧的一步——把应用改坏,不重录:
TODO_APP_BUG=1 node examples/todo-app/server.mjs &
agent-web verify .tmp-tap/learned/cases --adapter=.tmp-tap/learned/adapter.yml
FAIL todo/recorded assertion_false
目标未达成:tasks 变化 2(期望 3)
这条红说明案例抓的是应用的行为,不是录制的文本。如果 tap 只是把响应存下来,改坏的应用会照样绿。
三条硬规矩:
- 录状态,不录动作。 同类工具录动作得到一份脚本,我们录状态得到孪生与判决。
- 口令与 token 不落盘。 换成
password#1这种稳定伪名,同一个值始终同一个名字。正文里用户自己填的敏感内容不在脱敏范围内,录制文件仍然是内部资料。 - 生成的每一样都标
inferred,案例默认草稿。 不accept就不出判决——机器观测不许冒充成人的期望。
做不到的三件:不猜业务规则(录到下架成功不等于知道「库存不足该拒绝」);不重放没录到的路径(unknown,不是 pass);只支持单角色链路(录到管理员才做得成的删除,回放会 403)。
21 种 kind,全部机器可判,没有一种是「让模型看看对不对」:
| kind | 说什么 |
|---|---|
entity_exists / field_equals |
某行存在 / 某字段等于某值 |
count_delta |
某集合数量变化了多少 |
status_transition |
状态按给定序列走过 |
unchanged |
某段观测初态到终态没变 |
event_absent |
某类事件一次都没发生 |
relation |
字段之间的关系:合计等于明细之和、单价乘数量等于小计、列表有序 |
sorted_by / unique / type_of / subset |
形状:有序、不重、类型对、取值在集合内 |
composite |
与 / 或 / 非 |
window / every_step |
圈定步骤区间 / 每一步都检查不变量 |
pagination |
跨页检查重复、漏行、顺序与 total |
payload |
检查某一步的 HTTP 响应体 |
linkage |
检查跨集合实体关联与对应数量 |
timeliness |
终态 |
state_machine |
trace |
recurrence |
定时任务:窗口内按固定间隔铺槽,抓重复跑、跑偏、漏跑、总数不符 |
precedence |
流水相对序:expect 按先后列事件做贪心子序列匹配,抓「被跳过」(precedence_missing)与「顺序反了」(precedence_inverted)——插行不误报,位置型断言做不到 |
关系式比写死具体值更耐移植:totalAmount == Σ items.subTotal 在哪个实现上都该成立,
而 totalAmount == 56 只在菜单价格不变时成立。形状断言同理——「列表不重」不依赖任何具体编号。
断言不是描述,是武器。杀不掉任何变异体的断言不许入库。
做法:把「能力」拆成一组语义变异(金额少一分、数量全变 1、流水被吞、越权写放行……), 让每条断言去杀它们。在正确实现上成立、在坏实现上不成立,才算抓得住;两边都成立,就是摆设。
抓获率的演进,每一步都有对应机制上线:
| 阶段 | 抓到 | 靠什么 |
|---|---|---|
| 12 个手写变异体 | 8 / 12 | 基础断言 |
| + 关系式 | 11 / 12 | relation |
| + 形状断言 | 14 / 16 | sorted_by / unique / type_of / subset |
| + 机器生成 | 38 / 40 | breed 照着视图形状造变异体 |
抓不住的分三档,不许混在一起算:欠账(看得见差异,只差断言)、 说不清(连差异都造不出来,先确认案例走没走到)、不适用(该适配器没有它声明的能力)。 「说不清」不等于等价——等价变异体问题四十年没解决,这里只要求投稿人给出可区分性证明。
它不止说哪里有洞,还说补哪条:--suggest 给出的每条建议断言,都当场在两个实现上验过
(正确实现 true、坏实现 false)。这四行不是想出来的,是跑出来的。
同一条案例跑两遍,自增 id、时间、取餐号本来就会变。此前这些差异被算成「变异体被看见了」,
真实后端上 m-duplicate-row 的见证是订单号从 196 长到 210,还会据此建议写 orders.0.id == 196。
现在每案例先跑两遍正确实现求出漂移集,再从见证里剔除。
顺带的优化:state 层变异体只改观测,certify 不再重放整条轨迹,直接用正确实现的 ctx 变换求值。
真实后端 3 条案例 44.5s → 15.1s(2.95×;计时口径:作者自己的机器与 CI,自证)。等价性由 scripts/state-equivalence.mjs 逐格证明并进 CI。
breed 照着规范视图的形状造故障:数值字段差一 / 翻倍 / 丢分,字符串置空 / 大写,
数组丢尾 / 逆序,枚举换值,嵌套叶子逐个试。10 个可造位置出 29 个变异体,4 个恒等算子自动跳过。
生成不等于入库:每个都要过 certify,杀不掉的自动落「说不清」,不会污染抓获率。
变异体还能声明 applies_to——账本域的变异体在咖啡域就是「不适用」,不进分母。
翻页的四种坏法都藏在页与页之间:同一行出现在两页里、边界少一行、翻页顺序忽前忽后、
total 报的数字和实际条数不符。单看任何一页都是好的,逐页的 count_delta 也都是对的
—— 每页确实拿到了 pageSize 条。
{ "kind": "pagination", "collection": "orders", "steps": [6, 7, 8],
"total": 5, "pageSize": 2, "ordered_by": "id" }它读的是那几步的执行返回(trace[i].payload),不是规范视图——视图是整份状态快照,
一条翻过页的列表在视图里和没翻过一模一样,拿视图判分页永远判不出来。
三个只在返回体上下手的变异体,视图完全不变,所以只有它能抓:
| 变异体 | 怎么坏 | 被抓时说什么 |
|---|---|---|
m-page-duplicate |
第二页把上一页末行又带一遍 | 重复行:orders 的 id=1003 第 7 步的返回里出现了两次 |
m-page-drop |
每页少给尾行 | 第 7 步不是最后一页,却只有 1 条(每页 2) |
m-page-shuffle |
第一页内部倒序 | 翻页乱序:按页次拼接后 id 从 1002 掉到 1001 |
顺带修掉一个真 bug:memory 适配器的 order.list 原来在规范视图上按 ownerId 过滤,
而视图里只有 username —— 顾客的订单列表恒为空,只有店员看得见全部。之前没有案例用过
order.list,所以一直没露出来。
一个变异体加一条案例出一道题,题面只给症状和现有断言,不给 catch_hint、不给变异体 id。
批改不看提交说了什么,只把它的断言放到两个实现上各跑一遍:正确实现上成立、坏实现上不成立,才算解出。
同一批题两份提交:认真写的 11/11、exit 0;把案例原有断言抄一遍的那份 0/11、exit 1—— 它交的全是真断言,读代码看不出区别,只能跑。这就是「标签不需要人来标」的意思。
为什么 good 只有 11 份答案,题却有 22 道:建议断言只能表达「集合内某个字段的取值差」,
整实体替换与集合外差异表达不了。剩下 11 道得人写答案。于是拆成两条校验:
默认「必须答满」(漏一题就 exit 1),--allow-missed 是「答了就必须对」(没答的照实报,
答错或交不上来的仍然失败)。CI 用后者——防瞎写和防偷懒是两件事,不能混成一件。
drill grade --record 把每次批改记进台账,drill stats 出统计。
台账只存题号、状态、每个断言的判定、提交指纹——不存提交内容。
所以 11/11 会显示成 100.0% [74.1%, 100.0%],而不是「100% 稳了」;
每题至少 5 次批改才分层,不够就记 insufficient_data。
drill --order=empirical 按台账里的经验难度出题,难的在前。
不想自己跑?这一节的两个页面已经托管在 GitHub Pages 上,点开就是本次 CI 真跑出来的判决 ↗。
flow 把一条案例渲染成自包含 HTML:执行流程逐步可展开、状态事件、每条断言的三值结果、
被拒的步骤直接标红「期望 applied 实际 rejected」。点「只看偏差」只剩有问题的行。
给一个目录,出总览页:判决分布、可判定率、带锚点的案例目录,点目录跳到对应案例。
learn 学得出来的前提是「字段名认得」。别人的系统里 totalAmount 可能叫 sum、
pickupNo 叫 no、orders 叫 tickets。align 干的就是这件事——给建议,不替你决定。
$ agent-web align our-view.json their-view.json --out=align.yml
字段映射 · 3 个集合 · 17 / 17 条字段 → 100.0%
orders → tickets 置信 95.0%
createTime → at 94.0%
id → key 94.0%
items → lines 94.0%
pickupNo → no 88.0%
totalAmount → sum 94.0%
五路信号,权重写死不调参:名字(复数归一到同义词组)、逐行取值一致、取值多重集一致 (行序不同也认)、枚举重合、基数一致。类型不同直接出局。
低置信度一律不猜:低于 0.75 进 unresolved 并给 top-3 候选。改名的夹具恢复 17/17、
零错配;把行打乱、totalAmount 改成 x9、pickupNo 删掉之后恢复 15/17,仍然零错配,
剩下两条如实写着「需要人签」。宁可说不清,也不许错配——一个错配的字段会让案例在
「看起来对、其实错」的地方判 pass。
14 条档案,每条四要件:症状、根因、指纹(结构 / 见证 / 探测器)、判据(哪条命令或测试守着它)。 缺判据不收——没人守的档案只是回忆录。
recall 在跑批后比对指纹:第一次命中只提醒,第二次犯同一条就 exit 1 拦下。
加深记忆不是靠自觉,是靠退出码。
| 域 | 实现 | 它证明了什么 |
|---|---|---|
| 咖啡店点单 | 内置内存 + 真实 Spring Boot 后端 | 同一份案例打两个实现,判决一致率 100%;假通过能被抓出来 |
流浪动物追踪 cases/animal |
Spring Boot 3 + MySQL 真实后端(第三方项目) | Spring Page 信封、大写状态、三角色;审批接口没有角色检查——案例判 fail,细节 |
| 复式记账 | 两个自造实现(整数分 vs 两位小数字符串) | 内部表示完全不同,视图逐字相同;借贷平衡这类不变式能表达 |
词库检索 cases/lexicon |
两个自造实现(字典树 + 贪心分词 / 二分 + 反向 DP) | 900 条数据、每页 10 条翻三页:跨页重复、边界丢行、total 说谎这三种坏法只有在这个规模上才真的暴露,细节 |
| RealWorld 契约 cases/realworld | 两份独立实现(fake-server.mjs 用 Map,fake-server-b.mjs 用数组) | 一份 OpenAPI 契约 + 13 个官方 hurl 套件原样转成案例:100% 判决一致;把 B 故意弄坏之后,compare 立刻指出是哪一条案例分歧 |
| 定时任务调度 cases/cron | 两个自造实现(数组 + 槽位 Set + 存 nextDue / 槽位键 Map + lastSlot 现算) | 时间是槽位序列,不是标量:recurrence 抓重复跑、跑偏、漏跑;4/4 判决一致,细节 |
另外还接过一个真实后端:实验室预约平台(cases/labaccess,Spring Boot 3 + MySQL)。它带来六个域都没有的形状——状态被时间推着走:时段有容量,批准才发凭证,凭证只能在 startAt <= now < endAt 里签得出来,有效期还被预约结束时间截断。但只有一份实现、没有 compare,按 L11 的门槛不计入「验过的域」,细节。
第三个域逼出了一个机制:变异体可以声明 applies_to。
账本域的变异体在咖啡域是「不适用」,不进分母——否则抓获率会被无关变异体砸成假坑。
第四个域(词库)补的是数据规模这一维:前三个域的分页断言都只在小结果集上跑过,
total 说谎、边界丢行这类坏法在小数据上看着全绿。
词库域还顺手补上了一个断言盲区——pagination 原先只数各页行数,
从不读实现自己报的 total,于是「30 条报成 900」在每一页都满的情况下全绿。
RealWorld(84k star,一份 OpenAPI 契约,990+ 个实现)的官方 13 个 hurl 套件已经原样转成 13 条案例。
这个域的门槛是「两个独立实现 + compare 100% 一致」,所以补了第二份实现:
examples/realworld/fake-server-b.mjs——同一份契约,内部全换(数组存储、不同的 slug 规则、
增量计数器、单调时钟、表驱动校验),只保留可观测行为。
node examples/realworld/fake-server.mjs 8299 --allow-reset &
node examples/realworld/fake-server-b.mjs 8298 --allow-reset &
node bin/agent-web.mjs compare cases/realworld --adapters=realworld,realworld-b结果:13/13 一致,3 个集合逐字相同。
但「一致」本身可能是案例在睡觉,所以 B 带一个故障开关,专门用来证明对照看得见:
REALWORLD_B_BUG=order node examples/realworld/fake-server-b.mjs 8297 --allow-reset &
REALWORLD_B_BASE_URL=http://127.0.0.1:8297 node bin/agent-web.mjs compare cases/realworld --adapters=realworld,realworld-b
# → 12/13,realworld/pagination 分歧(pass vs fail),退出码 1REALWORLD_B_BUG 还有 count(收藏数恒 0)与 owner(跳过作者校验)两档。
这三档都写进了 test/realworld-compare.test.mjs——一个只验「一致」的测试,
一个什么都不做的适配器也能通过。
| 命令 | 干什么 |
|---|---|
selftest |
零依赖自测,三种判决各来一次 |
verify |
跑案例出判决 |
report |
由判决结果出指标;--counts 只数轨迹(步数 / 重复读 / 绕路比 / 撤销的写 / 重试),不进判决 |
accept |
确认草稿;没确认的案例不出判决 |
capabilities |
打印能力清单与证据等级 |
coverage |
能力覆盖率对账,--min 设门槛 |
probe |
探针:查适配器自己有没有在撒谎 |
certify |
判据准入:变异体 × 断言杀伤矩阵,--suggest 给补法 |
breed |
照着视图形状自动生成变异体 |
drill / drill grade |
出题 / 批改,标签由机制产出 |
compare |
同一份案例喂多个实现,比判决 |
drift |
比较两次跑批的初态形状 |
record / prepare / draft |
从真实动作日志还原轨迹、生成案例草稿 |
flow |
单案例判决页;给目录出总览页 |
discover |
探一个站点:只读适配器 + 案例草稿 + 施工单 |
crosscheck |
— |
import hurl |
把 hurl 套件转成案例草稿(不联网、不执行) |
tap / learn |
反向代理录一段流量,长出适配器 / 孪生 / 案例草稿 |
mutants |
变异体目录:谁的坏法、怎么抓、怎么投稿 |
ingest |
把别家的测试报告翻成三值判决,数清「跑绿了但什么都没证明」的比例 |
audit |
假绿审计:拿真实后端当尺子量候选实现 |
gate |
四行口径门禁:契约覆盖率/判据抗变异率/独立证据判决率/unknown 占比一次出齐,缺任一行 exit 1;独立证据行带可比分母(可比为零同缺行),--min-comparable 可设下限 |
traps |
陷阱普查:这批判决有多少是盲的 |
knowledge / recall |
事故档案 / 重犯检测(第二次 exit 1) |
init |
生成案例模板 |
version |
版本、最近变更、版本号三处是否一致 |
- 没有 LLM judge。 判不了就记
unknown并留在分母里。引入打分模型等于把自己要解决的问题搬回来。 - 规范视图是契约。 案例只写视图里的字段,不写实现细节;映射故意写在适配器里,可移植性靠
compare验。 - 「谁来做」是案例的一部分。 每一步带
as,适配器忽略角色会被 traps 指出来。 - 建议断言必须当场验过。 正确实现 true、坏实现 false 才给出来;位置相关的建议会额外警告。
- 三档状态不混算。 欠账、说不清、不适用各有各的出口;混在一起,基线表三天就没人看了。
- 两条校验不混用。 「必须答满」防偷懒,「答了就必须对」防瞎写——合成一条就会互相放水。
自己写案例是最贵的一步。import 承认这件事,给了一个不靠猜的入口:把别人已经写好的接口测试套件转成案例草稿。
RealWorld 是一组可复用的 API 契约语料:官方仓库附带 13 个 hurl 套件。导入器会保留路由动作、路径参数、角色和 token 捕获;不支持的断言会进入 .todo.md,不会静默丢弃。
node examples/realworld/fake-server.mjs 8299 --allow-reset
node bin/agent-web.mjs import hurl examples/realworld/hurl --out=cases/realworld --as=user
# 先逐条检查生成的案例与 .todo.md,再 accept,最后运行:
node bin/agent-web.mjs verify cases/realworld --adapter=realworld --detail转出来的都是未确认草稿,accept 之后才能出判决。转换器只做能确定的事:
| hurl 里的东西 | 转成什么 |
|---|---|
| 请求 + JSON 体 | 一步,能力名按方法 + 资源推导 |
HTTP 201 / HTTP 422 |
期望结果:applied / rejected;5xx 整块跳过 |
Authorization 头 |
这一步用哪个角色(--as 指定) |
[Captures] slug: jsonpath "$.article.slug" |
该步捕获,后续 {{slug}} 作为路径参数回填 |
[Asserts] jsonpath "$.tags" count >= 1 |
payload 断言(读返回体,不读视图) |
| 读不懂的断言 | 写进同名 .todo.md,一条不丢,一句不猜 |
本地夹具已经内置:examples/realworld/fake-server.mjs 提供零依赖的 19 端点实现,适配器位于 src/adapter/realworld.mjs。它便于验证导入器与判决链路;不能替代对真实 RealWorld 实现的兼容性测试。
状态口径要分清:cases/realworld/*.json 是 import hurl 的派生文件(断言逐条来自官方 hurl 原文,不许手改),当前实测 13/13 pass,并已进 CI。全绿只证明「转换器 + 适配器 + 夹具」自洽,不证明某个 RealWorld 实现是对的。
夹具只证明「转换器 + 适配器 + 判决链路是通的」,不证明「某个 RealWorld 实现是对的」——要验证真实实现,把 REALWORLD_BASE_URL 指向目标服务(详见 examples/realworld/README.md)。
这一节分两层。以下能力已经有代码和测试守着,不该再算成「没做」:
- 断言 21 种(完整清单见 docs/SPEC.md 第 4 章)。其中
window/every_step/pagination/linkage/payload/timeliness/state_machine曾经被写成「缺的能力」,现在都有了,各自配一个只有它能抓的变异体(kind-necessity会验)。 align能提出跨命名映射并附证据,含嵌套数组下钻与语义重叠检测;低置信度留给人签。crosscheck用不经过适配器自己的fetch的通道再发一遍,比对响应指纹。breed从视图形状生成变异体,13 个算子;生成的必须先过certify才准进目录。drill grade --record/drill stats已有台账、Wilson 95% 区间和经验排序;每题至少 5 次才分难度。negatives按清单的声明派生负例候选(必填缺失、角色越权、类型不符、边界越界、不存在实体、重复提交), 全部标disputed,只出审阅表。discover/tap/learn/import hurl能从网址、流量或现成套件长出适配器与案例草稿。
剩下的边界,编号与 docs/LIMITS.md 的 L1–L14 一一对应。 每条只写没解决的那部分——已经做到的在上面的清单里,这里不再重复:
先交代证据的等级:本 README 里的一切数字(抓获率、一致率、耗时)全部出自本仓自己的 CI,
脚本可复现——但复现跑的是同一套脚本,到 v0.78.3 为止没有第三方独立复现过。
「可复现」是机器事实,不是外部效度。knowledge/ 与 docs/mutants/ 的投稿通道
就是为把自证变成他证开的,进来一单算一单;在那之前,把这里的数字读作
「作者对他自己这把尺子的自洽性证明」,比读作「正确率」诚实。
等不及投稿通道的话,有一个更快的办法——盲测包:
一条命令生成自包含 zip(内置全部适配器 + 五个零依赖域的案例,不含任何判决),
你在自己的机器上两条命令跑完,把结果发回来;我们用构建时留底的对账基准逐案例对账,
判决一致即公布。37 个案例,pass/fail/unknown 会完全落在构建者看到的同一批格子里——
这就是「独立复现」的最小形式,已经在本机跨进程复现过(37/37)。
- 写语义仍要人定(L1)。
tap能记录应用做了什么,但不能推断「库存不足应拒绝」;业务方必须确认规则。 - 字段映射仍需人审(L2)。 嵌套数组字段、跨集合的同名异义,机器给不出定论,必须人签。
- 不给通用「流程质量分」(L3)。 先定义领域目标、指标和权重;合法性判决与质量评分分开。
- 负例期望仍要人签(L4)。 「该不该拒绝」是业务决定;候选只出审阅表,不进判决。
- 变异体覆盖不完整(L5)。
breed只从观测形状造故障(13 个算子;全仓口径 72 个变异体,适用 46 抓到 44)。执行层与业务层的坏法(超卖、退款超过实付、审批跳过)仍靠投稿——生成器知道形状,不知道语义。 - 任意等价变异不能普遍判定(L6)。 有限状态/有界场景可做证明或穷举;其余只能记为「预算内未区分」,不能宣称等价。
- 跨实体时序仍缺(L7)。 单条链路的时间已经能判了——
timeliness比两个事件的真实时间戳,强制声明timezone,缺时间戳记unknown而不是判假(v0.48.0)。缺的是跨实体:「订单必须在库存锁定之前创建」「退款必须在原支付之后」这类约束要等有依赖图的域才验得了。 - 适配器仍可撒谎(L8)。
probe查十五项机械性质,crosscheck(v0.54.0)再用独立通道复核响应,两件加起来能抓「声明了却不存在」「忽略角色」「读偷偷写」「返回体做手脚」「资源死了还谎报成功」。仍抓不到的是会看探测器的骗子(probe 中段七项已每次洗牌,但开场/泄漏/close 三个锚点与每项内部的交互模式仍固定,读得到源码的适配器仍能区分探测与运行),以及「把整个globalThis.fetch换掉」;同进程加载的爆炸半径也没有隔离——适配器 patch 得到裁判自己 import 的模块,「加载适配器 = 以你的身份执行代码」是字面意思。 - 并发暂不开放(L9)。 没有并行执行器;恢复前要证明串行与并行的判决、死因、逐步回执逐字一致。
- 难度仍受样本来源限制(L10)。 需要多个独立提交者/批次,才可把统计解释为外部难度,而不只是自测结果。
- 真实后端只覆盖六个领域(L11)。 门槛是「两个独立实现 +
compare一致率 100%」。前四个域各自补上了第二实现,第五个(RealWorld)在 v0.55.0 补齐,第六个(定时任务)在 v0.60.0 补齐;再往上加域仍然是手工活。 - 语义层只覆盖一域(L12)。
policy/coffee.rego是咖啡店规则的第二次声明,11 条判例(1 好 10 坏)由conftest判 allow/deny;其余六域没有规则副本,且 Java 改了规则 Rego 不会自动知道——只有判例红了才暴露。不判拒绝码,也不判跨订单时序。 - npm 包还没发出去(L13)。
package.json的name、bin双命令名、files白名单都就位,npm pack装到干净目录里--version与selftest跑通过;缺的是发布凭据——本机npm whoami仍是 need auth,registry 上agent-case返回 404。所以上面「六十秒上手」里那行npx agent-case selftest现在会 404,两处都照实标了。 - coffee 两通道对照不在 CI(L14)。
memory vs taixianglian需要运行中的 Spring Boot 真实后端,CI 里不跑;后端不在线时测试明确 skip。CI 的跨实现对照仍覆盖账本与 RealWorld,但不能把咖啡域算成已被 CI 覆盖。
先看 docs/README.md——文档地图:每份文档答一个问题,按你此刻的问题分流;下面是完整列表。
- docs/GUIDE.md —— 怎么用:从一个网址开始、从日志到正式判分、接入自己的应用
- docs/SPEC.md —— 规范:案例格式、21 种断言与相位、判决顺序表、各机制的判定规则
- docs/ADAPTER-GUIDE.md —— 接自己的应用:三档路径、只改四处、验收顺序
- docs/LIMITS.md —— 14 条限制的台账:状态、验收命令、为什么这条不解决
- docs/ROADMAP.md —— 接下来做什么、按什么顺序、每条怎么验收
- docs/domains/ —— 四个域的实测细节:动物、词库、定时任务、实验室
- docs/museum/ —— 假判决博物馆:12 件展品,每件都是这个仓库真踩过的坑
- docs/mutants/ —— 变异体目录与投稿标准
- knowledge/ —— 事故档案与入库宪法
- docs/media/ —— 本页面所有图、动图与介绍片,全部本地生成(顶部那三个自带 SVG 也是;只有 ci 徽章是 GitHub 实时渲染的)
- CONTRIBUTING.md —— 三条不可谈判的规则、提交前必须过的六道门、四种典型改动的步骤
- AGENTS.md —— 给要改这个仓库的 AI Agent 读的:五条硬约束、六个踩过的坑
- SECURITY.md —— 「能让适配器骗过探针」在本仓库算有效漏洞,以及哪些不算
- CODE_OF_CONDUCT.md —— 攻击主张,不攻击人
MIT。















