← PLAYBOOK
Architecture Quarterly
Vol. 04 · Issue 13 May 2026 Field Note № 08 EN
Field Note № 08 · Verifiable Components

Agent 自证清白
DOM 第一次
长出机器可读表面

data-verify-doc="agent-native-verification" data-verify-rounds="3" data-verify-status="ALL_PASS"

Contract testing 不是新概念。Fixtures、invariants、合约—— 这些 pattern 在教科书里活了二十年, 但只有少数后端团队真的把它跑成日常。 AI agent 第一次让它在前端经济上成立—— 代价从「人写、人维护、人解读」翻成趋近于零的边际成本。

Speaker
Ara · Anthropic Applied AI
Recorded
2026-05-23 · 31:43
Verified
fact-check-loop · 3 rounds
— The Argument in Brief

这不是一项技术发明,是经济学翻转带来的实践回归。 Anthropic 真正贡献的,是为「agent 与前端产物之间的协议」补上了一份具体的参考实现。

01

DOM 直接发射状态

每个组件在最外层元素上挂 data-verify-*,把内部状态当作合约暴露在 HTML 表面。Agent 不再需要去推导渲染结果,直接读属性即可

02

声明绑定组件

Fixtures + invariants + probes 写在 .verify.ts 里,与组件同框。验证矩阵就是组件的活文档,agent 改代码时一并维护。

03

三个消费者一份数据

人工 Dashboard、agent __verify.runAll()、CI vitest 矩阵——共用同一个 runFixture() 代码路径,没有版本漂移空间。

§I
Diagnosis · The Reality of Frontend Testing

先看一眼账本。前端测试在 2026 年的真实样子。

谈论"AI 让验证变可行"之前,必须先承认一件事:在大多数前端团队里,"严格验证"从来就没真的在场。把 contract testing 当作"原本就在做的事"是最常见的误读。

把 2024–2025 年几份覆盖面较大的调查并到一起,画面相当一致——单元测试稀薄、E2E 几乎缺席、覆盖率长期低于人们口头承认的水平,而做得起的团队也把维护成本列为最大痛点。

34%Reflect / YC
在 46 家 YC 受访技术创始人中,34% 完全不写单元测试。"E2E 测试:大多数受访公司根本不做。"
Reflect Survey · YC Companies
67%mabl 2024
500+ 受访者中,67% 团队覆盖率 ≤ 60%1/5 团队覆盖率 < 20%。测试维护被列为最耗时任务(21%)。
2024 State of Testing in DevOps
42%PractiTest 2024
42% 测试人员对写自动化脚本不自信。自动化在增长,但再培训缺口在加宽。
PractiTest · State of Testing 2024
6028State of Frontend
6028 名前端工程师受访(半数欧洲)。结论:"理想的测试金字塔对许多团队仍只是愿景"。组件级与集成测试是最被欠投资的环节
State of Frontend 2024
§II
The Old Solution · And Why It Stalled

Contract testing 在理论上正确。在前端,没人能维护得动。

Contract testing 这个概念早就存在。后端微服务之间的契约校验、Pact 等工具都活跃了多年。问题不在"想不到",而在于价值和成本的兑现窗口错位——价值要到部署之后才被感知(避免线上 bug),成本要在开发当下就被预付(写 fixture、维护 invariant、排查 flaky test)。

这套"先付账后兑现"的经济学,使得 contract testing 在 UI 层尤其难落地。看几位前线工程师怎么说:

"

The biggest issue seems to be setup and maintenance—it takes effort, and if teams on both sides of the contract aren't fully committed, it quickly becomes useless.

alonat.tech · 2025-01
"

Some folks found it so cumbersome that it never even caught meaningful bugs, making it feel like a wasted investment.

alonat.tech · 2025-01
"

At the last client I worked at, it took me almost a year of my "free time" at work to get one suite of flakey full stack Cucumber tests running reliably!

PactFlow blog · 2020
"

API contracts are formal, automated, and reassuring. When they pass, pipelines turn green and deployments feel justified. The problem is that contracts answer a very specific question, and it's not the one users care about.

neteye-blog.com · 2025-12

把这几条放一起看,结论很朴素:在人力时代,验证基础设施不是"可不可行",而是"养不养得起"。能养起的团队不多,就算养了,也有相当一部分会被维护成本反噬。

