@deepseek-ai/dsh-settings 0.1.6-alpha.2 → 0.1.7-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/settings/settings/README.md
5
- README.md: eabc7bf6f18b7d062462e02b63aa6c0d994755fc
6
- README.zh.md: afa6d621a3a51b855937b7705a55017bd21f04b6
5
+ README.md: a44dd50a4614401afa6e05bb5d55313a8b833732
6
+ README.zh.md: 0f7df0dec04159eaca4e143e10b4a8e6f81452a0
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: "The user-settings service for plugin authors and maintainers registering configurable namespaces, reading resolved values, or wiring configuration surfaces."
2
+ description: "Inspect and edit live plugin configuration through Config-derived forms."
3
3
  kind: "package-reference"
4
4
  ---
5
5
 
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
9
9
 
10
10
  ## Summary
11
11
 
12
- Use this package when users must change a plugin's configuration at runtime without restarting or rereading `cordis.yml`. Each namespace combines schema defaults, deployment configuration, and user overrides; readers receive a deep-frozen resolved snapshot and can observe committed changes. Writes affect only user overrides, are serialized per namespace, and may reject stale revisions instead of overwriting newer changes. Durable runtime edits require configured settings storage; without it, the plugin continues with its composed configuration.
12
+ Edit fields that plugins declare with `.volatile()` and inspect their effective values. Forms identify each plugin by its profile entry id, preserve secret values, and refuse stale writes. Changes persist through the active profile’s Cordis patch.
13
13
 
14
14
  ## Table of Contents
15
15
 
@@ -20,62 +20,23 @@ Use this package when users must change a plugin's configuration at runtime with
20
20
  - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
21
  - [Dev Note](#dev-note)
22
22
 
23
- -----
24
-
25
23
  <a id="use-this-package"></a>
26
24
  ## Use this package
27
25
 
28
- Plugins and configuration surfaces use `ctx.settings` to read and change configuration at runtime. The common path: mount a provider, register a namespace with a schema, read and watch the resolved value, and write through the owner scope.
29
-
30
- ### When to choose it
31
-
32
- Choose settings when a plugin's configuration should be changeable at runtime — by the user editing a document or by a configuration UI — without restarting or re-reading `cordis.yml`. It fits when several plugins each own one configuration namespace, and when a configuration surface must render schemas, mark user-overridden fields, and persist edits. It is unnecessary when configuration is fixed at load time: without a provider mounted, nothing changes and configuration stays exactly as composed.
33
-
34
- ### Mounting a provider
35
-
36
- The service stores nothing by itself; mount a provider such as the shipped file-backed one:
26
+ Mount this plugin with Loader and [config-editor](../../boot/config-editor/README.md). The base bundle provides this composition.
37
27
 
38
28
  ```yaml
39
- - name: '@deepseek-ai/dsh-settings-file'
40
- config:
41
- path: /absolute/path/to/settings.yaml
42
- ```
43
-
44
- `ctx.settings` appears once the provider is live. The provider README owns the full configuration surface; the generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-settings-file) lists every accepted field.
45
-
46
- ### Registering a namespace
47
-
48
- A plugin registers its own namespace with a schemastery schema, optionally supplying the composition entry as the `base` layer so the resolved value starts from what the deployment already configured:
49
-
50
- ```text
51
- const scope = ctx.settings.register('ui-theme', ThemeSchema, {
52
- base: config, // composition entry config; the user layer resolves above it
53
- })
54
- const theme = scope.get() // deep-frozen resolved snapshot
55
- scope.update({ density: 'compact' }) // merges into the user section and persists
29
+ - id: settings
30
+ name: '@deepseek-ai/dsh-settings'
56
31
  ```
57
32
 
