@deepseek-ai/dsh-storage 0.1.1-rc.2 → 0.1.2-alpha.2

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/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/storage/storage/README.md
5
- README.md: bf827220afe0d8b1cbc53b3a12e6d0034004e1d2
6
- README.zh.md: fb52c96e38c17b76ef0e6b70a79177911a088fb7
5
+ README.md: d81dbe8f99ae7594b8f33a389deec77259bbd610
6
+ README.zh.md: 9d1078782e3e09d3f9b8925e87c9b2f40085daca
package/README.md CHANGED
@@ -1,22 +1,117 @@
1
+ ---
2
+ description: "Storage hub (ctx.storage) for compositions and maintainers choosing, mounting, or debugging named storage backends and data-form facilities."
3
+ kind: "package-reference"
4
+ ---
5
+
1
6
  # @deepseek-ai/dsh-storage
2
7
 
3
8
  English | [中文](README.zh.md)
4
9
 
5
- Storage hub (`ctx.storage`) for non-session data: a named backend registry plus mounted data-form facilities. The hub performs no IO itself — backends own media, and data forms own semantics. The [storage family overview](../README.md) maps those packages; the [domain KV storage Agent Note](../../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md) records the design rationale.
10
+ ## Summary
11
+
12
+ Mount `dsh-storage` to give a composition durable, non-session storage: it is the hub where backends and data forms connect, so host packages can read and write typed records through `ctx.storageDomain`. The hub performs no IO itself — backends own the medium (a file-tree root, a database file), and data forms own semantics — so a composition pairs it with one or more backends and the domain form. It is optional and host-side only: it registers no tools, injects no prompts, and writes no session events, so the model and the agent loop never see it. Choose it whenever any package in the composition needs durable data that is not a session event log; a composition with no such data can omit the whole group.
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
+ Use this package to give a composition durable, non-session storage: mount it together with backend and domain-form packages, and host-side packages read and write validated records through `ctx.storageDomain`. The hub itself adds nothing observable — it is the meeting point that makes the family work, and everything below is what a composition gets from it.
29
+
30
+ ### When to use it
31
+
32
+ Mount the hub whenever any package in the composition persists data that is not a session event log — workspace records, session sidecars. It is required by the domain form and both shipped backends, so the storage rows of a composition are `storage` plus a backend plus `storage-domain`. Skip the whole group when nothing stores such data; the agent loop never needs it.
33
+
34
+ ### A minimal composition
35
+
36
+ ```yaml
37
+ - name: '@deepseek-ai/dsh-storage'
38
+ - name: '@deepseek-ai/dsh-storage-json'
39
+ config:
40
+ root: /var/lib/dsh/data
41
+ - name: '@deepseek-ai/dsh-storage-domain'
42
+ config:
43
+ backend: json
44
+ ```
45
+
46
+ With these rows, the `json` backend registers itself and the `domain` data form mounts; a consumer such as `dsh-workspace` then opens its domain over the routed backend and reads and writes records through `ctx.storageDomain`. Several backends can stay mounted side by side; which backend serves which domain is the domain form's configuration, never a hub-wide choice.
47
+
48
+ ### What you get
49
+
50
+ - A mounted backend resolves by name, so a composition with both shipped backends can route each domain to either medium by configuration.
51
+ - A mounted data form resolves as `ctx.storage.<form>`; the domain form is additionally served directly as `ctx.storageDomain`.
52
+ - Misconfiguration fails loud with a stable `StorageError` code instead of silently deferring: an unknown backend name, a form read before its owner mounts, or a duplicate registration all throw.
53
+
54
+ ### Failures and recovery
55
+
56
+ - `backend-not-found` — the domain form routes to a backend that is not mounted; add the backend package. The form waits for every configured backend to register, so row order is not a failure mode.
57
+ - `form-not-mounted` — a consumer reads `ctx.storage.domain` before `dsh-storage-domain` loads; mount the domain row before the consumer.
58
+ - `duplicate-backend` / `duplicate-mount` — the same name or form registers twice; that is a composition bug and fails loud.
59
+
60
+ -----
61
+
62
+ <a id="understand-the-implementation"></a>
63
+ ## Understand the implementation
64
+
65
+ <details>
66
+ <summary>Implementation internals — click to expand</summary>
6
67
 
7
- ## Shape
68
+ The hub is a pure registration table with two faces, designed so backends and data forms stay replaceable without the hub knowing their internals.
8
69
 
