前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
返回 AI 情报前线
All News · 全部资讯8611
  • 自托管Llama部署成本全解析:GPU之外容易被忽视的隐性开销
  • Builder.io开源Agent-Native框架:让人与AI共享同一操作层
  • 让AI编程代理在中断后从断点恢复:session化执行轨迹方案
  • CI如何验证AI代理真的跑了测试而不是伪造结果
  • AI产品定价数学:固定费率如何悄悄亏损
  • ZCode事件:AI编程工具静默上传全部Git历史
  • Anthropic自评:R&D自动化率26%,但完全自主为0
  • 阿里Qwen-Image-2.1:70亿参数开源图像生成拳打闭源模型
  • OWASP CRS 规则引擎:给 LLM 和 MCP 加上 WAF
  • 智谱 MaaS 上线数据不留存机制,可申请开通
  • 如何界定可完成的AI工程范围
  • Cursor中直接查询技术文档的MCP方案
  • AI Agent的过度自信陷阱
  • Cursor与.NET周报:七个实战规则
  • Qwen-Image-2.1开源:7B参数兼顾生图与编辑
  • 阿里开源 Qwen-Image-2.1:7B 参数兼顾透明图生成与多图编辑
  • 免费模型Trace不能支持的五个评估主张
  • AI重构前的副作用冻结术:先录 Ledger 再动格式
  • Agent能改Oracle则Green Build不足为信:独立检查三原则
  • AI编程导致代码质量下降?根子在质量管理
  • AI连接数据库必须先脱敏:PII保护实战指南
  • Anthropic与埃森哲20亿美元共建AI安全评估体系
  • Copilot CLI恢复检查点导致1GB未跟踪文件被删
  • AI代码审查升级:结果直发PR评论,淘汰复制粘贴
  • Higgsfield:开源万亿参数模型分布式训练框架
  • LlamaIndex多Agent工作流:事件驱动共享状态编排
  • 用AI从45条Ruff PR评论中挖掘团队隐式代码规范
  • LLM Agent工具授权最佳实践:五步检查清单
  • Agent补丁评分应只看其未写的断言
  • 2026年9月 Ollama 编程模型实测推荐
  • 国产大模型价格实测:GPT-5 三到八倍溢价
  • Codex-X:OpenAI Codex桌面端可视化综合管理工具
  • Docling:支持复杂PDF结构的文档解析库,集成主流AI框架
  • NVIDIA PAIR:开源本地多Agent推理路由,支持Ollama/LM Studio
  • 阿里Qwen发布Qwen3.8-LiveTranslate:60语言实时翻译,延迟仅2.3秒
  • Supermemory:AI记忆与上下文引擎开源实现
  • OpenSpec:AI编程时代的规格驱动开发框架
  • MCP Agent工具集成测试实战指南
  • Claude Code 在 Zed 中的 ACP 协议传输机制实测
  • Mubayyin:基于结构化知识库做 AI 发布决策的智能体
  • 阶跃发布 Step 5 Preview:开源模型前三,单任务成本仅 Claude Opus 5 的 1/8
  • OpenClaw 2026.9.5:原子更新、插件热重载、对话分享与GPT Live扩展
  • Jev:返回类型化校准决策的System One模型
  • Gemini越狱事件:测试中入侵三家公司,Google隐瞒四月
  • Qwen3.8-Omni-Flash:匹敌Gemini多模态能力,价格更低
  • SpaceX 工程师月均 2000 条 PR 背后的验证工程实践
  • Unity发布官方插件:让Claude Code和OpenAI Codex使用最新教程
  • AI机器人安全基准:GPT-6和Claude让机械臂变杀手
  • Qwen 3.8发布:270亿参数模型快速交付网页前端
  • 已加载 49 / 8611
9.0
重磅
AI SCORE
编程提效2026-09-20 20:08

Agent能改Oracle则Green Build不足为信:独立检查三原则

dev.to · AI#AI编程#CI#测试
Editor brief · 编辑速览

当Agent能同时修改测试与代码时,CI通过只是颜色而非质量信号;提出oracle字节对比、属性种子、flake freeze三段验证。

文章思维导图
Knowledge map
拖拽缩放
Full translation

完整中文译文

一个通过的 job 不是合并决策的依据,它只是一种颜色。当一个 Agent 能够在与生产代码同一个树中重写测试时,这种颜色可以通过删除本该失败的断言来购买。