58
- Literal namespace arguments are checked by TypeScript against the lowercase letter, digit, and hyphen grammar; dynamically supplied strings receive the same validation at runtime. `ctx.settings.installSection(owner, ns, schema, entry, hooks)` packages the optional-service wiring for a consumer plugin: while a settings service exists it registers the namespace with the plugin's composition entry as `base`; when the service goes away the plugin falls back to its entry config and keeps working exactly as composed.
59
-
60
- ### Reading and observing values
61
-
62
- `get(ns)` returns the resolved value as a deep-frozen snapshot, `undefined` while the namespace is unregistered. `watch(callback)` invokes the callback after each committed change with `(next, prev)`: invocations of one callback run one at a time in commit order, and failures are contained and logged, so a slow or throwing observer never blocks or breaks other observers.
63
-
64
- ### Writing values
65
-
66
- `update(ns, patch)` deep-merges a plain-object patch into the user section only — never into `base` — validates the resolved candidate, persists through the provider, then commits. `replace(ns, section)` sets the user section wholesale, which is the removal/reset path: `replace({})` re-inherits `base` and schema defaults. `mutate(ns, ops)` applies ordered `{ op: 'set' | 'unset', path }` edits to the section as it stands when the write reaches the front of the queue — the removal path for a caller holding an incomplete (for example redacted) view, because rebuilding a section from what a wire surface returned and replacing it wholesale would delete every field the wire never sent back.
67
-
68
- Every write rejects non-JSON-compatible data (a `Date`, `Map`, `BigInt`, non-finite number, or circular reference fails with its `$`-rooted path before anything persists), rejects on a read-only provider, and accepts an optional `expectedRevision`: pass back the `revision` from a descriptor, and a namespace that moved past it refuses the write with `SettingsConflictError` instead of overwriting the writer that landed first.
69
-
70
- ### Configuration surfaces
33
+ This plugin has no configuration fields. Forms expose only volatile fields from active, uniquely addressed profile entries. Ordinary configuration remains editable through Cordis configuration files.
71
34
 
