{"_id":"@buddhilive/dsh-settings","name":"@buddhilive/dsh-settings","dist-tags":{"alpha":"0.1.2-alpha.3","latest":"0.1.2-alpha.3"},"versions":{"0.1.2-alpha.3":{"name":"@buddhilive/dsh-settings","description":"Abstract user-settings seam (ctx.settings) for the DeepSeek Harness","version":"0.1.2-alpha.3","publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/Buddhilive/buddhi-ai-harness.git","directory":"packages/settings/settings"},"type":"module","main":"lib/index.js","types":"lib/types/index.d.ts","exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./invariant":{"types":"./lib/types/invariant.d.ts","default":"./lib/invariant.js"},"./types":{"types":"./lib/types/types.d.ts","default":"./lib/types/types.js"},"./src/*":"./src/*","./package.json":"./package.json"},"license":"MIT","peerDependencies":{"@buddhilive/dsh-brand":"^0.1.2-alpha.3","@buddhilive/dsh-session":"^0.1.2-alpha.3","@buddhilive/dsh-invariants":"^0.1.2-alpha.3","@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/schemastery":"^3.18.2"},"devDependencies":{"@deepseek-ai/cordis":"^4.0.2","@buddhilive/dsh-brand":"^0.1.2-alpha.3","@buddhilive/dsh-invariants":"^0.1.2-alpha.3","@buddhilive/dsh-session":"^0.1.2-alpha.3","@deepseek-ai/schemastery":"^3.18.2"},"dependencies":{"@buddhilive/dsh-util-values":"^0.1.2-alpha.3"},"_id":"@buddhilive/dsh-settings@0.1.2-alpha.3","bugs":{"url":"https://github.com/Buddhilive/buddhi-ai-harness/issues"},"homepage":"https://github.com/Buddhilive/buddhi-ai-harness#readme","_integrity":"sha512-Fm3cj/qG8A7rkAjLPaxOS0Oufi4XyyUvD+BvOmnRH4TVdUXnKz24+M+MJBNOgmGGbIJGLE+k2qu1TPobLU3z+g==","_resolved":"C:\\DevDojo\\Buddhi\\buddhi-ai-harness\\dist\\npm\\buddhilive-dsh-settings-0.1.2-alpha.3.tgz","_from":"file:C:/DevDojo/Buddhi/buddhi-ai-harness/dist/npm/buddhilive-dsh-settings-0.1.2-alpha.3.tgz","_nodeVersion":"24.13.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-Fm3cj/qG8A7rkAjLPaxOS0Oufi4XyyUvD+BvOmnRH4TVdUXnKz24+M+MJBNOgmGGbIJGLE+k2qu1TPobLU3z+g==","shasum":"53bebb15387135d5ef8c7d0359ec0985b9991ba8","tarball":"https://registry.npmjs.org/@buddhilive/dsh-settings/-/dsh-settings-0.1.2-alpha.3.tgz","fileCount":15,"unpackedSize":118985,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDlTbeurHYqz9b9HTGxie3aELhYgj1aVe23n99/dbb4ngIhAI+aTotfCdLzDWVWlV+EE+U5OuC1s4E4EeYK9EELmQf5"}]},"_npmUser":{"name":"buddhilive","email":"visitbudkavin@gmail.com"},"directories":{},"maintainers":[{"name":"buddhilive","email":"visitbudkavin@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dsh-settings_0.1.2-alpha.3_1788164966766_0.460350618812305"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-31T08:29:26.469Z","0.1.2-alpha.3":"2026-08-31T08:29:26.922Z","modified":"2026-08-31T08:29:27.266Z"},"maintainers":[{"name":"buddhilive","email":"visitbudkavin@gmail.com"}],"description":"Abstract user-settings seam (ctx.settings) for the DeepSeek Harness","homepage":"https://github.com/Buddhilive/buddhi-ai-harness#readme","repository":{"type":"git","url":"git+https://github.com/Buddhilive/buddhi-ai-harness.git","directory":"packages/settings/settings"},"bugs":{"url":"https://github.com/Buddhilive/buddhi-ai-harness/issues"},"license":"MIT","readme":"---\ndescription: \"面向插件作者与维护者的用户设置服务：注册可配置 namespace、读取解析值或接入配置界面。\"\nkind: \"package-reference\"\n---\n\n# @buddhilive/dsh-settings\n\n[English](README.md) | 中文\n\n## 概述\n\n`dsh-settings` 让插件把配置开放给用户运行时修改：插件用一个 schema 注册 namespace，解析值依次尊重 schema 默认值、部署自身的组合 `base` 与用户编辑的文档分节——用户覆盖优先。消费方读取解析值快照并在每次已提交变更后收到通知；配置界面每个 namespace 得到一条 descriptor——schema、当前值、每个字段来自哪一层、生效时机——而无需直接触碰存储。写入只改动用户覆盖、按 namespace 逐个执行，并可携带期望 revision，让持有陈旧快照的写入方被拒绝，而不是悄悄覆盖较新的写入。文档必须由挂载的提供方存储；没有提供方时一切照旧，配置保持组合原样。\n\n## 目录\n\n- [使用本包](#use-this-package)\n- [理解实现](#understand-the-implementation)\n- [进一步探索](#further-exploration)\n- [模型体验](#model-experience)\n- [已知限制与延期工作](#known-limitations-and-deferred-work)\n- [开发备注](#dev-note)\n\n-----\n\n<a id=\"use-this-package\"></a>\n## 使用本包\n\n插件与配置界面通过 `ctx.settings` 在运行时读取并修改配置。常用路径：挂载提供方、用 schema 注册 namespace、读取并观察解析值，并通过 owner scope 写入。\n\n### 何时选择\n\n当插件的配置需要在运行时可变——用户编辑文档或配置界面修改——且无需重启或重读 `cordis.yml` 时，选择设置服务。它适合多个插件各拥有一个配置 namespace、以及配置界面需要渲染 schema、标记用户覆盖字段并持久化编辑的场景。当配置在加载时固定则没有必要：没有挂载提供方时一切照旧，配置保持组合原样。\n\n### 挂载提供方\n\n服务本身不存储任何内容；请挂载一个提供方，例如随附的文件型提供方：\n\n```yaml\n- name: '@buddhilive/dsh-settings-file'\n  config:\n    path: /absolute/path/to/settings.yaml\n```\n\n提供方上线后 `ctx.settings` 即出现。完整配置面由提供方 README 负责；生成的[配置目录](../../../docs/config-catalog.zh.md#buddhilivedsh-settings-file)列出每个受支持字段。\n\n### 注册 namespace\n\n插件用 schemastery schema 注册自己的 namespace，并可选地把组合配置作为 `base` 层传入，让解析值从部署已配置的内容起步：\n\n```text\nconst scope = ctx.settings.register('ui-theme', ThemeSchema, {\n  base: config,   // composition entry config; the user layer resolves above it\n})\nconst theme = scope.get()              // deep-frozen resolved snapshot\nscope.update({ density: 'compact' })   // merges into the user section and persists\n```\n\nTypeScript 会按小写字母、数字与连字符文法检查字面量 namespace 参数；运行时动态传入的字符串接受相同校验。`ctx.settings.installSection(owner, ns, schema, entry, hooks)` 为消费方插件封装可选服务接线：只要设置服务存在，它就用插件的组合配置作为 `base` 注册 namespace；服务消失时插件回退到组合配置，行为与原先完全一致。\n\n### 读取与观察值\n\n`get(ns)` 以深冻结快照返回解析值，namespace 未注册时为 `undefined`。`watch(callback)` 在每次已提交变更后以 `(next, prev)` 调用回调：同一回调的调用按提交顺序逐个执行，异常被隔离并记入日志，因此慢或抛错的观察者绝不会阻塞或破坏其他观察者。\n\n### 写入值\n\n`update(ns, patch)` 把普通对象 patch 深合并进用户分节——绝不进 `base`——校验解析候选值、经提供方持久化后提交。`replace(ns, section)` 整体替换用户分节，是删除/重置路径：`replace({})` 重新继承 `base` 与 schema 默认值。`mutate(ns, ops)` 在写入排到队首那一刻的分节上按序施加 `{ op: 'set' | 'unset', path }` 编辑——这是持有不完整（例如脱敏后）视图的调用方的删除路径，因为按协议接口返回的内容重建分节再整体替换，会删掉协议从未回传的每个字段。\n\n每次写入都会拒绝与 JSON 不兼容的数据（`Date`、`Map`、`BigInt`、非有限数或循环引用会在任何内容持久化前以 `$` 为根的路径报错）、拒绝只读提供方上的写入，并可接受可选的 `expectedRevision`：把 descriptor 中的 `revision` 传回，namespace 已越过该值时写入会被 `SettingsConflictError` 拒绝，而不是覆盖先完成写入的一方。\n\n### 配置界面\n\n`describe()` 为每个已注册 namespace 返回一条 descriptor：序列化 schema、解析值、分离的 `base` 与 `user` 层（字段出现在 `user` 中即标记为用户覆盖）、生效时机与 namespace 的 revision。每个协议接口都必须传入 `redactSecrets: true`：它从每一层剥离 `role('secret')` 字段，并把它们枚举为 `{ path, set }` slot，让页面可以渲染只写输入而不接触任何机密。`documentPath` 与 `prepareDocument()` 在提供方拥有用户可编辑文件时把它暴露给原生编辑器。\n\n### 事件与失败\n\n`settings/updated (ns, next, prev, source)` 在每次已提交变更后触发——进程内写入（`source: 'update'`）或外部观察到的编辑（`source: 'provider'`）——解析值深相等时绝不触发。`settings/document-updated (ns, revision)` 在原始用户分节发生变化时触发，即使解析值没有变——已打开的编辑器正需要它来得知字段从继承变为覆盖。schema 拒绝的存量分节在重载时保留该 namespace 的最后可用值并告警；注册时同样的失败会直接拒绝注册。\n\n-----\n\n<a id=\"understand-the-implementation\"></a>\n## 理解实现\n\n<details>\n<summary>实现细节——点击展开</summary>\n\n本节解释服务背后的设计决策并指出实现它们的代码位置；可观察行为已在[使用本包](#use-this-package)中完整说明。\n\n### 设计理念\n\n- **分层解析，单一用户层。** namespace 的值依次为 schema 默认值、注册方的组合 `base`、用户文档分节；写入只触碰用户层，因此 `replace({})` 是真正的重置。\n- **提交以深相等为门槛。** 只有解析值变化时 `settings/updated` 才触发；原始分节事件独立存在，因为配置界面还必须得知「继承变成了覆盖」。\n- **写入排队并做 revision 检查。** 每个 namespace 的写队列按调用顺序串行，`expectedRevision` 在队首判断——那里服务才能分辨持有新鲜快照的写入方与持有陈旧快照的写入方。\n- **观察者与监听器异常被隔离。** watcher 调用与事件扇出隔离同步抛出与异步拒绝，一个坏掉的观察者不会卡死提交或提供方的重载循环；`INVARIANT` 编码的失败在所有监听器执行完后重新抛出。\n- **注册是 fiber 上的 effect。** 注册 namespace 是调用方插件 fiber 上的 effect：dispose（资源释放）该 fiber 即移除 namespace 及其观察者。\n\n### 源码地图\n\n| 文件 | 职责 |\n|---|---|\n| [`src/index.ts`](src/index.ts) | Service Definition：namespace 校验、注册、解析、写队列、describe/脱敏、事件、`installSection` |\n| [`src/redact.ts`](src/redact.ts) | `redactSecrets` 遍历器：剥离 `role('secret')` 字段并枚举其 slot |\n| [`src/types.ts`](src/types.ts) | 客户端安全类型面：事件声明、`SettingsNamespace`、`SettingsUpdateSource` |\n| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件：`settings/updated` 只对已注册 namespace、只在解析值变化时、且携带权威值触发 |\n\n### 解析与写入路径\n\n每次写入都在调用时对输入做快照（分离并校验 JSON 形状的数据），然后排上该 namespace 的串行链。在队首，服务按当前状态重读分节、检查 `expectedRevision`、合并/替换/编辑、经 schema 与 owner 的可选 `validate` 解析并校验候选值、经提供方持久化，然后才提交并发出事件。registrant fiber 在写入途中被 dispose 的写入仍到达存储，但不会提交、也不会通知任何人；卸载先拒绝新写入，并排干排队写入与已启动的 watcher 调用后才完成。\n\n### 变更检测与事件\n\n`commit` 用 seam 的 `deepEqualJson` 谓词比较解析值，并逐监听器扇出 `settings/updated`。`bumpRevision` 比较原始分节并携带新 revision 发出 `settings/document-updated`；它与解析值检查相互独立。两个扇出以相同方式隔离监听器异常。\n\n### 客户端安全类型\n\n`./types` 子路径出口持有事件声明及其签名点名的 `SettingsNamespace`、`SettingsUpdateSource` 类型，包根继续 re-export 这些类型。于是 Host 编译面之外的消费方读到的正是 Host 发射的那一份签名，而不必再写一遍。\n\n</details>\n\n-----\n\n<a id=\"further-exploration\"></a>\n## 进一步探索\n\n当服务级约定不够用时阅读以下页面。它们从共享子系统词汇逐步进入随附提供方与能力架构。\n\n- [设置子系统参考](../../../docs/subsystems/settings.zh.md)——namespace、注册、owner scope、descriptor、变更提交与生成的 cordis 接口面。\n- [文件型设置提供方](../settings-file/README.zh.md)——随附的 YAML/JSON 提供方：配置、热重载、保留注释的写入。\n- [设置包映射](../README.zh.md)——用户设置能力的两个包及其角色。\n- [能力 seam](../../../docs/capability-seams.zh.md)——本服务遵循的 Service Definition / Service Provider / Consumer 拆分。\n\n-----\n\n<a id=\"model-experience\"></a>\n## 模型体验\n\n间接生效：消费方插件拥有任何由设置值喂给的模型面内容；本服务只存储并解析用户设置，自身不注册任何模型面内容。\n\n#### KV Cache 影响\n\n无直接失效；把设置值纳入请求前缀的消费方负责该变更。\n\n## 已知限制与延期工作\n\n<a id=\"known-limitations-and-deferred-work\"></a>\n\n\n这些限制说明本服务何时不合适或需要特别注意。它们是当前包约束，不是任务积压。\n\n- **单一用户层**——解析只认识 schema 默认值、一个组合 `base` 与一个用户文档；它不记录每个解析值由哪一层提供。\n- **`redactSecrets` 并非一条可被证明的协议边界**——遍历器只跟随 `object`/`dict`/`array` 容器，因此只能经由 union、intersection 或 transform 抵达的 `role('secret')` 字段会被原样返回，且 `secrets` 列表为空；序列化 schema 还会把 secret 字段的默认值带给每个客户端。两种情况都不会被拒绝；机密无法经由被遍历的容器抵达的 schema，绝不可注册到暴露于协议的 namespace 上。fail-closed 的 `describeForWire()`——拒绝自己无法证明安全的 schema，并对序列化封装与错误文本做净化——是暂缓的答案。\n- **跨进程并发由提供方定义**——服务仅在进程内按 namespace 串行写入；跨进程并发按提供方行为收敛（文件提供方在写锁下读-改-写，因此并发写入者不会丢掉彼此的 namespace，同 namespace 冲突按后写胜出解决）。\n\n<a id=\"dev-note\"></a>\n### 开发备注\n\n<details>\n<summary>维护者的工作上下文——点击展开</summary>\n\n本开发备注是维护者的工作上下文：尚未决定的开放设计方向。它明确非权威——已发布的行为、限制与已接受的理由见上文各节与包代码。代码 TODO 中记录的开放方向：把公开的 `ns` 参数更名为 `namespace`（API、提供方约定、实现、测试与消费方同步）；注册释放时停用所有 watcher 并等待其 tail，让回调不越过 registrant fiber 存活；替换注册从持久化分节重新解析，让进行中的旧写入不会把它留成陈旧值；改用属性安全的对象构造，让 `__proto__` 这类合法 JSON 键保持为自有数据。fail-closed 的 `describeForWire()` 净化器是上文脱敏限制的暂缓答案。\n\n</details>\n","readmeFilename":"README.zh.md","_rev":"1-8f292a4701556ed77f7a65b13d61566f"}