跳到主要内容
路径文档

运行时不变量

一句话版@deepseek-ai/dsh-invariants 提供 ctx.invariants 注册表服务;每个工作区包用一个 ./invariant 配套入口注册对自身 npm 包名契约的运行时检查,违规抛带稳定 code:'INVARIANT'packageNameInvariantError。这是 DSH 对 session/agent/hook/compaction/goal/jobs 等运行时契约做持续自检的机制。

这是 运行时自省与临时插件 的姊妹篇:前者让模型手动检查/临时扩展运行时,这里让包自己声明"我的运行时契约不能坏"。读完你能给自己的插件加运行时健康断言,并理解 DSH 的持续自检机制。

一、整体模型

  • 服务层InvariantRegistryctx.invariants)是一个可配置注册表,不含任何产品检查、也不导入产品包
  • 配套入口层:每个工作区包发布一个 ./invariant 伴随入口,注册它精确的 npm 包名
  • 检查策略README 明说 —— 发布和注册是全覆盖的,但运行时断言刻意不人为捏造。只在包拥有可观察的事件关系或相关可变数据关系时装检查。

二、服务:ctx.invariants

// packages/runtime-diagnostics/invariants/src/index.ts
interface Config {
/** Global switch; defaults to `true`. */
readonly enabled?: boolean
/** Case-sensitive JavaScript regex sources that admit package names; empty admits all. */
readonly package_allowlist?: string[]
/** Case-sensitive JavaScript regex sources that exclude package names after allowlist matching. */
readonly package_blocklist?: string[]
}
配置项默认说明
enabledtrue服务级总开关;false 时所有包都不被选中
package_allowlist[]逐一用 new RegExp(pattern) 编译的正则源;空则全部放行
package_blocklist[]同样正则源;blocklist 匹配优先于 allowlist
// packages/runtime-diagnostics/invariants/src/index.ts
private selected(packageName: string): boolean {
if (!this.enabled) return false
if (this.packageAllowlist.length > 0
&& !this.packageAllowlist.some(pattern => pattern.test(packageName))) return false
return !this.packageBlocklist.some(pattern => pattern.test(packageName))
}

选中的三个条件缺一不可:服务启用 ∧(allowlist 空 ∨ 至少一个 pattern 匹配完整 npm 名)∧ 无 blocklist pattern 匹配。

正则过滤的约定

  • 匹配是区分大小写的 JS 正则源,用 new RegExp(pattern) 编译;不解析 /pattern/flags 语法。
  • 除非源自供 ^/$,否则不锚定(子串匹配)。
  • 同一列表里空白、带前后空格、无效、或重复条目 → 服务启动失败(index.tscompilePatterns)。
  • 有效 pattern 可以不匹配当前任何已加载包,这样后续加载和 HMR 保持确定性

register(packageName, installer)

// packages/runtime-diagnostics/invariants/src/index.ts(截选)
register(packageName: string, installer: InvariantInstaller): () => void {
// 校验包名非空、无空格、不重复注册
// const ctx = this.ownerCtx
return ctx.effect(async () => {
if (!this.selected(packageName)) {
return () => { registrations.delete(packageName) }
}
const child = ctx.plugin(installer.inject === undefined
? installInvariant
: Object.assign(installInvariant, { inject: installer.inject }))
try { await child } catch (error) { await child.dispose(); throw error }
return async () => { try { await child.dispose() } finally { registrations.delete(packageName) } }
}, `invariants.register(${JSON.stringify(packageName)})`)
}

要点:

  • 保留一个活动注册:即使过滤器让 installer 保持非活动,包名也被保留(用于占位)。
  • 专用子 fiber:enabled 贡献在专用子 Cordis fiber 里运行,通过 installer.inject 可声明所需服务接口。
  • fail(message) 注入:installer 收到 fail,调用它抛出一个绑定到注册包的 InvariantError(不会返回)。
  • 启动 join:同步或异步 installer 完成都会被 join,注册成功前不返回;失败会原子地 dispose 子 fiber 并释放归属,绝不留"半注册"。
  • disposer 归属:服务拥有每个注册 fiber;返回的 disposer 还属于配套 fiber。卸载任一侧都会移除监听器、跟踪状态与保留,所以配套入口可以重载并重新注册同一包名而不留旧状态。
  • applies to reload:session 支撑的配套入口从持久事件重建 baseline;仅 live 的配套入口观察 reload 之后开始的操作。

三、InvariantError 与其契约

// packages/runtime-diagnostics/invariants/src/index.ts
export class InvariantError extends Error {
readonly code = 'INVARIANT' as const
readonly packageName: string
constructor(packageName: string, message: string) {
super(`invariant violated by "${packageName}": ${message}`)
this.name = 'InvariantError'
this.packageName = packageName
}
}

InvariantError 扩展 Error,携带稳定的 code: 'INVARIANT'(机器可读),并暴露归属的 packageName,且不给服务本身加任何产品依赖。

四、配套入口:./invariant 伴随文件

每个包的配套入口是一个标准 cordis 插件,但在 install 阶段注册。以 dsh-invariants 自己的配套入口为例:

// packages/runtime-diagnostics/invariants/src/invariant.ts
const PACKAGE_NAME = '@deepseek-ai/dsh-invariants'