72
- `describe()` returns one descriptor per registered namespace: the serialized schema, the resolved value, the detached `base` and `user` layers (a field's presence in `user` marks it user-overridden), the effect timing, and the namespace's revision. Pass `redactSecrets: true` on every wire surface: it strips `role('secret')` fields from every layer and enumerates them as `{ path, set }` slots so a page can render write-only inputs without ever receiving a secret. `documentPath` and `prepareDocument()` expose the provider's user-editable file to a native editor when one exists.
35
+ Once the Loader has settled every entry after Settings starts, a `settings.yaml` left in the harness home by earlier releases is imported once: each section is written into the entry of the same id (`ui-developer-tools` → `ui-settings`, `ui-onboarding` → `ui-settings-general`, `shell` → the platform's shell executor entry), the file is renamed to `settings.yaml.imported` before the first write, and a section the running composition rejects is logged and stays only in the renamed file.
73
36
 
74
- ### Events and failures
37
+ Reset restores the value beneath the profile override, including schema defaults. Home patches and command-line overlays take precedence; a form write that they would override is refused.
75
38
 
76
- `settings/updated (ns, next, prev, source)` fires after each committed change — an in-process write (`source: 'update'`) or an externally observed edit (`source: 'provider'`) — and never when the resolved value is deep-equal. `settings/document-updated (ns, revision)` fires whenever the raw user section changed, even when the resolved value did not, which is what an open editor needs to learn that a field went from inherited to overridden. A stored section the schema rejects keeps the namespace's last good value and warns on reload; at registration the same failure rejects the registration itself.
77
-
78
- -----
39
+ Each form reports `autoGenerate`, enabled by default, for clients that build pages from the schema; no shipped client does so yet. A plugin that ships its own page registers `configure({ auto: false }, ctx.fiber)` as an effect inside an optional `ctx.inject(['settings'], ...)` child from `apply`: the child names the plugin fiber the policy belongs to, a late-loading or replaced Settings service picks the policy up, and the business plugin runs without Settings. The policy does not remove configuration reads or writes.
79
40
 
80
41
  <a id="understand-the-implementation"></a>
81
42
  ## Understand the implementation
@@ -83,72 +44,34 @@ Every write rejects non-JSON-compatible data (a `Date`, `Map`, `BigInt`, non-fin
83
44
  <details>
84
45
  <summary>Implementation internals — click to expand</summary>
85
46
 
86
- This section explains the design decisions behind the service and points at the code that realizes them; the observable behavior is fully covered in [Use this package](#use-this-package).
87
-
88
- ### Design philosophy
89
-
90
- - **Layered resolution, single user layer.** A namespace's value is schema defaults, then the registrant's composition `base`, then the user document section; writes touch only the user layer, so `replace({})` is a true reset.
91
- - **Commits are deep-equal gated.** `settings/updated` fires only when the resolved value moved; the raw-section event is separate because configuration surfaces must also learn "inherited became overridden".
92
- - **Writes are queued and revision-checked.** Per-namespace write queues serialize in call order, and `expectedRevision` is judged at the front of the queue, where the service can tell a fresh writer from one holding a stale snapshot.
93
- - **Observer and listener failures are contained.** Watcher invocations and event fan-out isolate sync throws and async rejections so one broken observer cannot wedge commits or a provider's reload loop; `INVARIANT`-coded failures rethrow after every listener ran.
94
- - **Registrations are fiber effects.** Registering a namespace is an effect on the calling plugin's fiber: disposing that fiber removes the namespace and its observers.
95
-
96
- ### Source map
97
-
98
- | File | Role |
99
- |---|---|
100
- | [`src/index.ts`](src/index.ts) | Service Definition: namespace validation, registration, resolution, write queue, describe/redaction, events, `installSection` |
101
- | [`src/redact.ts`](src/redact.ts) | `redactSecrets` walker: strip `role('secret')` fields and enumerate their slots |
102
- | [`src/types.ts`](src/types.ts) | Client-safe type surface: event declarations, `SettingsNamespace`, `SettingsUpdateSource` |
103
- | [`src/invariant.ts`](src/invariant.ts) | Invariant companion: `settings/updated` fires only for a registered namespace, only on a resolved-value change, with the authoritative value |
104
-
105
- ### Resolution and write paths
106
-
107
- Each write snapshots its input at call time (detaching and validating JSON-shaped data), then queues on the namespace's serialized chain. At the front of the queue the service re-reads the section as it stands, checks `expectedRevision`, merges/replaces/mutates, resolves and validates the candidate through the schema plus the owner's optional `validate`, persists through the provider, and only then commits and emits. A write whose registrant fiber was disposed mid-flight still reaches storage but commits and notifies nobody; teardown refuses new writes and drains queued writes and started watcher invocations before disposal completes.
108
-
109
- ### Change detection and events
47
+ The [form projection](src/schema.ts) strips runtime references and ordinary fields. The [service](src/index.ts) supplies revisioned descriptors and validates edits against the full plugin Config before delegating persistence. Business plugins read their Config references directly.
110
48
 
111
- `commit` compares resolved values with the seam's `deepEqualJson` predicate and fans `settings/updated` out one listener at a time. `bumpRevision` compares raw sections and emits `settings/document-updated` with the new revision; it runs independently of the resolved-value check. Both fan-outs contain listener failures the same way.
112
-
113
- ### Client-safe types
114
-
115
- The `./types` subpath export holds the event declarations together with the `SettingsNamespace` and `SettingsUpdateSource` types their signatures name, and the package root re-exports those types. A consumer outside the Host compilation face reads the exact signature the Host emits instead of restating it.
49
+ Secret roles are redacted from values, inherited values, profile overrides, and schema defaults; clients receive presence markers. Path edits preserve fields a client did not receive. No invariant companion is published because the service projects Loader configuration rather than maintaining an independent authoritative value.
116
50
 
117
51
  </details>
118
52
 
119
- -----
120
-
121
53
  <a id="further-exploration"></a>
122
54
  ## Further Exploration
123
55
 
124
- Read these pages when the service-level contract is not enough. They move from the shared subsystem vocabulary to the shipped provider and the capability architecture.
125
-
126
- - [Settings subsystem reference](../../../docs/subsystems/settings.md) — namespaces, registration, owner scope, descriptors, change commits, and the generated cordis surface.
127
- - [File-backed settings provider](../settings-file/README.md) — the shipped YAML/JSON provider: configuration, hot reload, comment-preserving writes.
128
- - [Settings package map](../README.md) — the two packages of the user-settings capability and their roles.
129
- - [Capability seams](../../../docs/capability-seams.md) — the Service Definition / Service Provider / Consumer split this service follows.
130
-
131
- -----
56
+ - [Settings reference](../../../docs/subsystems/settings.md) — form values and mutations.
57
+ - [Volatile configuration](../../../vendor/loader/README.md) — reference lifetime and notifications.
58
+ - [Configuration editor](../../boot/config-editor/README.md) — persistence and reload ordering.
132
59
 
133
60
  <a id="model-experience"></a>
134
61
  ## Model Experience
135
62
 
136
- Indirectly, through consumer plugins, which own any model-facing content fed by a settings value; the service only stores and resolves user settings and registers nothing model-facing itself.
63
+ Indirectly, through configuration values consumed by model-facing plugins.
137
64
 
138
65
  #### KV Cache effect
139
66
 
140
- No direct invalidation; a consumer that folds a settings value into the request prefix owns that change.
67
+ Consumers that change request prefixes determine cache effects.
141
68
 
142
69
  ## Known Limitations and Deferred Work
143
70
 
144
71
  <a id="known-limitations-and-deferred-work"></a>
145
72
 
146
-
147
- These limits define when the service is a poor fit or needs special care. They are current package constraints, not a task backlog.
148
-
149
- - **Single user layer** — resolution knows schema defaults, one composition `base`, and one user document; it does not record which layer supplied each resolved value.
150
- - **`redactSecrets` is not a proven wire boundary** — the walker follows `object`/`dict`/`array` containers, so a `role('secret')` field reachable only through a union, intersection, or transform is returned verbatim with an empty `secrets` list, and the serialized schema carries a secret field's default to every client. Neither case is rejected; a schema whose secrets are not reachable through the walked containers must not be registered on a wire-exposed namespace. A fail-closed `describeForWire()` — one that refuses a schema it cannot prove safe and sanitizes the serialized envelope and error text — is the deferred answer.
151
- - **Cross-process concurrency is provider-defined** — the service serializes writes per namespace in-process only; concurrent processes converge by provider behavior (the file provider read-modify-writes under a writer lock, so namespaces survive concurrent writers and same-namespace conflicts resolve last-write-wins).
73
+ - Nested Includes own separate configurations and are not editable through the active profile’s form.
74
+ - Field-level resets restore inherited values; they cannot delete a value supplied by a lower configuration layer. Unsetting an array index removes that element.
152
75
 
153
76
  <a id="dev-note"></a>
154
77
  ### Dev Note
@@ -156,6 +79,6 @@ These limits define when the service is a poor fit or needs special care. They a
156
79
  <details>
157
80
  <summary>Working context for maintainers — click to expand</summary>
158
81
 
159
- This Dev Note is working context for maintainers: open design directions that are not decided. It is explicitly non-authoritative — shipped behavior, limits, and accepted rationale live in the sections above and the package code. Open directions, tracked in code TODOs: rename the public `ns` parameter to `namespace` across the API, provider contract, implementations, tests, and consumers; deactivate watchers and await their tails on registration disposal so callbacks cannot outlive the registrant fiber; re-resolve a replacement registration from its persisted section so an in-flight old write cannot leave it stale; and use property-safe object construction so valid JSON keys such as `__proto__` remain own data. The fail-closed `describeForWire()` sanitizer is the deferred answer to the redaction limitation above.
82
+ None.
160
83
 
161
84
  </details>
package/README.zh.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: "面向插件作者与维护者的用户设置服务:注册可配置 namespace、读取解析值或接入配置界面。"
2
+ description: "通过 Config 派生表单查看和编辑插件的即时配置。"
3
3
  kind: "package-reference"
4
4
  ---
5
5
 
@@ -9,153 +9,76 @@ kind: "package-reference"
9
9
 
10
10
  ## 概述
11
11
 
12
- 当用户需要在运行时修改插件配置,而无需重启或重新读取 `cordis.yml` 时,请使用本包。每个 namespace 合并 schema 默认值、部署配置与用户覆盖;读取方会得到深冻结的解析值快照,并可观察已提交的变更。写入只影响用户覆盖、按 namespace 串行执行,并可拒绝陈旧 revision,避免覆盖较新的变更。持久化运行时编辑需要先配置设置存储;否则插件仍可继续使用组合配置。
12
+ 编辑插件通过 `.volatile()` 声明的字段,并查看其实际值。表单以 profile 条目 id 标识插件、保留秘密值,并拒绝过期写入。更改通过当前 profile 的 Cordis patch 持久化。
13
13
 
14
14
  ## 目录
15
15
 
16
- - [使用本包](#use-this-package)
16
+ - [使用此包](#use-this-package)
17
17
  - [理解实现](#understand-the-implementation)
18
18
  - [进一步探索](#further-exploration)
19
19
  - [模型体验](#model-experience)
20
- - [已知限制与延期工作](#known-limitations-and-deferred-work)
20
+ - [已知限制与延后工作](#known-limitations-and-deferred-work)
21
21
  - [开发备注](#dev-note)
22
22
 
23
- -----
24
-
25
23
  <a id="use-this-package"></a>
26
- ## 使用本包
27
-
28
- 插件与配置界面通过 `ctx.settings` 在运行时读取并修改配置。常用路径:挂载提供方、用 schema 注册 namespace、读取并观察解析值,并通过 owner scope 写入。
29
-
30
- ### 何时选择
31
-
32
- 当插件的配置需要在运行时可变——用户编辑文档或配置界面修改——且无需重启或重读 `cordis.yml` 时,选择设置服务。它适合多个插件各拥有一个配置 namespace、以及配置界面需要渲染 schema、标记用户覆盖字段并持久化编辑的场景。当配置在加载时固定则没有必要:没有挂载提供方时一切照旧,配置保持组合原样。
24
+ ## 使用此包
33
25
 
34
- ### 挂载提供方
35
-
36
- 服务本身不存储任何内容;请挂载一个提供方,例如随附的文件型提供方:
26
+ 将此插件与 Loader 和 [config-editor](../../boot/config-editor/README.zh.md) 一起挂载。基础组合包提供此组合。
37
27
 
38
28
  ```yaml
39
- - name: '@deepseek-ai/dsh-settings-file'
40
- config:
41
- path: /absolute/path/to/settings.yaml
42
- ```
43
-
44
- 提供方上线后 `ctx.settings` 即出现。完整配置面由提供方 README 负责;生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-settings-file)列出每个受支持字段。
45
-
46
- ### 注册 namespace
47
-
48
- 插件用 schemastery schema 注册自己的 namespace,并可选地把组合配置作为 `base` 层传入,让解析值从部署已配置的内容起步:
49
-
50
- ```text
51
- const scope = ctx.settings.register('ui-theme', ThemeSchema, {
52
- base: config, // composition entry config; the user layer resolves above it
53
- })
54
- const theme = scope.get() // deep-frozen resolved snapshot
55
- scope.update({ density: 'compact' }) // merges into the user section and persists
29
+ - id: settings
30
+ name: '@deepseek-ai/dsh-settings'
56
31
  ```
57
32
 
58
- TypeScript 会按小写字母、数字与连字符文法检查字面量 namespace 参数;运行时动态传入的字符串接受相同校验。`ctx.settings.installSection(owner, ns, schema, entry, hooks)` 为消费方插件封装可选服务接线:只要设置服务存在,它就用插件的组合配置作为 `base` 注册 namespace;服务消失时插件回退到组合配置,行为与原先完全一致。
59
-
60
- ### 读取与观察值
61
-
62
- `get(ns)` 以深冻结快照返回解析值,namespace 未注册时为 `undefined`。`watch(callback)` 在每次已提交变更后以 `(next, prev)` 调用回调:同一回调的调用按提交顺序逐个执行,异常被隔离并记入日志,因此慢或抛错的观察者绝不会阻塞或破坏其他观察者。
63
-
64
- ### 写入值
65
-
66
- `update(ns, patch)` 把普通对象 patch 深合并进用户分节——绝不进 `base`——校验解析候选值、经提供方持久化后提交。`replace(ns, section)` 整体替换用户分节,是删除/重置路径:`replace({})` 重新继承 `base` 与 schema 默认值。`mutate(ns, ops)` 在写入排到队首那一刻的分节上按序施加 `{ op: 'set' | 'unset', path }` 编辑——这是持有不完整(例如脱敏后)视图的调用方的删除路径,因为按协议接口返回的内容重建分节再整体替换,会删掉协议从未回传的每个字段。
67
-
68
- 每次写入都会拒绝与 JSON 不兼容的数据(`Date`、`Map`、`BigInt`、非有限数或循环引用会在任何内容持久化前以 `$` 为根的路径报错)、拒绝只读提供方上的写入,并可接受可选的 `expectedRevision`:把 descriptor 中的 `revision` 传回,namespace 已越过该值时写入会被 `SettingsConflictError` 拒绝,而不是覆盖先完成写入的一方。
69
-
70
- ### 配置界面
33
+ 此插件没有配置字段。表单只展示活动且可唯一定位的 profile 条目中的 volatile 字段。普通配置仍通过 Cordis 配置文件编辑。
71
34
 
72
- `describe()` 为每个已注册 namespace 返回一条 descriptor:序列化 schema、解析值、分离的 `base` 与 `user` 层(字段出现在 `user` 中即标记为用户覆盖)、生效时机与 namespace 的 revision。每个协议接口都必须传入 `redactSecrets: true`:它从每一层剥离 `role('secret')` 字段,并把它们枚举为 `{ path, set }` slot,让页面可以渲染只写输入而不接触任何机密。`documentPath` 与 `prepareDocument()` 在提供方拥有用户可编辑文件时把它暴露给原生编辑器。
35
+ Settings 启动后、Loader 完成所有条目的加载时,早期版本留在 harness home 中的 `settings.yaml` 会被导入一次:每个 section 写入同名条目(`ui-developer-tools` → `ui-settings`、`ui-onboarding` → `ui-settings-general`、`shell` → 当前平台的 shell 执行器条目),文件在第一次写入前改名为 `settings.yaml.imported`,被当前组合拒绝的 section 会记录日志并只保留在改名后的文件中。
73
36
 
74
- ### 事件与失败
37
+ 重置恢复 profile 覆盖层以下的值,包括 schema 默认值。Home patch 和命令行 overlay 优先级更高;表单写入若会被它们覆盖,则被拒绝。
75
38
 
76
- `settings/updated (ns, next, prev, source)` 在每次已提交变更后触发——进程内写入(`source: 'update'`)或外部观察到的编辑(`source: 'provider'`)——解析值深相等时绝不触发。`settings/document-updated (ns, revision)` 在原始用户分节发生变化时触发,即使解析值没有变——已打开的编辑器正需要它来得知字段从继承变为覆盖。schema 拒绝的存量分节在重载时保留该 namespace 的最后可用值并告警;注册时同样的失败会直接拒绝注册。
77
-
78
- -----
39
+ 每个表单报告 `autoGenerate`(默认开启),供按 schema 生成页面的客户端使用;目前没有已发布的客户端这样做。自带页面的插件在 `apply` 中于可选的 `ctx.inject(['settings'], ...)` 子级内以 effect 注册 `configure({ auto: false }, ctx.fiber)`:子级指明策略所属的插件 fiber,迟加载或被替换的 Settings 服务也会采用该策略,业务插件无需 Settings 即可运行。策略不移除配置读写。
79
40
 
80
41
  <a id="understand-the-implementation"></a>
81
42
  ## 理解实现
82
43
 
83
44
  <details>
84
- <summary>实现细节——点击展开</summary>
85
-
86
- 本节解释服务背后的设计决策并指出实现它们的代码位置;可观察行为已在[使用本包](#use-this-package)中完整说明。
87
-
88
- ### 设计理念
89
-
90
- - **分层解析,单一用户层。** namespace 的值依次为 schema 默认值、注册方的组合 `base`、用户文档分节;写入只触碰用户层,因此 `replace({})` 是真正的重置。
91
- - **提交以深相等为门槛。** 只有解析值变化时 `settings/updated` 才触发;原始分节事件独立存在,因为配置界面还必须得知「继承变成了覆盖」。
92
- - **写入排队并做 revision 检查。** 每个 namespace 的写队列按调用顺序串行,`expectedRevision` 在队首判断——那里服务才能分辨持有新鲜快照的写入方与持有陈旧快照的写入方。
93
- - **观察者与监听器异常被隔离。** watcher 调用与事件扇出隔离同步抛出与异步拒绝,一个坏掉的观察者不会卡死提交或提供方的重载循环;`INVARIANT` 编码的失败在所有监听器执行完后重新抛出。
94
- - **注册是 fiber 上的 effect。** 注册 namespace 是调用方插件 fiber 上的 effect:dispose(资源释放)该 fiber 即移除 namespace 及其观察者。
95
-
96
- ### 源码地图
97
-
98
- | 文件 | 职责 |
99
- |---|---|
100
- | [`src/index.ts`](src/index.ts) | Service Definition:namespace 校验、注册、解析、写队列、describe/脱敏、事件、`installSection` |
101
- | [`src/redact.ts`](src/redact.ts) | `redactSecrets` 遍历器:剥离 `role('secret')` 字段并枚举其 slot |
102
- | [`src/types.ts`](src/types.ts) | 客户端安全类型面:事件声明、`SettingsNamespace`、`SettingsUpdateSource` |
103
- | [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件:`settings/updated` 只对已注册 namespace、只在解析值变化时、且携带权威值触发 |
104
-
105
- ### 解析与写入路径
106
-
107
- 每次写入都在调用时对输入做快照(分离并校验 JSON 形状的数据),然后排上该 namespace 的串行链。在队首,服务按当前状态重读分节、检查 `expectedRevision`、合并/替换/编辑、经 schema 与 owner 的可选 `validate` 解析并校验候选值、经提供方持久化,然后才提交并发出事件。registrant fiber 在写入途中被 dispose 的写入仍到达存储,但不会提交、也不会通知任何人;卸载先拒绝新写入,并排干排队写入与已启动的 watcher 调用后才完成。
45
+ <summary>实现内部细节——点击展开</summary>
108
46
 
109
- ### 变更检测与事件
47
+ [表单投影](src/schema.ts) 去除运行时引用和普通字段。[服务](src/index.ts) 提供带修订号的描述符,在委托持久化前按完整插件 Config 验证编辑。业务插件直接读取自己的 Config 引用。
110
48
 
111
- `commit` 用 seam 的 `deepEqualJson` 谓词比较解析值,并逐监听器扇出 `settings/updated`。`bumpRevision` 比较原始分节并携带新 revision 发出 `settings/document-updated`;它与解析值检查相互独立。两个扇出以相同方式隔离监听器异常。
112
-
113
- ### 客户端安全类型
114
-
115
- `./types` 子路径出口持有事件声明及其签名点名的 `SettingsNamespace`、`SettingsUpdateSource` 类型,包根继续 re-export 这些类型。于是 Host 编译面之外的消费方读到的正是 Host 发射的那一份签名,而不必再写一遍。
49
+ 秘密角色从实际值、继承值、profile 覆盖值和 schema 默认值中隐藏;客户端接收存在性标记。路径编辑保留客户端未收到的字段。此服务投影 Loader 配置,不维护独立的权威值,因此不发布 invariant companion。
116
50
 
117
51
  </details>
118
52
 
119
- -----
120
-
121
53
  <a id="further-exploration"></a>
122
54
  ## 进一步探索
123
55
 
124
- 当服务级约定不够用时阅读以下页面。它们从共享子系统词汇逐步进入随附提供方与能力架构。
125
-
126
- - [设置子系统参考](../../../docs/subsystems/settings.zh.md)——namespace、注册、owner scope、descriptor、变更提交与生成的 cordis 接口面。
127
- - [文件型设置提供方](../settings-file/README.zh.md)——随附的 YAML/JSON 提供方:配置、热重载、保留注释的写入。
128
- - [设置包映射](../README.zh.md)——用户设置能力的两个包及其角色。
129
- - [能力 seam](../../../docs/capability-seams.zh.md)——本服务遵循的 Service Definition / Service Provider / Consumer 拆分。
130
-
131
- -----
56
+ - [设置参考](../../../docs/subsystems/settings.zh.md)——表单值与修改。
57
+ - [Volatile 配置](../../../vendor/loader/README.md)——引用生命周期与通知。
58
+ - [配置编辑器](../../boot/config-editor/README.zh.md)——持久化与重载顺序。
132
59
 
133
60
  <a id="model-experience"></a>
134
61
  ## 模型体验
135
62
 
136
- 间接生效:由设置值提供的所有面向模型的内容均由消费方插件负责;本服务只存储并解析用户设置,自身不注册任何面向模型的内容。
63
+ 通过面向模型的插件读取的配置值间接影响模型。
137
64
 
138
65
  #### KV Cache 影响
139
66
 
140
- 无直接失效;把设置值纳入请求前缀的消费方负责该变更。
67
+ 改变请求前缀的消费者决定缓存影响。
141
68
 
142
- ## 已知限制与延期工作
69
+ ## 已知限制与延后工作
143
70
 
144
71
  <a id="known-limitations-and-deferred-work"></a>
145
72
 
146
-
147
- 这些限制说明本服务何时不合适或需要特别注意。它们是当前包约束,不是任务积压。
148
-
149
- - **单一用户层**——解析只认识 schema 默认值、一个组合 `base` 与一个用户文档;它不记录每个解析值由哪一层提供。
150
- - **`redactSecrets` 并非一条可被证明的协议边界**——遍历器只跟随 `object`/`dict`/`array` 容器,因此只能经由 union、intersection 或 transform 抵达的 `role('secret')` 字段会被原样返回,且 `secrets` 列表为空;序列化 schema 还会把 secret 字段的默认值带给每个客户端。两种情况都不会被拒绝;机密无法经由被遍历的容器抵达的 schema,绝不可注册到暴露于协议的 namespace 上。fail-closed 的 `describeForWire()`——拒绝自己无法证明安全的 schema,并对序列化封装与错误文本做净化——是暂缓的答案。
151
- - **跨进程并发由提供方定义**——服务仅在进程内按 namespace 串行写入;跨进程并发按提供方行为收敛(文件提供方在写锁下读-改-写,因此并发写入者不会丢掉彼此的 namespace,同 namespace 冲突按后写胜出解决)。
73
+ - 嵌套 Include 独立拥有配置,不能通过当前 profile 的表单编辑。
74
+ - 字段重置恢复继承值,不能删除下层配置提供的值。取消设置数组索引会移除该元素。
152
75
 
153
76
  <a id="dev-note"></a>
154
77
  ### 开发备注
155
78
 
156
79
  <details>
157
- <summary>维护者的工作上下文——点击展开</summary>
80
+ <summary>维护者工作上下文——点击展开</summary>
158
81
 
159
- 本开发备注是维护者的工作上下文:尚未决定的开放设计方向。它明确非权威——已发布的行为、限制与已接受的理由见上文各节与包代码。代码 TODO 中记录的开放方向:把公开的 `ns` 参数更名为 `namespace`(API、提供方约定、实现、测试与消费方同步);释放注册项时停用所有 watcher 并等待其调用链完成,确保回调不会在 registrant fiber 释放后继续运行;替换注册从持久化分节重新解析,让进行中的旧写入不会把它留成陈旧值;改用属性安全的对象构造,让 `__proto__` 这类合法 JSON 键保持为自有数据。fail-closed 的 `describeForWire()` 净化器是上文脱敏限制的暂缓答案。
82
+ 无。
160
83
 
161
84
  </details>