DeepSeek Harness 源码精读 11:实战对质——把开发大坑拿回源码

第 11 章 实战对质:把开发大坑拿回源码

代码基线:commit 8f86b979a1(2026-10-01)| 0.2.0-rc.1 | 本系列讲次:M10

本章导读

最后一章,把你自己的实战档案拿回源码对质。

前面十章讲的是"这个仓库是什么"。这一章反过来:你在这台机器上踩过的那些坑,用我们学过的机制重新解释一遍。

ygsdoc/ 下有 8 篇开发大坑记录。本章逐条对回 0.2.0-rc.1 的源码,结论是:6 条可核对的根因断言全部成立,0 条需要修正。 剩下的三条(kitty Ctrl 键、六项投递、model TPS)属于本系列没覆盖的层,这里诚实标注而不是假装能解释。

其中最值得回看的是"inject 漏容器座位"那篇——第 2 章的实验脚本第 7 条原样复现了你的现场报错,并且解释了那个最反直觉的现象:为什么插件能激活成功,一点才崩。答案是 inject 门控与属性读取是两道独立的关卡,前者只看声明过的名字,后者走代理的 get 陷阱。

本章末尾给出一份书稿改版建议清单:必改 12 处(按 P0/P1/P2 分级)、建议补 36 条、以及一份不要动的清单。第七章、第八章零勘误;第五章"单调守护"和"策略与本体彻底分离"两段原文准确,不必改。

最后坦白一件事:这套材料最有价值的其实是三次当众纠错——误判"创造模式已消失"、手写接缝表错三处、两讲之后重犯同一个链接层级错误。一套能公开记录自己错在哪的学习材料,比一套永远正确的材料有用得多。

学习要点

  • 你的现场记录质量很高:6 条根因断言零需修正
  • 报错文本要能追到具体文件行号:cannot get property "..." without inject 在 reflect.ts:144
  • 声明和读取是两道独立的关卡——这是 L-04 的通用形式
  • 讲义的价值不在结论正确,在于结论可被复核

本章目标

  1. 把 8 篇开发大坑的根因断言逐条对回源码,判定成立 / 需修正 / 已过时
  2. 指出每条坑对应本系列哪一讲,验证这套讲义在你的实战里真的有用
  3. 给勘误附录做总收口,并产出书稿改版建议清单

一、逐条对质总表

# 坑 你记的根因 对质结论 对应讲次
1 inject 漏容器座位 remote.<ns> 是两条独立的服务,前者让插件激活、后者才允许读 ✅ 完全成立,已在实验里复现 M1 §实验第 7 条
2 remote.session 跨包调用 命名空间访问绑定到读取方 ctx;this.ctx 被重绑到调用方语境 ✅ 机制成立,且能追溯到具体代码行 M1 §2.1(service.ts:53 self.ctx = ctx)
3 dshmarket 重复挂载 已在 bundle 层注册的包若又出现在 profile dependencies 里,market 会再挂一份 shim ✅ 成立,且 M0 已给出它的结构解释 M0 §4 + M2 §三
4 settings.yaml 一个空格 非法 YAML 导致 no adapter registered for provider "xiaomi" ✅ 成立,且与全系列的 fail-loud 主题同源 M6 §四(env/dshEnv 三重保险)
5 ego 浏览器 macOS headed 必须显式开 headless ✅ 成立(环境判定矛盾类) M6 §六(平台探测链)
6 kitty Ctrl 键(TUI) 终端按键解析 ⚠️ 属于 TUI/kitty 交互层,本系列未覆盖 —
7 工作区六项投递三插件联动 从零到三个插件联动 ⚠️ 属于集成实战,本系列未覆盖 —
8 dsh model TPS / vision 补丁 路由与补丁 ⚠️ 属于模型接入层,本系列未覆盖 —

6 条根因断言全部成立,0 条需要修正。 这一点值得单独说:你的现场记录质量高于我预期的水平。


二、三条最值得回看的坑

2.1 坑 1 —— M1 的实验第 7 条精确复现了你的现场

ygsdoc/开发大坑_cordis只写命名空间服务漏了容器座位踩without inject_2026-10-01.md 记的现象是:

插件 boot 报 1 entry did not activate → failed;但按钮渲染正常,一点才崩

M1 的实验脚本第 7 条原样复现:

7) 只声明命名空间服务: state=2  ← 已激活(容器座位 remote 没声明,activate 不检查它)  
   实际读取时报 : cannot get property "remote" without inject  

