缓存文章的内容测试:回读 read_cache.rs 的 TTL、字段白名单和 p95 限定
AI 写出来的技术文章不能只靠 file:line 摆样子。我们把一篇缓存文章里的 TTL、字段白名单、失效路径和延迟限定写进 website test:源码一漂移,文章先红。
scripts/blog/blog-candidates.mjs,以及它里面的去重检查。commit e836ad28 连同整套开发 harness 一起删掉了它,也没有任何东西接替,所以这一条已经改写、去掉了那个说法——而不是留着一个指向空处的引用。这里讲的 sourceRange() 和那些回读源码的测试,今天仍然活在 website/tests/posts.test.mjs 里。另有两句描述周边流程的话,在同一次修订里一并更正:常设的 cross-vendor review,以及把这个测试说成发布闸门——它们都不像原文暗示的那样被强制执行。问题不是文章写得像不像
AI 写技术文章最安静的失败模式,是代码已经变了,文字还一脸笃定。file:line 引用能帮忙,但如果没人检查,它只是一个书签。下一次改动可能把 TTL 改掉、把返回字段放宽、把失效路径挪走,而公开文章仍然在讲旧事实。
这篇文章背后的 branch commit 是 ab6b277b6fb5fc4689826ee218a442c008678fc0,它被 bd78108af3ab1dc0a24f5c6e821afbc602a631d4 这个 merge(Merge feat/blog-fairness-fail-closed)合入。这个 branch 不只是发布了 read-cache contract 文章,也给这篇文章加了测试。
可复用的 lesson 不是“每一句文案都写测试”。真正值得抄的是:公开技术内容一旦承载信任、安全或产品承诺,就要在发布前把关键 claim 变成可执行检查。
先挑会伤信任的 claim
那篇缓存文章说了几件不能漂移的事:session 和 /api/auth/me cache 是 5 秒窗口,头像/昵称 cache 是 60 秒,缓存头像数据不能带 email,warm-path 的微秒预算不是生产延迟承诺。
第一层测试先把这些承诺列出来。在 commit ab6b277b6fb5fc4689826ee218a442c008678fc0 里,名为 pins the cache windows, invalidation boundary, field allowlist, and latency disclaimer 的测试读取 cache-the-read-path-contract 的中英文正文,确认文章仍然写着 TTL、no-email 边界、membership/entitlement 只依赖安全 TTL 的 caveat,以及延迟数字的限定范围。它还拒绝两个旧的错误说法,避免后来改文案时把 overclaim 又带回来。
但这还不够。只读文章自己的测试,最多证明文字没有自相矛盾;它不能证明文字还和代码一致。
让内容测试去读源码
真正有用的是 commit ab6b277b6fb5fc4689826ee218a442c008678fc0 给 website/tests/posts.test.mjs 加的 sourceRange() helper。它会读取仓库文件,再切出文章引用的那段真实源码。后面名为 keeps the cited Rust source ranges aligned with the cache contract 的测试不信任文章正文,而是直接打开 Rust 源码,检查真实 token:SESSION_TTL、ME_TTL、AVATAR_TTL、不含 email 的 CachedAvatar、generation 防 late insert、session invalidation、头像查询字段,以及 warm-path p95 断言。
这样文章就有了 fail-closed 路径。以后如果有人把 5 秒改成 30 秒、把 email 塞进头像缓存类型、删掉 generation guard,或者把 service-path 回归预算写成生产延迟承诺,website test 会红——它直接读源码,所以文章和代码不会在没人察觉的情况下分家。它是仓库里的测试,不是 CI 闸门,护的是发布前跑站点测试的那个人。
它不能替代 reviewer
这套做法是 tripwire,不是 reviewer。它不能判断英文有没有力度、中文是不是自然、文章选题是不是最值得写,也不能覆盖所有可能的 claim。它同样不会把一个性能数字变成生产承诺;缓存文章仍然把 5,000 微秒写成进程内 service-path 预算,而不是线上主机指标。
重点是把两件事分开:判断——清晰度、框架、有没有 overclaim——需要一个读者来做,人或者另一个模型都行;内容测试负责机器能检查的部分——源码范围、常量、字段边界,以及已经被 review 打掉、绝不能回来的说法。
我会复用的规则
以后让 AI 写公开工程内容,我会按这个清单处理:
- 挑出三到五个一旦漂移就会伤信任的 claim;
- 给每个 claim 放一个具体的
file:line来源; - 写内容测试去读那段源码,而不是只读文章文字;
- 连反面也一起测:不能出现的字段、review 已经删掉的说法、必须保留范围限定的延迟数字;
- 把这篇文章取材的那个 merge 写进文章清单的
sourceShas[]字段(website/lib/posts.ts),让下一个作者一眼看得出哪个已上线的改动已经被写过了。它只是写稿用的出处记录,不是对外字段:站点不会渲染它,而内容测试会断言这个字段和本文自己那条记录还在。
技术文章不应该等读者来抓错,才第一次享受“可执行证据”。如果这个 claim 重要,就在它上官网前先变成测试。