青雲的博客
深入浅出 DeepSeek Harness 第八部:证明与守卫——怎样知道系统真的成立 第 50 章

从一个 commit 到下一版:这本书怎样更新

锁 commit 不是永远停在旧版——是让每个"当前实现"有可复核的具体对象。更新有严格的五维度比较流程和四类证据刷新机制,不是批量替换字符串。doc-sync gate 跑完整的文档验证链。

源码版本
47f943859bef60e4160492346772ded9b24f765a
验证日期
Commit
47f943859bef60e4160492346772ded9b24f765a

你读到这里,可能有个疑问:这本书锁死在 commit 47f9438,但 DeepSeek Harness 是活的项目,代码在前进。你现在读的内容会不会已经过时了?

这是所有技术文档的终极问题。这本书的答案不是”我们会持续更新”这种空话——它有一套严格的更新方法论和机器验证链。锁 commit 不是为了永远停在旧版,而是让每个章节的每条 SourceEvidence、每个 bash 实验命令、每张 mermaid 图都有一个可复核的具体对象

这是全书最后一章。

锁 commit 的意义:可复核性

每个章节 frontmatter 里都有 sourceVersionverifiedCommit,都是同一个哈希。这是一个承诺:这一章里所有 SourceEvidence 引用的路径、行号范围、符号名,在这个 commit 上都是可验证的。

你不信任我?三步验证:

  1. git checkout 47f943859bef60e4160492346772ded9b24f765a
  2. 跑章节里的 bash 命令(全是只读 grep/sed/cat,不修改任何东西)
  3. 对比 SourceEvidence 引用的行范围,看内容是否与注释一致

如果对不上,要么是我写错了,要么是你 checkout 了不同的 commit——不存在”可能是版本不同”的模糊地带。这就是 fixed commit 的全部意义。

repo="${DSH_SOURCE_DIR:?set DSH_SOURCE_DIR to the official fixed checkout}"
cd "$repo" && git rev-parse HEAD

输出应该是 47f943859bef60e4160492346772ded9b24f765a。如果不是,你在验证不同版本的代码。

更新不是批量替换字符串

很多文档更新的做法是:把所有 verifiedCommit: "旧哈希" 批量替换成新哈希,行号大概调调,完事。这是制造漂移的最快方式。

正确的更新第一步:

  1. 建立新的 detached checkout——记录新 commit 哈希、版本号、日期
  2. 确认 checkout 干净——没有未提交修改、没有 stash、工作目录清洁
  3. 不在老 checkout 上直接更新——你需要同时能访问新旧两个 commit 做对比

然后做五维度比较:

  • package graph:包的依赖关系变了吗?有新增/删除的包吗?
  • public symbols:每个包导出的 API 有变化吗?函数改名了吗?参数变了吗?
  • event vocabulary:事件类型有增删吗?EventEnvelope schema 变了吗?surfaceOp 有新值吗?
  • config schema:配置项有增删改吗?默认值变了吗?
  • 产品入口:CLI flag、Web UI 入口、SDK API 有变化吗?

Owner 变化通常跨多章。如果 credentials 模块从一个包移到另一个包,不只是那一章的行号变了——所有引用 credentials 的章节都要更新。

flowchart TD
    A[决定更新到新 commit] --> B[建 detached checkout,确认干净]
    B --> C[五维度比较]
    C --> D{有架构变化?}
    D -->|是| E[重写受影响章节结构]
    D -->|否| F[刷新四类证据]
    E --> F
    F --> G[源码引用验证]
    F --> H[可复现实验重跑]
    F --> I[叙事审计]
    G --> K[最小更新闭包检查]
    H --> K
    I --> K
    K --> L[doc-sync gate 全套通过?]
    L -->|否| M[修复,回到 K]
    L -->|是| N[真实 dogfood]
    N --> O[提交更新]
    style O fill:#2e7d32,stroke:#fff,color:#fff

三类声明的证据等级

阅读源码有三个层次,它们能证明的东西不一样:

配置声明只能证明”可以怎样组合”。 cordis.yml、package.json 里的配置项告诉你系统支持哪些组合,但不告诉你运行时实际用了哪些配置。

类型声明只能证明”允许传什么”。 TypeScript 类型告诉你函数接受什么参数形状,但不告诉你运行时参数实际是什么值、错误路径怎么处理。类型正确的代码可能运行时完全错误。

只有调用点与状态写入能证明这版实际怎样运行。 谁调用了这个函数?传入什么参数?事件在哪里 append?状态在哪里修改?失败路径留了什么痕迹?恢复从哪里开始?

写这本书的每一章,我都是从调用点和状态写入反推的,不是从类型签名和配置声明正推的。

doc-sync gate:机器验证文档一致性

