fix(invariants): require justified empty companions

This commit is contained in:
Tianyi Cui
2026-07-20 18:39:37 +08:00
parent caaa1364ec
commit 3b7ce2e682
93 changed files with 162 additions and 99 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-19-package-owned-invariant-service.md: d7b77439c8cfdfdf2fc4f01e779a6d3c7f345457
2026-07-19-package-owned-invariant-service.zh.md: b553d137f9f06b57f24672bf6984d087ca99ceb2
2026-07-19-package-owned-invariant-service.md: 19512580db7b228fbf4c44109ccb07eeaca80b54
2026-07-19-package-owned-invariant-service.zh.md: f7c4bc4352e3ab1cd3a5aa7d2d0a64734bc22cb2

View File

@@ -18,7 +18,7 @@ Package ownership must also be exhaustive. Without a mechanical repository rule,
`@deepseek-ai/dsh-invariants` is a product-independent Cordis service plugin that registers `ctx.invariants`. It owns configuration, registration uniqueness, child-fiber lifecycle, and package-attributed failures. It imports no session, agent, scope, or agent-loop package and contains none of their checks.
Every workspace package publishes a `./invariant` companion plugin that registers its exact full npm name. A package with no relational check uses a generated ownership-only installer: it reserves the name through the real service boundary but installs no listeners. Package root entrypoints do not import or register diagnostics implicitly, so loading a root package does not change runtime checking or require the invariant service.
Every workspace package publishes a `./invariant` companion plugin that registers its exact full npm name. Checks protect observable relations in event streams or mutable runtime data; service method presence and plugin wiring are type, load, and repository-gate contracts rather than runtime invariants. A package with no plausible runtime relation uses an empty installer whose `No runtime invariant:` comment explains the absence instead of inventing a synthetic assertion. Package root entrypoints do not import or register diagnostics implicitly, so loading a root package does not change runtime checking or require the invariant service.
### Configuration and selection
@@ -64,9 +64,9 @@ The former functional-plugin entrypoint and one-argument `InvariantError` constr
| `@deepseek-ai/dsh-scope/invariant` | `@deepseek-ai/dsh-scope` | scoped-event carrier presence and subject consistency |
| `@deepseek-ai/dsh-agent-loop/invariant` | `@deepseek-ai/dsh-agent-loop` | model-request reconstruction |
These four owners contain stateful checks and focused tests. Every other package carries a generated baseline companion until it gains a relational assertion. Every companion is a separately bundled `./invariant` export with its own declarations and Loader-safe namespace plugin shape; the service package's own companion imports its local service type to avoid a self-dependency.
These four owners contain stateful checks and focused tests. Every other package carries a generated baseline companion until it gains a relational assertion or records why no runtime relation exists. Every companion is a separately bundled `./invariant` export with its own declarations and Loader-safe namespace plugin shape; the service package's own companion imports its local service type to avoid a self-dependency.
`verify-package-invariants` discovers every workspace package and rejects missing or stale companion source, foreign or unresolved registration names, missing `./invariant` exports or published files, missing invariant peer/development dependencies and project references, and bundle overrides that omit the companion entry. The generator writes only missing or marked ownership baselines, so a package-owned implementation is never replaced.
`verify-package-invariants` discovers every workspace package and rejects missing or stale companion source, foreign or unresolved registration names, unexplained empty installers, missing `./invariant` exports or published files, missing invariant peer/development dependencies and project references, and bundle overrides that omit the companion entry. The generator writes only missing or marked ownership baselines, so a package-owned implementation is never replaced.
### Scoped-event semantic map
@@ -91,12 +91,13 @@ Every Vitest configuration loads a test host that mounts an explicitly enabled s
- **Keep all checks in `dsh-invariants`.** Rejected because the registry would continue importing every checked product domain, owner changes would require central edits, and package tests would remain detached from the contracts they protect.
- **Let root package entrypoints register checks implicitly when `ctx.invariants` happens to exist.** Rejected because root behavior would depend on composition order and optional service presence, diagnostics could not be selected independently, and package loading would hide a registration effect outside an explicit companion.
- **Discover every `invariant.ts` file automatically at runtime.** Rejected because filesystem/package discovery is not a runtime ownership contract, makes bundled publication ambiguous, and cannot express explicit Cordis load order or dependency installation. Build-time generation, verification, and the test host may enumerate the source tree because they validate repository completeness rather than composing a shipped deployment.
- **Require a synthetic assertion from every package.** Rejected because checking method presence, plugin names, or fixed examples only turns repository wiring and unit-test facts into startup work. Packages without an observable event or mutable-data relation state that fact locally and keep an empty installer.
- **Validate allow/block entries against the currently loaded package set.** Rejected because a zero-match pattern can intentionally target a later or HMR-loaded contribution; current load order must not determine config validity.
## Consequences
- Product packages own and test their relational assertions while the service stays product-independent.
- Every package pays the small publication and dependency cost of an invariant companion, including packages whose generated baseline currently installs no listeners.
- Every package pays the small publication and dependency cost of an invariant companion, including packages whose justified empty installer has no listener.
- Standard compositions can disable all checks or select package names without changing their plugin tree.
- Explicit companion entries make diagnostic cost and ownership visible in Cordis config and package exports.
- One selected contribution adds one child fiber and its listener/state cost; filtered registrations retain only name ownership.