9
- - `ctx.storage.backend` — name → backend table. Multiple backends stay mounted side by side (`json`, `sqlite`); which backend serves a consumer is that consumer's configuration (the domain layer's route table), never a hub-global choice. `register()` returns the disposer; duplicate names and unknown lookups fail loud.
10
- - `ctx.storage.mount(form, facility)` / `ctx.storage.form(form)` — data-form mounting. `StorageForms` is merge-extensible; the domain layer merges `domain` and is reached as `ctx.storage.domain`.
11
- - A backend owns one medium and exposes the data-shape facets it supports. `kv` is the current facet; `src/backend.ts` owns its exact contract.
70
+ ### Design concept
12
71
 
72
+ - **Backends own media, data forms own semantics.** The hub never performs IO; it only holds the name → backend table and the form-name → facility map. Backend packages register their medium owner, data-form packages mount their facility, and neither needs the other's details.
73
+ - **Multiple backends stay side by side.** Which backend serves which consumer is the consumer's configuration (the domain form's route table), never a hub-global either-or.
74
+ - **Registration and mounting are effects.** `register()` and `mount()` return disposers; disposal removes only that registration's contribution, and does not close the backend — the owning plugin closes it after unregistering.
75
+ - **Activation cannot race registration.** Each backend plugin also publishes a lifecycle-only service key (`storage.backend.<name>`); form providers inject those keys, so the domain form activates only after every configured backend registers, while callers still resolve backends by name through the hub.
76
+
77
+ ### The backend contract
78
+
79
+ [`src/backend.ts`](src/backend.ts) is the normative contract for backend implementers, checked clause by clause by the shared conformance suite in `tests/contract.ts`. A backend owns exactly one medium and exposes optional data-shape facets; `kv` is the only facet, and opening a unit yields a versioned, globally-singleton schema handle whose single calls are atomic and durable once resolved. Unit and table names must match `UNIT_NAME_RE`; record keys are arbitrary strings that never reach file paths. The unit does not serialize concurrent writes — ordering belongs to the caller — and a stored version differing from the descriptor rejects `version-mismatch` (no migration).
80
+
81
+ ### Source map
82
+
83
+ | File | Role |
84
+ |---|---|
85
+ | [`src/index.ts`](src/index.ts) | Plugin entry: `Storage` service, form mounting, `StorageForms` map |
86
+ | [`src/registry.ts`](src/registry.ts) | `BackendRegistry`: name → backend table, registration disposers |
87
+ | [`src/backend.ts`](src/backend.ts) | The backend contract: facets, units, `UNIT_NAME_RE` |
88
+ | [`src/error.ts`](src/error.ts) | `StorageError` codes shared by the hub and every backend |
89
+ | [`src/invariant.ts`](src/invariant.ts) | Invariant companion (no runtime invariant: a pure registration table) |
90
+ | [`tests/contract.ts`](tests/contract.ts) | The shared conformance suite run against each backend |
91
+
92
+ </details>
93
+
94
+ -----
95
+
96
+ <a id="further-exploration"></a>
97
+ ## Further Exploration
98
+
99
+ Read these pages when the hub's view is not enough: the subsystem reference is the authoritative contract, and the Agent Note records the family design and its deferred work.
100
+
101
+ - [Storage subsystem](../../../docs/subsystems/storage.md) — the backend contract, domain semantics, change events, and generated API.
102
+ - [Storage package map](../README.md) — the family's packages and their repository position.
103
+ - [domain KV storage Agent Note](../../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md) — the design behind the hub, the domain form, and the session-backend migration.
104
+
105
+ -----
106
+
107
+ <a id="model-experience"></a>
13
108
  ## Model Experience
14
109
 
15
110
  ### Backend and form registrations
16
111
 
17
112
  #### What the model sees
18
113
 
19
- Nothing. `ctx.storage` is a host-side registration table; the hub registers no tools, injects no prompts, and writes no session events.
114
+ Nothing. `ctx.storage` is a host-side registration table: the hub registers no tools, injects no prompts, and writes no session events, so no request field ever carries this package's data.
20
115
 
21
116
  #### Token effect
22
117
 
@@ -28,5 +123,20 @@ Independent of live requests: the hub never touches a request prefix, so it cann
28
123
 
29
124
  ## Known Limitations and Deferred Work
30
125
 