根因在代码里的确切位置:vendor/cordis/src/reflect.ts:144 抛 cannot get property "${prop}" without inject。而激活门控走的是另一条路径(fiber.ts:_refresh,只看 inject 里声明过的名字)。

这就是方法论 L-04:inject 门控与属性读取是两道独立的关卡。 写 M1 时我把它立成一条通用规律,读完 M1 再回看你这篇,会发现这条规律就是你那篇坑的通用形式。

2.2 坑 2 —— this.ctx 被重绑,能追到 service.ts:53

你的根因写得很准:"Service 方法被跨包调用时 this.ctx 被重绑到调用方语境"。

代码位置在 M1 §2.1 讲过的 Service 构造函数(vendor/cordis/src/service.ts:42-59):

constructor(protected ctx: Context, name: string) {  
  ...  
  self.ctx = ctx        // ← 第 53 行:ctx 是构造参数,谁 new 就绑谁  
  ...  
}  

this.ctx 就是构造参数本身,没有别的来源。所以一个远端命名空间服务如果在调用方的 fiber 语境里被构造,它的 this.ctx 就是调用方的 ctx,而调用方的 isolate 映射里未必有那个命名空间 → 落到 reflect.ts:164 的 fiber.parent[symbols.isolate][prop] !== key 分支 → 抛同一个错。

补充一条讲义没写的事实:ctx.remote 这个命名空间是由 Typert 网关生成的(packages/api/remotes/src/index.ts:37-40,inject = ['typertGateway'])。M1 的作用域机制与 M8 的 Typert 在这条坑上合流 —— 它是"生成式类型契约"与"Cordis 作用域隔离"两条线唯一的交叉点。

2.3 坑 3 —— M0 早就给出了它的结构解释

你的坑 3 记的是现象(60+ client 插件 pending)。M0 §4 早就指出结构原因:

...  
# == @deepseek-ai/dsh-base                    ← 第 1-11 层  
...  
# == dshmarket                                 ← 第 12 层,它会去重扫前面已挂好的条目  

dshmarket 处在 12 层,它要重扫 11 层已经挂好的条目。 这是 M2 的层叠模型("扁平化后一次应用")和你现场那个 bug 的直接交叉。


三、这 8 篇里,本系列没有覆盖的部分

诚实标注,避免"讲义能解释一切"的错觉:

坑 归属 为什么没进这 11 讲
kitty Ctrl 键 TUI/终端按键层(packages/terminal 的消费者侧) 本系列重心在 Host 侧产品脊柱,未进 TUI 交互
工作区六项投递 packages/api/workspace-publish 一族 属于具体功能集成,不是架构机制
model TPS / vision 补丁 模型接入层 M6 讲了接缝形状,未讲具体 provider

如果要把这三块也讲透,需要额外三讲(约 TUI 交互层、workspace 发布族、模型路由层)。


四、勘误附录总收口

11 讲跑完,附录累计:

类别 数量 指向
【勘误】E 27 其中 25 条指向书稿,2 条指向本系列自己的讲义(E-26 M6 表、E-27 生成表漏报)
【补充】S 48 书稿缺口,含 3 条覆盖缺口(S-24/29/40 类的名单口径)
【方法论】L 13 本系列沉淀,其中 L-04 / L-12 / L-13 直接来自你的实战与自纠
【存续】 每讲一节 明确记录不要在后续推翻的结论

4.1 书稿勘误按章分布

书稿章 勘误数 性质
第一章 7(E-01~E-07) 数字过时 + preset 名字错 + profile vs preset 混淆
第二章 2(E-08~E-09)+ 1(E-21 跨第三章)+ 6 补充 派发模式数量、epoch 机制、上下文过滤缺失
第三章 9(E-13~E-17, E-21)+ 6 补充 最重要的一章:assistant/chunk 不存在、SurfaceEventType 数量、sourceEventSeqs 反了、EpochHeader.system 已移走
第四章 4(E-18~E-21)+ 8 补充 全部由 M4 找到,与第三章 E-13 同源
第五章 2(E-22~E-23)+ 6 补充 决策取值数量;单调守护那段完全正确
第六章 2(E-24~E-25)+ 5 补充 接缝结构与 Provider 数量
第七章 0 —
第八章 0 —
第九章 1(E-27)+ 4 补充 生成表这个视角缺失

五、★ 书稿改版建议清单

