@buddhilive/dsh-invariants 0.1.2-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +166 -0
- package/README.zh.md +166 -0
- package/lib/index.js +123 -0
- package/lib/invariant.js +23 -0
- package/lib/types/index.d.ts +83 -0
- package/lib/types/invariant.d.ts +16 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/runtime-diagnostics/invariants/README.md
|
|
5
|
+
README.md: 72bb74cf5b9b367d0a29cb942370c4fa3d86e84c
|
|
6
|
+
README.zh.md: b7cffce5cf988bdfedce6b7b5de41d224182e086
|
package/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Runtime invariant checks for live compositions: the registry service that runs package-owned checks, for users and maintainers choosing, configuring, or debugging them."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @buddhilive/dsh-invariants
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
`dsh-invariants` runs package-owned runtime checks — invariants — inside a DeepSeek Harness composition: any package can ship a `./invariant` companion that verifies its own durable relationships (authoritative event streams and mutable snapshots) while the composition runs. Checks run automatically, and a failed check reports an `InvariantError` attributed to the package that owns the violated relationship. Choose it for compositions that want self-checking diagnostics with a global switch and package-name filters; the standard agent composition already mounts it with the four core companions, and loading the service alone installs no checks.
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Use this package](#use-this-package)
|
|
17
|
+
- [Understand the implementation](#understand-the-implementation)
|
|
18
|
+
- [Further Exploration](#further-exploration)
|
|
19
|
+
- [Model Experience](#model-experience)
|
|
20
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
21
|
+
- [Dev Note](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## Use this package
|
|
27
|
+
|
|
28
|
+
Mount the registry when a composition should verify its own runtime contracts, then decide which packages' checks run. The service exposes `ctx.invariants`; companions register checks under their package's exact npm name, and every failure carries the owning package name.
|
|
29
|
+
|
|
30
|
+
### When to use it
|
|
31
|
+
|
|
32
|
+
Use the registry for compositions that want live diagnostics. The standard agent composition in [`agent-spine-demo`](../../../packages/examples/agent-spine-demo/README.md) already mounts it with the four core stateful companions — `dsh-session`, `dsh-agent`, `dsh-scope`, and `dsh-agent-loop`. Custom compositions mount the registry and add companions for any other loaded package whose contracts they want checked. Loading the registry alone installs no checks: it ships no product checks of its own, so a composition that never mounts a companion observes no diagnostic behavior.
|
|
33
|
+
|
|
34
|
+
### Enabling checks and selecting packages
|
|
35
|
+
|
|
36
|
+
The registry is enabled by default and checks every registered package unless filters say otherwise. Use `enabled` as a global switch, `package_allowlist` to admit only named packages, and `package_blocklist` to exclude packages after allowlist matching — a blocklist match overrides an allowlist match. Patterns are case-sensitive JavaScript regular-expression sources (unanchored unless they supply `^` and `$`), and an invalid, blank, or duplicate entry fails service startup instead of being skipped.
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
- name: '@buddhilive/dsh-invariants'
|
|
40
|
+
config:
|
|
41
|
+
enabled: true
|
|
42
|
+
package_allowlist:
|
|
43
|
+
- '^@buddhilive/dsh-'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
| Field | Default | Meaning |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| `enabled` | `true` | Global switch for all registered checks |
|
|
49
|
+
| `package_allowlist` | `[]` | Regex sources admitting package names; empty admits all |
|
|
50
|
+
| `package_blocklist` | `[]` | Regex sources excluding package names after allowlist matching |
|
|
51
|
+
|
|
52
|
+
The generated [configuration catalog](../../../docs/config-catalog.md#buddhilivedsh-invariants) is the exhaustive source for every accepted field and its JSDoc.
|
|
53
|
+
|
|
54
|
+
### Which checks run
|
|
55
|
+
|
|
56
|
+
Each companion protects relationships its package owns, and a companion installs a check only for an observable event or mutable-data relationship — never for a service or method presence. The shipped executable companions cover:
|
|
57
|
+
|
|
58
|
+
| Companion | Checks |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `dsh-session`, `dsh-agent`, `dsh-scope`, `dsh-agent-loop` | Session log enclosure and call/result trace, agent-status transitions, scope-filtered dispatch subjects, loop-built request reconstruction |
|
|
61
|
+
| `dsh-llm`, `dsh-llm-retry`, `dsh-tools`, `dsh-system-prompt` | LLM stream grammar, retry-failure shape, tool-pipeline stage pairing and frozen results, prompt-assembly section names |
|
|
62
|
+
| `dsh-compaction`, `dsh-hook-protocol`, `dsh-sandbox-policy` | Compaction stream pairing, hook invocation/result pairing, sandbox mode values |
|
|
63
|
+
| `dsh-fs`, `dsh-subagent`, `dsh-workflow`, `dsh-tool-workflow` | Filesystem event identity, subagent provider and start/end pairing, workflow lifecycle identity, workflow record shape |
|
|
64
|
+
| `dsh-goal`, `dsh-goal-round-driver` | Durable goal-stream folds and reconstructed continuation prompts |
|
|
65
|
+
| `dsh-permission-presets`, `dsh-user-approval`, `dsh-commands` | Preset references to live presets, approval asked/decided pairing, command run/done pairing |
|
|
66
|
+
| `dsh-jobs`, `dsh-tool-todo`, `dsh-time-context` | Job snapshot field relationships, whole-list todo shape, durable clock readings |
|
|
67
|
+
| `dsh-credentials`, `dsh-settings`, `dsh-storage-domain`, `dsh-workspace` | Commit events against the live service or memory state, entity-cache mirroring |
|
|
68
|
+
| `dsh-agent-presets`, `dsh-session-title`, `dsh-plan-mode`, `dsh-schedule`, `dsh-webserver` | Preset mount placement, title source citation, plan-mode payload, schedule stream, route disposer symmetry |
|
|
69
|
+
| `dsh-client-hmr`, `dsh-client-modules`, `dsh-client-runtime` | Browser/node-half stat-watcher lifecycle, boot entry graph, slot mutation versioning |
|
|
70
|
+
|
|
71
|
+
Every other workspace package publishes an empty companion with a `No runtime invariant:` explanation of why nothing is checkable.
|
|
72
|
+
|
|
73
|
+
### Adding a companion to a custom composition
|
|
74
|
+
|
|
75
|
+
A companion is a normal plugin you mount beside the registry. It declares any services it needs and registers under its package's exact npm name; the registry joins its setup before the registration completes.
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
79
|
+
import InvariantRegistry from '@buddhilive/dsh-invariants'
|
|
80
|
+
import * as SessionInvariant from '@buddhilive/dsh-session/invariant'
|
|
81
|
+
|
|
82
|
+
declare const ctx: Context
|
|
83
|
+
|
|
84
|
+
ctx.plugin(InvariantRegistry, { enabled: true })
|
|
85
|
+
ctx.plugin(SessionInvariant)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### When a check fails
|
|
89
|
+
|
|
90
|
+
A violation throws an `InvariantError` from the context that reported it: it carries the stable code `INVARIANT`, the full npm `packageName` of the owning package, and a message prefixed `invariant violated by "<package>": …`. The failure is therefore attributable to a package without the registry importing any product code. A companion whose installer itself fails is disposed and its registration rolled back, so a broken check cannot leave partial listeners behind.
|
|
91
|
+
|
|
92
|
+
-----
|
|
93
|
+
|
|
94
|
+
<a id="understand-the-implementation"></a>
|
|
95
|
+
## Understand the implementation
|
|
96
|
+
|
|
97
|
+
<details>
|
|
98
|
+
<summary>Implementation internals — click to expand</summary>
|
|
99
|
+
|
|
100
|
+
This section explains the design behind the registry; the observable behavior is covered in [Use this package](#use-this-package). The full decision rationale lives in the [invariant-service Agent Note](../../../.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.md).
|
|
101
|
+
|
|
102
|
+
### Design philosophy
|
|
103
|
+
|
|
104
|
+
- **Product-independent registry.** The service imports no session, agent, scope, or agent-loop package and contains none of their checks; companions carry checks next to their owners.
|
|
105
|
+
- **Real relationships, not synthetic assertions.** A companion checks an event-stream or mutable-data relationship its package owns; confirming a method, plugin name, injection, or fixed pure result is a type, load, or unit-test concern, never a runtime invariant.
|
|
106
|
+
- **Registration reserves ownership.** A package name is reserved even when filters keep its installer inactive, so two plugins can never silently claim the same name.
|
|
107
|
+
- **Exhaustive wiring, mechanically enforced.** `pnpm run verify-package-invariants` rejects generated markers, unexplained empty installers, non-empty installers that omit or ignore the reporter, wrong registration names, and incomplete export, publication, dependency, or bundle wiring ([contracts note](../../../.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md)).
|
|
108
|
+
|
|
109
|
+
### Source map
|
|
110
|
+
|
|
111
|
+
| File | Role |
|
|
112
|
+
|---|---|
|
|
113
|
+
| [`src/index.ts`](src/index.ts) | Plugin entry: `Config` schema, `InvariantRegistry` service, selection, registration, `InvariantError` |
|
|
114
|
+
| [`src/invariant.ts`](src/invariant.ts) | This package's own companion: an empty installer explaining that registration ownership is the service's own mutation boundary |
|
|
115
|
+
|
|
116
|
+
### Selection and registration lifecycle
|
|
117
|
+
|
|
118
|
+
`register(packageName, installer)` reserves the full npm name and returns an effect-scoped disposer. An enabled installer runs in a dedicated child fiber; `installer.inject` declares the services that fiber may access, and synchronous or asynchronous completion is joined before registration succeeds. Failure disposes the child and releases the reservation atomically. The service owns every registration fiber, while the returned disposer also belongs to the companion fiber, so unloading either side removes listeners, trace state, and the reservation — a companion can reload and register the same name again without retained state. Session-backed companions rebuild their baseline from durable events; live-only companions observe operations that begin after reload.
|
|
119
|
+
|
|
120
|
+
</details>
|
|
121
|
+
|
|
122
|
+
-----
|
|
123
|
+
|
|
124
|
+
<a id="further-exploration"></a>
|
|
125
|
+
## Further Exploration
|
|
126
|
+
|
|
127
|
+
Read these pages when the package-level contract is not enough. They move from the generated service reference to the decision evidence and the group map.
|
|
128
|
+
|
|
129
|
+
- [Runtime invariants subsystem](../../../docs/subsystems/invariants.md) — the generated reference for `Config`, the installer, the service, and the companion contract.
|
|
130
|
+
- [Generated configuration catalog](../../../docs/config-catalog.md#buddhilivedsh-invariants) — every accepted config field and its source declaration.
|
|
131
|
+
- [Package-owned invariant service Agent Note](../../../.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.md) — why checks live beside their owners and the registry owns selection and lifecycle.
|
|
132
|
+
- [Invariant runtime contracts Agent Note](../../../.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md) — what a runtime invariant may assert and the mechanical gate that enforces companion wiring.
|
|
133
|
+
- [Runtime-diagnostics group map](../../README.md) — adjacent diagnostics packages.
|
|
134
|
+
|
|
135
|
+
-----
|
|
136
|
+
|
|
137
|
+
<a id="model-experience"></a>
|
|
138
|
+
## Model Experience
|
|
139
|
+
|
|
140
|
+
None, as the observer validates requests but never rewrites their context.
|
|
141
|
+
|
|
142
|
+
#### KV Cache effect
|
|
143
|
+
|
|
144
|
+
Checks observe assembled requests and durable state without mutating request content, so provider cache reuse is exactly what the underlying composition produces.
|
|
145
|
+
|
|
146
|
+
## Known Limitations and Deferred Work
|
|
147
|
+
|
|
148
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
These limits define when the registry is a poor fit or needs operational care. They are current package constraints, not a task backlog.
|
|
152
|
+
|
|
153
|
+
- **Filters are fixed for the service lifetime** — `enabled`, `package_allowlist`, and `package_blocklist` are compiled once at startup; changing them requires a Cordis plugin reload.
|
|
154
|
+
- **Live-only companions miss pre-reload operations** — a companion that only observes live operations cannot reconstruct operations that began before its own reload; session-backed companions rebuild their baseline from durable events.
|
|
155
|
+
- **Request reconstruction covers loop-built requests only** — the `dsh-agent-loop` companion reconstructs requests explicitly built by the loop; direct one-shot LLM calls remain outside that contract even when callers freeze them or attach a session id.
|
|
156
|
+
- **No checks without a companion** — the registry ships no product checks; a composition that mounts the service alone observes nothing.
|
|
157
|
+
|
|
158
|
+
<a id="dev-note"></a>
|
|
159
|
+
### Dev Note
|
|
160
|
+
|
|
161
|
+
<details>
|
|
162
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
163
|
+
|
|
164
|
+
None.
|
|
165
|
+
|
|
166
|
+
</details>
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "面向用户与维护者的运行时不变量检查说明:选择、配置或排查由包自有检查组成的注册表服务。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @buddhilive/dsh-invariants
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
`dsh-invariants` 在 DeepSeek Harness 组合中运行包自有的运行时检查——不变量:任何包都可以发布一个 `./invariant` 配套入口,在组合运行期间验证其自身的持久关系(权威事件流与可变快照)。检查自动运行,失败的检查会报告归因到拥有被违反关系的包的 `InvariantError`。需要带全局开关与包名过滤器的自检诊断时选择它;标准 agent 组合已挂载它及四个核心配套入口,而单独加载服务不会安装任何检查。
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
- [使用本包](#use-this-package)
|
|
17
|
+
- [理解实现](#understand-the-implementation)
|
|
18
|
+
- [进一步探索](#further-exploration)
|
|
19
|
+
- [模型体验](#model-experience)
|
|
20
|
+
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
|
21
|
+
- [开发备注](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## 使用本包
|
|
27
|
+
|
|
28
|
+
当组合需要验证自身运行时约定时挂载注册表,然后决定运行哪些包的检查。服务暴露 `ctx.invariants`;配套入口以其包的精确 npm 名称注册检查,每次失败都会携带所属包名。
|
|
29
|
+
|
|
30
|
+
### 何时使用
|
|
31
|
+
|
|
32
|
+
需要实时诊断的组合请使用注册表。[`agent-spine-demo`](../../../packages/examples/agent-spine-demo/README.zh.md) 中的标准 agent 组合已挂载它及四个核心有状态配套入口——`dsh-session`、`dsh-agent`、`dsh-scope` 与 `dsh-agent-loop`。自定义组合挂载注册表,并为任何其他已加载、且希望检查其约定的包添加配套入口。单独加载注册表不会安装任何检查:它自身不携带任何产品检查,因此从不挂载配套入口的组合不会观察到任何诊断行为。
|
|
33
|
+
|
|
34
|
+
### 启用检查与选择包
|
|
35
|
+
|
|
36
|
+
注册表默认启用,并在没有过滤器的情况下检查每个已注册的包。用 `enabled` 作全局开关,用 `package_allowlist` 只接纳指定包,用 `package_blocklist` 在 allowlist 匹配之后排除包——blocklist 匹配优先于 allowlist 匹配。模式是区分大小写的 JavaScript 正则表达式源(除非自带 `^` 与 `$`,否则不锚定);无效、空白或重复的条目会使服务启动失败,而不是被跳过。
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
- name: '@buddhilive/dsh-invariants'
|
|
40
|
+
config:
|
|
41
|
+
enabled: true
|
|
42
|
+
package_allowlist:
|
|
43
|
+
- '^@buddhilive/dsh-'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
| 字段 | 默认值 | 含义 |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| `enabled` | `true` | 所有已注册检查的全局开关 |
|
|
49
|
+
| `package_allowlist` | `[]` | 接纳包名的正则源;为空则全部接纳 |
|
|
50
|
+
| `package_blocklist` | `[]` | 在 allowlist 匹配之后排除包名的正则源 |
|
|
51
|
+
|
|
52
|
+
生成的[配置目录](../../../docs/config-catalog.zh.md#buddhilivedsh-invariants)是每个受支持字段及其 JSDoc 的穷尽式真源。
|
|
53
|
+
|
|
54
|
+
### 运行哪些检查
|
|
55
|
+
|
|
56
|
+
每个配套入口保护其包拥有的关系,且只为可观察的事件或可变数据关系安装检查——绝不针对服务或方法是否存在。已发布的可执行配套入口覆盖:
|
|
57
|
+
|
|
58
|
+
| 配套入口 | 检查 |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `dsh-session`、`dsh-agent`、`dsh-scope`、`dsh-agent-loop` | 会话日志包含关系与调用/结果跟踪、agent 状态转换、作用域过滤分发的主体、loop 构建请求重建 |
|
|
61
|
+
| `dsh-llm`、`dsh-llm-retry`、`dsh-tools`、`dsh-system-prompt` | LLM 流语法、重试失败形状、工具流水线阶段配对与冻结结果、提示词组装章节名 |
|
|
62
|
+
| `dsh-compaction`、`dsh-hook-protocol`、`dsh-sandbox-policy` | 压缩流配对、钩子调用/结果配对、沙箱 mode 值 |
|
|
63
|
+
| `dsh-fs`、`dsh-subagent`、`dsh-workflow`、`dsh-tool-workflow` | 文件系统事件身份、subagent 提供方与开始/结束配对、workflow 生命周期身份、workflow 记录形状 |
|
|
64
|
+
| `dsh-goal`、`dsh-goal-round-driver` | 持久 goal 流折叠与重建的继续提示词 |
|
|
65
|
+
| `dsh-permission-presets`、`dsh-user-approval`、`dsh-commands` | preset 引用指向活动 preset、审批询问/决定配对、命令运行/完成配对 |
|
|
66
|
+
| `dsh-jobs`、`dsh-tool-todo`、`dsh-time-context` | 任务快照字段关系、整表 todo 形状、持久时钟读数 |
|
|
67
|
+
| `dsh-credentials`、`dsh-settings`、`dsh-storage-domain`、`dsh-workspace` | 提交事件对照活动服务或内存状态、实体缓存镜像 |
|
|
68
|
+
| `dsh-agent-presets`、`dsh-session-title`、`dsh-plan-mode`、`dsh-schedule`、`dsh-webserver` | preset 挂载位置、标题来源引用、plan-mode 载荷、schedule 流、路由 disposer 对称性 |
|
|
69
|
+
| `dsh-client-hmr`、`dsh-client-modules`、`dsh-client-runtime` | 浏览器/node 侧 stat-watcher 生命周期、启动入口图、slot 变更版本化 |
|
|
70
|
+
|
|
71
|
+
其余每个工作区包都发布一个空配套入口,并以 `No runtime invariant:` 说明为何没有可检查的内容。
|
|
72
|
+
|
|
73
|
+
### 向自定义组合添加配套入口
|
|
74
|
+
|
|
75
|
+
配套入口就是挂载在注册表旁的普通插件。它声明所需的服务,并以其包的精确 npm 名称注册;注册表会先完成其设置再完成注册。
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
79
|
+
import InvariantRegistry from '@buddhilive/dsh-invariants'
|
|
80
|
+
import * as SessionInvariant from '@buddhilive/dsh-session/invariant'
|
|
81
|
+
|
|
82
|
+
declare const ctx: Context
|
|
83
|
+
|
|
84
|
+
ctx.plugin(InvariantRegistry, { enabled: true })
|
|
85
|
+
ctx.plugin(SessionInvariant)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 检查失败时
|
|
89
|
+
|
|
90
|
+
违规会从报告它的上下文抛出 `InvariantError`:它携带稳定的 `INVARIANT` 代码、所属包的完整 npm `packageName`,以及以 `invariant violated by "<package>": …` 开头的信息。失败因此可以归因到某个包,而注册表无需导入任何产品代码。installer 本身失败的配套入口会被释放,其注册会回滚,因此损坏的检查不会遗留部分监听器。
|
|
91
|
+
|
|
92
|
+
-----
|
|
93
|
+
|
|
94
|
+
<a id="understand-the-implementation"></a>
|
|
95
|
+
## 理解实现
|
|
96
|
+
|
|
97
|
+
<details>
|
|
98
|
+
<summary>实现细节——点击展开</summary>
|
|
99
|
+
|
|
100
|
+
本节解释注册表背后的设计;可观察行为已在[使用本包](#use-this-package)中说明。完整决策理由见[不变式服务 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.zh.md)。
|
|
101
|
+
|
|
102
|
+
### 设计理念
|
|
103
|
+
|
|
104
|
+
- **与产品无关的注册表。** 服务不导入任何 session、agent、scope 或 agent-loop 包,也不包含它们的检查;配套入口把检查放在其归属者旁边。
|
|
105
|
+
- **真实关系,而非人为断言。** 配套入口只检查其包拥有的事件流或可变数据关系;确认方法、插件名、注入或固定纯函数结果是类型、加载或单元测试关注点,绝不是运行时不变量。
|
|
106
|
+
- **注册保留归属。** 即使过滤器让 installer 保持非活动,包名也会被保留,因此两个插件永远不会静默认领同一个名字。
|
|
107
|
+
- **穷尽接线,机械强制。** `pnpm run verify-package-invariants` 拒绝生成标记、未说明的空 installer、省略或忽略 reporter 的非空 installer、错误注册名,以及不完整的导出、发布、依赖或 bundle 接线([约定笔记](../../../.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.zh.md))。
|
|
108
|
+
|
|
109
|
+
### 源码地图
|
|
110
|
+
|
|
111
|
+
| 文件 | 职责 |
|
|
112
|
+
|---|---|
|
|
113
|
+
| [`src/index.ts`](src/index.ts) | 插件入口:`Config` schema、`InvariantRegistry` 服务、选择、注册、`InvariantError` |
|
|
114
|
+
| [`src/invariant.ts`](src/invariant.ts) | 本包自己的配套入口:一个空 installer,说明注册归属本身就是服务的变更边界 |
|
|
115
|
+
|
|
116
|
+
### 选择与注册生命周期
|
|
117
|
+
|
|
118
|
+
`register(packageName, installer)` 保留完整 npm 名称并返回作用域化 disposer。启用的 installer 在专用子 fiber 中运行;`installer.inject` 声明该 fiber 可访问的服务,同步或异步完成都会在注册成功前被 join。失败会释放子级并原子地收回保留。服务拥有每个注册 fiber,返回的 disposer 同时属于配套 fiber,因此卸载任一侧都会移除监听器、跟踪状态与保留——配套入口可以重新加载并再次注册同一名称而不保留旧状态。由会话支撑的配套入口从持久事件重建 baseline;仅实时配套入口观察重新加载后开始的操作。
|
|
119
|
+
|
|
120
|
+
</details>
|
|
121
|
+
|
|
122
|
+
-----
|
|
123
|
+
|
|
124
|
+
<a id="further-exploration"></a>
|
|
125
|
+
## 进一步探索
|
|
126
|
+
|
|
127
|
+
当包级约定不够用时阅读以下页面。它们从生成的服务参考逐步进入决策证据与组地图。
|
|
128
|
+
|
|
129
|
+
- [运行时不变式子系统](../../../docs/subsystems/invariants.zh.md)——`Config`、installer、服务与配套入口约定的生成参考。
|
|
130
|
+
- [生成的配置目录](../../../docs/config-catalog.zh.md#buddhilivedsh-invariants)——每个受支持配置字段及其源声明。
|
|
131
|
+
- [包自有不变式服务 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.zh.md)——检查为何放在归属者旁边,以及注册表为何拥有选择与生命周期。
|
|
132
|
+
- [不变式运行时约定 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.zh.md)——运行时不变量可以断言什么,以及强制配套入口接线的机械门禁。
|
|
133
|
+
- [runtime-diagnostics 组地图](../../README.zh.md)——相邻的诊断包。
|
|
134
|
+
|
|
135
|
+
-----
|
|
136
|
+
|
|
137
|
+
<a id="model-experience"></a>
|
|
138
|
+
## 模型体验
|
|
139
|
+
|
|
140
|
+
无。作为观察者,本包验证请求但从不改写其上下文。
|
|
141
|
+
|
|
142
|
+
#### KV Cache 影响
|
|
143
|
+
|
|
144
|
+
检查只观察已组装的请求与持久状态,不修改请求内容,因此提供方缓存复用与底层组合产生的结果完全一致。
|
|
145
|
+
|
|
146
|
+
## 已知限制与延期工作
|
|
147
|
+
|
|
148
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
这些限制说明注册表何时不合适或需要特别运维。它们是当前包约束,不是任务积压。
|
|
152
|
+
|
|
153
|
+
- **过滤器在服务生命周期内固定**——`enabled`、`package_allowlist` 与 `package_blocklist` 在启动时编译一次;更改它们需要执行 Cordis 插件重新加载。
|
|
154
|
+
- **仅实时配套入口会遗漏重载前的操作**——只观察实时操作的配套入口无法重建自身重新加载前开始的操作;由会话支撑的配套入口从持久事件重建 baseline。
|
|
155
|
+
- **请求重建只覆盖 loop 构建的请求**——`dsh-agent-loop` 配套入口只重建 loop 显式构建的请求;直接一次性 LLM 调用即使由调用方冻结或附加会话 id,仍不在此约定内。
|
|
156
|
+
- **没有配套入口就没有检查**——注册表自身不携带产品检查;只挂载服务的组合观察不到任何行为。
|
|
157
|
+
|
|
158
|
+
<a id="dev-note"></a>
|
|
159
|
+
### 开发备注
|
|
160
|
+
|
|
161
|
+
<details>
|
|
162
|
+
<summary>维护者的工作上下文——点击展开</summary>
|
|
163
|
+
|
|
164
|
+
无。
|
|
165
|
+
|
|
166
|
+
</details>
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
2
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
|
+
//#region lib/types/index.js
|
|
4
|
+
/**
|
|
5
|
+
* Configurable registry for package-owned runtime invariant contributions.
|
|
6
|
+
* Every workspace package registers checks from a `./invariant` companion;
|
|
7
|
+
* ordinary package entrypoints stay independent of diagnostics.
|
|
8
|
+
*
|
|
9
|
+
* @module @buddhilive/dsh-invariants
|
|
10
|
+
*/
|
|
11
|
+
/** Thrown when a package-owned runtime invariant is violated. */
|
|
12
|
+
var InvariantError = class extends Error {
|
|
13
|
+
/** Stable machine-readable invariant failure code. */
|
|
14
|
+
code = "INVARIANT";
|
|
15
|
+
/** Full npm package name that owns the violated invariant. */
|
|
16
|
+
packageName;
|
|
17
|
+
/**
|
|
18
|
+
* Construct a package-attributed invariant failure.
|
|
19
|
+
* @param packageName - full npm package name that registered the check.
|
|
20
|
+
* @param message - violated contract, without the standard error prefix.
|
|
21
|
+
*/
|
|
22
|
+
constructor(packageName, message) {
|
|
23
|
+
super(`invariant violated by "${packageName}": ${message}`);
|
|
24
|
+
this.name = "InvariantError";
|
|
25
|
+
this.packageName = packageName;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
/** Compile and validate one package-filter list. */
|
|
29
|
+
function compilePatterns(field, values) {
|
|
30
|
+
const seen = /* @__PURE__ */ new Set();
|
|
31
|
+
return values.map((value) => {
|
|
32
|
+
if (value.length === 0 || value.trim() !== value) throw new Error(`invariants: ${field} entries must be non-blank and have no surrounding whitespace`);
|
|
33
|
+
if (seen.has(value)) throw new Error(`invariants: ${field} contains duplicate regex ${JSON.stringify(value)}`);
|
|
34
|
+
seen.add(value);
|
|
35
|
+
try {
|
|
36
|
+
return new RegExp(value);
|
|
37
|
+
} catch (cause) {
|
|
38
|
+
throw new Error(`invariants: ${field} contains invalid regex ${JSON.stringify(value)}`, { cause });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/** Package-owned invariant registry with global and regex-based selection. */
|
|
43
|
+
var InvariantRegistry = class extends Service {
|
|
44
|
+
static Config = z.object({
|
|
45
|
+
enabled: z.boolean().default(true),
|
|
46
|
+
package_allowlist: z.array(z.string()).default([]),
|
|
47
|
+
package_blocklist: z.array(z.string()).default([])
|
|
48
|
+
});
|
|
49
|
+
enabled;
|
|
50
|
+
ownerCtx;
|
|
51
|
+
packageAllowlist;
|
|
52
|
+
packageBlocklist;
|
|
53
|
+
registrations = /* @__PURE__ */ new Set();
|
|
54
|
+
/**
|
|
55
|
+
* Create and install the invariant registry.
|
|
56
|
+
* @param ctx - Cordis context that owns the service.
|
|
57
|
+
* @param config - global enablement and package-name regex filters.
|
|
58
|
+
*/
|
|
59
|
+
constructor(ctx, config = {}) {
|
|
60
|
+
super(ctx, "invariants");
|
|
61
|
+
this.ownerCtx = ctx;
|
|
62
|
+
this.enabled = config.enabled ?? true;
|
|
63
|
+
this.packageAllowlist = compilePatterns("package_allowlist", config.package_allowlist ?? []);
|
|
64
|
+
this.packageBlocklist = compilePatterns("package_blocklist", config.package_blocklist ?? []);
|
|
65
|
+
}
|
|
66
|
+
/** Return whether one full package name passes the configured filters. */
|
|
67
|
+
selected(packageName) {
|
|
68
|
+
if (!this.enabled) return false;
|
|
69
|
+
if (this.packageAllowlist.length > 0 && !this.packageAllowlist.some((pattern) => pattern.test(packageName))) return false;
|
|
70
|
+
return !this.packageBlocklist.some((pattern) => pattern.test(packageName));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Register one package's invariant installer. The package name is reserved
|
|
74
|
+
* even when filtering disables its checks. Enabled installers run in a child
|
|
75
|
+
* fiber; failure disposes that fiber and releases the reservation.
|
|
76
|
+
* @param packageName - full npm package name that owns the contribution.
|
|
77
|
+
* @param installer - listener or startup-check installer for the child context.
|
|
78
|
+
* @returns an effect-scoped disposer for the registration.
|
|
79
|
+
*/
|
|
80
|
+
register(packageName, installer) {
|
|
81
|
+
if (packageName.length === 0 || packageName.trim() !== packageName || /\s/.test(packageName)) throw new Error("invariants: packageName must be non-blank and contain no whitespace");
|
|
82
|
+
if (this.registrations.has(packageName)) throw new Error(`invariants: package "${packageName}" is already registered`);
|
|
83
|
+
const ctx = this.ownerCtx;
|
|
84
|
+
const registrations = this.registrations;
|
|
85
|
+
registrations.add(packageName);
|
|
86
|
+
let registration;
|
|
87
|
+
try {
|
|
88
|
+
registration = ctx.effect(async () => {
|
|
89
|
+
if (!this.selected(packageName)) return () => {
|
|
90
|
+
registrations.delete(packageName);
|
|
91
|
+
};
|
|
92
|
+
const installInvariant = (childCtx) => installer(childCtx, (message) => {
|
|
93
|
+
throw new InvariantError(packageName, message);
|
|
94
|
+
});
|
|
95
|
+
try {
|
|
96
|
+
const child = ctx.plugin(installer.inject === void 0 ? installInvariant : Object.assign(installInvariant, { inject: installer.inject }));
|
|
97
|
+
try {
|
|
98
|
+
await child;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
await child.dispose();
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
return async () => {
|
|
104
|
+
try {
|
|
105
|
+
await child.dispose();
|
|
106
|
+
} finally {
|
|
107
|
+
registrations.delete(packageName);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
} catch (error) {
|
|
111
|
+
registrations.delete(packageName);
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}, `invariants.register(${JSON.stringify(packageName)})`);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
registrations.delete(packageName);
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
return registration;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
//#endregion
|
|
123
|
+
export { InvariantError, InvariantRegistry, InvariantRegistry as default };
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/**
|
|
3
|
+
* Package-owned invariant companion for `@buddhilive/dsh-invariants`.
|
|
4
|
+
* @module @buddhilive/dsh-invariants/invariant
|
|
5
|
+
*/
|
|
6
|
+
const PACKAGE_NAME = "@buddhilive/dsh-invariants";
|
|
7
|
+
/** Cordis companion plugin name. */
|
|
8
|
+
const name = "invariants-invariant";
|
|
9
|
+
/** Service required before the companion can reserve package ownership. */
|
|
10
|
+
const inject = ["invariants"];
|
|
11
|
+
/**
|
|
12
|
+
* No runtime invariant: registration ownership and child lifecycle are the service's mutation
|
|
13
|
+
* boundary itself; observing them from the same registry would only duplicate its implementation.
|
|
14
|
+
*/
|
|
15
|
+
const install = () => {};
|
|
16
|
+
/**
|
|
17
|
+
* Register this package's invariant companion.
|
|
18
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
19
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
20
|
+
*/
|
|
21
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
22
|
+
//#endregion
|
|
23
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configurable registry for package-owned runtime invariant contributions.
|
|
3
|
+
* Every workspace package registers checks from a `./invariant` companion;
|
|
4
|
+
* ordinary package entrypoints stay independent of diagnostics.
|
|
5
|
+
*
|
|
6
|
+
* @module @buddhilive/dsh-invariants
|
|
7
|
+
*/
|
|
8
|
+
import { Context, Service } from '@deepseek-ai/cordis';
|
|
9
|
+
import type { Inject } from '@deepseek-ai/cordis';
|
|
10
|
+
import type Schema from '@deepseek-ai/schemastery';
|
|
11
|
+
/** Runtime invariant selection configured on the service plugin. */
|
|
12
|
+
export interface Config {
|
|
13
|
+
/** Global switch; defaults to `true`. */
|
|
14
|
+
readonly enabled?: boolean;
|
|
15
|
+
/** Case-sensitive JavaScript regex sources that admit package names; empty admits all. */
|
|
16
|
+
readonly package_allowlist?: string[];
|
|
17
|
+
/** Case-sensitive JavaScript regex sources that exclude package names after allowlist matching. */
|
|
18
|
+
readonly package_blocklist?: string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Throw a package-attributed invariant failure.
|
|
22
|
+
* @param message - violated package contract without the standard prefix.
|
|
23
|
+
* @returns never because reporting a violation throws.
|
|
24
|
+
*/
|
|
25
|
+
export type InvariantFailure = (message: string) => never;
|
|
26
|
+
/** Install one package's checks into the registration's child context. */
|
|
27
|
+
export interface InvariantInstaller {
|
|
28
|
+
/**
|
|
29
|
+
* Install the package contribution.
|
|
30
|
+
* @param ctx - child context owned by this invariant registration.
|
|
31
|
+
* @param fail - reporter bound to the registering package name.
|
|
32
|
+
* @returns nothing, or a promise settling after asynchronous checks finish.
|
|
33
|
+
*/
|
|
34
|
+
(ctx: Context, fail: InvariantFailure): void | Promise<void>;
|
|
35
|
+
/** Services the child installer fiber may access. */
|
|
36
|
+
readonly inject?: Inject;
|
|
37
|
+
}
|
|
38
|
+
/** Thrown when a package-owned runtime invariant is violated. */
|
|
39
|
+
export declare class InvariantError extends Error {
|
|
40
|
+
/** Stable machine-readable invariant failure code. */
|
|
41
|
+
readonly code: "INVARIANT";
|
|
42
|
+
/** Full npm package name that owns the violated invariant. */
|
|
43
|
+
readonly packageName: string;
|
|
44
|
+
/**
|
|
45
|
+
* Construct a package-attributed invariant failure.
|
|
46
|
+
* @param packageName - full npm package name that registered the check.
|
|
47
|
+
* @param message - violated contract, without the standard error prefix.
|
|
48
|
+
*/
|
|
49
|
+
constructor(packageName: string, message: string);
|
|
50
|
+
}
|
|
51
|
+
declare module '@deepseek-ai/cordis' {
|
|
52
|
+
interface Context {
|
|
53
|
+
invariants: InvariantRegistry;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Package-owned invariant registry with global and regex-based selection. */
|
|
57
|
+
export declare class InvariantRegistry extends Service {
|
|
58
|
+
static Config: Schema<Config>;
|
|
59
|
+
private readonly enabled;
|
|
60
|
+
private readonly ownerCtx;
|
|
61
|
+
private readonly packageAllowlist;
|
|
62
|
+
private readonly packageBlocklist;
|
|
63
|
+
private readonly registrations;
|
|
64
|
+
/**
|
|
65
|
+
* Create and install the invariant registry.
|
|
66
|
+
* @param ctx - Cordis context that owns the service.
|
|
67
|
+
* @param config - global enablement and package-name regex filters.
|
|
68
|
+
*/
|
|
69
|
+
constructor(ctx: Context, config?: Config);
|
|
70
|
+
/** Return whether one full package name passes the configured filters. */
|
|
71
|
+
private selected;
|
|
72
|
+
/**
|
|
73
|
+
* Register one package's invariant installer. The package name is reserved
|
|
74
|
+
* even when filtering disables its checks. Enabled installers run in a child
|
|
75
|
+
* fiber; failure disposes that fiber and releases the reservation.
|
|
76
|
+
* @param packageName - full npm package name that owns the contribution.
|
|
77
|
+
* @param installer - listener or startup-check installer for the child context.
|
|
78
|
+
* @returns an effect-scoped disposer for the registration.
|
|
79
|
+
*/
|
|
80
|
+
register(packageName: string, installer: InvariantInstaller): () => void;
|
|
81
|
+
}
|
|
82
|
+
export default InvariantRegistry;
|
|
83
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@buddhilive/dsh-invariants`.
|
|
3
|
+
* @module @buddhilive/dsh-invariants/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "invariants-invariant";
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Register this package's invariant companion.
|
|
12
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
13
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
14
|
+
*/
|
|
15
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
16
|
+
//# sourceMappingURL=invariant.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@buddhilive/dsh-invariants",
|
|
3
|
+
"description": "Registry service for package-owned DeepSeek Harness runtime invariants",
|
|
4
|
+
"version": "0.1.2-alpha.3",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Buddhilive/buddhi-ai-harness.git",
|
|
11
|
+
"directory": "packages/runtime-diagnostics/invariants"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./invariant": {
|
|
22
|
+
"types": "./lib/types/invariant.d.ts",
|
|
23
|
+
"default": "./lib/invariant.js"
|
|
24
|
+
},
|
|
25
|
+
"./src/*": "./src/*",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"lib/index.js",
|
|
30
|
+
"lib/invariant.js",
|
|
31
|
+
"lib/types/**/*.d.ts"
|
|
32
|
+
],
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@deepseek-ai/cordis": "^4.0.2"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@deepseek-ai/schemastery": "^3.18.2"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@deepseek-ai/cordis": "^4.0.2"
|
|
42
|
+
}
|
|
43
|
+
}
|