提出将prompt视为锁文件并对grader做变异测试的eval套件,捕获prompt漂移、oracle腐化和模型过拟合已知用例三类静默故障。
一个中型系统团队将一个 AI 编写的补丁合并到了一个无锁队列中。单元测试保持绿色。两周后,一次 prompt 编辑要求模型优先使用更简单的原子操作。新补丁编译通过了。黄金用例依然通过了,因为它从未运行过两个生产者。生产环境在负载下丢失了数据。测试 harness 没有失败。它从未经过 mutation 评分,而 prompt 也从未被锁定。
廉价的 C++ 补丁制造了第二类技术债务。黄金用例腐烂。Grader 接受更弱的 oracle。Prompt 文本在聊天窗口中漂移,没有任何 hash。其余的文章描述了一个小型、可复现的 eval harness,它将 prompt 视为一个锁定的依赖项,并对 grader 本身进行 mutation 测试。
该工作流针对看起来局部正确的 AI 生成 C++ 补丁。它不能替代代码审查。它捕捉三类静默失败:prompt 漂移、oracle 腐烂,以及只在团队已经向模型展示过的测试上通过的补丁。
一个演示测试重复模型已经见过的愉快路径。一个契约用例陈述了在模型没有被辅导通过的补丁之后必须保持为真的内容。区别就是整个方法。
该产物有四个文件。
prompt.lock.json — 系统 prompt、用户模板和 eval 语料库的 hash。
cases/ — 包含输入、禁止的 diff 和 grader 命令的黄金用例。
mutate_grader.py — 一个有意削弱 oracle 并期望 harness 失败的脚本。
run_eval.sh — 一个轻量的运行器,编译、链接 sanitizer 并记录成绩。
每个文件都足够小,可以与被测试的 C++ 库放在同一个仓库中。锁文件是产物。Mutation 通道是团队了解产物不是形式主义的方式。
Prompt 比头文件变化得更快。一行编辑可以改变模型发出的 include。锁文件记录加密 hash,而不是 prompt 文本,这样 review diff 保持简短。
{
"schema": "cpp-eval-lock/v1",
"prompt": {
"system_sha256": "replace-with-sha256-of-system.txt",
"user_template_sha256": "replace-with-sha256-of-user.md.tmpl",
"style_rules_sha256": "replace-with-sha256-of-invariants.md"
},
"corpus": {
"cases_sha256": "replace-with-sha256-of-cases-tree",
"grader_sha256": "replace-with-sha256-of-mutate_grader.py"
},
"language": "c++17",
"sanitizers": ["address", "undefined"]
}
一个 pre-commit hook 重新计算 hash。不匹配就是一次失败的 eval,即使每个测试仍然是绿色的。这就是重点。Prompt 漂移情况下的绿色测试是 harness 的静默回归,而不是库的回归。
#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
import hashlib, json, pathlib, sys
lock = json.loads(pathlib.Path("prompt.lock.json").read_text())
def sha(p):
return hashlib.sha256(pathlib.Path(p).read_bytes()).hexdigest()
checks = {
"system_sha256": sha("system.txt"),
"user_template_sha256": sha("user.md.tmpl"),
"style_rules_sha256": sha("invariants.md"),
}
bad = [k for k, v in checks.items() if lock["prompt"][k] != v]
if bad:
print("lock mismatch:", ", ".join(bad))
sys.exit(2)
print("prompt lock ok")
PY
这段代码是一个提议。团队在使用它作为门控之前应该替换占位符 hash。聊天日志中未签名的文本不是锁定。
每个用例记录三个 oracle:编译标志、禁止的 diff 子串和一个行为命令。禁止的 diff 捕捉模型在 prompt 说"简化"时喜欢的删除。行为仍然重要。一个保留了原子操作但丢失了数据的补丁必须在命令 oracle 上失败。
{
"id": "queue-mpmc-no-loss",
"source_files": ["src/queue.hpp", "src/queue.cpp"],
"forbidden_diff": [
"std::memory_order_relaxed on head or tail",
"removal of mutex or atomic around published index"
],
"compile": {
"std": "c++17",
"flags": ["-Wall", "-Werror", "-fsanitize=address,undefined"]
},
"behavior": {
"cmd": ["./queue_mpmc_test", "--producers", "2", "--consumers", "2", "--n", "10000"],
"expect_exit": 0,
"expect_stdout_contains": ["lost=0"]
}
}
一个最小的 C++ 黄金 driver 如下。它是一个示例 driver,不是经过验证的无锁队列。
// cases/queue_mpmc_test.cpp — example driver, not production queue code
#include "queue.hpp"
#include <atomic>
#include <iostream>
#include <thread>
#include <vector>
int main() {
const int producers = 2, consumers = 2, n = 10000;
Queue<int> q;
std::atomic<int> produced{0}, consumed{0};
std::vector<std::thread> ts;
for (int i = 0; i < producers; ++i) {
ts.emplace_back([&] {
for (int k = 0; k < n; ++k) {
q.push(k);
produced.fetch_add(1, std::memory_order_relaxed);
}
});
}
for (int i = 0; i < consumers; ++i) {
ts.emplace_back([&] {
int v;
while (consumed.load(std::memory_order_relaxed) < producers * n) {
if (q.try_pop(v))
consumed.fetch_add(1, std::memory_order_relaxed);
}
});
}
for (auto& t : ts) t.join();
const int lost = produced.load() - consumed.load();
std::cout << "lost=" << lost << "\n";
return lost == 0 ? 0 : 1;
}
单线程测试本可以让最初的静默失败保持绿色。竞争是 oracle 的一部分。一个从不竞争的用例是一个戴着契约徽章的演示。
编号的 grader 保持失败模式可读。
Lock grade。Prompt hash 与 prompt.lock.json 匹配。
Compile grade。-Wall -Werror 加 ASan 和 UBSan。
Diff grade。禁止的子串不出现在统一 diff 中。
Behavior grade。黄金 driver 以预期标记退出 0。
运行器可以是一个 shell 脚本。保持它的简单。
#!/usr/bin/env bash
set -euo pipefail
case_id="${1:?case id}"
patch="${2:?unified diff}"
./check_lock.sh
git apply --check "$patch"
git apply "$patch"
g++ -std=c++17 -Wall -Werror -fsanitize=address,undefined \
-o queue_mpmc_test src/queue.cpp cases/queue_mpmc_test.cpp
python3 grade_diff.py --case "$case_id" --diff "$patch"
./queue_mpmc_test --producers 2 --consumers 2 --n 10000 | tee /tmp/out.txt
grep -q 'lost=0' /tmp/out.txt
提前停止很重要。编译失败不应该被记录为行为通过。混合成绩是静默回归隐藏在仪表板中的方式。每个补丁一行 CSV 就够了。
如果期望的 stdout 被删除而 harness 仍然通过,则 oracle 是装饰。Mutation 评分对用例定义应用敌意编辑,而不是对 C++ 库。Harness 必须让每个 mutant 都失败。
# mutate_grader.py — proposal: mutants target the oracle, not the product
import copy, json, pathlib, sys
def load(path):
return json.loads(pathlib.Path(path).read_text())
def mutants(case):
out = []
c = copy.deepcopy(case)
c["behavior"]["expect_exit"] = 1
out.append(("invert-exit", c))
c = copy.deepcopy(case)
c["behavior"]["cmd"] = ["./queue_mpmc_test", "--producers", "1",
"--consumers", "1", "--n", "1"]
out.append(("drop-contention", c))
c = copy.deepcopy(case)
c["forbidden_diff"] = []
out.append(("drop-forbidden", c))
c = copy.deepcopy(case)
c["compile"]["flags"] = ["-O0"]
out.append(("drop-sanitizers", c))
return out
def harness_fails(case) -> bool:
# Labeled stub: wire this to run_eval.sh against the mutant case.
return False
def main():
case = load(sys.argv[1])
killed = 0
rows = mutants(case)
for name, mutant in rows:
failed = harness_fails(mutant)
print(f"{name}: {'killed' if failed else 'SURVIVED'}")
killed += int(failed)
print(f"mutation score: {killed}/{len(rows)}")
sys.exit(0 if killed == len(rows) else 3)
if __name__ == "__main__":
main()
一个存活的 mutant 意味着 grader 会接受一个更安静的用例。这就是原始队列补丁逃脱的方式。单线程黄金用例是一个竞争 oracle 的存活 mutant。
团队应该在 mutation 分数满之前拒绝扩展语料库。带有死 oracle 的额外绿色用例增加了信心剧场。分数衡量的是 harness,而不是库。
锁文件和 mutation 通道是本地文件。它们不需要特定的 vendor。它们要求每个生成的补丁被保存为 diff,然后离线评分。
当团队使用 MonkeyCode 时,相同的 user.md.tmpl 应该是唯一发送的用户 prompt,而 run_eval.sh 加 mutate_grader.py 应该是实际对 diff 进行评分的命令。披露:本文是作为 MonkeyCode 产品推广的一部分准备的。操作员描述的免费层模型访问和免费服务器选项足以执行该循环。它们不是生产 SLA、配额、硬件声明或基准。
冻结 prompt 文件并刷新 prompt.lock.json。
使用锁定的模板请求一个失败的库测试的补丁。
保存统一 diff。
对刚刚对补丁进行评分的用例运行 mutate_grader.py。
在一行 CSV 中记录 lock hash、patch hash、成绩和 mutation 分数。
date,lock,patch,lock_grade,compile,diff,behavior,mutants_killed,mutants_total
2026-09-07,a1b2,c3d4,pass,pass,fail,pass,3,4
示例行是说明性的。diff 失败而 behavior 通过是有趣的类别:模型恢复了功能但删除了测试未编码的不变量。CSV 就够了。仪表板是可选的。
"有条件地"是故意的。Harness 不能证明无锁性、线性化或 API 品味。它证明 oracle 仍然有效。
该方法对 GUI 代码、生成的 protobufs 和正确性是视觉化的补丁很弱。当黄金 driver 本身是错误的时候也很弱。Mutation 评分检测安静的 oracle。它不能检测一个确信错误的期望值。
基于 sanitizer 的 C++ 测试需要一个实际链接 libasan 的工具链。Windows MSVC 设置不会原样粘贴示例标志。示例队列 driver 如果 try_pop 从不返回且停止条件草率,可能会挂起。超时应该放在运行器中。为简洁起见,上面省略了。
已经拥有成熟属性测试套件的团队可能会觉得 JSON 用例是多余的。无法锁定 prompt 的团队(因为每个请求都是一个新的聊天且没有文件)不应该假装他们有这个 harness。不要将此工作流作为无锁结构上内存模型审查的替代品。不要将 mutation 分数作为营销指标发布。四个粗糙 mutant 上的 4/4 击杀率不是安全案例。
模型从未见过的用例是一个单独的控制组。本文是关于未能失败的 grader。锁定 prompt。对 oracle 进行 mutation。保持 CSV 简单。
已经存储了编码助手 diff 的团队可以将 prompt.lock.json 和 mutate_grader.py 放在那些 diff 旁边,并在免费层模型访问或免费服务器选项上重用相同的运行器。这就是整个循环。
MonkeyCode 提供可以运行此工作流的免费模型。