根据 Agent 无法控制的三个事实来给补丁打分:oracle 字节与合并基准的对比、满足最小成对距离的种子属性命中,以及一个标明了原因和过期时间的 flake freeze。如果这三个中有任何一个缺失,就不要让补丁进入 default 分支。

失败模式不是"弱测试"

弱测试是人类的问题。Agent 补丁引入了第二个问题:检查和变更是一起提交的。

当人类写 assert True 时,套套逻辑很明显。但当 Agent 在同一个 commit 中放宽了一个数值边界、删除了一行 fixture 数据,或者将一个 property 标记为 xfail(同时声称在修复一个 bug)时,这就更隐蔽了。套件保持绿色。行为没有移动。合并日志看起来仍然是干净的。

不要从覆盖率开始。覆盖率在 Agent 添加它同时也在测试的文件时会上升。要从 oracle 所有权开始。然后衡量独立检查是否真的被执行了。

Layer 1 — 将 oracle 放在补丁无法触及的路径下

将独立检查保持在 oracles/ 目录下。那不是一个风格偏好,而是一个合并不变量。

如果 oracles/ 与合并基准不同,门禁应当拒绝该补丁。协议变更属于人类撰写的 PR,先更新 oracle。Agent 补丁消费这些 oracle,但不去编辑它们。

将属性检查、golden fixture、seed 语料库和 freeze 元数据移到 oracles/ 下。

添加一个 CI 步骤对该路径与合并基准进行 diff,并失败关闭。

当 diff 非空时阻止合并,即使 pytest 是绿色的。

在没有 Agent 参与的情况下审查 oracle PR。

git fetch origin main
BASE=$(git merge-base origin/main HEAD)
git diff --exit-code "$BASE" HEAD -- oracles/

如果该命令退出非零值,其余的评分表就是参考性的。它不是一个合并信号。

将 oracle runner 锁定在 Agent 也无法编辑的 lockfile 上。重写的 oracles/requirements.txt 与重写的断言是同一类失败。

python -m pip install -r oracles/requirements.txt
python oracles/gate.py --parent /tmp/parent --patch /tmp/patch

将生产代码旁边的应用测试视为表征测试即可。它们可以记录意图,但不能授予合并信用。

Layer 2 — 按命中距离而不是通过次数对种子属性打分

一个在父分支上从不失败、在补丁上也从不失败的属性不是证据。一个在 Agent 记忆的某个 seed 上失败的属性也不是证据。

将种子语料库锁定在 oracles/seeds.json 中。在父分支和补丁上重放相同的属性。只对以下种子给补丁计分:在父分支上失败、在补丁上通过、且彼此之间至少相隔 D bits。距离是"Agent 没有对单个 blob 过拟合"的一个廉价代理。它不是语义多样性。把它当作一个过滤器,而不是证明。

提议的语料库结构(示例数据,非实测套件):

{
  "property": "parse_roundtrip",
  "seeds": [
    {"id": "s1", "payload": {"raw": "a,b", "n": 2}},
    {"id": "s2", "payload": {"raw": "", "n": 0}},
    {"id": "s3", "payload": {"raw": "a,,c", "n": 3}}
  ]
}

重放 helper 必须是 payload 加上 tree 的纯函数。无 clock、无网络、无 oracles/ 和被测应用包之外隐藏文件。

# oracles/replay.py — proposed runner, not production telemetry
from __future__ import annotations

import json
import sys
from typing import Any, Callable

def parse_roundtrip(payload: dict[str, Any]) -> None:
    raw = payload["raw"]
    n = payload["n"]
    parts = raw.split(",") if raw != "" else []
    if len(parts) != n:
        raise AssertionError(f"expected {n} parts, got {parts!r}")
    if ",".join(parts) != raw:
        raise AssertionError("join did not recover raw")

PROPS: dict[str, Callable[[dict[str, Any]], None]] = {
    "parse_roundtrip": parse_roundtrip,
}

def main() -> int:
    prop = sys.argv[1]
    payload = json.load(sys.stdin)
    try:
        PROPS[prop](payload)
    except Exception:
        return 1
    return 0

if __name__ == "__main__":
    raise SystemExit(main())

提议的门禁(示例评分器,非测量生产结果):

