没有安全栈的 AI 速度,最后会变成脆弱性

AI-generated code 最大的危险,不是它总是错的。

真正危险的是,它经常“看起来已经对得足够可以 merge”。

这正是风险所在。明显有问题的 code 往往会被挡住。真正会进生产的是那种看起来合理、能过几个 happy-path tests、却悄悄削弱关键 boundary 的实现。

如果你的工作流只是 prompt、paste、review、merge,那么生成速度每提升一次,系统变化速度和系统可信度之间的缺口就会再拉大一点。

解决办法不是继续把希望压在 code review 的英雄主义上。解决办法是一套分层安全栈,在不同阶段拦截不同类型的失败。

第 1 层: 用 types、schemas、contracts 做 prevention

最便宜的 defect,是程序根本无法表达出来的 defect。

所以第一层是 prevention。

这一层的任务,是在 runtime behavior 发生之前先收紧 surface area:

  • Branded types 和 phantom types 防止那些结构相似、语义不同的值被混用。
  • Runtime schemas,例如 Zod,用来守住 untyped data 进入系统的边界。
  • Contracts 给关键 code paths 增加 preconditions、postconditions、invariants。

对于 AI-generated code,这一层尤其重要。模型很擅长生成“表面上自洽”的实现,但这种实现仍然可能犯 category mistakes。如果你的 types 和 schemas 很弱,模型就有太大空间停留在“差不多对”的状态。

第 2 层: 用 property-based tests 做 verification

Example tests 当然有价值,但它们太容易被人和模型一起 overfit。

模型完全可以同时生成一个 function 和一个看似匹配的 happy-path test。在 pull request 里这看上去像生产力,实际上语义约束仍然很弱。

Property-based testing 改变的是问题本身。它不再问“这段代码在三个例子上能不能跑通”,而是问“在一整类 inputs 上,哪些性质必须始终成立”。

通常最值得先上的几类 properties 是:

  • round trips
  • idempotence
  • ordering invariants
  • monotonicity
  • invalid input 下正确的 error behavior

这里 AI 其实很有帮助。模型通常能根据 signature 或 doc comment 给出一版不错的 first-draft properties。人还是要 review,但 blank-page problem 会小很多。

第 3 层: 用 mutation testing 做 assessment

Coverage 不是质量指标,它只是执行指标。

Mutation testing 问的是更关键的问题: 如果代码以一种“看起来合理但其实错误”的方式发生变化,你的 tests 能发现吗?

这就是为什么 mutation testing 应该放在 contracts 和 property tests 之上。它不是替代前两者,而是衡量前两者是否真的有牙齿。

在 AI 时代,这一点尤其重要。模型可以生成一整套“看起来很强”的 tests,执行很多行代码,但真正断言的东西很少。Mutation testing 会非常快地把这种 false confidence 揭出来。

实践上,不是要对所有代码时时刻刻跑 full mutation analysis。更现实的做法是:

  • 先从 critical modules 开始
  • 对 changed code 使用 incremental mutation testing
  • 对 survivors 做严格 triage
  • 随着 suite 成熟逐步提高 thresholds

在 AI 时代,mutation testing 本质上是对抗假信心的解药。

第 4 层: runtime containment 与 recovery

即使很强的 verification stack,也不可能抓住一切。

所以最外层一定要有 runtime containment。

这就是 crash-only design、deadlines、circuit breakers、leases、capability-based boundaries 开始发挥作用的地方。某个问题一旦漏过去,系统应该受控失败,而不是把一次坏路径放大成连锁事故。

对很多团队来说,这一层往往从更小的动作开始:

  • 给 external calls 加 explicit timeouts
  • 在会改状态的 endpoints 上加 idempotency keys
  • 给不稳定依赖加 circuit breakers
  • 为敏感操作保持 narrow capability surfaces

目标不是完美,而是 bounded blast radius。

为什么这些层叠在一起才真正有效

每一层抓住的都是不同类型的问题。

Types 和 contracts 先阻止明显 invalid states。Property-based tests 检查语义。Mutation testing 判断 tests 是否真的有力度。Runtime containment 负责兜住仍然漏出去的部分。

核心思想是: 你不需要一个完美方法,你需要多个不完美但彼此独立失效的层。

一个现实可落地的 rollout 长什么样

大多数团队都不该试图一次性全开。

更实际的顺序通常是:

  1. 先收紧 TypeScript 或 Rust 的 boundaries
  2. 在外部输入处加 runtime schemas
  3. 在关键 functions 上引入 contracts
  4. 给 serializers、reducers、validators 写 property tests
  5. 在高风险模块上接入 incremental mutation testing
  6. 在最常出故障的 dependencies 周围补上 runtime containment

这个顺序之所以有效,是因为每一层都会强化下一层。更好的 schemas 会带来更好的 properties。更好的 properties 会提高 mutation scores。Mutation feedback 又会反过来暴露 contracts 或 test depth 仍然薄弱的地方。

AI 时代工程实践真正的分水岭

最后赢的,不会是生成代码最多的团队,而是能够在不降低 trust 的前提下吸收更多 generated code 的团队。

这正是安全栈要解决的问题。

它把 AI 从一个 speed amplifier 变成一个 reliability amplifier。模型负责帮助生成 implementations、tests、contracts、rules,而这套栈负责确保这些 artifacts 会被 deterministic systems 持续检查,而不是仅仅因为“看起来不错”就被信任。

如果你真的打算把 AI 用进 production engineering,这才是该追求的标准。不是再加一张 review checklist,也不是再多写一句“be careful”。

而是一套分层系统,让每一次 generated change 都必须经过 prevention、verification、assessment、containment。

这样,快代码才会变成可信代码。