DSH plugin 运行时不变式:InvariantInstaller 与启动自检怎么写
DSH plugin 的运行时不变式,是包给自己立的「运行期必须始终成立」的规矩,由 ctx.invariants 统一注册与归因。 它明确不属于三包能力 seam,也不在 agent loop 主干里——它是一个 support 组服务:注册表负责选择哪些包启用、保留包名、管理子 fiber、把失败归因到具体包;每个工作区包各自发布一个 ./invariant 配套插件,用自己确切的 npm 包名注册检查。最容易搞错的一点是检查什么:官方约定只允许断言权威事件流或可变数据之间的关系,不允许检查服务或方法是否存在——那是加载期问题,不属于不变式。
DSH plugin 的运行时不变式:检查什么、不检查什么
一句话结论:不变式守的是「数据与事件的关系」,不是「对象在不在」——存在性交给依赖声明,不要塞进检查里。
- 可以断言:权威事件流里不该出现的顺序、可变数据的结构关系、跨事件的计数一致性。
- 不要断言:某个服务是否注册、某个方法是否存在、某个包是否加载——这些由 inject 与启动日志负责。
- 违规即失败:条件不成立就调用
fail(),而不是打日志继续跑。 - 每个包一个配套插件:发布与注册是穷尽式的,但「是否安装检查」由包自己决定,不合成断言。
DSH plugin 不变式的选择:enabled 与两张正则清单
一句话结论:服务默认开启;一个包被选中要同时满足「允许列表为空或命中」且「阻止列表未命中」,且阻止列表优先。
| 配置项 | 含义 | 默认 |
|---|---|---|
enabled | 全局开关 | true |
package_allowlist | 大小写敏感的正则源码,空表示全部放行 | 空 |
package_blocklist | 允许之后再做排除,优先级更高 | 空 |
- 匹配规则:条目按
new RegExp(source)编译;默认不锚定,要精确匹配请自己写^与$。 - 不解析
/pattern/flags写法:想表达 flags 请用内联分组一类正则语法,而不是斜杠包裹。 - 启动即校验:空白、首尾带空白、重复或无效的条目会直接抛异常,而不是被跳过。
- 允许不命中任何已加载包:有效模式可以匹配不到任何包,这样后续加载与 HMR 的行为保持确定。
- 过滤器在服务生命周期内固定:不要在运行中期望它动态变化。
DSH plugin 怎么写 InvariantInstaller:ctx、fail 与 inject
一句话结论:安装器就是一个 (ctx, fail) 函数;需要服务就声明 inject,检查是同步还是异步都可以。
type InvariantFailure = (message: string) => never
interface InvariantInstaller {
(ctx: Context, fail: InvariantFailure): void | Promise<void>
readonly inject?: Inject
}
- 先声明 inject:安装器只能访问它声明的服务,未声明的拿不到。
- 在函数体里做检查:不满足就
fail('...'),消息写「违反了哪条包契约」,不要重复标准前缀——框架会自动加上invariant violated by "<package>": …。 - 异步检查可以直接 await:注册会等安装器同步或异步执行完毕,才算成功。
- 失败即抛:
fail的返回类型是never,因为上报违规就是抛异常。 - 验证:故意制造一次违规,确认错误消息里带正确的包名与 code。
DSH plugin 注册与生命周期:包名保留、子 fiber 与失败回滚
一句话结论:注册会为完整 npm 包名保留唯一席位;安装器在自己的子 fiber 里跑,失败则原子回滚。
- 注册:
ctx.invariants.register(packageName, installer)
// 返回绑定到 effect 的 disposer
- 保留是强约束:即使过滤器让安装器保持不活跃,包名保留依然成立——两个插件不可能静默认领同一个包名。
- 重复即报错:重复、空白或含空白字符的名称会抛异常。
- 在子 fiber 中运行:被启用的安装器跑在专属子 Cordis fiber 里,
installer.inject决定它能访问哪些服务。 - 失败回滚:安装器失败会原子地处置子 fiber 并释放保留,所以修复后可以重载再注册,不留残余。
- 双向清理:注册 fiber 归服务所有,返回的 disposer 又属于配套插件 fiber——卸载任一侧都会移除监听器、trace 状态与保留项。
DSH plugin 配套插件约定:空安装器也要写理由
一句话结论:每个包都要有 ./invariant;没有可检查项时,空安装器必须写明为什么。
- 目录与发布:每个工作区包拥有
./invariant配套插件,导出、发布、依赖与打包接线要完整。 - 有可观察关系才安装检查:包拥有某个可观察事件或可变数据关系时,才装检查。
- 没有就写空安装器:起始注释必须以
No runtime invariant:开头,并针对该包具体解释原因。 - 机械校验:
pnpm run verify-package-invariants会拒绝「生成文件」标记、无解释的空安装器、遗漏报告器的非空安装器、错误注册名,以及不完整的发布接线。 - 别为了通过校验而编检查:没有真实不变式时,诚实的空安装器比一条假断言更有价值。
DSH plugin 排查注意事项
一句话结论:不变式报错先看包名归因,再区分「真违规」与「检查写错」。
- 报错带包名:
invariant violated by "<package>"直接告诉你该找谁。 - 先确认不是检查写错:条件过严会把正常状态判成违规,尤其是没锚定的正则与跨事件计数。
- 包名保留冲突 → 检查是否有两个插件注册了同一 npm 包名。
- 过滤器改了却不生效 → 过滤器在服务生命周期内固定,需要重启或重载组合。
- 空安装器报错 → 检查注释前缀与解释是否齐备。
- 修复状态如实:本文的选择规则、安装器签名、保留与回滚语义均取自官方「运行时不变式」页面与
dsh-invariantsREADME;具体校验命令与包约定以官方仓库为准。
常见问题
DSH plugin 的运行时不变式只断言权威事件流或可变数据之间的关系,不检查服务与方法是否存在。存在性属于加载期问题,用依赖声明与启动日志解决;不变式用来守住「运行一段时间后不该出现的数据状态」。
DSH plugin 的 InvariantInstaller 里,fail 是绑定到注册包名的失败上报器,调用它就会抛出 InvariantError 并携带稳定 code 与包名。它设计成抛异常而不是返回布尔值,是为了让违规立刻中断、并能归因到具体包。
不会,DSH plugin 的安装器失败会原子地处置它的子 fiber 并释放包名保留。因此配套插件可以修复后重载并再次注册同一名称,不会出现「名称被占用」的残留。
DSH plugin 的不变式选择里,阻止列表匹配优先于允许列表匹配;允许列表为空表示全部放行。条目按 new RegExp(source) 编译,默认不锚定,要精确匹配请自己写 ^ 与 $。
需要,DSH plugin 的配套插件约定要求每个包都有 ./invariant,没有可检查项时导出空安装器。空安装器的起始注释必须以 No runtime invariant: 开头,具体说明为什么没有检查项。
相关术语
- 运行时不变式(runtime invariant)
- 运行时不变式是 DSH plugin 对权威事件流或可变数据提出的、运行期必须始终成立的条件。它由包自己发布检查,注册表只负责选择、保留与归因失败。— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/invariants
- InvariantInstaller(不变式安装器)
- InvariantInstaller 是包提供的检查安装函数,签名为 (ctx, fail) => void | Promise<void>,可声明 inject 来访问所需服务。它在专属子 fiber 中运行,完成前注册不会被视为成功。— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/invariants
- InvariantError
- InvariantError 是违反不变式时抛出的错误类型,继承自 Error,带稳定 code INVARIANT、所属 packageName,消息前缀为 invariant violated by "<package>"。它让违规可归因,而不需要注册表导入产品包。— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/invariants
- package_allowlist / package_blocklist
- 这两个配置项用正则源码决定哪些包的不变式被启用,大小写敏感、按 new RegExp(source) 编译。阻止列表优先于允许列表;过滤器在服务生命周期内固定不变。— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/invariants
来源
- DeepSeek Harness 文档 — 运行时不变式(dsh-invariants、选择、安装器、配套插件约定)· deepseek-harness.github.io
- dsh-invariants 包 README(注册、目录与组合方式)· deepseek-ai(GitHub)
- DeepSeek Harness 文档 — 插件与生命周期(子 fiber 与失败语义)· deepseek-harness.github.io