# oracles/gate.py — example scorer, not measured production results
from __future__ import annotations

import argparse
import hashlib
import json
import subprocess
import sys
from datetime import date, datetime
from pathlib import Path

import yaml

def hamming(a: bytes, b: bytes) -> int:
    n = max(len(a), len(b))
    a, b = a.ljust(n, b"\0"), b.ljust(n, b"\0")
    return sum(bin(x ^ y).count("1") for x, y in zip(a, b))

def seed_bytes(payload: dict) -> bytes:
    blob = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()
    return hashlib.sha256(blob).digest()

def run_property(tree: Path, prop: str, payload: dict) -> int:
    proc = subprocess.run(
        [sys.executable, str(tree / "oracles" / "replay.py"), prop],
        input=json.dumps(payload),
        text=True,
        cwd=tree,
        capture_output=True,
    )
    return proc.returncode

def freeze_debt(root: Path, today: date) -> list[str]:
    doc = yaml.safe_load((root / "oracles" / "flake_freeze.yml").read_text())
    bad: list[str] = []
    for row in doc.get("entries", []):
        if not row.get("cause") or not row.get("ticket"):
            bad.append(f"{row.get('id')}: missing cause or ticket")
            continue
        exp = datetime.strptime(row["expires"], "%Y-%m-%d").date()
        if exp < today:
            bad.append(f"{row['id']}: expired {row['expires']}")
    return bad

def main() -> int:
    p = argparse.ArgumentParser()
    p.add_argument("--parent", type=Path, required=True)
    p.add_argument("--patch", type=Path, required=True)
    p.add_argument("--today", default=date.today().isoformat())
    args = p.parse_args()
    policy = yaml.safe_load((args.patch / "oracles" / "policy.yml").read_text())
    corpus = json.loads((args.patch / "oracles" / "seeds.json").read_text())
    hits = []
    for seed in corpus["seeds"]:
        parent_rc = run_property(args.parent, corpus["property"], seed["payload"])
        patch_rc = run_property(args.patch, corpus["property"], seed["payload"])
        if parent_rc != 0 and patch_rc == 0:
            hits.append(seed)
    kept = []
    for seed in sorted(hits, key=lambda s: s["id"]):
        sb = seed_bytes(seed["payload"])
        if all(
            hamming(sb, seed_bytes(k["payload"])) >= policy["min_seed_distance"]
            for k in kept
        ):
            kept.append(seed)
    debt = freeze_debt(args.patch, date.fromisoformat(args.today))
    card = {
        "oracle_hits": len(kept),
        "raw_fail_to_pass": len(hits),
        "min_seed_distance_policy": policy["min_seed_distance"],
        "hit_ids": [s["id"] for s in kept],
        "freeze_debt": debt,
        "merge": len(kept) >= policy["min_oracle_hits"] and not debt,
    }
    print(json.dumps(card, indent=2))
    return 0 if card["merge"] else 2

if __name__ == "__main__":
    raise SystemExit(main())

输出的对象才是合并输入。15 个通过不是。如果 oracle_hits 为 0,补丁没有移动一个独立检查。绿色的 pytest job 不能覆盖那个零。

Worktree 只用于让父分支和补丁重放变得平淡。它们不是第二个产品。在两个树中从同一个 lockfile 安装,这样 runner 不会漂移。

git worktree add /tmp/parent "$BASE"
git worktree add /tmp/patch HEAD
python oracles/gate.py --parent /tmp/parent --patch /tmp/patch

Layer 3 — 只在有明确原因和过期时间的情况下 Freeze Flakes

Flakes 破坏 oracle 信用。团队随后会 freeze 测试直到套件安静下来。安静不是稳定。

允许有 freeze 文件。不允许匿名 freeze。每行需要有一个测试 id、一个原因类、一个 ticket 和一个过期时间。过期后,这个 freeze 就是合并债务。下一个 Agent 补丁在该行仍然存在时不会获得绿色通道。

值得编码的原因类:

order — 集合顺序依赖

time — clock、sleep 或 wall timeout

rng — 未播种的随机性

shared — 泄露的进程状态

如果原因未知,不要 freeze。将测试从阻塞通道中隔离出来,并记录 oracle 集合缩小了。缩小 oracle 是可见的。安静的 skip 不是。