31
- - **`kv` is the only data shape** — backends currently have one facet to implement.
32
- - **Forms resolve lazily** — reading `ctx.storage.domain` before the domain plugin mounts throws `form-not-mounted`; assemblies order plugins accordingly (misconfiguration fails loud rather than silently deferring).
126
+ <a id="known-limitations-and-deferred-work"></a>
127
+
128
+
129
+ These limits define what the hub cannot do. They are current package constraints, not a task backlog.
130
+
131
+ - **`kv` is the only data shape** — a backend implements one facet; the `log` facet for session event logs is deferred to the session-backend migration ([Agent Note](../../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md)).
132
+ - **Forms resolve lazily** — reading `ctx.storage.domain` before the domain plugin mounts throws `form-not-mounted`; assemblies order plugins accordingly rather than silently deferring.
133
+
134
+ <a id="dev-note"></a>
135
+ ### Dev Note
136
+
137
+ <details>
138
+ <summary>Working context for maintainers — click to expand</summary>
139
+
140
+ None.
141
+
142
+ </details>
package/README.zh.md CHANGED
@@ -1,22 +1,117 @@
1
+ ---
2
+ description: "存储枢纽(ctx.storage):面向选择、挂载或排查具名存储后端与数据形式设施的组合方与维护者。"
3
+ kind: "package-reference"
4
+ ---
5
+
1
6
  # @deepseek-ai/dsh-storage
2
7
 
3
8
  [English](README.md) | 中文
4
9
 