export const name = 'invariants-invariant'
export const inject = ['invariants']

/* 空 installer:注册归属与子生命周期本身就是服务的变更边界,
* 在同一注册表里观察它只会重复实现。 */
const install: InvariantInstaller = () => {}

export const apply = (ctx: Context): Promise<() => void> =>
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))

配套入口的约定:

  • export const name = <package>-invariantexport const inject = ['invariants'](缺服务就挂起等待)。
  • apply 调用 ctx.invariants.register(PACKAGE_NAME, install) 并返回 disposer。
  • 空 installer:没有合理运行时关系时,用空 installer + 包专属的前缀注释 No runtime invariant: ... 说明为何(纯工具、行为已被接口包观察的薄实现、仅组合包、二进制、持久化适配器、测试支持包都常见)。当 owner 获得可变状态或事件协议时,必须重新审视该说明。

加一个真实检查的配套入口

要给自己的插件加运行时健康断言,在包目录加一个 src/invariant.ts,让 applyinstaller.inject 声明所需服务,并在 installer 里用 fail() 报违规:

// my-plugin/src/invariant.ts
import type { Context } from '@deepseek-ai/cordis'
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'

const PACKAGE_NAME = '@scope/my-plugin' // 必须是精确 npm 包名

export const name = 'my-plugin-invariant'
export const inject = ['invariants']

const install: InvariantInstaller = {
inject: ['tools'], // installer 需要声明的服务
(ctx, fail) => {
ctx.on('tool/call', () => { /* 观察事件,若违规 */ fail('trace invariant broken') })
},
}

export const apply = (ctx: Context): Promise<() => void> =>
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))

关键判断:确认必需方法、插件名、注入、effect 或固定纯函数结果属于类型 / 加载 / 单元测试关注点,而非运行时不变量。 只有存在可观察的事件关系或相关可变数据关系时才装运行时检查。

五、DSH 对自己做持续自检的机制

DSH 把运行时契约的自检挂在这些包的配套入口上(README.md 的 companions 表):

配套入口检查
dsh-sessiondsh-agentdsh-scopedsh-agent-loop会话包含关系、调用/结果跟踪、agent 状态转换、inbox FIFO 守恒、作用域 subject、模型请求重建
dsh-llmdsh-llm-retrydsh-toolsdsh-system-prompt流语法、持久重试位置与边界、工具流水线阶段与冻结结果、权威提示词组装数据
dsh-compactiondsh-hook-protocoldsh-sandbox-policy持久 compaction 与钩子配对、compaction 元数据、沙箱 mode 词汇
dsh-fsdsh-subagentdsh-workflow文件系统事件身份、provider/子级配对、workflow 与 agent 生命周期身份
dsh-goaldsh-goal-round-driver持久 goal 来源/内容一致性、修订与生命周期转换、时间戳、依次获准的 round、重建的继续提示词
dsh-permission-presetsdsh-user-approval活动 preset 引用、审批 asked/decided 审计配对
dsh-jobsdsh-tool-todo任务快照生命周期/归属字段、持久整表 todo 结构
dsh-time-context持久时钟读数与会话"正在进行轮次/下一步骤位置/已用时间基线"一致;渲染时间可解析、不晚于其事件

关键区分:Session 自己负责不可变、表面有效的日志存储(每候选项一次无损 JSON 快照、校验引用源事件覆盖与位置替换、tool/result 替换限为一个当前结果的 content、深冻结、经不可变数组快照暴露);dsh-session 配套入口检查的是 Session 不负责的其余跨记录规则。这符合"每包对自己的 npm 契约负责、服务只是注册表"的定位。

六、挂载与组合

标准 agent 组合挂载服务和四个核心有状态配套入口;自定义组合为想检查的其他已加载包显式加配套入口。过滤器可以不改包入口就禁用/选择注册。

// packages/runtime-diagnostics/invariants/README.md(组合示例)
import type { Context } from '@deepseek-ai/cordis'
import InvariantRegistry from '@deepseek-ai/dsh-invariants'
import * as SessionInvariant from '@deepseek-ai/dsh-session/invariant'

declare const ctx: Context

ctx.plugin(InvariantRegistry, {
enabled: true,
package_allowlist: ['^@deepseek-ai/dsh-'],
package_blocklist: ['^@deepseek-ai/dsh-agent-loop$'],
})
ctx.plugin(SessionInvariant)

每个 owner 的根入口独立于诊断:单独加载服务不装任何产品检查;没有服务时加载配套入口会等它的 invariants 注入。根入口普通 / Vitest 拓扑挂载显式启用的服务 + 当前测试包的配套入口;聚焦套件覆盖可执行配套入口的合法与违规观测,一个穷尽拓扑挂载全部配套入口以证明注册与 dispose 接线。

七、验证与试一试

# 全仓最低归属校验:发现所有工作区包,拒绝生成标记、未说明的空 installer、
# 省略/忽略 reporter 的非空 installer、错误注册名,以及不完整的导出/发布/依赖/
# TS 引用/bundle 接线
cd ~/.dsh/source/current && pnpm run verify-package-invariants

# 单独看某个包根入口是否独立于诊断(应能加载而不装产品检查)
grep -rn "invariants" packages/runtime-diagnostics/invariants/package.json

下一步