The more capable the models get, the more you should try to resist constraining them.

Ara · Code with Claude London 2026 · Workshop "How We Claude Code"
§III
Idea 1 · The Surface, Not the Source

把组件状态挂在 HTML 表面。Agent 读属性,而非反推渲染。

传统上,agent 想知道一个组件"现在到底是什么状态",必须读它生成出来的 HTML/CSS/文字 reverse-engineer 状态。这是个二阶推理问题:渲染层把 props 翻成 DOM,agent 又要把 DOM 翻回成"语义状态"。中间两次有损翻译。

Anthropic 的做法是:让组件把自己的语义状态直接发射到 DOM 属性上。`data-verify-unit="TodoApp"`、`data-verify-total="3"`、`data-verify-done="1"` ——这些不是渲染副产物,是显式合约

Specimen · TodoApp.tsx data-verify-* attributes emitted on render
<section
data-verify-unit="TodoApp"
data-verify-total="3"
data-verify-done="1"
data-verify-active="2"
data-verify-filter="all">
  
</section>
这个 section 不仅渲染了 todo 列表,还在外层属性上把"我现在内部认为的状态"原样贴出来。Agent 只需 query DOM 即可。

但这跟 data-testid 不一样吗?——形式上像,意图完全不同。data-testid 仅仅给元素一个标识,回答"我是谁";data-verify-* 暴露完整状态契约,回答"我是谁 + 我现在的状态是什么"。一个是定位器,一个是合约。