View File

@@ -18,7 +18,7 @@ Status: implemented
`@deepseek-ai/dsh-invariants` 是与产品无关的 Cordis 服务插件,注册 `ctx.invariants`。它只负责配置、注册唯一性、子 fiber 生命周期和带包归属的失败;不导入 session、agent、scope 或 agent-loop 包,也不包含这些包的检查。
工作区内的每个包都发布 `./invariant` 伴随插件,并注册自己完整且准确的 npm 包名。没有关系检查的包使用生成的仅声明所有权 installer:它通过真实服务边界占用包名,但不安装监听器。包的根入口不会隐式导入或注册诊断,因此加载根包不会改变运行时检查,也不要求不变式服务存在。
工作区内的每个包都发布 `./invariant` 伴随插件,并注册自己完整且准确的 npm 包名。检查保护事件流或可变运行时数据中可观察的关系;服务方法是否存在以及插件接线是否正确,属于类型、加载和仓库门禁契约,不是运行时不变式。没有合理运行时关系检查的包使用 installer,并通过 `No runtime invariant:` 注释解释原因,而不是编造断言。包的根入口不会隐式导入或注册诊断,因此加载根包不会改变运行时检查,也不要求不变式服务存在。
### 配置与选择
@@ -64,9 +64,9 @@ blocklist 匹配优先于 allowlist 匹配。每个条目都是区分大小写
| `@deepseek-ai/dsh-scope/invariant` | `@deepseek-ai/dsh-scope` | scoped event carrier 存在性与主体一致性 |
| `@deepseek-ai/dsh-agent-loop/invariant` | `@deepseek-ai/dsh-agent-loop` | 模型请求重建 |
这四个所有者保存有状态检查与聚焦测试。其他每个包在获得关系断言之前,都带有生成的基线伴随插件。每个伴随入口都是单独打包的 `./invariant` export具有独立声明和对 Loader 安全的命名空间插件形态;服务包自身的伴随插件导入本地服务类型,避免形成自依赖。
这四个所有者保存有状态检查与聚焦测试。其他每个包在获得关系断言或记录没有运行时关系的原因之前,都带有生成的基线伴随插件。每个伴随入口都是单独打包的 `./invariant` export具有独立声明和对 Loader 安全的命名空间插件形态;服务包自身的伴随插件导入本地服务类型,避免形成自依赖。
`verify-package-invariants` 会发现每个工作区包,并拒绝缺失或陈旧的伴随插件源码、外部或无法解析的注册名、缺失的 `./invariant` export 或发布文件、缺失的不变式对等依赖peer dependency、开发依赖及项目引用以及遗漏伴随入口的自定义构建配置。生成器只写入缺失或带生成标记的所有权基线因此绝不会替换包自行维护的实现。
`verify-package-invariants` 会发现每个工作区包,并拒绝缺失或陈旧的伴随插件源码、外部或无法解析的注册名、没有解释的空 installer、缺失的 `./invariant` export 或发布文件、缺失的不变式对等依赖peer dependency、开发依赖及项目引用以及遗漏伴随入口的自定义构建配置。生成器只写入缺失或带生成标记的所有权基线因此绝不会替换包自行维护的实现。
### Scoped event 语义映射
@@ -91,12 +91,13 @@ Workspace 约束识别独立的不变式 bundle包 exports、项目引用、
- **把所有检查保留在 `dsh-invariants`。** 不予采纳,因为注册包仍要导入所有被检查的产品领域,所有者变更仍需中央编辑,测试也继续远离被保护的契约。
- **当 `ctx.invariants` 恰好存在时,让根包入口隐式注册检查。** 不予采纳,因为根入口行为会依赖组合顺序与可选服务是否存在,诊断无法独立选择,而且包加载会隐藏一个不在显式伴随插件中的注册 effect。
- **在运行时自动发现所有 `invariant.ts` 文件。** 不予采纳,因为文件系统或包发现不是运行时所有权契约,会让 bundle 发布含义不清,也无法表达显式 Cordis 加载顺序或依赖安装。构建期生成与校验以及测试 host 可以枚举源码树,因为它们验证的是仓库完整性,而不是组合已发布的部署。
- **要求每个包提供合成断言。** 不予采纳,因为检查方法是否存在、插件名或固定样例,只会把仓库接线与单元测试事实变成启动时工作。没有可观察事件或可变数据关系的包会在本地说明原因,并保留空 installer。
- **根据当前已加载包集合验证 allow/block 条目。** 不予采纳,因为零匹配模式可能有意指向稍后加载或 HMR 加载的贡献;当前加载顺序不能决定配置有效性。
## 后果
- 产品包拥有并测试自己的关系断言,服务保持与产品无关。
- 每个包都要承担不变式伴随插件带来的少量发布与依赖成本,包括目前只安装生成基线、不添加监听器的包。
- 每个包都要承担不变式伴随插件带来的少量发布与依赖成本,包括使用有理由空 installer、不添加监听器的包。
- 标准组合无需改变插件树即可关闭全部检查或按包名选择。
- 显式伴随条目让诊断成本和所有权在 Cordis 配置与包 export 中可见。
- 每个选中贡献增加一个子 fiber 及其监听器和状态成本;被过滤注册只保留包名占用。