5
- 非会话数据的存储中心(`ctx.storage`):具名后端注册表加已挂载的数据形式设施。中心自身不执行 IO:后端拥有介质,数据形式拥有语义。[存储家族概述](../README.zh.md)列出了这些包;[领域 KV 存储 Agent Note](../../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.zh.md)记录了设计理由。
10
+ ## 概述
11
+
12
+ 挂载 `dsh-storage` 即可为组合提供持久的非会话存储:它是后端与数据形式交汇的枢纽(hub),宿主包因此可以通过 `ctx.storageDomain` 读写类型化记录。枢纽自身不执行任何 IO——后端拥有介质(一个文件树根目录、一个数据库文件),数据形式拥有语义——因此组合会把它与一个或多个后端以及领域数据形式一起挂载。它是可选项,且只面向宿主侧:不注册工具、不注入提示词,也不写入会话事件,因此模型与 agent loop(智能体循环)永远不会看到它。只要组合中任何包需要会话事件日志以外的持久数据就选择它;没有任何此类数据的组合可以省略整个组。
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.storageDomain` 读写经过校验的记录。枢纽自身不增加任何可观察行为——它是让整个家族运转起来的交汇点——以下内容就是组合从它得到的一切。
29
+
30
+ ### 何时使用
31
+
32
+ 当组合中任何包需要持久化会话事件日志以外的数据——工作区记录、会话伴随数据——时就挂载枢纽。领域数据形式与两个内置后端都依赖它,因此组合的存储行是 `storage` 加一个后端加 `storage-domain`。没有任何此类数据的组合可以省略整个组;agent loop 永远不需要它。
33
+
34
+ ### 最小组合
35
+
36
+ ```yaml
37
+ - name: '@deepseek-ai/dsh-storage'
38
+ - name: '@deepseek-ai/dsh-storage-json'
39
+ config:
40
+ root: /var/lib/dsh/data
41
+ - name: '@deepseek-ai/dsh-storage-domain'
42
+ config:
43
+ backend: json
44
+ ```
45
+
46
+ 这些行加载后,`json` 后端注册自身、`domain` 数据形式挂载;诸如 `dsh-workspace` 之类的消费方随后在已路由后端上打开自己的领域,并通过 `ctx.storageDomain` 读写记录。多个后端可以并排保持挂载;哪个后端服务哪个领域由领域数据形式的配置决定,绝非枢纽的全局选择。
47
+
48
+ ### 你能得到什么
49
+
50
+ - 已挂载的后端按名称解析,因此同时挂载两个内置后端的组合可以把每个领域按配置路由到任一种介质。
51
+ - 已挂载的数据形式解析为 `ctx.storage.<form>`;领域数据形式还直接以 `ctx.storageDomain` 对外服务。
52
+ - 错误配置会以稳定的 `StorageError` 代码明确报错,而不是静默推迟:未知的后端名称、在其所有者挂载前读取数据形式、或重复注册都会抛出异常。
53
+
54
+ ### 失败与恢复
55
+
56
+ - `backend-not-found`——领域数据形式路由到未挂载的后端;请添加后端包。数据形式会等待所有已配置后端注册,因此行序不会造成失败。
57
+ - `form-not-mounted`——消费方在 `dsh-storage-domain` 加载前读取 `ctx.storage.domain`;请把领域行放在消费方之前。
58
+ - `duplicate-backend`/`duplicate-mount`——同一名称或形式注册了两次;这是组合错误,会明确报错。
59
+
60
+ -----
61
+
62
+ <a id="understand-the-implementation"></a>
63
+ ## 理解实现
64
+
65
+ <details>
66
+ <summary>实现细节——点击展开</summary>
6
67
 
7
- ## 结构
68
+ 枢纽是一张纯注册表,拥有两个面,设计目标是后端与数据形式可以独立替换,而枢纽无需了解它们的内部实现。
8
69
 
9
- - `ctx.storage.backend`:名称 → 后端表。多个后端并排保持挂载(`json`、`sqlite`);为消费方提供服务的后端由该消费方自身的配置决定(领域层的路由表),绝非中心的全局选择。`register()` 返回资源释放函数;注册重复名称或查找未知名称时都会明确报错。
10
- - `ctx.storage.mount(form, facility)`/`ctx.storage.form(form)`:数据形式挂载。`StorageForms` 可通过合并扩展;领域层合并 `domain`,并通过 `ctx.storage.domain` 访问。
11
- - 后端拥有一种介质,并公开其支持的数据形状**分面**。当前分面为 `kv`;`src/backend.ts` 负责定义其确切约定。
70
+ ### 设计理念
12
71
 
72
+ - **后端拥有介质,数据形式拥有语义。** 枢纽从不执行 IO;它只持有名称 → 后端表和形式名 → 设施表。后端包注册其介质所有者,数据形式包挂载其设施,双方都不需要对方的细节。
73
+ - **多个后端并排共存。** 哪个后端服务哪个消费方由消费方自身的配置决定(领域数据形式的路由表),绝非枢纽全局的二选一。
74
+ - **注册与挂载都是 effect。** `register()` 与 `mount()` 返回资源释放函数;释放只移除该次注册的贡献,且不会关闭后端——由所属插件在注销后关闭。
75
+ - **激活不会与注册竞争。** 每个后端插件还会发布一个仅用于生命周期的服务键(`storage.backend.<name>`);数据形式提供方注入这些键,因此领域数据形式只在所有已配置后端注册后激活,而调用方仍通过枢纽按名称解析后端。
76
+
77
+ ### 后端约定
78
+
79
+ [`src/backend.ts`](src/backend.ts) 是后端实现者的规范性约定,由 `tests/contract.ts` 中的共享一致性套件逐条款检查。一个后端只拥有一种介质,并暴露可选的数据形状分面;`kv` 是唯一的分面,打开单元即可获得一个带版本、全局单例的 schema 句柄,其每次单独调用都是原子的,且 resolve 后即已持久。单元名与表名必须匹配 `UNIT_NAME_RE`;记录键是任意字符串,绝不进入文件路径。单元不对并发写入做串行化——顺序由调用方负责——介质上记录的版本与描述符不同时拒绝 `version-mismatch`(不做迁移)。
80
+
81
+ ### 源码地图
82
+
83
+ | 文件 | 职责 |
84
+ |---|---|
85
+ | [`src/index.ts`](src/index.ts) | 插件入口:`Storage` 服务、数据形式挂载、`StorageForms` 表 |
86
+ | [`src/registry.ts`](src/registry.ts) | `BackendRegistry`:名称 → 后端表、注册资源释放函数 |
87
+ | [`src/backend.ts`](src/backend.ts) | 后端约定:分面、单元、`UNIT_NAME_RE` |
88
+ | [`src/error.ts`](src/error.ts) | 枢纽与每个后端共享的 `StorageError` 代码 |
89
+ | [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件(无运行时不变式:纯注册表) |
90
+ | [`tests/contract.ts`](tests/contract.ts) | 针对每个后端运行的共享一致性套件 |
91
+
92
+ </details>
93
+
94
+ -----
95
+
96
+ <a id="further-exploration"></a>
97
+ ## 进一步探索
98
+
99
+ 当枢纽视角不够用时阅读以下页面:子系统参考是权威约定,Agent Note 记录了家族设计与延期工作。
100
+
101
+ - [存储子系统](../../../docs/subsystems/storage.zh.md)——后端约定、领域语义、变更事件与生成的 API。
102
+ - [存储包映射](../README.zh.md)——家族的各包及其在仓库中的位置。
103
+ - [领域 KV 存储 Agent Note](../../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.zh.md)——枢纽、领域数据形式与会话后端迁移背后的设计。
104
+
105
+ -----
106
+
107
+ <a id="model-experience"></a>
13
108
  ## 模型体验
14
109
 
15
110
  ### 后端与形式注册
16
111
 
17
- #### 模型看到的内容
112
+ #### 模型看到什么
18
113
 
19
- 无。`ctx.storage` 是主机侧注册表;中心不注册工具、不注入提示词,也不写入会话事件。
114
+ 无。`ctx.storage` 是宿主侧注册表:枢纽不注册工具、不注入提示词,也不写入会话事件,因此任何请求字段都不会携带本包的数据。
20
115
 
21
116
  #### Token 影响
22
117
 
@@ -24,9 +119,24 @@
24
119
 
25
120
  #### KV Cache 影响
26
121
 
27
- 与实时请求相互独立:中心绝不触碰请求前缀,因此无法使提供方缓存复用失效。
122
+ 与实时请求相互独立:枢纽绝不触碰请求前缀,因此无法使提供方缓存复用失效。
123
+
124
+ ## 已知限制与延期工作
125
+
126
+ <a id="known-limitations-and-deferred-work"></a>
127
+
128
+
129
+ 这些限制定义了枢纽不能做什么。它们是当前包约束,不是任务积压。
130
+
131
+ - **`kv` 是唯一的数据形状**——后端只实现一个分面;面向会话事件日志的 `log` 分面被推迟到会话后端迁移([Agent Note](../../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.zh.md))。
132
+ - **数据形式按需解析**——在领域插件挂载前读取 `ctx.storage.domain` 会抛出 `form-not-mounted`;组装会按相应顺序排列插件,而不是静默推迟。
133
+
134
+ <a id="dev-note"></a>
135
+ ### 开发备注
136
+
137
+ <details>
138
+ <summary>维护者的工作上下文——点击展开</summary>
28
139
 
29
- ## 已知限制与暂缓事项
140
+ 无。
30
141
 
31
- - **`kv` 是唯一的数据形状**:后端目前只有一个分面需要实现。
32
- - **数据形式按需解析**:在领域插件挂载前读取 `ctx.storage.domain` 会抛出 `form-not-mounted`;组装会按相应顺序排列插件(错误配置会明确报错,而不是静默推迟处理)。
142
+ </details>
@@ -47,6 +47,15 @@ export interface KvUnitDescriptor {
47
47
  readonly tables: readonly string[];
48
48
  /** Whether this unit carries the global singleton slot. */
49
49
  readonly hasGlobal: boolean;
50
+ /**
51
+ * Medium layout. `single` (the default) keeps the whole unit in one
52
+ * document; `per-record` keeps each record in its own document, so a unit
53
+ * whose records are large or sparse never rewrites the rest on one write,
54
+ * and a version bump discards stale records instead of rejecting the whole
55
+ * unit. Backends that only serve one layout accept the other's units as
56
+ * foreign documents.
57
+ */
58
+ readonly layout?: 'single' | 'per-record';
50
59
  }
51
60
  /**
52
61
  * One opened unit. Values are opaque JSON to this layer: no schema, no
@@ -70,7 +79,9 @@ export interface KvUnit {
70
79
  /**
71
80
  * Upsert one record durably. Overwrite semantics: an existing key is replaced.
72
81
  * @param table - Declared table name.
73
- * @param key - Record key; any string is safe (keys never reach file paths).
82
+ * @param key - Record key. In the `per-record` layout a key becomes a path
83
+ * segment and must match `[a-zA-Z0-9_-]+` (an unsafe key rejects); in the
84
+ * `single` layout keys stay opaque.
74
85
  * @param value - Opaque JSON-serializable record.
75
86
  * @returns resolution after durability.
76
87
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-storage",
3
3
  "description": "Storage hub (ctx.storage): named backend registry plus mounted data-form facilities for the DeepSeek Harness",
4
- "version": "0.1.1-rc.2",
4
+ "version": "0.1.2-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -32,11 +32,11 @@
32
32
  ],
33
33
  "license": "MIT",
34
34
  "peerDependencies": {
35
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
36
- "@deepseek-ai/cordis": "^4.0.1"
35
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2",
36
+ "@deepseek-ai/cordis": "^4.0.2"
37
37
  },
38
38
  "devDependencies": {
39
- "@deepseek-ai/cordis": "^4.0.1",
40
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2"
39
+ "@deepseek-ai/cordis": "^4.0.2",
40
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2"
41
41
  }
42
42
  }