维度
data-testid (传统)
data-verify-* (Agent-Native)
暴露什么
元素标识 — "我是谁"
完整状态契约 — "我是谁 + 状态"
验证逻辑
独立测试文件
与组件绑定(.verify.ts
正确答案
人在测试里硬编码
声明式 fixtures + Zod schema
消费者
人写的测试脚本
Agent API __verify.runAll()
维护者
人 — 经常过时 / 荒废
Agent — 边际成本趋近于零
实时性
测试时才跑
渲染时即把状态发射到 DOM
§IV
Idea 2 · The 6-line Mechanism

支撑这套合约的核心代码:六行

verifyAttrs() 拆开看,它干的事情就是遍历对象、加上 data-verify- 前缀、把所有值字符串化。仅此而已。它本身没什么聪明的,聪明的是这个约定本身。

01 ·SOURCE
export function verifyAttrs( attrs: Record<string, string | number | boolean | null | undefined> ): Record<string, string> { const out: Record<string, string> = {}; for (const [key, value] of Object.entries(attrs)) { if (value === null || value === undefined) continue; out[`${VERIFY_PREFIX}${key}`] = String(value); } return out; }

就这么短。价值不在代码量,在于这是整个 codebase 都遵守的约定——一个 6 行约定取代了上百行 testing utility。

02 ·USAGE IN A COMPONENT
// inside TodoApp.tsx return ( <section {...verifyAttrs({ unit: "TodoApp", total: stats.total, done: stats.done, active: stats.active, filter: state.filter, })} > {/* render */} </section> );

每个组件加 1 行—— spread 到外层元素上。剩下的事 React 自动接管:每次 render,最新状态自动发射到 data-verify-*

§V
Idea 3 · One Source, Three Surfaces

同一份验证结果,三个消费者读,一个代码路径产出。

验证结果的"唯一真源"是一个叫 runFixture() 的函数。它接受一个 unit + fixture,返回 PASS | FAIL | BLOCKED | SKIP 四种判定。三个使用者各自调用同一个函数,分别得到自己需要的形态:

Dashboard 人 ·
TodoStats / mixedPASS
TodoStats / none-donePASS
TodoStats / all-donePASS
🔍inconsistent-countsFAIL
TodoInput / typedPASS
TodoList / populatedPASS
浏览器里点 "Run all" — 给真人看的 verdict grid。
Agent API 机器 ·
{
"unit": "TodoStats",
"fixture": "mixed",
"verdict": "PASS",
"checks": [
{"id":"schema","ok":true},
{"id":"invariants","ok":true}
]
}
await __verify.runAll() — 给 agent 自动决策的结构化 JSON。
CI Headless 流水线 ·
TodoStats > mixed
TodoStats > none-done
TodoStats > all-done
✓ 🔍 inconsistent-counts
→ FAIL (by design)
TodoInput > typed
TodoList > populated

21 fixtures · all expected verdicts
bun run verify — vitest 矩阵跑全量,CI 红绿灯。

三个 surface 共享一份代码,意味着它们之间没有版本漂移空间——dashboard 看到的不会跟 CI 跑出来的不一致。这是个反"集成地狱"的设计:你不可能让其中一个先过,另一个后挂。

§VI
Run · The Demo Repo, Live

Demo repo 的真实运行结果。注意那个故意失败的探针。

Workshop 配套的 cwc-workshops/how-we-claude-code 包含 5 个 verifiable units 与 21 个 fixtures。clone 下来跑 bun run verify,输出大致如下:

~/cwc-workshops/how-we-claude-code/phase-3-verify · bun run verify
matrix · 27 expected verdicts
TodoItem > active ················································ PASS
TodoItem > done ·················································· PASS
TodoItem > 🔍 empty-text ········································· PASS
TodoItem > 🔍 long-text ·········································· PASS
TodoInput > empty ················································· PASS
TodoInput > typed ················································· PASS
TodoInput > 🔍 whitespace-only ····································· PASS
TodoList > empty ·················································· PASS
TodoList > populated ·············································· PASS
TodoList > all-done ··············································· PASS
TodoList > 🔍 long-text ·········································· PASS
TodoStats > mixed ················································· PASS
TodoStats > none-done ············································· PASS
TodoStats > all-done ·············································· PASS
TodoStats > 🔍 inconsistent-counts ······························· FAIL (by design)
todos.feature > fresh ············································· PASS
todos.feature > pre-seeded ········································ PASS
todos.feature > add-then-verify ··································· PASS
todos.feature > add-then-toggle ··································· PASS
todos.feature > 🔍 filter-active ································ PASS
todos.feature > 🔍 whitespace-submit ···························· PASS
Test Files 1 passed (1)
Tests 27 passed (27)
21 fixtures, 6 of them probes (🔍). Vitest 矩阵共 27 项,含结构校验。
关于 inconsistent-counts 的 FAIL: 这是一个 probe——一个故意违反 invariant 的 fixture(total=10, done=3, active=4,但 3+4 ≠ 10)。 测试矩阵在 EXPECTED_FAIL 集合中显式断言它必须 FAIL。 probe 存在的意义是:验证验证系统本身没在睁眼说瞎话。所有都 PASS 不该让人放心,应该让人怀疑。
§VII
Why Now · The Cost Inversion

这套模式一直在那。是 agent 把账重算了一遍。

把每个环节拆开看人力成本和 agent 成本,会得到一张相当鲜明的对照表。读这张表的方式不是"AI 替代了人",而是原本因为太贵而被舍弃的那一段,现在重新进入预算

人力时代验证作为成本中心
Agent 时代验证作为基础设施
写 fixtures
为每个组件覆盖边界场景。重复劳动,容易遗漏。
高 · 单调劳动
从 spec 或代码自动推导。Agent 也比人更擅长穷举。
趋近于零
维护 invariants
需求变了,invariants 没跟上,慢慢变成谎言。
高 · 长期漂移
代码一改,agent 同步更新 .verify.ts。验证矩阵成为活文档。
同步成本
运行验证
慢、flaky、需要专人解读。
中 · CI 时间 + 排查
秒级、确定性环境、JSON 结构化输出。
几乎免费
修复失败
人需要切回上下文、读报告、改代码、再跑一遍。
高 · 上下文切换
Agent 读 JSON、定位、修改、再次 verify、闭环。
回路时间

关键转变是这一条:验证从"一次性投资(写完之后很少更新)"变成"持续维护的活文档"。前者在人力时代是合理的取舍——因为持续维护不经济;后者在 agent 时代是自然的——因为持续维护已经不需要持续付钱

§VIII
The Whole Pattern · Six Ideas, in Brief

六个组件级要素,合起来才是这套架构

到这里我们已经讲了三个:DOM 表面、verifyAttrs、三个消费者。把六个一起列出来,方便实操对照——它们彼此互锁,缺一个就漏掉一个能力。

1
DOM 作为机器可读表面
每个组件外层挂 data-verify-*,把内部状态作为合约暴露在 HTML 上。
2
组件声明 Fixtures + Invariants
同名 .verify.ts 文件:Zod schema、命名 fixtures、不变量谓词、probes。
3
隔离渲染路由
每个 unit × fixture 有独立 URL:/verify/:unit/:fixture。无 app shell,可单独驱动。
4
插拔 Verifiers
schema / invariants / dom-contract / a11y 四类,新增不动组件。
5
window.__verify Agent Handle
manifest()、current()、runAll() ——给 agent 的结构化入口,与 dashboard 同源。
6
统一的结论体系
PASS / FAIL / BLOCKED / SKIP;BLOCKED ≠ FAIL,明确区分"测不到"与"测到错误"。

The agents are going to be doing more and more of this natively, and how can you set the artifacts that you produce up to natively be testable and verifiable in the way that you need.

Ara · Code with Claude London 2026
§IX
Evidence · Replay-as-PR-Artifact

验证完,录一段视频放进 PR。

Repo 还附了一个 scripts/record.ts,启动 headed Chromium、导航到 /verify/replay、等 window.__verify_replay.done === true,然后把整段验证过程录成 .webm 视频存到 recordings/

这个脚本本身不奇特——奇特的是它构成的工作流:开发者改完代码,agent 自动跑 verify、自动录视频,PR 里直接附上录屏。Reviewer 不用本地跑、不用打开浏览器,看一眼录屏就能确认行为是否正确。

The Claude Code team records basically all the code changes that they do like this, all the front end changes at least, especially at the pace of shipping that we have at the moment.

Ara · 描述 Anthropic 内部工作流
§X
Boundaries · Where the Pattern Doesn't Apply

这套东西不是银弹

报告本身不下任何"普适"判断。直接列出 demo 自带的、肉眼可见的局限:

i
仅适用于有 DOM 输出的前端组件。后端逻辑、数据管道、服务编排——它们没有渲染表面可挂 data-verify-*,需要别的合约协议。
ii
需要开发者主动加 verifyAttrs() 调用(每个组件约一行)。它不是零侵入——是侵入但廉价。
iii
验证的是状态一致性,不是视觉正确性。CSS 错位、颜色错误、字距奇怪等视觉 bug 仍需要人工或视觉回归测试补充。
iv
probe 的设计质量仍依赖人的判断。repo 强制每个 unit 至少一个 probe,但 probe 是不是真的踩在边界上,没法自动保证。
v
标准化程度低。目前是 Anthropic 内部实践 + 一个 demo repo,没有第三方采纳数据,没有 RFC,没有社区共识。
§XI
Closing · What Anthropic Actually Contributed

不是技术发明,是协议层的具体落地。

Contract testing 本来就在那。data-attribute 也本来就在那。React 把状态翻成 DOM 这件事更是讲了十年。Anthropic 没有发明任何一项新技术——他们做的是把这几件早已存在的东西串成一个 agent 友好的协议,并提供了一份能跑起来的参考实现。

这件事的价值不在"todo app demo 跑起来了",而在于:当 agent 写代码成为常态,"agent 能不能验证它写的东西"就不再是可选项,而是基础设施。这份 demo 给"agent 与前端产物之间的契约"画了第一根线——不一定就是最后那根线,但它把空间打开了。

剩下的事,是行业能不能也跟上来——能不能在框架层、设计系统层把 data-verify-*__verify 变成默认能力,而不是每个团队各写一套。

读完应当带走
件事
  • 验证不再是"人写测试"。它是"agent 自证"。组件渲染时把状态发射到 DOM,agent 读属性即可。
  • 同一份验证逻辑撑起三个 surface。Dashboard、Agent API、CI vitest——共享 runFixture(),没有漂移空间。
  • probe 是验证体系的免疫系统故意失败的 fixture 证明系统没在睁眼说瞎话。所有都 PASS 该让人怀疑。
不应当带走
件事
  • "AI 解决了所有测试问题"。它没有。视觉正确性、性能回归、跨浏览器一致性——这些都不在 data-verify-* 覆盖范围内。
  • "这是行业标准"。不是。是 Anthropic 一家的内部实践 + 一个 reference repo,且未经社区采纳验证。
  • "零侵入"。每个组件需要主动 spread verifyAttrs(),每个 unit 需要写 .verify.ts。代价低但非零。