DSH 项目自带 doc-sync 运行模式(pnpm run gates doc-sync),跑 20+ 个验证子任务。这不是 lint 空格——是验证文档与源码的结构一致性:

  • verify-cordis-catalog:文档里的 Cordis API 签名与源码 generated catalog 一致
  • verify-type-equiv:文档里的 ts type-equiv fence 与源码类型结构等价
  • verify-md-links:所有 markdown 内部链接指向存在的 target
  • verify-doc-refs:文档里引用的文件路径在仓库中存在
  • verify-mermaid:所有 mermaid 图语法合法
  • verify-export-jsdoc:导出函数的 JSDoc 与文档描述一致
  • verify-tool-catalog:tool catalog 文档与 tool registry 一致
  • verify-config-catalog:config catalog 文档与 config schema 一致
  • verify-persistence-catalog:持久化 catalog 文档与实际 persistence key 一致

这些 gate 跑的是 已 generated 产物与源码的比对——不是 “看起来对不对”,而是 “机器能证明一致”。任何一个 gate 失败,doc-sync 整体 fail,PR 不能合入。

repo="${DSH_SOURCE_DIR:?set DSH_SOURCE_DIR to the official fixed checkout}"
sed -n '571,612p' "$repo/scripts/run-gates.ts"

四类证据分别刷新

更新不是全量重写,是四类证据各自验证:

第一类:源码引用。 所有 SourceEvidence 的 path、lines 在新 commit 上重新验证——文件还在吗?行号范围在文件长度内吗?note 描述的逻辑还成立吗?符号名变了吗?如果函数被 rename,不只是改 note 文本——要确认新名字在 codebase 里的使用场景和旧名字一样。

第二类:可复现实验。 所有 bash 实验命令在新 commit 上重跑——命令还能执行吗?输出还是预期模式吗?有没有新增/删除的输出行改变了结论?grep pattern 还能命中吗?sed 行号范围还在文件长度内吗?

第三类:叙事审计。 每章的核心叙事——“谁拥有状态”、“失败后留下什么”、“恢复从哪里开始”——这些架构性结论变了吗?如果 owner 变了、恢复路径变了、失败边界变了,整个故事要重写,不是改改行号。这是最容易忘、但对读者误导最严重的一类漂移。

第四类:最小更新闭包。 验证清单:

  1. 50 个章节的 sourceVersion / verifiedCommit / lastVerified 全部指向新 commit
  2. 所有 SourceEvidence 能在新 commit 上命中——路径存在、行号范围在文件长度内、note 与实际内容一致
  3. 8 部连续顺序保持——部与部之间的引出关系、章与章之间的前后承接正确
  4. pnpm run gates doc-sync 全套通过——包括 type-equiv、cordis-catalog、md-links、doc-refs、mermaid 等 20+ gate
  5. 真实 dogfood:启动 Web 服务,实际操作几个场景,不碰源码项目

当新 commit 落地时发生什么

一个新 commit 合入 main。这本书的 fixed snapshot 现在落后了。什么时候该更新?怎么判断?

不是每个 commit 都需要更新。 如果新 commit 只改了测试文件、只修了一个 tool 的 output 格式、只加了一个新的 CLI flag——只要它没有改变本书任何一章的核心叙事(owner、边界、恢复路径),更新就是可选的。

必须更新的情况: event vocabulary 变了(新增/删除事件类型)、核心数据流变了(Session append 路径改了)、architecture 级重构(包被合并/拆分)、surface 语义变了。这些改变会让本书的叙事变成谎言。

更新的粒度不是”全书”: 如果只有第 18 章涉及的 bash subprocess 实现变了,只需要更新第 18 章。但所有章节的 verifiedCommit 要一起更新到新 commit——因为这个字段的含义是”在这个 commit 上已验证过”,不是”在这个 commit 上有变化”。这就是为什么需要最小更新闭包检查:确保你没有漏掉关联章节。

验证顺序: 先跑 doc-sync gate 看机器能查出什么。它能发现路径不存在、行号越界、类型不等价、链接断裂。它不能发现的是叙事漂移——“谁 own 这个状态”变了,但代码结构没变(比如一个注释被移到了另一个函数里),机器查不出来。叙事审计靠人。

全书完

你读完了全书 50 章。从 CLI 入口到 Cordis 组装,从 Turn 循环到工具调度,从事件账本到持久化恢复,从前端投影到扩展机制,到这一部的”证明与守卫”。

有件事最后还要再钉一下:这本书不是“教你怎么用 Harness”的用户手册。它是一本 internals——解剖运行时,让你看到每个决策背后的权衡、每个边界存在的理由、每个安全措施防的是什么故障。

这本书的可信度不来自作者的权威。它来自两样东西:

  1. 可复核性——你能 checkout 那个 commit,自己验证每一条证据
  2. 机器守卫——doc-sync gate 在 CI 里跑,任何可检测的漂移都无法合入

最好的验证不是相信我写的每一个字。最好的验证是:

git checkout 47f943859bef60e4160492346772ded9b24f765a

然后自己跑命令、自己看代码、自己得出结论。如果你发现我写错了——那就是我写错了,不是”版本不同”、不是”你理解不对”。Fixed commit 的意义就在于消灭这种模糊地带。

这本书只是地图。真正的理解在源码里。

全书完。