这是十讲下来最有交付价值的部分。

5.1 必改(事实错误,共 12 处)

优先级 章 改什么
🔴 P0 三 §3.3 删掉 assistant/chunk 整条,改为"流内嵌在 assistant/message / assistant/attempt 的 stream 数组里"
🔴 P0 四 §4.5 同上第四步改写;删掉 sourceEventSeqs 那句
🔴 P0 四 §4.5 / 三 §3.6 EpochHeader 去掉"系统提示",改为 system?: never + "系统提示是 system/message 事件(surface node 0)"
🔴 P0 三 §3.6 SurfaceEventType 由 3 种改为 5 种
🔴 P0 三 §3.3.2 "interrupted 是唯一永不实时发出的 reason"→ 两个,另一个是 forked
🟠 P1 一 §1.4 219 包 → 328;五十万行 → 356,402 行;标注基线版本
🟠 P1 一 §1.6 creative → cordis;补"profile 5 个 / preset 4 个"的正交区分
🟠 P1 一 §1.7 profile 由 2 个 → 5 个;bundle 补 sdk-app / acp-app / sdk-minimal
🟡 P2 五 §5.4 PreToolDecision 补 cancel;PostToolDecision 由 4 种行为改为 2 个 kind
🟡 P2 六 §6.2 shell 接缝结构重写(E-24)
🟡 P2 二 §2.5 派发模式 4 种 → 5 种(补 bail)

5.2 建议补(缺口,共 36 条,见附录 S-01~S-48)

优先级最高的六条:

  1. S-06 / S-07:第二章补「dsh 的 Cordis 不是上游 Cordis」(vendor/README.md 22 条本地改动)
  2. S-35 / S-34:第六章补沙箱探测链与 SandboxEnforcement 两类来源
  3. S-32 / S-33:第六章补 env/dshEnv 三重保险与模型可见面分层
  4. S-16 / S-17:第三章补「日志的物理形态」(zstd 多帧、格式代际并存)
  5. S-19:第四章补 agent/* 12 事件权威清单
  6. S-45:第九章补覆盖率门禁的"删死代码"立场

5.3 不要动(已验证成立)

  • 第七章、第八章:零勘误
  • 第五章 §5.4 第 4 站"单调守护"、§5.4.1"策略与本体彻底分离"
  • 第三章 §3.7 整节(ignorable)
  • 第三章 §3.6 关于 replace 机制的核心洞见(只需改字段名与取值数)
  • 第九章测试阶梯的形状

六、这十讲本身沉淀下来的东西

抛开对书稿的勘误,这套讲义自己留下了三样东西:

产物 位置 用途
9 个可复跑实验脚本 labs/M1..M9 全部 exit=0,可直接重跑核对任何结论
勘误活文档 勘误附录-书稿vs0.2.0-rc.1.md E/S/L 三分 + 每讲【存续】区块,可持续追加
13 条方法论 附录 L-01~L-13 其中 L-04 / L-12 / L-13 直接来自实战与自纠

而最有价值的,其实是三次当众纠错:

  • M0 误判"创造模式已消失" → 立 L-01(一手数据优先于二手结论)
  • M6 手写接缝表错三处 → 立 L-13(交叉验证同时暴露两边各自的错)
  • M8 重犯 M6 的链接层级错误 → 链接校验从此成为每讲收尾的固定动作

一套能公开记录自己错在哪的学习材料,比一套永远正确的材料更有用。 这一点是我这十讲最大的体会。



本章实验(附录)

七、动手验证(M10 专属)

本章不设新脚本——它的验证就是把前九讲的脚本全跑一遍:

export PATH="/opt/homebrew/bin:$PATH"  
cd /Users/ygs/ygs/deepseek-harness  
for l in ygsdoc/学习笔记/labs/M*.ts; do  
  printf "%-24s " "$(basename "$l")"  
  node --import tsx/esm "$l" >/dev/null 2>&1 && echo "✓" || echo "✗"  
done  

M10 通过标准:能对着 ygsdoc/开发大坑_cordis只写命名空间服务漏了容器座位_2026-10-01.md,指出报错文本来自 vendor/cordis/src/reflect.ts:144,并解释为什么插件能激活成功(inject 门控走 fiber.ts:_refresh,只看声明过的名字;属性读取走 reflect.ts 的代理 get 陷阱,是另一条路径)。


本章的勘误条目见 附录 A · 勘误总表。