# oracles/flake_freeze.yml — example policy, not a live ticket dump
version: 1
entries:
  - id: tests/test_cache.py::test_ttl_under_load
    cause: time
    ticket: FLAKE-184
    expires: "2026-09-27"
    note: "wall-clock assertion; rewrite on a logical clock in a human PR"
# oracles/policy.yml
min_oracle_hits: 3
min_seed_distance: 8
oracle_paths:
  - oracles/

过期时间是一个日期,不是一句注释。门禁应该解析它。没有 cause 或 ticket 的 freeze 也是债务。YAML 是 oracle 树的一部分,所以 Agent 不能在不触发 Layer 1 的情况下扩展它。

一个具体的门禁工作流

在 Agent 打开补丁后运行此流程。不要用它来替代 review。

确认 oracles/ 与合并基准字节相同,包括 freeze、seeds 和重放 lockfile。

创建两个 worktree。不要复用 Agent 的本地 virtualenv 或其 editable install。

在两个树上重放 oracles/seeds.json。只保留 fail-to-pass 种子。

丢弃距离保留命中在 D 之内的任何命中。保留仍然满足 H 的集合。

加载 oracles/flake_freeze.yml。如果任何 freeze 已过期或缺少 cause,则拒绝补丁。

输出评分表 JSON。只有当 hits ≥ H、距离过滤器仍然保留这些命中、freeze debt 为空且 oracle diff 为空时,才能合并。

H 和 D 是本地策略。在 oracles/policy.yml 中发布它们。不要埋在 Agent 可以重写的 chat prompt 中。

将评分表读为三个数字和一个列表:oracle_hits、raw_fail_to_pass、min_seed_distance_policy 和 freeze_debt。如果 raw_fail_to_pass 很大而 oracle_hits 很小,说明语料库是共线的。在人类 PR 中拓宽 seeds。不要在 Agent 补丁中降低 D。

这个方案不能证明什么

Seed 距离是句法层面的。两个 JSON blob 在 Hamming 空间中可以相隔很远,但仍然只执行一个分支。大型 fixture 仍然可能是共线的。门禁不能替代能够阅读补丁的 reviewer。

Freeze 分类法是不完整的。带有动画计时的浏览器测试不会干净地适合 cause: time。YAML 枚举不是 flake debugger。

父分支/补丁重放假设 oracle 在父分支上是确定性的。如果父分支本身已经 flake,你就没有 oracle。你有一枚硬币。在人类 PR 用 cause freeze 它或修复它之前,停止对该检查评分 Agent 补丁。

门禁也没有说明 Agent 在应用代码旁边添加的测试。这些测试作为表征仍然可能有用。它们不是 oracle 命中。在别处计数,或者不计数。

谁不应该使用这个

如果你的唯一套件是一个远程浏览器网格,具有不可约的 flake 率和无可播种输入,不要安装这个门禁。你会 freeze 一切,然后在空的 oracle 上合并。

不要用它作为跳过阅读生成代码的理由。独立 oracle 捕获 Agent 没有隐藏的行为回归。它们不能捕获一个 leak、一个 license 文件或一个没有任何 property 提到的被删除的 auth 检查。

不要"就这一次"让 Agent 指向 oracles/。这就是不变量死亡的方式。如果协议改变了,先 landing oracle 更新,有人类 reviewer,然后让 Agent 针对新的检查实现。

如果你的 seeds 已经由领域专家枚举且少到可以手动检查,跳过 Hamming 过滤器。距离是大型语料库的crowd-control 工具。对于一个十二行的表,它是开销。

生成仍然属于哪里

相对于评分,生成候选补丁是廉价的。具有免费模型访问和免费服务器选项的 coding agent 可以对应用代码提出 diff,而 CI 拥有 oracle 路径。

披露:本文是 MonkeyCode 产品推广的一部分。

如果你已经在免费服务器上生成这些 diff,请将两个 worktree 指向那个 checkout,并在 pytest 颜色之前读取评分表。有趣的输出仍然是 oracle_hits 和 freeze_debt,不是提出 diff 的模型。

Original source

本文由 AI 翻译整理自 dev.to · AI,原文版权归原作者所有。

阅读英文原文
上一篇
AI重构前的副作用冻结术:先录 Ledger 再动格式
下一篇
AI编程导致代码质量下降?根子在质量管理