上一篇《Agentic Assurance Engineering:让 AI Coding 进入真实工程》讨论的是:一个 Agent 的行动、判断和交付,凭什么可以进入真实 SDLC。这一篇反过来问:绿灯一片时,“完成”是怎样被假出来的。这里说的 Agent 失败,不只指模型输出,也包括交付链把局部信号当成外部事实。下面只抽取机制,不据此推断普遍率;事故名称、日期和敏感标识已隐去。
本文系 AI 共同创作
0x01 配置是新的,处理请求的却还是旧进程
我追过一次看起来已经完成的部署修复。配置已经更新,服务目录也切到了新的 checkout,服务管理器还显示 active。但继续沿请求路径检查时,真正存活并处理请求的进程仍然来自旧 checkout,还持有旧状态文件。
三个信号都没撒谎,它们只是各自指着不同的东西:配置指着新版本,进程跑着旧版本,而 active 只说明“有个进程活着”,没说是哪一个。简单地说,同一个服务分裂成了两个身份——你改的那个,和正在干活的那个。
这只是我经手的一次修复,拿它说“这类问题有多普遍”是不够的。但要确认它有没有发生,动作很明确:从活着的进程号(PID)反查工作目录和 revision,再确认请求真的打到了这个进程上。
我把这种完成叫作信念式完成(done-by-belief):系统检查自己写出的状态,却没有继续读取这个状态声称已经改变的现实。
DoneClaim = subject + revision + environment + predicate |
这不是数学等式,而是一项完成主张的最小记录。subject 是被核对的服务、进程或构建产物;revision 和 environment 固定它的版本与运行边界;predicate 写清要满足的性质;evidence_locator 必须能带人回到真实文件、可重跑的复现或可查询的执行记录;independent_observer 说明谁用不同的证据来源或职责来核验;limits 写明结论不能外推到哪里。授权、owner、scope 和 risk acceptance 仍是治理条件,不会被这行公式替代。单独一个标签、测试源码或命令返回 0,都不足以宣布完成。
这里借用
dereference:标签只是指向现实的引用,验收时要顺着它读回对象本身。比如从存活 PID 读回工作目录和 revision,再确认请求命中了它;不要对着active或v2这个标签下结论。
0x02 四个解引用断点
把四份复盘放在一起后,我看到的不是一条由浅入深的失败阶梯,而是四个可以独立断开的验证关系:
| 断在哪 | 你看到的绿灯 | 还得去核对什么 |
|---|---|---|
对象身份 subject identity:改的是不是那个东西 | 配置、目录、标签、声明的版本号都对 | 活着的进程、真正被加载的代码、请求实际走的路 |
验收语义 acceptance predicate:验的是不是那件事 | 组件能渲染、断言写了、接口返回 200 | 用户从头点到尾能不能走通,前后端对同一规则的理解是否一致 |
运行包络 workload / lifecycle:测的是不是那个世界 | before/after 数字好看、超时信号出现了 | 同一份代码、同一种负载、活着的运行时,以及“它真的停了吗” |
结论来源 claim provenance:证据是不是那么回事 | 报告完整、数字齐全、案例看着挺多 | 一手来源、同一个分母、存下来的快照,以及“什么能推翻它” |
这四类是诊断维度,不替代上一篇的 Source/CI/Deploy/Runtime 证据层;同一次交付可能同时断在几处。真正执行审计时,仍按 first-breakpoint 停在第一个可信不变量失败处,后续阶段记为 not reached。
下面逐项展开。
对象身份 subject identity:我改了 A,实际服务的是 B
名字只是索引,不是东西本身。active 不等于流量已经走上新版本,目录叫 v2 也不等于进程加载的是 v2。要确认“改的就是那个东西”,得把这几样摆在一起看:期望的版本号、活着的进程号、它的工作目录、它实际加载的代码、请求走的路径。缺一样或者对不上,结论就该是“不知道”,而不是“完成”。
再举个类似的。注册表里能查到一个能力的名字,不等于对应版本的代码里真有一个能跑的入口——名字是登记信息,入口是实现。所以一个能力至少要说清:在哪个仓库、哪个确切版本、入口在哪、输出长什么样。这四样不全,足以拒绝它登记;但也仅止于此,不能顺着推成“这个产品没有这个功能”。
遇到“已经发布”,先问一句:哪个进程正在处理请求?
验收语义 acceptance predicate:对象对了,验的却不是用户要的性质
前端最容易藏这种断点。有个高风险功能默认是关闭的,后端照规矩把这类请求全拒了;前端那个入口按钮却只检查了“你登录了没”,于是照样亮着。两边各自都说得通:后端拒绝是对的,前端“登录了就显示”也是对的。问题出在两边之间——“这个功能关掉的时候,按钮到底该不该出现”,谁都没负责回答。
这就是我说的前后端对同一规则理解不一致:根因不只是某一层写错,而是这条规则从来没有被写成两层共同的验收标准。用户点下去会发生什么,也就没人验过。
所以,看到“组件能渲染”或“接口返回 200”,还要把用户真正需要的那条路径和性质走一遍。
运行包络 workload / lifecycle:测到一个版本,不等于测到正在运行的世界
我曾经拿一组漂亮的 before/after 数字宣布性能优化成功。后来想复核,才发现没留下任何能对上的东西:测的是哪个版本的代码、在什么环境、压了多大的量、怎么测的,全都没记。数字大概真的跑出来过,代码改动看着也说得通,但今天谁也没法把它再验一遍——那它就不算一条性能事实,只算一次印象。
性能这件事必须连着“在什么量下”一起说。量级、频率、并发、成本,任何一个变了,结论都可能改变。一次请求快了,不能证明真实流量下它稳。另一头是收尾:外层超时了,底下那个 worker 未必真停——它可能已经脱离了调用方,继续在跑。所以除了“我发了取消”,还得有“它确实停了、资源也回收了”。
性能结论要连着 subject、workload 和停止确认一起读:这是在哪个东西、多大的量下测出来的,谁确认它真的停了?
结论来源 claim provenance:报告很完整,不代表它说的都是真的
最容易被二次传播的,往往不是代码,而是一份完整的复盘报告。
我们干过这么一件事:用脚本从日志里捞出几组疑似问题,然后把它们写成了“已核验的案例”。问题在哪?时间挨得近不等于是同一件事,同一件事被摘了三遍也不等于有三个独立来源。一堆待查线索攒在一起,还是待查线索,既不能升级成案例,也算不出失败率——每一条都得单独回到一手证据上定性。
改错的时候也别把现场毁了。如果直接在原报告上就地修改,旧版本可能失去稳定的公开引用,别人引过的那句话也很难回到原来的快照。更靠谱的做法是:来源说不清、分母缺失或者两个分母根本不可比、没绑定代码版本、快照没存——只要有一样,这个结论就不许往上升级;确实需要重算分母,那就出一个新版本,并且说明它取代了哪一版。
这里说的是报告升级规则,也就是 claim-provenance gate;它目前仍是 PROPOSED,我还没有用反面用例证明它能拦住错误升级,因此不能把它视为已上线能力。
一份报告如果不能回答来源在哪里、什么新证据会推翻它,就只能停在候选结论。
0x03 从失败地图回到 Assurance
四个断点共享同一条规则:别停在“完成”这个词上,去读它声称改变、满足、测量或证明的那个东西。
这里的 Assurance 不是保证系统永远成功,而是保证失败、未知、冲突和证据不足时,系统不会把它们伪装成 DONE。Agent 可以执行动作、收集证据;验证者负责回读;有权接受风险的 owner 负责最后判断。
这正是 Agentic Assurance Engineering 要管的事:每个动作都说清谁授权、谁负责、谁接受剩余风险;每次交付都绑住对象、版本和证据;状态机也要有合法出口。验证结果至少要区分 PASS、FAIL、NOT_RUN、UNKNOWN、CONFLICT 和 ERROR;不适用的义务标成 NOT_APPLICABLE;需要人提供权限或裁定风险时进入 HITL,预算耗尽或流程终止则进入 FAILED。这些状态不能被一个绿色图标压扁成同一个结论。
至于 DONE,它如果还要存在,只能是按主张强度和适用义务派生出来的结果:这项主张涉及的四类维度——对象身份、验收性质、运行包络、结论来源——其适用检查都必须在声明边界内达到 PASS 或 VERIFIED(前者是单项检查结果,后者是主张层结论);明确不适用的维度要标成 NOT_APPLICABLE,高风险主张还要有 runtime readback。授权与风险接受条件也必须满足。否则就保留为 NOT_RUN、UNKNOWN、CONFLICT、FAIL、ERROR、FAILED 或 HITL,不能由 Agent 自己写回一个 true。
真正落地不必先建立完整平台。先要求每个完成声明回答四个问题:对象和版本是什么,验收性质是什么,运行包络是什么,证据能否被独立解引用。任何适用项答不上来,就保留为相应的 NOT_RUN、UNKNOWN、CONFLICT 或 HITL,不要让语言把它抬成 DONE。
接下来四篇会分别以一个断点为主视角展开:9 种「假完成」的完整清单、前端的静默退化、运行时的规模与生命周期,以及复盘报告自己怎样翻车。再由最后一篇《Agent 时代的 SDLC》把工具身份、上下文与数据、供应链隔离、审批撤销和运行时回读放进同一条状态链,让高风险动作更难绕过授权,让成功声明更容易被外部事实核对。
Agent 交出来的东西,默认只是一份待核的草稿。它想被当成事实,就得先在对象、性质、场景、来源这四件事上都拿出证据——而且证据能支持多大的结论,它就只能算多大范围内的事实。