From 745bf39c7577eb04863c5b3a37f9bed82be0d2e9 Mon Sep 17 00:00:00 2001 From: techflag <562635045@qq.com> Date: Sat, 26 Sep 2026 16:19:44 +0800 Subject: [PATCH] chore: remove unused community workspace scaffolds --- AGENTS.md | 2 - CONTRIBUTING.en.md | 4 +- CONTRIBUTING.md | 4 +- README.i18n.yaml | 4 +- docs/README.en.md | 2 +- docs/README.md | 2 +- docs/plugin-ecosystem.en.md | 9 +- docs/plugin-ecosystem.md | 9 +- dsh-community-fabric/LICENSE | 21 - dsh-community-fabric/README.i18n.yaml | 4 - dsh-community-fabric/README.md | 63 -- dsh-community-fabric/README.zh.md | 63 -- .../compatibility-layer.i18n.yaml | 4 - .../docs/architecture/compatibility-layer.md | 365 ----------- .../architecture/compatibility-layer.zh.md | 373 ------------ .../community-issue-23-review.i18n.yaml | 4 - .../research/community-issue-23-review.md | 107 ---- .../research/community-issue-23-review.zh.md | 107 ---- .../docs/research/dsh-plugin-needs.i18n.yaml | 4 - .../docs/research/dsh-plugin-needs.md | 181 ------ .../docs/research/dsh-plugin-needs.zh.md | 181 ------ .../mature-plugin-frameworks.i18n.yaml | 4 - .../docs/research/mature-plugin-frameworks.md | 207 ------- .../research/mature-plugin-frameworks.zh.md | 206 ------- .../research/vscode-extension-model.i18n.yaml | 4 - .../docs/research/vscode-extension-model.md | 317 ---------- .../research/vscode-extension-model.zh.md | 317 ---------- ...gin-manifest-capabilities-events.i18n.yaml | 4 - ...001-plugin-manifest-capabilities-events.md | 452 -------------- ...-plugin-manifest-capabilities-events.zh.md | 473 --------------- ...resentation-invocation-transport.i18n.yaml | 4 - ...ntime-presentation-invocation-transport.md | 571 ------------------ ...me-presentation-invocation-transport.zh.md | 571 ------------------ ...ervice-providers-and-composition.i18n.yaml | 4 - .../0003-service-providers-and-composition.md | 421 ------------- ...03-service-providers-and-composition.zh.md | 421 ------------- ...nance-validation-and-diagnostics.i18n.yaml | 4 - ...4-provenance-validation-and-diagnostics.md | 264 -------- ...rovenance-validation-and-diagnostics.zh.md | 264 -------- dsh-community-fabric/package.json | 42 -- dsh-community-fabric/scripts/verify-docs.mjs | 157 ----- dsh-community-market/LICENSE | 21 - dsh-community-market/README.i18n.yaml | 4 - dsh-community-market/README.md | 16 - dsh-community-market/README.zh.md | 16 - dsh-community-market/SECURITY.i18n.yaml | 4 - dsh-community-market/SECURITY.md | 41 -- dsh-community-market/SECURITY.zh.md | 41 -- .../docs/catalog-adapter-guide.i18n.yaml | 4 - .../docs/catalog-adapter-guide.md | 194 ------ .../docs/catalog-adapter-guide.zh.md | 194 ------ .../docs/catalog-provider-contract.i18n.yaml | 4 - .../docs/catalog-provider-contract.md | 417 ------------- .../docs/catalog-provider-contract.zh.md | 417 ------------- .../catalog-provider-page.example.json | 59 -- ...catalog-provider-page.minimal.example.json | 19 - .../docs/examples/catalog-query.example.json | 7 - .../examples/catalog-snapshot.example.json | 73 --- .../docs/examples/catalog-source.example.json | 25 - .../docs/install-and-uninstall.i18n.yaml | 4 - .../docs/install-and-uninstall.md | 68 --- .../docs/install-and-uninstall.zh.md | 68 --- .../docs/market-shell.i18n.yaml | 4 - dsh-community-market/docs/market-shell.md | 55 -- dsh-community-market/docs/market-shell.zh.md | 55 -- .../schemas/catalog-provider-page.schema.json | 376 ------------ .../docs/schemas/catalog-query.schema.json | 74 --- .../docs/schemas/catalog-snapshot.schema.json | 467 -------------- .../docs/schemas/catalog-source.schema.json | 151 ----- dsh-community-market/package.json | 44 -- dsh-community-market/scripts/verify-docs.mjs | 509 ---------------- package.json | 6 +- scripts/verify-desktop-dsh-alignment.mjs | 2 +- scripts/verify-layout.mjs | 34 +- yarn.lock | 31 +- 75 files changed, 19 insertions(+), 9705 deletions(-) delete mode 100644 dsh-community-fabric/LICENSE delete mode 100644 dsh-community-fabric/README.i18n.yaml delete mode 100644 dsh-community-fabric/README.md delete mode 100644 dsh-community-fabric/README.zh.md delete mode 100644 dsh-community-fabric/docs/architecture/compatibility-layer.i18n.yaml delete mode 100644 dsh-community-fabric/docs/architecture/compatibility-layer.md delete mode 100644 dsh-community-fabric/docs/architecture/compatibility-layer.zh.md delete mode 100644 dsh-community-fabric/docs/research/community-issue-23-review.i18n.yaml delete mode 100644 dsh-community-fabric/docs/research/community-issue-23-review.md delete mode 100644 dsh-community-fabric/docs/research/community-issue-23-review.zh.md delete mode 100644 dsh-community-fabric/docs/research/dsh-plugin-needs.i18n.yaml delete mode 100644 dsh-community-fabric/docs/research/dsh-plugin-needs.md delete mode 100644 dsh-community-fabric/docs/research/dsh-plugin-needs.zh.md delete mode 100644 dsh-community-fabric/docs/research/mature-plugin-frameworks.i18n.yaml delete mode 100644 dsh-community-fabric/docs/research/mature-plugin-frameworks.md delete mode 100644 dsh-community-fabric/docs/research/mature-plugin-frameworks.zh.md delete mode 100644 dsh-community-fabric/docs/research/vscode-extension-model.i18n.yaml delete mode 100644 dsh-community-fabric/docs/research/vscode-extension-model.md delete mode 100644 dsh-community-fabric/docs/research/vscode-extension-model.zh.md delete mode 100644 dsh-community-fabric/docs/rfcs/0001-plugin-manifest-capabilities-events.i18n.yaml delete mode 100644 dsh-community-fabric/docs/rfcs/0001-plugin-manifest-capabilities-events.md delete mode 100644 dsh-community-fabric/docs/rfcs/0001-plugin-manifest-capabilities-events.zh.md delete mode 100644 dsh-community-fabric/docs/rfcs/0002-runtime-presentation-invocation-transport.i18n.yaml delete mode 100644 dsh-community-fabric/docs/rfcs/0002-runtime-presentation-invocation-transport.md delete mode 100644 dsh-community-fabric/docs/rfcs/0002-runtime-presentation-invocation-transport.zh.md delete mode 100644 dsh-community-fabric/docs/rfcs/0003-service-providers-and-composition.i18n.yaml delete mode 100644 dsh-community-fabric/docs/rfcs/0003-service-providers-and-composition.md delete mode 100644 dsh-community-fabric/docs/rfcs/0003-service-providers-and-composition.zh.md delete mode 100644 dsh-community-fabric/docs/rfcs/0004-provenance-validation-and-diagnostics.i18n.yaml delete mode 100644 dsh-community-fabric/docs/rfcs/0004-provenance-validation-and-diagnostics.md delete mode 100644 dsh-community-fabric/docs/rfcs/0004-provenance-validation-and-diagnostics.zh.md delete mode 100644 dsh-community-fabric/package.json delete mode 100644 dsh-community-fabric/scripts/verify-docs.mjs delete mode 100644 dsh-community-market/LICENSE delete mode 100644 dsh-community-market/README.i18n.yaml delete mode 100644 dsh-community-market/README.md delete mode 100644 dsh-community-market/README.zh.md delete mode 100644 dsh-community-market/SECURITY.i18n.yaml delete mode 100644 dsh-community-market/SECURITY.md delete mode 100644 dsh-community-market/SECURITY.zh.md delete mode 100644 dsh-community-market/docs/catalog-adapter-guide.i18n.yaml delete mode 100644 dsh-community-market/docs/catalog-adapter-guide.md delete mode 100644 dsh-community-market/docs/catalog-adapter-guide.zh.md delete mode 100644 dsh-community-market/docs/catalog-provider-contract.i18n.yaml delete mode 100644 dsh-community-market/docs/catalog-provider-contract.md delete mode 100644 dsh-community-market/docs/catalog-provider-contract.zh.md delete mode 100644 dsh-community-market/docs/examples/catalog-provider-page.example.json delete mode 100644 dsh-community-market/docs/examples/catalog-provider-page.minimal.example.json delete mode 100644 dsh-community-market/docs/examples/catalog-query.example.json delete mode 100644 dsh-community-market/docs/examples/catalog-snapshot.example.json delete mode 100644 dsh-community-market/docs/examples/catalog-source.example.json delete mode 100644 dsh-community-market/docs/install-and-uninstall.i18n.yaml delete mode 100644 dsh-community-market/docs/install-and-uninstall.md delete mode 100644 dsh-community-market/docs/install-and-uninstall.zh.md delete mode 100644 dsh-community-market/docs/market-shell.i18n.yaml delete mode 100644 dsh-community-market/docs/market-shell.md delete mode 100644 dsh-community-market/docs/market-shell.zh.md delete mode 100644 dsh-community-market/docs/schemas/catalog-provider-page.schema.json delete mode 100644 dsh-community-market/docs/schemas/catalog-query.schema.json delete mode 100644 dsh-community-market/docs/schemas/catalog-snapshot.schema.json delete mode 100644 dsh-community-market/docs/schemas/catalog-source.schema.json delete mode 100644 dsh-community-market/package.json delete mode 100644 dsh-community-market/scripts/verify-docs.mjs diff --git a/AGENTS.md b/AGENTS.md index aeb281d8a1..d3cf4be1f8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,8 +30,6 @@ This repository owns WorkDSH Web and Desktop. The Desktop product runs an unmodi - `deepseek-harness/` is a pinned upstream Git submodule. Never edit files inside it from a desktop feature branch. - `dsh-plugin-desktop/` owns the Electron carrier, packaging, and release tests. DSH Host and Client code comes from the pinned upstream runtime Profile; WorkDSH features belong to the WorkDSH Profile packages. -- `dsh-community-fabric/` owns the community interoperability RFC. Until schemas and a reviewed reference adapter exist, it remains a private documentation scaffold and must not declare loadable DSH or package entry points. -- `dsh-community-market/` owns the community-market shell. Until its runtime is implemented, it remains a private documentation scaffold and must not declare loadable DSH or package entry points. - The Desktop workspace uses the root Yarn release with `nodeLinker: node-modules`. The nested `workdsh-web/` workspace retains its own pinned pnpm lockfile and package manager; run its commands from that directory. Do not install it into the root Yarn workspace. - The upstream submodule keeps its own pnpm workspace. Run upstream commands through the root `upstream:*` scripts, whose Yarn portable-shell commands enter the submodule before invoking Corepack. - Keep presentation and WorkDSH feature changes in the Profile rather than adding a second Desktop Host or Client implementation. diff --git a/CONTRIBUTING.en.md b/CONTRIBUTING.en.md index c1436a043e..870526f392 100644 --- a/CONTRIBUTING.en.md +++ b/CONTRIBUTING.en.md @@ -16,8 +16,6 @@ DSH is built around plugins. If you write plugins, start with: - [Plugin development](docs/plugin-development.en.md): how to write ordinary DSH plugins and Desktop plugins. - [DSH plugin ecosystem manifesto](docs/plugin-ecosystem.en.md): our vision of an open, composable, sustainable ecosystem, and the three principles — composition first, declare clearly, compatibility first. -- [DSH Community Fabric Draft](dsh-community-fabric/README.md): join the public discussion of manifests, capabilities, Host Descriptors, and event contracts. -- [Community Market design](dsh-community-market/docs/market-shell.md): how the future market will discover plugins and why listing is not a security review. Plugins that follow the manifesto coexist better with other plugins and will be easier to discover and trust in the marketplace when it ships. @@ -35,7 +33,7 @@ corepack yarn dev # launch the application when a graphical session is avail ### Repository boundaries (please read before starting) - `deepseek-harness/` is the pinned upstream submodule. **Desktop development never edits files inside it**; upstream updates land through separate pin commits. -- Desktop code lives in `dsh-plugin-desktop/`; `dsh-community-fabric/` owns the community-standard Draft and `dsh-community-market/` owns the market-shell design. Both community packages are currently documentation-only and not loadable; all three owned packages share the outer Yarn workspace. +- Desktop code lives in `dsh-plugin-desktop/`, and WorkDSH feature packages live in `workdsh-web/`. The former uses the root Yarn workspace; the latter retains its own pnpm workspace. The pinned upstream `deepseek-harness/` is a Git submodule. - Builds, typechecks, unit tests, and smoke checks must stay headless-safe. ### Commits and pull requests diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 58f549da24..573b57a844 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,8 +16,6 @@ DSH 的核心是插件。如果你写插件,请先阅读: - [插件开发](docs/plugin-development.md):如何编写普通 DSH 插件和 Desktop 插件。 - [DSH 插件生态倡议书](docs/plugin-ecosystem.md):开放、可组合、可持续的生态愿景,以及组合优先、声明清晰、兼容优先三条原则。 -- [DSH Community Fabric Draft](dsh-community-fabric/README.zh.md):参与 Manifest、Capability、Host Descriptor 和事件 contract 的公开讨论。 -- [Community Market 设计](dsh-community-market/docs/market-shell.zh.md):未来市场如何发现插件,以及为什么收录不等于安全审核。 遵循倡议书的插件更容易与其他插件共存,也会在未来上线时更容易在插件市场中被发现和信任。 @@ -35,7 +33,7 @@ corepack yarn dev # 有图形环境时启动应用 ### 仓库边界(开始前务必了解) - `deepseek-harness/` 是固定版本的上游子模块,**桌面开发不修改其中的任何文件**;上游内容更新走独立的 pin 提交。 -- 桌面代码位于 `dsh-plugin-desktop/`;`dsh-community-fabric/` 保存社区标准 Draft,`dsh-community-market/` 保存市场壳设计。两个社区 package 当前都只有文档、尚不可加载,三个自有 package 共用外层 Yarn workspace。 +- 桌面代码位于 `dsh-plugin-desktop/`,WorkDSH 功能包位于 `workdsh-web/`;前者使用根目录 Yarn 工作区,后者保留独立 pnpm 工作区。上游 `deepseek-harness/` 是固定版本的 Git 子模块。 - 构建、类型检查、单元测试和冒烟检查必须保持 headless-safe。 ### 提交与 PR diff --git a/README.i18n.yaml b/README.i18n.yaml index 5abb4e92fa..64e97dd3ad 100644 --- a/README.i18n.yaml +++ b/README.i18n.yaml @@ -1,5 +1,5 @@ # Bilingual-pair consistency record: the git blob hash of each side as of the last # confirmed-consistent state. Both languages carry equal authority. Update both files # and re-record their hashes after editing either side. -README.md: edf952a3df8b27368ad5d342f51ca0e085f3f102 -README.zh-CN.md: eb5a612277377505c32107e520e4ca46ec276037 +README.md: 0d11f05e25608c5c34d30eb0b95d4e3212393f14 +README.zh-CN.md: 6d7025fa93be348b50387f5ebb1606c465e5bab4 diff --git a/docs/README.en.md b/docs/README.en.md index f179d09f4d..5a71f69a64 100644 --- a/docs/README.en.md +++ b/docs/README.en.md @@ -6,6 +6,6 @@ Start with the [user guide](user-guide.en.md) for installation and use. The [FAQ Developers can read the [architecture](architecture.en.md), [Desktop ownership boundaries](desktop-boundaries.md), [plugin development](plugin-development.en.md), and [package build guide](../dsh-plugin-desktop/README.md). `deepseek-harness/` is the pinned, unmodified official upstream submodule. WorkDSH feature packages are composed by the runtime Profile. -The [Community Fabric draft](../dsh-community-fabric/README.md) and [Community Market draft](../dsh-community-market/README.md) currently contain design documents only; they do not imply runtime entry points in installers. The [plugin ecosystem manifesto](plugin-ecosystem.en.md) describes the longer-term direction. +The [plugin ecosystem manifesto](plugin-ecosystem.en.md) describes the longer-term direction; there is currently no separate community standard or online market runtime package. The root [`README.md`](../README.md) is the default English product entry point, with [`README.zh-CN.md`](../README.zh-CN.md) as its Chinese counterpart. Each `.i18n.yaml` records the Git blob hashes of its bilingual documents; update both languages and the record together. diff --git a/docs/README.md b/docs/README.md index 3bc5ac0219..5dffc82ed4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,6 +6,6 @@ 开发者可阅读[架构说明](architecture.md)、[Desktop 归属约束](desktop-boundaries.md)、[插件开发](plugin-development.md)和[包级构建说明](../dsh-plugin-desktop/README.zh.md)。`deepseek-harness/` 是固定版本且不修改的官方上游子模块;WorkDSH 的功能包由运行时 Profile 组合。 -[社区 Fabric 草案](../dsh-community-fabric/README.zh.md)与[社区 Market 草案](../dsh-community-market/README.zh.md)目前只包含设计文档,不代表安装包已提供这些运行时入口。[插件生态倡议书](plugin-ecosystem.md)描述长期方向。 +[插件生态倡议书](plugin-ecosystem.md)描述长期方向;当前没有独立的社区标准或在线市场运行包。 根目录的 [`README.md`](../README.md) 是默认英文产品入口,[`README.zh-CN.md`](../README.zh-CN.md) 是中文入口。各 `.i18n.yaml` 记录相应双语文件的 Git blob hash,修改文档时应同步更新两种语言和记录。 diff --git a/docs/plugin-ecosystem.en.md b/docs/plugin-ecosystem.en.md index 8f6a2bd71b..704bc42c77 100644 --- a/docs/plugin-ecosystem.en.md +++ b/docs/plugin-ecosystem.en.md @@ -30,17 +30,14 @@ This manifesto is not a unilateral rulebook. It is a **living document**: it fol Once the plugin marketplace ships, plugins that follow this manifesto will be easier to discover, install, and trust. We want convention-driven development to be the beneficial choice for every author, not an extra burden. -## From a manifesto to a testable contract +## Current boundary -[DSH Community Fabric](../dsh-community-fabric/README.md) is turning this vision into a public Draft for manifests, capabilities, Host Descriptors, and events. It currently contains documentation only, not a released standard or runtime; plugins still use existing DSH and Cordis APIs today. +Plugins currently use the published DSH/Cordis APIs. Any future shared manifest or catalog should first be validated against real plugins, compatibility tests, and user needs. Capability declarations can help with compatibility, consent, and audit, but cannot present in-process JavaScript as a security sandbox. Only a Host with evidence of real isolation may claim technical permission enforcement. -Fabric capabilities begin as compatibility, consent, and audit declarations. They do not present in-process JavaScript as a security sandbox. Only a Host with evidence of real isolation may claim technical permission enforcement. - -The market is still in its [product and safety design phase](../dsh-community-market/README.md), with no usable page or installer yet. Catalog inclusion means that a project matched catalog rules; it is not a security review or endorsement. +There is no online market page or installer today. Future catalog inclusion would mean that a project met catalog rules, not that it passed a security review or received an endorsement. ## How to participate - Learn how plugins are written in [plugin development](plugin-development.en.md). -- Read and comment on [Community Fabric RFC 0001](../dsh-community-fabric/docs/rfcs/0001-plugin-manifest-capabilities-events.md). - Learn how to install and manage plugins in the [user guide](user-guide.en.md). - Share your thoughts on this manifesto through issues and discussions. diff --git a/docs/plugin-ecosystem.md b/docs/plugin-ecosystem.md index cacbd50177..02a266aa62 100644 --- a/docs/plugin-ecosystem.md +++ b/docs/plugin-ecosystem.md @@ -30,17 +30,14 @@ WorkDSH Desktop 使用 Electron 承载固定版本的官方 DSH 运行时。桌 插件市场上线后,符合本倡议的插件将更容易被发现、安装和信任。我们希望让"按规范开发"成为对每个作者都有利的选择,而不是额外的负担。 -## 从倡议走向可测试的 contract +## 当前边界 -[DSH Community Fabric](../dsh-community-fabric/README.zh.md) 正在把这份愿景整理成可公开讨论的 Manifest、Capability、Host Descriptor 与事件 Draft。它目前只有文档,不是已经发布的标准或运行时;当前插件仍使用现有 DSH/Cordis 接口。 +当前插件使用 DSH/Cordis 已发布的公开接口。未来若建立统一的插件声明或目录,应先以真实插件、兼容测试和用户需求验证;能力声明可辅助兼容判断、用户确认与审计,但不能把同进程 JavaScript 描述成安全沙箱。只有具备真实隔离证据的 Host 才能声称权限被技术强制执行。 -Fabric 的 capability 首先用于兼容判断、用户确认和审计,不会把同进程 JavaScript 伪装成安全沙箱。只有具备真实隔离证据的 Host 才能声称权限被技术强制执行。 - -市场目前仍处于[产品与安全设计阶段](../dsh-community-market/README.zh.md),尚未提供可用页面或安装器。目录收录只代表符合目录规则,不等于安全审核或推荐。 +目前没有在线市场页面或安装器。未来的目录收录只代表满足目录规则,不等于安全审核或推荐。 ## 如何参与 - 在[插件开发](plugin-development.md)中了解插件如何编写。 -- 阅读并评论 [Community Fabric RFC 0001](../dsh-community-fabric/docs/rfcs/0001-plugin-manifest-capabilities-events.zh.md)。 - 在[用户指南](user-guide.md)中了解如何安装和管理插件。 - 通过 issue 和讨论区提出你对本倡议的意见。 diff --git a/dsh-community-fabric/LICENSE b/dsh-community-fabric/LICENSE deleted file mode 100644 index bc9f0683c5..0000000000 --- a/dsh-community-fabric/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2026 Anywhere Labs - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/dsh-community-fabric/README.i18n.yaml b/dsh-community-fabric/README.i18n.yaml deleted file mode 100644 index a358aa2a0d..0000000000 --- a/dsh-community-fabric/README.i18n.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Bilingual-pair consistency record. Both languages carry equal authority. -# Update both files and record their normalized Git blob hashes after editing either side. -README.md: 3c4cb3b775ebff482edbd16802b791c6529f1567 -README.zh.md: d9acccaada61d7ae41c06fe0b85701b80e4acf65 diff --git a/dsh-community-fabric/README.md b/dsh-community-fabric/README.md deleted file mode 100644 index 3c4cb3b775..0000000000 --- a/dsh-community-fabric/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# DSH Community Fabric - -[中文说明](README.zh.md) - -DSH Community Fabric is a proposed community interoperability standard for DSH plugins and hosts. Its goal is simple: a plugin should describe what it is and what it needs once, while Desktop, Web UI, TUI, launchers, and distribution tools interpret the same declaration consistently. - -> **Current status: Draft and documentation only.** There is no Fabric runtime, SDK, schema release, compatibility badge, or loadable plugin in this workspace yet. This repository's working plugins still use the existing DSH and Cordis APIs. - -## Why this project exists - -The DSH community now has different user interfaces, launchers, plugin collections, and distribution channels. Authors should not have to guess which host can run a plugin, and users should not have to install it before discovering that it is incompatible. - -Fabric proposes four shared building blocks: - -1. A static manifest that tools can inspect without executing plugin code. -2. Versioned capabilities that describe what a plugin requests and what a host supports. -3. Predictable activation, deactivation, and event contracts. -4. Machine-readable compatibility results and conformance tests. - -The proposal is an interoperability layer, not a replacement for DSH, Cordis, or each host's internal architecture. A host may use its native plugin system behind an adapter while exposing the same community contract to compatible plugins. - -## An important safety boundary - -Capability declarations are useful for compatibility, consent, and auditing, but they are **not automatically a security sandbox**. A trusted JavaScript plugin running in the same Node.js process may still access operating-system APIs outside the provided context. - -Only a host that implements real isolation, controlled module loading, and mediated IPC may claim that a permission is technically enforced. The standard must show users the difference between requested, granted, tested, and enforced capabilities. - -## First milestone - -The first experimental milestone is intentionally small: - -- a static JSON manifest and JSON Schema; -- a machine-readable Host Descriptor; -- required and optional capability negotiation; -- deterministic lifecycle hooks; -- one immutable `messages.observe` event; -- fixtures and a headless conformance suite. - -Mutable `before-*` events, sensitive filesystem/network permissions, rich cross-host UI, marketplace certification, and isolated execution require separate proposals and evidence. - -Community review also exposed several important problems that should not be forced into the first milestone. Remote execution and the interface currently calling it need separate identities; plugins that provide shared services need deterministic composition; and users need to know what a plugin changed and whether it cleaned up. These topics now have focused Draft RFCs so they can be reviewed and tested independently without expanding v0.1 by implication. - -## Read and participate - -- [Compatibility layer and developer framework](docs/architecture/compatibility-layer.md) -- [RFC 0001: Plugin Manifest, Capabilities, and Events](docs/rfcs/0001-plugin-manifest-capabilities-events.md) -- [RFC 0002: Runtime, Presentation, Control, Transport, and Invocation](docs/rfcs/0002-runtime-presentation-invocation-transport.md) -- [RFC 0003: Service Providers and Deterministic Composition](docs/rfcs/0003-service-providers-and-composition.md) -- [RFC 0004: Provenance, Validation, Diagnostics, and the Effect Ledger](docs/rfcs/0004-provenance-validation-and-diagnostics.md) -- [Review of Community Issue #23 and the disposition of each proposal](docs/research/community-issue-23-review.md) -- [Research: lessons from Koishi, Chrome, and VS Code](docs/research/mature-plugin-frameworks.md) -- [Research: the VS Code extension model and its RFC implications](docs/research/vscode-extension-model.md) -- [Research: what real DSH plugins need](docs/research/dsh-plugin-needs.md) -- [Existing DSH plugin development](../docs/plugin-development.en.md) -- [Community plugin ecosystem manifesto](../docs/plugin-ecosystem.en.md) - -The RFC is a discussion draft, not an official DeepSeek or DSH standard. Open an issue, start a discussion, or propose edits by pull request. Plugin authors, GUI/Web UI/TUI maintainers, launcher maintainers, market maintainers, security reviewers, and ordinary users are all invited. - -DSH Community Fabric is not affiliated with FabricMC. The name describes a community compatibility fabric around DSH. - -## License - -The proposal and future reference code are licensed under the [MIT License](LICENSE). diff --git a/dsh-community-fabric/README.zh.md b/dsh-community-fabric/README.zh.md deleted file mode 100644 index d9acccaada..0000000000 --- a/dsh-community-fabric/README.zh.md +++ /dev/null @@ -1,63 +0,0 @@ -# DSH Community Fabric - -[English](README.md) - -DSH Community Fabric 是一项面向 DSH 插件与宿主的社区互操作标准提案。目标很简单:插件只需要用同一种方式描述“我是谁、我需要什么”,Desktop、Web UI、TUI、启动器和分发工具都能一致地理解这份声明。 - -> **当前状态:Draft,仅有文档。** 这个 workspace 还没有 Fabric runtime、SDK、正式 schema、兼容认证或可加载插件。本仓库当前的插件仍使用现有 DSH 与 Cordis 接口。 - -## 为什么需要这个项目 - -DSH 社区已经出现不同界面、启动器、插件集合与分发渠道。插件作者不应该靠猜测判断一个宿主能否运行自己的插件,用户也不应该等安装失败后才知道“不兼容”。 - -Fabric 提议共同维护四块基础: - -1. 静态 manifest,让工具无需执行插件代码就能检查基本信息。 -2. 带版本的 capability,描述插件申请什么、宿主支持什么。 -3. 可预测的激活、停用与事件 contract。 -4. 机器可读的兼容结果和一致性测试。 - -这个提案是互操作层,不会替代 DSH、Cordis,也不会要求各宿主放弃自己的内部架构。宿主可以继续使用原生插件系统,只需通过 adapter 向兼容插件提供统一的社区 contract。 - -## 必须说清楚的安全边界 - -Capability 声明有助于兼容判断、用户确认和审计,但它**不会自动变成安全沙箱**。如果一个受信任的 JavaScript 插件与宿主运行在同一个 Node.js 进程中,它仍可能绕过 `ctx`,直接调用操作系统接口。 - -只有真正实现隔离执行、受控模块加载和受管 IPC 的宿主,才能声称某项权限被技术强制执行。标准必须向用户分别展示插件申请了什么、用户授权了什么、哪些组合经过实测,以及宿主是否真正隔离。 - -## 第一个里程碑 - -首个实验版本会刻意保持很小: - -- 静态 JSON manifest 与 JSON Schema; -- 机器可读的 Host Descriptor; -- required / optional capability 协商; -- 顺序确定的生命周期 hook; -- 一个不可修改的 `messages.observe` 事件; -- fixtures 与可在 headless 环境运行的一致性测试。 - -可修改的 `before-*` 事件、文件与网络等敏感权限、复杂的跨端 UI、市场认证和隔离执行,都需要独立提案与实际证据。 - -社区审查还指出了几类不能硬塞进首个里程碑的重要问题:远程执行位置与当前调用界面需要独立身份;插件提供的共享 service 需要确定性组合;用户需要知道插件实际修改了什么、停用后是否清理完整。现在这些议题都拆成独立 Draft RFC,可以分别审查和验证,不会因此暗中扩大 v0.1。 - -## 阅读与参与 - -- [兼容层与开发框架设计](docs/architecture/compatibility-layer.zh.md) -- [RFC 0001:Plugin Manifest、Capability 与事件模型](docs/rfcs/0001-plugin-manifest-capabilities-events.zh.md) -- [RFC 0002:Runtime、Presentation、Control、Transport 与 Invocation](docs/rfcs/0002-runtime-presentation-invocation-transport.zh.md) -- [RFC 0003:Service Provider 与确定性组合](docs/rfcs/0003-service-providers-and-composition.zh.md) -- [RFC 0004:溯源、验证、诊断与 Effect Ledger](docs/rfcs/0004-provenance-validation-and-diagnostics.zh.md) -- [社区 Issue #23 意见审查与逐条处置](docs/research/community-issue-23-review.zh.md) -- [调研:Koishi、Chrome 与 VS Code 的成熟模式](docs/research/mature-plugin-frameworks.zh.md) -- [调研:VS Code 扩展模型及其对 RFC 的价值](docs/research/vscode-extension-model.zh.md) -- [调研:真实 DSH 插件需要什么](docs/research/dsh-plugin-needs.zh.md) -- [当前可用的 DSH 插件开发方式](../docs/plugin-development.md) -- [DSH 插件生态倡议书](../docs/plugin-ecosystem.md) - -RFC 是社区讨论稿,不是 DeepSeek 或 DSH 官方标准。欢迎通过 issue、discussion 或 PR 提出修改;插件作者、GUI / Web UI / TUI 维护者、启动器与市场维护者、安全研究者和普通用户都可以参与。 - -DSH Community Fabric 与 FabricMC 没有关联;这里的 Fabric 表示连接 DSH 社区各宿主与插件的兼容网络。 - -## License - -提案与未来参考代码遵循 [MIT License](LICENSE)。 diff --git a/dsh-community-fabric/docs/architecture/compatibility-layer.i18n.yaml b/dsh-community-fabric/docs/architecture/compatibility-layer.i18n.yaml deleted file mode 100644 index dc687728e6..0000000000 --- a/dsh-community-fabric/docs/architecture/compatibility-layer.i18n.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Bilingual-pair consistency record. Both languages carry equal authority. -# Update both files and record their normalized Git blob hashes after editing either side. -compatibility-layer.md: b17e91005aae003553c09481f1989f7a61163ea6 -compatibility-layer.zh.md: 98a83b9ea6432c5f98700f84b37f5ba1693400b2 diff --git a/dsh-community-fabric/docs/architecture/compatibility-layer.md b/dsh-community-fabric/docs/architecture/compatibility-layer.md deleted file mode 100644 index b17e91005a..0000000000 --- a/dsh-community-fabric/docs/architecture/compatibility-layer.md +++ /dev/null @@ -1,365 +0,0 @@ -# Fabric Compatibility Layer and Developer Framework - -English | [中文](compatibility-layer.zh.md) - -Status: Design Draft. This document defines the central product boundary of DSH Community Fabric: how plugins perform real work through stable capabilities without importing official DSH source internals or private services. - -## 1. The real problem - -A manifest alone is insufficient. If a plugin declares its needs and then imports upstream internals, reads private objects, or patches source, an upstream update still breaks the ecosystem. - -Fabric needs a stable middle layer: - -```text -Plugin code - ↓ depends only on Fabric SDK, DTOs, and capabilities -Capability Broker - ↓ negotiation, grants, lifecycle, resource ownership -Versioned DSH Adapter - ↓ maps stable contracts to official services, events, slots, and profile composition -Official DSH / Cordis runtime -``` - -Plugins do not know the concrete DSH version. The Adapter is the only layer allowed to absorb upstream changes. When semantics cannot be preserved, it disables the capability with a reason rather than returning an approximate success. - -## 2. Five architecture invariants - -1. **Fabric entrypoints depend only on Fabric contracts.** A conforming entrypoint has no runtime dependency on DSH, Cordis, Desktop, or Adapter packages. It receives no upstream implementation object and does not inspect private services or monkey patch official functions. Only the Adapter may import the upstream runtime. -2. **Public boundaries carry stable data only.** APIs use versioned plain DTOs, opaque IDs, and typed errors rather than upstream class instances, database rows, or internal event objects. -3. **Every resource belongs to an activation scope.** Listeners, commands, timers, streams, and background operations are owned and released when that plugin instance deactivates. -4. **Adapters fail closed.** A missing equivalent upstream behavior is `unsupported`, not an approximation through private patches. -5. **Portable core and Host extensions stay separate.** Proven cross-Host behavior uses standard namespaces; tray, Electron, DOM, or TUI keymap behavior uses organization-namespaced extensions. - -These are supported-contract and conformance rules. In trusted in-process mode, lint and review can detect direct upstream dependencies, but they cannot act like an operating-system sandbox against malicious code. - -## 3. Layer responsibilities - -```mermaid -flowchart TB - Plugin["Fabric plugin\nmanifest + plugin code"] - SDK["Fabric SDK\ntypes, definePlugin, generated context"] - Broker["Capability Broker\nvalidation, negotiation, grants, lifecycle"] - Services["Versioned capability contracts\nsession, tools, storage, UI..."] - Adapter["DSH Adapter\nversion-specific mapping"] - Official["Official DSH / Cordis\nservices, events, slots, profile composition"] - TestKit["Schemas + test kit + conformance fixtures"] - - Plugin --> SDK --> Broker --> Services --> Adapter --> Official - TestKit -. validates .-> Plugin - TestKit -. verifies .-> Broker - TestKit -. verifies .-> Adapter -``` - -### 3.1 Contracts and schemas - -This layer defines machine-readable facts only: Manifest, Host Descriptor, capability names and versions, DTOs, event payloads, error codes, and conformance fixtures. It contains no DSH-version checks or Electron code. - -### 3.2 Plugin SDK - -The SDK provides types, `definePlugin()`, activation context, AbortSignal, test fakes, and small helpers. It never exports the official Cordis Context or a generic `get(name)` that can retrieve arbitrary Host services. - -### 3.3 Capability Broker - -The Broker is the framework core. It: - -- validates manifests and entrypoints; -- negotiates required and optional capabilities against the Host Descriptor; -- evaluates user and policy grants; -- constructs the minimum activation context; -- owns listeners, commands, timers, streams, and operations; -- aborts, drains, and releases resources during deactivation; -- converts implementation failures to stable Fabric errors; -- records compatibility and audit events without sensitive payloads. - -It contains no special cases for a particular DSH release. - -### 3.4 DSH Adapter - -The Adapter is the only upstream translation layer. Each Adapter release declares an explicit DSH runtime range and maps capabilities to official mechanisms. - -Implementation priority is: - -1. documented public services, events, slots, routes, and profile composition; -2. published but unstable contracts, pinned and covered by real integration tests; -3. private source paths, monkey patches, or modified upstream files never become stable capabilities. A necessary experiment belongs in an explicit vendor extension. - -Adapter tests cover both fake contracts and the real pinned DSH runtime. Upstream upgrades pass the Adapter compatibility matrix before its support range changes. - -### 3.5 Host integration - -A GUI, Web UI, TUI, or launcher selects and assembles the Broker and Adapter, publishes an honest Host Descriptor, and supplies authorization and error UX. A Host need not implement every capability; missing support is valid, false support is not. - -## 4. Scope: what the framework should support - -Fabric should not cover everything at once. It starts with the most common and stable 80 percent and expands through versioned modules. - -The exact experimental v0.1 surface is deliberately small. `host.info`, `log`, and lifecycle cancellation are baseline context available to every activation. The first negotiated capabilities are `storage.local`, `commands`, and one immutable `messages.observe` event. Everything else in the tables below is a planned candidate, not part of v0.1 until its own contract and fixtures land. - -This scope is informed by three source studies rather than an invented API wishlist: [mature plugin framework patterns](../research/mature-plugin-frameworks.md), the detailed [VS Code extension model](../research/vscode-extension-model.md), and [twelve representative DSH plugins](../research/dsh-plugin-needs.md). Those studies also define the seams that v0.1 must preserve for later Host/Client faces, typed renderers, cross-face messaging, interceptors, context contributions, and mediated system access. - -[Community Issue #23](https://github.com/omdsh-dev/community/issues/23) supplied concrete counterexamples after this architecture was drafted. The [disposition record](../research/community-issue-23-review.md) explains each decision. Follow-up Drafts now isolate [Runtime/Presentation invocation](../rfcs/0002-runtime-presentation-invocation-transport.md), [service composition](../rfcs/0003-service-providers-and-composition.md), and [provenance plus effect ownership](../rfcs/0004-provenance-validation-and-diagnostics.md) so none of them silently expands the experimental v0.1 contract. - -### 4.1 Portable Core - -| Capability | Operations | Constraints | -| --- | --- | --- | -| `host.info` | Host ID/version/platform, standard versions, execution mode. | Read-only, no upstream objects. | -| `log` | Structured debug/info/warn/error. | Plugin ID attached; no sensitive payload by default. | -| `lifecycle` | Activation signal, deactivation, health state. | Resources belong to activation scope. | -| `storage.local` | Plugin-private get/set/delete/list. | Quota, schema version, plugin namespace. | -| `settings.schema` | Declare settings, read granted values, observe changes. | Host renders UI; no DOM access. | -| `commands` | Bind handlers to commands declared in the manifest. | IDs belong to the plugin namespace; discoverable metadata has one source of truth. | - -`storage.local` is a Broker-owned, plugin-namespaced KV contract backed by a narrow persistence port. A DSH Adapter must not expose an upstream storage hub or database object as its implementation. - -### 4.2 DSH domain capabilities - -| Capability | Planned operations | Evidence required before stability | -| --- | --- | --- | -| `sessions.read` | List/get canonical snapshots, paginate, observe create/close. | DTO, pagination, privacy redaction, cross-version mapping. | -| `messages.observe` | Immutable sent/received observation. | Per-session order, backpressure, sensitive scopes. | -| `sessions.actions` | Create, send, cancel, resume. | Grants, idempotency, operation outcomes, recovery. | -| `tools.register` | Register schema-defined tools with cancellation. | I/O schemas, timeout, audit, repeated-call semantics. | -| `tools.observe` | Observe started/result/failed. | Redaction, identity, event order. | -| `models.read` | Read redacted model capabilities and selection. | Never expose credentials or provider internals. | -| `models.provider` | Register a model provider. | Separate RFC for streams, tools, usage, and errors. | -| `profiles.read` | Read current and available work profiles. | May be unsupported where Host has no profile concept. | -| `profiles.select` | Request an ordered switch. | Consent, restart boundary, rollback. | - -Read-only session data can still be highly sensitive. It needs explicit grants, scopes, and redaction rather than being labeled low-risk merely because it does not mutate state. - -### 4.3 UI extension layers - -UI is not one universal renderer. Fabric separates four layers: - -1. **Declarative contributions** for commands, settings schemas, menus, status, notifications, theme tokens, and small forms. The Host owns presentation, localization, accessibility, ordering, and conflicts. -2. **Typed providers and named renderers** for tool results, message content, composer accessories, file viewers, session trees, and similar domain surfaces. Each extension point defines input DTOs, cardinality, priority, fallback, and lifecycle. -3. **Sandboxed rich views** for GenUI, dashboards, editors, visualizations, or complete workbenches. They use a separate Client/Worker face, a versioned message bridge, approved resources, theme tokens, and explicit Host placement. -4. **Host extensions** for raw DOM, Electron, native widgets, terminal protocols, and other behavior without portable semantics. - -High-portability candidates include `ui.notification`, `ui.status`, settings schemas, command metadata, and small forms. A common `ui.panel.basic` remains a later prototype, not proof that arbitrary GUI UI can run unchanged in a TUI. - -Fabric does not expose raw DOM, React components, Electron BrowserWindow, or TUI screen handles in its portable API. Rich views and Host extensions need separate specifications and honest compatibility labels. - -### 4.4 Business behavior protocols - -Fabric does not use one stringly typed event bus for every operation: - -- **immutable observation streams** report canonical message, session, tool, or job facts without changing the source operation; -- **commands and actions** are authorized request/result operations with cancellation, idempotency, stable errors, and audit identity; -- **ordered interceptor pipelines** may allow, deny, or narrowly rewrite an operation only after ordering, timeout, failure, conflict, privacy, and reentrancy semantics are specified; -- **context-contribution pipelines** collect bounded, attributable, budgeted memory or instruction fragments and freeze the result before execution; -- **durable jobs** define identity, progress, checkpoint, cancellation, retry, ownership, and restart behavior. - -Only immutable `messages.observe` belongs to v0.1. Interceptors, context contributions, and jobs require independent RFCs and conformance fixtures. - -### 4.5 Sensitive mediated capabilities - -`net.fetch`, `workspace.read/write`, clipboard, secrets, process, terminal, and package management are mediated operations: scoped input, bounded output, cancellation, audit, and renewed consent when permissions expand. - -In trusted in-process mode, those grants still are not a hard sandbox. Real enforcement requires isolated execution. Raw shell, unrestricted process spawning, raw Electron, and unrestricted filesystem access do not belong to Portable Core. - -### 4.6 Host extensions - -Behavior without cross-Host semantics uses organization namespaces: - -- `x-ai.anywhere.desktop.tray` -- `x-org.example.tui.keymap` -- `x-org.example.web.panel` - -Extensions still have schemas, versions, and lifecycle rules but are not described as ecosystem-portable. Standardization requires real implementations from at least two independent Hosts. - -## 5. Canonical DTOs isolate upstream change - -Plugins receive Fabric DTOs, never official class instances or database shapes. DTOs are: - -- JSON/structured-clone serializable; -- schema- and version-defined; -- based on opaque IDs rather than parseable internal paths or keys; -- explicit about time, pagination, ordering, and missing values; -- bounded by default and cursor-paginated; -- data-minimized, expanding only with grants; -- immutable in events; -- explicit operation outcomes for mutations rather than internal controllers; -- backed by stable errors such as `unsupported`, `permission-denied`, `aborted`, `conflict`, and `upstream-unavailable`. - -The Adapter converts upstream data in both directions. If conversion loses required semantics, it removes the capability from the Host Descriptor. - -## 6. Target developer experience - -These are target shapes, not published commands or APIs. - -### 6.1 Project layout - -```text -my-fabric-plugin/ - dsh-plugin.json # the single static declaration - src/index.ts # imports only the Fabric SDK - tests/plugin.spec.ts - package.json -``` - -### 6.2 Workflow - -```sh -yarn dlx dsh-community-fabric init -yarn fabric validate # schema, ID, entrypoint, capability versions -yarn fabric generate # exact context types from the manifest -yarn fabric test # fake Host, lifecycle, capability fixtures -yarn fabric dev --host web # connect to an explicit development Host integration -yarn fabric pack # static manifest and inspectable package -``` - -Names are not frozen. The invariant is that one manifest drives validation, generated types, market compatibility, and Host negotiation without duplicated configuration. - -### 6.3 Plugin code - -```ts -import { definePlugin } from 'dsh-community-fabric/sdk' - -export default definePlugin(async (ctx) => { - ctx.commands.handle('com.example.hello.open', async () => { - ctx.log.info('Hello from Fabric') - }) - - ctx.messages.onReceived(async (message) => { - await ctx.storage.local.set('lastMessageId', message.id) - }) - - // Registrations are activation-scoped and released automatically. - // ctx.signal aborts when this instance deactivates. -}) -``` - -This demonstrates the intended shape only. The manifest owns command metadata; code only binds a handler by ID. The SDK context is generated from the manifest: undeclared capabilities do not exist in its type and fail at runtime. Optional capabilities remain optional members until `ctx.capabilities.has(name)` narrows them. There is no unnegotiated `ctx.get(anyName)` escape hatch. - -The v0.1 manifest-to-SDK mapping has one canonical path: - -| Contract item | SDK member | -| --- | --- | -| baseline `host.info` | `ctx.host` | -| baseline `log` | `ctx.log` | -| baseline lifecycle cancellation | `ctx.signal` | -| `storage.local` | `ctx.storage.local` | -| `commands` | `ctx.commands.handle(id, handler)` | -| `messages.observe` | `ctx.messages.onReceived(handler)` | - -Capability IDs describe negotiated contracts; SDK member names provide ergonomic typed access. This mapping is generated rather than reconfigured by each plugin. - -### 6.4 Understandable failures - -Plugin authors and users should not receive upstream stack traces or internal service names. Stable failures cover invalid manifests, incompatible API ranges, missing required capabilities, denied grants, temporarily unavailable adapters, aborted/timed-out/conflicting operations, and activation failures. - -Every failure has a machine code, developer detail, and localizable user summary without leaking tokens, message text, or local paths. Adapter diagnostics retain the original upstream cause behind a correlation ID in Host-owned logs; that cause never crosses into the plugin-facing contract. - -## 7. Host and Adapter developer experience - -Host maintainers should not reimplement manifest parsing, SemVer, grant state machines, or lifecycle ownership. Future reference modules may be organized as: - -```text -schema/ static schemas and validators; no Host or DSH dependency -contract/ DTOs, errors, capability interfaces, and Adapter SPI -sdk/ definePlugin and generated plugin-side context -broker/ negotiation, grants, activation scope, and resource ownership -dsh-adapter/ the only layer allowed to import DSH/Cordis runtime packages -testkit/ headless fixtures and shared conformance suites -cli/ init, validate, generate, test, and pack -``` - -Exact package names and subpaths remain unfrozen. `dsh-community-fabric` may be a lightweight plugin-facing entry, but it must not re-export the Broker or Adapter by default. Dependency direction stays one-way: schemas have no Host dependency; contracts depend only on schemas; the SDK depends on contracts; the Broker depends on schemas and contracts but not DSH; only `dsh-adapter` imports upstream runtime packages. Production Host Descriptors belong to the schema/contract boundary, while authorization is a Broker responsibility rather than a test-kit feature. - -Tools generate the Host Descriptor from registered implementations to prevent documentation drift. Each capability implementation runs the shared headless contract suite from `testkit`; real DSH integration tests remain owned by `dsh-adapter`. A complete Host additionally runs lifecycle and cross-capability integration tests. - -## 8. Versions and upstream upgrades - -Adapters maintain an explicit matrix: - -| Fabric API | Adapter | DSH runtime | Status | -| --- | --- | --- | --- | -| 0.1.x | adapter-dsh 0.1.x | explicit range | experimental / tested / unsupported | - -Upgrade flow: - -1. pin the new upstream release and run real Adapter contract tests; -2. inspect DTO and behavior semantics for every capability; -3. update only mappings whose semantics remain valid; -4. remove unsupported capabilities from the Host Descriptor with migration notes; -5. change the standard contract only through community RFC and compatibility-window rules. - -Plugins do not publish one variant per DSH release. Adapters do not manufacture compatibility by swallowing errors. - -## 9. Migrating existing plugins - -Migration is capability-by-capability, not a flag day: - -1. generate a static manifest documenting current dependencies and Host restrictions; -2. inspect direct upstream imports, private services, source patches, and global side effects; -3. replace supported behavior with Fabric contracts; -4. retain unsupported behavior as explicit legacy or vendor extensions; -5. verify against two Host products/integrations or one Host plus the fake conformance suite; -6. only then publish a Fabric compatibility result. - -Existing `cordis.patch.yml` is official declarative composition rather than a source patch and can remain the Adapter or bundle assembly entry. Existing DSH plugins do not become invalid when Fabric appears. - -## 10. Delivery stages - -### Stage A: infrastructure that never executes plugins - -- Manifest and Host Descriptor Schemas; -- pure negotiator; -- canonical errors; -- fixtures, lint, and package inspection; -- documentation and RFC governance. - -### Stage B: minimal Broker and trusted DSH Adapter - -- activation scope, AbortSignal, automatic resource ownership; -- the exact v0.1 baseline and negotiated set: `host.info`, `log`, lifecycle cancellation, `storage.local`, `commands`, and immutable `messages.observe`; -- real contract tests against a pinned DSH release; -- explicit `trusted-in-process` labeling. -- after the full v0.1 surface exists, interoperability evidence from two Host products or integrations, which may share one versioned DSH Adapter. - -### Stage C: DSH domain capabilities - -- canonical session and tool DTOs beyond the v0.1 message event; -- additional immutable observation events; -- user-triggered session actions; -- tool registration; -- typed Host/Client bridge and static-resource transport; -- mediated files/artifacts, network, and secret references; - -### Stage D: UI and sensitive capabilities - -- declarative contributions and typed renderer prototypes; -- one sandboxed rich-view prototype; -- context-contribution and ordered-interceptor RFCs; -- process/PTY/job and transactional package-management contracts; -- permissions UX; -- an isolated-runner prototype. - -Each stage is independently useful and testable. Early demos never expose raw upstream context to tell a more complete story. - -## 11. Success criteria - -Fabric succeeds when: - -- an ordinary plugin performs real work without importing upstream runtime; -- two Host products statically evaluate and correctly activate or reject the same package; -- most fixes for a DSH upgrade remain in the Adapter; -- plugins receive stable DTOs and errors rather than internal objects; -- users see clear missing-capability reasons instead of post-install crashes; -- the reference Broker, Adapter, and plugin run conformance tests in headless CI; -- documentation never presents compatibility declarations as security review or hard permission enforcement. - -## 12. Community decisions still needed - -1. After v0.1, which DSH domain capability should come next: `sessions.read`, `sessions.actions`, or `tools.register`? -2. How does the manifest generate precise TypeScript context without source drift? -3. How are Host, Client, and isolated Worker faces split and connected? -4. Which upstream public contracts may an Adapter use, and where is the experimental-extension boundary? -5. Are grants scoped by plugin, profile, workspace, session, or device? -6. Which DTO content is redacted by default? -7. When does a vendor capability qualify for the portable standard? -8. Who publishes and revokes a Fabric compatibility result? - -Separate RFCs, fixtures, and prototypes should decide these questions rather than accidental behavior in one reference implementation. diff --git a/dsh-community-fabric/docs/architecture/compatibility-layer.zh.md b/dsh-community-fabric/docs/architecture/compatibility-layer.zh.md deleted file mode 100644 index 98a83b9ea6..0000000000 --- a/dsh-community-fabric/docs/architecture/compatibility-layer.zh.md +++ /dev/null @@ -1,373 +0,0 @@ -# Fabric 兼容层与开发框架设计 - -[English](compatibility-layer.md) | 中文 - -状态:Design Draft。本文描述 DSH Community Fabric 最重要的产品边界:插件如何在不直接依赖官方源码或内部 service 的情况下,使用稳定能力完成真实工作。 - -## 1. 我们真正要解决的问题 - -仅有 manifest 还不够。Manifest 可以告诉 Host“插件想做什么”,但如果插件随后仍然直接 import 官方内部模块、读取私有对象或 patch 源码,上游一更新,生态仍会整体破裂。 - -Fabric 必须提供一层稳定中间层: - -```text -插件代码 - ↓ 只依赖稳定 Fabric SDK、DTO 与 capability -Capability Broker - ↓ 协商、授权、生命周期、资源 ownership -版本化 DSH Adapter - ↓ 把稳定 contract 映射到官方 service / event / slot / profile composition -官方 DSH / Cordis runtime -``` - -插件不感知具体 DSH 版本。Adapter 是唯一允许吸收上游变化的地方。无法保持语义时,Adapter 必须关闭对应 capability 并给出原因,不能返回“看起来成功”的近似结果。 - -## 2. 五条架构不变量 - -1. **Fabric entrypoint 只依赖 Fabric contract。** Fabric 标准 entrypoint 运行时不依赖 DSH、Cordis、Desktop 或 Adapter package,不获取上游实现对象,也不读取私有 service 或 monkey patch 官方函数。只有 Adapter 可以 import 上游 runtime。 -2. **公开边界只传稳定数据。** API 使用版本化 plain DTO、opaque ID 和 typed error,不把上游 class instance、数据库 row 或内部事件对象泄漏给插件。 -3. **所有资源都有 activation scope。** Event listener、command、timer、stream 和后台 operation 都归当前插件实例所有,deactivate 时自动取消和释放。 -4. **Adapter fail closed。** 上游缺少等价能力时报告 unsupported,不靠私有 patch 猜测语义。 -5. **可移植核心与宿主扩展分开。** 跨 Host 能证明一致的能力进入标准命名空间;托盘、Electron、DOM、TUI keymap 等进入组织命名空间 extension。 - -这些规则首先是受支持 contract 和一致性要求。在 trusted in-process 档位中,lint 和审核可以发现直接上游依赖,但不能像操作系统沙箱一样阻止恶意代码绕过。 - -## 3. 分层职责 - -```mermaid -flowchart TB - Plugin["Fabric plugin\nmanifest + plugin code"] - SDK["Fabric SDK\ntypes, definePlugin, generated context"] - Broker["Capability Broker\nvalidation, negotiation, grants, lifecycle"] - Services["Versioned capability contracts\nsession, tools, storage, UI..."] - Adapter["DSH Adapter\nversion-specific mapping"] - Official["Official DSH / Cordis\nservices, events, slots, profile composition"] - TestKit["Schemas + test kit + conformance fixtures"] - - Plugin --> SDK --> Broker --> Services --> Adapter --> Official - TestKit -. validates .-> Plugin - TestKit -. verifies .-> Broker - TestKit -. verifies .-> Adapter -``` - -### 3.1 Contract 与 Schema - -这一层只定义机器可读事实:manifest、Host Descriptor、capability 名称与版本、DTO、事件 payload、错误码和 conformance fixtures。它不包含 DSH 版本判断或 Electron 代码。 - -### 3.2 Plugin SDK - -SDK 提供类型、`definePlugin()`、activation context、AbortSignal、测试 fake 和少量辅助函数。它不导出官方 Cordis Context,也不允许插件通过一个通用 `get(name)` 获取任意 Host service。 - -### 3.3 Capability Broker - -Broker 是框架核心,负责: - -- 校验 manifest 与 entrypoint; -- 对照 Host Descriptor 协商 required / optional capability; -- 执行用户或策略授权; -- 为本次 activation 构造最小 context; -- 跟踪 listener、command、timer、stream 和 operation; -- 在 deactivate 时 abort、drain 和释放资源; -- 把实现错误转换成稳定 Fabric error; -- 记录不包含敏感 payload 的兼容与审计事件。 - -Broker 不应包含某个 DSH 版本的特殊判断。 - -### 3.4 DSH Adapter - -Adapter 是唯一的上游翻译层。每个 Adapter 版本明确声明支持的 DSH runtime 范围,并把 capability 映射到官方公开机制。 - -实现优先级: - -1. 官方公开 service、event、slot、route 与 profile composition; -2. 已发布但尚不稳定的公共 contract,必须固定版本并有真实集成测试; -3. 私有源码路径、猴子补丁或修改上游文件不得成为稳定 capability。若实验确实需要,应放在显式 experimental vendor extension 中。 - -Adapter 测试必须同时覆盖 fake contract 和真实、固定版本的 DSH runtime。上游升级先在 Adapter compatibility matrix 中验证,再改变支持范围。 - -### 3.5 Host integration - -GUI、Web UI、TUI 或启动器负责选择并装配 Broker 与 Adapter,发布真实 Host Descriptor,并提供授权和错误提示界面。Host 不必实现所有 capability;缺失是合法状态,伪装支持不是。 - -## 4. 支持范围:做到什么程度 - -Fabric 不追求“一开始覆盖一切”。它应该先覆盖插件最常见、最容易稳定抽象的 80%,剩余能力通过版本化模块逐步进入。 - -实验性 v0.1 的精确范围会刻意保持很小。`host.info`、`log` 和生命周期取消信号是每次 activation 都具备的基础 context;首批需要协商的 capability 只有 `storage.local`、`commands` 和一个不可修改的 `messages.observe` 事件。下表中的其他项目都是规划候选,在独立 contract 与 fixtures 落地前不属于 v0.1。 - -这个范围不是凭空列出的 API 愿望清单,而是来自三份源码调研:[成熟插件框架模式](../research/mature-plugin-frameworks.zh.md)、详细的 [VS Code 扩展模型](../research/vscode-extension-model.zh.md)和[十二个代表性 DSH 插件](../research/dsh-plugin-needs.zh.md)。这些调研也规定了 v0.1 必须为后续 Host/Client face、强类型 renderer、跨 face 消息、拦截器、上下文贡献和受控系统访问保留哪些接缝。 - -这份架构成稿后,[社区 Issue #23](https://github.com/omdsh-dev/community/issues/23) 又提供了具体反例。[意见处置记录](../research/community-issue-23-review.zh.md)解释了每项决定;后续 Draft 分别讨论 [Runtime/Presentation invocation](../rfcs/0002-runtime-presentation-invocation-transport.zh.md)、[service composition](../rfcs/0003-service-providers-and-composition.zh.md)和[溯源与 effect ownership](../rfcs/0004-provenance-validation-and-diagnostics.zh.md),避免其中任何一项暗中扩大实验性 v0.1 contract。 - -### 4.1 Portable Core:所有兼容 Host 都应理解 - -| Capability | 支持的操作 | 约束 | -| --- | --- | --- | -| `host.info` | 读取 Host ID、版本、平台、标准版本与执行档位。 | 只读、无上游对象。 | -| `log` | 结构化 debug/info/warn/error。 | 自动附插件 ID;禁止默认记录敏感 payload。 | -| `lifecycle` | activation signal、deactivate、健康状态。 | 资源归 activation scope。 | -| `storage.local` | 插件私有 get/set/delete/list。 | 配额、schema version、插件命名空间。 | -| `settings.schema` | 声明设置 schema、读取获准值、观察变更。 | Host 决定呈现,不给 DOM。 | -| `commands` | 为 manifest 中声明的命令绑定 handler。 | ID 必须属于插件命名空间;可发现元数据只有一个权威来源。 | - -`storage.local` 是由 Broker 拥有、按插件隔离命名空间的 KV contract,底层只依赖窄的 persistence port。DSH Adapter 不能把上游 storage hub 或数据库对象直接暴露成它的实现。 - -### 4.2 DSH Domain:由 Adapter 提供的标准领域能力 - -| Capability | 计划操作 | 进入稳定标准前需要证明 | -| --- | --- | --- | -| `sessions.read` | list/get canonical snapshot、分页读取、观察创建/关闭。 | DTO、分页、隐私裁剪、跨版本 mapping。 | -| `messages.observe` | 观察不可变的 sent/received 事件。 | 每 session 顺序、回压、敏感内容 scope。 | -| `sessions.actions` | create、send、cancel、resume 等用户可见操作。 | 权限、幂等、operation result、失败恢复。 | -| `tools.register` | 注册 schema 化工具并接收取消信号。 | input/output schema、超时、审计、重复调用语义。 | -| `tools.observe` | 观察 tool started/result/failed。 | 脱敏、调用身份和 event ordering。 | -| `models.read` | 枚举经过裁剪的模型能力与当前选择。 | 不暴露 credential 或 provider 私有对象。 | -| `models.provider` | 注册模型 provider。 | 独立 RFC;stream、tool calling、usage、错误语义复杂。 | -| `profiles.read` | 查看可用工作配置和当前选择。 | profile 是 Host 概念时允许 unsupported。 | -| `profiles.select` | 请求有序切换。 | 用户确认、重启边界、回滚。 | - -Session 读取即使“只读”也可能高度敏感,仍需明确授权、scope 和裁剪,不能因为不修改数据就标为低风险。 - -### 4.3 UI 扩展分层 - -UI 不是一个万能 renderer。Fabric 把它分为四层: - -1. **声明式贡献**:命令、设置 schema、菜单、状态、通知、主题 token 和小型表单。宿主拥有呈现、国际化、无障碍、顺序和冲突处理。 -2. **强类型 Provider 与命名 Renderer**:工具结果、消息内容、输入框附件、文件查看器、会话树等有明确业务含义的界面。每个扩展点定义输入 DTO、数量、优先级、fallback 和生命周期。 -3. **隔离富视图**:GenUI、看板、编辑器、可视化和完整工作台。它们运行在独立 Client/Worker face,通过版本化消息桥和被批准的资源工作,并由宿主决定位置和主题。 -4. **宿主扩展**:raw DOM、Electron、原生控件、终端协议和其他无法保持跨宿主语义的能力。 - -高可移植性候选包括 `ui.notification`、`ui.status`、设置 schema、命令元数据和小型表单。公共 `ui.panel.basic` 仍是以后需要验证的原型,不能用来证明任意 GUI 都能原样运行在 TUI。 - -Fabric 的 portable API 不提供 raw DOM、React component、Electron BrowserWindow 或 TUI screen handle。富视图和宿主扩展需要独立规范和诚实的兼容标签。 - -### 4.4 业务行为协议 - -Fabric 不会用一个只有字符串和 unknown payload 的事件总线承载所有操作: - -- **不可变观察流**只报告标准 message、session、tool 或 job 事实,不能改变原操作; -- **命令与动作**是有授权、取消、幂等、稳定错误和审计身份的 request/result 操作; -- **有序拦截器流程**只有在顺序、超时、失败、冲突、隐私和重入语义都明确后,才允许 allow、deny 或有限 rewrite; -- **上下文贡献流程**收集有界、有来源、有预算的记忆或指令片段,并在执行前冻结结果; -- **持久任务**定义身份、进度、checkpoint、取消、重试、所有者和重启行为。 - -v0.1 只包含不可变 `messages.observe`。拦截器、上下文贡献和持久任务必须有独立 RFC 与 conformance fixture。 - -### 4.5 Sensitive mediated capabilities - -`net.fetch`、`workspace.read/write`、clipboard、secret、process、terminal 和 package management 必须是受管操作:输入有 scope、输出有界、支持取消、产生审计,并在权限新增时重新确认。 - -在 trusted in-process 模式中,这些授权仍不是强沙箱。真正的权限强制需要 isolated execution。`process.spawn`、任意 shell、原始 Electron 和无范围文件系统不进入 portable core。 - -### 4.6 Host extensions - -无法跨 Host 保持语义的能力使用组织命名空间: - -- `x-ai.anywhere.desktop.tray` -- `x-org.example.tui.keymap` -- `x-org.example.web.panel` - -Extension 仍应有 schema、版本和生命周期,但不会被描述为全生态可移植能力。标准化必须由至少两个独立 Host 的真实实现推动,而不是把一个产品私有 API 直接改名。 - -## 5. Canonical DTO:隔离上游变化的关键 - -插件只接收 Fabric DTO,不接收官方 class instance 或数据库结构。DTO 应遵循: - -- 可 JSON / structured-clone 序列化; -- 字段有 schema 和版本; -- ID opaque,插件不能解析内部路径或数据库 key; -- 时间、分页、排序和缺失值语义明确; -- 列表默认有界并支持 cursor; -- 敏感内容默认最小化,按 grant 扩大 scope; -- event payload 不可变; -- mutation 返回明确 operation outcome,不返回内部 controller; -- 错误转换为稳定 code,例如 `unsupported`、`permission-denied`、`aborted`、`conflict`、`upstream-unavailable`。 - -当上游 DTO 变化时,Adapter 完成双向转换。若转换会丢失标准要求的语义,Adapter 必须取消该 capability 的支持声明。 - -## 6. 目标开发体验 - -以下是目标体验,不是当前已经发布的命令或 API。 - -### 6.1 项目结构 - -```text -my-fabric-plugin/ - dsh-plugin.json # 唯一静态声明 - src/index.ts # 只 import Fabric SDK - tests/plugin.spec.ts - package.json -``` - -### 6.2 开发流程 - -```sh -yarn dlx dsh-community-fabric init -yarn fabric validate # schema、ID、entrypoint、capability 版本 -yarn fabric generate # 从 manifest 生成精确 context 类型 -yarn fabric test # fake Host + lifecycle + capability fixtures -yarn fabric dev --host web # 连接明确的开发 Host integration -yarn fabric pack # 产出静态 manifest 与可审查包 -``` - -命令名称尚未冻结。关键是同一份 manifest 同时驱动静态校验、类型生成、市场兼容判断和 Host 协商,避免配置重复。 - -### 6.3 插件代码 - -```ts -import { definePlugin } from 'dsh-community-fabric/sdk' - -export default definePlugin(async (ctx) => { - ctx.commands.handle('com.example.hello.open', async () => { - ctx.log.info('Hello from Fabric') - }) - - ctx.messages.onReceived(async (message) => { - await ctx.storage.local.set('lastMessageId', message.id) - }) - - // registrations are activation-scoped and are released automatically. - // ctx.signal aborts when this instance is deactivated. -}) -``` - -这个示例只表达希望达到的形状。命令元数据以 manifest 为权威,代码只按 ID 绑定 handler。实际 SDK 必须由 manifest 生成精确 context:未声明的 capability 在类型中不存在,在运行时调用也会失败;optional capability 在 `ctx.capabilities.has(name)` 完成类型收窄前仍是可选成员。不能提供绕过协商的 `ctx.get(anyName)`。 - -v0.1 的 manifest 到 SDK 映射只有一条规范路径: - -| Contract 项 | SDK member | -| --- | --- | -| 基础 `host.info` | `ctx.host` | -| 基础 `log` | `ctx.log` | -| 基础 lifecycle cancellation | `ctx.signal` | -| `storage.local` | `ctx.storage.local` | -| `commands` | `ctx.commands.handle(id, handler)` | -| `messages.observe` | `ctx.messages.onReceived(handler)` | - -Capability ID 描述协商 contract,SDK member 提供符合 TypeScript 习惯的类型化入口。这份映射由工具生成,不由每个插件重复配置。 - -### 6.4 可理解的失败 - -插件作者和用户不应该看到上游 stack 或内部 service 名。框架提供稳定错误: - -- manifest 无效; -- Host API 版本不兼容; -- required capability 缺失; -- capability 未授权; -- Adapter 暂时不可用; -- operation 被取消、超时或冲突; -- plugin activation 失败。 - -每个错误同时包含机器 code、面向开发者的 detail 和可本地化的用户摘要,但不能泄漏 token、消息正文或本地路径。Adapter 诊断会在 Host 自有日志中通过 correlation ID 保留原始上游 cause;这个 cause 不会跨入插件侧 contract。 - -## 7. Host 与 Adapter 开发体验 - -Host 维护者不应重复实现 manifest parser、SemVer、授权状态机或生命周期 Broker。Fabric reference packages 最终可拆为: - -```text -schema/ 静态 schema 与校验器;不依赖 Host 或 DSH -contract/ DTO、错误、capability interface 与 Adapter SPI -sdk/ definePlugin 与插件侧 generated context -broker/ negotiation、grant、activation scope 与 resource ownership -dsh-adapter/ 唯一允许 import DSH/Cordis runtime package 的层 -testkit/ headless fixtures 与共享 conformance suite -cli/ init、validate、generate、test 与 pack -``` - -具体 npm package/subpath 在 RFC 接受前不冻结。`dsh-community-fabric` 可以作为轻量的插件侧统一入口,但默认不能重新导出 Broker 或 Adapter。依赖方向保持单向:schema 不依赖 Host;contract 只依赖 schema;SDK 依赖 contract;Broker 依赖 schema 与 contract,但不依赖 DSH;只有 `dsh-adapter` import 上游 runtime package。生产 Host Descriptor 属于 schema/contract 边界,授权属于 Broker 职责,不能藏在测试工具中。 - -Host 注册 capability implementation 后,由工具生成 Host Descriptor,避免文档与实际实现漂移。每项实现运行 `testkit` 提供的同一套 headless contract suite;真实 DSH integration test 仍由 `dsh-adapter` 持有。完整 Host 再运行生命周期和跨 capability integration tests。 - -## 8. 版本与上游升级 - -Adapter 维护显式 compatibility matrix: - -| Fabric API | Adapter | DSH runtime | 状态 | -| --- | --- | --- | --- | -| 0.1.x | adapter-dsh 0.1.x | 明确范围 | experimental / tested / unsupported | - -升级流程: - -1. 固定新上游版本,运行真实 Adapter contract tests; -2. 检查每项 capability 的 DTO 与行为语义; -3. 只修改 Adapter 能保持的 mapping; -4. 无法保持的能力从 Host Descriptor 移除,并给出迁移说明; -5. 标准 contract 只有在社区 RFC 和兼容窗口流程下才发生 breaking change。 - -插件无需为每个 DSH 版本发布变体。Adapter 也不能通过吞掉错误来制造“版本兼容”。 - -## 9. 现有插件迁移 - -迁移按能力逐步进行,不要求一夜重写: - -1. 生成静态 manifest,记录当前真实依赖和 Host 限制; -2. 用检查工具列出直接上游 import、私有 service、源码 patch 与全局副作用; -3. 把已有能力逐项替换成 Fabric contract; -4. 尚无标准能力的部分保留为明确 legacy 或 vendor extension; -5. 在至少两个 Host product/integration,或一个 Host + fake conformance suite 上验证; -6. 最后才申请“Fabric compatible”结果。 - -现有 `cordis.patch.yml` 是官方声明式 composition,不是源码 patch,可以继续作为 Adapter 或 bundle 的装配入口。Fabric 不要求现有 DSH 插件立即失效。 - -## 10. 分阶段交付 - -### Stage A:不会执行插件的基础设施 - -- Manifest 与 Host Descriptor Schema; -- pure negotiator; -- canonical errors; -- fixtures、lint 与 package inspection; -- 文档和 RFC 治理。 - -### Stage B:最小 Broker 与 trusted DSH Adapter - -- activation scope、AbortSignal、自动 resource ownership; -- v0.1 的精确基础与协商集合:`host.info`、`log`、生命周期取消、`storage.local`、`commands` 和不可修改的 `messages.observe`; -- 固定 DSH 版本上的真实 contract tests; -- 明确标注 trusted-in-process。 -- 完整 v0.1 能力存在后,由两个 Host product 或 integration 提供 interoperability evidence;它们可以共享同一个版本化 DSH Adapter。 - -### Stage C:DSH 领域能力 - -- v0.1 消息事件之外的 canonical session 与 tool DTO; -- 更多不可修改的观察事件; -- 用户触发的 session action; -- tool registration; -- 强类型 Host/Client bridge 与静态资源 transport; -- 受控 files/artifacts、network 和 secret reference; - -### Stage D:UI 与敏感能力 - -- 声明式贡献与强类型 renderer 原型; -- 一个隔离富视图原型; -- 上下文贡献与有序拦截器 RFC; -- process/PTY/job 与事务化包管理 contract; -- permissions UX; -- isolated runner 原型。 - -每个 Stage 可以独立使用和验证。不能为了演示完整故事,在早期阶段偷偷暴露 raw upstream context。 - -## 11. 成功标准 - -Fabric 成功不是“API 数量很多”,而是: - -- 一个普通插件在不 import 上游 runtime 的情况下完成真实功能; -- 同一 package 能被两个 Host product 静态判断并正确激活或拒载; -- DSH 上游升级时,大部分修复只发生在 Adapter; -- 插件拿到的是稳定 DTO 和错误,而不是内部对象; -- Host 缺失能力时用户得到明确说明,不是安装后崩溃; -- reference Broker、Adapter 和 plugin 都能在 headless CI 中跑 conformance tests; -- 文档不会把兼容声明冒充安全审核或强制权限。 - -## 12. 仍需社区决定 - -1. v0.1 之后,下一项 DSH 领域能力应是 `sessions.read`、`sessions.actions`,还是 `tools.register`? -2. Manifest 如何驱动 TypeScript 精确类型,避免生成文件与 source drift? -3. Host、Client 和隔离 Worker 的 face 与通信协议如何拆分? -4. Adapter 可以依赖哪些上游 public contract,experimental extension 的红线在哪里? -5. 权限 grant 按插件、profile、workspace、session 还是设备保存? -6. 哪些 DTO 内容默认需要脱敏? -7. vendor capability 何时有资格进入 portable standard? -8. “Fabric compatible”测试结果由谁发布、如何撤销? - -这些问题应通过独立 RFC、fixtures 和原型回答,而不是由某个参考实现的偶然行为决定。 diff --git a/dsh-community-fabric/docs/research/community-issue-23-review.i18n.yaml b/dsh-community-fabric/docs/research/community-issue-23-review.i18n.yaml deleted file mode 100644 index e5d7eca057..0000000000 --- a/dsh-community-fabric/docs/research/community-issue-23-review.i18n.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Bilingual-pair consistency record. Both languages carry equal authority. -# Update both files and record their normalized Git blob hashes after editing either side. -community-issue-23-review.md: d4fd6e164da1c2a54ef0a62bba935d69febadd99 -community-issue-23-review.zh.md: d2f1faceeeadd611240455c11d3e439a401453bc diff --git a/dsh-community-fabric/docs/research/community-issue-23-review.md b/dsh-community-fabric/docs/research/community-issue-23-review.md deleted file mode 100644 index d4fd6e164d..0000000000 --- a/dsh-community-fabric/docs/research/community-issue-23-review.md +++ /dev/null @@ -1,107 +0,0 @@ -# Community Issue #23: Review and Disposition - -English | [中文](community-issue-23-review.zh.md) - -| Field | Value | -| --- | --- | -| Source | [omdsh-dev/community issue #23](https://github.com/omdsh-dev/community/issues/23) | -| Snapshot | 2026-08-17; Open; 13 comments; no milestone or accepted specification | -| Purpose | Record which ideas changed the Fabric drafts, which need separate RFCs, and which are not portable core contracts | - -## 0. Summary - -Issue #23 is valuable design input, not an approved community standard. The comments exposed four gaps in the original proposal: - -1. Runtime, Presentation, Control, Transport, and the current Invocation are separate concerns; -2. plugins need deterministic service-provider composition, not just capability checks; -3. compatibility needs machine-readable validation and runtime ownership evidence; -4. migration from private patches needs an Adapter strategy without turning patches into the public plugin API. - -The Fabric documents now absorb those points through a strengthened [RFC 0001](../rfcs/0001-plugin-manifest-capabilities-events.md) and three focused follow-up drafts: - -- [RFC 0002: Runtime, Presentation, Control, Transport, and Invocation](../rfcs/0002-runtime-presentation-invocation-transport.md); -- [RFC 0003: Service Providers and Composition](../rfcs/0003-service-providers-and-composition.md); -- [RFC 0004: Provenance, Validation, Diagnostics, and the Effect Ledger](../rfcs/0004-provenance-validation-and-diagnostics.md). - -None of these documents claims that a runtime, SDK, schema release, or compatibility certification already exists. - -## 1. How decisions are classified - -| Disposition | Meaning | -| --- | --- | -| Adopted | The current Fabric drafts include the requirement. | -| Adopted with limits | The underlying need is accepted, but the draft narrows an unsafe or over-broad implementation. | -| Separate RFC | The idea is useful but needs its own contract, evidence, and review before it can become portable API. | -| Adapter experiment | A version-pinned implementation may explore the idea behind the DSH Adapter boundary; plugins cannot depend on it as stable Fabric API. | -| Not portable core | The idea may belong to a product or UI, but it is not a cross-Host Fabric requirement. | -| Recorded | The input is linked for traceability but does not itself create a normative change. | - -“Adopted” means adopted by these Draft documents. It does not mean Issue #23 participants reached formal consensus. - -## 2. Comment-by-comment disposition - -| Community input | Disposition | Result in the Fabric drafts | -| --- | --- | --- | -| [`plugin.json` conflicts with the Agent Plugins specification](https://github.com/omdsh-dev/community/issues/23#issuecomment-5305622804) | Adopted | Fabric uses the unambiguous root filename `dsh-plugin.json` and keeps the document static. | -| [Kubernetes-style type metadata and versioned services/events](https://github.com/omdsh-dev/community/issues/23#issuecomment-5305636433) | Adopted with limits | Schema identity, Fabric API version, capability versions, Host Descriptor version, plugin version, and event type version are separate axes. Fabric does not copy Kubernetes resource semantics wholesale. | -| [Patch/version churn and the need for credible verification](https://github.com/omdsh-dev/community/issues/23#issuecomment-5305638423) | Adopted | RFC 0004 separates listing, declaration, testing, attestation, and enforced isolation. A passing format check is never presented as “safe”. | -| [URL query state for multi-panel Web UI](https://github.com/omdsh-dev/community/issues/23#issuecomment-5305642357) | Not portable core | Deep-link and URL-state conventions belong to a Web Presentation capability. They cannot be required of TUI, native GUI, or headless Runtime implementations. | -| [Installation preview and runtime provenance](https://github.com/omdsh-dev/community/issues/23#issuecomment-5305656025) | Adopted | RFC 0004 defines an installation-impact report, validation report, activation ownership, and a Host-observed effect ledger with cleanup diagnostics. | -| [dsh-forge / dsh-neoforge runtime mixin proof of concept](https://github.com/omdsh-dev/community/issues/23#issuecomment-5305908558) | Adapter experiment | The current [dsh-neoforge proof of concept](https://github.com/r05En1cU/dsh-neoforge) provides useful evidence for explicit conflict detection, reversible ownership, and lifecycle cleanup. Runtime method replacement remains an experimental, version-pinned DSH Adapter technique; manifests cannot carry executable mixin instructions and plugins cannot treat private targets as portable API. | -| [Static validation, separate schema/API versions, capability registry, contribution IDs, migration metadata, and validation reports](https://github.com/omdsh-dev/community/issues/23#issuecomment-5306132230) | Adopted with limits | RFC 0001 requires static JSON, explicit schema selection, an authoritative machine-readable registry, and deterministic contribution identities. RFC 0004 defines report evidence and a read-only `legacyEffects` diagnostic section; declaring a legacy effect never authorizes it. | -| [dsh-TUI as an early conformance implementation](https://github.com/omdsh-dev/community/issues/23#issuecomment-5306241618) | Adopted with limits | Real TUI evidence is welcome and headless conformance is required, but no implementation self-certifies by volunteering. Mutable `before-*` interception remains outside v0.1 until ordering, timeout, cancellation, privacy, and audit semantics are specified. | -| [Remote SSH counterexample, command trees, invocation capabilities, and ephemeral presentation](https://github.com/omdsh-dev/community/issues/23#issuecomment-5306386927) | Separate RFC | RFC 0002 separates Runtime, Presentation, Control, Transport, and Invocation. Presentation capabilities travel with each invocation; command trees and non-persistent presentation messages are first-class draft contracts rather than TUI-only metadata. | -| [Reference Host and attachable Runtime/Presentation model](https://github.com/omdsh-dev/community/issues/23#issuecomment-5306670321) | Adopted with limits | RFC 0002 defines the separation and conformance scenarios. Fabric will define reference components and suites, not bless a single product architecture or require a specific SSH/WebSocket transport. | -| [Dependency locking, replay, and observable environments](https://github.com/omdsh-dev/community/issues/23#issuecomment-5306757296) | Separate RFC | RFC 0004 records immutable artifact and environment evidence. Complete lockfiles, modpacks, migration, rollback, and reproducible workspace distribution remain a later packaging/distribution proposal. | -| [`requires` / `provides` / `contributes` and deterministic composition](https://github.com/omdsh-dev/community/issues/23#issuecomment-5307228009) | Adopted | RFC 0003 defines provider cardinality, user selection, conflict plans, replacement, health, and lifecycle ownership. Load order is not an arbitration mechanism. | -| [Link to the expanded Fabric research and drafts](https://github.com/omdsh-dev/community/issues/23#issuecomment-5308979722) | Recorded | This review closes the loop by linking each community concern to a concrete draft or a documented deferral. | - -## 3. Decisions that remain deliberately strict - -### 3.1 No demand activation in v0.1 - -Fabric v0.1 keeps generation-scoped eager activation after negotiation. Lazy activation adds a second lifecycle, concurrent first-use races, delayed failures, and harder cleanup. It can be proposed later with measurements and a complete state machine. - -### 3.2 No mutable `before-*` event in v0.1 - -The first event is immutable observation. A mutation or cancellation hook requires deterministic participant ordering, conflict rules, deadlines, backpressure, error isolation, replay rules, payload privacy, and an audit record. Calling an ordinary event listener “before” does not solve these requirements. - -### 3.3 Capability declarations are not a sandbox - -A trusted in-process Node.js plugin can import operating-system modules outside its provided context. Fabric can validate, negotiate, record, and reject unsupported calls at its API boundary; only an isolated execution tier with mediated imports and IPC may claim technical enforcement. - -### 3.4 Legacy compatibility is owned by the Adapter - -Fabric-managed plugins use public contracts. A reviewed DSH Adapter may temporarily map a stable contract to a pinned private seam, but the seam is not exposed to plugin authors. When upstream changes, the Adapter must adapt, degrade, or reject activation instead of asking every plugin to patch a new target. - -### 3.5 Products may coexist with legacy loading - -Fabric can prohibit bypass loading for Fabric-managed plugins. It cannot claim that every DSH product immediately removes its existing Cordis/plugin/profile paths. Compatibility mode and non-Fabric plugins remain explicit product boundaries during migration. - -## 4. What should be built next - -The next implementation work should stay smaller than the full vision and distinguish the v0.1 critical path from later experiments. - -### 4.1 Experimental v0.1 critical path - -1. freeze a Draft manifest Schema, Host Descriptor Schema, capability registry, and event envelope; -2. implement a pure manifest/Host-capability negotiator with no DSH dependency; -3. add a Broker prototype that owns registrations, activation lifetime, and the minimum transition ledger; -4. build one version-pinned DSH Adapter and compare its observed effects with real DSH registrations; -5. publish headless fixtures for supported, degraded, conflicting, cancelled, and incomplete-cleanup cases; -6. collect v0.1 results from at least two Host integrations without treating either implementation as the standard itself. - -The v0.1 negotiator covers Host capabilities and the declarations actually supported by RFC 0001. It does not activate plugin-provided services or claim the post-v0.1 extensions proposed by RFC 0002–0004. - -### 4.2 Parallel and post-v0.1 exploration - -- RFC 0002 may prototype one Runtime with two simultaneous Presentation descriptors so Remote SSH assumptions fail visibly. -- RFC 0003 may build a pure static service-composition planner before it exposes any runtime Provider binding. -- RFC 0004 may prototype installation reports and materialized diagnostics on top of the canonical v0.1 transition ledger. -- TUI, Web UI, and Desktop may contribute evidence without any one product becoming the standard itself. - -UI rendering languages, strong sandboxing, package distribution, market attestation, mutable interception, and full reproducibility must remain separate milestones. - -## 5. How this record changes - -This is a dated review of the linked Issue snapshot. New comments do not silently rewrite a Draft. A material change should update the relevant RFC through review and then update this record with the comment link, disposition, and affected contract. diff --git a/dsh-community-fabric/docs/research/community-issue-23-review.zh.md b/dsh-community-fabric/docs/research/community-issue-23-review.zh.md deleted file mode 100644 index d2f1faceee..0000000000 --- a/dsh-community-fabric/docs/research/community-issue-23-review.zh.md +++ /dev/null @@ -1,107 +0,0 @@ -# 社区 Issue #23:意见审查与处置记录 - -[English](community-issue-23-review.md) | 中文 - -| 字段 | 内容 | -| --- | --- | -| 来源 | [omdsh-dev/community issue #23](https://github.com/omdsh-dev/community/issues/23) | -| 快照 | 2026-08-17;Open;13 条评论;没有 milestone,也没有已通过的正式规范 | -| 目的 | 记录哪些意见修改了 Fabric Draft、哪些需要独立 RFC、哪些不属于可移植核心 contract | - -## 0. 一句话结论 - -Issue #23 是很有价值的设计输入,但还不是已经通过的社区标准。评论指出了原提案中的四个重要缺口: - -1. Runtime、Presentation、Control、Transport 与当前 Invocation 是不同职责; -2. 插件除了 capability 判断,还需要确定性的 service provider 组合规则; -3. 兼容性需要机器可读验证和运行时 ownership 证据; -4. 从私有 patch 迁移需要 Adapter 策略,但不能把 patch 变成公共插件 API。 - -Fabric 文档现在通过强化后的 [RFC 0001](../rfcs/0001-plugin-manifest-capabilities-events.zh.md)和三份聚焦的后续 Draft 吸收这些意见: - -- [RFC 0002:Runtime、Presentation、Control、Transport 与 Invocation](../rfcs/0002-runtime-presentation-invocation-transport.zh.md); -- [RFC 0003:Service Provider 与组合规则](../rfcs/0003-service-providers-and-composition.zh.md); -- [RFC 0004:溯源、验证、诊断与 Effect Ledger](../rfcs/0004-provenance-validation-and-diagnostics.zh.md)。 - -这些文档都没有声称 runtime、SDK、正式 schema 或兼容认证已经实现。 - -## 1. 如何标记处置结果 - -| 结果 | 含义 | -| --- | --- | -| 已采纳 | 当前 Fabric Draft 已包含这项要求。 | -| 限定采纳 | 接受真实需求,但缩小了不安全或过宽的实现范围。 | -| 独立 RFC | 方向有价值,但在成为可移植 API 前需要独立 contract、证据和审查。 | -| Adapter 实验 | 固定版本的实现可以在 DSH Adapter 边界内探索;插件不能把它当成稳定 Fabric API。 | -| 不属于可移植核心 | 它可以属于某个产品或 UI,但不是跨 Host 的 Fabric 必选要求。 | -| 已记录 | 为了可追踪性保留输入链接,但它本身不会产生规范变化。 | - -“已采纳”只表示被这些 Draft 文档采纳,不表示 Issue #23 的参与者已经形成正式共识。 - -## 2. 逐条意见处置 - -| 社区意见 | 处置 | Fabric Draft 中的结果 | -| --- | --- | --- | -| [`plugin.json` 与 Agent Plugins 规范冲突](https://github.com/omdsh-dev/community/issues/23#issuecomment-5305622804) | 已采纳 | Fabric 使用不会混淆的根文件名 `dsh-plugin.json`,并保持静态文档。 | -| [借鉴 Kubernetes 的 type metadata 和带版本 service/event](https://github.com/omdsh-dev/community/issues/23#issuecomment-5305636433) | 限定采纳 | Schema identity、Fabric API version、capability version、Host Descriptor version、plugin version 与 event type version 是不同维度;Fabric 不会照搬整套 Kubernetes resource 语义。 | -| [Patch/版本震荡与可信验证需求](https://github.com/omdsh-dev/community/issues/23#issuecomment-5305638423) | 已采纳 | RFC 0004 区分收录、声明、实测、签名证明和强制隔离。格式检查通过绝不能展示成“安全”。 | -| [多 Panel Web UI 的 URL query state](https://github.com/omdsh-dev/community/issues/23#issuecomment-5305642357) | 不属于可移植核心 | Deep link 与 URL state 规范属于 Web Presentation capability,不能要求 TUI、原生 GUI 或 headless Runtime 实现。 | -| [安装前影响预览与运行时溯源](https://github.com/omdsh-dev/community/issues/23#issuecomment-5305656025) | 已采纳 | RFC 0004 定义安装影响报告、验证报告、activation ownership,以及带清理诊断的 Host 观测 effect ledger。 | -| [dsh-forge / dsh-neoforge 运行时 mixin PoC](https://github.com/omdsh-dev/community/issues/23#issuecomment-5305908558) | Adapter 实验 | 当前的 [dsh-neoforge 概念验证](https://github.com/r05En1cU/dsh-neoforge) 为显式冲突检测、可恢复 ownership 和 lifecycle cleanup 提供了有价值的证据。运行时方法替换仍然只是固定版本的实验性 DSH Adapter 技术;manifest 不能携带可执行 mixin 指令,插件也不能把私有 target 当作可移植 API。 | -| [静态验证、Schema/API 分版、capability registry、contribution ID、迁移信息和验证报告](https://github.com/omdsh-dev/community/issues/23#issuecomment-5306132230) | 限定采纳 | RFC 0001 要求静态 JSON、明确 Schema 选择、权威机器可读 registry 与确定性 contribution identity。RFC 0004 定义报告证据和只读 `legacyEffects` 诊断段;声明 legacy effect 永远不会授权执行。 | -| [dsh-TUI 作为早期一致性实现](https://github.com/omdsh-dev/community/issues/23#issuecomment-5306241618) | 限定采纳 | 欢迎真实 TUI 证据,并要求 headless conformance;但任何实现都不能因为主动认领就自行认证。可修改的 `before-*` 拦截仍不进入 v0.1,直到顺序、timeout、取消、隐私和审计语义被定义。 | -| [Remote SSH 反例、command tree、invocation capability 与 ephemeral presentation](https://github.com/omdsh-dev/community/issues/23#issuecomment-5306386927) | 独立 RFC | RFC 0002 拆分 Runtime、Presentation、Control、Transport 和 Invocation。Presentation capability 随每次 invocation 传递;command tree 和非持久 presentation message 是 Draft 的一等 contract,而不是 TUI 私有 metadata。 | -| [Reference Host 与可 attach 的 Runtime/Presentation 模型](https://github.com/omdsh-dev/community/issues/23#issuecomment-5306670321) | 限定采纳 | RFC 0002 定义分层和 conformance 场景。Fabric 会提供参考组件和测试套件,但不会指定唯一产品架构,也不会要求具体 SSH/WebSocket transport。 | -| [依赖锁定、复现与环境可观测性](https://github.com/omdsh-dev/community/issues/23#issuecomment-5306757296) | 独立 RFC | RFC 0004 记录不可变 artifact 与环境证据。完整 lockfile、modpack、迁移、回滚和可复现 workspace 分发仍属于后续 packaging/distribution 提案。 | -| [`requires` / `provides` / `contributes` 与确定性组合](https://github.com/omdsh-dev/community/issues/23#issuecomment-5307228009) | 已采纳 | RFC 0003 定义 provider cardinality、用户选择、冲突计划、替换、健康状态和 lifecycle ownership;加载顺序不是仲裁机制。 | -| [指向扩展后的 Fabric 调研与 Draft](https://github.com/omdsh-dev/community/issues/23#issuecomment-5308979722) | 已记录 | 本审查把每项社区关注点关联到具体 Draft 或明确的延期决定,闭合反馈链路。 | - -## 3. 仍然刻意保持严格的决定 - -### 3.1 v0.1 不做按需激活 - -Fabric v0.1 在协商后执行 generation-scoped eager activation。按需激活会引入第二套生命周期、首次并发竞态、延迟失败和更难验证的清理;以后可以基于测量结果和完整状态机单独提案。 - -### 3.2 v0.1 不开放可修改的 `before-*` 事件 - -第一种事件只允许不可修改的观察。修改或取消 hook 必须定义参与者顺序、冲突规则、deadline、backpressure、错误隔离、replay、payload privacy 与审计记录。给普通 listener 加上 `before` 名字并不能解决这些要求。 - -### 3.3 Capability 声明不是沙箱 - -同进程内受信任的 Node.js 插件可以绕过提供的 context,直接 import 操作系统模块。Fabric 可以在自己的 API 边界校验、协商、记录和拒绝不支持的调用;只有使用受管 import 与 IPC 的隔离执行档位,才能声称技术强制。 - -### 3.4 Legacy 兼容由 Adapter 负责 - -Fabric 管理的插件使用公共 contract。经过审查的 DSH Adapter 可以临时把稳定 contract 映射到固定版本的私有 seam,但不能把 seam 暴露给插件作者。上游发生变化时,Adapter 必须适配、降级或拒绝激活,不能要求每个插件重新 patch 新 target。 - -### 3.5 产品可以在迁移期保留旧加载方式 - -Fabric 可以禁止 Fabric-managed plugin 绕过标准加载,但不能声称所有 DSH 产品立刻删除现有 Cordis/plugin/profile 路径。兼容模式和非 Fabric 插件在迁移期仍然是明确的产品边界。 - -## 4. 下一步应该实现什么 - -下一轮实现应继续小于完整愿景,并明确分开 v0.1 关键路径与后续实验。 - -### 4.1 实验性 v0.1 关键路径 - -1. 冻结 Draft manifest Schema、Host Descriptor Schema、capability registry 和 event envelope; -2. 实现不依赖 DSH 的纯 manifest/Host-capability negotiator; -3. 实现拥有 registration、activation lifetime 和最小 transition ledger 的 Broker 原型; -4. 实现一个固定版本的 DSH Adapter,并把它观测的 effect 与真实 DSH registration 对比; -5. 发布 supported、degraded、conflicting、cancelled 和 incomplete-cleanup 的 headless fixture; -6. 从至少两个 Host integration 收集 v0.1 结果,但不把任何一个实现当成标准本身。 - -v0.1 negotiator 只覆盖 RFC 0001 实际支持的 Host capability 和声明,不会激活 plugin-provided service,也不会宣称实现 RFC 0002–0004 提议的 post-v0.1 extension。 - -### 4.2 并行与 v0.1 之后的探索 - -- RFC 0002 可以用一个 Runtime 同时连接两个 Presentation descriptor,确保 Remote SSH 假设会在测试中显式失败。 -- RFC 0003 可以先实现纯静态 service-composition planner,再考虑任何 runtime Provider binding。 -- RFC 0004 可以在规范 v0.1 transition ledger 之上验证安装报告和物化诊断。 -- TUI、Web UI 与 Desktop 都可以贡献证据,但任何单一产品都不会成为标准本身。 - -UI 渲染语言、强沙箱、package distribution、市场签名证明、可修改拦截与完整可复现性必须保持为独立里程碑。 - -## 5. 这份记录如何更新 - -这是对链接 Issue 快照的日期化审查。新评论不会静默改写 Draft。重要变化应先通过审查更新对应 RFC,再在本记录中补充评论链接、处置和受影响 contract。 diff --git a/dsh-community-fabric/docs/research/dsh-plugin-needs.i18n.yaml b/dsh-community-fabric/docs/research/dsh-plugin-needs.i18n.yaml deleted file mode 100644 index f83b5e7ee4..0000000000 --- a/dsh-community-fabric/docs/research/dsh-plugin-needs.i18n.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Bilingual-pair consistency record. Both languages carry equal authority. -# Update both files and record their normalized Git blob hashes after editing either side. -dsh-plugin-needs.md: 891acd3903babc86ac91d807153bac8d6ee251d0 -dsh-plugin-needs.zh.md: 4ad12f1c1081352a38287961ed10be3563e5547c diff --git a/dsh-community-fabric/docs/research/dsh-plugin-needs.md b/dsh-community-fabric/docs/research/dsh-plugin-needs.md deleted file mode 100644 index 891acd3903..0000000000 --- a/dsh-community-fabric/docs/research/dsh-plugin-needs.md +++ /dev/null @@ -1,181 +0,0 @@ -# Research: What DSH Plugin Developers Actually Need - -English | [中文](dsh-plugin-needs.zh.md) - -Status: Source study, 2026-08-17. This describes observed needs and design implications; it is not an endorsement, security review, compatibility badge, or stable Fabric API. - -## 1. Method and limits - -We used two evidence sets: - -1. the public [DSH 1024Store / awesome DeepSeek Harness plugin catalog](https://github.com/imsai-sh/awesome-deepseek-harness-plugins) at commit [`415a2d0`](https://github.com/imsai-sh/awesome-deepseek-harness-plugins/tree/415a2d0a78c93b3671dc2718721e52f39f06fb96), whose generated README listed 3,809 repositories on 2026-08-17; -2. static source inspection of twelve open-source plugins selected to cover UI, tools, sessions, memory, models, files, external integrations, package management, and terminal behavior. - -We cloned repositories and read manifests, patches, Host and Client entrypoints, tests, and documentation. We did not install dependencies or execute third-party plugin code. The sample is intentionally functional rather than statistically random. It tells us which contracts are needed; it does not measure popularity or code quality. - -The catalog itself is evidence for a better manifest. Its current generated categories include 65 UI plugins, 81 tools, 51 development/runtime plugins, 24 workflows, 20 session/message plugins, 19 notifications/integrations, 17 memory plugins, 7 model/provider plugins, 6 themes, and 3,491 entries still awaiting classification. Package names and a patch file are not enough to infer compatibility, privileges, runtime faces, native requirements, or extension points reliably. - -## 2. Representative plugins - -| Plugin | User-facing function | Current implementation | Compatibility-layer need | -| --- | --- | --- | --- | -| [DSH Better Sidebar](https://github.com/omdsh-dev/DSH-better-sidebar/tree/a673f2399f14c5cec8e1673511049721512e28ad) | A full file/editor/terminal/Git/sidebar workbench and a registry for third-party tabs and file viewers. | Dual Host/Client plugin; private HTTP/WS routes, UI slots, client service registry, structural probes, and native PTY. | Versioned sidebar/tab/file-viewer contribution, cross-face bridge, scoped files, process/PTY, and conflict rules. | -| [dsh-stylevault](https://github.com/GptsApp/dsh-stylevault/tree/b627f3a40c86cee9016d3749368479c08b5443b9) | Theme catalog and live appearance editing. | Client theme APIs plus settings UI and localStorage; DOM observation and generated-class patching when semantic tokens are insufficient. | Theme/token contribution, declarative settings, durable client storage, and an explicit Host-specific escape hatch instead of DOM patching. | -| [dsh-session-export](https://github.com/bwndlct/dsh-session-export/tree/eb18389192e36934718877fd7c6eb397f5cf1cd4) | Export a session through a model tool or slash command. | Reads internal session events and writes directly to the workspace with Node filesystem APIs. | Canonical transcript projection, commands/tools, mediated artifact export, workspace scopes, and user-visible file outcomes. | -| [dsh-memento](https://github.com/PerryLink/dsh-memento/tree/724ad2ec2853f136d9730858295d4d397f4711fc) | Long-term memory, tools, prompt injection, approvals, and a management panel. | Dual face; storage and tool services plus internal event vocabulary, structural service probing, and raw UI. | Plugin-private storage, context-contribution pipeline, tool/interceptor contracts, transcript read, and typed management UI. | -| [dsh-web-search-exa](https://github.com/TonyDua/dsh-web-search-exa/tree/083706bae60af8e1f3776b02448f17c140c3f571) | Exa-backed search provider. | Registers a Host search provider, uses API-key or remote MCP network paths, and relies on manual provider-ID coordination. | Provider registry/arbitration, secret references, scoped network grants, settings schema, health and fallback semantics. | -| [dsh-bash-terminal](https://github.com/MAXeaglet/dsh-bash-terminal/tree/6894913d71098f2ea24120d3a1afd5771f9ccd4a) | Model shell tool and interactive terminal. | Host subprocess/sandbox services, Client settings row, and a direct node-pty fallback where the official seam is missing. | Process, shell environment, PTY and job capabilities with platform/native ABI descriptors, tree cancellation, and explicit fallback status. | -| [dsh-codex-auth](https://github.com/suntianc/dsh-codex-auth/tree/484f5383dc7a80df426ef817daf02a67d9c1dc45) | Codex authentication, models, search, image tool, usage, and settings. | Registers LLM/search/tool providers, reads local auth, makes provider requests, and opens custom loopback RPC to Client UI. | Model/provider SPI, secret vault, network scopes, media/files, provider conflict policy, typed Host↔Client bridge, and settings. | -| [dsh-market](https://github.com/dsh-market/dsh-market/tree/5c4d8c25f0860d67755f719f5e149f99219fd79a) | Install, update, remove, back up, and manage plugins and themes. | Reads and mutates profile manifests, lockfiles and modules; invokes DSH/pnpm; inspects Loader entries for some live updates. Desktop already supplies narrower profile and package services. | Transactional profile/package management, progress, locks, build-script consent, rollback/restart, and no raw Loader/Fiber access. | -| [dsh-genui](https://github.com/omdsh-dev/dsh-genui/tree/4415bef7c15376b0b4cecc895fe26823840d0977) | Interactive UI blocks inside assistant replies and an action loop back to the model. | Host tool/system-prompt/assets; Client fence renderer and slots; falls back to internal service reflection and DOM observation on older Hosts. | Typed content renderer, sandboxed rich view, versioned action messages, static assets, context contribution, and feature negotiation. | -| [dsh-notify-bark](https://github.com/pc439527/dsh-notify-bark/tree/26e229876312b18cc46b7a7ba04daa73e0226603) | Send turn/tool/approval notifications through Bark. | Observes internal session event names, stores settings, sends outbound requests, and builds custom RPC because third-party settings are not exposed to Client. | Canonical observations, notification policy, network/secret scopes, deduplication, settings, and a standard bridge. | -| [dsh-files](https://github.com/taxueseek/dsh-files/tree/2c453ab3f74659f91a84a35f71ff270eea77e674) | File upload cards and a document-reading tool. | Dual face; custom upload route, direct workspace writes, tool registration, conversation slots, DOM drag/drop, custom CSS, TTL and deduplication. | File picker/upload/artifact APIs, attachment contribution, quotas, session/workspace scope, tools, and composer contribution. | -| [dsh-sidechain](https://github.com/omdsh-dev/dsh-sidechain/tree/ee6fadd9bae9efb36477ec17c58e1409eeabaf88) | Side conversations and sub-session panels. | Host agents/subagents plus Client conversation UI; patches Agent methods to change settlement and message delivery. | Session actions, child-session identity, delivery/interceptor contract, durable relationships, and typed conversation contributions. | - -Across this sample, two plugins were Host-only, one was effectively Client-only, and nine needed both faces. Cross-face behavior is not an edge case; it is the normal shape of substantial DSH plugins. - -## 3. The capabilities developers repeatedly need - -### 3.1 Identity, compatibility, and services - -A useful manifest needs more than `dsh.bundle.patch`: - -- stable plugin ID, publisher and plugin version; -- manifest and Fabric API versions; -- Host and Client faces, supported surfaces, platforms, architectures, and native modules; -- required, optional, and provided capabilities with versions; -- declared contributions, subscriptions, sensitive scopes, external domains, executable code, and install/build scripts; -- honest compatibility evidence: declared, authorized, tested, certified, or unknown. - -The runtime needs a versioned service registry with required/optional dependency semantics, provider uniqueness or arbitration, health, feature negotiation, and owner-scoped disposal. It must replace internal reflection and arbitrary `ctx.get()` probing, not standardize those workarounds. - -### 3.2 UI contributions and renderers - -Observed UI needs are structurally different: - -- settings sections and rows; -- command palettes, menu actions, status indicators, notifications, and dialogs; -- conversation header, composer button/dock, tool card, command result, message-content and fence renderers; -- theme and semantic token layers; -- sidebar tabs and file viewers; -- full rich views such as GenUI, dashboards, editors, terminals, and workbenches. - -Fabric therefore needs the four-layer UI model from the [mature-framework study](mature-plugin-frameworks.md): declarative contributions, typed providers/renderers, sandboxed rich views, and clearly labeled Host extensions. Slot IDs need schemas, version ranges, cardinality, priority, fallback and collision diagnostics. Generated CSS classes, MutationObserver, raw DOM, or imported product React components cannot be the supported portable path. - -### 3.3 Agent, tool, model, and context extension - -Developers need to: - -- register tools and slash/product commands; -- register LLM, search, image, memory, and other provider types; -- inspect model capabilities without credentials; -- add bounded system/context fragments at a defined phase; -- observe tool execution; -- request user approval or enforce policy before a tool executes; -- render the result through a declared renderer. - -These are registries and pipelines, not one generic service. Provider IDs need ownership and arbitration. Tool inputs/results need schemas, cancellation, timeout, audit and privacy. Prompt contributions need provenance, deterministic order and token budgets. Tool approval needs an ordered interceptor contract with an explicit failure policy. - -### 3.4 Sessions, messages, and workflows - -Plugins need stable views and actions for sessions without receiving live Agent objects: - -- list/get/paginate redacted sessions and canonical transcript entries; -- observe message, turn, tool, approval, child-session and job events; -- send, continue, interrupt, resume, branch or create a session when granted; -- select model or mode through a stable operation; -- relate child sessions and jobs to their owners; -- append a namespaced custom durable event when the Host supports it. - -Observation, action, interception, context contribution, and durable job behavior must be separate protocols. Event DTOs need IDs, correlation/causation, scope sequence, privacy classification, ordering and replay boundaries. Sidechain-style monkey patches and hard-coded private event vocabularies are evidence of a missing contract, not APIs to preserve. - -### 3.5 Cross-face bridge - -Every dual-face plugin should receive a namespaced, typed bridge instead of opening a private route: - -- request/response RPC for small operations; -- bounded streams for progress and live data; -- Host-served static/media resources; -- automatic plugin/session/workspace scoping; -- authentication, authorization, CSRF/origin policy, size/rate limits, cancellation, disconnect and secret redaction; -- schema/version negotiation and test fakes. - -The Broker can map this to Cordis, loopback HTTP/WS, IPC, or another Host transport. Plugin code should not care which transport is used. - -### 3.6 Files, network, secrets, process, and packages - -These sensitive features are common enough to require first-class mediated APIs: - -- sandbox-aware file read/write, user file picker, upload, attachment/media, and artifact export; -- declared network origins/methods, bounded fetch, redirect/timeout policy, and secret references that never cross to Client code; -- subprocess, shell environment, PTY, background jobs, progress, process-tree cancellation, platform and native ABI constraints; -- active profile identity plus transactional plugin install/update/remove/enable/disable/restart with locking, backup and rollback. - -In trusted in-process mode these APIs improve compatibility, consent and auditing but cannot stop malicious code from importing Node APIs. Strong enforcement requires an isolated execution mode. - -## 4. Priority based on observed breakage - -### P0 — remove the most common private coupling - -1. manifest/schema and Host Descriptor; -2. versioned service negotiation and activation ownership; -3. declarative UI contributions with collision diagnostics; -4. canonical session/message/tool observations and narrow session actions; -5. typed Host↔Client bridge and static assets; -6. mediated files/artifacts, network and secrets; -7. lifecycle/testkit fixtures for HMR, provider replacement and shutdown. - -### P1 — enable advanced plugin categories - -1. typed renderers and sandboxed rich views; -2. tool, model/search and other provider SPIs; -3. context-contribution and tool-approval interceptor RFCs; -4. process/PTY/job contracts; -5. transactional profile/package management. - -### P2 — distribution and stronger isolation - -1. signing, provenance, review/certification evidence and vulnerability response; -2. isolated Worker/process execution with enforceable module, file, network and resource boundaries; -3. modpack compatibility evidence and reproducible cross-Host test matrices. - -This does not mean all P0 APIs must ship in Fabric v0.1. It means the manifest, Broker, face model and Adapter must leave space for them without exposing upstream internals as an interim public API. - -## 5. Target authoring experience - -For a normal plugin, developers should be able to write: - -```ts -import { definePlugin } from 'dsh-community-fabric/sdk' - -export default definePlugin((ctx) => { - ctx.tools.register('com.example.export', exportTool) - - ctx.messages.observe('received', event => { - ctx.log.debug('message received', { id: event.id }) - }) - - ctx.ui.toolResults.bind('com.example.export.result', exportResultRenderer) -}) -``` - -The exact package and method names are not frozen. The intended experience is: - -- the manifest declares the tool, renderer, event interest, permissions and faces once; -- generated types expose only negotiated APIs; -- registrations are automatically removed with the activation; -- a fake Host exercises the same schemas, cancellation, lifecycle and error behavior as a real Host; -- the DSH Adapter translates to official services and slots; -- missing semantics produce `unsupported` with a human explanation, never a silent private workaround. - -Advanced plugins may have a Host entry and an isolated Client view, but they use a generated bridge instead of importing each other's implementation or inventing HTTP routes. - -## 6. Product conclusion - -Koishi and Chrome are strong references, but real DSH plugins show where a theoretical standard would fail. A useful Fabric compatibility layer must cover the ways plugins extend the agent, not only how modules are loaded. - -The decisive product boundary is: - -> Plugins describe and implement their intent against Fabric contracts. The Host owns placement, authorization, lifecycle, transport, and policy. The DSH Adapter alone touches version-specific upstream mechanisms. - -That boundary lets the ecosystem evolve without promising impossible cross-Host UI parity or pretending that a trusted JavaScript plugin is sandboxed. diff --git a/dsh-community-fabric/docs/research/dsh-plugin-needs.zh.md b/dsh-community-fabric/docs/research/dsh-plugin-needs.zh.md deleted file mode 100644 index 4ad12f1c10..0000000000 --- a/dsh-community-fabric/docs/research/dsh-plugin-needs.zh.md +++ /dev/null @@ -1,181 +0,0 @@ -# 调研:DSH 插件开发者真正需要什么 - -[English](dsh-plugin-needs.md) | 中文 - -状态:源码调研,2026-08-17。本文记录观察到的需求和设计含义,不代表推荐、代码安全审查、兼容认证或稳定 Fabric API。 - -## 1. 方法与限制 - -我们使用了两组证据: - -1. 公开的 [DSH 1024Store / awesome DeepSeek Harness 插件目录](https://github.com/imsai-sh/awesome-deepseek-harness-plugins),快照为 commit [`415a2d0`](https://github.com/imsai-sh/awesome-deepseek-harness-plugins/tree/415a2d0a78c93b3671dc2718721e52f39f06fb96),其自动生成 README 在 2026-08-17 列出 3,809 个仓库; -2. 对十二个开源插件做静态源码检查,覆盖 UI、工具、会话、记忆、模型、文件、外部集成、包管理和终端。 - -我们 clone 仓库并阅读 manifest、patch、Host/Client 入口、测试和文档,没有安装依赖或执行第三方插件代码。这是按功能选取的样本,不是随机统计;它可以说明需要哪些 contract,不能说明流行度或代码质量。 - -目录本身也证明了更强 manifest 的必要性:当前生成分类中有 65 个 UI 插件、81 个工具插件、51 个开发/运行时插件、24 个工作流插件、20 个会话/消息插件、19 个通知/集成插件、17 个记忆插件、7 个模型/provider 插件、6 个主题,以及 3,491 个仍待分类条目。只知道包名和 patch 文件,无法可靠判断兼容性、权限、运行 face、原生依赖或扩展点。 - -## 2. 代表性插件 - -| 插件 | 用户功能 | 当前实现方式 | 兼容层需求 | -| --- | --- | --- | --- | -| [DSH Better Sidebar](https://github.com/omdsh-dev/DSH-better-sidebar/tree/a673f2399f14c5cec8e1673511049721512e28ad) | 文件、编辑器、终端、Git 组成的完整工作台,并允许第三方注册 Tab 和文件查看器。 | Host/Client 双 face;私有 HTTP/WS 路由、UI slot、Client 服务注册、结构探测和原生 PTY。 | 版本化侧栏/Tab/文件查看器贡献、跨 face bridge、受控文件、进程/PTY 和冲突规则。 | -| [dsh-stylevault](https://github.com/GptsApp/dsh-stylevault/tree/b627f3a40c86cee9016d3749368479c08b5443b9) | 主题目录与实时外观编辑。 | Client theme API、设置 UI 和 localStorage;语义 token 不足时使用 DOM 观察和生成 class patch。 | 主题/token 贡献、声明式设置、持久 Client storage,以及明确的宿主专属 escape hatch。 | -| [dsh-session-export](https://github.com/bwndlct/dsh-session-export/tree/eb18389192e36934718877fd7c6eb397f5cf1cd4) | 用模型工具或斜杠命令导出会话。 | 读取内部 session event,并通过 Node fs 直接写工作区。 | 标准 transcript 投影、命令/工具、受控 artifact 导出、workspace scope 和用户可见结果。 | -| [dsh-memento](https://github.com/PerryLink/dsh-memento/tree/724ad2ec2853f136d9730858295d4d397f4711fc) | 长期记忆、工具、prompt 注入、审批和管理面板。 | 双 face;storage/tool 服务加内部事件词汇、结构化服务探测和原始 UI。 | 插件私有 storage、上下文贡献流程、工具/拦截器 contract、transcript read 和强类型管理 UI。 | -| [dsh-web-search-exa](https://github.com/TonyDua/dsh-web-search-exa/tree/083706bae60af8e1f3776b02448f17c140c3f571) | Exa 搜索 provider。 | 注册 Host search provider,使用 API key REST 或远端 MCP,provider ID 只能人工避冲突。 | Provider 注册/仲裁、secret reference、网络 scope、设置 schema、健康与 fallback 语义。 | -| [dsh-bash-terminal](https://github.com/MAXeaglet/dsh-bash-terminal/tree/6894913d71098f2ea24120d3a1afd5771f9ccd4a) | 模型 shell 工具和交互终端。 | Host subprocess/sandbox 服务、Client 设置行;官方接缝缺失时直接使用 node-pty。 | 进程、shell 环境、PTY 和 job capability,平台/原生 ABI 描述、进程树取消和明确 fallback 状态。 | -| [dsh-codex-auth](https://github.com/suntianc/dsh-codex-auth/tree/484f5383dc7a80df426ef817daf02a67d9c1dc45) | Codex 登录、模型、搜索、图片工具、用量和设置。 | 注册 LLM/search/tool provider,读取本地 auth,访问 provider,并自建 loopback RPC 给 Client UI。 | 模型/provider SPI、secret vault、网络 scope、媒体/文件、provider 冲突策略、强类型 Host↔Client bridge 和设置。 | -| [dsh-market](https://github.com/dsh-market/dsh-market/tree/5c4d8c25f0860d67755f719f5e149f99219fd79a) | 安装、更新、卸载、备份和管理插件/主题。 | 直接读写 profile manifest、lockfile 和 modules,调用 DSH/pnpm;部分热更新读取 Loader entry。Desktop 已提供更窄的 profile/package 服务。 | 事务化 profile/包管理、进度、锁、build-script 授权、回滚/重启,并禁止 raw Loader/Fiber 访问。 | -| [dsh-genui](https://github.com/omdsh-dev/dsh-genui/tree/4415bef7c15376b0b4cecc895fe26823840d0977) | 在助手回复中显示交互 UI,并把动作回传给模型。 | Host tool/system-prompt/assets;Client fence renderer/slots;旧 Host 上退回内部服务反射和 DOM 观察。 | 强类型内容 renderer、隔离富视图、版本化 action 消息、静态资源、上下文贡献和 feature negotiation。 | -| [dsh-notify-bark](https://github.com/pc439527/dsh-notify-bark/tree/26e229876312b18cc46b7a7ba04daa73e0226603) | 通过 Bark 推送 turn/tool/approval 通知。 | 监听内部 session event 名,保存设置,发出网络请求;第三方设置不对 Client 暴露时自建 RPC。 | 标准观察事件、通知策略、network/secret scope、去重、设置和标准 bridge。 | -| [dsh-files](https://github.com/taxueseek/dsh-files/tree/2c453ab3f74659f91a84a35f71ff270eea77e674) | 文件上传卡片和文档读取工具。 | 双 face;自建上传路由、直接写工作区、注册 tool、conversation slot、全页 DOM 拖放、自定义 CSS、TTL 和去重。 | 文件选择/上传/artifact API、附件贡献、quota、session/workspace scope、tool 和 composer 贡献。 | -| [dsh-sidechain](https://github.com/omdsh-dev/dsh-sidechain/tree/ee6fadd9bae9efb36477ec17c58e1409eeabaf88) | 侧会话与子会话面板。 | Host agents/subagents + Client conversation UI;patch Agent 方法以改变 settlement 和消息投递。 | Session action、子会话身份、delivery/interceptor contract、持久关系和强类型 conversation 贡献。 | - -这十二个样本中,两个只需要 Host,一个实质上只需要 Client,另外九个同时需要两个 face。跨 face 行为不是少数特例,而是有一定复杂度的 DSH 插件的常见形态。 - -## 3. 开发者反复需要的能力 - -### 3.1 身份、兼容性和服务 - -有用的 manifest 不能只包含 `dsh.bundle.patch`: - -- 稳定 plugin ID、publisher 和插件版本; -- manifest 与 Fabric API 版本; -- Host/Client face、支持的 surface、平台、架构和原生模块; -- 带版本的 required、optional 和 provided capability; -- 声明式贡献、订阅、敏感 scope、外部域名、可执行代码和安装/build script; -- 诚实的兼容证据:仅声明、已授权、已测试、已认证或未知。 - -运行时需要带版本的服务注册表,包含 required/optional 依赖、provider 唯一性或仲裁、健康、feature negotiation 和 owner-scoped dispose。它应该替代内部反射和任意 `ctx.get()` 探测,而不是把这些 workaround 标准化。 - -### 3.2 UI 贡献与 Renderer - -样本中的 UI 需求结构完全不同: - -- 设置 section 和 row; -- 命令面板、菜单动作、状态、通知和对话框; -- conversation header、composer button/dock、tool card、command result、message content 和 fence renderer; -- 主题与语义 token layer; -- 侧栏 Tab 和文件查看器; -- GenUI、看板、编辑器、终端和工作台等完整富视图。 - -因此 Fabric 需要[成熟框架调研](mature-plugin-frameworks.zh.md)提出的四层 UI:声明式贡献、强类型 Provider/Renderer、隔离富视图、明确标注的宿主扩展。Slot ID 必须有 schema、版本范围、数量、优先级、fallback 和冲突诊断。生成 CSS class、MutationObserver、原始 DOM 或产品内部 React 组件不能成为受支持的可移植路径。 - -### 3.3 Agent、工具、模型与上下文扩展 - -开发者需要: - -- 注册工具和斜杠/产品命令; -- 注册 LLM、搜索、图片、记忆等 provider; -- 在不接触凭据的情况下读取模型能力; -- 在明确阶段加入有限 system/context 片段; -- 观察工具执行; -- 在工具执行前申请用户批准或实施策略; -- 用声明过的 renderer 展示结果。 - -这些是不同的注册表和流程,不是一个万能服务。Provider ID 需要所有权和仲裁。工具输入/结果需要 schema、取消、超时、审计和隐私。Prompt 贡献需要来源、确定性顺序和 token 预算。工具审批需要有序拦截器 contract 和明确失败策略。 - -### 3.4 会话、消息与工作流 - -插件需要稳定的 session view 和 action,而不是 live Agent 对象: - -- 分页 list/get 经过裁剪的 session 和标准 transcript entry; -- 观察 message、turn、tool、approval、child-session 和 job 事件; -- 授权后 send、continue、interrupt、resume、branch 或 create session; -- 通过稳定 operation 选择 model 或 mode; -- 关联子会话、任务及其 owner; -- 宿主支持时追加带 namespace 的自定义持久事件。 - -观察、动作、拦截、上下文贡献和持久 job 必须是不同协议。事件 DTO 需要 ID、correlation/causation、scope sequence、隐私等级、顺序和 replay 边界。Sidechain 式 monkey patch 和硬编码私有事件词汇说明缺少 contract,不是我们需要保留的 API。 - -### 3.5 跨 face Bridge - -每个双 face 插件都应该获得自动 namespace 的强类型 bridge,而不是自建私有路由: - -- 小型操作使用 request/response RPC; -- 进度和实时数据使用有界 stream; -- 宿主负责提供静态/媒体资源; -- 自动带上 plugin/session/workspace scope; -- 宿主统一实现认证、授权、CSRF/Origin、大小/速率限制、取消、断连和 secret 裁剪; -- schema/版本协商和测试 fake。 - -Broker 可以把它映射到 Cordis、loopback HTTP/WS、IPC 或其他宿主 transport,插件代码不应关心实际传输方式。 - -### 3.6 文件、网络、Secret、进程与包管理 - -这些敏感功能很常见,应该有一等的受控 API: - -- 感知 sandbox 的文件读写、用户文件选择、上传、附件/媒体和 artifact 导出; -- 声明网络 Origin/Method、有界 fetch、跳转/超时策略,以及永远不进入 Client 的 secret reference; -- subprocess、shell 环境、PTY、后台 job、进度、进程树取消、平台和原生 ABI 约束; -- 当前 profile 身份,以及带锁、备份、回滚的插件 install/update/remove/enable/disable/restart 事务。 - -在 trusted in-process 模式下,这些 API 能改善兼容、授权和审计,但阻止不了恶意代码直接导入 Node API。强制执行需要隔离运行模式。 - -## 4. 按真实断点排序 - -### P0——先消除最常见的私有耦合 - -1. manifest/schema 与 Host Descriptor; -2. 带版本的服务协商和激活所有权; -3. 带冲突诊断的声明式 UI 贡献; -4. 标准 session/message/tool 观察和范围很窄的 session action; -5. 强类型 Host↔Client bridge 与静态资源; -6. 受控 files/artifacts、network 和 secrets; -7. HMR、provider 替换和 shutdown 的 lifecycle/testkit fixture。 - -### P1——支持高级插件类别 - -1. 强类型 renderer 与隔离富视图; -2. tool、model/search 等 provider SPI; -3. 上下文贡献与工具审批拦截器 RFC; -4. process/PTY/job contract; -5. 事务化 profile/包管理。 - -### P2——分发与更强隔离 - -1. 签名、provenance、审核/认证证据和漏洞响应; -2. 真正强制模块、文件、网络和资源边界的隔离 Worker/进程; -3. modpack 兼容证据和可复现的跨宿主测试矩阵。 - -这不意味着所有 P0 API 都必须进入 Fabric v0.1,而是 manifest、Broker、face 模型和 Adapter 必须为它们留出正确位置,不能先把上游内部对象临时暴露成公开 API。 - -## 5. 目标开发体验 - -普通插件开发者应该可以写出: - -```ts -import { definePlugin } from 'dsh-community-fabric/sdk' - -export default definePlugin((ctx) => { - ctx.tools.register('com.example.export', exportTool) - - ctx.messages.observe('received', event => { - ctx.log.debug('message received', { id: event.id }) - }) - - ctx.ui.toolResults.bind('com.example.export.result', exportResultRenderer) -}) -``` - -准确包名和方法名尚未冻结。目标体验是: - -- manifest 只声明一次工具、renderer、事件兴趣、权限和 face; -- 生成类型只暴露协商后的 API; -- 注册项随激活自动释放; -- fake Host 使用与真实 Host 相同的 schema、取消、生命周期和错误行为; -- DSH Adapter 翻译到官方 service 和 slot; -- 无法保持语义时返回带人类说明的 `unsupported`,不默默使用私有 workaround。 - -高级插件可以同时有 Host 入口和隔离 Client view,但它们使用生成的 bridge,不互相 import 实现,也不发明私有 HTTP 路由。 - -## 6. 产品结论 - -Koishi 和 Chrome 是很好的参考,但真实 DSH 插件能告诉我们理论标准会在哪里失效。真正有用的 Fabric 兼容层必须覆盖插件如何扩展 agent,而不只是模块如何加载。 - -决定性的产品边界是: - -> 插件用 Fabric contract 描述并实现自己的意图;宿主拥有位置、授权、生命周期、传输和策略;只有 DSH Adapter 接触与版本相关的上游机制。 - -这样才能让生态持续演进,同时不虚假承诺所有 UI 都能跨宿主运行,也不把可信 JavaScript 插件假装成已经被 sandbox 隔离。 diff --git a/dsh-community-fabric/docs/research/mature-plugin-frameworks.i18n.yaml b/dsh-community-fabric/docs/research/mature-plugin-frameworks.i18n.yaml deleted file mode 100644 index 31b6eb8351..0000000000 --- a/dsh-community-fabric/docs/research/mature-plugin-frameworks.i18n.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Bilingual-pair consistency record. Both languages carry equal authority. -# Update both files and record their normalized Git blob hashes after editing either side. -mature-plugin-frameworks.md: 1a4758c036dab97d27ccc012beb4615d4c24a170 -mature-plugin-frameworks.zh.md: 4eb2ac65d09d17bdddf9c1a13dc8082c51144b8e diff --git a/dsh-community-fabric/docs/research/mature-plugin-frameworks.md b/dsh-community-fabric/docs/research/mature-plugin-frameworks.md deleted file mode 100644 index 1a4758c036..0000000000 --- a/dsh-community-fabric/docs/research/mature-plugin-frameworks.md +++ /dev/null @@ -1,207 +0,0 @@ -# Research: Mature Plugin Framework Patterns - -English | [中文](mature-plugin-frameworks.zh.md) - -Status: Research note, 2026-08-17. This is design input for DSH Community Fabric, not a published Fabric API. - -## 1. Question and method - -Fabric needs more than a manifest. It needs a durable answer for lifecycle ownership, dependency negotiation, UI extension, events, permissions, multiple execution environments, and developer tooling. - -We compared primary documentation from three mature systems: - -- [Koishi plugin lifecycle](https://koishi.chat/en-US/guide/plugin/lifecycle), [services and dependencies](https://koishi.chat/en-US/guide/plugin/service), and [event dispatch](https://koishi.chat/en-US/api/service/events.html); -- [Chrome extension permissions](https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions), [optional runtime grants](https://developer.chrome.com/docs/extensions/reference/api/permissions), [message passing](https://developer.chrome.com/docs/extensions/develop/concepts/messaging), [service-worker lifecycle](https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle), and [extension UI](https://developer.chrome.com/docs/extensions/develop/ui); -- [VS Code extension anatomy](https://code.visualstudio.com/api/get-started/extension-anatomy), [contribution points](https://code.visualstudio.com/api/references/contribution-points), [extension capabilities](https://code.visualstudio.com/api/extension-capabilities/overview), [web extensions](https://code.visualstudio.com/api/extension-guides/web-extensions), and [webviews](https://code.visualstudio.com/api/extension-guides/webview). - -They solve different product problems. The goal is not to copy one framework. It is to identify patterns that remain useful for DSH GUI, Web UI, TUI, launchers, and future isolated runtimes. - -## 2. What each framework gets right - -### 2.1 Koishi: Context owns dependencies and side effects - -Koishi's most useful idea is not a particular event name. It is that every plugin activation receives a Context which owns registrations and side effects. - -- `ctx.on()`, commands, middleware, and child plugins are released with the activation. -- Plugins can be enabled, disabled, reloaded, and activated more than once. -- Required services delay activation until available. If a required provider changes, dependent work rolls back and activates again. -- Optional services do not hold the whole plugin lifecycle hostage. -- `ctx.inject()` gives one feature a narrower dependency scope than the rest of the plugin. -- Service providers and consumers are separated. Multiple implementations may satisfy one service contract. -- Event dispatch has distinct parallel, serial, and first-result forms rather than pretending every event has identical semantics. - -This solves a real ecosystem problem: a plugin should not leak listeners, routes, timers, tools, or UI entries after HMR, profile recomposition, provider replacement, or shutdown. - -What Fabric should borrow: - -1. activation-scoped resource ownership; -2. required and optional service negotiation; -3. provider replacement as an explicit lifecycle transition; -4. narrowly scoped child activations; -5. different dispatch contracts for observation and decision pipelines. - -What Fabric should not expose: - -- a raw Cordis/Koishi Context with arbitrary service lookup; -- TypeScript declaration merging as the public compatibility contract; -- same-process service access described as a security permission. - -Fabric should generate a minimal typed context from the manifest. The Broker may use Cordis internally, but the plugin-facing contract must remain independent of Cordis and DSH versions. - -### 2.2 Chrome: static intent, separate faces, mediated messages - -Chrome extensions make identity, entrypoints, UI surfaces, permissions, and site access statically inspectable in `manifest.json`. Required and optional permissions are different. Optional access can be requested when the user invokes the feature, which gives the Host a meaningful moment to explain why it is needed. - -Chrome also treats the extension as several cooperating execution environments: - -- an event-driven service worker; -- content scripts attached to eligible pages; -- popups, options pages, side panels, and other extension pages; -- explicit one-shot messages or long-lived ports between those environments. - -The service worker may be terminated when idle, so durable state belongs in storage rather than global variables. This is a valuable discipline even for a Host that initially keeps plugins alive: code becomes restartable, reconnectable, and easier to isolate later. - -What Fabric should borrow: - -1. one static manifest as the inspection and consent source of truth; -2. distinct required and optional grants; -3. explicit execution faces with serializable messages between them; -4. durable state outside transient runtime globals; -5. user-gesture boundaries for sensitive or disruptive actions; -6. Host-owned UI surfaces instead of arbitrary mutation of the product shell. - -What Fabric must qualify: - -- a capability declaration is only a request; support, user grant, and technical enforcement are separate facts; -- Chrome's security model depends on browser process and origin isolation. A trusted in-process Node plugin does not gain the same protection by adopting similar manifest fields; -- Fabric needs DSH scopes such as session, workspace, tool execution, model access, and profile, not Chrome URL match patterns. - -### 2.3 VS Code: declare the contribution, bind the implementation - -VS Code separates three concerns: - -1. **Contribution Points** statically declare commands, settings, views, menus, themes, and other discoverable objects. -2. **Activation** decides when extension code needs to run. -3. **Runtime APIs** bind handlers or providers to declared IDs. - -For example, command title and identity are declared once, while code registers the handler for that ID. This prevents the market, settings UI, command palette, and runtime from inventing separate metadata. - -VS Code also has multiple UI levels: - -- native, constrained surfaces such as commands, settings, notifications, status items, trees, and file pickers; -- typed providers for richer product-owned views; -- Webviews for custom HTML when native APIs are insufficient. - -Webviews run in a separate context and communicate by messages. VS Code explicitly recommends using them sparingly because they cost resources and can easily violate product UX, accessibility, and theme conventions. Extensions cannot directly access the workbench DOM. - -Web extensions add another important lesson: `main` and `browser` entrypoints are different runtime faces. A contribution-only extension can work without executable code, while a browser entrypoint runs without Node APIs. - -What Fabric should borrow: - -1. static contribution metadata plus runtime binding by stable ID; -2. typed product-owned UI surfaces before custom rich UI; -3. a sandboxed rich-view escape hatch with message passing, themes, accessibility, and resource policy; -4. separate Host and Client/Worker entrypoints rather than one bundle that assumes every environment. - -Fabric deliberately does not adopt VS Code's demand-activation policy in v0.1. A Host activates every selected and authorized plugin while assembling a runtime generation. Contributions remain discovery metadata, and subscriptions control event delivery; neither becomes an implicit first-use activation trigger. - -What Fabric should not copy literally: - -- VS Code's workbench layout or editor-specific object model; -- arbitrary HTML as the default way to add every UI feature; -- one product's `when`-clause vocabulary as a cross-Host standard. -- demand activation before Fabric has a proven need and deterministic cross-Host semantics. - -## 3. Combined design decisions for Fabric - -| Problem | Adopted pattern | Fabric interpretation | -| --- | --- | --- | -| Identity and compatibility | Chrome/VS Code static manifest | Schema-valid identity, API range, faces, requirements, permissions, subscriptions, and contributions. | -| Dependencies | Koishi required/optional services | Capability negotiation and narrowly scoped activation; no generic service locator. | -| Cleanup and HMR | Koishi Context/Fork | Every registration and operation belongs to an activation and is disposed or aborted automatically. | -| UI discovery | VS Code Contribution Points | The manifest owns stable IDs, labels, placement requests, settings schemas, and compatibility metadata. | -| Rich UI | VS Code Webview + Chrome extension pages | Isolated view face, message protocol, CSP/resource policy, theme tokens, accessibility, and explicit Host placement. | -| Multiple runtimes | Chrome scripts/pages + VS Code main/browser | Separate Host, Client, and later Worker faces with a brokered cross-face protocol. | -| Sensitive access | Chrome required/optional permissions | Separate support, request, grant, and enforcement; request narrow scopes near user intent. | -| Restartability | Chrome ephemeral worker | Durable state lives in Host-managed storage; event handlers tolerate restart, reconnect, and replay. | -| Business events | Koishi's different dispatch modes | Separate observation streams, commands, interceptor pipelines, and context-contribution pipelines. | - -## 4. UI should be four layers, not one universal renderer - -A single `panel.render()` API cannot serve a settings toggle, Diff renderer, command palette, file uploader, full sidebar workbench, rich assistant card, and TUI. - -Fabric should define four layers: - -### Layer 1 — Declarative contributions - -For commands, settings schemas, menus, status items, theme tokens, notifications, and simple forms. The Host owns rendering, localization, accessibility, ordering, and conflict UX. Some plugins can contain no Client code at all. - -### Layer 2 — Typed providers and named renderers - -For domain surfaces whose input and output have stable meaning: tool-result renderers, message-content renderers, composer accessories, file viewers, session trees, or model/settings cards. A provider binds to a declared ID and receives canonical DTOs rather than product components. - -Each extension point defines cardinality, priority, fallback, conflicts, lifecycle, error boundaries, and Host coverage. Replacing one renderer is not equivalent to inserting a panel. - -### Layer 3 — Sandboxed rich views - -For dashboards, GenUI, complex editors, visualizations, or an entire sidebar workbench. The plugin supplies a Client/Worker view bundle in an isolated frame or equivalent Host container. It receives only a versioned message bridge and approved resources. - -The contract must cover CSP, resource URLs, navigation, size, focus, keyboard handling, theme tokens, localization, accessibility, persistence, crash recovery, and message schemas. A TUI may reject this capability or provide a different Host-specific implementation. - -### Layer 4 — Host extensions - -Raw DOM, Electron, native widgets, terminal escape protocols, and Host-specific composition belong to organization-namespaced `x-*` capabilities. They may be useful and documented, but markets must not present them as portable. - -Fabric may later define a very small cross-Host UI description for text, lists, buttons, inputs, and basic forms. It must not promise that arbitrary GUI UI can be rendered faithfully in a TUI. - -## 5. Business behavior needs several protocols, not one event bus - -### 5.1 Immutable observation streams - -Examples: message received, session created, tool started, tool completed. Observers cannot alter the operation. Every contract defines payload schema, privacy scope, event identity, per-scope ordering, replay boundary, backpressure, error isolation, and shutdown behavior. - -### 5.2 Commands and actions - -Examples: send a message, resume a session, select a model, open a file, or run a declared command. These are request/result operations with authorization, cancellation, idempotency, stable errors, audit data, and a clear owning scope. They are not fake events. - -### 5.3 Ordered interceptor pipelines - -Examples: tool approval or a `before-send` policy. Interceptors can allow, deny, or return a narrowly defined rewrite. The contract must define deterministic ordering, timeout, failure policy, conflict behavior, provenance, reentrancy, and what later interceptors see. This needs a separate RFC before it becomes stable. - -### 5.4 Context-contribution pipelines - -Examples: memory, system instructions, or per-turn policy. Plugins contribute bounded fragments with source, priority, privacy classification, expiry, and token budget. The Host collects, validates, orders, and freezes them. Plugins do not mutate one shared prompt object or patch an internal prompt builder. - -### 5.5 Durable jobs and workflows - -Long-running automation needs job identity, checkpoints, progress, cancellation, retry policy, ownership, and reconnect/restart behavior. An in-memory listener plus a timer is not a workflow contract. - -## 6. Target developer experience - -The best parts of all three systems lead to a simple authoring model: - -```text -manifest: declare identity, faces, requirements, permissions, - subscriptions, and contributions -code: bind handlers and providers to declared IDs -SDK: expose only negotiated capabilities as typed APIs -Broker: own cleanup, cross-face messages, grants, errors, and audit -Adapter: translate stable contracts to the pinned DSH runtime -testkit: validate the manifest and run the same lifecycle/capability fixtures as Hosts -``` - -A developer should not need to import DSH source, discover a Cordis service name, edit a patch file, create a private HTTP route, or manipulate the product DOM for common features. The normal loop should be scaffold → declare → implement → test against a fake Host → run against a development Adapter → pack. - -## 7. What this research changes - -Fabric should keep v0.1 small, but its architecture must leave the right seams: - -1. keep the current manifest, negotiation, lifecycle, `storage.local`, `commands`, and immutable observation baseline; -2. add separate future specifications for runtime faces and cross-face messaging; -3. design UI as contribution/provider/rich-view/Host-extension layers; -4. split business behavior into observation, action, interceptor, context-contribution, and job protocols; -5. treat permissions as a four-stage support/request/grant/enforcement model; -6. make activation scopes and automatic cleanup non-negotiable; -7. never expose the upstream Cordis Context or internal DSH objects as the compatibility API. - -The dedicated [VS Code extension-model study](vscode-extension-model.md) expands its contribution, Provider, UI, placement, lifecycle, and arbitration patterns from official documentation and samples. The [DSH plugin-needs study](dsh-plugin-needs.md) then tests the combined conclusions against real community plugins. diff --git a/dsh-community-fabric/docs/research/mature-plugin-frameworks.zh.md b/dsh-community-fabric/docs/research/mature-plugin-frameworks.zh.md deleted file mode 100644 index 4eb2ac65d0..0000000000 --- a/dsh-community-fabric/docs/research/mature-plugin-frameworks.zh.md +++ /dev/null @@ -1,206 +0,0 @@ -# 调研:成熟插件框架的设计模式 - -[English](mature-plugin-frameworks.md) | 中文 - -状态:研究记录,2026-08-17。本文是 DSH Community Fabric 的设计输入,不是已经发布的 Fabric API。 - -## 1. 问题与方法 - -Fabric 不能只有一份 manifest。它还要长期解决生命周期所有权、依赖协商、UI 扩展、业务事件、权限、多运行环境和开发工具。 - -我们阅读了三类成熟系统的一手文档: - -- [Koishi 插件生命周期](https://koishi.chat/zh-CN/guide/plugin/lifecycle)、[服务与依赖](https://koishi.chat/zh-CN/guide/plugin/service)和[事件分发](https://koishi.chat/zh-CN/api/service/events.html); -- [Chrome 扩展权限声明](https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions)、[运行时可选授权](https://developer.chrome.com/docs/extensions/reference/api/permissions)、[消息通信](https://developer.chrome.com/docs/extensions/develop/concepts/messaging)、[Service Worker 生命周期](https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle)和[扩展 UI](https://developer.chrome.com/docs/extensions/develop/ui); -- [VS Code 扩展结构](https://code.visualstudio.com/api/get-started/extension-anatomy)、[Contribution Points](https://code.visualstudio.com/api/references/contribution-points)、[扩展能力](https://code.visualstudio.com/api/extension-capabilities/overview)、[Web 扩展](https://code.visualstudio.com/api/extension-guides/web-extensions)和[Webview](https://code.visualstudio.com/api/extension-guides/webview)。 - -它们解决的问题并不相同。我们的目标不是照抄某一个框架,而是找出对 DSH GUI、Web UI、TUI、启动器和未来隔离运行时都成立的模式。 - -## 2. 每个框架最值得借鉴的部分 - -### 2.1 Koishi:Context 同时管理依赖和副作用 - -Koishi 最值得借鉴的不是某个具体事件名,而是每次插件激活都会得到一个拥有注册项和副作用的 Context。 - -- `ctx.on()`、命令、中间件和子插件会随这次激活一起释放。 -- 插件可以动态启用、停用、重载,也可以反复激活。 -- 必需服务未就绪时插件不会提前运行;必需服务更换时,相关功能会回滚后重新激活。 -- 可选服务不会绑住整个插件的生命周期。 -- `ctx.inject()` 可以让插件中的某一项功能拥有比整个插件更窄的依赖范围。 -- 服务提供者和消费者相互分离,同一个服务 contract 可以由不同插件实现。 -- 并行、串行和取第一个结果是不同的事件分发方式,并没有假设所有事件都具备相同语义。 - -这正好解决生态里的真实问题:HMR、profile 重组、服务替换或应用退出后,插件不能遗留监听器、路由、定时器、工具或 UI 注册项。 - -Fabric 应直接吸收: - -1. 激活范围内的资源所有权; -2. 必需与可选服务协商; -3. 把服务替换视为明确的生命周期变化; -4. 更窄的子激活范围; -5. 为观察和决策流程定义不同的分发 contract。 - -Fabric 不应直接暴露: - -- 可以任意查询服务的 Cordis/Koishi 原始 Context; -- 把 TypeScript declaration merging 当成公开兼容 contract; -- 把同进程服务访问描述成安全权限。 - -Fabric 应根据 manifest 生成最小的强类型上下文。Broker 内部可以使用 Cordis,但插件面对的 contract 必须独立于 Cordis 和 DSH 版本。 - -### 2.2 Chrome:静态意图、运行环境分离、受控通信 - -Chrome 扩展把身份、入口、UI、权限和站点访问写进 `manifest.json`,工具无需执行代码就能检查。必需权限和可选权限是两件事;可选访问可以等用户真正触发功能时再申请,让宿主有机会解释为什么需要它。 - -Chrome 还把一个扩展拆成多个协作运行环境: - -- 事件驱动的 Service Worker; -- 运行在符合条件页面上的 Content Script; -- Popup、设置页、Side Panel 等扩展页面; -- 这些环境之间的一次性消息或长连接。 - -Service Worker 空闲时可能被终止,因此持久状态必须放在 storage,而不是全局变量。这种纪律即使在第一版常驻插件中也很有价值:代码天然更容易重启、重连,并为未来隔离做好准备。 - -Fabric 应直接吸收: - -1. 用一份静态 manifest 作为检查和授权的唯一信源; -2. 区分必需授权与可选授权; -3. 明确定义不同运行 face,并用可序列化消息通信; -4. 将持久状态放在短生命周期运行时之外; -5. 敏感或打扰用户的操作必须靠近用户动作; -6. UI 位置由宿主管理,插件不能任意修改产品外壳。 - -Fabric 必须说明: - -- capability 声明只是请求,宿主支持、用户授权和技术强制执行是三个额外事实; -- Chrome 的安全性来自浏览器进程与 Origin 隔离。只复制 manifest 字段,不会让同进程 Node 插件得到相同保护; -- Fabric 需要的是 session、workspace、工具执行、模型和 profile 等 DSH scope,而不是网址匹配规则。 - -### 2.3 VS Code:先声明贡献,再绑定实现 - -VS Code 把三件事分开: - -1. **Contribution Points** 静态声明命令、设置、视图、菜单、主题等可发现对象; -2. **Activation** 决定何时真的加载扩展代码; -3. **运行时 API** 把 handler 或 provider 绑定到声明过的 ID。 - -例如命令标题和身份只声明一次,代码只为这个 ID 注册 handler。市场、设置页、命令面板和运行时就不会各自维护一份容易漂移的元数据。 - -VS Code 的 UI 也有不同层级: - -- 命令、设置、通知、状态项、树和文件选择器等受限的原生界面; -- 为更复杂的产品界面提供强类型 provider; -- 原生 API 不够时才使用能显示任意 HTML 的 Webview。 - -Webview 在独立上下文中运行,通过消息与扩展通信。VS Code 明确建议谨慎使用,因为它消耗更多资源,也很容易破坏产品一致性、无障碍和主题体验。扩展不能直接操作 Workbench DOM。 - -Web 扩展还有一个重要经验:`main` 和 `browser` 是不同运行 face。只有声明式贡献的扩展甚至可以不包含可执行代码;浏览器入口也不能使用 Node API。 - -Fabric 应直接吸收: - -1. 静态贡献元数据 + 按稳定 ID 绑定运行时实现; -2. 优先使用宿主拥有的强类型 UI,再开放自定义富 UI; -3. 富 UI 使用隔离视图、消息通信、主题、无障碍和资源策略; -4. Host 与 Client/Worker 分开入口,而不是一个 bundle 假设所有环境都一样。 - -Fabric 明确不在 v0.1 采用 VS Code 的按需激活策略。Host 在组装一次 runtime generation 时激活所有已选中、已授权的插件;contribution 只负责静态发现,subscription 只控制事件投递,两者都不会成为首次使用时的隐式激活触发器。 - -Fabric 不应照抄: - -- VS Code 的 Workbench 布局和编辑器对象模型; -- 把任意 HTML 当作所有 UI 功能的默认答案; -- 把某个产品的 `when` 条件词汇直接当成跨宿主标准。 -- 在没有真实需求和确定跨 Host 语义前引入按需激活。 - -## 3. Fabric 的组合设计结论 - -| 问题 | 借鉴模式 | Fabric 中的含义 | -| --- | --- | --- | -| 身份与兼容性 | Chrome/VS Code 静态 manifest | 用 schema 描述身份、API 范围、face、依赖、权限、订阅和贡献。 | -| 依赖 | Koishi 必需/可选服务 | capability 协商和窄激活范围,不提供通用服务定位器。 | -| 清理与 HMR | Koishi Context/Fork | 所有注册和操作都属于一次激活,自动释放或取消。 | -| UI 发现 | VS Code Contribution Points | manifest 统一拥有 ID、标题、位置请求、设置 schema 和兼容信息。 | -| 富 UI | VS Code Webview + Chrome 扩展页 | 隔离的 view face、消息协议、CSP/资源策略、主题 token、无障碍和宿主位置。 | -| 多运行环境 | Chrome scripts/pages + VS Code main/browser | 分离 Host、Client 和未来 Worker face,由 Broker 管理跨 face 通信。 | -| 敏感访问 | Chrome 必需/可选权限 | 把支持、请求、授权和强制执行分开,并在用户意图附近申请最小 scope。 | -| 可重启性 | Chrome 短生命周期 Worker | 持久状态进入宿主 storage;事件处理能应对重启、重连和 replay。 | -| 业务事件 | Koishi 的多种分发模式 | 分开观察流、命令、拦截器和上下文贡献流程。 | - -## 4. UI 应有四层,而不是一个万能 renderer - -一个 `panel.render()` 无法同时承载设置开关、Diff renderer、命令面板、文件上传、完整侧栏工作台、富交互回复和 TUI。 - -Fabric 应定义四层 UI: - -### 第 1 层——声明式贡献 - -用于命令、设置 schema、菜单、状态项、主题 token、通知和简单表单。宿主负责渲染、国际化、无障碍、顺序和冲突提示。有些插件可以完全没有 Client 代码。 - -### 第 2 层——强类型 Provider 和命名 Renderer - -用于输入输出有稳定业务含义的界面:工具结果 renderer、消息内容 renderer、输入框附件、文件查看器、会话树、模型或设置卡片。Provider 绑定到声明过的 ID,接收标准 DTO,而不是产品内部组件。 - -每个扩展点都要规定数量、优先级、fallback、冲突、生命周期、错误边界和宿主覆盖范围。替换一个 renderer 和插入一个 panel 不是同一种操作。 - -### 第 3 层——隔离的富视图 - -用于看板、GenUI、复杂编辑器、可视化或完整侧栏工作台。插件提供运行在隔离 frame 或等价宿主容器中的 Client/Worker view bundle,只能使用版本化消息桥和被批准的资源。 - -contract 必须覆盖 CSP、资源 URL、导航、尺寸、焦点、键盘、主题 token、国际化、无障碍、持久化、崩溃恢复和消息 schema。TUI 可以拒绝这个 capability,或只提供自己的宿主扩展。 - -### 第 4 层——宿主专属扩展 - -原始 DOM、Electron、原生控件、终端转义协议和宿主特定组合都属于组织命名空间下的 `x-*` capability。它们可以有文档,但市场不能把它们展示成可跨宿主运行。 - -Fabric 以后可以定义一个非常小的跨宿主 UI 描述,只包含文本、列表、按钮、输入和基础表单;绝不能承诺任意 GUI 都能在 TUI 中无损运行。 - -## 5. 业务行为需要多种协议,而不是一个事件总线 - -### 5.1 不可变观察流 - -例如消息收到、会话创建、工具开始、工具结束。观察者不能改变原操作。每个 contract 都要定义 payload schema、隐私 scope、事件身份、scope 内顺序、replay 边界、背压、错误隔离和退出行为。 - -### 5.2 命令和动作 - -例如发送消息、恢复会话、选择模型、打开文件或执行已声明命令。它们是带授权、取消、幂等、稳定错误、审计信息和明确所有者的 request/result 操作,不是伪装成事件的方法调用。 - -### 5.3 有序拦截器流程 - -例如工具审批或 `before-send` 策略。拦截器可以允许、拒绝,或返回范围很窄的重写。contract 必须规定确定性顺序、超时、失败策略、冲突、来源、重入以及后续拦截器看到的内容。它需要单独 RFC,不能在没有语义时直接稳定。 - -### 5.4 上下文贡献流程 - -例如记忆、系统指令和每轮策略。插件提交带来源、优先级、隐私等级、过期时间和 token 预算的有限片段,由宿主收集、校验、排序并冻结。插件不能共同修改一个共享 prompt 对象,也不能 patch 内部 prompt builder。 - -### 5.5 持久任务与工作流 - -长期自动化需要任务 ID、checkpoint、进度、取消、重试策略、所有者和重启重连行为。一个内存 listener 加定时器不能成为工作流 contract。 - -## 6. 目标开发体验 - -三个系统最有价值的部分可以组成一个简单的开发模型: - -```text -manifest:声明身份、face、依赖、权限、订阅和贡献 -代码: 为声明过的 ID 绑定 handler 和 provider -SDK: 只以强类型 API 暴露已协商 capability -Broker: 管理清理、跨 face 消息、授权、错误和审计 -Adapter: 把稳定 contract 翻译到固定版本的 DSH runtime -testkit: 校验 manifest,并运行和宿主相同的生命周期/capability fixture -``` - -对于常见功能,开发者不应再导入 DSH 源码、猜 Cordis 服务名、编辑 patch 文件、创建私有 HTTP 路由或修改产品 DOM。标准流程应该是:创建脚手架 → 声明 → 实现 → 在 fake Host 测试 → 用开发 Adapter 联调 → 打包。 - -## 7. 这次调研会改变什么 - -Fabric 仍应保持 v0.1 很小,但架构必须预留正确接缝: - -1. 保留当前 manifest、协商、生命周期、`storage.local`、`commands` 和不可变观察基线; -2. 为运行 face 与跨 face 消息单独设计后续规范; -3. 把 UI 拆成贡献、Provider、富视图和宿主扩展四层; -4. 把业务行为拆成观察、动作、拦截器、上下文贡献和任务协议; -5. 权限使用支持/请求/授权/强制执行四阶段模型; -6. 激活范围和自动清理是不可让步的基础; -7. 永远不把上游 Cordis Context 或 DSH 内部对象作为兼容 API 暴露。 - -独立的 [VS Code 扩展模型调研](vscode-extension-model.zh.md)会根据官方文档与样例,进一步展开 contribution、Provider、UI、运行位置、生命周期和仲裁模式;[DSH 插件需求调研](dsh-plugin-needs.zh.md)再用真实社区插件检验汇总后的结论。 diff --git a/dsh-community-fabric/docs/research/vscode-extension-model.i18n.yaml b/dsh-community-fabric/docs/research/vscode-extension-model.i18n.yaml deleted file mode 100644 index 79670e7b7f..0000000000 --- a/dsh-community-fabric/docs/research/vscode-extension-model.i18n.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Bilingual-pair consistency record. Both languages carry equal authority. -# Update both files and record their normalized Git blob hashes after editing either side. -vscode-extension-model.md: b99b6f0b3b9db68c354f76a328198a07e59dab40 -vscode-extension-model.zh.md: 7249a4ec1152daf68b7a8309420a33bd4678d920 diff --git a/dsh-community-fabric/docs/research/vscode-extension-model.md b/dsh-community-fabric/docs/research/vscode-extension-model.md deleted file mode 100644 index b99b6f0b3b..0000000000 --- a/dsh-community-fabric/docs/research/vscode-extension-model.md +++ /dev/null @@ -1,317 +0,0 @@ -# Research: The VS Code Extension Model and Its Value to the Fabric RFC - -English | [中文](vscode-extension-model.zh.md) - -Status: source and official-documentation research, 2026-08-17. This document is design input for DSH Community Fabric, not a published Fabric API. - -## 1. Scope - -VS Code is relevant not because DSH should become a code editor, but because it has spent years solving three problems that closely resemble DSH's: - -1. letting extensions add commands, settings, views, and domain behavior without editing product source or the product DOM; -2. separating statically discoverable feature declarations from runtime implementations; -3. exposing one product API across local, browser, and remote environments without sharing internal objects. - -This investigation uses only first-party Microsoft sources: - -- [Extension Manifest](https://code.visualstudio.com/api/references/extension-manifest), [Extension Anatomy](https://code.visualstudio.com/api/get-started/extension-anatomy), and [Contribution Points](https://code.visualstudio.com/api/references/contribution-points); -- [Extension Capabilities](https://code.visualstudio.com/api/extension-capabilities/overview), [Extension Host](https://code.visualstudio.com/api/advanced-topics/extension-host), [Remote Extensions](https://code.visualstudio.com/api/advanced-topics/remote-extensions), and [Web Extensions](https://code.visualstudio.com/api/extension-guides/web-extensions); -- [Webview](https://code.visualstudio.com/api/extension-guides/webview), [Workspace Trust](https://code.visualstudio.com/api/extension-guides/workspace-trust), [Extension Runtime Security](https://code.visualstudio.com/docs/configure/extensions/extension-runtime-security), and [Proposed API](https://code.visualstudio.com/api/advanced-topics/using-proposed-api); -- a fixed snapshot of Microsoft's official [`vscode-extension-samples`](https://github.com/microsoft/vscode-extension-samples/tree/3d8442b16c7f353779e266f16295703b2b4a6dcc) repository. - -We read documentation, manifests, and source. We did not install dependencies or execute sample extensions. - -## 2. What VS Code Actually Implements - -VS Code extensibility is not one universal API. It is composed of static declarations, activation, runtime APIs, and Extension Hosts. - -```text -package.json / contributes - ↓ static discovery, indexing, presentation, and compatibility decisions -activation - ↓ load code only when a feature is needed -register handler / provider - ↓ bind an implementation to a declared ID -Extension Host - ↓ isolate the product UI and select local, Web, or remote placement -``` - -### 2.1 Manifest and compatibility metadata - -Every extension uses a root `package.json` as its manifest. Besides identity and version, it may declare: - -- `engines.vscode`: the compatible VS Code API/product range; -- `main` and `browser`: Node and Web Worker entrypoints; -- `contributes`: static commands, settings, views, themes, tasks, tools, and more; -- `activationEvents`: when extension code is needed; -- `extensionKind`: whether execution should be near the UI or the workspace; -- `extensionDependencies` and `extensionPack`: functional dependencies and grouped installation; -- `capabilities.untrustedWorkspaces` and `virtualWorkspaces`: support in restricted environments. - -The Marketplace and Host can understand much of the extension's presentation and runtime requirements without executing it. - -### 2.2 Static Contribution Points - -The official [Contribution Points](https://code.visualstudio.com/api/references/contribution-points) can be grouped by product purpose: - -| Category | Representative implemented features | Design property | -| --- | --- | --- | -| Commands and entry surfaces | `commands`, `menus`, `submenus`, `keybindings` | Declare ID, title, and placement first; code only binds the handler. | -| Settings and conditions | `configuration`, `configurationDefaults`, `when` conditions | One schema drives validation, editor completion, and settings UI. | -| Product UI | `views`, `viewsContainers`, `viewsWelcome`, `customEditors`, themes, colors, and icons | The Host owns layout and rendering; the extension contributes metadata or a Provider. | -| Work execution | `taskDefinitions`, `terminal`, `debuggers`, `problemMatchers` | Declare discoverable types first, then create instances through runtime Providers. | -| Languages and documents | `languages`, `grammars`, `snippets`, semantic-token types | Simple capabilities can be fully declarative and require no executable code. | -| Authentication and Providers | `authentication`, language-model chat providers | The Host owns unified presentation and provider selection. | -| Agents and AI | agents, instructions, prompts, skills, language-model tools | Statically describe identity, input, and intent, then bind a controlled implementation. | -| Onboarding | `walkthroughs` | The Host presents installation steps and completion conditions. | - -The important conclusion is not that Fabric should implement dozens of points at once. It is that each extension point has its own schema, ID, lifecycle, placement, conditions, and runtime binding rules. - -### 2.3 Runtime handlers and Providers - -Declarations describe what exists. Runtime APIs provide behavior: - -| Pattern | VS Code example | Why it matters | -| --- | --- | --- | -| Handler | `commands.registerCommand(id, handler)` | A declared ID has one explicit execution path. | -| Data Provider | `registerTreeDataProvider(viewId, provider)` | The Host renders the tree; the extension supplies structured data and refresh events. | -| Domain Provider | completion, hover, task, debug, test, SCM, and filesystem Providers | Each domain defines its own input, output, cancellation, and composition semantics. | -| Controller | Test Controller, Source Control, and similar APIs | The Host owns user experience while the extension manages constrained domain objects. | -| Codec / Controller / Renderer | Notebook Serializer, Controller, and MIME Renderer | Data format, execution, and presentation can use separate contracts and runtimes. | -| Rich View bridge | Webview `postMessage` / `onDidReceiveMessage` | Custom UI exchanges messages with the extension process instead of sharing DOM or objects. | - -Many registration APIs return a `Disposable`; long operations receive a `CancellationToken`. Extensions add owned resources to `ExtensionContext.subscriptions` so they are released during deactivation. - -### 2.4 Conflict and arbitration are domain-specific - -VS Code does not resolve every conflict with “last registration wins.” It selects rules by domain: - -- commands, authentication providers, filesystem schemes, and similar identities require a unique owner and reject duplicate registration; -- some language Providers merge results from multiple implementations; -- some Providers use selector matching to choose the best implementation; -- when several Custom Editors match, the user can choose and persist a default; -- keybindings combine Host defaults, extension suggestions, and user configuration. - -Fabric need not copy VS Code's selector scoring, but every capability or contribution must define cardinality, selector, priority, merge / first-result / pipeline / user-choice behavior, equal-priority tie-breaking, error isolation, timeout, cancellation, duplicate registration, and hot replacement. Load order must never become an undocumented arbitration rule. - -### 2.5 UI capability levels - -VS Code provides a clear UI escalation path: - -1. native commands, menus, settings, notifications, Quick Pick, and status items; -2. Host-rendered Provider UI such as Tree View, Test Controller, and SCM; -3. Webview or Custom Editor only when native capabilities are insufficient. - -Official guidance forbids extensions from accessing the Workbench DOM or injecting custom styles, and recommends using Webviews only when necessary. A Webview runs in a separate context and communicates through messages; it must also address CSP, resource URIs, themes, accessibility, state restoration, and disposal. - -This strongly supports Fabric's four layers: declarative contributions, typed Providers/Renderers, isolated rich views, and Host-specific extensions. - -### 2.6 Placement and multiple environments - -The [Extension Host](https://code.visualstudio.com/api/advanced-topics/extension-host) may be: - -- a local Node.js Host; -- a remote Node.js Host; -- a browser/Web Worker Host. - -Extensions expose different entrypoints through `main` or `browser`, and use `extensionKind` to express a preference to run near the UI or workspace. In remote configurations, the UI and workspace can be on different machines; extensions cannot assume that local paths, processes, and UI share one environment. - -This directly supports defining Fabric Host, Client, Worker, and Rich View as distinct runtime faces that communicate only through versioned DTO, RPC, stream, and asset channels. - -Virtual Workspaces and FileSystemProvider also show that a resource need not be a local absolute path. Fabric portable resource contracts should use URIs, opaque resource IDs, and Host-mediated file/artifact capabilities. Only explicit local Host extensions should promise a physical disk path. - -### 2.7 Lifecycle, state, and cancellation - -VS Code activates an extension when its feature is needed. Commands, views, languages, filesystems, and tasks can trigger activation. Recent VS Code versions derive some activation conditions from contributions, avoiding duplicated author declarations. - -Extensions receive workspace/global state, storage directories, and SecretStorage. They place registrations in subscriptions and use `deactivate` for additional cleanup. - -Fabric should borrow resource ownership without copying the assumption that an extension activates only once per Extension Host session. DSH profile recomposition, Provider replacement, HMR, and recovery require the same plugin to activate and dispose repeatedly. - -### 2.8 Trust and security facts - -VS Code Workspace Trust lets an extension declare whether an untrusted workspace is: - -- fully supported; -- completely unsupported; -- supported with limited functionality and restricted workspace configuration. - -Microsoft also states that a desktop Node Extension Host has the same file, network, and process permissions as VS Code itself. Extension Host isolation protects the UI and some failure boundaries; it is not a plugin permission sandbox. - -Fabric must therefore keep these states separate: - -```text -Host supports a capability -≠ plugin requests it -≠ user or policy grants it -≠ runtime technically enforces isolation -≠ Marketplace security review -``` - -Publisher trust, plugin grants, and profile/workspace content trust must also remain distinct states. - -### 2.9 Versions, experimental APIs, and tooling - -VS Code makes a best effort to preserve stable Extension API compatibility. Unstable Proposed APIs must be enabled explicitly in Insiders/development environments and cannot be normal Marketplace dependencies. - -Its toolchain covers scaffolding, types, an Extension Development Host, unit and integration testing, Web testing, VSIX packaging, and publication. Workspace Trust behavior is tested separately in trusted and untrusted states. - -Fabric should retain a clearer multi-axis version model than VS Code: plugin, manifest schema, Fabric API, capability/event, Host product, SDK, and Adapter versions must not collapse into one product version. - -## 3. What the official samples prove - -The following samples come from Microsoft's official repository at commit [`3d8442b`](https://github.com/microsoft/vscode-extension-samples/tree/3d8442b16c7f353779e266f16295703b2b4a6dcc): - -| Sample | Implementation pattern | Verifiable lesson for Fabric | -| --- | --- | --- | -| [Hello World](https://github.com/microsoft/vscode-extension-samples/tree/3d8442b16c7f353779e266f16295703b2b4a6dcc/helloworld-sample) | The manifest declares a command; code registers its handler with the same ID and owns the Disposable. | Contribution/implementation separation should be a contract, not a style suggestion. | -| [Tree View](https://github.com/microsoft/vscode-extension-samples/tree/3d8442b16c7f353779e266f16295703b2b4a6dcc/tree-view-sample) | The manifest declares containers, views, commands, menus, and settings; code supplies a TreeDataProvider. | A complex sidebar does not require DOM access when a domain Data Provider exists. | -| [Webview View](https://github.com/microsoft/vscode-extension-samples/tree/3d8442b16c7f353779e266f16295703b2b4a6dcc/webview-view-sample) | The manifest declares a View; code registers a WebviewViewProvider. | A Rich View still has a stable ID, Host placement, and Provider lifecycle. | -| [Custom Editor](https://github.com/microsoft/vscode-extension-samples/tree/3d8442b16c7f353779e266f16295703b2b4a6dcc/custom-editor-sample) | The document model and Webview are separate; messages synchronize state and standard save/undo/redo participate. | Rich UI must not bypass domain models and operation semantics. | -| [File System Provider](https://github.com/microsoft/vscode-extension-samples/tree/3d8442b16c7f353779e266f16295703b2b4a6dcc/fsprovider-sample) | Registers a URI scheme and a controlled filesystem Provider. | Plugins should implement a capability SPI rather than share internal storage objects. | -| [Task Provider](https://github.com/microsoft/vscode-extension-samples/tree/3d8442b16c7f353779e266f16295703b2b4a6dcc/task-provider-sample) | The manifest defines a task schema; code discovers and resolves tasks with cancellation. | Task definition, discovery, execution, and cancellation require a dedicated contract. | -| [Test Provider](https://github.com/microsoft/vscode-extension-samples/tree/3d8442b16c7f353779e266f16295703b2b4a6dcc/test-provider-sample) | The Host provides Test UI while the extension maintains the test tree, run profiles, and results. | Providers can support complex workflows without giving plugins control of the entire UI. | -| [Chat and tools](https://github.com/microsoft/vscode-extension-samples/tree/3d8442b16c7f353779e266f16295703b2b4a6dcc/chat-sample) | The manifest declares Chat/Tool metadata; runtime binds handlers/tools and receives cancellation and confirmation flows. | DSH tools, agents, and renderers should also use static declaration plus typed runtime binding. | - -Together these samples show that a sustainable extension platform does not hand every internal object to a universal `ctx`; it accumulates well-bounded domain contracts. - -## 4. Direct value to the Fabric RFC - -### 4.1 Make contribution/implementation binding a core principle - -The RFC should state that: - -- the Manifest is the sole authority for discoverable command, setting, menu, View, Renderer, and Tool metadata; -- plugin code only binds a handler or Provider to a declared ID; -- every contribution type independently specifies schema, IDs, conflicts, cardinality, conditions, fallback, and lifecycle; -- the Marketplace and Host can present features and compatibility without executing the plugin. - -The current RFC command design already follows this model. Every later standard extension point should use the same path. - -### 4.2 Separate activation policy, business events, and interceptors - -VS Code Activation Events only answer when to load an extension; they do not allow it to modify a business workflow. Fabric should still separate: - -- `contributes`: what the Host knows before code runs; -- the Host's activation policy: when a selected and authorized plugin enters an activation scope; -- `subscriptions`: which events an active plugin receives; -- observation: what an active plugin may observe; -- action: what it may request; -- interceptor: what it may modify through an ordered controlled pipeline. - -Fabric deliberately does not adopt demand activation in v0.1. A Host activates every selected and authorized plugin while assembling a runtime generation. Declared commands, Providers, and subscriptions do not trigger first-use activation. This matches current DSH composition, avoids missed events and first-call latency, and keeps activation order and failure reporting deterministic across Hosts. Demand activation can be reconsidered only in a later RFC backed by measured startup needs and conformance tests. - -### 4.3 Prefer Providers over universal events and Panels - -Tree, Task, Test, Debug, SCM, Language, and Tool capabilities use domain Providers instead of listening to a string event and mutating product internals. - -Fabric should define Providers/Renderers for observed high-frequency needs one at a time: - -- session tree; -- message and tool-result renderers; -- composer attachments; -- file viewers; -- model and search providers; -- tasks and jobs; -- package transactions. - -Each Provider needs input/output DTO, cancellation, concurrency, ordering/arbitration, errors, and teardown semantics. - -### 4.4 Model runtime faces and placement separately - -VS Code local/web/remote behavior shows that placement is not a simple platform field. A later Fabric Runtime Faces RFC should define: - -- the runtime type of each entrypoint; -- which capabilities are available in each face; -- requirements to run near UI, workspace/profile, DSH runtime, or isolated computation; -- identity, schema, cancellation, timeout, disconnect, and resource limits for cross-face RPC/streams; -- a prohibition on passing arbitrary JavaScript objects across faces or plugins. - -### 4.5 Specify synchronous and asynchronous disposal - -Fabric can define a stricter shutdown sequence than VS Code: - -1. abort `ctx.signal`; -2. stop accepting new operations; -3. drain in-flight operations within a bounded deadline; -4. run Disposable / AsyncDisposable resources in reverse order; -5. invoke explicit deactivate; -6. isolate timeout failures and record unreleased resources. - -This should become a Broker and testkit conformance requirement. - -### 4.6 Add a content-trust dimension - -Future Project/Profile Trust must not be folded into an ordinary capability grant. A plugin granted `process.run`, for example, should not automatically execute scripts from an untrusted repository. - -At minimum, Fabric should distinguish: - -- trusted publisher; -- approved plugin capability; -- trusted profile/workspace content; -- user intent confirmation for the current sensitive operation. - -This need not enter v0.1, but the Manifest, Host Descriptor, and Broker must leave room for it. - -### 4.7 Establish an experimental API channel - -Borrow the Proposed API discipline: - -- experimental capabilities are explicitly declared; -- only development Hosts or Hosts that explicitly allow them can enable them; -- stable Marketplace channels reject them by default; -- types and fixtures bind to a particular proposal version; -- maturation moves them into a stable namespace instead of silently changing an existing contract. - -## 5. What Fabric should not copy - -| VS Code design | Why Fabric should not copy it directly | -| --- | --- | -| Global `vscode` API namespace | Fabric should generate a minimal context from the Manifest; undeclared capabilities must not appear in the standard SDK. | -| Single-axis `engines.vscode` compatibility | Fabric has independent Hosts and must separate API, capability, Host, SDK, and Adapter versions. | -| Arbitrary string `when` context | Early versions should expose a small, versioned set of Host-owned condition keys instead of another Host-specific expression language. | -| ID-only `extensionDependencies` | Fabric services need required/optional semantics, version ranges, provider arbitration, and dynamic lifecycle. | -| Treating Node Extension Host as security isolation | It still has file, network, and process access; enforcement needs constrained modules and IPC. | -| Editor-specific layout and object model | Editor Groups, document selections, and debug concepts are not automatically cross-Host DSH standards. | -| Shipping every capability in the first release | VS Code accumulated these contracts over years; Fabric v0.1 should remain small. | - -## 6. Recommended changes to RFC 0001 - -### Add to RFC 0001 now - -1. Promote “static contribution + runtime binding by stable ID” to a core invariant. -2. Specify eager generation activation for every selected and authorized plugin; contributions and subscriptions are not activation triggers. -3. Distinguish Host activation policy, business subscriptions, observations, actions, and interceptors. -4. Specify activation-scoped Disposable / AsyncDisposable and bounded drain. -5. Preserve repeatable activation for profile recomposition and Provider replacement. -6. State that an execution process or Extension Host is a failure boundary, not automatically a security boundary. -7. Define how experimental capabilities mature into the stable standard. -8. Prohibit arbitrary cross-plugin object APIs through the standard contract. - -### Split into later RFCs - -- Runtime Faces and the cross-face bridge; -- UI Contribution, Provider, Renderer, Rich View, and conditions; -- Project/Profile Trust; -- Marketplace packaging, signing, scanning, and publication; -- multi-scope storage and Secret capabilities; -- domain semantics for Tools, Providers, tasks, and interceptors. - -### Keep v0.1 deliberately small - -The VS Code research does not justify putting dozens of extension points into Fabric v0.1. v0.1 still only needs to prove: - -- Manifest, Host Descriptor, and capability negotiation; -- activation scope and repeatable disposal; -- static `commands` declaration and handler binding; -- `storage.local`; -- immutable `messages.observe`; -- conformance between a fake Host and a pinned DSH Adapter. - -Its schema, Broker, and testkit must nevertheless leave the right seams for later domain contracts rather than exposing DSH, Cordis, DOM, or Loader internals as temporary public APIs. - -## 7. Conclusion - -VS Code's most important proof for Fabric is that an extension ecosystem can be powerful while extensions remain unable to modify product source or the UI DOM directly. The key is not a universal API, but a maintained set of domain extension points with clear responsibilities, static declarations, tests, cancellation, and disposal. - -Fabric should adopt that engineering discipline while retaining its own advantages: multi-Host capability negotiation, repeatable activation, versioned Adapters, clearer authorization layers than VS Code, and a future optional isolated execution tier. diff --git a/dsh-community-fabric/docs/research/vscode-extension-model.zh.md b/dsh-community-fabric/docs/research/vscode-extension-model.zh.md deleted file mode 100644 index 7249a4ec11..0000000000 --- a/dsh-community-fabric/docs/research/vscode-extension-model.zh.md +++ /dev/null @@ -1,317 +0,0 @@ -# 调研:VS Code 扩展模型及其对 Fabric RFC 的价值 - -[English](vscode-extension-model.md) | 中文 - -状态:源码与官方文档调研,2026-08-17。本文是 DSH Community Fabric 的设计输入,不代表已经发布的 Fabric API。 - -## 1. 调研范围 - -VS Code 值得参考,不是因为 DSH 要变成代码编辑器,而是因为 VS Code 已经长期解决了三类与 DSH 很相似的问题: - -1. 如何让插件在不修改产品源码和 DOM 的前提下扩展命令、设置、视图和业务能力; -2. 如何把静态可发现的功能声明与运行时实现分开; -3. 如何让本地、浏览器和远程环境中的扩展使用同一套产品 API,而不共享内部对象。 - -本次调查只使用微软官方资料: - -- [Extension Manifest](https://code.visualstudio.com/api/references/extension-manifest)、[Extension Anatomy](https://code.visualstudio.com/api/get-started/extension-anatomy)与[Contribution Points](https://code.visualstudio.com/api/references/contribution-points); -- [Extension Capabilities](https://code.visualstudio.com/api/extension-capabilities/overview)、[Extension Host](https://code.visualstudio.com/api/advanced-topics/extension-host)、[Remote Extensions](https://code.visualstudio.com/api/advanced-topics/remote-extensions)与[Web Extensions](https://code.visualstudio.com/api/extension-guides/web-extensions); -- [Webview](https://code.visualstudio.com/api/extension-guides/webview)、[Workspace Trust](https://code.visualstudio.com/api/extension-guides/workspace-trust)、[Extension Runtime Security](https://code.visualstudio.com/docs/configure/extensions/extension-runtime-security)与[Proposed API](https://code.visualstudio.com/api/advanced-topics/using-proposed-api); -- 微软官方 [`vscode-extension-samples`](https://github.com/microsoft/vscode-extension-samples/tree/3d8442b16c7f353779e266f16295703b2b4a6dcc) 仓库的固定快照。 - -我们只阅读文档、manifest 和源码,没有安装依赖或执行样例扩展。 - -## 2. VS Code 实际提供了哪些功能 - -VS Code 的扩展能力不是一个万能 API,而是由静态声明、激活、运行时 API 和 Extension Host 四部分组成。 - -```text -package.json / contributes - ↓ 静态发现、索引、展示和兼容判断 -activation - ↓ 在真正需要功能时加载代码 -register handler / provider - ↓ 为声明过的 ID 绑定实现 -Extension Host - ↓ 隔离产品 UI,并选择本地、Web 或远程执行位置 -``` - -### 2.1 Manifest 与兼容信息 - -每个扩展以根目录 `package.json` 为 manifest。除了名称和版本,它还可以声明: - -- `engines.vscode`:兼容的 VS Code API/产品范围; -- `main` 与 `browser`:Node 和 Web Worker 入口; -- `contributes`:命令、设置、视图、主题、任务、工具等静态贡献; -- `activationEvents`:何时需要加载扩展; -- `extensionKind`:更适合靠近 UI 还是 workspace 运行; -- `extensionDependencies` 与 `extensionPack`:功能依赖和组合安装; -- `capabilities.untrustedWorkspaces` 与 `virtualWorkspaces`:受限环境中的支持程度。 - -这使市场和 Host 可以在不执行扩展代码时理解它的大部分外观和运行要求。 - -### 2.2 静态 Contribution Points - -[Contribution Points](https://code.visualstudio.com/api/references/contribution-points)覆盖的能力可以按产品目的归纳为: - -| 类别 | 已实现的典型功能 | 设计特点 | -| --- | --- | --- | -| 命令与导航 | `commands`、`menus`、`submenus`、`keybindings` | ID、标题和位置先声明;代码只绑定 handler。 | -| 设置与条件 | `configuration`、`configurationDefaults`、`when` 条件 | Schema 同时驱动校验、编辑器提示和设置 UI。 | -| 产品 UI | `views`、`viewsContainers`、`viewsWelcome`、`customEditors`、主题、颜色与图标 | Host 拥有布局和渲染;插件贡献元数据或 Provider。 | -| 工作执行 | `taskDefinitions`、`terminal`、`debuggers`、`problemMatchers` | 先声明可发现的类型,再由运行时 Provider 创建实例。 | -| 语言与文档 | `languages`、`grammars`、`snippets`、semantic token 类型 | 简单能力可以纯声明,无需可执行代码。 | -| 认证与 Provider | `authentication`、语言模型聊天 Provider | Host 统一展示和选择 Provider。 | -| Agent 与 AI | Agent、instructions、prompt、skill、language model tool 等贡献 | 静态描述名称、输入和用途,再绑定受控执行实现。 | -| 上手与教育 | `walkthroughs` | 安装后由 Host 展示步骤和完成条件。 | - -这份列表最重要的结论不是 Fabric 也要一次实现几十个扩展点,而是:每种扩展点都有单独的 schema、ID、生命周期、位置、条件和运行时绑定规则。 - -### 2.3 运行时 handler 与 Provider - -声明只描述“有什么”。真正的行为由运行时 API 提供: - -| 模式 | VS Code 示例 | 为什么有价值 | -| --- | --- | --- | -| Handler | `commands.registerCommand(id, handler)` | 一个声明 ID 只有一条明确执行路径。 | -| Data Provider | `registerTreeDataProvider(viewId, provider)` | Host 渲染树;插件只提供结构化数据和刷新事件。 | -| Domain Provider | completion、hover、task、debug、test、SCM、filesystem Provider | 每个领域拥有自己的输入、输出、取消和组合语义。 | -| Controller | Test Controller、Source Control 等 | Host 持有用户体验,插件管理受限的领域对象。 | -| Codec / Controller / Renderer | Notebook Serializer、Controller 与 MIME Renderer | 数据格式、执行和呈现可以由不同 contract 与运行环境承担。 | -| Rich View bridge | Webview `postMessage` / `onDidReceiveMessage` | 自定义 UI 与扩展进程之间只传消息,不共享 DOM 或对象。 | - -大量注册 API 返回 `Disposable`,长操作接收 `CancellationToken`。扩展把这些资源加入 `ExtensionContext.subscriptions`,停用时统一释放。 - -### 2.4 冲突和仲裁按领域定义 - -VS Code 没有用“后注册覆盖前注册”处理所有冲突,而是根据领域选择不同规则: - -- command、authentication provider、filesystem scheme 等要求唯一所有者,重复注册失败; -- 一些语言 Provider 会合并多个结果; -- 一些 Provider 按 selector 匹配度选择最佳实现; -- 多个 Custom Editor 同时匹配时,可以交给用户选择并保存默认项; -- 快捷键由 Host、扩展建议值和用户配置共同决定。 - -Fabric 不必复制 VS Code 的具体 selector 分数,但每项 capability 或 contribution 都必须明确:cardinality、selector、priority、merge / first-result / pipeline / user-choice、同优先级 tie-break、错误隔离、超时、取消、重复注册和热替换。加载顺序不能成为未写明的仲裁规则。 - -### 2.5 UI 能力的层次 - -VS Code 提供了一条明确的 UI 升级路径: - -1. 命令、菜单、设置、通知、Quick Pick、状态栏等原生 UI; -2. Tree View、Test Controller、SCM 等由 Host 渲染的 Provider UI; -3. 原生能力不够时才使用 Webview 或 Custom Editor。 - -官方明确禁止扩展访问 Workbench DOM 或注入自定义样式,并建议仅在必要时使用 Webview。Webview 运行在独立上下文,通过消息与扩展通信,还要处理 CSP、资源 URI、主题、无障碍、状态恢复和释放。 - -这与 Fabric 已提出的“声明式贡献 → 强类型 Provider/Renderer → 隔离富视图 → Host 私有扩展”四层模型高度一致。 - -### 2.6 运行位置与多环境 - -[Extension Host](https://code.visualstudio.com/api/advanced-topics/extension-host)可以是: - -- 本地 Node.js Host; -- 远程 Node.js Host; -- 浏览器/Web Worker Host。 - -扩展通过 `main` 或 `browser` 提供不同入口,通过 `extensionKind` 表达靠近 UI 或 workspace 的运行偏好。远程模式中,UI 和 workspace 可能在不同机器;扩展不能假设本地路径、进程和 UI 位于同一环境。 - -这个经验直接支持 Fabric 将 Host、Client、Worker 和 Rich View 定义为不同 runtime face,并规定它们只能通过版本化 DTO、RPC、stream 和 asset channel 通信。 - -VS Code 的 Virtual Workspace 和 FileSystemProvider 还说明,资源不一定是本地绝对路径。Fabric 的 portable resource contract 应使用 URI、opaque resource ID 和 Host-mediated file/artifact capability;只有明确的本地 Host 扩展才可以承诺真实磁盘路径。 - -### 2.7 生命周期、状态与取消 - -VS Code 在需要某项功能时才激活扩展。命令、View、语言、文件系统、任务等 contribution 可以触发对应 activation;新版 VS Code 还能从部分 contribution 自动推导激活条件,避免作者重复维护两份声明。 - -扩展获得 workspace/global state、文件目录和 SecretStorage。它需要把注册资源放进 subscriptions,并在 `deactivate` 中处理额外清理。 - -Fabric 要借鉴资源 ownership,但不能照搬“一次 Extension Host 会话只激活一次”的假设。DSH profile 重组、Provider 更换、HMR 和恢复都要求同一个插件能够重复 activate/dispose。 - -### 2.8 信任和安全事实 - -VS Code 的 Workspace Trust 可以声明扩展在不可信 workspace 中: - -- 完全支持; -- 完全禁用; -- 只提供有限功能,并屏蔽危险的 workspace 配置。 - -但微软同时明确说明:桌面 Node Extension Host 拥有和 VS Code 本身相同的文件、网络和进程权限。Extension Host 能隔离 UI 和部分故障,不等于插件已经进入权限沙箱。 - -因此 Fabric 必须继续区分: - -```text -Host 支持 capability -≠ 插件声明请求 -≠ 用户或策略授权 -≠ 技术强制隔离 -≠ 市场安全审核 -``` - -此外,发布者信任、插件能力授权、profile/workspace 内容信任也应是不同状态。 - -### 2.9 版本、实验 API 与工具链 - -VS Code 对稳定 Extension API 尽力保持向后兼容。不稳定 Proposed API 只能在 Insiders/开发环境中显式启用,不能作为普通 Marketplace 扩展依赖。 - -开发工具覆盖脚手架、类型、Extension Development Host、单元/集成测试、Web 测试、VSIX 打包和发布。Workspace Trust 还要求分别测试 trusted/untrusted 状态。 - -Fabric 应保留比 VS Code 更清晰的多轴版本:插件、manifest schema、Fabric API、capability/event、Host 产品、SDK 和 Adapter 不能被压成一个产品版本号。 - -## 3. 官方样例证明了什么 - -以下样例来自微软官方仓库固定 commit [`3d8442b`](https://github.com/microsoft/vscode-extension-samples/tree/3d8442b16c7f353779e266f16295703b2b4a6dcc): - -| 样例 | 实现模式 | Fabric 可验证的启示 | -| --- | --- | --- | -| [Hello World](https://github.com/microsoft/vscode-extension-samples/tree/3d8442b16c7f353779e266f16295703b2b4a6dcc/helloworld-sample) | Manifest 声明命令,代码按相同 ID 注册 handler 并保存 Disposable。 | contribution 与实现分离应是规范,不是风格建议。 | -| [Tree View](https://github.com/microsoft/vscode-extension-samples/tree/3d8442b16c7f353779e266f16295703b2b4a6dcc/tree-view-sample) | Manifest 声明容器、视图、命令、菜单和设置;代码提供 TreeDataProvider。 | 复杂侧栏不必先开放 DOM;先定义领域 Data Provider。 | -| [Webview View](https://github.com/microsoft/vscode-extension-samples/tree/3d8442b16c7f353779e266f16295703b2b4a6dcc/webview-view-sample) | Manifest 声明 View;代码注册 WebviewViewProvider。 | Rich View 仍有稳定 ID、Host placement 和 Provider 生命周期。 | -| [Custom Editor](https://github.com/microsoft/vscode-extension-samples/tree/3d8442b16c7f353779e266f16295703b2b4a6dcc/custom-editor-sample) | 文档模型与 Webview 分离,通过消息同步并接入 save/undo/redo。 | 富 UI 不能绕过标准领域模型和操作语义。 | -| [File System Provider](https://github.com/microsoft/vscode-extension-samples/tree/3d8442b16c7f353779e266f16295703b2b4a6dcc/fsprovider-sample) | 注册 URI scheme 和受控文件系统 Provider。 | 插件应实现 capability SPI,不应直接把内部存储对象交给其他插件。 | -| [Task Provider](https://github.com/microsoft/vscode-extension-samples/tree/3d8442b16c7f353779e266f16295703b2b4a6dcc/task-provider-sample) | Manifest 定义 task schema,代码发现和解析 task,操作带取消信号。 | 任务定义、实例发现、执行和取消需要独立 contract。 | -| [Test Provider](https://github.com/microsoft/vscode-extension-samples/tree/3d8442b16c7f353779e266f16295703b2b4a6dcc/test-provider-sample) | Host 提供 Test UI,插件维护测试树、运行 profile 和结果。 | Provider 可以承载复杂业务,而无需让插件控制整个 UI。 | -| [Chat 与工具](https://github.com/microsoft/vscode-extension-samples/tree/3d8442b16c7f353779e266f16295703b2b4a6dcc/chat-sample) | Manifest 声明 Chat/Tool 元数据,运行时绑定 handler/tool,并接收取消与确认流程。 | DSH tool、agent、renderer 也应采用静态声明 + typed runtime binding。 | - -这些样例共同说明:真正可持续的扩展平台不是提供一个可以拿到所有内部对象的 `ctx`,而是不断积累边界清晰的领域 contract。 - -## 4. 对 Fabric RFC 的直接参考价值 - -### 4.1 把 contribution 与实现绑定提升为核心原则 - -RFC 应明确: - -- Manifest 是命令、设置、菜单、View、Renderer、Tool 等可发现元数据的唯一权威来源; -- 插件代码只能为已声明 ID 绑定 handler 或 Provider; -- 每种 contribution 单独定义 schema、ID、冲突、数量、条件、fallback 和生命周期; -- 市场和 Host 不执行插件即可展示功能与兼容性。 - -当前 RFC 的 `commands` 已经符合这个方向,后续所有标准扩展点都应沿用同一模式。 - -### 4.2 分开激活策略、业务事件和拦截器 - -VS Code 的 Activation Event 只回答“何时加载插件”,并不代表插件可以修改业务流程。Fabric 仍应分开: - -- `contributes`:插件未运行时 Host 已经知道什么; -- Host activation policy:已选中、已授权插件何时进入 activation scope; -- `subscriptions`:active 插件会收到哪些事件; -- observation:激活后能观察什么; -- action:激活后能请求什么; -- interceptor:激活后能按受控顺序修改什么。 - -Fabric 明确不在 v0.1 采用按需激活。Host 在组装一次 runtime generation 时激活所有已选中、已授权的插件;已声明的 command、Provider 和 subscription 都不会在首次使用时隐式触发 activation。这样更符合当前 DSH 组合方式,也能避免漏事件、首次调用延迟,以及不同 Host 出现不一致的激活顺序和故障提示。只有后续 RFC 拿出真实启动数据和一致性测试后,才重新讨论按需激活。 - -### 4.3 Provider 优先于万能事件和万能 Panel - -Tree、Task、Test、Debug、SCM、Language、Tool 都使用领域 Provider,而不是让插件监听一个字符串事件后操作产品内部对象。 - -Fabric 应为真实高频需求逐项定义 Provider/Renderer: - -- session tree; -- message/tool result renderer; -- composer attachment; -- file viewer; -- model/search provider; -- task/job; -- package transaction。 - -每个 Provider 都必须有输入输出 DTO、取消、并发、排序/仲裁、错误和 teardown 语义。 - -### 4.4 Runtime face 与运行位置必须单独建模 - -VS Code 的 local/web/remote 经验说明,运行位置不是简单 platform 字段。Fabric 后续 Runtime Faces RFC 应定义: - -- 每个 entrypoint 的 runtime 类型; -- capability 在哪个 face 可用; -- 靠近 UI、workspace/profile、DSH runtime 或隔离计算的位置要求; -- 跨 face RPC/stream 的身份、schema、取消、超时、断线和资源限制; -- 不允许跨 face 或跨插件直接传任意 JavaScript 对象。 - -### 4.5 正式定义同步与异步释放 - -Fabric 可采用比 VS Code 更严格的释放顺序: - -1. `ctx.signal` 先 abort; -2. Broker 停止接受新 operation; -3. 在有界时间内等待 in-flight operation drain; -4. 按逆序执行 Disposable / AsyncDisposable; -5. 调用显式 deactivate; -6. 超时后隔离故障并记录未释放资源。 - -这应成为 Broker 和 testkit 的一致性要求。 - -### 4.6 增加内容信任维度 - -未来的 Project/Profile Trust 不应混入普通 capability grant。例如插件获准使用 `process.run`,不代表它可以自动执行未信任仓库中的脚本。 - -至少要区分: - -- 插件发布者是否可信; -- 插件请求的 capability 是否获批; -- 当前 profile/workspace 内容是否可信; -- 某次敏感操作是否得到用户意图确认。 - -这不必进入 v0.1,但 manifest、Host Descriptor 和 Broker 不能封死这个维度。 - -### 4.7 为 experimental API 建立正式通道 - -建议借鉴 Proposed API: - -- experimental capability 必须显式声明; -- 只能由开发 Host 或明确允许实验 API 的 Host 启用; -- 稳定市场渠道默认拒绝; -- 类型和 fixture 固定到具体 proposal 版本; -- 成熟后进入稳定命名空间,不能悄悄改变已有 contract。 - -## 5. 不应该照搬的部分 - -| VS Code 设计 | Fabric 不应直接照搬的原因 | -| --- | --- | -| 全局 `vscode` API namespace | Fabric 需要根据 manifest 生成最小 context,未声明 capability 不应出现在标准 SDK 中。 | -| `engines.vscode` 单轴兼容 | Fabric 有多个独立 Host,必须分开 API、capability、Host、SDK 和 Adapter 版本。 | -| 任意字符串 `when` context | 初期只应提供少量、版本化、Host-owned 的条件键,避免形成另一套无法跨 Host 的表达式语言。 | -| `extensionDependencies` 只按扩展 ID | Fabric 的服务依赖需要 required/optional、版本范围、Provider 仲裁和动态生命周期。 | -| Node Extension Host 等同安全隔离 | 它仍拥有文件、网络和进程权限;真正隔离需要受控模块和 IPC。 | -| 编辑器专属布局与对象模型 | Editor Group、文档 selection、调试协议等不能直接成为 DSH 跨 Host 标准。 | -| 所有功能都进入首版 | VS Code 的能力经过多年逐项定义;Fabric v0.1 应继续保持小范围。 | - -## 6. 对 RFC 0001 的建议修改 - -### 现在可以纳入 RFC 0001 - -1. 将“静态 contribution + 稳定 ID 的运行时绑定”提升为核心不变量; -2. 明确所有已选中、已授权插件在 generation 组装时主动激活;contribution 与 subscription 都不是激活触发器; -3. 区分 Host 激活策略、业务 subscription、observation、action 和 interceptor; -4. 规定 activation-scoped Disposable / AsyncDisposable 和有界 drain; -5. 保留 Fabric 的重复 activation,适配 profile 重组和 Provider 更换; -6. 明确执行进程/Extension Host 是故障边界,不自动等于安全边界; -7. 定义 experimental capability 进入稳定标准的流程; -8. 禁止插件通过标准 contract 导出跨插件任意对象 API。 - -### 应拆成后续独立 RFC - -- Runtime Faces 与跨 face bridge; -- UI Contribution、Provider、Renderer、Rich View 和条件系统; -- Project/Profile Trust; -- Market 打包、签名、扫描和发布规范; -- 多 scope storage 与 Secret capability; -- Tool、Provider、任务和拦截器的领域语义。 - -### v0.1 仍然保持小范围 - -VS Code 调研不会把几十个扩展点一次塞进 Fabric v0.1。v0.1 仍只需要证明: - -- Manifest、Host Descriptor 和 capability 协商; -- activation scope 与可重复释放; -- `commands` 的静态声明和 handler 绑定; -- `storage.local`; -- 不可修改的 `messages.observe`; -- fake Host 与固定 DSH Adapter 的一致性测试。 - -但 v0.1 的 schema、Broker 和 testkit 必须为上述后续领域 contract 留出正确位置,不能先暴露 DSH、Cordis、DOM 或 Loader 内部对象作为临时公共 API。 - -## 7. 结论 - -VS Code 对 Fabric 最重要的证明是:插件生态可以很强大,同时禁止插件直接修改产品源码和 UI DOM。实现这一点的关键不是一个万能 API,而是长期维护一组职责清楚、可静态声明、可测试、可取消、可释放的领域扩展点。 - -Fabric 应吸收这套工程纪律,同时保留自己的优势:多 Host capability 协商、重复 activation、版本化 Adapter、比 VS Code 更清晰的授权层次,以及未来可选的强隔离执行档位。 diff --git a/dsh-community-fabric/docs/rfcs/0001-plugin-manifest-capabilities-events.i18n.yaml b/dsh-community-fabric/docs/rfcs/0001-plugin-manifest-capabilities-events.i18n.yaml deleted file mode 100644 index 6965781c56..0000000000 --- a/dsh-community-fabric/docs/rfcs/0001-plugin-manifest-capabilities-events.i18n.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Bilingual-pair consistency record. Both languages carry equal authority. -# Update both files and record their normalized Git blob hashes after editing either side. -0001-plugin-manifest-capabilities-events.md: d705fa5cbb10518f4ed999c6b0863f496da8e38b -0001-plugin-manifest-capabilities-events.zh.md: 7fcbed64300a4affcd32c3da59c3a71a8cb030c0 diff --git a/dsh-community-fabric/docs/rfcs/0001-plugin-manifest-capabilities-events.md b/dsh-community-fabric/docs/rfcs/0001-plugin-manifest-capabilities-events.md deleted file mode 100644 index d705fa5cbb..0000000000 --- a/dsh-community-fabric/docs/rfcs/0001-plugin-manifest-capabilities-events.md +++ /dev/null @@ -1,452 +0,0 @@ -# RFC 0001: A Unified DSH Plugin Contract — Manifest, Capabilities, and Events - -English | [中文](0001-plugin-manifest-capabilities-events.zh.md) - -| Field | Value | -| --- | --- | -| Status | Draft / request for comments | -| Target | Experimental v0.1 | -| Scope | Interoperability contract between plugins and Hosts | -| Reference implementation | DSH Community Fabric (not implemented) | -| Discussion | [Community Issue #23](https://github.com/omdsh-dev/community/issues/23), related discussions, and pull requests editing this document | - -## 0. Summary - -Define a community-governed, statically analyzable interoperability standard for DSH plugins. A plugin declares its identity and capability requirements in a manifest; a Host publishes a machine-readable descriptor and uses negotiation plus a common lifecycle to decide whether and how to activate it. - -The proposal borrows the manifest-and-capability idea from browser extensions and the stable lifecycle-hook idea from Forge/Fabric. It does not claim to provide browser-grade isolation, and it must not create a second plugin-loading ecosystem beside DSH and Cordis. - -## 1. Draft boundary - -This is a community discussion draft, not an official DeepSeek or DSH standard and not an API developers can use today. - -Existing DSH plugins continue to use current package metadata, Cordis services, slots, and patches. Fabric begins as an interoperability layer assembled by Host integrations over a versioned DSH Adapter. It neither requires immediate upstream changes nor requires a Host to remove built-in or legacy extensions. - -The words MUST, SHOULD, and MAY describe the strength of proposals. They do not create a stable compatibility promise until the RFC is accepted, schemas are published, and conformance tests exist. - -The filename, topology, composition, and provenance refinements in this revision respond to counterexamples collected in [community Issue #23](https://github.com/omdsh-dev/community/issues/23). They are decisions of this draft, not a claim that the community discussion has already reached formal consensus. - -## 2. Motivation - -The community now has GUI, Web UI, TUI, launcher, modpack, and distribution projects. Growth exposes common problems: - -- compatibility requirements cannot be inspected reliably before installation; -- extensions tied to loader details, internal functions, or source patches break when implementations change; -- different Hosts expose different paths for the same user need; -- multiple plugins can alter one behavior without declaration, ordering, or conflict rules; -- markets and launchers lack static compatibility metadata and fall back to hand-tested locked combinations. - -This proposal concentrates upstream-specific change in the versioned DSH Adapter while Host integrations own product policy and UX. Governance and the plugin-facing contract are not versioned with one DSH release. When upstream behavior changes, an Adapter and Host must adapt, explicitly downgrade, or stop advertising a capability rather than pretending its old semantics still hold. - -This is not absolute independence from upstream. If upstream no longer exposes the observation or operation required for a capability, that capability cannot honestly be implemented. - -## 3. Goals - -1. **Static declaration:** inspect identity, version, entrypoints, capability requirements, and declarative contributions without executing code. -2. **Compatibility negotiation:** reject missing required capabilities clearly and allow deterministic degradation for missing optional capabilities. -3. **One community contract:** one normative API and behavior model for each operation covered by the standard. -4. **Adapt existing ecosystems:** implement the contract on DSH, Cordis, or another native Host mechanism rather than creating a parallel loader. -5. **Verifiability:** publish schemas, fixtures, and headless conformance tests for manifests, Host Descriptors, negotiation, and lifecycle behavior. -6. **Lower user friction:** let markets and launchers distinguish compatible, incompatible, awaiting authorization, tested, and unknown combinations before installation. - -## 4. Non-goals - -- Requiring immediate adoption by DSH upstream. -- Standardizing the internal rendering technology of GUI, Web UI, or TUI Hosts. -- Building a package manager, market backend, ranking system, or account service in this RFC. -- Treating valid static metadata as a source-code security review. -- Promising that arbitrary rich UI runs unchanged on every Host. -- Standardizing a complete set of mutable `before-*` events in v0.1. -- Requiring Hosts to remove built-in, legacy, or non-standard extension paths; those paths simply do not participate in Fabric conformance claims. - -## 5. Trust and execution modes - -Capability handling has four separate stages: - -1. **support:** the Host says it can provide a capability; -2. **request:** the plugin asks for it in its manifest; -3. **grant:** the user or policy authorizes it; -4. **enforcement:** isolation prevents the plugin from bypassing the grant. - -The v0.1 reference adapter may use a **trusted in-process** mode. In this mode, capabilities support compatibility, consent, and auditing; they are not a security sandbox. The Host must say so prominently. - -A future **isolated** mode needs a separate specification covering process or realm isolation, module controls, mediated IPC, resource limits, filesystem and network scopes, crash recovery, and platform differences. A Host without that evidence must not claim technical permission enforcement. - -## 6. Version model - -The following versions are distinct: - -| Name | Meaning | -| --- | --- | -| `version` | The plugin's own SemVer version. | -| `manifestVersion` | The JSON document structure version. | -| `apiVersion` | The community Host API compatibility range requested by the plugin. | -| Capability/event version | The contract version for one capability or payload; v0.1 may temporarily tie it to the API version. | -| Host version | The product version of a GUI, Web UI, TUI, or launcher. | -| SDK version | The release version of types and tooling, not automatically the standard version. | - -Breaking standard changes require a new incompatible API range. During `0.x`, experimental releases must state their own compatibility discipline instead of presenting `1.x` stability. - -### 6.1 Terminology - -- **Host product:** a GUI, Web UI, TUI, or launcher product that supports plugins. -- **Host-side runtime face:** the Node.js environment inside a Host product that executes a v0.1 plugin entrypoint. -- **Activation instance:** one bounded activation of one plugin entrypoint; lifecycle and resource ownership are scoped to it. -- **Adapter:** the implementation mapping Fabric capabilities to a concrete DSH/Cordis version. -- **Runtime:** the execution placement plus trust and resource boundary in which plugin code runs. -- **Presentation:** the user-facing capabilities attached to one interaction, such as a GUI window, browser, TUI, or headless caller. -- **Control:** the authority that selects plugins, applies policy, routes an invocation, and owns cancellation. -- **Transport:** the mechanism carrying contract messages between those parties, such as in-process calls, IPC, WebSocket, or SSH forwarding. - -v0.1 specifies only a Host-side Node.js entrypoint and its activation instance. Browser Client, native UI, isolated Worker, and other executable faces plus their communication protocols are later RFCs. TUI is a Host product in this document, not a runtime-face name. - -### 6.2 Runtime topology is not a Host type - -Runtime, Presentation, Control, and Transport are independent axes. They MUST NOT be collapsed into fields such as `hostType: "gui"` or `isRemote: true`: a plugin can execute on a remote Node.js Runtime, be controlled by a server-side session, present through a local GUI, and cross SSH plus WebSocket transports in one invocation. Transport never proves where code executes, which surface is present, or who may authorize an action. - -A Host Descriptor may advertise the presentation kinds it can potentially route, but that is not a grant and not evidence that a surface exists for a particular call. Any future presentation capability is **invocation-scoped**: the Control plane supplies a versioned, immutable invocation snapshot describing the currently attached surface and grants, and plugin code must not cache that offer as activation-global state. [RFC 0002](0002-runtime-presentation-invocation-transport.md) proposes identities, routing, authentication, cancellation, reconnect, replay, and failure boundaries without enlarging v0.1. Experimental v0.1 exposes no generic presentation channel. - -## 7. Core model - -```text -Manifest (plugin identity, requirements, exports, and contributions) - ↓ -Host Descriptor (Host support and execution mode) - ↓ -Negotiation + Authorization - ↓ -Lifecycle + Events - ↓ -Capability-scoped Host API -``` - -### 7.1 Manifest - -v0.1 freezes the manifest as static JSON at **`dsh-plugin.json` in the package root** and rejects dynamically generated JavaScript manifests. The distinct name is deliberate: the [Agent Plugins Specification](https://agent-plugins.org/specification) Working Draft already reserves root `plugin.json` for its own manifest contract. A package may support both ecosystems with both files, but neither file overrides or silently extends the other. - -The Phase 0 schema MUST require a top-level `$schema` canonical identifier. A Host selects a locally supported, bundled schema from that identifier and MUST NOT retrieve a schema or other validation policy over the network while loading a plugin. The canonical identifier becomes immutable when the schema is published. `$schema` selects manifest parsing and validation; if `manifestVersion` remains in the final shape, it MUST match the schema version selected by `$schema` rather than create another negotiation axis. The separate `apiVersion` remains the plugin's requested runtime Host API range. The placeholder below is therefore a discussion marker, not a published identifier or valid fixture. - -```json -{ - "$schema": "", - "manifestVersion": "0.1.0", - "id": "com.example.message-memory", - "name": "Message Memory", - "version": "1.2.0", - "apiVersion": ">=0.1.0 <0.2.0", - "entrypoints": { - "host": "dist/host.js" - }, - "capabilities": { - "required": { - "messages.observe": ">=0.1.0 <0.2.0", - "commands": ">=0.1.0 <0.2.0", - "storage.local": ">=0.1.0 <0.2.0" - }, - "optional": { - "ui.panel.basic": ">=0.1.0 <0.2.0" - } - }, - "subscriptions": [ - { "event": "messages.observe", "version": ">=0.1.0 <0.2.0" } - ], - "contributes": { - "commands": [ - { "id": "com.example.message-memory.show-last", "title": "Show Last Message" } - ] - } -} -``` - -The final schema must also define: - -- plugin ID syntax, namespace ownership, and collision handling; -- entrypoints constrained to the package root, module format, and execution environment; -- whether Host, renderer, and worker entrypoints coexist and how they communicate; -- capability version ranges and sensitive scopes; -- renewed consent when an update adds capabilities; -- contribution ID namespaces and conflicts; -- the authority of fields duplicated in npm package metadata. - -The schema and tooling MUST keep five declaration classes semantically separate, even if their final JSON nesting is refined with fixtures: - -| Declaration | Meaning | -| --- | --- | -| `requires` | Versioned Host capabilities or service contracts needed by the plugin, including required and optional dependencies. | -| `permissions` | User- or policy-granted sensitive scopes; support alone does not grant them. | -| `provides` | Versioned service or Provider contracts exported for other plugins or the Host to consume. | -| `contributes` | Declarative product metadata discoverable before code executes. | -| `subscriptions` | Event interests that control delivery after eager activation, not activation triggers. | - -These five names reserve distinct semantics for the standard roadmap; they do not make every class executable in v0.1. The v0.1 schema MUST reject `provides` and `requires.services` until the service-composition contract and a versioned schema revision are accepted. A Host must not preserve an unsupported field inertly and present it as functional. The initial schema accepts only the requirement, permission, subscription, and contribution forms whose concrete v0.1 contracts exist. - -Sharing one manifest does not make these the same compatibility or security object. A consumer depends on a `provides` contract ID and version, never on the concrete package chosen to satisfy it. v0.1 reserves this declaration class but exposes no general plugin-provided service runtime; [RFC 0003](0003-service-providers-and-composition.md) proposes cardinality, multiple instances, user selection, replacement, and dependency cycles for a later runtime. - -In the discussion example, `capabilities.required` / `optional` is the provisional v0.1 encoding of Host capability requirements, while `subscriptions` separately requests event delivery. The final schema may nest the former under `requires`; it must not merge either declaration with permissions, exports, or contributions. - -Following the VS Code Contribution Point pattern, `contributes` describes metadata that a Host can discover before plugin code runs; it is not a capability, grant, runtime implementation, or activation trigger. After activation, plugin code may bind handlers or Providers only to IDs declared in the manifest. Tooling and conformance tests should report both declared-but-unbound and bound-but-undeclared entries. - -The standard does not mandate a particular loader or source transformer. A Host locates entrypoints from the manifest and activates them through its native mechanism following the standard lifecycle. Fabric-managed plugins use this path; other Host extension paths are labeled non-standard. - -A conforming Fabric entrypoint has no runtime dependency on DSH, Cordis, Desktop, or Adapter packages. Package inspection, dependency rules, and conformance fixtures enforce this supported boundary against accidental coupling; trusted in-process mode still cannot turn it into a malicious-code sandbox. - -### 7.2 Host Descriptor - -Every compatible Host publishes a machine-readable descriptor. This is also only a discussion shape: - -```json -{ - "descriptorVersion": "0.1.0", - "id": "org.example.dsh-webui", - "version": "1.4.0", - "apiVersions": ["0.1.0"], - "execution": { - "environment": "node", - "trustMode": "trusted-in-process" - }, - "capabilities": { - "messages.observe": "0.1.0", - "commands": "0.1.0", - "storage.local": "0.1.0" - }, - "platforms": ["darwin-arm64", "win32-x64", "linux-x64"] -} -``` - -Compatibility is primarily derived from API and capabilities, not ambiguous names such as `gui>=2.0`. Exceptional Host constraints use stable organization-namespaced IDs. - -The descriptor reports the Runtime and trust mode it actually supplies. It MUST NOT use `hostType` or `isRemote` as a shortcut for Runtime, Presentation, Control, or Transport, and a statically advertised presentation kind does not become an activation-wide capability. - -Markets distinguish at least: - -- **declared compatible:** static negotiation passed; -- **awaiting authorization:** support exists but a sensitive grant is missing; -- **tested:** a named Host, system, plugin, and suite combination passed; -- **incompatible:** a required capability or API range cannot be met; -- **unknown:** evidence is insufficient. - -Declared compatibility is neither test evidence nor a security review. - -The default product experience should show but disable incompatible plugins and list missing capabilities. Hiding them makes a plugin appear to vanish when a user changes device or profile. - -### 7.3 Capabilities - -A capability is a versioned Host service contract. Candidate v0.1 namespaces are: - -| Name | Purpose | v0.1 status | -| --- | --- | --- | -| `storage.local` | Host-managed plugin-private persistence. | v0.1 negotiated capability | -| `commands` | Bind handlers to commands declared in the manifest. | v0.1 negotiated capability | -| `messages.observe` | Observe immutable message events. | v0.1 negotiated capability | -| `sessions.read` | Read a versioned, redacted session view. | Later design | -| `ui.panel.basic` | A tiny, versioned declarative UI subset. | Later prototype | -| `sessions.actions`, `net.*`, `fs.*` | Session mutation, network, and file access. | Deferred | - -Each capability defines methods, schemas, errors, cancellation, lifecycle, privacy, resource limits, and tests. Private extensions use organization namespaces such as `x-org.example.tui.keymap`. - -The standard publishes a versioned, machine-readable Capability Registry rather than asking implementations to scrape this RFC. Every entry contains at least its canonical ID and version, status, owning RFC, input/output/error schema identifiers and immutable hashes, sensitivity and grant class, lifecycle scope, and deprecation or replacement metadata. A Host Descriptor advertises exact registry entries it implements; private entries remain explicitly namespaced and cannot masquerade as standard capabilities. - -Every contribution and Provider contract also defines cardinality, selector, priority, merge / first-result / pipeline / user-choice behavior, equal-priority tie-breaking, error isolation, timeout, duplicate registration, and hot replacement. Load order cannot become an undocumented conflict-resolution rule. - -The “one standard method” rule applies inside the Fabric contract. It does not claim to stop trusted in-process code from importing Node.js APIs directly. - -Declarative contributions never imply runtime access or a grant. Manifest command metadata is authoritative; a command contribution also requests `commands`, and plugin code only binds its handler by ID. Required APIs are present after negotiation. Optional APIs remain optional until an explicit capability check narrows them. - -The v0.1 `commands` contract is deliberately limited to **flat action leaves**: one globally namespaced command ID maps to one declared action and one activation-owned handler. A Host chooses whether that action appears in a palette, menu, button, or TUI without changing its identity. Nested command trees, subcommands, CLI-style option parsing, interactive prompts, streaming output, and background command sessions are outside v0.1. - -Device codes, temporary URLs, QR codes, confirmations, and similar short-lived interaction MUST NOT be smuggled into persistent session messages. [RFC 0002](0002-runtime-presentation-invocation-transport.md) proposes expiring, sensitivity-labelled presentation items and delivery acknowledgements for a later protocol. Until then, a v0.1 command cannot require such a channel. - -### 7.4 Lifecycle and events - -Host product state and plugin activation are separate state machines. A Host normally moves through: - -```text -starting → ready → stopping → stopped -``` - -While a Host is ready, each activation instance independently moves through: - -```text -discover → validate → negotiate → authorize -→ activating → active → deactivating → disposed -``` - -Experimental v0.1 does not use demand activation. After discovery, negotiation, and authorization, a Host activates every selected plugin while assembling a runtime generation. Contributions describe discoverable features and subscriptions control event delivery; invoking a command, requesting a Provider, or matching a subscription never activates an inactive plugin. Future interceptors still need independent grants, ordering, and failure contracts. - -A Host guarantees ordering for a normal activation and best-effort deactivation during normal shutdown, but cannot guarantee deactivation after a crash, power loss, or forced termination. Plugin cleanup is idempotent and recovery-aware. A plugin may activate and dispose repeatedly while the Host remains ready, including during HMR or profile recomposition. - -Activation and deactivation are Host-invoked activation-instance hooks, not ordinary business events a plugin subscribes to itself. The same v0.1 Host-side entrypoint may activate repeatedly; the final lifecycle contract defines repeated activation, HMR, and provider replacement. Client or isolated-Worker scopes and cross-face communication belong to later RFCs. - -v0.1 standardizes lifecycle plus one immutable `messages.observe` event. It uses a versioned envelope with at least: - -- `envelopeVersion`, `eventType`, and `eventVersion`; -- a unique `eventId`, source `runtimeId`, and `occurredAt` time; -- `scopeType`, `scopeId`, and a monotonically increasing `scopeSequence` for ordering within that scope; -- optional `correlationId` and `causationId` for one operation chain; -- `privacyClass` and an explicit `redactions` summary; -- a canonical `payloadSchema` identifier plus the immutable `payload`. - -The payload contract still needs to freeze message fields, sensitive-field rules, concurrency, backpressure, error isolation, cancellation signals, and shutdown behavior. Cross-scope global order is not implied by timestamps or delivery order. - -The standard also publishes a machine-readable Event Registry. Each entry binds the canonical event ID and version to its envelope and payload schema identifiers plus immutable hashes, scope and ordering rules, privacy/redaction class, delivery/backpressure contract, error policy, status, owning RFC, and deprecation metadata. `subscriptions` and Host Descriptors refer to these registry entries; implementations do not invent equivalent event names from prose. - -Mutable or cancellable `before-*` events are deferred. A later RFC must define plugin order, priorities, merge behavior, cancellation continuation, timeout, errors, rollback, reentrancy, per-session ordering, cross-session concurrency, and privacy. - -### 7.5 Host API - -A future SDK may provide an experience like this, but package names and signatures are not frozen: - -```ts -export default definePlugin((ctx) => { - ctx.commands.handle('com.example.message-memory.show-last', async () => { - const lastMessageId = await ctx.storage.local.get('lastMessageId') - ctx.log.info('Last observed message', { lastMessageId }) - }) - - ctx.messages.onReceived(async (message) => { - await ctx.storage.local.set('lastMessageId', message.id) - }) - - return { - deactivate() { - // release resources owned by this activation - }, - } -}) -``` - -The context exposes only negotiated and granted standard capabilities. A missing required capability prevents activation. A missing optional capability has no API and requires an explicit degradation path. - -In trusted in-process mode, this remains a supported contract facade rather than a JavaScript security boundary. - -### 7.6 Broker ownership and effect ledger - -Every standard registration crosses the Host API Broker, which assigns it to the current activation instance. From v0.1 onward, the Broker MUST maintain a machine-readable effect ledger so diagnostics and cleanup can answer which plugin created, replaced, or failed to release a resource. The minimum record contains: - -- `ledgerVersion`, `recordId`, a monotonic `sequence`, and `recordedAt`; -- owner `pluginId`, `pluginVersion` or `manifestDigest`, `activationId`, and `runtimeId`; -- `effectId`, `effectKind`, canonical contract ID/version, and `resourceId` when one exists; -- `operation` and resulting `state`, covering at least `create`, `bind`, `replace`, `release`, and `cleanup-failed`; -- optional `correlationId`, previous/new owner or related effect IDs for replacement, and a non-sensitive `outcome` or canonical `errorCode`; -- `sensitivityClass` and the applied redaction policy. - -Command handlers, subscriptions, Providers, UI contributions, and other activation-owned registrations use the same ownership rule when those contracts exist. The Broker coordinates disposal with the native Host lifecycle and records the outcome. Ledger records MUST NOT include message bodies, secrets, command arguments, or arbitrary plugin payloads by default. The ledger improves provenance and diagnosis; trusted in-process code can still bypass it, so it is not proof of sandbox enforcement. - -## 8. Host obligations - -A compatible Host should: - -1. read static manifests for Fabric-managed plugins without executing dynamic manifest code; -2. publish an honest Host Descriptor and stop advertising semantics it cannot preserve; -3. validate schemas, negotiate API/capabilities, and obtain required grants before executing plugin code; -4. explain missing required capabilities in user language and make optional degradation deterministic; -5. preserve normal lifecycle ordering and catch ordinary errors crossing standard callback/Promise boundaries; trusted in-process code cannot isolate `process.exit`, native crashes, or infinite loops; -6. publish its execution mode and never describe trusted in-process code as sandboxed; -7. resolve standard capabilities and events through the published machine-readable registries rather than product-local aliases; -8. assign every standard registration to a plugin and activation, maintain the minimum effect ledger, and attempt bounded cleanup on disposal; -9. run versioned conformance tests and publish the environment and result. - -## 9. Relationship to DSH and Cordis - -Fabric must not answer loader fragmentation by inventing another loader. A reference adapter maps the Fabric contract onto existing DSH/Cordis composition: - -- the manifest provides static discovery and negotiation; -- the Host integration asks the versioned DSH Adapter to map granted capabilities to existing services, slots, routes, or events; -- native Cordis lifecycle retains ownership of real resource cleanup; -- a capability without an equivalent mapping is reported unsupported rather than approximated through private APIs; -- existing plugins may gain manifests through migration tools but do not become invalid merely because Fabric exists. - -The portable v0.1 contract rejects source modification, monkey patching, and private-function hooks as plugin APIs. Existing `cordis.patch.yml` files are DSH's official declarative profile-composition layers, not source patches; the Fabric Adapter itself may enter a profile through a standard bundle patch. A separately labelled, version-pinned Adapter experiment may study a reviewed private compatibility bridge, but it cannot expose patch targets to ordinary plugins, advertise them as portable capabilities, or pass portable conformance on that basis. - -The current `desktopProfiles` and `desktopPnpm` services in this repository are Desktop-specific Host contracts, not automatic cross-Host standards. Standardizing one of their use cases requires a separate capability RFC and evidence from multiple Hosts. - -## 10. Markets, modpacks, and evidence - -A market can index manifests and Host Descriptors to calculate compatibility before installation. Catalog inclusion is not review, endorsement, or security certification. - -Modpacks remain first-class reproducible releases: they can lock standard, Host, plugin, platform, and test-suite versions. Locking does not replace SemVer contracts or compatibility windows. - -A “tested” record binds standard/schema version, Host ID/version/platform, plugin ID/version, conformance suite version and commit, date, and outcome. - -## 11. Minimal delivery path - -Experimental v0.1 is complete only when the minimum Phase 0–2 contracts have specifications and tests. Phase numbers describe implementation order, not conflicting version scopes. - -Its exact runtime surface is: baseline `host.info`, `log`, and lifecycle cancellation, plus negotiated `storage.local`, `commands`, and one immutable `messages.observe` event. Other names in this RFC are future candidates. - -### Phase 0: standard foundations - -- RFC 0000 for governance and status transitions; -- package-root `dsh-plugin.json` Manifest Schema with a required canonical `$schema` identifier; -- Host Descriptor Schema; -- machine-readable Capability and Event Registries with immutable schema hashes; -- valid and invalid fixtures; -- a pure capability negotiator; -- a headless conformance harness skeleton. - -### Phase 1: trusted reference adapter - -- one explicit Node.js Host execution environment; -- discover, validate, negotiate, activate, and deactivate; -- only low-risk, non-mutating initial capabilities; sensitive read access still requires grants and redaction; -- Broker-assigned plugin/activation ownership and the minimum effect ledger; - -### Phase 2: events and a minimal contribution - -- one immutable `messages.observe` event with the minimum versioned envelope; -- `storage.local`; -- `commands` as flat action leaves with same-ID runtime binding, without command trees or interactive presentation; -- activation-scoped Disposable / AsyncDisposable, bounded drain, and repeated activation; -- failure, duplicate-ID, undeclared/unbound contribution, timeout, cancellation, and shutdown fixtures. -- after the complete v0.1 surface exists, interoperability evidence from at least two different Host products or integrations; they may share the same versioned DSH Adapter. - -### Separate later RFCs - -- mutable `before-*` events; -- [Runtime / Presentation / Control / Transport identities, invocation routing, command trees, and ephemeral presentation](0002-runtime-presentation-invocation-transport.md); -- [Service Provider contracts, `provides` composition, cardinality, selection, and dependency cycles](0003-service-providers-and-composition.md); -- UI Contribution, Provider, Renderer, Rich View, conditions, and a minimal cross-Host UI IR; -- Project/Profile Trust and experimental-capability graduation; -- multi-scope storage and Secret capabilities; -- filesystem, network, and session-write permissions; -- isolated execution and mediated IPC; -- [install impact previews plus full provenance, validation, and diagnostic exchange beyond the minimum effect ledger](0004-provenance-validation-and-diagnostics.md); -- market compatibility labels and test-result interchange. - -## 12. Governance requirements - -Before this RFC becomes Accepted, RFC 0000 should define statuses, minimum public review, decision and appeal processes, capability/event naming registries, breaking changes, deprecation, errata, private security reporting, licensing, and the boundary between a community and official standard. - -The reference implementation cannot define the standard by accident. Behavior belongs to the contract only when normative text, fixtures, and conformance tests describe it. - -## 13. v0.1 acceptance and conformance evidence - -Experimental v0.1 separates evidence into four classes: - -1. **Schema validation:** public `dsh-plugin.json` and Host Descriptor Schemas, required recognized `$schema`, complete SemVer rules, registries, and valid/invalid fixtures. -2. **Host conformance:** required/optional negotiation, unknown versions, denied grants, activation order, best-effort shutdown, standard callback errors, truthful Runtime/trust descriptions, and plugin/activation effect ownership. -3. **Plugin validation:** manifest/entrypoint consistency, declared-capability use, matching contribution declarations/bindings without ID conflicts, optional degradation, releasable synchronous/asynchronous resources after repeated activation, and understandable errors. -4. **Interop evidence:** two independent Host products or integrations and three example plugins complete the same scenarios as the standard-graduation evidence for v0.1. The Hosts may share a DSH Adapter, but their integration and descriptor evidence remain independent. - -Because Events are in both the RFC title and v0.1 scope, at least one immutable observation event has the minimum versioned envelope, a payload schema, privacy redaction, ordering within its scope, backpressure/timeout, error handling, shutdown semantics, and headless contract tests. - -A Host may claim only that it passes the v0.1 Host conformance suite; a plugin may claim only that it passes v0.1 plugin validation. Neither claim means “safe plugin” or “officially certified.” - -## 14. Open questions - -1. Who owns and publishes canonical `$schema` identifiers and offline compatibility mappings? -2. How are publisher namespaces proven, transferred, and disputed? -3. Which Node.js version, module format, and entrypoint-loading boundary should v0.1 support? -4. Which message content fields and redaction rules belong inside the now-defined v0.1 `messages.observe` envelope? -5. Do capability versions use independent SemVer or follow `apiVersion` during v0? -6. What evidence proves that flat `commands` actions behave consistently across GUI, Web UI, and TUI Hosts? -7. Who publishes, stores, and revokes Host conformance results? -8. How should RFC review, merge rights, and dispute resolution be governed by the community? - -## 15. Why now - -Multiple Hosts, plugin authors, and distribution channels already exist. A static and testable interoperability contract is cheaper to establish now than after interfaces fragment further. - -The reusable asset is not a loader. It is the declaration, negotiation, lifecycle, and verification method. Fabric should be a community-maintained adapter and experiment, not another unilateral parallel plugin system. - -The next step is not automatic standardization after one week. It is to collect counterexamples publicly, finish governance plus schema fixtures, and validate the minimum contract with two Hosts and real plugins. diff --git a/dsh-community-fabric/docs/rfcs/0001-plugin-manifest-capabilities-events.zh.md b/dsh-community-fabric/docs/rfcs/0001-plugin-manifest-capabilities-events.zh.md deleted file mode 100644 index 7fcbed6430..0000000000 --- a/dsh-community-fabric/docs/rfcs/0001-plugin-manifest-capabilities-events.zh.md +++ /dev/null @@ -1,473 +0,0 @@ -# RFC 0001:DSH 统一插件 contract——Manifest、Capability 与事件模型 - -[English](0001-plugin-manifest-capabilities-events.md) | 中文 - -| 字段 | 内容 | -| --- | --- | -| 状态 | Draft / 征求意见 | -| 目标版本 | 实验性 v0.1 | -| 范围 | 插件与 Host 的互操作 contract | -| 参考实现 | DSH Community Fabric(尚未实现) | -| 讨论方式 | [社区 Issue #23](https://github.com/omdsh-dev/community/issues/23)、相关 discussion 或修改本文档的 PR | - -## 0. 一句话摘要 - -为 DSH 社区定义一套由社区治理、可被静态分析的插件互操作标准:插件通过 manifest 声明身份与能力需求,Host 通过机器可读描述、能力协商和统一生命周期决定是否以及如何激活插件。 - -它借鉴浏览器扩展“manifest + capability + 统一 API”的思路,也借鉴 Forge/Fabric 把扩展挂在稳定生命周期上的经验,但不会宣称已经具备浏览器级沙箱,也不会另造一套与现有 DSH/Cordis 平行的插件加载生态。 - -## 1. Draft 边界 - -这是一份社区讨论稿,不是 DeepSeek 或 DSH 官方标准,也不是当前可用的开发 API。 - -当前 DSH 插件继续通过已有 package manifest、Cordis service、slot 与 patch 组合。Fabric 的第一步是建立由 Host integration 装配、位于版本化 DSH Adapter 之上的互操作层,不要求上游立即修改,也不要求 Host 移除 legacy 或内置扩展。 - -本 RFC 使用“必须”“应该”“可以”表达提案强度,但在 RFC 被接受、schema 发布且一致性测试存在前,这些词不构成稳定兼容承诺。 - -本次修订中的文件命名、运行拓扑、组合和溯源边界,来自[社区 Issue #23](https://github.com/omdsh-dev/community/issues/23) 收集的反例。它们是这份 Draft 当前采用的设计,不代表社区讨论已经形成正式共识。 - -## 2. 背景 - -社区正在形成 GUI、Web UI、TUI、启动器、组合包和不同分发渠道。增长带来了几类共同问题: - -- **兼容信息缺失**:安装前无法可靠知道插件需要图形界面、会话读写、托盘或其他能力。 -- **实现耦合**:直接依赖 loader、内部函数或源码 patch 的扩展容易随上游变化失效。 -- **接口重复**:不同 Host 为同一需求提供不同路径,插件作者需要维护多套适配。 -- **组合冲突**:多个插件修改同一行为时,缺少声明、顺序和冲突规则。 -- **分发困难**:市场与启动器缺少可静态读取的兼容元数据,只能依赖人工组合和锁版本。 - -本提案把上游特有变化集中到版本化 DSH Adapter,产品策略与用户体验由 Host integration 负责。插件侧 contract 与治理不绑定某个 DSH 版本;上游变化时,Adapter 与 Host 负责适配、明确降级或拒载,不能伪装成仍支持原有语义。 - -这并不意味着标准可以完全不受上游影响。若上游不再暴露实现某项能力所需的观察点或操作点,对应 Host capability 就必须暂时下线。 - -## 3. 目标 - -1. **静态声明**:工具无需执行插件代码即可读取身份、版本、入口、能力需求与声明式贡献。 -2. **兼容协商**:required capability 缺失时明确拒载;optional capability 缺失时允许可预测降级。 -3. **统一 contract**:标准范围内的同一件事只有一个规范接口和一套行为语义。 -4. **适配现有生态**:Host 可以在 DSH/Cordis 等原生机制之上实现 adapter,不新增必须并行维护的加载体系。 -5. **可验证**:manifest、Host Descriptor、协商器和生命周期具备 schema、fixtures 与 headless 一致性测试。 -6. **降低用户摩擦**:市场和启动器能在安装前展示兼容、不兼容、待授权、已实测和未知。 - -## 4. 非目标 - -- 不要求 DSH 上游立即采纳本提案。 -- 不统一 GUI、Web UI 与 TUI 的内部渲染技术。 -- 不在本 RFC 中实现包管理器、市场后台、排行榜或账号系统。 -- 不把“静态声明通过”描述为代码安全审核。 -- 不承诺任意复杂 UI 一份代码无损运行在所有 Host。 -- 不在 v0.1 中定义可修改的全套 `before-*` 事件。 -- 不要求 Host 删除内置、legacy 或非标准插件路径;这些路径只是不参与 Fabric 一致性声明。 - -## 5. 信任与执行档位 - -Capability 需要区分四件事: - -1. **support**:Host 声明能够提供某项能力。 -2. **request**:插件在 manifest 中申请该能力。 -3. **grant**:用户或策略允许该插件使用它。 -4. **enforcement**:Host 通过隔离和受控边界真正阻止绕过。 - -v0.1 的参考实现可以采用 **trusted in-process** 档位:插件作为受信任代码运行,capability 用于兼容、授权和审计,不构成安全沙箱。Host 必须显著声明这个事实。 - -未来的 **isolated** 档位必须另行规定进程或 realm 隔离、模块白名单、受控 IPC、资源限制、文件与网络 scope、崩溃恢复和平台差异。没有这些证据的 Host 不得声称权限被强制执行。 - -## 6. 版本模型 - -以下版本不能混为一个字段: - -| 名称 | 含义 | -| --- | --- | -| `version` | 插件自己的 SemVer 版本。 | -| `manifestVersion` | manifest JSON 结构版本。 | -| `apiVersion` | 插件要求的社区 Host API 兼容范围。 | -| capability / event version | 某项能力或事件 payload 的 contract 版本;v0.1 可暂时跟随 API 版本。 | -| Host version | 某个 GUI、Web UI、TUI 或启动器自己的产品版本。 | -| SDK version | 类型与开发工具 package 的发布版本,不自动等于标准版本。 | - -标准的 breaking change 必须进入新的不兼容 API 范围。v0 阶段若采用 `0.x`,仍按“minor 可能 breaking”的实验规则明确标注,不能对外伪装成稳定 `1.x`。 - -### 6.1 术语 - -- **Host product**:承载插件的 GUI、Web UI、TUI 或启动器产品。 -- **Host-side runtime face**:Host product 中实际执行 v0.1 插件 entrypoint 的 Node.js 环境。 -- **Activation instance**:某个插件 entrypoint 的一次有界激活;生命周期与资源 ownership 都以它为 scope。 -- **Adapter**:把 Fabric capability 映射到具体 DSH/Cordis 版本的实现。 -- **Runtime**:插件代码实际执行的位置,以及对应的信任与资源边界。 -- **Presentation**:附着在某次交互上的用户界面能力,例如 GUI 窗口、浏览器、TUI 或 headless 调用方。 -- **Control**:选择插件、执行策略、路由调用并持有取消权的控制方。 -- **Transport**:在这些参与方之间传递 contract 消息的机制,例如进程内调用、IPC、WebSocket 或 SSH 转发。 - -v0.1 只规范 Host-side Node.js entrypoint 与 activation instance。浏览器 Client、原生界面或隔离 Worker 等 executable face,以及它们之间的通信协议,留给后续 RFC。TUI 在本文中是 Host product,不是 runtime face 名称。 - -### 6.2 Runtime 拓扑不是 Host 类型 - -Runtime、Presentation、Control 与 Transport 是四个独立维度,不能压缩为 `hostType: "gui"` 或 `isRemote: true` 之类的字段:同一次调用中,插件可能在远端 Node.js Runtime 执行,由服务端 session 控制,通过本地 GUI 呈现,并同时跨越 SSH 与 WebSocket transport。Transport 不能证明代码在哪里执行、当前有哪些界面,也不能证明谁有权批准操作。 - -Host Descriptor 可以声明它可能路由到哪些 presentation 类型,但这既不是授权,也不能证明某一次调用确实存在对应界面。未来的 presentation capability 必须是 **invocation-scoped**:Control plane 在每次调用时提供版本化、不可修改的 invocation snapshot,描述当前附着的界面与授权;插件不能把这次 offer 缓存成 activation-global 状态。[RFC 0002](0002-runtime-presentation-invocation-transport.zh.md)在不扩大 v0.1 的前提下提议身份、路由、认证、取消、重连、重放和失败边界。实验性 v0.1 不提供通用 presentation channel。 - -## 7. 核心模型 - -```text -Manifest(插件是谁、依赖什么、提供什么、贡献什么) - ↓ -Host Descriptor(Host 支持什么、以何种执行档位支持) - ↓ -Negotiation + Authorization(能否运行、用户是否授权) - ↓ -Lifecycle + Events(何时激活、能观察什么) - ↓ -Capability-scoped Host API(激活后如何调用) -``` - -### 7.1 Manifest - -v0.1 将 manifest 冻结为 package 根目录中的静态 JSON 文件 **`dsh-plugin.json`**,不支持运行 JavaScript 动态生成。使用独立名称是有意为之:[Agent Plugins Specification](https://agent-plugins.org/specification) Working Draft 已经把根目录 `plugin.json` 保留给它自己的 manifest contract。一个 package 可以用两个文件同时支持两套生态,但两者不能互相覆盖或隐式扩展。 - -Phase 0 schema 必须要求顶层 `$schema` canonical identifier。Host 根据它选择本地已支持、随 Host 提供的 schema,加载插件时不能从网络获取 schema 或其他校验策略。正式 schema 发布后,其 canonical identifier 不得被重新赋予其他内容。`$schema` 负责选择 manifest 解析与校验规则;如果最终结构继续保留 `manifestVersion`,它必须与 `$schema` 选中的 schema 版本一致,不能成为另一个协商轴。独立的 `apiVersion` 仍表示插件要求的 runtime Host API 范围。所以下面的占位值只用于讨论,不是已发布 identifier,也不是合法 fixture。 - -```json -{ - "$schema": "", - "manifestVersion": "0.1.0", - "id": "com.example.message-memory", - "name": "Message Memory", - "version": "1.2.0", - "apiVersion": ">=0.1.0 <0.2.0", - "entrypoints": { - "host": "dist/host.js" - }, - "capabilities": { - "required": { - "messages.observe": ">=0.1.0 <0.2.0", - "commands": ">=0.1.0 <0.2.0", - "storage.local": ">=0.1.0 <0.2.0" - }, - "optional": { - "ui.panel.basic": ">=0.1.0 <0.2.0" - } - }, - "subscriptions": [ - { "event": "messages.observe", "version": ">=0.1.0 <0.2.0" } - ], - "contributes": { - "commands": [ - { "id": "com.example.message-memory.show-last", "title": "Show Last Message" } - ] - } -} -``` - -正式 schema 还必须定义: - -- `id` 的语法、命名空间所有权和冲突处理; -- entrypoint 必须位于 package 根目录内,以及其模块格式和执行环境; -- Host / renderer / worker 等多个 entrypoint 是否允许及其通信边界; -- capability 版本范围和敏感 scope; -- 插件更新新增 capability 时的重新确认; -- `contributes` ID 的命名空间与冲突规则; -- manifest 与 npm package metadata 重复字段的权威来源。 - -Schema 与工具必须在语义上区分五类声明,即使最终 JSON 层级还要结合 fixtures 继续细化: - -| 声明 | 含义 | -| --- | --- | -| `requires` | 插件依赖的版本化 Host capability 或 service contract,包括 required 与 optional 依赖。 | -| `permissions` | 需要用户或策略授权的敏感 scope;Host 支持不等于已经授权。 | -| `provides` | 向其他插件或 Host 导出的版本化 service / Provider contract。 | -| `contributes` | 插件代码执行前即可发现的声明式产品元数据。 | -| `subscriptions` | eager activation 后控制事件投递的订阅,不是 activation trigger。 | - -这五个名称只为标准 roadmap 保留彼此独立的语义,并不表示每一类都能在 v0.1 执行。在 service-composition contract 与带版本 schema revision 被接受前,v0.1 schema 必须拒绝 `provides` 和 `requires.services`。Host 不能把不支持的字段静默保存后展示成“已经生效”。首版 schema 只接受已有具体 v0.1 contract 的 requirement、permission、subscription 与 contribution 形式。 - -它们不能仅因为都写在 manifest 中就被当成同一种兼容或安全对象。消费者依赖的是 `provides` contract ID 与版本,不能直接依赖被选中实现它的具体 package。v0.1 只保留这类声明的语义,不提供通用 plugin-provided service runtime;[RFC 0003](0003-service-providers-and-composition.zh.md)为后续 runtime 提议 cardinality、多实例、用户选择、替换和依赖环。 - -在讨论用示例中,`capabilities.required` / `optional` 是 v0.1 暂定的 Host capability requirement 写法,`subscriptions` 则单独申请事件投递。最终 schema 可以把前者放入 `requires`,但不能把两者与 permission、export 或 contribution 混在一起。 - -借鉴 VS Code 的 Contribution Point 模型,`contributes` 只描述 Host 在插件运行前即可发现的元数据,不等同于 capability、授权、运行时实现或 activation 触发器。插件激活后只能为 manifest 已声明的 ID 绑定 handler / Provider;“声明但未绑定”和“绑定但未声明”都应由开发工具和一致性测试报告。 - -标准不规定某一种 loader 或源码转换实现。Host 通过 manifest 找到 entrypoint,再用自己的原生机制按标准生命周期激活。Fabric-managed 插件必须走这条入口;Host 的其他扩展路径必须明确标为非标准。 - -符合标准的 Fabric entrypoint 运行时不依赖 DSH、Cordis、Desktop 或 Adapter package。Package inspection、依赖规则和 conformance fixtures 会阻止意外耦合;trusted in-process 模式仍不能把这条受支持边界变成恶意代码沙箱。 - -### 7.2 Host Descriptor - -每个兼容 Host 必须发布机器可读描述。以下同样是讨论草案: - -```json -{ - "descriptorVersion": "0.1.0", - "id": "org.example.dsh-webui", - "version": "1.4.0", - "apiVersions": ["0.1.0"], - "execution": { - "environment": "node", - "trustMode": "trusted-in-process" - }, - "capabilities": { - "messages.observe": "0.1.0", - "commands": "0.1.0", - "storage.local": "0.1.0" - }, - "platforms": ["darwin-arm64", "win32-x64", "linux-x64"] -} -``` - -兼容判断优先依据 API 与 capability,而不是 `gui>=2.0` 这样的模糊产品名称。必须限制具体 Host 时,应使用稳定、带组织命名空间的 Host ID。 - -Descriptor 只报告 Host 实际提供的 Runtime 与 trust mode,不能用 `hostType` 或 `isRemote` 代替 Runtime、Presentation、Control 或 Transport。静态声明可能存在的 presentation 类型,也不会因此成为 activation-wide capability。 - -市场展示至少区分: - -- **声明兼容**:静态协商通过; -- **等待授权**:Host 支持,但敏感能力未获用户授权; -- **已实测**:明确的 Host、系统、插件和测试套件组合通过; -- **不兼容**:required capability 或 API 范围无法满足; -- **未知**:信息不足。 - -声明兼容不等于实测,更不等于安全审核。 - -默认交互应展示但禁用不兼容插件,并列出缺少的 capability;直接隐藏会让跨设备或跨 profile 的插件看起来凭空消失。 - -### 7.3 Capability - -Capability 是带版本的 Host service contract。v0.1 候选命名空间包括: - -| 名称 | 目的 | v0.1 状态 | -| --- | --- | --- | -| `storage.local` | 插件私有、受 Host 管理的持久化。 | v0.1 协商 capability | -| `commands` | 为 manifest 中声明的命令绑定 handler。 | v0.1 协商 capability | -| `messages.observe` | 观察不可变的消息事件。 | v0.1 协商 capability | -| `sessions.read` | 读取经过版本化和裁剪的会话视图。 | 后续设计 | -| `ui.panel.basic` | 极小、版本化的声明式 UI 公共子集。 | 后续原型 | -| `sessions.actions`、`net.*`、`fs.*` | 修改会话、网络与文件能力。 | 暂缓 | - -每项 capability 都必须单独规定方法、输入输出 schema、错误、取消、生命周期、隐私、资源限制和测试。私有扩展使用组织命名空间,例如 `x-org.example.tui.keymap`,不能使用容易冲突的短名称。 - -标准必须发布版本化、机器可读的 Capability Registry,不能要求实现方从 RFC 正文中抓取名称。每条记录至少包含 canonical ID 与版本、状态、owning RFC、输入/输出/错误 schema identifier 及不可变 hash、敏感级别与授权类型、生命周期 scope,以及弃用或替代信息。Host Descriptor 只能声明自己实际实现的 registry 精确条目;私有能力继续使用显式命名空间,不能伪装成标准 capability。 - -每项 contribution / Provider contract 还必须明确 cardinality、selector、priority、merge / first-result / pipeline / user-choice、同优先级 tie-break、错误隔离、timeout、重复注册和热替换。加载顺序不能成为未写明的冲突解决规则。 - -“标准接口唯一”只约束 Fabric contract:标准插件不能为同一项标准能力发明旁路。它不声称能够阻止 trusted in-process 代码直接使用 Node.js API。 - -声明式 contribution 不会隐含运行时访问或授权。Manifest 中的命令元数据是权威来源;命令 contribution 还要申请 `commands`,插件代码只按 ID 绑定 handler。Required API 在协商后一定存在;optional API 必须先经过显式 capability 检查与类型收窄。 - -v0.1 的 `commands` contract 刻意只支持 **flat action leaf**:一个全局命名空间 command ID 对应一个已声明 action 与一个归 activation 所有的 handler。Host 可以把同一 action 放进 palette、菜单、按钮或 TUI,但不能改变其身份。嵌套 command tree、subcommand、CLI 风格 option parser、交互式 prompt、流式输出与后台 command session 都不属于 v0.1。 - -Device code、临时 URL、二维码、确认请求等短期交互不能被塞进持久 session message。[RFC 0002](0002-runtime-presentation-invocation-transport.zh.md)为后续协议提议带过期时间、敏感等级和投递确认的 presentation item。在此之前,v0.1 command 不能要求这类 channel。 - -### 7.4 Lifecycle 与事件 - -Host product 状态与插件 activation 是两套独立状态机。Host 通常经历: - -```text -starting → ready → stopping → stopped -``` - -Host ready 后,每个 activation instance 独立经历: - -```text -discover → validate → negotiate → authorize -→ activating → active → deactivating → disposed -``` - -实验性 v0.1 不采用按需激活。完成 discover、negotiate 与 authorize 后,Host 在组装一次 runtime generation 时激活所有已选中的插件。Contribution 负责描述可发现功能,subscription 只控制事件投递;执行 command、请求 Provider 或匹配 subscription 都不能激活 inactive 插件。未来 interceptor 仍需要独立授权、顺序和失败 contract。 - -Host 必须为正常 activation 保证顺序,并在正常关闭时 best-effort deactivate,但不能在进程崩溃、断电或强制终止时保证 `deactivate` 送达。Plugin 必须把清理设计为可重复,并假设下一次启动可能需要恢复残留状态。Host 保持 ready 时,同一插件也可能因 HMR 或 profile 重新组合而重复 activate/dispose。 - -`activate` / `deactivate` 是 Host 调用的 activation-instance hook,不是插件自行订阅的普通业务事件。v0.1 的同一个 Host-side entrypoint 可以被重复激活;正式 lifecycle contract 必须定义重复激活、HMR 与 provider 替换行为。Client 或 isolated Worker 等其他 face 的 scope 与跨 face 通信另写 RFC。 - -v0.1 首先规范生命周期和一个不可修改的 `messages.observe` 事件。它使用带版本的最小 envelope,至少包含: - -- `envelopeVersion`、`eventType` 与 `eventVersion`; -- 唯一 `eventId`、来源 `runtimeId` 与 `occurredAt` 时间; -- `scopeType`、`scopeId`,以及在该 scope 内单调递增的 `scopeSequence`; -- 同一操作链可选的 `correlationId` 与 `causationId`; -- `privacyClass` 与明确的 `redactions` 摘要; -- canonical `payloadSchema` identifier 与不可修改的 `payload`。 - -Payload contract 仍要冻结具体消息字段、敏感字段规则、并发、回压、错误隔离、取消信号和关闭行为。时间戳与投递顺序都不隐含跨 scope 的全局顺序。 - -标准还必须发布机器可读的 Event Registry。每条记录把 canonical event ID 与版本绑定到 envelope / payload schema identifier 及不可变 hash,并记录 scope 与顺序规则、隐私/裁剪级别、投递/回压 contract、错误策略、状态、owning RFC 和弃用信息。`subscriptions` 与 Host Descriptor 引用这些 registry 条目;实现方不能从正文中自行发明“等价”事件名。 - -可修改或取消的 `before-*` 事件暂不进入 v0.1。后续 RFC 必须先回答: - -- 多插件执行顺序与优先级; -- 多次修改的合并方式; -- cancel 后是否继续调用; -- timeout、异常、回滚和重入; -- 每 session 的顺序与跨 session 并发; -- 隐私与敏感数据裁剪。 - -### 7.5 Host API - -未来 SDK 可能提供类似下面的开发体验,但 package 名称和签名尚未冻结: - -```ts -export default definePlugin((ctx) => { - ctx.commands.handle('com.example.message-memory.show-last', async () => { - const lastMessageId = await ctx.storage.local.get('lastMessageId') - ctx.log.info('Last observed message', { lastMessageId }) - }) - - ctx.messages.onReceived(async (message) => { - await ctx.storage.local.set('lastMessageId', message.id) - }) - - return { - deactivate() { - // release resources owned by this activation - }, - } -}) -``` - -`ctx` 只暴露协商后获准的标准 capability。required 缺失时插件不会激活;optional 缺失时对应 API 不存在,插件必须走明确降级路径。 - -在 trusted in-process 档位中,这仍然只是受支持 contract facade,不是 JavaScript 安全边界。 - -### 7.6 Broker 归属与 effect ledger - -所有标准注册都必须经过 Host API Broker,由 Broker 把资源归属到当前 activation instance。从 v0.1 开始,Broker 必须维护机器可读的 effect ledger,让诊断与清理能够回答“哪个插件创建、替换或未能释放了这项资源”。最小记录包含: - -- `ledgerVersion`、`recordId`、单调递增的 `sequence` 与 `recordedAt`; -- owner `pluginId`、`pluginVersion` 或 `manifestDigest`、`activationId` 与 `runtimeId`; -- `effectId`、`effectKind`、canonical contract ID/version,以及存在时的 `resourceId`; -- `operation` 与结果 `state`,至少覆盖 `create`、`bind`、`replace`、`release` 和 `cleanup-failed`; -- 可选的 `correlationId`、用于替换的旧/新 owner 或关联 effect ID,以及不含敏感数据的 `outcome` 或 canonical `errorCode`; -- `sensitivityClass` 与实际采用的裁剪策略。 - -Command handler、subscription、Provider、UI contribution 以及未来其他归 activation 所有的注册,在对应 contract 存在时都使用同一套 ownership 规则。Broker 与 Host 原生 lifecycle 协作释放资源,并记录结果。Ledger 默认不能写入消息正文、secret、command argument 或任意插件 payload。它改善溯源与诊断,但 trusted in-process 代码仍可绕过 Broker,所以 ledger 不是沙箱强制执行的证明。 - -## 8. Host 的义务 - -兼容 Host 应当: - -1. 对 Fabric-managed 插件只读取静态 manifest,不执行动态 manifest 代码。 -2. 发布真实的 Host Descriptor,不声明无法保持语义的 capability。 -3. 在执行插件代码前完成 schema 校验、API 与 capability 协商和必要授权。 -4. 对 required 缺失给出用户能理解的拒载原因,对 optional 缺失提供确定的降级结果。 -5. 保证正常生命周期顺序,并捕获跨越标准 callback / Promise 边界的普通异常;trusted in-process 无法隔离 `process.exit`、native crash 或死循环。 -6. 公开执行档位与限制,不能把 trusted in-process 描述成沙箱。 -7. 通过已发布的机器可读 registry 解析标准 capability 与 event,不能使用产品本地别名替代。 -8. 把每项标准注册归属到具体插件与 activation,维护最小 effect ledger,并在 dispose 时尝试有界清理。 -9. 运行与版本绑定的一致性测试,并发布测试环境和结果。 - -## 9. 与现有 DSH/Cordis 的关系 - -Fabric 不能通过重新发明 loader 来解决 loader 割裂。参考 adapter 应把 Fabric contract 映射到现有 DSH/Cordis composition: - -- manifest 负责静态发现与协商; -- Host integration 通过版本化 DSH Adapter,把获准 capability 映射到已有 service、slot、route 或事件; -- 原生 Cordis lifecycle 继续拥有实际资源释放; -- 无法等价映射的能力必须报告不支持,不能偷偷使用内部接口近似; -- 现有插件可以通过迁移工具补 manifest,但不会因为 Fabric 出现而立即失效。 - -可移植 v0.1 contract 拒绝把修改上游源码、猴子补丁和私有函数 hook 当作插件 API。现有 `cordis.patch.yml` 是 DSH 官方的声明式 profile 组合层,不是源码 patch;Fabric Adapter 本身也可能通过标准 bundle patch 进入现有 composition。单独标记、固定版本的 Adapter 实验可以研究经过审查的私有兼容桥,但不能把 patch target 暴露给普通插件、宣传成可移植 capability,或凭此通过 portable conformance。 - -本仓库当前公开的 `desktopProfiles` 与 `desktopPnpm` 是 Desktop 特定 Host service,不会自动成为跨 Host 标准。若社区希望标准化其中某一用途,应另写 capability RFC,并由多个 Host 共同证明语义可移植。 - -## 10. 市场、组合包与兼容证据 - -市场可以索引 manifest 和 Host Descriptor,在安装前计算静态兼容性,但不能把目录收录描述为审核或安全认证。 - -组合包继续是一等公民:它可以锁定标准版本、Host 版本、插件版本、平台和测试结果。锁版本从“对抗不稳定”转化为可复现发行策略,但不能替代每个 contract 的 SemVer 与兼容窗口。 - -任何“已实测”记录都应绑定: - -- 标准与 schema 版本; -- Host ID、版本、平台与架构; -- 插件 ID 与版本; -- 一致性测试套件版本和 commit; -- 测试时间与结果。 - -## 11. 最小落地路径 - -实验性 v0.1 只有在 Phase 0–2 的最小 contract 都有规范和测试后才完成;阶段编号表示实现顺序,不是三个相互矛盾的版本范围。 - -它的精确 runtime 范围是:基础 `host.info`、`log` 和生命周期取消,再加需要协商的 `storage.local`、`commands` 与一个不可修改的 `messages.observe` 事件。本文中的其他名称都是后续候选。 - -### Phase 0:标准基础 - -- RFC 0000:治理、状态与变更流程; -- package 根目录 `dsh-plugin.json` Manifest Schema,并要求 canonical `$schema` identifier; -- Host Descriptor Schema; -- 带不可变 schema hash 的机器可读 Capability / Event Registry; -- 合法/非法 fixtures; -- 纯函数 capability 协商器; -- headless 一致性测试骨架。 - -### Phase 1:受信任参考 adapter - -- 只支持一个明确的 Node.js Host 执行环境; -- 实现 discover / validate / negotiate / activate / deactivate; -- 首批 capability 保持低风险且不修改业务状态;敏感只读数据仍需授权与裁剪; -- Broker 分配的插件/activation ownership 与最小 effect ledger; - -### Phase 2:事件与最小贡献点 - -- 一个带最小版本化 envelope 的不可修改 `messages.observe` 事件; -- `storage.local`; -- `commands` 只提供 flat action leaf 与同 ID runtime binding,不包含 command tree 或交互式 presentation; -- activation-scoped Disposable / AsyncDisposable、有界 drain 与重复 activation; -- 故障插件、重复 ID、未声明/未绑定 contribution、timeout、取消和关闭 fixtures。 -- 完整 v0.1 能力存在后,由至少两个不同 Host product 或 integration 提供互操作证据;它们可以共享同一个版本化 DSH Adapter。 - -### 后续独立 RFC - -- 可修改的 `before-*` 事件; -- [Runtime / Presentation / Control / Transport 身份、invocation routing、command tree 与 ephemeral presentation](0002-runtime-presentation-invocation-transport.zh.md); -- [Service Provider contract、`provides` 组合、cardinality、选择与依赖环](0003-service-providers-and-composition.zh.md); -- UI Contribution、Provider、Renderer、Rich View、条件系统与最小跨 Host UI IR; -- Project/Profile Trust 与 experimental capability 晋级流程; -- 多 scope storage 与 Secret capability; -- 文件、网络与会话写入权限; -- 隔离执行与受控 IPC; -- [安装影响预览,以及超出最小 effect ledger 的完整溯源、验证与诊断交换](0004-provenance-validation-and-diagnostics.zh.md); -- 市场兼容标签与一致性结果交换格式。 - -## 12. 治理要求 - -在本 RFC 进入 Accepted 前,应先通过 RFC 0000 明确: - -- Draft、Review、Accepted、Final、Deprecated、Superseded、Withdrawn、Rejected 等状态; -- 最短公开评审期、决策方式、异议与申诉; -- capability/event 命名登记; -- breaking change、弃用窗口与勘误; -- 安全问题的非公开披露渠道; -- 规范与参考实现许可证; -- “社区标准”与“官方标准”的表述边界。 - -参考实现不能反向决定规范。一个行为只有在规范文本、fixtures 和一致性测试中被定义,才属于标准 contract。 - -## 13. v0.1 验收与一致性证据 - -实验性 v0.1 把证据分成四类: - -1. **Schema validation**:公开 `dsh-plugin.json` / Host Descriptor Schema、required 且已识别的 `$schema`、完整 SemVer 规则、registry,以及合法/非法 fixtures。 -2. **Host conformance**:required / optional 协商、未知版本、授权拒绝、activation 顺序、best-effort 关闭、标准 callback 异常、真实 Runtime/trust 描述,以及插件/activation effect ownership。 -3. **Plugin validation**:manifest 与 entrypoint 一致、只使用已声明 capability、contribution 声明/绑定一致且 ID 无冲突、optional 降级路径、重复 activation 后同步/异步资源可释放、错误可理解。 -4. **Interop evidence**:两个独立 Host product 或 integration 与三个示例插件完成同一组场景,作为 v0.1 从 Draft 晋级的标准证据。两个 Host 可以共享 DSH Adapter,但 integration 与 descriptor 证据必须独立。 - -由于 Events 属于 RFC 标题与 v0.1 范围,至少一个不可变观察事件必须拥有最小版本化 envelope、payload schema、隐私裁剪、scope 内顺序、回压/timeout、异常处理、关闭语义和 headless contract tests。 - -Host 只能声称“该 Host 通过 v0.1 Host conformance suite”;插件只能声称“该插件通过 v0.1 plugin validation”。两者都不能称为“安全插件”或“官方认证”。 - -## 14. 开放问题 - -1. Canonical `$schema` identifier 与离线兼容映射由谁持有和发布? -2. 插件 ID 的发布者命名空间如何证明所有权并处理转移? -3. v0.1 应支持哪个 Node.js 版本、模块格式和 entrypoint 加载边界? -4. 已定义的 v0.1 `messages.observe` envelope 内应包含哪些消息正文字段与裁剪规则? -5. capability 版本是独立 SemVer,还是在 v0 阶段跟随 `apiVersion`? -6. 需要什么证据才能证明 flat `commands` action 在 GUI、Web UI 与 TUI Host 中语义一致? -7. Host 一致性结果由谁签发、保存和撤销? -8. RFC 评审期、merge 权限和争议解决如何由社区共同治理? - -## 15. 为什么现在做 - -生态已经出现多个 Host、插件作者和分发渠道。此时建立静态、可测试的互操作 contract,比等接口进一步碎片化后再统一成本更低。 - -真正要复用的不是某个 loader,而是长期稳定的声明、协商、生命周期与验证方法。我们希望 Fabric 成为社区共同维护的适配层和实验场,而不是另一家单方面宣布的平行插件系统。 - -下一步不是“一周后自动成为标准”,而是公开收集反例、先完成治理 RFC 与 schema fixtures,再用两个 Host 和真实插件验证最小 contract。 diff --git a/dsh-community-fabric/docs/rfcs/0002-runtime-presentation-invocation-transport.i18n.yaml b/dsh-community-fabric/docs/rfcs/0002-runtime-presentation-invocation-transport.i18n.yaml deleted file mode 100644 index 494cd8c60e..0000000000 --- a/dsh-community-fabric/docs/rfcs/0002-runtime-presentation-invocation-transport.i18n.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Bilingual-pair consistency record. Both languages carry equal authority. -# Update both files and record their normalized Git blob hashes after editing either side. -0002-runtime-presentation-invocation-transport.md: e1ca5364e84b9d28911f619280d2b0e0e7ddfdf2 -0002-runtime-presentation-invocation-transport.zh.md: 21db381adf22e8b8629a3f5f0eac5b3ba5013607 diff --git a/dsh-community-fabric/docs/rfcs/0002-runtime-presentation-invocation-transport.md b/dsh-community-fabric/docs/rfcs/0002-runtime-presentation-invocation-transport.md deleted file mode 100644 index e1ca5364e8..0000000000 --- a/dsh-community-fabric/docs/rfcs/0002-runtime-presentation-invocation-transport.md +++ /dev/null @@ -1,571 +0,0 @@ -# RFC 0002: Runtime, Presentation, Control, Transport, and Invocation - -English | [中文](0002-runtime-presentation-invocation-transport.zh.md) - -| Field | Value | -| --- | --- | -| Status | Draft / request for comments | -| Target | Post-v0.1 protocol exploration | -| Scope | Interaction between plugin Runtimes and user-facing Presentations | -| Depends on | [RFC 0001](0001-plugin-manifest-capabilities-events.md) | -| Reference implementation | DSH Community Fabric (not implemented) | -| Discussion | [Community Issue #23](https://github.com/omdsh-dev/community/issues/23) and pull requests editing this document | - -## 0. Summary - -Separate five concepts that a local-only design can easily conflate: - -- a **Runtime** executes plugins; -- a **Presentation** interacts with a user; -- **Control** authenticates attachments, applies policy, and coordinates invocations; -- a **Transport** carries protocol messages without changing plugin semantics; -- an **Invocation** is one bounded request from an authorized Presentation to a Runtime. - -Presentation capabilities are immutable input to each invocation. They are never activation-time globals. A plugin must not branch on `isRemote`, `hostType`, the transport name, or a remembered “current client.” The same Runtime may serve several Presentations concurrently, and one Presentation may attach to or switch between several Runtimes without changing the plugin contract. - -## 1. Status and relationship to RFC 0001 - -This document is a discussion draft prompted by the [Remote SSH counterexample](https://github.com/omdsh-dev/community/issues/23#issuecomment-5306386927) and the subsequent [Runtime / Presentation / Control decomposition](https://github.com/omdsh-dev/community/issues/23#issuecomment-5306670321). It is not an API developers can use today. - -[RFC 0001](0001-plugin-manifest-capabilities-events.md) deliberately limits experimental v0.1 to one Host-side Node.js runtime face. This RFC does not enlarge that v0.1 runtime surface. Descriptor schemas, protocol schemas, a reviewed Control implementation, transport adapters, and conformance fixtures must exist before any implementation can claim this RFC. - -The RFC 0001 activation decision also remains unchanged: experimental v0.1 does **not** use demand activation. A selected plugin activates while its Runtime generation is assembled. Attaching a Presentation, discovering a command, or invoking a command does not activate an inactive plugin. - -The words MUST, SHOULD, and MAY express the intended strength of the draft. They do not create a compatibility promise until this RFC is accepted and backed by schemas and tests. - -## 2. The Remote SSH counterexample - -The community report describes one remote profile that executes a plugin while a local TUI or Web UI presents its commands. Three failures expose the missing boundary: - -1. A root command reaches the remote command catalog, but subcommands held in a TUI-only service do not. Portable command syntax was incorrectly owned by one Presentation implementation. -2. A login handler chooses browser mode during plugin registration. On a remote Runtime, it may try to open a browser on the wrong machine. Another Presentation attached to the same Runtime may have entirely different capabilities. -3. A device authorization URI and user code are returned through a persistent command/session result even though they are short-lived and sensitive. - -Adding `isRemote: true` does not solve these failures. Locality is not a Presentation capability, and a Runtime can be remote to one client while serving many clients simultaneously. Adding `hostType: "tui"` also fails because execution and presentation are independent axes. - -The protocol therefore needs a portable command tree, per-invocation Presentation capabilities, and a non-persistent Presentation channel. - -## 3. Goals - -1. Give Runtime, Presentation, Control, Transport, and Invocation one precise meaning each. -2. Allow many Presentations to attach to one Runtime without shared “current client” state. -3. Allow one Presentation to attach to or switch between many Runtimes with separate identity and grants. -4. Make command discovery, typed invocation, cancellation, and transient user interaction portable across GUI, Web UI, TUI, CLI, and headless test clients. -5. Keep plugin behavior independent of SSH, WebSocket, local IPC, container exec, or any future transport. -6. Make authorization, sensitive-data handling, attachment lifetime, and failure behavior observable and testable without a graphical environment. - -## 4. Non-goals - -- This RFC does not enter the RFC 0001 experimental v0.1 runtime surface. -- It does not introduce demand activation or change generation-scoped eager activation. -- It is not a cluster scheduler, workflow engine, profile manager, deployment system, service discovery system, or fleet control plane. -- It does not standardize GUI, Web UI, TUI, or CLI rendering technology, layout, navigation, styling, or component libraries. -- It does not make arbitrary rich UI portable. Rich views and renderer extensions require separate capability RFCs. -- It does not require SSH or select a preferred transport. -- It does not make a trusted in-process plugin a security sandbox. -- It does not define a persistent session-history format or permit transient secrets to enter one. - -## 5. Terminology and invariants - -### 5.1 Runtime - -A **Runtime** is the execution location and trust/resource boundary in which one Runtime generation activates selected plugin entrypoints. It owns activation instances, Runtime capabilities, command handlers, storage bindings, and business-event subscriptions. - -A Runtime is not “the UI” and is not defined by whether another machine considers it local or remote. Its descriptor may expose relevant execution facts such as operating system, architecture, API versions, and trust mode. It must not expose a plugin-facing `isRemote` shortcut. - -### 5.2 Presentation - -A **Presentation** is the user-interaction surface available to an invocation. A Presentation endpoint may remain attached and can discover commands, collect input, render output, or offer transient interaction affordances, but its capabilities have plugin-facing meaning only through an invocation snapshot. A desktop window, browser client, TUI, CLI, and deterministic headless test client can each be a Presentation. - -A Presentation advertises versioned capabilities, not a branch-driving product label. Plugins choose only among negotiated capabilities. Product identity may be retained for diagnostics and conformance evidence, but `hostType`, `clientType`, or a product name is not a substitute for capability checks. - -### 5.3 Control - -**Control** is the policy and coordination plane between Presentation and Runtime. It selects the applicable plugin/runtime generation, authenticates peers, authorizes Runtime access, negotiates descriptors, creates and revokes attachments, binds Presentation claims to invocations, routes requests and cancellation, enforces quotas, and records provenance-safe diagnostics. - -Control may be embedded in a local product or split across processes. Its logical obligations do not imply a centralized internet service. - -### 5.4 Transport - -A **Transport** moves versioned protocol envelopes between Control endpoints. Examples include in-memory calls, local IPC, SSH channels, WebSocket, HTTP streams, container exec, or Kubernetes exec. - -Transport affects connectivity, latency, framing, and failure signals. It does not change command IDs, payload schemas, Presentation capability semantics, authorization rules, or plugin APIs. Transport details are unavailable to ordinary plugin code. - -### 5.5 Invocation - -An **Invocation** is one authorized, cancellable, bounded execution of a declared command or Provider operation against one Runtime generation. It carries an immutable snapshot of the Presentation capabilities available for that request. - -### 5.6 Normative invariants - -1. Plugin activation is scoped to a Runtime generation; Presentation attachment is not activation. -2. Presentation state is scoped to an attachment and snapshotted into every invocation. -3. No Runtime or plugin may store a global `currentPresentation`, `hostType`, or `isRemote` for later requests. -4. A capability advertised by one attachment must never leak into another attachment or invocation. -5. A Transport may forward a standard envelope but must not reinterpret its business meaning. -6. Control is authoritative for attachment identity, grants, deadlines, and revocation; an untrusted client claim alone is insufficient. -7. An invocation targets one immutable `runtimeId` plus `generationId`; reconnecting or switching cannot silently retarget it. -8. Attach, detach, command discovery, and invocation do not demand-activate plugins. - -## 6. Topology and ownership - -The relationship is many-to-many: - -```text -Presentation A ─┐ ┌─ Runtime 1 / generation X - ├─ Control plane ┤ -Presentation B ─┘ └─ Runtime 2 / generation Y -``` - -One remote Runtime may simultaneously serve a local TUI and a browser Presentation. Their capability snapshots and grants remain independent. One TUI may attach to Runtime 1, attach to Runtime 2, or switch its active view without transferring invocation IDs, grants, ephemeral messages, or cancellation handles between them. - -A product may implement more than one role. A desktop application can contain Runtime, Control, Presentation, and local IPC in one process tree. Conformance still tests the logical boundaries; process co-location is not permission to replace explicit context with globals. - -## 7. Descriptors and invocation context - -All examples in this section are discussion shapes, not published schemas. Final documents must reject unknown security-sensitive fields, define size limits, and include valid and invalid fixtures. - -### 7.1 RuntimeDescriptor - -Control obtains a `RuntimeDescriptor` for one live Runtime generation: - -```json -{ - "descriptorVersion": "0.1.0", - "runtimeId": "runtime:01K3EXAMPLE", - "generationId": "generation:01K3EXAMPLE", - "product": { - "id": "org.example.dsh-runtime", - "version": "2.0.0" - }, - "execution": { - "environment": "node", - "trustMode": "trusted-in-process" - }, - "platform": { - "os": "linux", - "arch": "x64" - }, - "apiVersions": ["0.1.0"], - "capabilities": { - "commands": "0.1.0", - "storage.local": "0.1.0" - } -} -``` - -`runtimeId` is unique in the relevant Control authority; it is not a hostname. `generationId` changes whenever profile composition or active plugin bindings are replaced. Both IDs are opaque and must not be parsed to infer topology or product behavior. A stale generation target is rejected rather than redirected. - -The RFC 0001 Host Descriptor and this Runtime Descriptor have different lifetimes. A Host Descriptor is pre-generation product/integration evidence used for installation and composition planning; a Runtime Descriptor describes one live, already assembled generation. The live descriptor must be derivable from the Host/Adapter implementation and actual generation, and it cannot advertise a capability that the applicable Host Descriptor and negotiated contracts do not support. - -The Runtime Descriptor says what the Runtime can execute. It says nothing about the current user's browser, clipboard, QR renderer, or prompt surface. - -### 7.2 PresentationDescriptor - -Control validates a `PresentationDescriptor` while attaching a Presentation: - -```json -{ - "descriptorVersion": "0.1.0", - "product": { - "id": "org.example.dsh-tui", - "version": "1.4.0" - }, - "locale": "en-US", - "capabilities": { - "presentation.text": "0.1.0", - "presentation.link.show": "0.1.0", - "presentation.clipboard.copy": "0.1.0" - } -} -``` - -The client-submitted descriptor has no trusted `presentationId`. After authentication, Control assigns one within the attachment authority and adds it only to the authenticated descriptor snapshot used by an invocation. It must not be supplied as or derived from a stable hardware fingerprint. The assigned ID is opaque and must not drive plugin behavior. Capability values are versioned contracts. Absence means unavailable. A product name, locale, screen size, or TUI/GUI label never implies a capability. - -Candidate capability contracts include: - -| Capability | Meaning | -| --- | --- | -| `presentation.text` | Render bounded plain text with defined control-character handling. | -| `presentation.prompt.select` | Collect one selection from a bounded, typed choice set. | -| `presentation.link.show` | Display an audited HTTPS URI without opening it. | -| `presentation.link.open` | Offer to open an audited URI after an explicit user gesture. | -| `presentation.clipboard.copy` | Offer an explicit user action that copies an identified field. | -| `presentation.qr.render` | Render a QR representation of an audited field. | - -Each contract still needs accessibility, timeout, size, scheme, gesture, error, and privacy rules. Claiming a capability does not bypass user consent or platform policy. - -### 7.3 InvocationContext - -Control creates a fresh context for every invocation: - -```json -{ - "protocolVersion": "0.1.0", - "invocationId": "invocation:01K3EXAMPLE", - "attachmentId": "attachment:01K3EXAMPLE", - "runtime": { - "runtimeId": "runtime:01K3EXAMPLE", - "generationId": "generation:01K3EXAMPLE" - }, - "presentation": { - "descriptorVersion": "0.1.0", - "presentationId": "presentation:01K3EXAMPLE", - "capabilities": { - "presentation.text": "0.1.0", - "presentation.link.show": "0.1.0" - } - }, - "authority": { - "subject": "subject:opaque-user-reference", - "grantId": "grant:01K3EXAMPLE" - }, - "deadline": "2026-08-17T12:00:00Z", - "trace": { - "correlationId": "correlation:01K3EXAMPLE" - }, - "command": { - "id": "com.example.codex.login.device", - "input": {} - } -} -``` - -The serialized context contains only the minimum data required for authorization, routing, capability negotiation, cancellation, and diagnostics. The SDK may expose a narrower typed facade plus an `AbortSignal` instead of the raw envelope. - -The Presentation capability map is copied into and authenticated with the invocation. It is not fetched from mutable plugin state. Even a Presentation with no interactive capability sends an explicit empty map. - -## 8. Attach and detach lifecycle - -Each attachment has an independent state machine: - -```text -connecting → authenticating → negotiating → attached - → draining → detached -``` - -An attachment proceeds as follows: - -1. Transport establishes a channel and reports its authenticated peer evidence to Control. -2. Control authenticates the principal and authorizes access to one specific Runtime generation. -3. Runtime and Presentation descriptors are validated and version-negotiated. -4. Control assigns a fresh `attachmentId`, binds descriptor evidence and grants to it, and exposes command discovery. -5. Every invocation revalidates that the attachment, generation, command, grant, deadline, and capability snapshot are still acceptable. -6. Detach prevents new invocations, applies the declared policy to in-flight work, revokes attachment-scoped handles, and disposes ephemeral messages. - -Attach does not activate plugins, create a global current Presentation, or transfer a grant from another Runtime. Reconnect creates a new attachment unless a future resume protocol proves continuity and replay safety. - -Runtime shutdown detaches all Presentations for that generation. Presentation shutdown detaches only its own attachments; a shared Runtime continues serving other authorized Presentations. - -## 9. Invocation protocol and cancellation - -An invocation moves through a single monotonic state machine: - -```text -accepted → running → succeeded - ├→ failed - └→ cancelling → cancelled -``` - -Only one terminal state is valid. Control and Runtime must deduplicate `invocationId`; a retry cannot execute a non-idempotent handler again unless an operation-specific contract explicitly permits it. - -Invocation input and output are schema-validated at the Control boundary and again at the Runtime boundary. Errors use stable machine codes plus safe user-facing messages; transport errors are not exposed as plugin business errors. - -Cancellation has these rules: - -- the SDK provides a per-invocation cancellation signal; -- a deadline causes the same cancellation path as an explicit authorized cancel request; -- cancellation is cooperative unless an isolated execution mode specifies stronger termination; -- Control waits for a terminal acknowledgement up to a bounded drain timeout; -- a Transport disconnect does not silently mean either “cancel” or “continue”; each operation class declares and tests its disconnect policy; -- late output after a terminal state is discarded and reported through diagnostics without being shown to another attachment. - -## 10. Portable command descriptor and command tree - -Portable command syntax belongs to the Runtime command catalog, not a TUI-only or GUI-only registry. One `CommandDescriptor` contains the complete tree needed for discovery and invocation: - -```json -{ - "descriptorVersion": "0.1.0", - "id": "com.example.codex", - "name": "codex", - "title": "Codex", - "description": "Manage Codex integration", - "children": [ - { - "id": "com.example.codex.login", - "name": "login", - "description": "Authenticate Codex", - "children": [ - { - "id": "com.example.codex.login.browser", - "name": "browser", - "description": "Use browser authentication", - "inputSchema": { - "type": "object", - "additionalProperties": false - } - }, - { - "id": "com.example.codex.login.device", - "name": "device", - "description": "Use device-code authentication", - "inputSchema": { - "type": "object", - "additionalProperties": false - } - } - ] - }, - { - "id": "com.example.codex.set", - "name": "set", - "description": "Change a Codex setting", - "children": [ - { - "id": "com.example.codex.set.native-compaction", - "name": "native-compaction", - "arguments": [ - { - "id": "value", - "position": 0, - "required": true, - "type": "string", - "enum": ["on", "off"] - } - ], - "options": [], - "inputSchema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { "enum": ["on", "off"] } - }, - "additionalProperties": false - } - } - ] - } - ] -} -``` - -`inputSchema` validates `command.input` in the InvocationContext. Argument and option declarations map Presentation syntax to stable input-property names; raw command tokens never cross the Runtime protocol boundary. - -The final contract must define: - -- globally namespaced command IDs and sibling-unique syntax tokens; -- ordered positional arguments, named options, subcommands, defaults, enums, validation, sensitive-input markings, and their mapping to stable input properties; -- a bounded JSON Schema vocabulary for terminal-node input and output; -- localization and aliases as display/input metadata that never replace stable IDs; -- duplicate-ID, duplicate-token, shadowing, depth, size, and cycle rejection; -- authorization, timeout, concurrency, disconnect, and cancellation policy per terminal operation; -- deterministic behavior when an intermediate tree node is invoked. - -A TUI may parse `/codex login device`; a Web UI may render nested controls; both produce the same typed invocation of `com.example.codex.login.device`. A raw shell command string is not the Runtime protocol payload. - -Explicit commands such as `login browser` and `login device` are preferred when they represent a user choice. A generic `login` command may negotiate a flow, but it may inspect only the current invocation's Presentation capabilities. - -## 11. Ephemeral Presentation channel - -Short-lived user interaction must not be smuggled into a persistent session result. During an authorized invocation, a plugin may request a bounded transient message through the invocation-scoped Presentation facade: - -```json -{ - "messageVersion": "0.1.0", - "messageId": "ephemeral:01K3EXAMPLE", - "invocationId": "invocation:01K3EXAMPLE", - "kind": "auth.device-code", - "sensitivity": "secret", - "noPersist": true, - "expiresAt": "2026-08-17T11:55:00Z", - "content": { - "verificationUri": "https://example.com/device", - "userCode": "ABCD-EFGH" - }, - "affordances": [ - { "type": "link.show", "field": "verificationUri" }, - { "type": "clipboard.copy", "field": "userCode" }, - { "type": "qr.render", "field": "verificationUri" } - ] -} -``` - -`noPersist` is always `true`; a false or missing value is rejected. `expiresAt` is required and bounded by the message-kind contract. Expiry, detach, cancellation, or invocation termination disposes the message and instructs every Presentation replica to clear it. - -Initial sensitivity levels are: - -| Level | Minimum handling | -| --- | --- | -| `public` | Transient and excluded from session history; bounded diagnostic metadata may be recorded. | -| `private` | Content redacted from logs, traces, analytics, crash reports, and notifications. | -| `secret` | Private handling plus no content caching, offline queues, previews, or unattended actions. | - -The name `public` does not waive `noPersist`. It only controls diagnostic redaction. - -Every registered message kind defines a minimum sensitivity. A plugin may request stricter handling but cannot downgrade that minimum; for example, `auth.device-code` is always `secret` regardless of the value supplied by plugin code. - -An affordance is a request, not an instruction to take an unattended action. `link.open` and `clipboard.copy` require the matching negotiated capability and an explicit user gesture. QR rendering encodes the already-audited field; the Runtime does not send arbitrary image or script payloads. The initial URI policy should allow HTTPS only, with other schemes requiring separate review. - -If the current invocation cannot present a required message safely, the handler returns a typed `presentation-unavailable` outcome or follows an explicitly defined fallback. It must not open a Runtime-side browser, write the secret to a session log, or borrow another attachment's Presentation. - -## 12. Authorization, privacy, and security - -Runtime capability, Presentation capability, permission, and trust evidence are distinct: - -- a Runtime capability says the Runtime implements an operation; -- a Presentation capability says the attached endpoint can offer an interaction under a defined contract; -- a grant says an authenticated subject may use an operation against a specific Runtime scope; -- enforcement says Control and the execution mode prevent unauthorized paths. - -Minimum requirements are: - -1. Control authenticates both ends and binds the Presentation descriptor, Runtime generation, principal, and grant to every invocation. -2. Runtime reauthorizes the command and scope before invoking plugin code. Client-provided capability JSON alone is not trusted. -3. Grants are Runtime-scoped and least-privilege. Switching Runtime requires an independent grant decision. -4. Revocation blocks new work immediately and cancels or drains existing work according to declared policy. -5. Invocation IDs, attachment IDs, generation IDs, deadlines, and replay protection are verified before dispatch. -6. Descriptors and contexts minimize stable device identifiers and personal data. Plugins normally receive a typed capability facade, not authentication credentials or raw transport metadata. -7. Cross-machine transports provide confidentiality, integrity, peer authentication, bounded frames, and resistance to replay and resource exhaustion. -8. Presentation surfaces attribute the requesting plugin and Runtime, sanitize text, validate URI schemes, and keep link opening and clipboard writes behind user gestures. -9. Ephemeral content is excluded from session history, logs, telemetry, analytics, crash reports, persistent queues, and reconnect replay. -10. Provenance records may contain IDs, timestamps, state transitions, sizes, and redacted error codes, but never transient secret content. - -These controls do not make trusted in-process plugins safe against direct Node.js access. The execution-mode limitation from RFC 0001 remains visible to the user and in conformance evidence. - -## 13. Failure, detach, and recovery semantics - -Failures are scoped so one attachment cannot corrupt another: - -- **Presentation failure:** detach its attachments, dispose its transient messages, and apply each invocation's disconnect policy. Other Presentations remain attached. -- **Transport interruption:** mark the attachment unavailable, stop new dispatch, and either drain or cancel in-flight work according to the declared policy. No implicit replay occurs. -- **Control restart:** reject stale attachment and invocation credentials unless a separately specified resume protocol restores them safely. -- **Runtime generation replacement:** reject invocations for the old `generationId`, dispose old handlers through normal plugin deactivation, and require descriptor renegotiation. -- **Plugin handler failure:** return a typed, redacted failure for that invocation and preserve the Runtime plus unrelated handlers where the execution mode permits. -- **Slow consumer:** apply bounded queues and backpressure; never convert a transient secret channel into a durable backlog. - -Cleanup is idempotent. Detach and cancellation may race, so the final protocol defines which component owns the terminal transition and how duplicate cleanup is acknowledged. - -## 14. Intended developer experience - -Plugin code binds handlers during normal generation activation, then receives per-invocation context: - -```ts -ctx.commands.handle('com.example.codex.login.browser', async (_input, invocation) => { - if (invocation.presentation.has('presentation.link.open')) { - const challenge = await createBrowserChallenge() - - await invocation.presentation.show({ - kind: 'auth.browser-link', - sensitivity: 'secret', - noPersist: true, - expiresAt: challenge.expiresAt, - content: { verificationUri: challenge.verificationUri }, - affordances: [{ type: 'link.open', field: 'verificationUri' }], - }) - - return await challenge.wait({ signal: invocation.signal }) - } - - return { code: 'presentation-unavailable' } -}) -``` - -Package names and signatures are illustrative. The important properties are: - -- command metadata and tree are declarative and available before invocation; -- the handler is already active and receives one typed context per call; -- capability checks are made against that call, not activation state; -- cancellation uses the invocation signal; -- transient interaction uses a constrained facade and never the session result; -- no ordinary plugin API reveals SSH, WebSocket, local IPC, or “remote.” - -Tooling should generate TypeScript types from command input/output schemas, provide a fake Presentation for unit tests, and fail tests that use undeclared commands, missing capability checks, persistent transient payloads, or attachment-global state. - -## 15. Headless conformance requirements - -The first reference harness must run without a graphical session. It uses fake Runtime, Control, Presentation, and Transport implementations plus a deterministic clock. - -Conformance includes at least: - -1. RuntimeDescriptor, PresentationDescriptor, CommandDescriptor, InvocationContext, result, cancellation, and ephemeral-message schema fixtures. -2. Two concurrent Presentations with disjoint capabilities invoking the same active handler without capability leakage. -3. One Presentation attached to two Runtimes with independent grants, generations, cancellation, and command catalogs. -4. Proof that attach, command discovery, and invocation never activate an inactive plugin. -5. Complete command-tree round trips, typed validation, duplicate and malformed tree rejection, and identical command IDs across renderers. -6. Cancellation before dispatch, during execution, after completion, at deadline, and during detach. -7. Transport loss, duplication, reordering within allowed framing, stale generation, replay, and bounded-backpressure fixtures. -8. Ephemeral expiry, redaction, no persistence, no reconnect replay, gesture gating, and capability-fallback fixtures. -9. Authorization denial before plugin code, cross-attachment isolation, grant revocation, and redacted provenance reports. -10. A zero-capability headless Presentation that receives typed unavailability instead of a crash or an attempt to launch a browser. - -Passing an in-memory test does not prove an SSH or WebSocket adapter. Each adapter publishes its own versioned transport evidence against the same semantic suite. - -## 16. Remote SSH conformance matrix - -Remote SSH is the first required end-to-end counterexample, not the privileged architecture. - -| Scenario | Presentation capability snapshot | Required assertion | -| --- | --- | --- | -| Remote Runtime + local TUI | text, link display, optional clipboard; no browser open | Device flow is rendered locally; no browser launch occurs on the Runtime. | -| Remote Runtime + local GUI | text, link display/open, optional QR | Opening is offered by the GUI after a user gesture; the Transport only forwards the standard message. | -| One Runtime + TUI and Web UI concurrently | Different capability maps and grants | Each invocation sees only its own immutable snapshot; messages and cancellation never cross attachments. | -| One TUI attached to Runtime A and Runtime B | Separate Runtime generations and grants | Switching views never retargets an invocation or reuses a grant, ephemeral handle, or cancellation token. | -| Full `/codex` command tree over SSH | Typed command catalog | Root, nested `login browser`, `login device`, options, and help metadata survive the round trip. | -| SSH disconnect during a command | Operation-specific disconnect policy | Control reaches one bounded terminal outcome; no implicit replay and no transient-secret backlog occurs. | -| Presentation reconnects with changed capabilities | New attachment and descriptor snapshot | Old invocations retain their old snapshot; new invocations use only renegotiated capabilities. | -| Unauthorized Presentation or stale generation | Any | Runtime rejects before plugin code and emits only redacted diagnostics. | -| Headless client | Empty capability map | Handler returns a typed unavailable/fallback result; no GUI assumption or crash occurs. | - -Equivalent semantic cases must later pass over local IPC and at least one non-SSH remote transport so the standard does not accidentally encode SSH behavior. - -## 17. Delivery and compatibility path - -This RFC proceeds separately from RFC 0001 v0.1: - -### Phase A: protocol documents - -- freeze terminology and ownership boundaries; -- publish versioned schemas and valid/invalid fixtures; -- define command-tree, invocation, cancellation, attachment, and ephemeral-message state machines; -- define authorization and privacy threat models. - -### Phase B: headless reference broker - -- implement deterministic in-memory Control and Transport; -- implement fake Runtime and Presentation adapters; -- publish typed SDK facades and provenance-safe diagnostics; -- pass multi-Presentation and multi-Runtime isolation tests. - -### Phase C: Remote SSH counterexample - -- implement one reviewed SSH transport adapter without changing plugin APIs; -- prove full command-tree transport, per-invocation capability snapshots, cancellation, detach, and transient device authorization; -- compare with a local IPC adapter using the same semantic fixtures. - -### Phase D: independent Presentation evidence - -- run the same suite with at least two independently implemented Presentation products; -- document capability gaps rather than emulating unsupported behavior; -- publish version-bound conformance evidence and known limitations. - -No phase creates a package entrypoint or compatibility claim merely by publishing this Draft. - -## 18. Open questions - -1. Which party signs or attests Runtime and Presentation descriptors, and how are keys rotated? -2. Which Presentation capabilities form the smallest portable initial registry? -3. Is `presentation.prompt.select` part of this protocol or a separate interactive-prompt RFC? -4. Which operation classes default to cancel or continue after detach, and may a user override that policy? -5. What deduplication and idempotency evidence is required before retrying an invocation? -6. What maximum lifetime, size, and allowed URI schemes apply to each ephemeral message kind? -7. Can a reconnect resume an attachment safely, or must v0 always create a new one? -8. What minimum provenance can be retained without creating a cross-device tracking identifier? -9. How are capability downgrades handled while an attachment is draining? -10. Which Control responsibilities must be centralized within one trust authority, and which can be federated? - -## 19. References and discussion inputs - -- [RFC 0001: Plugin Manifest, Capabilities, and Events](0001-plugin-manifest-capabilities-events.md) -- [Community Issue #23: unified plugin API, events, and SDK discussion](https://github.com/omdsh-dev/community/issues/23) -- [Remote SSH counterexample and command/presentation gaps](https://github.com/omdsh-dev/community/issues/23#issuecomment-5306386927) -- [Runtime, Presentation, Control, and transport decomposition](https://github.com/omdsh-dev/community/issues/23#issuecomment-5306670321) diff --git a/dsh-community-fabric/docs/rfcs/0002-runtime-presentation-invocation-transport.zh.md b/dsh-community-fabric/docs/rfcs/0002-runtime-presentation-invocation-transport.zh.md deleted file mode 100644 index 21db381adf..0000000000 --- a/dsh-community-fabric/docs/rfcs/0002-runtime-presentation-invocation-transport.zh.md +++ /dev/null @@ -1,571 +0,0 @@ -# RFC 0002:Runtime、Presentation、Control、Transport 与 Invocation - -[English](0002-runtime-presentation-invocation-transport.md) | 中文 - -| 字段 | 内容 | -| --- | --- | -| 状态 | Draft / 征求意见 | -| 目标 | v0.1 之后的协议探索 | -| 范围 | 插件 Runtime 与用户侧 Presentation 之间的交互 | -| 依赖 | [RFC 0001](0001-plugin-manifest-capabilities-events.zh.md) | -| 参考实现 | DSH Community Fabric(尚未实现) | -| 讨论方式 | [社区 Issue #23](https://github.com/omdsh-dev/community/issues/23) 或修改本文档的 PR | - -## 0. 一句话摘要 - -把本地单体设计中容易混在一起的五个概念拆开: - -- **Runtime** 执行插件; -- **Presentation** 与用户交互; -- **Control** 负责 attachment 认证、策略与 invocation 协调; -- **Transport** 只传递协议消息,不改变插件语义; -- **Invocation** 是一个获准 Presentation 对某个 Runtime 发起的一次有边界请求。 - -Presentation capability 是每次 invocation 的不可变输入,绝不能成为 activation 阶段的全局状态。插件不能依据 `isRemote`、`hostType`、Transport 名称或记住的“当前客户端”分支。同一个 Runtime 可以并发服务多个 Presentation;一个 Presentation 也可以 attach 或切换多个 Runtime,而不改变插件 contract。 - -## 1. 状态及与 RFC 0001 的关系 - -本文是由 [Remote SSH 反例](https://github.com/omdsh-dev/community/issues/23#issuecomment-5306386927)及后续 [Runtime / Presentation / Control 分层建议](https://github.com/omdsh-dev/community/issues/23#issuecomment-5306670321)推动的讨论草案,不是开发者今天就能使用的 API。 - -[RFC 0001](0001-plugin-manifest-capabilities-events.zh.md) 有意把实验性 v0.1 限定为单一 Host-side Node.js runtime face。本 RFC 不扩大 v0.1 runtime 范围。Descriptor schema、协议 schema、经过评审的 Control 实现、Transport adapter 与一致性 fixtures 全部存在前,任何实现都不能宣称支持本 RFC。 - -RFC 0001 的 activation 决策也保持不变:实验性 v0.1 **不采用按需激活**。Runtime generation 组装时激活已选插件;Presentation attach、发现 command 或执行 command 都不会激活 inactive plugin。 - -本文中的“必须”“应该”“可以”表示 Draft 的提案强度;RFC 被接受并拥有 schema 与测试前,不构成兼容承诺。 - -## 2. Remote SSH 反例 - -社区报告描述了这样一个场景:远端 profile 负责执行插件,本地 TUI 或 Web UI 展示其 command。三个失败揭示出缺失的边界: - -1. 根 command 能进入远端 command catalog,但保存在 TUI-only service 中的子命令无法到达远端。可移植命令语法被错误地交给了某一个 Presentation 实现。 -2. 登录 handler 在插件注册阶段选择 browser 模式;远端 Runtime 可能因此尝试在错误的机器上打开浏览器。同一 Runtime 上另一个 Presentation 的能力还可能完全不同。 -3. Device authorization URI 与 user code 被放入持久化 command/session 结果,尽管它们短期有效且敏感。 - -增加 `isRemote: true` 不能解决这些问题。本地或远端不是 Presentation capability;同一个 Runtime 可能对某个客户端是远端,同时并发服务许多客户端。增加 `hostType: "tui"` 也同样失败,因为执行与呈现是两个独立维度。 - -因此协议需要可移植 command tree、逐 invocation 的 Presentation capability,以及非持久化 Presentation channel。 - -## 3. 目标 - -1. 为 Runtime、Presentation、Control、Transport 与 Invocation 分别给出唯一、精确的含义。 -2. 允许多个 Presentation attach 同一个 Runtime,且不存在共享的“当前客户端”状态。 -3. 允许一个 Presentation attach 或切换多个 Runtime,身份与授权相互隔离。 -4. 让 command 发现、typed invocation、取消与短期用户交互在 GUI、Web UI、TUI、CLI 和 headless 测试客户端之间可移植。 -5. 让插件行为与 SSH、WebSocket、local IPC、container exec 或未来 Transport 无关。 -6. 让授权、敏感数据处理、attachment 生命周期与故障行为在无图形环境中同样可观察、可测试。 - -## 4. 非目标 - -- 本 RFC 不进入 RFC 0001 实验性 v0.1 runtime 范围。 -- 不引入按需激活,也不改变以 generation 为 scope 的 eager activation。 -- 不是集群调度器、workflow engine、profile manager、部署系统、service discovery 或 fleet control plane。 -- 不统一 GUI、Web UI、TUI 或 CLI 的渲染技术、布局、导航、样式或组件库。 -- 不让任意 rich UI 自动跨端可移植;Rich View 与 Renderer extension 需要独立 capability RFC。 -- 不要求使用 SSH,也不选择首选 Transport。 -- 不把 trusted in-process plugin 变成安全沙箱。 -- 不定义持久 session history 格式,也不允许短期 secret 进入其中。 - -## 5. 术语与不变量 - -### 5.1 Runtime - -**Runtime** 是插件实际执行的位置及其 trust/resource boundary;一次 Runtime generation 会在其中激活已选中的 plugin entrypoint。它拥有 activation instance、Runtime capability、command handler、storage binding 与业务事件 subscription。 - -Runtime 不是“UI”,也不由另一台机器认为它是本地还是远端来定义。其 descriptor 可以公开与执行有关的事实,例如操作系统、架构、API version 与 trust mode,但不能向插件公开 `isRemote` 这种捷径。 - -### 5.2 Presentation - -**Presentation** 是某次 invocation 可用的用户交互界面。Presentation endpoint 可以持续 attached,并负责发现 command、收集输入、呈现输出或提供短期交互 affordance,但其 capability 只有通过 invocation snapshot 才对插件有意义。桌面窗口、browser client、TUI、CLI 与确定性的 headless test client 都可以是 Presentation。 - -Presentation 声明带版本的 capability,而不是供插件分支的产品标签。插件只能在协商后的 capability 中选择。产品身份可以用于诊断和一致性证据,但 `hostType`、`clientType` 或产品名不能代替 capability 检查。 - -### 5.3 Control - -**Control** 是 Presentation 与 Runtime 之间的策略和协调平面。它负责选择适用的 plugin/runtime generation、对端认证、Runtime 访问授权、descriptor 协商、attachment 创建与撤销、把 Presentation 声明绑定到 invocation、路由请求与取消、限制配额,以及记录不泄露来源数据的诊断信息。 - -Control 可以嵌入本地产品,也可以跨进程拆分。它在逻辑上的职责不意味着必须存在一个中心化互联网服务。 - -### 5.4 Transport - -**Transport** 在 Control 端点之间传输带版本的协议 envelope,例如 in-memory call、local IPC、SSH channel、WebSocket、HTTP stream、container exec 或 Kubernetes exec。 - -Transport 会影响连接、延迟、framing 与故障信号,但不能改变 command ID、payload schema、Presentation capability 语义、授权规则或插件 API。普通插件代码看不到 Transport 细节。 - -### 5.5 Invocation - -**Invocation** 是针对某一 Runtime generation,对一个已声明 command 或 Provider operation 进行的一次获准、可取消且有边界的执行。它携带本次请求可用 Presentation capability 的不可变快照。 - -### 5.6 规范性不变量 - -1. Plugin activation 以 Runtime generation 为 scope;Presentation attachment 不等于 activation。 -2. Presentation 状态以 attachment 为 scope,并在每次 invocation 中形成快照。 -3. Runtime 与插件都不能保存全局 `currentPresentation`、`hostType` 或 `isRemote` 供后续请求使用。 -4. 一个 attachment 声明的 capability 绝不能泄漏到另一个 attachment 或 invocation。 -5. Transport 可以转发标准 envelope,但不能重新解释其业务语义。 -6. Control 对 attachment identity、grant、deadline 与 revoke 具有权威性;仅凭不受信客户端的声明不够。 -7. Invocation 指向不可变的 `runtimeId` 与 `generationId`;重连或切换不能静默改变目标。 -8. Attach、detach、command discovery 与 invocation 都不会按需激活插件。 - -## 6. 拓扑与 ownership - -它们之间是多对多关系: - -```text -Presentation A ─┐ ┌─ Runtime 1 / generation X - ├─ Control plane ┤ -Presentation B ─┘ └─ Runtime 2 / generation Y -``` - -一个远端 Runtime 可以同时服务本地 TUI 与 browser Presentation,其 capability snapshot 和 grant 彼此独立。一个 TUI 可以 attach Runtime 1、attach Runtime 2,或切换当前视图,但不能在 Runtime 之间转移 invocation ID、grant、ephemeral message 或 cancellation handle。 - -同一个产品可以实现多个角色。例如桌面应用可以在一个进程树内同时包含 Runtime、Control、Presentation 与 local IPC。一致性测试仍然检查这些逻辑边界;进程共置不等于允许用全局状态替代显式 context。 - -## 7. Descriptor 与 invocation context - -本节所有例子都只是讨论结构,并非已发布 schema。正式文档必须拒绝未知的安全敏感字段、规定大小上限,并提供合法与非法 fixtures。 - -### 7.1 RuntimeDescriptor - -Control 为一个存活的 Runtime generation 获取 `RuntimeDescriptor`: - -```json -{ - "descriptorVersion": "0.1.0", - "runtimeId": "runtime:01K3EXAMPLE", - "generationId": "generation:01K3EXAMPLE", - "product": { - "id": "org.example.dsh-runtime", - "version": "2.0.0" - }, - "execution": { - "environment": "node", - "trustMode": "trusted-in-process" - }, - "platform": { - "os": "linux", - "arch": "x64" - }, - "apiVersions": ["0.1.0"], - "capabilities": { - "commands": "0.1.0", - "storage.local": "0.1.0" - } -} -``` - -`runtimeId` 在对应 Control authority 中唯一,但不是 hostname。Profile composition 或 active plugin binding 被替换时,`generationId` 必须改变。两个 ID 都是不透明值,不能被解析以推断拓扑或产品行为。指向过期 generation 的请求应被拒绝,不能被重定向。 - -RFC 0001 的 Host Descriptor 与这里的 Runtime Descriptor 具有不同生命周期。Host Descriptor 是在 generation 组装前用于安装与 composition planning 的产品/integration 证据;Runtime Descriptor 描述一个已经组装并正在运行的 generation。Live descriptor 必须能从 Host/Adapter implementation 与实际 generation 推导,不能公布对应 Host Descriptor 和已协商 contract 不支持的 capability。 - -Runtime Descriptor 描述 Runtime 能执行什么,不描述当前用户是否有浏览器、clipboard、QR renderer 或 prompt surface。 - -### 7.2 PresentationDescriptor - -Control 在 attach Presentation 时校验 `PresentationDescriptor`: - -```json -{ - "descriptorVersion": "0.1.0", - "product": { - "id": "org.example.dsh-tui", - "version": "1.4.0" - }, - "locale": "zh-CN", - "capabilities": { - "presentation.text": "0.1.0", - "presentation.link.show": "0.1.0", - "presentation.clipboard.copy": "0.1.0" - } -} -``` - -Client 提交的 descriptor 不包含可信 `presentationId`。完成认证后,Control 在 attachment authority 内分配该 ID,并只把它加入 invocation 使用的已认证 descriptor snapshot。它不能由稳定硬件指纹提供或派生。这个 ID 是不透明值,不能驱动插件行为。Capability value 是带版本的 contract;字段缺失表示不可用。产品名、locale、screen size 或 TUI/GUI 标签都不会隐含某项 capability。 - -候选 capability contract 包括: - -| Capability | 含义 | -| --- | --- | -| `presentation.text` | 在明确处理控制字符的前提下呈现有界纯文本。 | -| `presentation.prompt.select` | 从有界 typed choice set 中收集一个选择。 | -| `presentation.link.show` | 显示经过审计的 HTTPS URI,但不自动打开。 | -| `presentation.link.open` | 在用户明确操作后,提供打开已审计 URI 的能力。 | -| `presentation.clipboard.copy` | 提供用户明确触发的操作,复制指定字段。 | -| `presentation.qr.render` | 把经过审计的字段渲染为 QR。 | - -每项 contract 仍需规定 accessibility、timeout、大小、scheme、gesture、错误与隐私规则。声明 capability 不会绕过用户确认或平台策略。 - -### 7.3 InvocationContext - -Control 为每次 invocation 新建 context: - -```json -{ - "protocolVersion": "0.1.0", - "invocationId": "invocation:01K3EXAMPLE", - "attachmentId": "attachment:01K3EXAMPLE", - "runtime": { - "runtimeId": "runtime:01K3EXAMPLE", - "generationId": "generation:01K3EXAMPLE" - }, - "presentation": { - "descriptorVersion": "0.1.0", - "presentationId": "presentation:01K3EXAMPLE", - "capabilities": { - "presentation.text": "0.1.0", - "presentation.link.show": "0.1.0" - } - }, - "authority": { - "subject": "subject:opaque-user-reference", - "grantId": "grant:01K3EXAMPLE" - }, - "deadline": "2026-08-17T12:00:00Z", - "trace": { - "correlationId": "correlation:01K3EXAMPLE" - }, - "command": { - "id": "com.example.codex.login.device", - "input": {} - } -} -``` - -序列化 context 只保留授权、路由、capability 协商、取消和诊断所需的最少数据。SDK 可以公开更窄的 typed facade,并以 `AbortSignal` 代替 raw envelope。 - -Presentation capability map 会被复制到 invocation 并与之一起认证,不能从插件的可变状态中读取。即使 Presentation 没有任何交互能力,也要显式发送空 map。 - -## 8. Attach 与 detach 生命周期 - -每个 attachment 都有独立状态机: - -```text -connecting → authenticating → negotiating → attached - → draining → detached -``` - -Attachment 流程如下: - -1. Transport 建立 channel,并把经过认证的 peer evidence 报告给 Control。 -2. Control 认证 principal,并授权其访问一个特定 Runtime generation。 -3. 校验 Runtime / Presentation descriptor 并进行版本协商。 -4. Control 分配新的 `attachmentId`,将 descriptor evidence 与 grant 绑定到它,并开放 command discovery。 -5. 每次 invocation 都重新确认 attachment、generation、command、grant、deadline 与 capability snapshot 仍然有效。 -6. Detach 阻止新 invocation,按声明的策略处理进行中工作,撤销 attachment-scoped handle,并清理 ephemeral message。 - -Attach 不会激活插件、创建全局 current Presentation,也不能转移另一个 Runtime 的 grant。除非未来的 resume protocol 能证明连续性与 replay safety,否则 reconnect 必须创建新 attachment。 - -Runtime shutdown 会 detach 该 generation 的所有 Presentation。Presentation shutdown 只 detach 自己的 attachment;共享 Runtime 继续服务其他已授权 Presentation。 - -## 9. Invocation 协议与取消 - -一次 invocation 只允许沿单调状态机前进: - -```text -accepted → running → succeeded - ├→ failed - └→ cancelling → cancelled -``` - -只能有一个 terminal state。Control 与 Runtime 必须按 `invocationId` 去重;除非某项 operation contract 明确允许,否则重试不能让 non-idempotent handler 再执行一次。 - -Invocation input / output 在 Control 边界做 schema 校验,到 Runtime 边界后再次校验。错误使用稳定的 machine code 与安全的用户提示;Transport error 不作为插件业务错误暴露。 - -取消规则如下: - -- SDK 为每次 invocation 提供 cancellation signal; -- deadline 与明确获准的 cancel request 进入同一条取消路径; -- 除非 isolated execution mode 定义了更强终止机制,否则取消是协作式的; -- Control 在有界 drain timeout 内等待 terminal acknowledgement; -- Transport 断开不能被静默解释为“取消”或“继续”;每类 operation 都要声明并测试 disconnect policy; -- terminal state 之后到达的延迟输出会被丢弃并进入诊断,不能展示给另一个 attachment。 - -## 10. 可移植 Command Descriptor 与 command tree - -可移植 command 语法属于 Runtime command catalog,不属于某个 TUI-only 或 GUI-only registry。一份 `CommandDescriptor` 包含 discovery 与 invocation 所需的完整树: - -```json -{ - "descriptorVersion": "0.1.0", - "id": "com.example.codex", - "name": "codex", - "title": "Codex", - "description": "Manage Codex integration", - "children": [ - { - "id": "com.example.codex.login", - "name": "login", - "description": "Authenticate Codex", - "children": [ - { - "id": "com.example.codex.login.browser", - "name": "browser", - "description": "Use browser authentication", - "inputSchema": { - "type": "object", - "additionalProperties": false - } - }, - { - "id": "com.example.codex.login.device", - "name": "device", - "description": "Use device-code authentication", - "inputSchema": { - "type": "object", - "additionalProperties": false - } - } - ] - }, - { - "id": "com.example.codex.set", - "name": "set", - "description": "Change a Codex setting", - "children": [ - { - "id": "com.example.codex.set.native-compaction", - "name": "native-compaction", - "arguments": [ - { - "id": "value", - "position": 0, - "required": true, - "type": "string", - "enum": ["on", "off"] - } - ], - "options": [], - "inputSchema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { "enum": ["on", "off"] } - }, - "additionalProperties": false - } - } - ] - } - ] -} -``` - -`inputSchema` 校验 InvocationContext 中的 `command.input`。Argument / option declaration 把 Presentation 语法映射为稳定 input property name;raw command token 永远不会跨过 Runtime 协议边界。 - -正式 contract 必须定义: - -- 全局命名空间 command ID 与 sibling-unique syntax token; -- ordered positional argument、named option、subcommand、default、enum、validation、敏感输入标记,以及它们到稳定 input property 的映射; -- terminal node input / output 使用的有界 JSON Schema vocabulary; -- localization 与 alias 只作为 display/input metadata,永远不替换稳定 ID; -- 重复 ID、重复 token、shadowing、深度、大小与 cycle 拒绝规则; -- 每个 terminal operation 的 authorization、timeout、concurrency、disconnect 与 cancellation policy; -- intermediate tree node 被执行时的确定行为。 - -TUI 可以解析 `/codex login device`,Web UI 可以呈现嵌套控件,但两者最终都生成对 `com.example.codex.login.device` 的同一种 typed invocation。Raw shell command string 不是 Runtime 协议 payload。 - -当 `login browser` 与 `login device` 代表用户选择时,应优先提供这些明确 command。通用 `login` command 可以协商 flow,但只能检查当前 invocation 的 Presentation capability。 - -## 11. Ephemeral Presentation channel - -短期用户交互不能被塞进持久 session result。在获准 invocation 执行期间,插件可以通过 invocation-scoped Presentation facade 请求一条有界 transient message: - -```json -{ - "messageVersion": "0.1.0", - "messageId": "ephemeral:01K3EXAMPLE", - "invocationId": "invocation:01K3EXAMPLE", - "kind": "auth.device-code", - "sensitivity": "secret", - "noPersist": true, - "expiresAt": "2026-08-17T11:55:00Z", - "content": { - "verificationUri": "https://example.com/device", - "userCode": "ABCD-EFGH" - }, - "affordances": [ - { "type": "link.show", "field": "verificationUri" }, - { "type": "clipboard.copy", "field": "userCode" }, - { "type": "qr.render", "field": "verificationUri" } - ] -} -``` - -`noPersist` 永远是 `true`;缺失或为 false 都应拒绝。`expiresAt` 必填,并受对应 message-kind contract 的上限约束。消息到期、detach、取消或 invocation 终止时,都要释放该消息,并要求所有 Presentation 副本清除内容。 - -初始 sensitivity level 如下: - -| 等级 | 最低处理要求 | -| --- | --- | -| `public` | 仍是 transient 且不得进入 session history;只可记录有界诊断 metadata。 | -| `private` | 日志、trace、analytics、crash report 与 notification 都必须裁剪 content。 | -| `secret` | 在 private 规则上,进一步禁止 content cache、offline queue、preview 与 unattended action。 | - -`public` 这个名称不会取消 `noPersist`,它只影响诊断裁剪规则。 - -每种已登记 message kind 都规定最低 sensitivity。Plugin 可以要求更严格处理,但不能降低这一下限;例如无论插件代码填写什么值,`auth.device-code` 永远属于 `secret`。 - -Affordance 是交互请求,不是无人值守操作指令。`link.open` 与 `clipboard.copy` 既要求匹配的已协商 capability,也要求用户明确操作。QR 只能编码已经审计的字段;Runtime 不发送任意图片或脚本 payload。第一版 URI policy 应只允许 HTTPS,其他 scheme 必须单独评审。 - -若当前 invocation 无法安全呈现必要消息,handler 应返回 typed `presentation-unavailable` outcome,或走明确规定的 fallback。它不能在 Runtime 侧打开浏览器、把 secret 写入 session log,或借用另一个 attachment 的 Presentation。 - -## 12. 授权、隐私与安全 - -Runtime capability、Presentation capability、permission 与 trust evidence 是四件不同的事: - -- Runtime capability 表示 Runtime 实现了某项操作; -- Presentation capability 表示已 attach 端点可以按明确 contract 提供某种交互; -- grant 表示已认证 subject 可以在指定 Runtime scope 使用某项操作; -- enforcement 表示 Control 与 execution mode 能阻止未获准路径。 - -最低要求如下: - -1. Control 认证两端,并把 Presentation descriptor、Runtime generation、principal 与 grant 绑定到每次 invocation。 -2. Runtime 在执行插件代码前再次授权 command 与 scope;只凭 client 提供的 capability JSON 不可信。 -3. Grant 以 Runtime 为 scope 且遵循最小权限;切换 Runtime 需要独立授权决定。 -4. Revoke 立即阻止新工作,并按声明策略取消或 drain 现有工作。 -5. Dispatch 前校验 invocation ID、attachment ID、generation ID、deadline 与 replay protection。 -6. Descriptor 与 context 尽量减少稳定设备标识和个人信息;插件通常只拿到 typed capability facade,看不到认证凭据或 raw Transport metadata。 -7. 跨机器 Transport 必须提供 confidentiality、integrity、peer authentication、有界 frame,并抵抗 replay 与资源耗尽。 -8. Presentation surface 应标注请求来源插件与 Runtime、清理文本、校验 URI scheme,并把打开链接和写 clipboard 放在用户操作之后。 -9. Ephemeral content 不进入 session history、log、telemetry、analytics、crash report、持久 queue 或 reconnect replay。 -10. Provenance record 可以包含 ID、时间、状态变化、大小与已裁剪 error code,但不能包含 transient secret content。 - -这些控制无法阻止 trusted in-process plugin 直接访问 Node.js。RFC 0001 的 execution-mode 限制仍须向用户展示,并写入一致性证据。 - -## 13. 故障、detach 与恢复语义 - -故障必须被限制在 scope 内,不能让一个 attachment 破坏另一个: - -- **Presentation failure**:detach 它自己的 attachment、清理 transient message,并按每次 invocation 的 disconnect policy 处理;其他 Presentation 保持连接。 -- **Transport interruption**:把 attachment 标记为 unavailable、停止新 dispatch,并按已声明策略 drain 或取消进行中工作;不做隐式 replay。 -- **Control restart**:拒绝过期 attachment / invocation credential,除非独立定义的 resume protocol 能安全恢复。 -- **Runtime generation replacement**:拒绝旧 `generationId` 的 invocation,通过正常 plugin deactivation 清理旧 handler,并重新协商 descriptor。 -- **Plugin handler failure**:为该 invocation 返回 typed、已裁剪 failure;在 execution mode 允许时保留 Runtime 与其他无关 handler。 -- **Slow consumer**:使用有界 queue 与 backpressure,不能把 transient secret channel 变成持久 backlog。 - -Cleanup 必须幂等。Detach 与 cancellation 可能产生竞态,因此正式协议必须定义哪个组件拥有 terminal transition,以及重复 cleanup 如何确认。 - -## 14. 预期开发体验 - -插件代码在正常 generation activation 期间绑定 handler,随后为每次调用接收 invocation context: - -```ts -ctx.commands.handle('com.example.codex.login.browser', async (_input, invocation) => { - if (invocation.presentation.has('presentation.link.open')) { - const challenge = await createBrowserChallenge() - - await invocation.presentation.show({ - kind: 'auth.browser-link', - sensitivity: 'secret', - noPersist: true, - expiresAt: challenge.expiresAt, - content: { verificationUri: challenge.verificationUri }, - affordances: [{ type: 'link.open', field: 'verificationUri' }], - }) - - return await challenge.wait({ signal: invocation.signal }) - } - - return { code: 'presentation-unavailable' } -}) -``` - -Package name 与签名只是示意,关键属性是: - -- command metadata 与 tree 是声明式的,invocation 前即可读取; -- handler 已处于 active 状态,每次调用单独收到 typed context; -- capability check 针对本次调用,而非 activation state; -- cancellation 使用 invocation signal; -- transient interaction 走受限 facade,绝不进入 session result; -- 普通 plugin API 不暴露 SSH、WebSocket、local IPC 或“remote”。 - -Tooling 应从 command input/output schema 生成 TypeScript type,提供 fake Presentation 用于 unit test,并对 undeclared command、缺少 capability check、持久化 transient payload 或 attachment-global state 报错。 - -## 15. Headless 一致性要求 - -首个参考 harness 必须在无图形 session 的环境中运行。它使用 fake Runtime、Control、Presentation、Transport 与确定性 clock。 - -一致性测试至少包括: - -1. RuntimeDescriptor、PresentationDescriptor、CommandDescriptor、InvocationContext、result、cancellation 与 ephemeral-message 的 schema fixtures。 -2. 两个 capability 不同的 Presentation 并发调用同一个 active handler,且 capability 不泄漏。 -3. 一个 Presentation attach 两个 Runtime,grant、generation、cancellation 与 command catalog 彼此独立。 -4. 证明 attach、command discovery 与 invocation 都不会激活 inactive plugin。 -5. 完整 command tree round trip、typed validation、重复/非法树拒绝,以及不同 renderer 使用相同 command ID。 -6. Dispatch 前、执行中、完成后、deadline 与 detach 期间的 cancellation。 -7. Transport 丢失、重复、允许 framing 内的乱序、过期 generation、replay 与有界 backpressure fixtures。 -8. Ephemeral expiry、裁剪、no persistence、no reconnect replay、gesture gate 与 capability fallback fixtures。 -9. 插件代码执行前的 authorization denial、跨 attachment 隔离、grant revoke 与已裁剪 provenance report。 -10. 零 capability 的 headless Presentation 得到 typed unavailable,而不是 crash 或尝试打开 browser。 - -通过 in-memory 测试不能证明 SSH 或 WebSocket adapter。每个 adapter 都要针对同一 semantic suite 发布自身带版本的 Transport 证据。 - -## 16. Remote SSH 一致性矩阵 - -Remote SSH 是第一个必须验证的端到端反例,但不是享有特权的架构。 - -| 场景 | Presentation capability snapshot | 必须成立的断言 | -| --- | --- | --- | -| Remote Runtime + local TUI | text、link display、可选 clipboard;无 browser open | Device flow 在本地呈现;Runtime 不打开 browser。 | -| Remote Runtime + local GUI | text、link display/open、可选 QR | GUI 在用户操作后提供打开动作;Transport 只转发标准消息。 | -| 一个 Runtime + TUI 与 Web UI 并发 | 不同 capability map 与 grant | 每次 invocation 只看到自己的不可变 snapshot;message 与 cancellation 不跨 attachment。 | -| 一个 TUI attach Runtime A 与 Runtime B | 不同 Runtime generation 与 grant | 切换视图不重定向 invocation,也不复用 grant、ephemeral handle 或 cancellation token。 | -| 完整 `/codex` command tree 通过 SSH | Typed command catalog | Root、嵌套 `login browser`、`login device`、option 与 help metadata 完整往返。 | -| Command 期间 SSH 断开 | Operation-specific disconnect policy | Control 在有界时间内进入唯一 terminal outcome;无隐式 replay 与 transient-secret backlog。 | -| Presentation 以不同 capability 重连 | 新 attachment 与 descriptor snapshot | 旧 invocation 保留旧 snapshot;新 invocation 只使用重新协商后的 capability。 | -| 未授权 Presentation 或过期 generation | 任意 | 插件代码执行前拒绝,只产生已裁剪诊断。 | -| Headless client | 空 capability map | Handler 返回 typed unavailable/fallback;不假设 GUI,也不 crash。 | - -之后还必须通过 local IPC 与至少一种非 SSH remote Transport 的等价 semantic case,避免标准意外编码 SSH 行为。 - -## 17. 落地与兼容路径 - -本 RFC 与 RFC 0001 v0.1 分开推进: - -### Phase A:协议文档 - -- 冻结术语与 ownership 边界; -- 发布带版本的 schema 与合法/非法 fixtures; -- 定义 command tree、invocation、cancellation、attachment 与 ephemeral-message 状态机; -- 定义授权与隐私 threat model。 - -### Phase B:Headless reference broker - -- 实现确定性的 in-memory Control 与 Transport; -- 实现 fake Runtime / Presentation adapter; -- 发布 typed SDK facade 与不泄露来源数据的诊断; -- 通过 multi-Presentation 与 multi-Runtime 隔离测试。 - -### Phase C:Remote SSH 反例 - -- 实现一套经过评审、且不改变 plugin API 的 SSH Transport adapter; -- 证明完整 command tree 传输、逐 invocation capability snapshot、cancellation、detach 与 transient device authorization; -- 使用同一 semantic fixtures 对比 local IPC adapter。 - -### Phase D:独立 Presentation 证据 - -- 至少两个独立实现的 Presentation product 运行相同 suite; -- 对 capability gap 做明确记录,不伪装实现不支持的行为; -- 发布与版本绑定的一致性证据和已知限制。 - -任何阶段都不能只凭发布这份 Draft 就创建 package entrypoint 或兼容声明。 - -## 18. 开放问题 - -1. Runtime / Presentation descriptor 由哪一方签名或 attestation?如何轮换 key? -2. 哪些 Presentation capability 构成最小可移植初始 registry? -3. `presentation.prompt.select` 应属于本协议,还是单独的 interactive-prompt RFC? -4. 哪类 operation 在 detach 后默认 cancel 或 continue?用户能否覆盖该策略? -5. Retry invocation 前需要什么 deduplication 与 idempotency 证据? -6. 每类 ephemeral message 的最大生命周期、大小与允许 URI scheme 是什么? -7. Reconnect 能否安全恢复 attachment,还是 v0 必须始终新建? -8. 在不形成跨设备跟踪 ID 的前提下,最少可以保留哪些 provenance? -9. Attachment 处于 draining 时如何处理 capability downgrade? -10. 哪些 Control 职责必须集中在一个 trust authority,哪些可以 federation? - -## 19. 参考资料与讨论输入 - -- [RFC 0001:Plugin Manifest、Capability 与事件模型](0001-plugin-manifest-capabilities-events.zh.md) -- [社区 Issue #23:统一插件 API、事件与 SDK 讨论](https://github.com/omdsh-dev/community/issues/23) -- [Remote SSH 反例与 command / presentation 缺口](https://github.com/omdsh-dev/community/issues/23#issuecomment-5306386927) -- [Runtime、Presentation、Control 与 Transport 分层建议](https://github.com/omdsh-dev/community/issues/23#issuecomment-5306670321) diff --git a/dsh-community-fabric/docs/rfcs/0003-service-providers-and-composition.i18n.yaml b/dsh-community-fabric/docs/rfcs/0003-service-providers-and-composition.i18n.yaml deleted file mode 100644 index 905e7094b7..0000000000 --- a/dsh-community-fabric/docs/rfcs/0003-service-providers-and-composition.i18n.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Bilingual-pair consistency record. Both languages carry equal authority. -# Update both files and record their normalized Git blob hashes after editing either side. -0003-service-providers-and-composition.md: 9684a53bc21171249ca20c77da7ab5f694caadb6 -0003-service-providers-and-composition.zh.md: 49032ecdaee38211f6bde8b6f40aba54cb67729b diff --git a/dsh-community-fabric/docs/rfcs/0003-service-providers-and-composition.md b/dsh-community-fabric/docs/rfcs/0003-service-providers-and-composition.md deleted file mode 100644 index 9684a53bc2..0000000000 --- a/dsh-community-fabric/docs/rfcs/0003-service-providers-and-composition.md +++ /dev/null @@ -1,421 +0,0 @@ -# RFC 0003: Service Providers and Deterministic Composition - -English | [中文](0003-service-providers-and-composition.zh.md) - -| Field | Value | -| --- | --- | -| Status | Draft / request for comments | -| Target | Post-v0.1 service runtime; v0.1 reserves semantics only | -| Scope | Service dependencies, Providers, and deterministic plugin-set composition | -| Depends on | [RFC 0001](0001-plugin-manifest-capabilities-events.md) | -| Community input | [Issue #23 composition comment](https://github.com/omdsh-dev/community/issues/23#issuecomment-5307228009) | -| Reference implementation | DSH Community Fabric (not implemented) | - -## 0. Summary - -Fabric needs to compose a set of plugins before it executes any of them. A plugin may require Host capabilities or services, provide a service implementation, contribute discoverable product metadata, request permissions, and subscribe to events. Those are five different declarations and must never be collapsed into one generic `capabilities` bag. - -This RFC proposes a statically computed **Composition Plan**. Given manifests, a Host Descriptor, contract registries, grants, policy, and saved user selections, the planner classifies the plugin set as `merge`, `soft-conflict`, `selection-needed`, or `hard-conflict`. Provider cardinality and arbitration belong to each versioned service or contribution contract. Plugin discovery order, package-manager order, and activation timing are never arbitration inputs. - -The plan is then the only authority from which a future Broker may construct activation instances, bind consumers to Providers, replace Providers, and dispose dependent resources. This makes plugin composition explainable before launch and recoverable during HMR, profile recomposition, health changes, or shutdown. - -## 1. Draft boundary - -This is a design draft, not an API that plugin authors can use today. It extends the model in [RFC 0001](0001-plugin-manifest-capabilities-events.md), whose experimental v0.1 runtime remains limited to Host-provided capabilities, lifecycle, `storage.local`, `commands`, and one immutable observation event. - -For v0.1, `requires`, `provides`, `contributes`, `permissions`, and `subscriptions` reserve distinct meanings in the standard design, not five executable fields. The RFC 0001 v0.1 schema rejects `provides` and `requires.services` until this contract and a versioned schema revision are accepted. v0.1 does **not** ship a general plugin-provided service registry, dynamic dependency graph, Provider health protocol, or hot-replacement runtime. A Host must not claim that those features are part of Fabric v0.1 conformance. - -This RFC also does not: - -- expose a generic service locator, raw Cordis/Koishi Context, DSH object, or dependency-injection container to plugins; -- make capability declarations a security sandbox in trusted in-process execution; -- introduce demand activation—selected and authorized v0.1 plugins still activate while a runtime generation is assembled; -- standardize every model, search, Git, UI, tool, or storage service in one generic protocol; -- allow runtime registration to repair a statically invalid plugin set; -- require zero-downtime Provider replacement or automatic fallback. - -The words MUST, SHOULD, and MAY have the draft strength defined by RFC 0001. They become compatibility commitments only after schemas, a reference Broker, and conformance fixtures are accepted. - -## 2. Terms and two kinds of dependency - -### 2.1 Host capability - -A **Host capability** is implemented and owned by the Host integration or its versioned DSH Adapter. The Host advertises it in the Host Descriptor. Plugins may require and, where applicable, request permission to use it, but they cannot replace or shadow it through `provides`. - -Examples include activation-scoped logging, `storage.local`, mediated network access, or the registration API for a domain Provider. A capability may expose a Provider SPI, but the capability itself remains Host-owned. - -### 2.2 Plugin-provided service - -A **plugin-provided service** is a versioned domain contract implemented by one plugin activation and consumed by another through Broker-generated typed proxies. Examples might later include `git.client`, `models.provider`, or `search.provider`. - -The global service contract defines request and result schemas, cardinality, allowed scopes, version and feature negotiation, selection, health, cancellation, timeout, errors, teardown, and conformance tests. A package name or plugin ID is not a service contract. - -The requirement namespace must identify which plane is being resolved. `requires.capabilities.storage.local` asks the Host; `requires.services.git.client` asks the Composition Planner for a compatible Provider. Reusing one unqualified string in both planes is invalid. - -### 2.3 Provider and Provider instance - -A **Provider declaration** states that a plugin can implement one service contract. A **Provider instance** is the runtime binding owned by one activation instance in one allowed scope. The Provider's package version, implementation version, and service-contract version are separate values. - -A Host may implement a domain service only when that service contract explicitly allows a Host Provider. Contracts whose provider eligibility is `host-only` do not accept plugin Providers at all. - -### 2.4 Runtime generation and Composition Plan - -A **runtime generation** is one selected plugin set plus its Host Descriptor, grants, policy, selections, and resolved versions. A **Composition Plan** is the normalized, machine-readable result computed for that generation before plugin code executes. - -The plan contains dependency edges, candidates, selected Providers, negotiated versions/features, contribution ownership, suppressed alternatives, pending grants, diagnostics, and provenance for every decision. The same normalized inputs must produce the same plan on every conforming Host. - -## 3. Five declaration classes - -The manifest keeps these declarations structurally and semantically separate: - -| Declaration | Question answered | Authority and effect | -| --- | --- | --- | -| `requires` | What must or may already exist? | Compatibility dependency on a named Host capability or service contract. Required absence blocks the dependent activation; optional absence activates a declared degradation path. | -| `provides` | Which service contract can this plugin implement? | Provider candidacy only. It does not grant permissions, select the Provider, or activate code. | -| `contributes` | What discoverable product object does this plugin offer? | Static metadata such as a command, panel request, renderer ID, or setting. The Host owns validation, placement, composition, and presentation. | -| `permissions` | Which sensitive operation or data scope does this plugin ask the user or policy to grant? | Authorization request only. It neither proves Host support nor satisfies a dependency. | -| `subscriptions` | Which versioned event streams should be delivered after activation? | Delivery interest only. It is not a capability, permission, dependency, contribution, or activation trigger. | - -An item may need more than one declaration. A search implementation may `provide` a search service, `require` the Host's mediated network capability, request a network-origin `permission`, `contribute` settings metadata, and `subscribe` to credential-revocation events. None of those declarations implies another. - -An illustrative future shape follows. It is not the frozen manifest schema: - -```json -{ - "requires": { - "capabilities": { - "storage.local": { "version": ">=0.1.0 <0.2.0", "optional": false } - }, - "services": { - "git.client": { - "version": ">=1.2.0 <2.0.0", - "requiredFeatures": ["status"], - "optionalFeatures": ["worktrees"], - "optional": false, - "as": "gitClient" - } - } - }, - "provides": [ - { - "service": "git.client", - "providerId": "com.example.git-native/client", - "contractVersion": "1.4.0", - "features": ["status", "worktrees"], - "scope": "profile" - } - ], - "contributes": { - "commands": [ - { "id": "com.example.git-native.refresh", "title": "Refresh Git Status" } - ] - }, - "permissions": { - "required": [ - { "name": "fs.read", "scope": "workspace" } - ] - }, - "subscriptions": [ - { "event": "workspace.changed", "version": ">=1.0.0 <2.0.0" } - ] -} -``` - -Tooling must reject a runtime binding that was not declared, report a declaration that never binds, and keep permission/grant status out of compatibility claims. - -## 4. Contract identity, versions, and features - -### 4.1 Globally governed contract IDs - -Portable Host capabilities, service contracts, event types, and standard contribution kinds use globally governed IDs from Fabric registries, for example `storage.local` or `git.client`. Their meanings cannot be privately redefined. Organization-specific contracts use a proven namespace such as `x-org.example.git.client`. - -### 4.2 Publisher-namespaced resource IDs - -Provider IDs, contribution IDs, commands, renderers, and other plugin-owned resources use publisher-controlled, globally unique namespaces, normally a reverse-domain prefix. IDs are stable identities, not display labels. Updating a package must not silently change the owner of an existing ID. - -Two installed declarations claiming the same global resource ID are a hard conflict unless the resource contract explicitly defines an update/replacement identity and the package transaction proves continuity. The later-loaded declaration never overwrites the earlier one. - -### 4.3 Version negotiation - -The planner keeps these values separate: - -| Value | Meaning | -| --- | --- | -| Plugin version | Release version of the package containing the Provider or consumer. | -| Service-contract version | SemVer version of the request/result and lifecycle contract. | -| Required range | Contract versions the consumer can use. | -| Provider-supported range/version | Contract versions the implementation has passed against. | -| Feature set | Named optional or required additions inside a compatible contract line. | - -A candidate is compatible only when consumer, Provider, Broker, and Host/Adapter support have a non-empty contract-version intersection. The registry defines the deterministic selection rule; by default it chooses the highest mutually supported stable version. Prereleases participate only when every relevant range explicitly permits them. - -Feature negotiation occurs only after a contract version is chosen. Missing or unknown required features make that candidate incompatible. Missing or unknown optional features are recorded as unavailable and the generated API omits them. Features cannot be used to smuggle a breaking contract change into an unchanged version. - -The plan records the selected version, supported features, excluded candidates, and human-readable reasons. No consumer may inspect a Provider package and guess support from its package version. - -## 5. Cardinality and provider eligibility - -Every service and contribution contract declares cardinality independently from who is eligible to provide it. Combining them into one mode would make it impossible to express, for example, a Host-only service with many scoped instances or a selected service whose candidates may come from either the Host or plugins. - -### 5.1 Cardinality - -| Cardinality | Meaning | Static result when multiple declarations exist | -| --- | --- | --- | -| `many` | Zero or more Providers/resources may coexist. The contract defines enumeration, merge, selector, pipeline, or per-invocation routing semantics. | `merge` when IDs and contract rules are compatible; otherwise the relevant conflict class. | -| `single` | At most one Provider/resource may exist in a scope. This does not imply that one is required. | More than one compatible claim is a `hard-conflict` unless the contract defines an explicit replacement transaction. | -| `selected-one` | Multiple candidates may be installed, but exactly one is selected for an affected scope when the service is required. | A valid saved/policy selection resolves the set; otherwise `selection-needed`. | - -Cardinality is scoped. A contract must say whether its instance space is runtime, profile, workspace, session, invocation, or another registered scope. A Provider cannot broaden its declared scope, and a Host cannot silently treat a global selection as a workspace selection. - -For `many`, the service contract must still define what “many” means. Returning all Providers, merging values, selecting by an explicit selector, calling a pipeline, and asking the user are different protocols. A generic “highest priority wins” rule is not sufficient. - -### 5.2 Provider eligibility - -| Eligibility | Meaning | Invalid claim | -| --- | --- | --- | -| `plugin-only` | Only declared plugin Providers may satisfy the service. | A Host implementation cannot silently shadow or satisfy it. | -| `host-only` | Only the Host/Adapter implementation advertised in the Host Descriptor may satisfy it. | Any plugin `provides` claim is a `hard-conflict`. | -| `host-or-plugin` | Host and plugin implementations are candidates under the same version, feature, scope, cardinality, selection, and provenance rules. | A Host candidate cannot receive implicit priority merely because it is built in. | - -Provider eligibility does not select an implementation and does not grant permissions. For `host-or-plugin`, the contract still needs an explicit selection or merge rule; Host identity is provenance, not arbitration priority. Contribution contracts that do not represent a callable Provider instead declare an equivalent ownership policy for their resource kind. - -## 6. Static composition outcomes - -The planner evaluates the entire selected plugin set before activation and assigns a disposition to every service, contribution, and dependent activation: - -| Outcome | Meaning | Required Host behavior | -| --- | --- | --- | -| `merge` | All declarations compose under the contract's deterministic many/merge rules. | Include every accepted owner and record normalized ordering or routing. | -| `soft-conflict` | Declarations overlap, but a published deterministic policy can suppress, reposition, route, or otherwise resolve them without inventing semantics. | Apply the named policy, preserve all provenance, and show suppressed/adjusted results in diagnostics. | -| `selection-needed` | Multiple valid candidates exist for a user- or policy-selected contract and no valid selection is available. | Do not guess. Block only the affected required dependents and request an explicit selection through UI or headless configuration. | -| `hard-conflict` | The set violates identity, ownership, version, feature, scope, cycle, or cardinality rules and no published resolution exists. | Reject the affected generation or plugin subset according to Host policy; never activate it partially and silently. | - -Missing required dependencies and denied required grants are also blocking diagnostics, but remain distinct from a collision: `dependency-unsatisfied` and `authorization-required` should not be mislabeled as conflicts. - -The Composition Plan must be serializable and explain at least: - -- which manifest and Host Descriptor supplied each claim; -- which contract and registry version supplied each rule; -- which user selection or administrative policy was applied; -- why each candidate was accepted, suppressed, awaiting selection, or rejected; -- which activations become blocked or degraded as a result; -- which normalized order is used where a contract genuinely defines ordered merging. - -The service dependency graph must be acyclic. Late-bound cycles require a separate contract and are outside this RFC; without one, a cycle is a hard conflict with the complete cycle path in diagnostics. - -### 6.1 Load order is not policy - -Filesystem enumeration, npm dependency order, manifest discovery order, object insertion order, network arrival, and activation completion order must not select a winner or change a plan. - -A contract may define explicit priority, selectors, or a stable lexical order for merging. Equal priority still needs a declared tie rule. Lexical ordering is suitable for reproducible display or concatenation, but must not silently choose a semantically exclusive Provider unless the contract explicitly makes that behavior part of its public contract. - -Conformance tests must permute the same inputs and obtain a byte-equivalent normalized plan. - -## 7. User and policy selection - -For `selected-one`, the Host owns selection state. The selection key includes the service contract, allowed scope, and stable Provider ID; a plugin cannot write the selection directly. - -Selection UI or headless configuration must show each candidate's owning plugin, plugin and contract versions, negotiated features, requested grants, current health evidence, compatibility/test evidence, and exclusion reason. Presence in a market or a previous selection is not endorsement. - -Selection precedence is explicit and auditable, for example: - -1. an applicable administrative policy; -2. an applicable current user selection; -3. a contract-defined default only when the contract is allowed to have one; -4. otherwise `selection-needed`. - -The Host must not invent a default from install or load order. If the selected Provider disappears or becomes incompatible, the Host may use another Provider only when an already-approved policy explicitly permits fallback. Otherwise it returns to `selection-needed` and keeps unaffected plugins running. - -Selections should be portable only when their scope and Provider identity remain meaningful. Importing a profile must preserve an unresolved selection rather than silently map it to a similarly named Provider. - -## 8. Runtime binding and activation ownership - -A future Broker binds only Providers and dependencies already authorized by a Composition Plan. Runtime code cannot add a new contract, change cardinality, or make an incompatible candidate valid. - -Every registration, typed proxy, listener, timer, stream, operation, and child scope belongs to one activation instance. The Broker records the owner and removes its registry entries even if plugin cleanup throws. A plugin cannot unregister, replace, or dispose another activation's resources. - -Provider replacement and HMR always create a new activation identity, even when the plugin version and Provider ID are unchanged. An old activation identity is never recycled; it remains historical provenance while current bindings point to the new owner. - -Activation is the ownership boundary. If one activation owns several inseparable Providers or contributions, replacing one requires the plan to transition all affected resources and dependents. A Provider that needs independent replacement must be placed in its own declared child activation scope rather than detached from its owner after the fact. - -Consumers receive only the dependencies declared for them, through generated/narrow typed handles. Fabric does not expose `ctx.get(string)`, registry enumeration, or a raw Provider object. A handle becomes unusable after its generation or Provider is disposed and fails with a stable `service-unavailable` error. - -The future authoring experience might resemble the following, but names and signatures are not frozen: - -```ts -export default definePlugin((ctx) => { - // Generated from requires.services[...].as; no arbitrary service lookup. - const git = ctx.dependencies.gitClient - - // Binding is allowed only for the providerId declared in provides. - ctx.providers.bind('com.example.git-native/client', createGitClient(ctx)) - - ctx.commands.handle('com.example.git-native.refresh', async ({ signal }) => { - return git.status({ signal }) - }) -}) -``` - -Disposal is bounded and idempotent. During normal recomposition, consumers stop before their required Provider, dependency edges dispose in reverse topological order, and activations restart in topological order. The Host aborts in-flight calls, allows a contract-defined drain window, awaits `Disposable` / `AsyncDisposable` cleanup within a deadline, then forcibly removes registry state and records cleanup failures. - -Consumers do not inherit a Provider's permissions, and Providers do not inherit consumer permissions. Delegating a sensitive authority requires a separately specified, scoped, expiring delegation token and an audit record. `provides` by itself grants nothing. - -## 9. Health, replacement, and dependent reactivation - -Each service contract states whether health is advisory or required for binding and defines a small state model such as `starting`, `healthy`, `degraded`, `unhealthy`, `stopping`, and `disposed`. Health is operational evidence, not proof of safety, compatibility, or correctness. - -A Provider may publish health only through the Broker contract. Arbitrary plugin events cannot change selection state. The Broker rate-limits transitions, records reason and timestamp, and prevents a stale activation from reporting after disposal. - -When a required or selected Provider is removed, replaced, or becomes unbindable, the Broker performs a generation transition: - -1. validate and compute the replacement Composition Plan without executing new plugin code; -2. cancel or drain calls according to the service contract; -3. deactivate affected dependents in reverse dependency order; -4. dispose the old Provider activation and all owner-scoped resources; -5. activate and health-check the selected replacement; -6. reactivate dependents in dependency order with new typed handles; -7. publish the new generation or report a failed transition with provenance. - -Optional dependencies should be isolated in child activation scopes where practical, so losing one optional Provider can dispose and re-create only the feature that uses it. A required dependency loss blocks the dependent activation. - -Rollback is allowed only when the old Provider and its state remain valid and the contract defines a safe rollback boundary. Otherwise the Host exposes a degraded or selection-needed state. It must not retain a stale object reference or silently call a different Provider. - -Zero-downtime handoff is contract-specific and deferred. The baseline may pause calls during replacement. - -## 10. Calls, cancellation, timeout, and errors - -Every service method contract defines request/result schemas, side effects, concurrency, idempotency, cancellation points, deadline behavior, maximum payloads, privacy, and audit requirements. - -- Every asynchronous call receives a cancellation signal and effective deadline from the Broker. -- Cancellation is cooperative; requesting it does not prove that a remote or native side effect stopped. -- A timeout ends the consumer's wait and marks the call outcome, but does not imply rollback. -- Retry is forbidden unless the method contract declares it safe and defines idempotency keys or equivalent semantics. -- A Provider failure is isolated at the Broker boundary where the execution mode permits it. Trusted in-process execution still cannot contain `process.exit`, an infinite loop, or a native crash. -- Automatic “try the next Provider” behavior is forbidden unless the service contract and active user/policy selection explicitly define it. - -At minimum, normalized errors distinguish: - -| Code | Meaning | -| --- | --- | -| `service-unavailable` | No currently bound usable Provider exists. | -| `incompatible-version` | No mutually supported service-contract version exists. | -| `required-feature-missing` | A candidate lacks a required negotiated feature. | -| `selection-required` | A `selected-one` contract lacks a valid selection. | -| `composition-conflict` | A static hard conflict blocks the operation or activation. | -| `provider-unhealthy` | Health policy prevents use of the selected Provider. | -| `cancelled` | The caller or generation transition requested cancellation. | -| `deadline-exceeded` | The effective deadline elapsed. | -| `provider-failed` | The Provider returned or threw a normalized failure. | -| `activation-disposed` | The calling or serving activation no longer owns a valid handle. | - -Error payloads are serializable, size-bounded, privacy-redacted, and carry a correlation ID plus Provider/consumer provenance. Raw stack traces, credentials, and arbitrary thrown objects do not cross plugin boundaries. Each contract states whether an error fails one call, degrades a feature, blocks an activation, or triggers a generation transition. - -## 11. Composition Plan and runtime diagnostics - -The Host retains both the static plan and runtime transitions. A diagnostic view or machine-readable report should answer: - -- who declared, provided, selected, bound, replaced, and disposed a resource; -- which version and feature negotiation occurred; -- which policy or user action selected a Provider; -- which dependents were deactivated or degraded and why; -- which resources failed to clean up; -- whether a result is declared compatibility, runtime health, conformance evidence, or an unverified claim. - -Composition decisions are owned by the Composition Plan and Host/Broker policy. Accepted, suppressed, selected, and rejected candidates remain `decided` evidence in the Composition Plan or its decision log. Only an actually created binding or runtime transition—such as bind, replace, release, or cleanup failure—enters the Host-observed effect ledger as `observed` evidence. The ledger is never an arbitration input. Plugin logs, events, or returned payloads cannot create, rewrite, or attest either record class; at most they are clearly labeled plugin claims. - -This supports the provenance and impact-analysis request in the [Issue #23 discussion](https://github.com/omdsh-dev/community/issues/23#issuecomment-5305656025) without treating a market listing as verification. - -## 12. Conformance and test matrix - -Before the service runtime in this RFC can graduate beyond Draft, a headless testkit must cover at least: - -| Area | Required fixtures and properties | -| --- | --- | -| Declaration separation | `requires`, `provides`, `contributes`, `permissions`, and `subscriptions` cannot satisfy or imply one another; undeclared binding and unbound declarations are reported. | -| Identity | Global contract registry lookup, namespace ownership, duplicate Provider/contribution IDs, update continuity, and provider-eligibility rejection. | -| Static planning | Every cardinality, every provider-eligibility value, and all four composition outcomes; missing dependency and pending grant remain distinct; dependency cycles show the complete path. | -| Determinism | Every permutation of equivalent manifest/descriptor inputs produces a byte-equivalent normalized plan; filesystem and activation timing do not change it. | -| Version/features | Empty and non-empty SemVer intersections, prereleases, required/optional features, unknown features, and Broker/Adapter support. | -| Selection | GUI-equivalent and headless selection, scope precedence, missing/stale selection, explicit fallback policy, and profile import. | -| Lifecycle ownership | Repeated activation, reverse-order disposal, async cleanup deadline, thrown cleanup, stale handles, and no resource leakage. | -| Health/replacement | Healthy/degraded/unhealthy transitions, stale health events, Provider removal, failed replacement, optional child reactivation, required dependent reactivation, and rollback boundaries. | -| Calls | Success, normalized failure, cancellation, timeout, late result, retry prohibition, concurrency limits, and redaction. | -| Interoperability | The same planner fixtures pass in a fake Host and at least two independent Host integrations; runtime fixtures use at least two Providers for one service contract. | - -The normalized plan format, error format, health events, and transition log require schemas. Tests record the standard version, contract-registry version, Host/Adapter version, plugin versions, platform, and testkit commit. Passing the matrix is not a security certification. - -## 13. Delivery stages - -This RFC deliberately separates semantic reservation from implementation: - -### Stage A — v0.1 semantic reservation - -- keep the five declaration classes distinct in design while rejecting unsupported `provides` and service requirements in the v0.1 schema; -- reserve Host capability versus plugin-service namespaces; -- require service and contribution contracts to state cardinality, provider/owner eligibility, and conflict behavior; -- continue to activate the selected v0.1 plugin set eagerly by generation; -- do not expose a general plugin service runtime. - -### Stage B — static planner prototype - -- publish service/contribution contract registries and schemas; -- define the normalized Composition Plan and diagnostics schemas; -- implement a pure, headless planner with permutation tests; -- add fixtures for IDs, versions, features, cardinalities, scopes, conflicts, selections, and cycles. - -### Stage C — Broker runtime experiment - -- implement activation-owned Provider bindings and typed consumer proxies; -- implement bounded calls, health, teardown, replacement, and dependent reactivation; -- keep the runtime behind an experimental API range; -- validate it with fake Providers before adapting real DSH services. - -### Stage D — domain contracts and interoperability evidence - -- standardize individual high-value services through separate RFCs; -- test at least two implementations of one service contract; -- publish cross-Host evidence and operational diagnostics; -- graduate only the contracts whose semantics and failure behavior are proven. - -## 14. Developer experience requirements - -The eventual workflow should be: - -```text -declare dependency/provider/contribution/permission/subscription - → validate manifest and namespaces - → preview the static Composition Plan - → implement against generated typed contracts - → run fake-Host lifecycle and conflict fixtures - → test Provider replacement and cleanup - → package without importing DSH, Cordis, or Host internals -``` - -The SDK should generate narrow dependency properties and binding functions from the manifest and contract registry. Editors should complete versions and features, explain why a candidate is incompatible, and display the same Composition Plan as the Host. Authors should never need to coordinate load order, probe `ctx.get()`, mutate another plugin's registry entry, or import a concrete Provider plugin merely to consume its service. - -## 15. Security and trust - -Static planning improves predictability and consent; it does not make arbitrary code safe. In trusted in-process mode, a malicious plugin may bypass the supported API through Node.js or native modules. Strong enforcement still requires the isolated execution specification described by RFC 0001. - -The Broker must nevertheless enforce its own supported boundary: owner-scoped handles, declared dependencies, schema validation, size/rate/deadline limits, redaction, grants, delegation, and audit. A Provider's health, popularity, user selection, or conformance result must not be presented as a security review or endorsement. - -## 16. Open questions - -1. Which organization governs global service-contract IDs and publisher namespace disputes? -2. Which scopes belong in the first planner schema: runtime, profile, workspace, session, and invocation? -3. Should a Provider advertise one exact contract version or a tested discrete version set rather than a broad range? -4. Which selected-one services may define a default, and what evidence is required before fallback can be enabled? -5. How should persistent Provider state migrate across implementation replacement? -6. Which health signals are generic enough for the Broker, and which must remain domain-specific? -7. Can any late-bound dependency cycle be made portable, or should cycles remain forbidden permanently? -8. What minimum service contract should become the first two-Provider interoperability fixture? - -## 17. References and design input - -- [Issue #23: proposal for a unified plugin API and events](https://github.com/omdsh-dev/community/issues/23), especially the [Composition Rules comment](https://github.com/omdsh-dev/community/issues/23#issuecomment-5307228009). -- [RFC 0001: Manifest, Capabilities, and Events](0001-plugin-manifest-capabilities-events.md). -- [Research: Mature Plugin Framework Patterns](../research/mature-plugin-frameworks.md), especially Koishi-style dependency replacement and activation ownership. -- [Research: The VS Code Extension Model](../research/vscode-extension-model.md), especially domain Providers, contribution cardinality, selection, timeout, and replacement rules. -- [Research: What DSH Plugin Developers Actually Need](../research/dsh-plugin-needs.md), especially versioned service negotiation, Provider arbitration, health, and owner-scoped disposal. - -The community comment establishes the missing composition layer; the research notes supply implementation evidence. This RFC turns both into a testable design while keeping the experimental v0.1 runtime deliberately small. diff --git a/dsh-community-fabric/docs/rfcs/0003-service-providers-and-composition.zh.md b/dsh-community-fabric/docs/rfcs/0003-service-providers-and-composition.zh.md deleted file mode 100644 index 49032ecdae..0000000000 --- a/dsh-community-fabric/docs/rfcs/0003-service-providers-and-composition.zh.md +++ /dev/null @@ -1,421 +0,0 @@ -# RFC 0003:服务 Provider 与确定性组合 - -[English](0003-service-providers-and-composition.md) | 中文 - -| 字段 | 内容 | -| --- | --- | -| 状态 | Draft / 征求意见 | -| 目标 | v0.1 之后的 service runtime;v0.1 只预留语义位置 | -| 范围 | 服务依赖、Provider 与插件集合的确定性组合 | -| 依赖 | [RFC 0001](0001-plugin-manifest-capabilities-events.zh.md) | -| 社区输入 | [Issue #23 的 composition 评论](https://github.com/omdsh-dev/community/issues/23#issuecomment-5307228009) | -| 参考实现 | DSH Community Fabric(尚未实现) | - -## 0. 一句话摘要 - -Fabric 需要在执行任何插件代码之前完成插件集合的组合。一个插件可能需要 Host capability 或服务、提供某个服务实现、贡献可发现的产品元数据、申请权限并订阅事件。这是五类不同声明,绝不能被压进同一个泛化的 `capabilities` 容器。 - -本 RFC 提出静态计算的**组合计划(Composition Plan)**。Planner 读取 manifest、Host Descriptor、contract registry、授权、策略与已保存的用户选择,把插件集合判定为 `merge`、`soft-conflict`、`selection-needed` 或 `hard-conflict`。Provider cardinality 和仲裁规则属于每项版本化 service 或 contribution contract。插件发现顺序、包管理器顺序与 activation 时机永远不能作为仲裁输入。 - -未来的 Broker 只能依据这份计划创建 activation instance、把 consumer 绑定到 Provider、替换 Provider 并释放依赖资源。这样,插件组合能在启动前被解释,也能在 HMR、profile 重组、健康状态变化或退出时恢复到可控状态。 - -## 1. Draft 边界 - -这是一份设计草案,不是插件作者今天可以使用的 API。它扩展 [RFC 0001](0001-plugin-manifest-capabilities-events.zh.md) 的模型;后者的实验性 v0.1 runtime 仍只包含 Host 提供的 capability、生命周期、`storage.local`、`commands` 和一个不可变观察事件。 - -在 v0.1 中,`requires`、`provides`、`contributes`、`permissions` 和 `subscriptions` 只在标准设计中保留不同含义,不代表五类字段都能执行。在本 contract 与带版本 schema revision 被接受前,RFC 0001 的 v0.1 schema 会拒绝 `provides` 和 `requires.services`。v0.1 **不会**发布通用的插件服务注册表、动态依赖图、Provider 健康协议或热替换 runtime。Host 不得宣称这些功能属于 Fabric v0.1 一致性范围。 - -本 RFC 同样不会: - -- 向插件暴露通用 service locator、原始 Cordis/Koishi Context、DSH 对象或依赖注入容器; -- 把 trusted in-process 执行中的 capability 声明包装成安全沙箱; -- 引入按需激活——v0.1 仍在组装 runtime generation 时激活所有已选中、已授权的插件; -- 把 model、search、Git、UI、tool 或 storage 等所有服务塞进一套万能协议; -- 允许通过运行时注册修复静态无效的插件集合; -- 要求零停机 Provider 替换或自动 fallback。 - -“必须”“应该”“可以”沿用 RFC 0001 中的草案强度。只有 schema、参考 Broker 和一致性 fixtures 被接受后,它们才会构成兼容承诺。 - -## 2. 术语与两类依赖 - -### 2.1 Host capability - -**Host capability** 由 Host integration 或其版本化 DSH Adapter 实现和拥有。Host 在 Host Descriptor 中公布它。插件可以依赖它,并在适用时申请使用权限,但不能通过 `provides` 替换或遮蔽它。 - -例如 activation-scoped logging、`storage.local`、受控网络访问,或注册某类领域 Provider 的 API。Capability 可以暴露 Provider SPI,但 capability 本身仍由 Host 拥有。 - -### 2.2 插件提供的服务 - -**插件提供的服务**是一项版本化领域 contract,由一个插件 activation 实现,另一个插件通过 Broker 生成的强类型 proxy 使用。例如未来可能出现 `git.client`、`models.provider` 或 `search.provider`。 - -全局 service contract 需要定义请求与结果 schema、cardinality、允许的 scope、版本和 feature 协商、选择、健康、取消、超时、错误、teardown 与一致性测试。Package 名或插件 ID 不是 service contract。 - -Requirement namespace 必须表明自己解析的是哪一层。`requires.capabilities.storage.local` 向 Host 请求;`requires.services.git.client` 请求 Composition Planner 寻找兼容 Provider。同一个无类型限定的字符串不能同时用于两层,否则 manifest 无效。 - -### 2.3 Provider 与 Provider instance - -**Provider 声明**表示插件可以实现某项 service contract。**Provider instance** 是某个 activation instance 在一个允许 scope 内拥有的运行时 binding。Provider 所在 package 的版本、实现版本与 service-contract version 是三个不同的值。 - -只有 service contract 明确允许 Host Provider 时,Host 才能实现某项领域服务。Provider eligibility 为 `host-only` 的 contract 完全不接受插件 Provider。 - -### 2.4 Runtime generation 与 Composition Plan - -一个 **runtime generation** 由一组已选插件及其 Host Descriptor、授权、策略、选择与解析后的版本构成。**Composition Plan** 是在执行插件代码之前为该 generation 计算出的规范化、机器可读结果。 - -计划包含依赖边、候选项、已选 Provider、协商后的版本/feature、contribution ownership、被抑制的候选项、待授权状态,以及每个决定的 provenance。相同的规范化输入必须在每个兼容 Host 上产生相同计划。 - -## 3. 五类声明 - -Manifest 必须在结构和语义上分开以下声明: - -| 声明 | 回答的问题 | 权威与效果 | -| --- | --- | --- | -| `requires` | 哪些对象必须或可以已存在? | 对命名 Host capability 或 service contract 的兼容依赖。缺少 required 项会阻止依赖者 activation;缺少 optional 项时进入已声明的降级路径。 | -| `provides` | 这个插件能实现哪项 service contract? | 只表示 Provider 候选资格。它不会授予权限、选择 Provider 或激活代码。 | -| `contributes` | 这个插件向产品提供什么可发现对象? | Command、panel 请求、renderer ID 或 setting 等静态元数据。Host 负责验证、位置、组合与呈现。 | -| `permissions` | 插件请求用户或策略授予哪项敏感操作或数据 scope? | 只表示授权请求。它既不证明 Host 支持,也不满足依赖。 | -| `subscriptions` | 插件激活后应收到哪些版本化事件流? | 只表示投递意向。它不是 capability、permission、dependency、contribution 或 activation trigger。 | - -同一项功能可能需要多类声明。一个 search 实现可以 `provide` search service、`require` Host 的受控网络 capability、申请某个网络来源 `permission`、`contribute` 设置元数据,并 `subscribe` 凭据撤销事件。这些声明互不隐含。 - -下面是未来可能采用的形状,仅用于说明,不是冻结后的 manifest schema: - -```json -{ - "requires": { - "capabilities": { - "storage.local": { "version": ">=0.1.0 <0.2.0", "optional": false } - }, - "services": { - "git.client": { - "version": ">=1.2.0 <2.0.0", - "requiredFeatures": ["status"], - "optionalFeatures": ["worktrees"], - "optional": false, - "as": "gitClient" - } - } - }, - "provides": [ - { - "service": "git.client", - "providerId": "com.example.git-native/client", - "contractVersion": "1.4.0", - "features": ["status", "worktrees"], - "scope": "profile" - } - ], - "contributes": { - "commands": [ - { "id": "com.example.git-native.refresh", "title": "Refresh Git Status" } - ] - }, - "permissions": { - "required": [ - { "name": "fs.read", "scope": "workspace" } - ] - }, - "subscriptions": [ - { "event": "workspace.changed", "version": ">=1.0.0 <2.0.0" } - ] -} -``` - -Tooling 必须拒绝未声明的运行时 binding、报告声明后从未绑定的项目,并且不能把 permission/grant 状态写成兼容性声明。 - -## 4. Contract 身份、版本与 feature - -### 4.1 全局治理的 contract ID - -可移植 Host capability、service contract、event type 与标准 contribution kind 使用 Fabric registry 中全局治理的 ID,例如 `storage.local` 或 `git.client`。任何私有实现都不能重新定义这些 ID 的含义。组织私有 contract 使用经过所有权证明的 namespace,例如 `x-org.example.git.client`。 - -### 4.2 Publisher namespace 下的资源 ID - -Provider ID、contribution ID、command、renderer 与其他插件资源使用 publisher 控制、全局唯一的 namespace,通常采用反向域名前缀。ID 是稳定身份,不是展示名称。Package 更新不能偷偷更换已有 ID 的 owner。 - -两个已安装声明声称拥有同一全局资源 ID 时属于 hard conflict;只有资源 contract 明确定义更新/替换身份,且 package transaction 能证明身份连续性时除外。后加载的声明永远不能覆盖先加载的声明。 - -### 4.3 版本协商 - -Planner 分开保存以下值: - -| 值 | 含义 | -| --- | --- | -| Plugin version | 包含 Provider 或 consumer 的 package 发布版本。 | -| Service-contract version | 请求/结果与生命周期 contract 的 SemVer 版本。 | -| Required range | Consumer 可以使用的 contract version。 | -| Provider-supported range/version | 实现已经通过测试的 contract version。 | -| Feature set | 兼容 contract line 内命名的 optional 或 required 扩展。 | - -只有 consumer、Provider、Broker 与 Host/Adapter 支持范围的 contract-version 交集非空时,候选项才兼容。Registry 定义确定性选择规则;默认选择双方共同支持的最高稳定版本。只有所有相关 range 都明确允许时,prerelease 才参与协商。 - -只能在选出 contract version 后协商 feature。缺少或不认识 required feature 会让候选项不兼容;缺少或不认识 optional feature 会被记录为不可用,生成的 API 不包含它。Feature 不能用来在版本未变化时偷渡 breaking contract change。 - -计划记录已选版本、支持的 feature、排除的候选项和可读原因。Consumer 不能检查 Provider package 后根据其 package version 猜测支持情况。 - -## 5. Cardinality 与 Provider eligibility - -每项 service 与 contribution contract 必须把 cardinality 和“谁有资格提供”分开声明。把二者合成一个 mode 会导致一些合法 contract 无法表达,例如 Host-only 但允许多个 scope instance 的 service,或候选项可以同时来自 Host 和插件的 selected service。 - -### 5.1 Cardinality - -| Cardinality | 含义 | 存在多个声明时的静态结果 | -| --- | --- | --- | -| `many` | 可以有零个或多个 Provider/resource 共存。Contract 定义枚举、合并、selector、pipeline 或逐 invocation 路由语义。 | ID 与 contract rule 兼容时为 `merge`,否则进入相应 conflict class。 | -| `single` | 一个 scope 内最多存在一个 Provider/resource;这不表示它一定 required。 | 多于一个兼容 claim 时为 `hard-conflict`,除非 contract 定义了显式替换 transaction。 | -| `selected-one` | 可以安装多个候选项,但该服务在某 scope 内被 required 时只能选择一个。 | 有效的已保存选择或策略可完成解析,否则为 `selection-needed`。 | - -Cardinality 具有 scope。Contract 必须说明 instance space 属于 runtime、profile、workspace、session、invocation 或另一种已注册 scope。Provider 不能扩大声明的 scope,Host 也不能把全局选择偷偷当成 workspace 选择。 - -对于 `many`,service contract 仍必须定义“多个”的含义。返回全部 Provider、合并值、按明确 selector 选择、执行 pipeline 和询问用户是不同协议。泛化的“最高 priority 获胜”不是充分规则。 - -### 5.2 Provider eligibility - -| Eligibility | 含义 | 无效 claim | -| --- | --- | --- | -| `plugin-only` | 只有已声明的插件 Provider 可以满足该 service。 | Host implementation 不能静默遮蔽或满足它。 | -| `host-only` | 只有 Host Descriptor 中公布的 Host/Adapter implementation 可以满足它。 | 任何插件 `provides` claim 都是 `hard-conflict`。 | -| `host-or-plugin` | Host 与插件实现共同作为候选项,遵循相同版本、feature、scope、cardinality、选择和 provenance 规则。 | Host candidate 不能仅因为内置就获得隐式优先级。 | - -Provider eligibility 不会选择实现,也不授予 permission。对于 `host-or-plugin`,contract 仍必须提供明确的选择或合并规则;Host identity 只是 provenance,不是仲裁优先级。不表示可调用 Provider 的 contribution contract 则为自己的 resource kind 声明等价 ownership policy。 - -## 6. 静态组合结果 - -Planner 在 activation 前评估完整的已选插件集合,并为每项 service、contribution 与依赖 activation 分配 disposition: - -| 结果 | 含义 | Host 必须采取的行为 | -| --- | --- | --- | -| `merge` | 所有声明都能按照 contract 的确定性 many/merge rule 组合。 | 纳入每个已接受 owner,并记录规范化顺序或路由。 | -| `soft-conflict` | 声明发生重叠,但已发布的确定性策略能够抑制、调整位置、路由或以其他方式解决,无需临时发明语义。 | 应用具名策略、保留完整 provenance,并在诊断中显示被抑制/调整的结果。 | -| `selection-needed` | 某项由用户或策略选择的 contract 存在多个有效候选项,但当前没有有效选择。 | 不能猜测。只阻止受影响的 required dependent,并通过 UI 或 headless 配置请求明确选择。 | -| `hard-conflict` | 插件集合违反身份、ownership、版本、feature、scope、cycle 或 cardinality 规则,并且不存在已发布的解决方案。 | 按 Host 策略拒绝受影响的 generation 或插件子集;绝不能静默地部分激活。 | - -缺少 required dependency 与 required grant 被拒也是阻塞诊断,但它们和 collision 不同:应该分别报告 `dependency-unsatisfied` 和 `authorization-required`,不能误报成 conflict。 - -Composition Plan 必须可序列化,并且至少能够解释: - -- 每项 claim 来自哪份 manifest 和 Host Descriptor; -- 每条规则来自哪个 contract 与 registry version; -- 应用了哪项用户选择或管理策略; -- 每个候选项为什么被接受、抑制、等待选择或拒绝; -- 哪些 activation 因此被阻止或降级; -- contract 确实规定 ordered merge 时使用什么规范化顺序。 - -Service dependency graph 必须无环。Late-bound cycle 需要单独 contract,不属于本 RFC;没有这种 contract 时,cycle 是 hard conflict,诊断必须包含完整循环路径。 - -### 6.1 加载顺序不是策略 - -文件系统枚举、npm dependency 顺序、manifest 发现顺序、对象插入顺序、网络到达顺序与 activation 完成顺序都不能选择赢家或改变计划。 - -Contract 可以定义显式 priority、selector 或用于合并的稳定字典序。相同 priority 仍需要声明 tie rule。字典序适合可复现展示或拼接,但不能偷偷选择语义上排他的 Provider,除非 contract 明确把这种行为规定为公共语义。 - -一致性测试必须排列相同输入的所有顺序,并得到逐字节相等的规范化计划。 - -## 7. 用户与策略选择 - -对于 `selected-one`,Host 拥有 selection state。Selection key 包含 service contract、允许的 scope 与稳定 Provider ID;插件不能直接写入选择。 - -Selection UI 或 headless 配置必须展示每个候选项所属插件、plugin/contract version、协商后的 feature、申请的 grant、当前 health evidence、兼容/测试证据和排除原因。出现在市场中或曾被选择不代表推荐。 - -选择优先级必须明确且可审计,例如: - -1. 适用的管理策略; -2. 适用且仍有效的用户选择; -3. 只有 contract 被允许拥有 default 时,才应用 contract-defined default; -4. 否则进入 `selection-needed`。 - -Host 不能根据安装或加载顺序发明默认项。已选 Provider 消失或不再兼容时,只有事先批准的策略明确允许 fallback,Host 才能使用其他 Provider;否则回到 `selection-needed`,不受影响的插件继续运行。 - -只有 scope 与 Provider identity 仍然有意义时,selection 才应该可移植。导入 profile 时应保留无法解析的选择,而不是把它静默映射到名称相似的 Provider。 - -## 8. Runtime binding 与 activation ownership - -未来的 Broker 只能绑定 Composition Plan 已授权的 Provider 与依赖。运行时代码不能新增 contract、改变 cardinality 或让不兼容候选项变得有效。 - -每个 registration、typed proxy、listener、timer、stream、operation 与 child scope 都属于一个 activation instance。即使插件清理抛错,Broker 也会记录 owner 并移除其 registry entry。插件不能 unregister、replace 或 dispose 另一个 activation 的资源。 - -Provider replacement 与 HMR 必须始终创建新的 activation identity,即使 plugin version 和 Provider ID 都没有变化。旧 activation identity 永远不能复用;它作为历史 provenance 保留,当前 binding 则指向新 owner。 - -Activation 是 ownership boundary。如果一个 activation 拥有多个不可分离的 Provider 或 contribution,替换其中一项时,计划必须 transition 所有受影响资源与 dependent。需要独立 replacement 的 Provider 必须进入自己已声明的 child activation scope,不能事后脱离 owner。 - -Consumer 只能通过生成的窄类型 handle 获得自己声明过的 dependency。Fabric 不暴露 `ctx.get(string)`、registry 枚举或原始 Provider object。Generation 或 Provider 被 dispose 后,handle 立即失效,并返回稳定的 `service-unavailable` 错误。 - -未来的开发体验可能类似下面这样,但名称和签名尚未冻结: - -```ts -export default definePlugin((ctx) => { - // 由 requires.services[...].as 生成,不允许任意 service lookup。 - const git = ctx.dependencies.gitClient - - // 只能绑定 provides 中已声明的 providerId。 - ctx.providers.bind('com.example.git-native/client', createGitClient(ctx)) - - ctx.commands.handle('com.example.git-native.refresh', async ({ signal }) => { - return git.status({ signal }) - }) -}) -``` - -Disposal 必须有界且幂等。正常重组时,consumer 在 required Provider 之前停止;依赖边按反向拓扑顺序 dispose,activation 按拓扑顺序重新启动。Host 中止正在进行的调用、按 contract 规定的 drain window 等待、在 deadline 内等待 `Disposable` / `AsyncDisposable` 清理,然后强制移除 registry state 并记录清理失败。 - -Consumer 不继承 Provider permission,Provider 也不继承 consumer permission。委托敏感权限需要另行规定、具有 scope 和过期时间的 delegation token,以及 audit record。`provides` 本身不授予任何权限。 - -## 9. Health、替换与 dependent reactivation - -每项 service contract 需要说明 health 对 binding 是建议还是硬要求,并定义小型状态模型,例如 `starting`、`healthy`、`degraded`、`unhealthy`、`stopping` 和 `disposed`。Health 是运行证据,不是安全、兼容或正确性的证明。 - -Provider 只能通过 Broker contract 发布 health。任意插件事件不能改变 selection state。Broker 对 transition 限速,记录原因和时间戳,并阻止已经 dispose 的旧 activation 继续上报。 - -Required 或 selected Provider 被移除、替换或变为无法绑定时,Broker 执行 generation transition: - -1. 在不执行新插件代码的情况下验证并计算替代 Composition Plan; -2. 按 service contract 取消或 drain 调用; -3. 以反向依赖顺序 deactivate 受影响的 dependent; -4. dispose 旧 Provider activation 及其所有 owner-scoped resource; -5. activate 并 health-check 已选 replacement; -6. 按依赖顺序使用新 typed handle 重新 activate dependent; -7. 发布新 generation,或者报告带 provenance 的 transition failure。 - -在可行情况下,optional dependency 应隔离在 child activation scope,这样丢失 optional Provider 时只需 dispose 并重建使用它的功能。Required dependency 丢失会阻止 dependent activation。 - -只有旧 Provider 与其状态仍有效,且 contract 定义了安全 rollback boundary 时,才允许 rollback。否则 Host 暴露 degraded 或 selection-needed 状态。它不能保留 stale object reference,也不能偷偷调用另一 Provider。 - -零停机 handoff 取决于具体 contract,本 RFC 暂缓。Baseline 可以在 replacement 期间暂停调用。 - -## 10. 调用、取消、超时与错误 - -每项 service method contract 都要定义请求/结果 schema、副作用、并发、幂等性、取消点、deadline 行为、最大 payload、隐私与 audit 要求。 - -- 每个异步调用都从 Broker 收到 cancellation signal 和有效 deadline。 -- Cancellation 是协作式的;发出取消请求并不能证明远端或原生副作用已经停止。 -- Timeout 会结束 consumer 的等待并标记调用结果,但不表示 rollback 已完成。 -- 除非 method contract 明确声明安全并定义 idempotency key 或等价语义,否则禁止 retry。 -- 在 execution mode 能力范围内,Provider failure 在 Broker 边界隔离。Trusted in-process 执行仍无法容纳 `process.exit`、无限循环或 native crash。 -- 除非 service contract 和当前用户/策略选择都明确规定,否则禁止自动“尝试下一个 Provider”。 - -规范化错误至少区分: - -| Code | 含义 | -| --- | --- | -| `service-unavailable` | 当前不存在已绑定且可用的 Provider。 | -| `incompatible-version` | 不存在双方支持的 service-contract version。 | -| `required-feature-missing` | 候选项缺少 required negotiated feature。 | -| `selection-required` | `selected-one` contract 没有有效选择。 | -| `composition-conflict` | 静态 hard conflict 阻止 operation 或 activation。 | -| `provider-unhealthy` | Health policy 阻止使用已选 Provider。 | -| `cancelled` | Caller 或 generation transition 请求了取消。 | -| `deadline-exceeded` | 有效 deadline 已到期。 | -| `provider-failed` | Provider 返回或抛出了规范化 failure。 | -| `activation-disposed` | 发起或服务该调用的 activation 不再拥有有效 handle。 | - -Error payload 必须可序列化、有大小限制、经过隐私裁剪,并携带 correlation ID 及 Provider/consumer provenance。原始 stack trace、credential 和任意 thrown object 不能跨越插件边界。每项 contract 需要说明错误会终止单次调用、降级功能、阻止 activation,还是触发 generation transition。 - -## 11. Composition Plan 与运行时诊断 - -Host 保存静态计划与 runtime transition。诊断界面或机器可读报告应该能回答: - -- 谁声明、提供、选择、绑定、替换和 dispose 了某项资源; -- 发生了哪种版本与 feature 协商; -- 哪项策略或用户操作选择了 Provider; -- 哪些 dependent 被 deactivate 或降级,以及原因; -- 哪些资源清理失败; -- 某个结果属于声明兼容、运行时健康、一致性证据还是未验证 claim。 - -Composition decision 由 Composition Plan 与 Host/Broker policy 决定。已接受、抑制、选择和拒绝的候选项作为 `decided` 证据留在 Composition Plan 或其 decision log 中。只有真正创建的 binding 或 runtime transition,例如 bind、replace、release 或 cleanup failure,才会以 `observed` 证据进入 Host 观测的 effect ledger。Ledger 绝不能成为仲裁输入。插件日志、事件或返回 payload 不能创建、重写或证明任一类 record;它们最多只能作为明确标记的插件 claim。 - -这回应了 [Issue #23 讨论](https://github.com/omdsh-dev/community/issues/23#issuecomment-5305656025)中的溯源与影响分析需求,同时不会把市场收录当成验证。 - -## 12. 一致性与测试矩阵 - -本 RFC 的 service runtime 从 Draft 毕业前,headless testkit 至少覆盖: - -| 领域 | 必需 fixture 与性质 | -| --- | --- | -| 声明分离 | `requires`、`provides`、`contributes`、`permissions` 与 `subscriptions` 不能互相满足或隐含;报告未声明 binding 与声明后未 binding。 | -| 身份 | 全局 contract registry 查询、namespace ownership、重复 Provider/contribution ID、更新连续性与 provider-eligibility 拒绝。 | -| 静态规划 | 每种 cardinality、每种 provider-eligibility 值与四种 composition outcome;缺失依赖和待授权保持不同;dependency cycle 展示完整路径。 | -| 确定性 | 等价 manifest/descriptor 输入的每种排列都产生逐字节相同的规范化计划;文件系统和 activation 时机不会改变结果。 | -| 版本/feature | 空与非空 SemVer 交集、prerelease、required/optional feature、未知 feature 与 Broker/Adapter 支持范围。 | -| 选择 | GUI 等价选择和 headless 选择、scope 优先级、缺失/过期选择、显式 fallback policy 与 profile import。 | -| 生命周期 ownership | 重复 activation、反向顺序 disposal、异步清理 deadline、清理抛错、stale handle 与无资源泄漏。 | -| Health/replacement | Healthy/degraded/unhealthy transition、stale health event、Provider 移除、替换失败、optional child reactivation、required dependent reactivation 与 rollback boundary。 | -| 调用 | 成功、规范化 failure、取消、超时、迟到结果、禁止 retry、并发限制与裁剪。 | -| 互操作 | 同一 planner fixture 在 fake Host 和至少两个独立 Host integration 中通过;runtime fixture 为同一 service contract 使用至少两个 Provider。 | - -规范化 plan format、error format、health event 与 transition log 都需要 schema。测试记录标准版本、contract-registry version、Host/Adapter version、plugin version、平台与 testkit commit。通过测试矩阵不等于安全认证。 - -## 13. 交付阶段 - -本 RFC 刻意分开语义预留与实现: - -### Stage A——v0.1 语义预留 - -- 在设计中保持五类声明相互独立,同时让 v0.1 schema 拒绝不支持的 `provides` 与 service requirement; -- 预留 Host capability 与 plugin service 两类 namespace; -- 要求 service 与 contribution contract 写明 cardinality、provider/owner eligibility 与 conflict 行为; -- 继续按 generation eager activation 已选 v0.1 插件集合; -- 不暴露通用 plugin service runtime。 - -### Stage B——静态 planner 原型 - -- 发布 service/contribution contract registry 与 schema; -- 定义规范化 Composition Plan 和诊断 schema; -- 实现纯函数、headless planner 与排列测试; -- 为 ID、版本、feature、cardinality、scope、conflict、selection 和 cycle 添加 fixture。 - -### Stage C——Broker runtime 实验 - -- 实现 activation-owned Provider binding 与 typed consumer proxy; -- 实现有界调用、health、teardown、replacement 与 dependent reactivation; -- 把 runtime 保持在 experimental API range; -- 先使用 fake Provider 验证,再适配真实 DSH service。 - -### Stage D——领域 contract 与互操作证据 - -- 通过单独 RFC 标准化高价值具体服务; -- 测试同一 service contract 的至少两个实现; -- 发布跨 Host 证据与运行诊断; -- 只让语义和失败行为已经得到证明的 contract 毕业。 - -## 14. 开发体验要求 - -最终工作流应该是: - -```text -声明 dependency/provider/contribution/permission/subscription - → 验证 manifest 与 namespace - → 预览静态 Composition Plan - → 使用生成的强类型 contract 实现 - → 运行 fake-Host lifecycle 与 conflict fixture - → 测试 Provider replacement 与清理 - → 打包,且不导入 DSH、Cordis 或 Host internal -``` - -SDK 应该从 manifest 和 contract registry 生成窄类型 dependency property 与 binding function。Editor 应该能补全版本和 feature、解释候选项为什么不兼容,并展示与 Host 相同的 Composition Plan。作者不应再协调加载顺序、探测 `ctx.get()`、修改其他插件的 registry entry,或为了消费服务而导入某个具体 Provider plugin。 - -## 15. 安全与信任 - -静态规划改善可预测性与知情授权,但不会让任意代码自动安全。在 trusted in-process 模式中,恶意插件仍可能通过 Node.js 或 native module 绕过受支持 API。强制执行仍依赖 RFC 0001 描述的隔离执行规范。 - -Broker 仍必须强制执行自己的受支持边界:owner-scoped handle、已声明 dependency、schema validation、size/rate/deadline limit、redaction、grant、delegation 与 audit。Provider 的 health、流行度、用户选择或一致性结果不得被展示为安全审核或推荐。 - -## 16. 开放问题 - -1. 哪个组织负责治理全局 service-contract ID 与 publisher namespace 争议? -2. 第一版 planner schema 应包含哪些 scope:runtime、profile、workspace、session 和 invocation? -3. Provider 应公布一个精确 contract version,还是经过测试的离散版本集合,而不是宽泛 range? -4. 哪些 selected-one service 可以定义 default,允许 fallback 前需要什么证据? -5. Persistent Provider state 如何在不同实现替换时迁移? -6. 哪些 health signal 足够通用,可以进入 Broker;哪些必须保留为领域特有? -7. 是否存在可移植的 late-bound dependency cycle,还是应该永久禁止 cycle? -8. 哪项最小 service contract 适合作为第一个双 Provider 互操作 fixture? - -## 17. 参考与设计输入 - -- [Issue #23:统一插件 API 与事件提案](https://github.com/omdsh-dev/community/issues/23),尤其是 [Composition Rules 评论](https://github.com/omdsh-dev/community/issues/23#issuecomment-5307228009)。 -- [RFC 0001:Manifest、Capability 与事件模型](0001-plugin-manifest-capabilities-events.zh.md)。 -- [调研:成熟插件框架模式](../research/mature-plugin-frameworks.zh.md),特别是 Koishi 风格的依赖替换与 activation ownership。 -- [调研:VS Code 扩展模型](../research/vscode-extension-model.zh.md),特别是领域 Provider、contribution cardinality、选择、超时与 replacement rule。 -- [调研:DSH 插件开发者的真实需求](../research/dsh-plugin-needs.zh.md),特别是版本化 service negotiation、Provider arbitration、health 与 owner-scoped disposal。 - -社区评论指出了缺失的组合层,调研文档提供了实现证据。本 RFC 把二者转化为可测试设计,同时让实验性 v0.1 runtime 保持克制。 diff --git a/dsh-community-fabric/docs/rfcs/0004-provenance-validation-and-diagnostics.i18n.yaml b/dsh-community-fabric/docs/rfcs/0004-provenance-validation-and-diagnostics.i18n.yaml deleted file mode 100644 index 50b7a6d125..0000000000 --- a/dsh-community-fabric/docs/rfcs/0004-provenance-validation-and-diagnostics.i18n.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Bilingual-pair consistency record. Both languages carry equal authority. -# Update both files and record their normalized Git blob hashes after editing either side. -0004-provenance-validation-and-diagnostics.md: 1a60927ad26154021d3bc2af758b939acbd1eba6 -0004-provenance-validation-and-diagnostics.zh.md: 7e6999a8d63a19395c294f94ed32478c55724f58 diff --git a/dsh-community-fabric/docs/rfcs/0004-provenance-validation-and-diagnostics.md b/dsh-community-fabric/docs/rfcs/0004-provenance-validation-and-diagnostics.md deleted file mode 100644 index 1a60927ad2..0000000000 --- a/dsh-community-fabric/docs/rfcs/0004-provenance-validation-and-diagnostics.md +++ /dev/null @@ -1,264 +0,0 @@ -# RFC 0004: Provenance, Validation, Diagnostics, and the Effect Ledger - -English | [中文](0004-provenance-validation-and-diagnostics.zh.md) - -| Field | Value | -| --- | --- | -| Status | Draft / request for comments | -| Target | Cross-cutting contract extending the minimum v0.1 ownership ledger | -| Scope | Installation impact, runtime ownership, validation evidence, cleanup diagnostics | -| Depends on | [RFC 0001](0001-plugin-manifest-capabilities-events.md) | -| Community input | [omdsh-dev/community issue #23](https://github.com/omdsh-dev/community/issues/23) | - -## 0. Summary - -Fabric must be able to answer four questions without guessing: - -1. What does this plugin claim it will add or change? -2. What did the Host actually allow and activate? -3. Which plugin owns the command, service, UI, process, route, or other effect now visible? -4. After disable, replacement, or uninstall, what was cleaned up and what remains? - -This RFC proposes three machine-readable products: an installation-impact report, a validation report, and a Host-observed runtime effect ledger. They make compatibility and failures explainable; they do not turn untrusted local code into safe code. - -## 1. Motivation - -Manifest validation alone proves only that JSON has an expected shape. A catalog listing proves only that a source supplied metadata. Neither explains package scripts, native dependencies, grants, shared-service conflicts, runtime registrations, or cleanup. - -The community requested separate installation preview and runtime tracing in [this issue comment](https://github.com/omdsh-dev/community/issues/23#issuecomment-5305656025). The verification-tool discussion also requested a common machine-readable report instead of Host-specific prose in [this comment](https://github.com/omdsh-dev/community/issues/23#issuecomment-5306132230). - -RFC 0001 already requires activation-scoped ownership and a minimum append-only transition ledger in v0.1. This RFC extends that foundation with installation, validation, materialized diagnostics, retention, and recovery semantics. Reconstructing ownership later from ordinary logs would be incomplete and unreliable. - -## 2. Goals - -- Distinguish plugin/provider claims, package-manager observations, Host decisions, and runtime observations. -- Bind every report to an immutable plugin artifact identity. -- Preview meaningful installation effects before executable package code runs. -- Record every Fabric-managed runtime effect with plugin and activation ownership. -- Produce stable validation outcomes and reason codes for Hosts, CI, launchers, and markets. -- Explain conflicts, replacements, failed activation, incomplete cleanup, and residual state. -- Preserve enough evidence to reproduce a tested combination without leaking user content or secrets. - -## 3. Non-goals - -- Defining a universal malware scanner or claiming that a passing report means “safe”. -- Replacing code signing, sandboxing, operating-system policy, or human review. -- Standardizing every package manager or lockfile in this RFC. -- Treating catalog popularity, stars, publisher claims, or a cooperating source as validation. -- Granting permission to patch private code merely because a legacy effect was declared. -- Requiring raw message content, credentials, local paths, or environment variables in reports. - -## 4. Evidence classes - -Every field in a report has an evidence class. A UI must not silently promote one class into another. - -| Class | Meaning | Example | -| --- | --- | --- | -| `declared` | Supplied by the plugin manifest or publisher. | Requested capability, claimed repository. | -| `resolved` | Derived by a package manager or resolver from immutable inputs. | Exact package version, dependency graph, artifact digest. | -| `decided` | Chosen by Host policy or the user. | Granted scope, selected provider, denied build script. | -| `observed` | Recorded by the Broker or Adapter while running. | Registered command, opened process, cleanup result. | -| `tested` | Produced by a named suite in a defined environment. | Host conformance case passed on win32-x64. | -| `attested` | Signed by an identified verifier. | Future signed validation statement. | - -`attested` says who signed a statement, not that the statement is universally trustworthy. Signature and trust policy remain separate. - -## 5. Immutable subject identity - -Reports never bind only to a mutable package name, branch, URL, or “latest” label. Their subject includes: - -- Fabric plugin ID and plugin version; -- package ecosystem and exact package version when applicable; -- canonical source identity and immutable repository commit when applicable; -- cryptographic digest of the inspected artifact; -- manifest Schema identifier and Fabric API range; -- optional build-output digest when installation creates a different executable artifact. - -If an artifact digest changes, an earlier report does not apply. A Host may display related historical evidence, but it must mark it stale. - -## 6. Installation-impact report - -The installation-impact report is generated before installation confirmation from manifest data, package metadata, a resolved dependency plan, and Host policy. It contains no executable command supplied by a catalog. - -At minimum it records: - -- exact subject identity and source attribution; -- direct and transitive package dependencies with native-addon markers; -- lifecycle/build scripts that would be eligible to run; -- requested Fabric capabilities, permissions, and scopes; -- declared contributions, provided services, and subscriptions; -- profile or composition records that would be added, changed, or removed; -- expected files, storage namespaces, network origins, processes, and secrets by stable scope rather than raw local paths; -- conflicts, required user selections, unsupported capabilities, and restart requirements; -- any detected legacy patch/mixin/override target as an explicit non-portable effect. - -Static inspection can be incomplete. Every field carries `complete`, `partial`, `unknown`, or `not-applicable`; absence of evidence is never rendered as “no effect”. - -The final confirmation presents a bounded product summary and links to the detailed report. Installation may proceed only with an immutable target and a fresh report for the selected profile and Host policy. - -## 7. Validation report - -A validation report is an exchange format, not a single boolean. A draft shape is: - -```json -{ - "schemaVersion": "0.1.0", - "reportId": "urn:uuid:...", - "subject": { - "pluginId": "com.example.plugin", - "version": "1.2.0", - "digest": "sha256:..." - }, - "standard": { - "apiVersion": "0.1.0", - "manifestSchema": "https://example.invalid/fabric/manifest/0.1.0" - }, - "validator": { - "id": "org.example.fabric-verify", - "version": "0.3.0" - }, - "environment": { - "hostDescriptorDigest": "sha256:...", - "platform": "linux-x64", - "trustMode": "trusted-in-process" - }, - "suite": { - "id": "fabric-plugin-validation", - "version": "0.1.0", - "commit": "..." - }, - "startedAt": "2026-08-17T00:00:00Z", - "outcome": "pass", - "checks": [] -} -``` - -Each check has a stable ID, version, outcome (`pass`, `fail`, `warning`, `skipped`, `unknown`), evidence class, reason code, and redacted diagnostic. An aggregate `pass` is invalid when a required check failed or was silently omitted. - -The first report families should be separate: - -- Manifest and package validation; -- Host conformance; -- plugin contract validation; -- plugin × Host interoperability evidence; -- installation-impact inspection; -- runtime cleanup diagnostics. - -Markets and launchers may consume these reports, but must show the verifier, artifact digest, environment, time, and stale/revoked status. “Listed”, “declared compatible”, “tested”, “attested”, and “sandbox-enforced” remain distinct labels. - -## 8. Runtime effect ledger - -The canonical ledger is an append-only sequence of immutable transition records. Its v0.1 minimum is the same model defined by RFC 0001: - -- `ledgerVersion`, `recordId`, monotonic `sequence`, and `recordedAt`; -- owner `pluginId`, `pluginVersion` or `manifestDigest`, `activationId`, and `runtimeId`; -- `effectId`, `effectKind`, canonical contract ID/version, and stable `resourceId` when one exists; -- `operation` and resulting `state`, including at least `create`, `bind`, `replace`, `release`, and `cleanup-failed`; -- optional `correlationId`, previous/new owner or related-effect IDs, and a non-sensitive `outcome` or canonical `errorCode`; -- `sensitivityClass` and the applied redaction policy. - -As an RFC 0004 extension, a Host may add versioned observer metadata identifying the Runtime or Adapter component that produced the observation. It is not part of the v0.1 minimum, must remain Host-generated, and must not expose Transport details to plugin code. - -A Host may derive a materialized current view containing creation time, last-transition time, current owner, and current state. That view is a cache over transition records, not a second source of truth; it cannot erase failed cleanup, historical owners, or sequence gaps. Composition candidates that were suppressed or rejected before runtime stay in the Composition Plan decision log and never become observed effects. - -Initial effect kinds include command handlers, service providers, contributions, subscriptions, routes/RPC handlers, timers, background jobs, child processes, storage namespaces, temporary files, and experimental legacy effects. - -The ledger is Host-observed evidence. Plugins cannot write or rewrite ownership records. Adapters may submit observations only through the Broker SPI, which attaches the active owner and validates the resource kind. - -## 9. Activation, replacement, and cleanup - -An activation record links negotiation, grants, provider selections, effects, diagnostics, and final disposition. - -On normal deactivation the Broker: - -1. stops new invocations; -2. aborts and drains owned work within a documented bound; -3. releases effects in contract-defined order; -4. records each success, timeout, and residual effect; -5. marks the activation disposed only after the ledger reaches a terminal state. - -A process crash can prevent final records. On the next start, a recovery scanner compares persistent resource markers with the last durable ledger and reports `orphaned` or `unknown`; it must not fabricate successful cleanup. - -Provider replacement and HMR create a new activation identity. Historical ownership remains queryable, while the current resource points to the new owner according to its composition contract. - -## 10. Diagnostics graph - -User-facing diagnostics should answer “what failed and what can I do?” without exposing internals. Developer diagnostics may follow stable IDs across: - -```text -artifact → manifest → negotiation/grant → activation - → provider/contribution → invocation → effect → error/cleanup -``` - -The graph records causal IDs, not arbitrary object references or stack-trace objects. Raw upstream causes remain in Host-owned logs and are correlated by ID. - -Suggested stable outcomes include: - -- incompatible API or missing capability; -- permission denied; -- unresolved or conflicting provider; -- duplicate contribution ID; -- activation failed or timed out; -- invocation failed or cancelled; -- cleanup incomplete; -- stale validation evidence; -- legacy effect detected; -- report subject mismatch. - -## 11. Legacy effects and migration - -Migration tooling may emit a read-only `legacyEffects` inspection section describing known source patches, mixin targets, private-service access, or unmanaged global side effects. - -This is diagnostic metadata only: - -- it does not grant permission; -- it does not make the effect portable; -- it does not promote a private seam into a Fabric capability; -- it does not allow an ordinary plugin to provide executable patch instructions in its manifest. - -An experimental, version-pinned DSH Adapter may use a reviewed compatibility bridge. Such effects are labeled experimental and excluded from portable conformance until replaced by a public, testable capability. - -## 12. Privacy and retention - -- Never store message bodies, prompts, model output, secret values, authorization codes, raw environment variables, or unredacted local paths in standard reports. -- Use opaque scope/resource IDs and explicit sensitivity labels. -- Keep ephemeral presentation data out of the durable ledger unless a redacted fact such as “shown” is required for audit. -- Give users a way to inspect and delete local diagnostics subject to required security/audit policy. -- Telemetry export is separate, opt-in policy; a local ledger does not authorize upload. -- Bound report size, history length, and retention. Summarization must preserve failed cleanup and unresolved conflicts. - -## 13. Conformance requirements - -A conforming prototype must test at least: - -- artifact digest mismatch invalidates prior evidence; -- unknown checks never become pass; -- validation reason codes remain stable and localizable text stays separate; -- every Fabric-managed registration receives the active plugin and activation owner; -- a plugin cannot forge another owner; -- replacement records both owners and follows composition policy; -- synchronous and asynchronous resources release on deactivation; -- timeout and crash paths report incomplete or unknown cleanup; -- sensitive values are absent from serialized reports; -- catalog listing does not produce a validation label by itself; -- legacy-effect declaration never authorizes execution; -- reports from a different Host descriptor, platform, suite, or artifact are visibly non-matching. - -Tests must run headlessly. At least one real Adapter integration test should compare ledger observations with actual DSH registrations and teardown. - -## 14. Relationship to other work - -- [RFC 0001](0001-plugin-manifest-capabilities-events.md) owns manifest, negotiation, lifecycle, and basic activation ownership. -- [RFC 0003](0003-service-providers-and-composition.md) owns service-provider conflict and replacement policy; this RFC records only the resulting runtime transitions as observed effects while decisions remain in the Composition Plan. -- [RFC 0002](0002-runtime-presentation-invocation-transport.md) owns invocation and Runtime/Presentation identity; this RFC records opaque identities and redacted outcomes. -- [DSH plugin-needs research](../research/dsh-plugin-needs.md) supplies real examples of private routes, UI registration, processes, package operations, and monkey patches that require ownership. -- [DSH Community Market](../../../dsh-community-market/README.md) may display reports but cannot create or upgrade their trust class. - -## 15. Open questions - -1. Beyond the RFC 0001 minimum, which transition records and materialized views must be durable, and which may remain in memory? -2. Who owns namespaces for validation check IDs and reason codes? -3. Which report families require signatures, transparency logs, or revocation? -4. How are package scripts and native modules represented consistently across package managers? -5. What minimum recovery marker allows orphan detection without storing user paths? -6. Which diagnostics are visible to ordinary users, plugin authors, Host maintainers, and security reviewers? diff --git a/dsh-community-fabric/docs/rfcs/0004-provenance-validation-and-diagnostics.zh.md b/dsh-community-fabric/docs/rfcs/0004-provenance-validation-and-diagnostics.zh.md deleted file mode 100644 index 7e6999a8d6..0000000000 --- a/dsh-community-fabric/docs/rfcs/0004-provenance-validation-and-diagnostics.zh.md +++ /dev/null @@ -1,264 +0,0 @@ -# RFC 0004:溯源、验证、诊断与 Effect Ledger - -[English](0004-provenance-validation-and-diagnostics.md) | 中文 - -| 字段 | 内容 | -| --- | --- | -| 状态 | Draft / 征求意见 | -| 目标 | 扩展 v0.1 最小 ownership ledger 的横切 contract | -| 范围 | 安装影响、运行时 ownership、验证证据、清理诊断 | -| 依赖 | [RFC 0001](0001-plugin-manifest-capabilities-events.zh.md) | -| 社区输入 | [omdsh-dev/community issue #23](https://github.com/omdsh-dev/community/issues/23) | - -## 0. 一句话摘要 - -Fabric 必须能在不猜测的情况下回答四个问题: - -1. 这个插件声称会添加或修改什么? -2. Host 实际允许并激活了什么? -3. 当前可见的 command、service、UI、process、route 或其他 effect 属于哪个插件? -4. 停用、替换或卸载之后,哪些内容已经清理,哪些仍然残留? - -本 RFC 提议三种机器可读产物:安装影响报告、验证报告和由 Host 观测的运行时 effect ledger。它们让兼容性和故障可以解释,但不会把不受信任的本地代码变成安全代码。 - -## 1. 背景 - -Manifest 校验只能证明 JSON 结构符合预期。目录收录也只能证明某个来源提供了元数据。二者都无法解释 package script、native dependency、授权、共享 service 冲突、运行时注册或清理结果。 - -社区在[这条评论](https://github.com/omdsh-dev/community/issues/23#issuecomment-5305656025)中明确区分了安装前影响预览和运行时溯源。验证工具相关讨论也在[这条评论](https://github.com/omdsh-dev/community/issues/23#issuecomment-5306132230)中提出统一机器可读报告,而不是让每个 Host 输出自己的自然语言。 - -RFC 0001 已要求 activation-scoped ownership,以及 v0.1 中最小的 append-only transition ledger。本 RFC 在这个基础上扩展安装、验证、物化诊断、保留与恢复语义。以后再尝试从普通日志还原 ownership 会不完整且不可靠。 - -## 2. 目标 - -- 区分插件/provider 声明、包管理器观测、Host 决策和运行时观测。 -- 把每份报告绑定到不可变的插件 artifact identity。 -- 在执行任何 package 代码前预览有意义的安装影响。 -- 为每个 Fabric 管理的运行时 effect 记录插件与 activation ownership。 -- 为 Host、CI、启动器和市场提供稳定验证结果与 reason code。 -- 解释冲突、替换、激活失败、清理不完整和残留状态。 -- 在不泄露用户内容或 secret 的前提下,保留足够证据复现经过测试的组合。 - -## 3. 非目标 - -- 定义通用恶意代码扫描器,或把通过报告称为“安全”。 -- 替代代码签名、沙箱、操作系统策略或人工审核。 -- 在本 RFC 中统一所有包管理器和 lockfile。 -- 把目录热度、stars、publisher claim 或合作来源当成验证。 -- 仅因为声明了 legacy effect 就获得 patch 私有代码的权限。 -- 要求报告包含原始消息、credential、本地路径或环境变量。 - -## 4. 证据类型 - -报告中的每个字段都有 evidence class,UI 不能把一种类型静默升级成另一种。 - -| 类型 | 含义 | 示例 | -| --- | --- | --- | -| `declared` | 由插件 manifest 或 publisher 提供。 | 申请的 capability、声明的 repository。 | -| `resolved` | 包管理器或 resolver 根据不可变输入推导。 | 精确 package 版本、依赖图、artifact digest。 | -| `decided` | 由 Host policy 或用户选择。 | 已授权 scope、选中的 provider、拒绝的 build script。 | -| `observed` | Broker 或 Adapter 在运行时记录。 | 注册的 command、启动的 process、清理结果。 | -| `tested` | 由具名测试套件在明确环境中产生。 | Host conformance 用例在 win32-x64 通过。 | -| `attested` | 由可识别的 verifier 签名。 | 未来的签名验证声明。 | - -`attested` 只说明谁签署了声明,不代表该声明天然可信。签名与信任策略仍然分开。 - -## 5. 不可变 subject identity - -报告绝不能只绑定可变 package 名、branch、URL 或 `latest` 标签。Subject 至少包含: - -- Fabric plugin ID 和插件版本; -- 适用时的 package ecosystem 与精确 package 版本; -- 适用时的规范 source identity 与不可变 repository commit; -- 被检查 artifact 的加密 digest; -- manifest Schema 标识与 Fabric API range; -- 安装产生不同可执行 artifact 时的可选 build-output digest。 - -Artifact digest 改变后,旧报告不再适用。Host 可以展示相关历史证据,但必须明确标记为 stale。 - -## 6. 安装影响报告 - -安装影响报告在安装确认前生成,输入包括 manifest、package metadata、已解析依赖计划和 Host policy。它不包含目录提供的可执行命令。 - -至少记录: - -- 精确 subject identity 与来源声明; -- 直接和间接 package dependency,并标记 native addon; -- 可能运行的 lifecycle/build script; -- 申请的 Fabric capability、permission 与 scope; -- 声明的 contribution、provided service 与 subscription; -- 将新增、修改或移除的 profile/composition 记录; -- 预期文件、storage namespace、network origin、process 和 secret,以稳定 scope 而不是原始本地路径表示; -- 冲突、需要用户选择、缺失 capability 和 restart 要求; -- 检测到的 legacy patch/mixin/override target,并明确标为不可移植 effect。 - -静态检查可能不完整。每个字段都带 `complete`、`partial`、`unknown` 或 `not-applicable`;缺少证据绝不能显示成“没有影响”。 - -最终确认界面展示有界的产品摘要,并可以打开详细报告。只有安装目标不可变、且报告与所选 profile 和 Host policy 仍然匹配时才能继续。 - -## 7. 验证报告 - -验证报告是交换格式,不是单一 boolean。讨论用结构如下: - -```json -{ - "schemaVersion": "0.1.0", - "reportId": "urn:uuid:...", - "subject": { - "pluginId": "com.example.plugin", - "version": "1.2.0", - "digest": "sha256:..." - }, - "standard": { - "apiVersion": "0.1.0", - "manifestSchema": "https://example.invalid/fabric/manifest/0.1.0" - }, - "validator": { - "id": "org.example.fabric-verify", - "version": "0.3.0" - }, - "environment": { - "hostDescriptorDigest": "sha256:...", - "platform": "linux-x64", - "trustMode": "trusted-in-process" - }, - "suite": { - "id": "fabric-plugin-validation", - "version": "0.1.0", - "commit": "..." - }, - "startedAt": "2026-08-17T00:00:00Z", - "outcome": "pass", - "checks": [] -} -``` - -每项 check 包含稳定 ID、版本、结果(`pass`、`fail`、`warning`、`skipped`、`unknown`)、evidence class、reason code 和脱敏诊断。只要 required check 失败或被静默省略,aggregate outcome 就不能是 `pass`。 - -首批报告类型应彼此分开: - -- Manifest 与 package validation; -- Host conformance; -- plugin contract validation; -- plugin × Host interoperability evidence; -- 安装影响检查; -- 运行时清理诊断。 - -市场和启动器可以消费这些报告,但必须展示 verifier、artifact digest、环境、时间以及 stale/revoked 状态。`listed`、`declared compatible`、`tested`、`attested` 与 `sandbox-enforced` 始终是不同标签。 - -## 8. 运行时 Effect Ledger - -规范 ledger 是一组 append-only、不可修改的 transition record。它的 v0.1 最小模型与 RFC 0001 完全相同: - -- `ledgerVersion`、`recordId`、单调递增 `sequence` 与 `recordedAt`; -- owner `pluginId`、`pluginVersion` 或 `manifestDigest`、`activationId` 与 `runtimeId`; -- `effectId`、`effectKind`、规范 contract ID/version,以及存在时的稳定 `resourceId`; -- `operation` 与结果 `state`,至少包含 `create`、`bind`、`replace`、`release` 和 `cleanup-failed`; -- 可选 `correlationId`、旧/新 owner 或相关 effect ID,以及不敏感的 `outcome` 或规范 `errorCode`; -- `sensitivityClass` 与已应用的 redaction policy。 - -作为 RFC 0004 extension,Host 可以加入带版本的 observer metadata,标识产生观测的 Runtime 或 Adapter component。它不属于 v0.1 最小字段,必须由 Host 生成,也不能把 Transport 细节暴露给插件代码。 - -Host 可以推导包含创建时间、最近 transition 时间、当前 owner 和当前 state 的物化视图。这个视图只是 transition record 上的 cache,不是第二份事实来源;它不能抹掉清理失败、历史 owner 或 sequence gap。在 runtime 之前就被抑制或拒绝的 composition candidate 留在 Composition Plan decision log 中,绝不能变成 observed effect。 - -首批 effect kind 包括 command handler、service provider、contribution、subscription、route/RPC handler、timer、background job、child process、storage namespace、temporary file 和实验性 legacy effect。 - -Ledger 是 Host 观测的证据。插件不能写入或重写 ownership record。Adapter 只能通过 Broker SPI 提交观测,由 Broker 附加当前 owner 并校验 resource kind。 - -## 9. 激活、替换与清理 - -Activation record 关联协商、授权、provider 选择、effect、诊断和最终状态。 - -正常 deactivation 时,Broker: - -1. 停止接收新的 invocation; -2. 在明确时间边界内 abort 并 drain 自己拥有的工作; -3. 按 contract 规定的顺序释放 effect; -4. 记录每项成功、timeout 和残留 effect; -5. 只有 ledger 到达终态后才把 activation 标记为 disposed。 - -Process crash 可能让最终记录无法写入。下次启动时,恢复扫描器对比持久 resource marker 与最后的 durable ledger,报告 `orphaned` 或 `unknown`,不能伪造清理成功。 - -Provider 替换和 HMR 会创建新的 activation identity。历史 ownership 继续可查询,当前 resource 根据 composition contract 指向新 owner。 - -## 10. 诊断图 - -面向用户的诊断应该回答“哪里失败、我可以做什么”,同时避免暴露内部实现。开发者诊断可以沿稳定 ID 追踪: - -```text -artifact → manifest → negotiation/grant → activation - → provider/contribution → invocation → effect → error/cleanup -``` - -图中记录 causal ID,而不是任意 object reference 或 stack-trace object。原始上游 cause 留在 Host 自有日志中,通过 ID 关联。 - -建议的稳定结果包括: - -- API 不兼容或缺失 capability; -- permission denied; -- provider 未解析或冲突; -- contribution ID 重复; -- activation 失败或 timeout; -- invocation 失败或 cancelled; -- cleanup 不完整; -- validation evidence 过期; -- 检测到 legacy effect; -- report subject 不匹配。 - -## 11. Legacy effect 与迁移 - -迁移工具可以生成只读 `legacyEffects` 检查段,描述已知 source patch、mixin target、私有 service 访问或未受管全局副作用。 - -它只是诊断元数据: - -- 不授予权限; -- 不让 effect 变成可移植; -- 不把 private seam 升级成 Fabric capability; -- 不允许普通插件在 manifest 中提供可执行 patch 指令。 - -实验性、固定版本的 DSH Adapter 可以使用经过审查的兼容桥。这类 effect 必须标记 experimental,在被公开、可测试 capability 替代之前不能通过 portable conformance。 - -## 12. 隐私与保留 - -- 标准报告绝不保存消息正文、prompt、模型输出、secret value、authorization code、原始环境变量或未脱敏本地路径。 -- 使用不透明 scope/resource ID 和明确 sensitivity label。 -- Ephemeral presentation 数据不进入 durable ledger;如审计需要,只记录“已经展示”等脱敏事实。 -- 在安全/审计策略允许范围内,用户可以检查并删除本地诊断。 -- Telemetry export 是独立、opt-in policy;存在本地 ledger 不代表允许上传。 -- 限制报告大小、历史长度和保留期;摘要不能丢掉失败清理和未解决冲突。 - -## 13. 一致性要求 - -兼容原型至少测试: - -- artifact digest 不匹配会让旧证据失效; -- unknown check 绝不能变成 pass; -- validation reason code 稳定,本地化文本与 code 分开; -- 每个 Fabric 管理的 registration 都获得当前 plugin/activation owner; -- 插件不能冒充其他 owner; -- 替换记录新旧 owner,并遵循 composition policy; -- 同步和异步资源在 deactivation 时释放; -- timeout/crash 路径报告 incomplete 或 unknown cleanup; -- 序列化报告不包含敏感值; -- 目录收录本身不能生成 validation label; -- 声明 legacy effect 绝不能授权执行; -- 来自不同 Host descriptor、platform、suite 或 artifact 的报告明确显示不匹配。 - -测试必须能在 headless 环境运行。至少一个真实 Adapter integration test 应把 ledger 观测与实际 DSH registration/teardown 对比。 - -## 14. 与其他工作的关系 - -- [RFC 0001](0001-plugin-manifest-capabilities-events.zh.md) 管理 manifest、协商、生命周期和基础 activation ownership。 -- [RFC 0003](0003-service-providers-and-composition.zh.md)管理 service-provider 冲突与替换策略;本 RFC 只把最终 runtime transition 记录为 observed effect,decision 仍留在 Composition Plan。 -- [RFC 0002](0002-runtime-presentation-invocation-transport.zh.md)管理 invocation 与 Runtime/Presentation identity;本 RFC 记录不透明 identity 和脱敏结果。 -- [DSH 插件需求调研](../research/dsh-plugin-needs.zh.md)包含 private route、UI 注册、process、package operation 和 monkey patch 等需要 ownership 的真实例子。 -- [DSH Community Market](../../../dsh-community-market/README.zh.md)可以展示报告,但不能自行创建或提升其 trust class。 - -## 15. 开放问题 - -1. 除 RFC 0001 最小记录外,哪些 transition record 与物化视图必须持久化,哪些可以只保留在内存? -2. validation check ID 与 reason code 的 namespace 由谁管理? -3. 哪类报告需要签名、透明日志或撤销? -4. 如何跨包管理器一致表示 package script 和 native module? -5. 什么最小 recovery marker 可以在不保存用户路径的情况下发现 orphan? -6. 哪些诊断分别对普通用户、插件作者、Host 维护者和安全审核者可见? diff --git a/dsh-community-fabric/package.json b/dsh-community-fabric/package.json deleted file mode 100644 index d25a26dbb1..0000000000 --- a/dsh-community-fabric/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "dsh-community-fabric", - "version": "0.1.0-dev.0", - "private": true, - "description": "Documentation-first home for a community DSH plugin interoperability standard", - "license": "MIT", - "publishConfig": { - "access": "public", - "registry": "https://registry.npmjs.org" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/techflag/workdsh.git", - "directory": "dsh-community-fabric" - }, - "homepage": "https://github.com/techflag/workdsh/tree/main/dsh-community-fabric#readme", - "bugs": { - "url": "https://github.com/techflag/workdsh/issues" - }, - "type": "module", - "files": [ - "docs/**", - "LICENSE", - "README.md", - "README.zh.md", - "README.i18n.yaml" - ], - "engines": { - "node": "^22.19.0 || >=24.0.0" - }, - "scripts": { - "check": "node scripts/verify-docs.mjs", - "prepack": "yarn run check" - }, - "keywords": [ - "deepseek", - "dsh", - "plugin", - "interoperability", - "community-standard" - ] -} diff --git a/dsh-community-fabric/scripts/verify-docs.mjs b/dsh-community-fabric/scripts/verify-docs.mjs deleted file mode 100644 index e6f7cbd14f..0000000000 --- a/dsh-community-fabric/scripts/verify-docs.mjs +++ /dev/null @@ -1,157 +0,0 @@ -import { execFileSync } from 'node:child_process' -import { existsSync, readdirSync, readFileSync } from 'node:fs' -import { dirname, resolve } from 'node:path' -import { fileURLToPath } from 'node:url' - -const packageRoot = dirname(dirname(fileURLToPath(import.meta.url))) -const fail = message => { throw new Error(`verify-fabric-docs: ${message}`) } -const read = path => readFileSync(resolve(packageRoot, path), 'utf8') -const manifest = JSON.parse(read('package.json')) - -if (manifest.name !== 'dsh-community-fabric') fail('package name must remain dsh-community-fabric') -if (manifest.private !== true) fail('the Draft scaffold must stay private until a reviewed runtime exists') -for (const field of ['main', 'module', 'types', 'exports', 'bin', 'dsh', 'dependencies', 'optionalDependencies']) { - if (manifest[field] !== undefined) fail(`documentation scaffold must not declare ${field}`) -} - -const publicFiles = [ - 'LICENSE', - 'README.i18n.yaml', - 'README.md', - 'README.zh.md', - 'docs/architecture/compatibility-layer.i18n.yaml', - 'docs/architecture/compatibility-layer.md', - 'docs/architecture/compatibility-layer.zh.md', - 'docs/research/dsh-plugin-needs.i18n.yaml', - 'docs/research/dsh-plugin-needs.md', - 'docs/research/dsh-plugin-needs.zh.md', - 'docs/research/community-issue-23-review.i18n.yaml', - 'docs/research/community-issue-23-review.md', - 'docs/research/community-issue-23-review.zh.md', - 'docs/research/mature-plugin-frameworks.i18n.yaml', - 'docs/research/mature-plugin-frameworks.md', - 'docs/research/mature-plugin-frameworks.zh.md', - 'docs/research/vscode-extension-model.i18n.yaml', - 'docs/research/vscode-extension-model.md', - 'docs/research/vscode-extension-model.zh.md', - 'docs/rfcs/0001-plugin-manifest-capabilities-events.i18n.yaml', - 'docs/rfcs/0001-plugin-manifest-capabilities-events.md', - 'docs/rfcs/0001-plugin-manifest-capabilities-events.zh.md', - 'docs/rfcs/0002-runtime-presentation-invocation-transport.i18n.yaml', - 'docs/rfcs/0002-runtime-presentation-invocation-transport.md', - 'docs/rfcs/0002-runtime-presentation-invocation-transport.zh.md', - 'docs/rfcs/0003-service-providers-and-composition.i18n.yaml', - 'docs/rfcs/0003-service-providers-and-composition.md', - 'docs/rfcs/0003-service-providers-and-composition.zh.md', - 'docs/rfcs/0004-provenance-validation-and-diagnostics.i18n.yaml', - 'docs/rfcs/0004-provenance-validation-and-diagnostics.md', - 'docs/rfcs/0004-provenance-validation-and-diagnostics.zh.md', -] -for (const path of [...publicFiles, 'scripts/verify-docs.mjs']) { - if (!existsSync(resolve(packageRoot, path))) fail(`${path} is missing`) -} - -const discoverDocs = (directory, prefix) => { - const paths = [] - for (const entry of readdirSync(resolve(packageRoot, directory), { withFileTypes: true })) { - const path = prefix ? `${prefix}/${entry.name}` : entry.name - if (entry.isDirectory()) paths.push(...discoverDocs(path, path)) - else if (path.endsWith('.md') || path.endsWith('.i18n.yaml')) paths.push(path) - } - return paths -} -const discoveredDocs = [ - ...readdirSync(packageRoot, { withFileTypes: true }) - .filter(entry => entry.isFile() && (entry.name.endsWith('.md') || entry.name.endsWith('.i18n.yaml'))) - .map(entry => entry.name), - ...discoverDocs('docs', 'docs'), -].sort() -const declaredDocs = publicFiles.filter(path => path.endsWith('.md') || path.endsWith('.i18n.yaml')).sort() -if (JSON.stringify(discoveredDocs) !== JSON.stringify(declaredDocs)) { - fail(`documentation inventory differs: declared=${declaredDocs.join(',')} discovered=${discoveredDocs.join(',')}`) -} - -const expectedFiles = ['docs/**', 'LICENSE', 'README.md', 'README.zh.md', 'README.i18n.yaml'] -if (JSON.stringify(manifest.files) !== JSON.stringify(expectedFiles)) { - fail('package files must contain only the reviewed documentation surface') -} - -const pairs = [ - ['README.i18n.yaml', ['README.md', 'README.zh.md']], - [ - 'docs/architecture/compatibility-layer.i18n.yaml', - ['docs/architecture/compatibility-layer.md', 'docs/architecture/compatibility-layer.zh.md'], - ], - [ - 'docs/research/dsh-plugin-needs.i18n.yaml', - ['docs/research/dsh-plugin-needs.md', 'docs/research/dsh-plugin-needs.zh.md'], - ], - [ - 'docs/research/community-issue-23-review.i18n.yaml', - ['docs/research/community-issue-23-review.md', 'docs/research/community-issue-23-review.zh.md'], - ], - [ - 'docs/research/mature-plugin-frameworks.i18n.yaml', - ['docs/research/mature-plugin-frameworks.md', 'docs/research/mature-plugin-frameworks.zh.md'], - ], - [ - 'docs/research/vscode-extension-model.i18n.yaml', - ['docs/research/vscode-extension-model.md', 'docs/research/vscode-extension-model.zh.md'], - ], - [ - 'docs/rfcs/0001-plugin-manifest-capabilities-events.i18n.yaml', - [ - 'docs/rfcs/0001-plugin-manifest-capabilities-events.md', - 'docs/rfcs/0001-plugin-manifest-capabilities-events.zh.md', - ], - ], - [ - 'docs/rfcs/0002-runtime-presentation-invocation-transport.i18n.yaml', - [ - 'docs/rfcs/0002-runtime-presentation-invocation-transport.md', - 'docs/rfcs/0002-runtime-presentation-invocation-transport.zh.md', - ], - ], - [ - 'docs/rfcs/0003-service-providers-and-composition.i18n.yaml', - [ - 'docs/rfcs/0003-service-providers-and-composition.md', - 'docs/rfcs/0003-service-providers-and-composition.zh.md', - ], - ], - [ - 'docs/rfcs/0004-provenance-validation-and-diagnostics.i18n.yaml', - [ - 'docs/rfcs/0004-provenance-validation-and-diagnostics.md', - 'docs/rfcs/0004-provenance-validation-and-diagnostics.zh.md', - ], - ], -] -for (const [recordPath, paths] of pairs) { - const lines = read(recordPath).split(/\r?\n/u) - for (const path of paths) { - const hash = execFileSync('git', ['hash-object', `--path=${path}`, path], { - cwd: packageRoot, - encoding: 'utf8', - stdio: ['ignore', 'pipe', 'pipe'], - }).trim() - const name = path.split('/').at(-1) - if (!lines.includes(`${name}: ${hash}`)) fail(`${recordPath} is stale for ${path}`) - } -} - -const markdownFiles = publicFiles.filter(path => path.endsWith('.md')) -for (const path of markdownFiles) { - const source = read(path) - for (const match of source.matchAll(/\]\(([^)]+)\)/gu)) { - const target = match[1].trim().replace(/^<|>$/gu, '') - if (/^(?:https?:|mailto:|#)/u.test(target)) continue - const localPath = decodeURIComponent(target.split('#', 1)[0]) - if (!localPath) continue - if (!existsSync(resolve(packageRoot, dirname(path), localPath))) { - fail(`${path} links to missing ${localPath}`) - } - } -} - -process.stdout.write(`verify-fabric-docs: ${markdownFiles.length} Markdown files and ${pairs.length} bilingual pairs are consistent\n`) diff --git a/dsh-community-market/LICENSE b/dsh-community-market/LICENSE deleted file mode 100644 index bc9f0683c5..0000000000 --- a/dsh-community-market/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2026 Anywhere Labs - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/dsh-community-market/README.i18n.yaml b/dsh-community-market/README.i18n.yaml deleted file mode 100644 index 086a2e6a8e..0000000000 --- a/dsh-community-market/README.i18n.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Bilingual-pair consistency record. Both languages carry equal authority. -# Update both files and record their Git blob hashes after editing either side. -README.md: 84a230a88db0bf9c996b111e06494fab8d2690c4 -README.zh.md: 215e455073806716b378e838c3bba868fd8b3d86 diff --git a/dsh-community-market/README.md b/dsh-community-market/README.md deleted file mode 100644 index 84a230a88d..0000000000 --- a/dsh-community-market/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# DSH Community Market design draft - -[中文](README.zh.md) - -This directory currently holds product and technical design documents only. It has **no loadable Host, Client, or installer code** and is not part of the WorkDSH Desktop installer. Earlier experimental runtime code depended on old DSH versions and has been removed from the current product workspace. Any future Market implementation must be reviewed against the then-pinned official DSH version and its public interfaces and security boundaries. - -The draft covers plugin discovery, catalog selection, install confirmation, and uninstall. A catalog listing is not a security review or endorsement. Installing a third-party npm package runs code with the user's permissions. A future Market must not depend on the removed `desktopPnpm` or `desktopProfiles` services; it should use public DSH or WorkDSH Profile contracts available at implementation time. - -- [Catalog provider contract](docs/catalog-provider-contract.md) -- [Catalog adapter guide](docs/catalog-adapter-guide.md) -- [Install and uninstall design](docs/install-and-uninstall.md) -- [Interface design](docs/market-shell.md) -- [Security policy](SECURITY.md) -- [Desktop ownership boundaries](../docs/desktop-boundaries.md) - -Documents and examples use the [MIT License](LICENSE). Catalog providers are independent projects responsible for their own data and service policies. diff --git a/dsh-community-market/README.zh.md b/dsh-community-market/README.zh.md deleted file mode 100644 index 215e455073..0000000000 --- a/dsh-community-market/README.zh.md +++ /dev/null @@ -1,16 +0,0 @@ -# DSH Community Market 设计草案 - -[English](README.md) - -此目录目前只保存插件市场的产品和技术设计,**没有可加载的 Host、Client 或安装器代码**,也不会进入 WorkDSH Desktop 安装包。此前试验性的运行代码依赖旧版 DSH,已从当前产品工程中移除。实际开发市场功能时,需要重新以当时固定的官方 DSH 版本为兼容基线,并先审查接口与安全边界。 - -草案拟支持插件发现、来源选择、安装确认与卸载。目录结果不代表安全审核或推荐;安装第三方 npm 包意味着在用户权限下运行代码。市场设计不能依赖已经移除的 `desktopPnpm` 或 `desktopProfiles` 服务,应使用届时公开的 DSH/WorkDSH Profile 接口。 - -- [目录提供方合同](docs/catalog-provider-contract.zh.md) -- [目录适配器指南](docs/catalog-adapter-guide.zh.md) -- [安装与卸载设计](docs/install-and-uninstall.zh.md) -- [界面设计](docs/market-shell.zh.md) -- [安全策略](SECURITY.zh.md) -- [Desktop 归属约束](../docs/desktop-boundaries.md) - -文档和示例使用 [MIT License](LICENSE)。目录提供方是独立项目,各自负责其数据和服务策略。 diff --git a/dsh-community-market/SECURITY.i18n.yaml b/dsh-community-market/SECURITY.i18n.yaml deleted file mode 100644 index 661c86aa19..0000000000 --- a/dsh-community-market/SECURITY.i18n.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Bilingual-pair consistency record. Both languages carry equal authority. -# Update both files and record their Git blob hashes after editing either side. -SECURITY.md: 82543963fe104d0d772653baeb896ea80d8010a2 -SECURITY.zh.md: 0b586d89225e9fdbdbb88230118da6598a466295 diff --git a/dsh-community-market/SECURITY.md b/dsh-community-market/SECURITY.md deleted file mode 100644 index 82543963fe..0000000000 --- a/dsh-community-market/SECURITY.md +++ /dev/null @@ -1,41 +0,0 @@ -# Security Policy - -[中文说明](SECURITY.zh.md) - -This policy documents a historical Market proposal. The current Desktop does not ship this Market or its `desktopPnpm` service. - -## Trust model - -Catalog responses are untrusted remote data. A listing, provider badge, repository link, or **Installable** result is not a security review, maintainer verification, recommendation, or compatibility guarantee. - -Installed plugins and their dependency trees run locally with the user's permissions. The Market intentionally does not claim to inspect their code or dependencies for malicious behavior. - -## Package-operation boundary - -- Installation starts only after explicit user confirmation. -- The confirmation shows the Host-resolved npm package, npm `latest` stable version, and active Profile. -- Provider commands, scripts, HTML, headers, credentials, and package-manager argv are never accepted from catalog data. -- Automatic installation requires one valid npm package identity and a valid `dsh.bundle.patch` declaration in the official npm `latest` manifest. -- Source-provided versions and verification claims are not installation authority. -- Market package changes use only `desktopPnpm.run(argv)` and run one at a time. -- The Renderer submits source/item identities for install or an opaque Desktop `bundleId` for uninstall; it never chooses an arbitrary package name at execution time. -- Installed inventory comes from current Profile direct dependencies, so packages installed by other markets or the DSH CLI are visible. Removable direct dependencies offer uninstall only; Market exposes no enable or disable operation. -- Market creates no install receipt, install-specific snapshot, retry, cleanup, or rollback. Recovery is owned by Desktop's unified three-slot healthy-start checkpoints. -- A successful mutation may issue a short-lived one-shot restart grant. Restart remains an explicit user action. -- **Open DSH Terminal** carries an empty body and only opens Desktop's terminal; it never pastes or executes a displayed command. - -These rules constrain authority and identity. They do not make a third-party plugin safe. - -## Catalog sources - -Adding or selecting a source is an explicit local action. A remote manifest cannot enable itself, choose priority, supply adapter code, or supply credentials. - -Production source requests are HTTPS-only and credential-free. They enforce bounded redirects, timeouts, concurrency, decoded response size, item counts, nesting, and string lengths. Redirect and DNS targets are checked against loopback, private, link-local, and cloud-metadata destinations. JSON must satisfy the published schema before normalization. - -Exactly one source is selected for browsing. Source failure never silently selects a fallback, changes the active Profile, or blocks DSH Desktop startup. - -## Reporting a vulnerability - -For suspected vulnerabilities, first check the repository [Security page](https://github.com/techflag/workdsh/security) for an available private reporting option. If none is available, open a [GitHub issue](https://github.com/techflag/workdsh/issues) requesting a private channel without disclosing vulnerability details. Once a private channel is agreed, include the affected version or commit, operating system, reproduction steps, expected impact, and a minimal proof of concept that can be shared safely. - -Do not include secrets or personal data, and do not open a public issue for an unpatched vulnerability. Ordinary bugs, catalog corrections, and feature requests may use the public issue tracker. diff --git a/dsh-community-market/SECURITY.zh.md b/dsh-community-market/SECURITY.zh.md deleted file mode 100644 index 0b586d8922..0000000000 --- a/dsh-community-market/SECURITY.zh.md +++ /dev/null @@ -1,41 +0,0 @@ -# 安全策略 - -[English](SECURITY.md) - -本文记录历史 Market 设计。当前 Desktop 不包含该 Market,也没有 `desktopPnpm` 服务。 - -## 信任模型 - -目录响应是不可信远程数据。被目录收录、provider 徽章、仓库链接或显示在**可安装**中,都不代表安全审核、维护者身份验证、推荐或兼容性保证。 - -插件及其依赖树安装后会以用户权限作为本地代码运行。Market 不声称能够检查其中是否存在恶意行为。 - -## Package 操作边界 - -- 只有用户明确确认后才开始安装。 -- 确认框展示 Host 解析出的 npm package、npm `latest` 稳定版本和当前 Profile。 -- 不接受目录数据提供的命令、脚本、HTML、header、凭据或 package-manager argv。 -- 自动安装要求唯一合法的 npm package 身份,并要求 npm 官方 `latest` manifest 声明合法的 `dsh.bundle.patch`。 -- 来源提供的版本和验证结论不构成安装权限。 -- Market package 修改只使用 `desktopPnpm.run(argv)`,同一时间只执行一个。 -- Renderer 在安装时提交来源/条目身份,在卸载时提交 Desktop 返回的不透明 `bundleId`;执行时不能自行选择任意 package name。 -- 已安装清单来自当前 Profile 的直接依赖,因此其他市场或 DSH CLI 安装的 package 也会显示。可移除的直接依赖只提供卸载;Market 不暴露启用或禁用操作。 -- Market 不创建安装 receipt、安装专用快照、重试、清理或回滚。恢复统一由 Desktop 的三个健康启动 checkpoint 槽位负责。 -- 修改成功后可以签发短时、一次性重启许可,但重启始终需要用户明确操作。 -- **打开 DSH 终端**使用空 body,只负责打开 Desktop 终端,不会粘贴或执行界面中的命令。 - -这些规则限制操作权限和 package 身份,但不能让第三方插件变得安全。 - -## 目录来源 - -添加或选择来源是明确的本地操作。远程 manifest 不能自行启用、决定优先级、提供 adapter 代码或提供凭据。 - -生产环境来源请求仅允许 HTTPS 且不携带凭据,并限制重定向、超时、并发、解码后响应大小、条目数、嵌套和字符串长度。每次重定向和 DNS 目标都会检查 loopback、私有网络、link-local 和云 metadata 地址。JSON 必须先通过公开 Schema 再进入标准化。 - -同一时间只选择一个来源浏览。来源故障不会静默选择兜底、修改当前 Profile 或阻止 DSH Desktop 启动。 - -## 报告安全问题 - -发现可能的安全问题时,请先查看仓库的 [Security 页面](https://github.com/techflag/workdsh/security) 是否提供私密报告入口。若没有,可在 [GitHub Issues](https://github.com/techflag/workdsh/issues) 请求私密联系渠道,但不要公开漏洞细节。确定私密渠道后,再提供受影响版本或 commit、操作系统、复现步骤、预期影响,以及可安全分享的最小 proof of concept。 - -不要发送 secret 或个人数据,也不要为未修复漏洞创建公开 issue。普通 bug、目录修正和功能建议可以使用公开 issue tracker。 diff --git a/dsh-community-market/docs/catalog-adapter-guide.i18n.yaml b/dsh-community-market/docs/catalog-adapter-guide.i18n.yaml deleted file mode 100644 index 9b114f0106..0000000000 --- a/dsh-community-market/docs/catalog-adapter-guide.i18n.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Bilingual-pair consistency record. Both languages carry equal authority. -# Update both files and record their Git blob hashes after editing either side. -catalog-adapter-guide.md: cc56dcacf13076f0f3c16320c4372035007b8262 -catalog-adapter-guide.zh.md: 098dc0ed018f4fc58bc3bcc0d4449cf526312d34 diff --git a/dsh-community-market/docs/catalog-adapter-guide.md b/dsh-community-market/docs/catalog-adapter-guide.md deleted file mode 100644 index cc56dcacf1..0000000000 --- a/dsh-community-market/docs/catalog-adapter-guide.md +++ /dev/null @@ -1,194 +0,0 @@ -# Catalog integration and adapter guide - -[中文](catalog-adapter-guide.zh.md) - -Status: Implemented public v1 integration guide. The authoritative Schemas and compatibility rules are versioned by the [catalog provider contract](catalog-provider-contract.md). - -DSH Community Market is open to every catalog provider and user-owned source. Anyone can use path A immediately by publishing Schema-conforming public HTTPS JSON and sharing the manifest URL; no Market code change or partnership approval is required. Providers that need path B are welcome to propose a reviewed adapter collaboration for their existing public API. - -## Choose one integration path - -### A. Standard source: no Market code - -Use this path when the provider can publish two anonymous HTTPS JSON resources on one origin: - -1. a static [`catalog-source` manifest](schemas/catalog-source.schema.json); and -2. one GET `/v1/plugins` endpoint returning [`catalog-provider-page`](schemas/catalog-provider-page.schema.json). - -The user registers only the manifest URL. Start with the [minimal manifest](examples/catalog-source.example.json), [minimal query](examples/catalog-query.example.json), and [minimal page](examples/catalog-provider-page.minimal.example.json). The recommended minimum supports `q`, `category`, `cursor`, and `limit`, with example `defaultLimit` and `maxLimit` values of 50. That value is not a global cap: a standard source may declare values through the Schema safety maximum of 200. Repeated `category` values mean OR. Optional metadata and media can be added later without changing the transport. - -The Desktop Host normally builds a bounded local index, follows source cursors using the effective network page limit, and performs visible filtering and 50-item pagination locally. A reviewed adapter may instead forward a user query when the provider's unfiltered response is intentionally capped below its catalog total. The 1024Store adapter uses this exception for `q` and for exactly one `category`; results still pass through the same normalization, provenance, origin, and cursor boundaries. A standard source may return up to its declared effective page limit; 50 is the visible UI page size, not a universal provider-response cap. - -### B. Existing API: reviewed Host adapter - -Use this path when an existing API cannot return the standard page shape. Give the Market team: - -- the public endpoint documentation and response schema; -- representative success, empty, pagination, and error responses with secrets removed; -- stable field meanings and pagination rules; -- attribution, rate limits, and the provider's icon ownership semantics. - -The adapter is local TypeScript reviewed, tested, and released with Market. It uses the constrained Host HTTP client and returns a validated `CatalogSnapshot`. Open cooperation does not bypass review: a manifest or remote response can never supply JavaScript, mapping expressions, install commands, credentials, or adapter code. - -## Copyable adapter skeleton - -Place the adapted version in `src/adapters/example-provider.ts`. This skeleton assumes the reviewed API accepts `search`, repeated `tag` values with OR semantics, `after`, and `pageSize`. It uses a default page size of 50 and a reviewed maximum of 200. Change those explicit mappings and constants to match the documented API; do not create a remotely configurable mapper. The 1024Store adapter is intentionally explicit: discovery keeps its 50-item Client page while Installable requests 200 remote registry entries per batch and filters direct npm targets locally. - -```ts -import type { CatalogQuery, CatalogSnapshot } from '../contracts/index.js' -import type { CatalogAdapter, CatalogFetchContext } from '../contracts/types.js' -import { parseCatalogSnapshot } from '../contracts/validate.js' - -const ADAPTER_ID = 'market.example-provider-v1' -const ENDPOINT = 'https://catalog.example.org/api/plugins' -const ORIGIN = new URL(ENDPOINT).origin -const DEFAULT_PAGE_LIMIT = 50 -const MAX_PAGE_LIMIT = 100 - -interface RawPlugin { - readonly id: string - readonly title: string - readonly summary: string - readonly npm: string - readonly categories?: readonly string[] -} - -interface RawPage { - readonly plugins: readonly RawPlugin[] - readonly next?: string - readonly total?: number -} - -function readRawPage(value: unknown, effectiveLimit: number): RawPage { - if (value === null || typeof value !== 'object' || Array.isArray(value)) { - throw new Error('example provider response is not an object') - } - const record = value as Record - if (!Array.isArray(record.plugins) || record.plugins.length > effectiveLimit) { - throw new Error('example provider page is invalid') - } - const plugins = record.plugins.map((entry): RawPlugin => { - if (entry === null || typeof entry !== 'object' || Array.isArray(entry)) { - throw new Error('example provider item is invalid') - } - const item = entry as Record - if ( - typeof item.id !== 'string' - || typeof item.title !== 'string' - || typeof item.summary !== 'string' - || typeof item.npm !== 'string' - || (item.categories !== undefined - && (!Array.isArray(item.categories) || item.categories.some(value => typeof value !== 'string'))) - ) { - throw new Error('example provider item fields are invalid') - } - return { - id: item.id, - title: item.title, - summary: item.summary, - npm: item.npm, - ...(item.categories === undefined ? {} : { categories: item.categories as string[] }), - } - }) - if (record.next !== undefined && (typeof record.next !== 'string' || record.next.length === 0)) { - throw new Error('example provider cursor is invalid') - } - if (record.total !== undefined && ( - typeof record.total !== 'number' - || !Number.isSafeInteger(record.total) - || record.total < 0 - )) { - throw new Error('example provider total is invalid') - } - return { - plugins, - ...(record.next === undefined ? {} : { next: record.next as string }), - ...(record.total === undefined ? {} : { total: record.total as number }), - } -} - -function requestUrl(query: CatalogQuery, effectiveLimit: number): URL { - const url = new URL(ENDPOINT) - if (query.q !== undefined) url.searchParams.set('search', query.q) - for (const category of query.category ?? []) url.searchParams.append('tag', category) - if (query.cursor !== undefined) url.searchParams.set('after', query.cursor) - url.searchParams.set('pageSize', String(effectiveLimit)) - return url -} - -function snapshot( - raw: RawPage, - responseFinalUrl: string, - context: CatalogFetchContext, -): CatalogSnapshot { - return parseCatalogSnapshot({ - schemaVersion: '1.0.0', - source: { - sourceRecordId: context.source.sourceRecordId, - providerId: context.source.providerId, - adapterId: context.source.adapterId, - registrationKind: context.source.registrationKind, - fetchedAt: new Date().toISOString(), - finalUrl: responseFinalUrl, - }, - items: raw.plugins.map(item => ({ - id: item.id, - name: item.npm, - displayName: item.title, - summary: item.summary, - ...(item.categories === undefined ? {} : { categories: [...item.categories] }), - package: { registry: 'npm', name: item.npm }, - provenance: { - sourceRecordId: context.source.sourceRecordId, - providerId: context.source.providerId, - itemId: item.id, - }, - })), - page: { - ...(raw.next === undefined ? {} : { nextCursor: raw.next }), - ...(raw.total === undefined ? {} : { total: raw.total }), - }, - }) -} - -export const exampleProviderAdapter: CatalogAdapter = { - adapterId: ADAPTER_ID, - async fetch(query, context) { - const effectiveLimit = Math.min(query.limit ?? DEFAULT_PAGE_LIMIT, MAX_PAGE_LIMIT) - const response = await context.http.getJson( - requestUrl(query, effectiveLimit).href, - context.signal, - { allowedOrigin: ORIGIN }, - ) - if (new URL(response.finalUrl).origin !== ORIGIN) { - throw new Error('example provider response changed the reviewed origin') - } - return snapshot(readRawPage(response.value, effectiveLimit), response.finalUrl, context) - }, -} -``` - -Register it only in the static Host adapter map and add a reviewed built-in provider definition. This is a code review change, never provider data: - -```ts -import { exampleProviderAdapter } from '../adapters/example-provider.js' - -const adapters = new Map([ - [standardHttpAdapter.adapterId, standardHttpAdapter], - [dsh1024StoreAdapter.adapterId, dsh1024StoreAdapter], - [exampleProviderAdapter.adapterId, exampleProviderAdapter], -]) -``` - -## Review checklist - -- The endpoint and allowed origin are compile-time reviewed constants. -- The adapter parses and bounds the provider response before mapping it. -- Search, repeated-category OR filtering, cursor ownership, the default page size, the reviewed maximum, and response-over-limit rejection have explicit tests. -- Every item has a package or normalized repository identity and Host-injected provenance. -- Provider commands, HTML, scripts, credentials, and unknown fields never enter the snapshot. -- Optional icons use `context.media.register()` with exact reviewed hostnames; the Renderer receives only `assetRef`. -- Timeout, redirect, response-size, cancellation, and selected-source reset tests pass. -- The adapter and built-in provider definition ship in the same reviewed Market release. - -The Market team, not the remote provider, owns the adapter's behavior and release lifecycle. diff --git a/dsh-community-market/docs/catalog-adapter-guide.zh.md b/dsh-community-market/docs/catalog-adapter-guide.zh.md deleted file mode 100644 index 098dc0ed01..0000000000 --- a/dsh-community-market/docs/catalog-adapter-guide.zh.md +++ /dev/null @@ -1,194 +0,0 @@ -# 目录接入与 adapter 指南 - -[English](catalog-adapter-guide.md) - -状态:已实现的公开 v1 接入指南。权威 Schema 与兼容规则仍以[目录提供方契约](catalog-provider-contract.zh.md)为准。 - -DSH Community Market 对所有目录 provider 和用户自有来源开放。任何人都可以直接选择路径 A:发布符合 Schema 的公开 HTTPS JSON,并分享 manifest URL;无需修改 Market 代码,也无需先获得合作批准。需要路径 B 的 provider 也可以使用现有公开 API 提出经过审核的 adapter 合作接入。 - -## 选择一条接入路径 - -### A. 标准来源:无需编写 Market 代码 - -Provider 能在同一个 origin 发布两个匿名 HTTPS JSON 资源时,选择这条路径: - -1. 一份静态 [`catalog-source` manifest](schemas/catalog-source.schema.json); -2. 一个 GET `/v1/plugins` endpoint,返回 [`catalog-provider-page`](schemas/catalog-provider-page.schema.json)。 - -用户只登记 manifest URL。可以从[最小 manifest](examples/catalog-source.example.json)、[最小 query](examples/catalog-query.example.json)和[最小 page](examples/catalog-provider-page.minimal.example.json)开始。建议的最小能力只包含 `q`、`category`、`cursor` 和 `limit`,示例中的 `defaultLimit` 与 `maxLimit` 都是 50。这个值不是全局上限;标准来源可以在 Schema 安全上限 200 以内声明。重复 `category` 使用 OR 语义。以后可以增加可选元数据和媒体,不需要改变 transport。 - -Desktop Host 通常会建立有界本地索引,按照来源的有效网络 page limit 跟随 cursor,并在本地执行可见筛选和每页 50 条的分页。如果 provider 的无筛选响应刻意小于目录总量,经过审查的 adapter 可以转发用户查询。1024Store adapter 会针对 `q` 和恰好一个 `category` 使用这条例外;结果仍然通过相同的标准化、provenance、origin 和 cursor 边界。标准来源每次可以返回不超过其声明有效 page limit 的条目;50 是 UI 可见 page size,不是通用 provider response 上限。 - -### B. 已有 API:受审 Host adapter - -已有 API 无法返回标准 page 时,选择这条路径,并向 Market 团队提供: - -- 公开 endpoint 文档与 response schema; -- 已移除 secret 的成功、空结果、分页和错误 response 样例; -- 稳定字段语义和分页规则; -- 来源声明、rate limit 和 provider 的图标所有权语义。 - -Adapter 是本地 TypeScript,经过审核与测试后随 Market 发布。它只使用受限 Host HTTP client,并返回经过校验的 `CatalogSnapshot`。开放合作不会绕过审核:manifest 或远程 response 绝不能提供 JavaScript、mapping 表达式、install command、credential 或 adapter 代码。 - -## 可复制 adapter skeleton - -把适配后的版本放在 `src/adapters/example-provider.ts`。下面假设经审核的 API 接受 `search`、使用 OR 语义的重复 `tag`、`after` 和 `pageSize`;默认 page size 为 50,经过审核的最大值为 200。请根据 API 文档明确修改这些 mapping 和常量;不能做成由远程配置的 mapper。1024Store adapter 会明确区分两条链路:发现页保持 50 条 Client page,而“可安装”每批请求 200 条远程 registry 记录,并在本地筛选直接 npm 目标。 - -```ts -import type { CatalogQuery, CatalogSnapshot } from '../contracts/index.js' -import type { CatalogAdapter, CatalogFetchContext } from '../contracts/types.js' -import { parseCatalogSnapshot } from '../contracts/validate.js' - -const ADAPTER_ID = 'market.example-provider-v1' -const ENDPOINT = 'https://catalog.example.org/api/plugins' -const ORIGIN = new URL(ENDPOINT).origin -const DEFAULT_PAGE_LIMIT = 50 -const MAX_PAGE_LIMIT = 100 - -interface RawPlugin { - readonly id: string - readonly title: string - readonly summary: string - readonly npm: string - readonly categories?: readonly string[] -} - -interface RawPage { - readonly plugins: readonly RawPlugin[] - readonly next?: string - readonly total?: number -} - -function readRawPage(value: unknown, effectiveLimit: number): RawPage { - if (value === null || typeof value !== 'object' || Array.isArray(value)) { - throw new Error('example provider response is not an object') - } - const record = value as Record - if (!Array.isArray(record.plugins) || record.plugins.length > effectiveLimit) { - throw new Error('example provider page is invalid') - } - const plugins = record.plugins.map((entry): RawPlugin => { - if (entry === null || typeof entry !== 'object' || Array.isArray(entry)) { - throw new Error('example provider item is invalid') - } - const item = entry as Record - if ( - typeof item.id !== 'string' - || typeof item.title !== 'string' - || typeof item.summary !== 'string' - || typeof item.npm !== 'string' - || (item.categories !== undefined - && (!Array.isArray(item.categories) || item.categories.some(value => typeof value !== 'string'))) - ) { - throw new Error('example provider item fields are invalid') - } - return { - id: item.id, - title: item.title, - summary: item.summary, - npm: item.npm, - ...(item.categories === undefined ? {} : { categories: item.categories as string[] }), - } - }) - if (record.next !== undefined && (typeof record.next !== 'string' || record.next.length === 0)) { - throw new Error('example provider cursor is invalid') - } - if (record.total !== undefined && ( - typeof record.total !== 'number' - || !Number.isSafeInteger(record.total) - || record.total < 0 - )) { - throw new Error('example provider total is invalid') - } - return { - plugins, - ...(record.next === undefined ? {} : { next: record.next as string }), - ...(record.total === undefined ? {} : { total: record.total as number }), - } -} - -function requestUrl(query: CatalogQuery, effectiveLimit: number): URL { - const url = new URL(ENDPOINT) - if (query.q !== undefined) url.searchParams.set('search', query.q) - for (const category of query.category ?? []) url.searchParams.append('tag', category) - if (query.cursor !== undefined) url.searchParams.set('after', query.cursor) - url.searchParams.set('pageSize', String(effectiveLimit)) - return url -} - -function snapshot( - raw: RawPage, - responseFinalUrl: string, - context: CatalogFetchContext, -): CatalogSnapshot { - return parseCatalogSnapshot({ - schemaVersion: '1.0.0', - source: { - sourceRecordId: context.source.sourceRecordId, - providerId: context.source.providerId, - adapterId: context.source.adapterId, - registrationKind: context.source.registrationKind, - fetchedAt: new Date().toISOString(), - finalUrl: responseFinalUrl, - }, - items: raw.plugins.map(item => ({ - id: item.id, - name: item.npm, - displayName: item.title, - summary: item.summary, - ...(item.categories === undefined ? {} : { categories: [...item.categories] }), - package: { registry: 'npm', name: item.npm }, - provenance: { - sourceRecordId: context.source.sourceRecordId, - providerId: context.source.providerId, - itemId: item.id, - }, - })), - page: { - ...(raw.next === undefined ? {} : { nextCursor: raw.next }), - ...(raw.total === undefined ? {} : { total: raw.total }), - }, - }) -} - -export const exampleProviderAdapter: CatalogAdapter = { - adapterId: ADAPTER_ID, - async fetch(query, context) { - const effectiveLimit = Math.min(query.limit ?? DEFAULT_PAGE_LIMIT, MAX_PAGE_LIMIT) - const response = await context.http.getJson( - requestUrl(query, effectiveLimit).href, - context.signal, - { allowedOrigin: ORIGIN }, - ) - if (new URL(response.finalUrl).origin !== ORIGIN) { - throw new Error('example provider response changed the reviewed origin') - } - return snapshot(readRawPage(response.value, effectiveLimit), response.finalUrl, context) - }, -} -``` - -只在 Host 的静态 adapter map 中登记它,并添加经过审核的内置 provider 定义。这是一次代码评审变更,绝不是 provider 数据: - -```ts -import { exampleProviderAdapter } from '../adapters/example-provider.js' - -const adapters = new Map([ - [standardHttpAdapter.adapterId, standardHttpAdapter], - [dsh1024StoreAdapter.adapterId, dsh1024StoreAdapter], - [exampleProviderAdapter.adapterId, exampleProviderAdapter], -]) -``` - -## 评审清单 - -- Endpoint 与 allowed origin 是编译期受审常量。 -- Adapter 在 mapping 前解析 provider response 并执行边界限制。 -- 搜索、重复分类 OR 过滤、cursor 归属、默认 page size、受审最大值和超限 response 拒绝都有明确测试。 -- 每个条目都有 package 或标准化 repository identity,以及由 Host 注入的 provenance。 -- Provider command、HTML、script、credential 和未知字段都不能进入 snapshot。 -- 可选图标通过 `context.media.register()` 登记精确受审 hostname;Renderer 只能获得 `assetRef`。 -- Timeout、redirect、response size、取消和已选来源重置测试通过。 -- Adapter 与内置 provider 定义随同一份经过审核的 Market release 发布。 - -Adapter 的行为和发布生命周期由 Market 团队负责,而不是远程 provider。 diff --git a/dsh-community-market/docs/catalog-provider-contract.i18n.yaml b/dsh-community-market/docs/catalog-provider-contract.i18n.yaml deleted file mode 100644 index 34a94158a2..0000000000 --- a/dsh-community-market/docs/catalog-provider-contract.i18n.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Bilingual-pair consistency record. Both languages carry equal authority. -# Update both files and record their Git blob hashes after editing either side. -catalog-provider-contract.md: 99122fe40505af1065a4f91c3842222599e90487 -catalog-provider-contract.zh.md: f917eb08da21627064e395917b638093337c75d7 diff --git a/dsh-community-market/docs/catalog-provider-contract.md b/dsh-community-market/docs/catalog-provider-contract.md deleted file mode 100644 index 99122fe405..0000000000 --- a/dsh-community-market/docs/catalog-provider-contract.md +++ /dev/null @@ -1,417 +0,0 @@ -# Catalog provider contract - -[中文](catalog-provider-contract.zh.md) - -Status: **Implemented public v1 contract.** The versioned Schemas, generated types, strict validation, source persistence, constrained network and media boundaries, standard HTTP adapter, reviewed DSH 1024Store and dshfind adapters, complete local indexing, and loadable Host/Client entries are implemented and tested in DSH Desktop. This document and its fixtures are the public interoperability contract for `manifestVersion` and `schemaVersion` `1.x`. - -## Decision summary - -- DSH Community Market has **no default, preferred, or fallback catalog source**. -- Users may save several source registrations, but explicitly select exactly one source for the current browsing session. -- A user may add any source that implements this contract. Adding a source does not install a plugin and does not grant that source execution access. -- The catalog ecosystem is open: any person, community, or service may publish a conforming source and any user may register its manifest URL. No Market code change or partnership approval is required for the standard path. -- Providers with an existing public API that cannot emit the standard page shape may propose a reviewed adapter integration. Cooperation adds local, tested Market code; it never allows a provider to send executable adapter code. -- DSH 1024Store is one of the catalog providers currently cooperating with this project. A reviewed built-in adapter is included; it does not select or fall back to 1024Store automatically. -- dshfind is another optional cooperating provider. Its reviewed adapter accepts only unambiguous structured npm evidence for structural install candidacy; other entries remain browse-only. It is neither selected by default nor used as a preferred, recommended, or fallback source. -- A source being bundled as a choice or supported by an adapter does not mean that Anywhere Labs has recommended, audited, or endorsed the source or the plugins it lists. -- Every provider is converted into one normalized model before data reaches the market UI or installation boundary. - -These are product and trust decisions, not implementation defaults that a later team may change for convenience. - -## Scope - -This contract defines: - -- how a standard catalog describes itself with a static source manifest; -- how the standard HTTP endpoint is queried; -- how a built-in adapter represents a cooperating provider whose wire format is different; -- the normalized snapshot consumed by the market; -- saved-source registration, single-source selection, provenance, pagination, and failure behavior; -- the minimum network and data-safety boundary; -- the implemented v1 capability checklist and verified acceptance matrix. - -It does not define catalog governance, plugin review, account systems, payments, arbitrary authenticated sources, or package installation commands. Installation remains a separate user-confirmed operation owned by the market Host and the active-profile services. - -## Terms - -| Term | Meaning | -| --- | --- | -| Catalog source | A provider of plugin metadata. It is data, not executable plugin code. | -| Source manifest | A static JSON declaration describing a standard source and its supported query features. | -| Provider ID | A provider-claimed stable ID. It is attribution data, not local authority. | -| Source record ID | An opaque UUID generated by the Host for one local source registration. Cache, cursors, selection, and item identity use this ID. | -| Adapter | Reviewed local code that requests a provider and converts its response into the normalized model. | -| Standard adapter | The built-in adapter for sources that implement this contract directly. It must not contain provider-specific behavior. | -| Provider adapter | A reviewed built-in adapter for a cooperating provider with a different existing API. | -| Provider page | The untrusted wire response returned by a standard source before Host provenance is added. | -| Normalized snapshot | The only catalog data shape the selected-source session, UI, and installation candidate resolver may consume. | -| Remote icon candidate | An optional provider-declared HTTPS image in `media.icon`. It is untrusted input for the Host media resolver, never a Renderer URL. | -| Asset reference | An opaque Host-managed token in a normalized `media.icon`. The Renderer may consume the token through the Host asset boundary but cannot turn it into an arbitrary network request or filesystem path. | -| Local source settings | The saved registrations and optional selected source record. These values never come from a remote manifest. | - -## Source selection is explicit - -Source selection is local user state. Every registration receives a Host-generated `sourceRecordId`; a remote `providerId` can never replace it or earn a built-in/partner badge by matching a known string. The source management UI must support: - -1. adding a standard source from a manifest URL; -2. choosing a known built-in provider adapter; -3. selecting exactly one saved source for browsing; -4. switching the selected source; -5. removing a user-added source; -6. seeing the provider name, attribution, endpoint host, adapter type, and last result before trusting its data. - -No manifest may declare itself selected, trusted, official, recommended, or a fallback. The source schema intentionally rejects `selected`, `enabled`, `order`, `priority`, authentication material, custom headers, scripts, and install commands. A built-in source and a user-added source may claim the same `providerId` without sharing identity, cache, cursor, trust, or presentation. The local adapter registration—not that claim—controls whether a reviewed partner badge is shown. - -On first run, the market may present available source choices, including cooperating providers, but it must not preselect them. With no selected source, the UI shows an explicit “choose or add a source” state, sends no catalog requests, and never silently switches to DSH 1024Store or any other provider. - -Switching the selected source cancels the old source's in-flight catalog work and starts a fresh browsing session. The visible list, search text, selected categories, discovered category choices, pagination cursor, and current errors are reset before the new source is fetched. - -The private persistence shape is not part of the provider contract. It stores Host-generated source records plus one local selection marker. Existing settings may encode that marker on a record for migration compatibility; the invariant is what matters: catalog I/O resolves at most one saved record, never a list of concurrently active sources. Exactly one of `manifestUrl` or `builtInProviderKey` is present on each record. A user-added record retains the validated registration-time manifest so the UI can show its name, attribution, endpoint, and adapter type before selection; the standard adapter still refetches and revalidates the remote manifest for every catalog read. - -## Three contract layers - -```mermaid -flowchart LR - Settings["Local source settings
zero or one selected source"] --> Registry["Saved source registry"] - Manifest["Standard source
manifest + GET /v1/plugins"] --> Standard["Standard adapter"] - Partner["Cooperating provider
provider-specific API"] --> Builtin["Reviewed built-in adapter"] - Registry --> Standard - Registry --> Builtin - Standard --> Validate["Validate and normalize"] - Builtin --> Validate - Validate --> Session["Selected-source session
provenance preserved"] - Session --> UI["Market UI and confirmed install boundary"] -``` - -### Layer 1: source manifest - -A standard source publishes a static manifest validated by [`catalog-source.schema.json`](schemas/catalog-source.schema.json). Its public v1 shape identifies: - -- `manifestVersion`, fixed to `1.0.0` for v1; -- a provider-claimed `providerId`, human-readable `name`, and optional description/homepage; -- provider attribution with a name, URL, and optional notice; -- a public `https-json` GET endpoint; -- the supported query parameters, default and maximum page size, and supported sort values. - -The manifest describes provider capability; it does not control local policy. Public v1 sources are anonymous: the contract has no bearer token, cookies, request headers, secret fields, executable mapping, or dynamic JavaScript. - -The source manifest URL and catalog endpoint are distinct. Adding the manifest URL is an explicit user action. The Host generates a fresh `sourceRecordId`, validates and stores a registration-time copy of the manifest with that local user-added record, exposes its disclosure fields in source management, and does not select it until the user chooses it. - -For direct standard integration, the user registers only the manifest URL. The smallest recommended manifest uses one public GET endpoint, advertises only `q`, `category`, `cursor`, and `limit`, sets both example page limits to 50, and leaves `sorts` empty. Fifty is a convenient starter value, not a standard-source ceiling: a manifest may declare limits through the Schema safety maximum of 200. Capability, sort, locale, icons, and richer display fields remain optional extensions. See the [minimal source manifest](examples/catalog-source.example.json) and [minimal provider page](examples/catalog-provider-page.minimal.example.json). - -Registration also pins the provider claim and network origin. On every fetch, the manifest `providerId` must still equal the value saved in the local source record. The user-approved manifest URL, the manifest request's final URL, `transport.endpoint`, and the provider-page request's final URL must all remain on the same credential-free HTTPS origin. Public v1 network URLs and manifests use only standard HTTPS port 443; custom ports are not part of the standard-source contract. Same-origin redirects are allowed; crossing to another origin is rejected even when both origins use HTTPS. A deployment that requires a separate API origin or port uses the reviewed provider-adapter path unless a later contract revision defines that relationship explicitly. - -### Layer 2: adapter - -An adapter is a local, typed boundary with one responsibility: fetch a source under Host limits and return a normalized snapshot. - -```ts -interface CatalogAdapter { - readonly adapterId: string - fetch(query: CatalogQuery, context: CatalogFetchContext): Promise -} -``` - -`CatalogFetchContext` exposes an `AbortSignal`, a constrained HTTP client, the validated source identity, configured limits, and a narrow Host media registrar that accepts reviewed candidates and returns opaque asset references. It does not expose Electron globals, arbitrary filesystem access, a shell, ambient credentials, or package-manager execution. - -There are only two supported integration paths: - -1. **Standard source:** publish the static manifest and return the standard provider-page JSON from its single endpoint. No Market code is required. -2. **Reviewed adapter:** when an existing public API cannot return the standard shape, provide its API schema and representative responses to the Market maintainers. A local adapter is written, reviewed, tested, and released with Market code. A remote response can never inject JavaScript, mapping expressions, or adapter code. - -The [catalog adapter guide](catalog-adapter-guide.md) provides the decision checklist and a complete TypeScript skeleton for the second path. - -The standard adapter maps the query contract below to the standard endpoint, validates the wire response against [`catalog-provider-page.schema.json`](schemas/catalog-provider-page.schema.json), and only then creates a normalized snapshot. Provider adapters may translate names, pagination, categories, media candidates, or legacy response fields, but must return the same normalized model and preserve provider attribution. Provider adapters are compiled and reviewed with the market package; a manifest or response can never download or supply adapter code. - -Provider input never supplies Host provenance. After a response succeeds, the adapter injects the local `sourceRecordId`, locally registered `adapterId` and registration kind, Host-observed `fetchedAt`, and the validated final response URL. Provider generation time and revision remain explicitly labeled provider claims. - -### Layer 3: normalized model - -Every successful result is validated against [`catalog-snapshot.schema.json`](schemas/catalog-snapshot.schema.json) before it can be cached, displayed, or considered for installation. - -A public v1 normalized snapshot contains: - -- `schemaVersion: "1.0.0"`; -- Host-generated source record identity, provider claim, local adapter identity, registration kind, observed fetch time, and final URL; -- optional provider generation time and revision, clearly separated from Host observations; -- normalized plugin items; -- pagination metadata with an optional opaque next cursor and total. - -Each item has stable source-local identity, display text, and explicit Host-injected provenance. It may identify an npm package, a canonical repository plus optional subdirectory, or both. It may also contain bounded descriptive metadata, categories, capabilities, compatibility claims, update time, and Host-resolved media. It never contains an install command, shell fragment, HTML, script, executable callback, remote media URL, or filesystem path. - -Within one provider page, every item `id` must be unique. The adapter rejects duplicate IDs before provenance is injected. In the normalized snapshot, every `provenance.itemId` must exactly equal its containing item `id`. - -The Host must verify that the snapshot and every item provenance carry the local record's `sourceRecordId` and provider claim. A provider cannot supply those Host fields, impersonate a built-in registration, or collide with another registration's cache and cursor by choosing the same `providerId`. - -### Media and icon resolution - -Media has two deliberately different representations: - -- A standard provider page may optionally declare `media.icon: { url, alt? }`. The URL is an untrusted HTTPS candidate for a plugin-owned icon. It is not display-ready data. -- A normalized snapshot may optionally contain `media.icon: { assetRef, role, alt? }`. `assetRef` is an opaque Host-managed token; it is neither a remote URL nor a filesystem path. Current Host tokens match `mktimg_` plus 32 URL-safe characters; providers never generate or return this token. `role` is either `plugin-icon` or `publisher-avatar`. - -Before emitting a normalized snapshot, the Host validates and registers a remote candidate with a dedicated media boundary and replaces it with a new `assetRef`. The image bytes are fetched lazily only when the Renderer requests that reference. For a standard source, the candidate must share the final provider-page response origin and every redirect must remain on a Host-approved exact hostname; a provider that uses a separate image CDN must serve or proxy its standard v1 icons from the catalog origin. The asset service applies the same destination and redirect protections as catalog requests, enforces image media types and byte/pixel budgets, decodes the image, and returns only a safe local representation. A failed or invalid image makes that reference unavailable without failing an otherwise valid catalog item, and the Renderer uses its local placeholder. The Renderer never receives or requests the provider URL directly. - -Host catalog caches, registered media references, decoded-image caches, and concurrent image work are bounded. Deselecting or removing a source cancels its in-flight catalog work and revokes that session's media references only after the local source change has been persisted. A saved source's last-good cache remains isolated under its own record and query and is never displayed as another source's result. - -Within the selected source record, the media presentation order is deterministic: - -1. a valid direct provider `media.icon`, normalized with `role: "plugin-icon"`; -2. a reviewed provider-adapter fallback, normalized with its truthful role, such as `role: "publisher-avatar"`; -3. a local placeholder generated by the client when the normalized item has no media. - -An adapter must not label an owner or organization avatar as a plugin icon. This priority is applied when the Host chooses which candidate to register; a later retrieval failure falls back to the local placeholder rather than contacting a second remote candidate. Media from a previously selected source never carries into the new selected-source session. - -## Standard HTTP source - -A standard v1 source exposes the absolute HTTPS endpoint declared in its manifest. Its path is `/v1/plugins` (or ends with that path when the service is mounted below a fixed prefix), with no embedded query or fragment: - -```text -GET https://catalog.example.org/v1/plugins?q=memory&category=utility&limit=50 -Accept: application/json -``` - -The Host first builds and validates a [`CatalogQuery`](schemas/catalog-query.schema.json), then serializes only parameters listed in the source manifest's `query.supported` array. Missing values are omitted rather than serialized as empty strings or `null`. - -| Parameter | Cardinality | Public v1 meaning | -| --- | --- | --- | -| `q` | zero or one | Trimmed search text, 1–200 characters. Matching and ranking are provider-defined. | -| `category` | zero or more | Stable category IDs. Repeated values mean “match any requested category”. Duplicates are invalid. | -| `capability` | zero or more | Fabric/host capability IDs. Repeated values mean the item must declare all requested capabilities. Duplicates are invalid. | -| `cursor` | zero or one | Opaque continuation value returned by the same source for the same effective filters and sort. Maximum 2048 characters. | -| `limit` | zero or one | Integer from 1 through 200. The normalized Host query defaults to 50; the effective requested value cannot exceed the manifest's `maxLimit`. | -| `sort` | zero or one | One of `relevance`, `updated`, `name`, or `downloads`, and also declared by the source manifest. | -| `locale` | zero or one | A BCP 47-like language tag such as `zh-CN` or `en`. It is a preference, not permission to omit stable IDs. | - -`category` and `capability` are serialized as repeated query parameters. All other fields are single-valued. Query text and values are URL-encoded as data; they are never concatenated into a URL, header, or command without the platform URL builder. - -Repeated `category` values are a multi-select OR filter for consumers that send provider-side filters: an item matches when it belongs to any selected category. The standard adapter sends this field only when the source manifest advertises `category` in `query.supported`; otherwise it omits the field instead of inventing provider semantics. - -The normalized Host query default and the provider default are separate. A valid consumer may request any value through 200, and the Host reduces it to the manifest's `maxLimit` when needed. The response must not contain more items than that effective requested limit. When a source does not support `limit`, the Host omits the parameter and accepts up to the source's declared `defaultLimit`. A manifest must keep `defaultLimit` less than or equal to `maxLimit`, and both values remain bounded by 200. - -A provider cursor is scoped to one selected source and one effective wire query. The Host never sends a cursor from one source to another or reuses it after that wire query changes. - -For a standard source, the current Desktop scans the bounded complete source by sending only supported scan fields such as `cursor`, `limit`, and locale preference, following `page.nextCursor` until exhaustion. It does not send the user's search text or selected categories during this scan. The reviewed 1024Store adapter instead forwards discovery queries to v2 remote pages and requests 200 registry entries per Installable batch before retaining only direct npm targets. - -For adapters without reviewed remote-query support, search, sorting, multi-category OR filtering, category enumeration, and pagination run over a bounded complete local index. Reviewed adapters may instead forward those operations to the provider. In both cases the Renderer receives at most one visible page and only Host-owned opaque cursors. - -Only a successful JSON response that passes the provider-page schema is accepted from a standard source. The adapter then injects Host provenance and validates the normalized snapshot schema. A timeout, non-200 response, wrong content type, oversized body, parse error, unsupported schema version, or either validation error fails that source request without affecting application startup. A standard response containing more items than the effective `limit` is rejected. - -## Selected-source browsing session - -Saved sources remain independent, but the product reads only the selected one: - -- At most one saved source record is selected, and only that source receives catalog requests. -- The selected source has its own timeout, cancellation, opaque cursor, loading state, error state, and any adapter-appropriate bounded cache. -- Standard-source network pages follow the effective requested limit or declared `defaultLimit`, through the safety maximum of 200; visible discovery pages contain at most 50 items. -- The 1024Store adapter requests v2 pages remotely, forwards supported query parameters, normalizes only the requested page, and exposes a Host-owned cursor for the next page. -- Optional catalog metadata applies to adapters that build a bounded complete index and reports when that scan finished (`scannedAt`), when its cache expires (`expiresAt`), an optional consistent source revision (`providerRevision`), and whether the index was freshly scanned or reused (`cacheStatus`). -- A failure stays attached to the selected source and offers Retry; the Host never falls back to or silently requests another saved source. -- Explicit refresh bypasses the relevant catalog HTTP cache and invalidates any applicable local index. Switching or removing the selected source cancels its in-flight work and clears the browsing session without restarting DSH. -- Every card, detail view, search result, and install confirmation keeps visible attribution for the selected source. - -The canonical item identity is the pair `{ sourceRecordId, itemId }`. Two registrations—even for the same provider—remain distinct and may legitimately describe the same plugin differently. The selected-source UI does not group or merge records across saved sources. Switching sources replaces the browsing session, and no source silently overwrites another source's cache or identity. A name, repository name, or similar description alone is never enough to deduplicate items within the selected source. - -## DSH 1024Store cooperation - -[DSH 1024Store](https://github.com/imsai-sh/awesome-deepseek-harness-plugins) is one of the providers currently cooperating with DSH Community Market. Its existing registry API does not need to adopt the standard wire shape. The integration uses the public reviewed-adapter path and ships as a built-in provider adapter that: - -- requests the provider's current paginated `/api/v2/plugins` API under the same Host network limits and never uses the frozen 500-item v1 compatibility feed as the directory; -- maps its complete v2 category facets and paged plugin metadata into the normalized response; -- treats the provider item `id` only as source-local identity and derives canonical GitHub repository and publisher identity from the validated repository URL, so repository renames or transfers do not silently point at the old name; -- because the current 1024Store dataset has no direct plugin icon, derives a GitHub owner/avatar candidate only as a reviewed fallback, resolves it through the Host media boundary, and labels the result `role: "publisher-avatar"`; -- sends ordinary discovery, search, sort, and single-category queries to v2 pages; multi-category OR queries merge bounded provider-ranked prefixes behind an opaque Host cursor; -- filters direct npm targets from each requested v2 page for Installable and carries the opaque provider cursor forward, so neither Host nor Renderer receives the complete directory at once; -- injects and validates DSH 1024Store provenance and attribution; -- never treats remote command text or install hints as executable input; the adapter accepts only an exact inert `dsh plugin --profile … add ` shape as package-name evidence, discards the command, and lets installation preview resolve npm `latest` independently; -- reports the selected source as unavailable when the provider fails or returns invalid data, without falling back to another saved source. - -This relationship makes 1024Store a supported source choice. It does **not** make it the default, preferred, official, recommended, audited, or fallback source. The adapter does not auto-select it, and an empty or failed selection never triggers a hidden request to it. Its catalog remains an independent project, and catalog inclusion is not a security review of any listed plugin. - -## dshfind cooperation - -[dshfind](https://dshfind.com) is an optional cooperating source with a reviewed built-in adapter for its public REST API. The adapter uses only the compiled-in `https://api.dshfind.com` origin and public anonymous requests. It requests page 1 with `per_page=100`, records the returned `data_version`, and sends that exact value on every later page. A `409 stale_data`, inconsistent version/total, invalid page, or incomplete traversal rejects the complete scan; no partial index is published. Search, category filtering, category enumeration, sorting, and 50-item UI pagination are then performed against the complete local index without sending those interactions back to dshfind. - -dshfind documents an anonymous quota of 30 requests per minute with a burst of 10, while its current catalog requires more than 30 pages at 100 items per page. The adapter therefore uses a fixed sequential delay below the published sustained rate and makes the slower first synchronization visible as source loading rather than parallelizing around the provider limit. A rate-limit response rejects the whole scan and the ordinary source Retry action may start it again. A completed local index remains subject to the ordinary bounded cache; explicit refresh starts a new consistent full scan. - -The dshfind response may contain `install.cmd`, other installation claims, and `install.methods`. Command text and ordinary claims are not execution authority: the adapter discards `install.cmd` before normalization, never displays or executes it, and never parses package identity from it. The adapter may emit a structured npm package identity and an informational provider version, but automatic install preview ignores that provider version and resolves the official npm `latest` manifest. Preview requires matching npm identity, an exact stable latest version, a valid DSH bundle declaration, and active-Profile availability. - -The adapter may normalize bounded plain-text identity, description, tags/category, update time, and a canonical credential-free `https://github.com/owner/repository` link. The current API exposes no plugin icon or README field. Any owner-avatar fallback must be labelled `publisher-avatar` and resolved through the Host media boundary; the adapter does not fetch or render remote README content. dshfind scores, grades, `official`/featured labels, risk labels, and installation conclusions remain provider-owned operational claims and never become an Anywhere Labs security review, recommendation, or verification signal. - -This cooperation makes dshfind visible as an optional partner choice only. It does **not** make it default, preferred, official, recommended, audited, or a fallback, and source failure never causes a hidden switch to another provider. - -## Installation boundary - -Catalog browsing and plugin installation are separate operations: - -- Fetching a manifest or snapshot is read-only and never invokes pnpm, DSH, a shell, or a lifecycle script. -- Remote data cannot supply an install command, custom package-manager arguments, environment variables, or a working directory. -- A browse record is not yet an install target. The current path requires one unambiguous valid npm package identity. -- Preview resolves npm `latest` and requires matching identity, an exact stable version, a valid DSH bundle declaration, and active-Profile availability. -- Execution consumes a one-shot preview and rejects changed catalog or Profile identity. -- The final confirmation shows the exact source record, pinned npm package and version, active profile, and local-code warning. -- Installation begins only after that explicit user gesture and uses the existing managed active-profile service. - -Supporting a source means its metadata can be browsed. It does not grant that source the ability to install, update, enable, or execute a plugin. - -## Security requirements - -### Network boundary - -- Production manifest and catalog URLs must use HTTPS. Reject credentials in the URL, fragments, nonstandard schemes, and endpoints with an embedded query. -- Validate every redirect hop, cap the redirect count, reject HTTPS downgrade, and apply all address checks again at every hop. -- Block loopback, private, link-local, multicast, unspecified, carrier-grade NAT, and cloud metadata destinations after DNS resolution. Protect the connection against DNS rebinding. A local-development override must be explicit, visible, and unavailable in production builds. -- Do not attach ambient cookies, authorization headers, client certificates, or provider-supplied custom headers. Public v1 standard sources are anonymous. -- Apply connect, first-byte, and total deadlines plus `AbortSignal` cancellation. -- Limit compressed and decoded response sizes, item count, pagination depth, string lengths, arrays, and URL lengths. Limits must be constants covered by tests; schema maxima remain authoritative for data fields. -- Require a JSON media type for catalog responses, decode once, and validate before caching. The catalog loader never follows discovered URLs; only the explicit Host media resolver may process a validated provider-page `media.icon.url` under its separate image limits. - -The public v1 runtime budgets are part of the provider contract, not implementation hints: - -| Boundary | Body and redirect budget | Deadlines | Additional rules | -| --- | --- | --- | --- | -| Standard source manifest and provider page | At most 2 MiB per JSON response and at most 3 redirects | 8 s connect, 12 s first-byte, 30 s total | These limits apply independently to the manifest request and each catalog request. | -| DSH 1024Store built-in adapter | At most 2 MiB per v2 JSON page and at most 3 redirects | 8 s connect, 12 s first-byte, 30 s total | Discovery and Installable both retain the remote page boundary; the complete registry is not returned to the Client. | -| Icon asset service | At most 2 MiB per image response and at most 2 redirects | 8 s connect, 12 s first-byte, 30 s total | Input must be a single-frame PNG, JPEG, or WebP image with at most `16 * 1024 * 1024` decoded pixels. The Host emits a metadata-free 128 × 128 PNG. | - -The Host performs catalog I/O only for the selected source. The icon asset service performs at most two network-and-decode jobs at once. Its limit is global to one Market plugin generation. - -### Data and renderer boundary - -- Use strict schemas with unknown object properties rejected. Unknown major versions fail closed. -- Treat names, descriptions, publisher claims, notices, and all other remote strings as untrusted plain text. Never inject them as HTML or Markdown with raw HTML enabled. -- Canonicalize package and repository identity before grouping or installation. Reject ambiguous, credential-bearing, or unsupported repository URLs. -- Do not load remote scripts, adapter definitions, stylesheets, iframes, or executable mappings from a source manifest or snapshot. Remote icon candidates are fetched only by the Host media resolver; a normalized snapshot contains only opaque `assetRef` values. -- Reject or visibly neutralize control characters and bidirectional text controls in display and confirmation fields. Open external HTTPS links only after a user gesture. -- Keep raw bodies, local paths, environment variables, credentials, and command internals out of user-facing errors and telemetry. -- Record source attribution and validation outcome separately from plugin trust. Schema validity proves shape only, not safety or authorship. - -### Local state boundary - -- Adding, selecting, switching, clearing, or removing a source requires an explicit user action. -- Remote manifests cannot modify local source settings or add another source. -- Persist source settings separately from cached remote data. A cache refresh cannot change the local selected-source marker. -- Scope cache keys and cursors by local `sourceRecordId` and effective query; never reuse a successful response as another source record's response. - -## Versioning and schema authority - -The public v1 schemas use JSON Schema Draft 2020-12: - -- [`catalog-source.schema.json`](schemas/catalog-source.schema.json) is authoritative for source manifests. -- [`catalog-query.schema.json`](schemas/catalog-query.schema.json) is authoritative for the normalized query object and parameter bounds. -- [`catalog-provider-page.schema.json`](schemas/catalog-provider-page.schema.json) is authoritative for the untrusted standard HTTP wire response. -- [`catalog-snapshot.schema.json`](schemas/catalog-snapshot.schema.json) is authoritative for normalized responses. - -Implementations must compile these schemas with Draft 2020-12 format assertion enabled and complete URI, date-time, and UUID format validation. `format` is a validation requirement here, not documentation-only annotation. Semantic checks still apply where relationships span fields, including `defaultLimit <= maxLimit` and non-empty `sorts` when `sort` is advertised. - -### Copyable v1 fixtures - -Provider and adapter authors can run compatibility tests from the matching [minimal source manifest](examples/catalog-source.example.json), [minimal query](examples/catalog-query.example.json), [minimal provider page](examples/catalog-provider-page.minimal.example.json), richer [provider page with optional media](examples/catalog-provider-page.example.json), and [normalized snapshot](examples/catalog-snapshot.example.json) fixtures. They are examples only: the source fixture is not a bundled or selected provider, and none of these files is runtime configuration. - -`manifestVersion` and response `schemaVersion` version this contract, not the DSH, Desktop, Market package, provider, or plugin version. The published `1.x` Schemas are the implemented compatibility surface. Additive compatible evolution requires review, fixtures, generated types, and contract tests; incompatible changes require a new major version. - -An implementation must reject an unsupported major version. Any contract change follows review together with schema fixtures and compatibility tests; loosening validation ad hoc inside one provider adapter is not allowed. - -## Current Desktop lifecycle - -1. Load local source settings without contacting a provider. -2. Resolve built-in adapter records and validate stored standard manifests. -3. Wait for the UI or Host consumer to request catalog data; there is no startup-blocking fetch. -4. Resolve the single selected source and prefer its reviewed remote query and pagination capabilities; validate and normalize each requested page before returning it. -5. Forward supported search, category, and sort parameters to the provider and expose only opaque Host cursors. Adapters without reviewed remote query support may still use a bounded local index. -6. Filter fail-closed **Installable** structural candidates from each visible page; perform authoritative official-registry verification only when the user previews one candidate, then revalidate mutable evidence before execution. Keep source and item provenance visible throughout. -7. Reuse bounded HTTP and local-index caches where applicable; an explicit refresh bypasses the relevant catalog HTTP cache. -8. Cancel owned requests and reset the session when the selected source changes, the selection is cleared, the plugin generation is disposed, or DSH shuts down. - -## Implemented v1 checklist - -The current package implements and tests every capability below. - -### Contract and types - -- [x] Publish the four v1 Schemas together and generate matching TypeScript types. -- [x] Maintain positive and negative JSON fixtures for every Schema. -- [x] Enforce cross-field semantics that JSON Schema alone cannot express, including endpoint, identity, provenance, query-limit, repository, and cursor rules. -- [x] Keep adapter types local while the standard, DSH 1024Store, and dshfind paths share the same normalized contract. - -### Source registry and UI - -- [x] Persist user-owned source records and one provider-inaccessible local selection marker. -- [x] Provide add, inspect, select, switch, retry, reorder, and remove actions. -- [x] Start with no source selected and show an explicit source-choice state. -- [x] Show attribution, endpoint host, adapter type, and last result in source management and preserve source provenance in catalog and install surfaces. -- [x] Keep failures attached to the selected source without automatic replacement or fallback. - -### Fetch and selected-source session - -- [x] Share one constrained HTTP client across standard and reviewed provider adapters. -- [x] Implement standard GET `/v1/plugins`, exact query serialization, bounded complete indexing where required, and reviewed remote pagination where available. -- [x] Ship reviewed optional DSH 1024Store and dshfind adapters with fail-closed structural install evidence. -- [x] Enforce abort, deadlines, bounded caches, force refresh, Schema-bounded network pages, and Client pages of at most 50. -- [x] Validate untrusted wire data before normalization and validate every normalized snapshot again. -- [x] Preserve provenance through remote or local pagination, filtering, cache, details, and installation confirmation. - -### Installation handoff - -- [x] Derive candidates only from normalized identity and reviewed provider verification; never consume remote commands. -- [x] Admit only exact stable npm candidates, then independently revalidate registry identity, repository, integrity, runtime, lifecycle scripts, and DSH bundle evidence during preview and execution. -- [x] Bind each candidate to the source record currently selected and visible to the user. -- [x] Revalidate the chosen record, mutable package evidence, and active profile immediately before managed installation. -- [x] Keep browsing fully usable when installation capability is absent or an item remains browse-only. - -### Release gate - -- [x] Provide reviewed Host/Client runtime entries, package-export checks, and Loader smoke tests. -- [x] Document configured network/data limits and return bounded user-facing failures. -- [x] Cover user-added URLs, redirects, DNS pinning, renderer text handling, media isolation, and install-candidate derivation with fail-closed boundaries. -- [x] Exercise the normalized contract through the standard source and two independently shaped reviewed provider APIs. - -## Verified test matrix - -The current automated contract, adapter, Host, Client, media, and installation suites exercise the following acceptance behavior. Rows may group several assertions from those suites. - -| Area | Case | Expected result | -| --- | --- | --- | -| No default | Fresh profile has no selected source | Explicit source-choice empty state; no network request and no fallback | -| Selection | User saves two sources and selects one | Only the selected source is requested; the other remains saved and idle | -| Selection | User switches from source A to source B | A's request is cancelled; list, search, categories, and cursor reset before B is fetched | -| Selection | Remote manifest contains `selected`, `enabled`, `priority`, auth, header, script, or install fields | Manifest rejected by the strict schema | -| Selection | DSH 1024Store adapter is available on first run | It is visible as a choice but remains unselected until the user chooses it | -| Query | All supported parameters are populated | Correct URL encoding; repeated `category`/`capability`; other fields appear once | -| Query | A parameter is valid but absent from `query.supported` | Host omits it for that source | -| Query | `limit` is zero, above 200, non-integer, or above provider maximum | Invalid values are rejected; a valid value above `maxLimit` is reduced before network I/O | -| Query | Cursor is reused with another source or changed filters | Cursor rejected locally; no request sent | -| Query | Standard response exceeds the effective requested limit, or its declared `defaultLimit` when `limit` is unsupported | Response rejected before cache or UI update | -| Query | Standard source validly returns 51–200 items within its effective manifest limit | Response accepted; 50 is only the current discovery UI default, not a global contract cap | -| Full index | Standard source returns several cursor pages | Every page is validated once; local search and multi-category OR filtering can find items beyond the first network page | -| Remote pagination | 1024Store has more than 100 valid entries | Each request normalizes at most one provider page; **Load more** uses the next opaque cursor and never returns the complete directory | -| Pagination | A bounded local index has more than 50 matching entries | First visible page contains 50; **Load more** advances through the Host-owned local cursor | -| Refresh | A page or completed local index is cached, then explicitly refreshed | Refresh bypasses the relevant catalog HTTP cache and replaces applicable local state | -| Schema | Valid manifest, query, provider-page, and snapshot fixtures | Accepted and round-trip without losing defined data | -| Schema | Unknown property or unsupported major version | Affected manifest/request/snapshot rejected | -| Schema | Provider page attempts to supply Host provenance | Strict wire schema rejects the response | -| Schema | Provider page contains a valid HTTPS `media.icon`; normalized snapshot contains a safe `assetRef` and valid role | Both fixtures pass, and the normalized item contains no remote URL | -| Schema | Icon URL uses HTTP/credentials, or a normalized icon contains a URL/path/unknown role instead of an opaque `assetRef` | Strict schema rejects the affected response | -| Schema | Snapshot source record or item provenance differs from the local record | Source result rejected as identity spoofing | -| Schema | Item lacks both npm package and repository identity | Item/snapshot rejected | -| Schema | Provider page repeats an item `id`, or normalized `provenance.itemId` differs from its item `id` | Entire source response rejected | -| Normalization | 1024Store fixture uses its existing provider format without an icon | Adapter resolves its GitHub owner avatar as `publisher-avatar`; a direct provider icon, when available, takes precedence | -| Selection | Selected source times out | Its safe error and Retry are shown; no other saved source is requested as fallback | -| Identity | Two saved sources list the same canonical package | They remain isolated and are never merged across source switches | -| Identity | Two selected-source items only have similar names | They remain distinct `{sourceRecordId, itemId}` records | -| Security | URL targets HTTP, URL credentials, loopback/private/link-local/metadata, or a redirect to one | Request rejected before protected resources are contacted | -| Security | DNS answer changes to a prohibited address | Connection blocked; source-specific safe error shown | -| Security | Body is oversized, deeply invalid, non-JSON, slow, or contains unknown fields | Request aborted/rejected; no cache or renderer update | -| Security | Icon redirects to a prohibited host, exceeds image limits, has a false media type, or fails decoding | Asset request becomes unavailable and Renderer uses its local placeholder; Renderer never contacts the remote host and the valid catalog item remains usable | -| Security | Remote text contains HTML/script/Markdown injection | Displayed as inert text; no code or navigation executes | -| Security | Display text contains controls/Bidi spoofing, or an external link appears without a gesture | Unsafe text is rejected/neutralized; no link opens automatically | -| Security | Source attempts to use cookies, auth, custom headers, or remote adapter code | Capability unavailable and input rejected | -| Lifecycle | Selection changes, is cleared, or Host is disposed during fetch | Fetch aborts, resources release, session state resets when applicable, and no late result mutates state | -| Install | Snapshot contains a command-like string or URL query crafted as a command | It cannot reach the managed install operation | -| Install | Exact stable npm version or reviewed provider verification is absent, invalid, or changes during revalidation | Install remains disabled; no package operation starts | -| Install | Catalog repository and authoritative npm package repository are unverified or conflicting | Install remains disabled; no catalog claim wins over authoritative verification | -| Install | User chooses an item from the selected source | Confirmation shows exact source, identity, and active profile before execution | - -## Versioned extension points - -Compatible v1 revisions may refine documented cache TTLs, byte/item budgets, locale fallback behavior, and UI layout together with tests. They may not weaken the exactly-one-selected-source rule, explicit selection, no-default, no-fallback, strict validation, provenance, or non-executable-data rules; changing those boundaries requires a reviewed contract revision. diff --git a/dsh-community-market/docs/catalog-provider-contract.zh.md b/dsh-community-market/docs/catalog-provider-contract.zh.md deleted file mode 100644 index f917eb08da..0000000000 --- a/dsh-community-market/docs/catalog-provider-contract.zh.md +++ /dev/null @@ -1,417 +0,0 @@ -# 目录提供方契约 - -[English](catalog-provider-contract.md) - -状态:**已实现的公开 v1 契约。** 带版本的 Schema、生成类型、严格校验、来源持久化、受限网络与媒体边界、标准 HTTP adapter、经过审核的 DSH 1024Store 与 dshfind adapter、完整本地索引,以及可加载的 Host/Client 入口均已在 DSH Desktop 中实现并通过测试。本文档和 fixture 是 `manifestVersion` 与 `schemaVersion` `1.x` 的公开互操作契约。 - -## 决策摘要 - -- DSH Community Market **没有默认、优先或兜底目录来源**。 -- 用户可以保存多个来源注册,但当前浏览会话必须明确且最多只选择一个来源。 -- 用户可以添加任何符合本契约的来源。添加来源不会安装插件,也不会给该来源任何执行能力。 -- 目录生态是开放的:任何个人、社区或服务都可以发布符合规范的来源,任何用户都可以登记其 manifest URL。标准接入不需要修改 Market 代码,也不需要先获得合作批准。 -- 已有公开 API 无法直接输出标准 page 结构的 provider,可以提出经过审核的 adapter 合作接入。合作只会增加本地、经过测试的 Market 代码,绝不允许 provider 下发可执行 adapter 代码。 -- DSH 1024Store 是当前与本项目合作的目录提供方之一。市场已包含经过审核的内置 adapter;这个 adapter 不会自动选择 1024Store,也不会在当前来源失败时用它兜底。 -- dshfind 是另一个可选合作提供方。它经过审查的 adapter 只接受无歧义的结构化 npm 证据作为结构安装候选;其他条目仍然只能浏览。它不会被默认选择、优先排序、推荐或用作兜底。 -- 某个来源出现在内置选项中或受到 adapter 支持,不代表 Anywhere Labs 推荐、审核或背书该来源及其收录的插件。 -- 所有 provider 必须先转换成同一个标准化模型,数据才能到达市场界面或安装边界。 - -这些是产品与信任边界,不是后续团队为了实现方便就可以修改的默认值。 - -## 范围 - -本契约定义: - -- 标准目录如何用静态来源 manifest 描述自己; -- 标准 HTTP endpoint 的查询方式; -- wire format 不同的合作提供方如何通过内置 adapter 接入; -- 市场消费的标准化快照; -- 已保存来源注册、单一来源选择、provenance、分页和失败行为; -- 最小网络与数据安全边界; -- 已实现的 v1 能力清单和已验证验收矩阵。 - -它不定义目录治理、插件审核、账号系统、付费、任意鉴权来源或 package 安装命令。安装仍然是独立的用户确认操作,由 Market Host 和当前 profile 服务负责。 - -## 术语 - -| 术语 | 含义 | -| --- | --- | -| 目录来源(catalog source) | 插件元数据的提供方;它提供数据,不是可执行插件代码。 | -| 来源 manifest | 描述标准来源及其查询能力的静态 JSON 声明。 | -| Provider ID | Provider 声称的稳定 ID;它是来源声明数据,不是本地权威身份。 | -| Source record ID | Host 为一条本地来源注册生成的不透明 UUID;cache、cursor、选择和条目身份都使用该 ID。 | -| Adapter | 经过审核的本地代码,用于请求 provider 并把响应转换成标准化模型。 | -| 标准 adapter | 面向直接实现本契约的来源的内置 adapter,不包含 provider 私有逻辑。 | -| Provider adapter | 面向已有不同 API 的合作提供方的、经过审核的内置 adapter。 | -| Provider page | 标准来源返回的不可信 wire response,此时尚未注入 Host provenance。 | -| 标准化快照 | 单一已选来源会话、UI 和安装候选解析器唯一可以消费的目录数据结构。 | -| 远程图标候选 | `media.icon` 中由 provider 可选声明的 HTTPS 图片。它是 Host 媒体解析器的不可信输入,绝不是 Renderer 可以直接访问的 URL。 | -| Asset reference | 标准化 `media.icon` 中由 Host 管理的不透明 token。Renderer 只能通过 Host asset 边界消费它,不能把它变成任意网络请求或文件系统路径。 | -| 本地来源设置 | 用户保存的来源注册和可选的已选来源记录;这些值绝不来自远程 manifest。 | - -## 来源必须由用户明确选择 - -来源选择属于本地用户状态。每条注册都会获得 Host 生成的 `sourceRecordId`;远程 `providerId` 不能替代它,也不能靠匹配已知字符串获得内置/合作方 badge。来源管理界面必须支持: - -1. 通过 manifest URL 添加标准来源; -2. 选择已知的内置 provider adapter; -3. 恰好选择一个已保存来源用于浏览; -4. 切换当前选择的来源; -5. 删除用户添加的来源; -6. 在信任其数据前看到 provider 名称、来源声明、endpoint host、adapter 类型和最近结果。 - -Manifest 不能把自己声明成已选择、可信、官方、推荐或兜底来源。来源 schema 会有意拒绝 `selected`、`enabled`、`order`、`priority`、鉴权材料、自定义 header、script 和 install command。内置来源与用户来源可以声称同一 `providerId`,但不会共享身份、cache、cursor、信任或展示权重。是否显示经审查的合作方 badge,由本地 adapter 注册决定,不由 provider 声称决定。 - -首次运行时,市场可以展示可选来源,包括合作提供方,但不能预选它们。没有选择来源时,UI 显示明确的“选择或添加来源”状态,不发送目录请求,也绝不静默切换到 DSH 1024Store 或任何其他 provider。 - -切换已选来源时,必须取消旧来源正在进行的目录工作并开始全新浏览会话。读取新来源前,要重置当前列表、搜索文本、已选分类、已发现分类选项、分页 cursor 和当前错误。 - -私有持久化结构不属于 provider contract。它保存 Host 生成的来源记录和一个本地选择标记。为兼容旧设置,实现可以把这个标记编码在记录上;真正重要的不变量是:目录 I/O 最多只解析一条已保存记录,绝不解析成并发激活的来源列表。每条记录的 `manifestUrl` 与 `builtInProviderKey` 必须且只能存在一个。User-added 记录还会保留注册时校验过的 manifest,让 UI 在选择前展示名称、来源声明、endpoint 与 adapter 类型;每次读取目录时,标准 adapter 仍会重新获取并校验远程 manifest。 - -## 三层契约 - -```mermaid -flowchart LR - Settings["本地来源设置
没有或恰好一个已选来源"] --> Registry["已保存来源 registry"] - Manifest["标准来源
manifest + GET /v1/plugins"] --> Standard["标准 adapter"] - Partner["合作提供方
provider 私有 API"] --> Builtin["经审核的内置 adapter"] - Registry --> Standard - Registry --> Builtin - Standard --> Validate["校验并标准化"] - Builtin --> Validate - Validate --> Session["单一已选来源会话
保留 provenance"] - Session --> UI["市场 UI 与确认式安装边界"] -``` - -### 第一层:来源 manifest - -标准来源发布一份静态 manifest,并由 [`catalog-source.schema.json`](schemas/catalog-source.schema.json) 校验。公开 v1 的结构用于声明: - -- `manifestVersion`,v1 固定为 `1.0.0`; -- provider 声称的 `providerId`、可读 `name`,以及可选 description/homepage; -- 包含名称、URL 和可选 notice 的 provider 来源声明; -- 一个公开的 `https-json` GET endpoint; -- 支持的 query 参数、默认和最大分页大小,以及支持的排序值。 - -Manifest 描述 provider 能力,不控制本地策略。公开 v1 标准来源只支持匿名访问:契约不包含 bearer token、cookie、request header、secret 字段、可执行 mapping 或动态 JavaScript。 - -来源 manifest URL 与目录 endpoint 是两个不同地址。添加 manifest URL 必须来自用户明确操作。Host 生成全新 `sourceRecordId`,校验 manifest 后将注册时副本与该本地用户来源记录一起保存,在来源管理中展示其披露字段,并且只有用户选择后才把它设为当前来源。 - -标准直接接入时,用户只需要登记 manifest URL。建议的最小 manifest 只使用一个公开 GET endpoint,只声明 `q`、`category`、`cursor` 和 `limit`,把示例中的两个 page limit 都设为 50,并将 `sorts` 留空。50 是方便起步的值,不是标准来源上限;manifest 可以在 Schema 安全上限 200 以内声明 limit。Capability、sort、locale、图标和更丰富的展示字段仍是可选扩展。参见[最小来源 manifest](examples/catalog-source.example.json)与[最小 provider page](examples/catalog-provider-page.minimal.example.json)。 - -注册同时固定 provider 声明与网络 origin。每次请求都必须重新确认 manifest 的 `providerId` 与本地来源记录保存的值完全一致。用户确认的 manifest URL、manifest 请求的最终 URL、`transport.endpoint` 和 provider-page 请求的最终 URL 必须始终属于同一个无凭据 HTTPS origin。公开 v1 的网络 URL 和 manifest 只允许标准 HTTPS 443 端口,不把自定义端口纳入标准来源契约。允许同源 redirect;即使两个地址都使用 HTTPS,也必须拒绝跨 origin。确实需要独立 API origin 或端口的部署,应使用经过审核的 provider adapter 接入路径,除非后续契约修订明确描述这种关系。 - -### 第二层:adapter - -Adapter 是本地的类型化边界,只承担一个职责:在 Host 限制下请求来源,并返回标准化快照。 - -```ts -interface CatalogAdapter { - readonly adapterId: string - fetch(query: CatalogQuery, context: CatalogFetchContext): Promise -} -``` - -`CatalogFetchContext` 只提供 `AbortSignal`、受限 HTTP client、已校验来源身份、配置限制,以及一个只接受已审核候选并返回不透明 asset reference 的窄 Host media registrar。它不暴露 Electron 全局对象、任意文件系统访问、shell、ambient credentials 或包管理器执行能力。 - -接入只有两条受支持路径: - -1. **标准来源:** 发布静态 manifest,并从唯一 endpoint 返回标准 provider-page JSON;不需要编写 Market 代码。 -2. **受审 adapter:** 已有公开 API 无法返回标准结构时,把 API schema 和有代表性的 response 样例交给 Market 维护者。Adapter 由本地编写、审核、测试,并随 Market 代码发布;远程 response 绝不能注入 JavaScript、mapping 表达式或 adapter 代码。 - -[目录 adapter 指南](catalog-adapter-guide.zh.md)提供了选择清单和第二条路径的完整 TypeScript skeleton。 - -标准 adapter 把下文 query 契约映射到标准 endpoint,用 [`catalog-provider-page.schema.json`](schemas/catalog-provider-page.schema.json) 校验 wire response,之后才创建标准化快照。Provider adapter 可以翻译字段名、分页、分类、媒体候选或旧响应字段,但必须返回相同的标准化模型,并保留 provider 来源声明。Provider adapter 随市场 package 一起编译和审核;manifest 或 response 绝不能下载或提供 adapter 代码。 - -Provider 输入绝不提供 Host provenance。Response 成功后,adapter 注入本地 `sourceRecordId`、本地注册的 `adapterId` 与 registration kind、Host 观测的 `fetchedAt` 和已校验最终 response URL。Provider 生成时间与 revision 必须明确标注为 provider claim。 - -### 第三层:标准化模型 - -每个成功结果都必须先通过 [`catalog-snapshot.schema.json`](schemas/catalog-snapshot.schema.json) 校验,之后才能缓存、展示或用于生成安装候选。 - -公开 v1 标准化快照包含: - -- `schemaVersion: "1.0.0"`; -- Host 生成的来源记录身份、provider claim、本地 adapter 身份、registration kind、观测的抓取时间和最终 URL; -- 可选 provider 生成时间和 revision,它们与 Host 观测值明确分开; -- 标准化插件条目; -- 带可选不透明 next cursor 和 total 的分页信息。 - -每个条目都有稳定的来源内身份、展示文本和由 Host 注入的明确 provenance。它可以声明 npm package、规范化 repository 加可选 subdirectory,或同时声明两者;也可以包含有界的描述元数据、分类、capability、兼容性声明、更新时间和 Host 已解析的媒体。条目绝不包含 install command、shell fragment、HTML、script、可执行 callback、远程媒体 URL 或文件系统路径。 - -同一页 provider response 中,每个条目的 `id` 必须唯一。Adapter 必须在注入 provenance 之前拒绝重复 ID。标准化快照中的每个 `provenance.itemId` 必须与所在条目的 `id` 完全一致。 - -Host 必须确认快照和每个条目 provenance 都携带本地记录的 `sourceRecordId` 与 provider claim。Provider 不能提供这些 Host 字段、冒充内置注册,或靠选择同一 `providerId` 与其他注册冲突 cache 和 cursor。 - -### 媒体与图标解析 - -媒体有两种刻意不同的表示形式: - -- 标准 provider page 可以声明可选的 `media.icon: { url, alt? }`。URL 是一个不可信的 HTTPS 插件图标候选,还不是可以直接展示的数据。 -- 标准化快照可以包含可选的 `media.icon: { assetRef, role, alt? }`。`assetRef` 是 Host 管理的不透明 token,既不是远程 URL,也不是文件系统路径。当前 Host token 的格式是 `mktimg_` 加 32 个 URL-safe 字符;provider 绝不能生成或返回这个 token。`role` 只能是 `plugin-icon` 或 `publisher-avatar`。 - -Host 在输出标准化快照之前,会通过专用媒体边界校验并登记远程候选,再用新的 `assetRef` 替换它;只有 Renderer 请求该引用时才会懒加载图片字节。对于标准来源,候选必须与 provider-page 的最终 response 同源,每次 redirect 也必须留在 Host 批准的精确 hostname 内;如果提供方使用独立图片 CDN,就必须从目录同源地址提供或代理标准 v1 图标。Asset 服务复用目录请求的目标地址与 redirect 防护,限制图片 media type、字节数和像素数,解码图片,并且只返回安全的本地表示。图片无效或加载失败时,该引用会变为不可用,但不影响其余合法目录条目;Renderer 改用本地占位图。Renderer 不能收到或直接请求 provider URL。 - -Host 的目录 cache、已注册媒体引用、解码图片 cache 和并发图片任务都必须有界。取消选择或删除来源时,Host 会在本地来源变更成功保存后,取消其进行中的目录任务并撤销该会话的媒体引用。已保存来源的 last-good cache 必须按记录和 query 隔离,绝不能作为另一个来源的结果展示。 - -当前已选来源记录中的媒体展示优先级固定为: - -1. 有效的 provider 直接 `media.icon`,标准化为 `role: "plugin-icon"`; -2. 经审核的 provider adapter fallback,并标记真实角色,例如 `role: "publisher-avatar"`; -3. 标准化条目没有媒体时,由 client 生成本地占位图。 - -Adapter 不能把 owner 或组织头像冒充成插件图标。这个优先级在 Host 选择登记哪个候选时生效;如果选中的图片之后加载失败,会改用本地占位图,而不会继续联系第二个远程候选。此前已选来源的媒体不能带入新的单一来源会话。 - -## 标准 HTTP 来源 - -标准 v1 来源暴露 manifest 中声明的绝对 HTTPS endpoint。它的 path 为 `/v1/plugins`;如果服务挂载在固定前缀下,也必须以该 path 结尾,并且 endpoint 本身不能带 query 或 fragment: - -```text -GET https://catalog.example.org/v1/plugins?q=memory&category=utility&limit=50 -Accept: application/json -``` - -Host 先构造并校验 [`CatalogQuery`](schemas/catalog-query.schema.json),然后只序列化来源 manifest 的 `query.supported` 数组中声明的参数。缺失值直接省略,不能序列化为空字符串或 `null`。 - -| 参数 | 数量 | 公开 v1 语义 | -| --- | --- | --- | -| `q` | 0 或 1 个 | 去除首尾空白的搜索文本,1–200 个字符;匹配和排序方式由 provider 决定。 | -| `category` | 0 或多个 | 稳定 category ID。重复参数表示“匹配任意一个请求分类”;不允许重复值。 | -| `capability` | 0 或多个 | Fabric/host capability ID。重复参数表示条目必须声明全部请求 capability;不允许重复值。 | -| `cursor` | 0 或 1 个 | 同一来源在相同有效 filter 和 sort 下返回的不透明 continuation value,最长 2048 字符。 | -| `limit` | 0 或 1 个 | 1 到 200 的整数。Host 标准化 query 默认值为 50;有效请求值不能超过 manifest `maxLimit`。 | -| `sort` | 0 或 1 个 | `relevance`、`updated`、`name` 或 `downloads` 之一,并且来源 manifest 也必须声明支持该值。 | -| `locale` | 0 或 1 个 | 类 BCP 47 语言标签,例如 `zh-CN` 或 `en`。它只是偏好,provider 仍必须返回稳定 ID。 | - -`category` 和 `capability` 序列化为重复 query 参数,其余字段都是单值。Query 文本和值必须由平台 URL builder 作为数据进行 URL encode,不能直接拼接进 URL、header 或命令。 - -对发送 provider 侧 filter 的 consumer 来说,重复 `category` 是多选 OR 过滤:条目属于任一已选分类即算匹配。只有来源 manifest 在 `query.supported` 中声明支持 `category` 时,标准 adapter 才会发送该字段;否则会省略该字段,不擅自创造 provider 语义。 - -Host 标准化 query 默认值和 provider 默认值是两个概念。合法 consumer 可以请求不超过 200 的值,Host 会在需要时收窄到 manifest 的 `maxLimit`。Response 条目数不能超过这个有效请求值。来源不支持 `limit` 时,Host 省略该参数,并接受不超过来源声明 `defaultLimit` 的条目。Manifest 必须保证 `defaultLimit` 小于或等于 `maxLimit`,且两者都不能超过 200。 - -Provider cursor 只属于一个已选来源和一个有效 wire query。Host 绝不能把一个来源的 cursor 发送给另一个来源,也不能在 wire query 改变后复用。 - -对于标准来源,当前 Desktop 会通过只发送 `cursor`、`limit` 和 locale 偏好等受支持字段来扫描有界完整来源,并跟随 `page.nextCursor` 直到结束。扫描不会把用户搜索文本或已选分类发给 provider。经过审核的 1024Store adapter 则把发现页查询转发给 v2 远程 page;“可安装”每批请求 200 条 registry 记录,再只保留直接 npm 目标。 - -对于没有已评审远程查询能力的 adapter,搜索、排序、多分类 OR 筛选、分类枚举和分页在有界完整本地索引上运行;已评审 adapter 可以把这些操作转发给 provider。两种情况下 Renderer 都只会收到一个可见 page 和 Host 拥有的不透明 cursor。 - -标准来源只有返回通过 provider-page schema 的成功 JSON response 才能接受。Adapter 随后注入 Host provenance,再校验标准化 snapshot schema。超时、非 200、错误 content type、响应过大、解析失败、不支持的 schema version 或任一校验错误只会让该来源请求失败,不影响应用启动。标准 response 条目数超过有效 `limit` 时也必须拒绝。 - -## 单一已选来源浏览会话 - -已保存来源彼此隔离,但产品只读取当前已选来源: - -- 同一时间最多只有一条已保存来源记录被选择,并且只有该来源会收到目录请求。 -- 已选来源拥有自己的 timeout、cancellation、不透明 cursor、loading state、error state,以及适合该 adapter 的有界 cache。 -- 标准来源网络 page 遵守有效请求值或声明的 `defaultLimit`,Schema 安全上限为 200;发现页可见页面最多包含 50 条。 -- 1024Store adapter 远程请求 v2 page,转发受支持的查询参数,只标准化当前请求的 page,并为下一页提供 Host 不透明 cursor。 -- 可选目录 metadata 只适用于构建有界完整索引的 adapter,并报告扫描完成时间(`scannedAt`)、cache 截止时间(`expiresAt`)、可选且整次扫描一致的来源 revision(`providerRevision`),以及索引是新扫描还是复用(`cacheStatus`)。 -- 失败只归属于已选来源,并提供重试;Host 绝不退回或暗中请求另一个已保存来源。 -- 明确刷新会绕过相关目录 HTTP cache,并使适用的本地索引失效。切换或删除已选来源会取消 in-flight 工作并清空浏览会话,不需要重启 DSH。 -- 每个 card、详情、搜索结果和安装确认都保留当前已选来源的可见声明。 - -条目的规范身份是 `{ sourceRecordId, itemId }`。即使属于同一 provider,两条注册也保持独立,并且完全可以对同一个插件给出不同描述。当前单一来源 UI 不会跨已保存来源分组或合并记录。切换来源会替换整个浏览会话,任何来源都不能静默覆盖另一个来源的 cache 或身份。仅名称、repository 名称或描述相似绝不足以在当前来源内去重。 - -## 与 DSH 1024Store 的合作 - -[DSH 1024Store](https://github.com/imsai-sh/awesome-deepseek-harness-plugins) 是当前与 DSH Community Market 合作的提供方之一。它现有的 registry API 不需要改成标准 wire 结构,而是通过公开的受审 adapter 路径接入,并随 Market 提供一份内置 provider adapter。它会: - -- 在相同 Host 网络限制下请求 provider 当前分页的 `/api/v2/plugins` API,绝不再把冻结在 500 条的 v1 兼容 feed 当作目录; -- 把 v2 的完整分类 facet 和分页插件元数据映射成标准化 response; -- 只把 provider item `id` 当作来源内部身份,并从经过校验的 repository URL 推导规范 GitHub 仓库与 publisher 身份,避免仓库改名或转移后继续指向旧名称; -- 由于当前 1024Store 数据集没有直接插件图标,仅把 GitHub owner/avatar 候选作为经过审核的 fallback,通过 Host 媒体边界解析,并将结果标为 `role: "publisher-avatar"`; -- 普通发现、搜索、排序和单分类查询直接使用 v2 page;多分类 OR 查询会合并有界的 provider 排序前缀,并隐藏在 Host 不透明 cursor 后; -- 从每个请求到的 v2 page 中筛选直接 npm 目标用于“可安装”,并继续携带不透明 provider cursor,使 Host 和 Renderer 都不会一次收到完整目录; -- 注入并校验 DSH 1024Store 的 provenance 和来源声明; -- 永远不把远程 command 文本或安装提示当作可执行输入;adapter 只接受严格的惰性 `dsh plugin --profile … add ` 形状作为 package name 证据,随即丢弃命令,并由安装 preview 独立解析 npm `latest`; -- Provider 不可用或数据非法时,把当前已选来源报告为不可用,绝不退回另一个已保存来源。 - -这一合作关系使 1024Store 成为一个受到支持的来源选项,但**不会**使它成为默认、优先、官方、推荐、已审核或兜底来源。Adapter 不会自动选择它;没有选择或当前来源失败也不会触发对它的隐藏请求。它的目录仍属于独立项目,收录某个插件不等于完成了该插件的安全审核。 - -## 与 dshfind 的合作 - -[dshfind](https://dshfind.com) 是一个可选合作来源,Market 为其公开 REST API 提供经过审查的内置 adapter。Adapter 只使用编译期固定的 `https://api.dshfind.com` origin 和公开匿名请求。它以 `per_page=100` 请求首页,记录返回的 `data_version`,并在所有后续分页中携带完全相同的值。遇到 `409 stale_data`、版本/总数不一致、非法分页或遍历不完整时,整次扫描失败,不能发布部分索引。搜索、分类筛选、分类枚举、排序和每页 50 条的 UI 分页随后都在完整本地索引上运行,不会把这些交互继续发给 dshfind。 - -dshfind 文档说明匿名配额为每分钟 30 次、突发 10 次,而当前目录以每页 100 条读取时需要超过 30 个 page。因此 adapter 会使用低于已公布持续速率的固定串行间隔,并把较慢的首次同步表现为来源加载状态,不能通过并发绕过 provider 限制。限流 response 会使整轮扫描失败,用户可以通过普通来源“重试”重新开始。完成的本地索引仍遵循普通有界 cache;明确刷新会启动一轮新的、一致的完整扫描。 - -dshfind response 可能包含 `install.cmd`、其他安装 claim 和 `install.methods`。命令文本与普通 claim 都不是执行权限:adapter 会在标准化前丢弃 `install.cmd`,不展示、不执行,也绝不从中解析 package 身份。Adapter 可以输出结构化 npm package 身份和只作信息展示的 provider 版本,但自动安装 preview 会忽略该版本并解析 npm 官方 `latest` manifest。Preview 要求 npm identity 一致、latest 是精确稳定版本、DSH bundle 声明合法,并且当前 Profile 可以安装。 - -Adapter 可以标准化有界纯文本身份、描述、标签/分类、更新时间,以及规范、无凭据的 `https://github.com/owner/repository` 链接。当前 API 没有插件图标或 README 字段。任何 owner 头像 fallback 都必须标记为 `publisher-avatar` 并通过 Host 媒体边界解析;adapter 不获取或渲染远程 README 内容。dshfind 的分数、等级、`official`/精选标记、风险标记和安装结论仍是 provider 自有运营声明,绝不会成为 Anywhere Labs 的安全审核、推荐或验证信号。 - -这一合作只会让 dshfind 作为可选合作来源显示;它**不会**成为默认、优先、官方、推荐、已审核或兜底来源,来源失败也不会触发对其他 provider 的隐藏切换。 - -## 安装边界 - -目录浏览与插件安装是两个独立操作: - -- 获取 manifest 或 snapshot 是只读操作,绝不会调用 pnpm、DSH、shell 或 lifecycle script。 -- 远程数据不能提供 install command、自定义包管理器参数、环境变量或工作目录。 -- 浏览记录还不是安装目标。当前路径要求一个明确且合法的 npm package 身份。 -- Preview 解析 npm `latest`,并要求 identity 一致、版本精确稳定、DSH bundle 声明合法且当前 Profile 可以安装。 -- 执行阶段消费一次性 preview,并拒绝目录或 Profile 身份已经变化的操作。 -- 最终确认展示精确来源记录、锁定的 npm package 与版本、当前 profile 和本地代码风险提示。 -- 只有用户明确操作后才开始安装,并使用现有的受管当前 profile 服务。 - -支持一个来源只表示可以浏览它的元数据,不会授予该来源安装、更新、启用或执行插件的能力。 - -## 安全要求 - -### 网络边界 - -- 生产环境中的 manifest 和目录 URL 必须使用 HTTPS。拒绝 URL credentials、fragment、非标准 scheme,以及 endpoint 自带 query 的情况。 -- 校验每次 redirect,限制 redirect 次数,拒绝 HTTPS downgrade,并在每一跳重新执行全部地址检查。 -- DNS 解析后阻止 loopback、private、link-local、multicast、unspecified、运营商级 NAT 和 cloud metadata 地址,并保护连接不受 DNS rebinding 影响。仅可提供明确、可见且生产 build 不存在的本地开发 override。 -- 不附带 ambient cookie、authorization header、client certificate 或 provider 提供的自定义 header。公开 v1 标准来源只支持匿名访问。 -- 设置 connect、first-byte 和 total deadline,并支持 `AbortSignal` 取消。 -- 限制压缩后与解压后 response 大小、条目数量、分页深度、字符串、数组和 URL 长度。限制值必须是有测试的常量;数据字段以 schema maximum 为准。 -- 目录 response 必须使用 JSON media type,只解码一次,校验后才能缓存。目录 loader 绝不跟随数据中发现的 URL;只有明确的 Host 媒体解析器可以在独立图片限制下处理已校验 provider-page `media.icon.url`。 - -公开 v1 的运行时预算属于 provider contract,而不只是实现提示: - -| 边界 | Body 与 redirect 预算 | Deadline | 其他规则 | -| --- | --- | --- | --- | -| 标准来源 manifest 与 provider page | 每个 JSON response 最大 2 MiB,最多 3 次 redirect | connect 8 秒、first-byte 12 秒、total 30 秒 | Manifest request 与每次目录 request 分别应用这些限制。 | -| DSH 1024Store 内置 adapter | 每个 v2 JSON page 最大 2 MiB,最多 3 次 redirect | connect 8 秒、first-byte 12 秒、total 30 秒 | 发现页和“可安装”都保留远程分页边界,不会把完整目录返回给 Client。 | -| 图标 asset service | 每个图片 response 最大 2 MiB,最多 2 次 redirect | connect 8 秒、first-byte 12 秒、total 30 秒 | 输入必须是单帧 PNG、JPEG 或 WebP,解码后最多 `16 * 1024 * 1024` 像素;Host 输出移除 metadata 的 128 × 128 PNG。 | - -Host 只为当前已选来源执行目录 I/O。图标 asset service 同时最多执行两个网络请求与解码任务;这个上限作用于单个 Market plugin generation 的全局范围。 - -### 数据与 renderer 边界 - -- 使用 strict schema,拒绝 object 的未知字段;遇到未知 major version 时关闭失败。 -- 名称、描述、publisher claim、notice 和其他远程字符串都按不可信纯文本处理;不得作为 HTML 注入,也不得启用 Markdown raw HTML。 -- 分组或安装前必须规范化 package 与 repository identity。拒绝歧义、带 credentials 或不支持的 repository URL。 -- 不从来源 manifest 或 snapshot 加载远程 script、adapter definition、stylesheet、iframe 或可执行 mapping。远程图标候选只能由 Host 媒体解析器获取;标准化 snapshot 只能包含不透明 `assetRef`。 -- 拒绝或明确中和展示/确认字段中的 control character 与双向文本控制符。只有用户操作后才能打开外部 HTTPS 链接。 -- 面向用户的错误和遥测不能包含原始 body、本地路径、环境变量、credential 或命令内部信息。 -- 来源声明和校验结果必须与插件信任分开记录。通过 schema 只证明数据结构正确,不证明安全或作者身份。 - -### 本地状态边界 - -- 添加、选择、切换、清空选择或删除来源都需要用户明确操作。 -- 远程 manifest 不能修改本地来源设置,也不能添加另一个来源。 -- 来源设置和远程数据 cache 必须分开持久化;刷新 cache 不能改变本地已选来源标记。 -- Cache key 和 cursor 必须按本地 `sourceRecordId` 与有效 query 隔离,绝不能把一个来源记录的成功 response 当成另一个来源记录的数据。 - -## 版本与 schema 权威性 - -公开 v1 Schema 使用 JSON Schema Draft 2020-12: - -- [`catalog-source.schema.json`](schemas/catalog-source.schema.json) 是来源 manifest 的权威定义。 -- [`catalog-query.schema.json`](schemas/catalog-query.schema.json) 是标准化 query object 和参数边界的权威定义。 -- [`catalog-provider-page.schema.json`](schemas/catalog-provider-page.schema.json) 是不可信标准 HTTP wire response 的权威定义。 -- [`catalog-snapshot.schema.json`](schemas/catalog-snapshot.schema.json) 是标准化 response 的权威定义。 - -实现必须在启用 Draft 2020-12 format assertion 的情况下编译这些 schema,并完整校验 URI、date-time 和 UUID format。这里的 `format` 是校验要求,不是只用于说明的 annotation。字段间关系仍需语义校验,例如 `defaultLimit <= maxLimit`,以及宣告支持 `sort` 时 `sorts` 不得为空。 - -### 可复制的 v1 fixture - -Provider 与 adapter 作者可以直接使用对应的[最小来源 manifest](examples/catalog-source.example.json)、[最小 query](examples/catalog-query.example.json)、[最小 provider page](examples/catalog-provider-page.minimal.example.json)、带可选媒体的完整 [provider page](examples/catalog-provider-page.example.json) 和[标准化 snapshot](examples/catalog-snapshot.example.json) fixture 编写兼容性测试。它们只是示例:来源 fixture 不是内置或已选择的 provider,这些文件也都不是 runtime configuration。 - -`manifestVersion` 和 response `schemaVersion` 对本契约进行版本管理,不代表 DSH、Desktop、Market package、provider 或插件版本。已发布的 `1.x` Schema 是当前已实现的兼容性边界。兼容性的新增必须同时经过评审、fixture、生成类型和契约测试;不兼容变更必须使用新的 major version。 - -实现必须拒绝不支持的 major version。所有契约修改都需要连同 schema fixture 和兼容性测试一起评审;不允许在某个 provider adapter 中临时放宽校验。 - -## 当前 Desktop 生命周期 - -1. 加载本地来源设置,不联系任何 provider。 -2. 解析内置 adapter 记录,并校验保存的标准 manifest。 -3. 等待 UI 或 Host consumer 请求目录数据,不进行阻塞启动的 fetch。 -4. 解析唯一已选来源,并优先使用其已评审的远程查询和分页能力;每个请求到的 page 都必须先校验和标准化再返回。 -5. 把受支持的搜索、分类和排序参数转发给 provider,并只暴露 Host 不透明 cursor。没有已评审远程查询能力的 adapter 仍可使用有界本地索引。 -6. 从每个可见 page 中筛选 fail-closed 的**可安装**结构候选;只有用户预览某个候选时才执行官方 registry 权威复核,并在执行前重新检查可变证据。在整个流程中保持来源与条目 provenance 可见。 -7. 在适用时复用有界 HTTP cache 和本地索引 cache;明确刷新会绕过相关目录 HTTP cache。 -8. 已选来源变化、清空选择、plugin generation 被 dispose 或 DSH 关闭时,取消自己拥有的请求并重置会话。 - -## 已实现的 v1 清单 - -当前 package 已实现并测试以下全部能力。 - -### 契约与类型 - -- [x] 同时发布四份 v1 Schema,并生成对应 TypeScript 类型。 -- [x] 为每份 Schema 维护正向与反向 JSON fixture。 -- [x] 执行 JSON Schema 无法单独表达的 endpoint、identity、provenance、query limit、repository 和 cursor 跨字段语义校验。 -- [x] Adapter 类型保持本地使用,标准来源、DSH 1024Store 与 dshfind 通过同一标准化契约。 - -### 来源 registry 与 UI - -- [x] 持久化用户拥有的来源记录,以及 provider 无法控制的一条本地选择标记。 -- [x] 提供添加、检查、选择、切换、重试、排序和删除操作。 -- [x] 首次启动不预选来源,并显示明确的来源选择状态。 -- [x] 在来源管理中展示来源声明、endpoint host、adapter 类型和最近结果,并在目录与安装界面保留 provenance。 -- [x] 让失败归属于当前已选来源,不自动替换或兜底。 - -### 请求与单一已选来源会话 - -- [x] 标准 adapter 与受审 provider adapter 共用一个受限 HTTP client。 -- [x] 实现标准 GET `/v1/plugins`、精确 query 序列化、必要时使用的有界完整索引,以及可用时采用的已评审远程分页。 -- [x] 提供经过审核的可选 DSH 1024Store 与 dshfind adapter,并对结构安装证据 fail closed。 -- [x] 实施取消、deadline、有界 cache、强制刷新、Schema 有界网络分页和每页最多 50 条的 Client 分页。 -- [x] 在标准化前校验不可信 wire 数据,并再次校验每份标准化 snapshot。 -- [x] 在远程或本地分页、筛选、cache、详情和安装确认中始终保留 provenance。 - -### 安装交接 - -- [x] 只从标准化 identity 与经过审核的 provider 验证推导候选,永不消费远程 command。 -- [x] 只接纳精确稳定的 npm 候选,并在 preview 与执行时独立复核 registry identity、repository、integrity、runtime、lifecycle script 和 DSH bundle 证据。 -- [x] 把每个候选绑定到用户当前看见并选择的来源记录。 -- [x] 调用受管安装服务前重新校验所选记录、可变 package 证据和当前 profile。 -- [x] 缺少安装能力或条目只能浏览时,目录浏览仍然完整可用。 - -### 发布门槛 - -- [x] 提供经过审核的 Host/Client runtime 入口、package export 检查和 Loader smoke test。 -- [x] 记录网络/数据限制,并返回有界的用户可见失败信息。 -- [x] 以 fail-closed 边界覆盖用户添加 URL、redirect、DNS pinning、renderer 文本、媒体隔离和安装候选推导。 -- [x] 通过标准来源和两个 wire 结构独立的受审 provider API 验证标准化契约。 - -## 已验证测试矩阵 - -当前自动化契约、adapter、Host、Client、媒体和安装测试覆盖以下验收行为;部分行汇总同一测试套件中的多项断言。 - -| 范围 | 用例 | 预期结果 | -| --- | --- | --- | -| 无默认来源 | 新 profile 没有已选来源 | 显示来源选择空状态;不发网络请求,也不兜底 | -| 选择 | 用户保存两个来源并选择其中一个 | 只请求已选来源;另一个来源保持已保存且不活动 | -| 选择 | 用户从来源 A 切换到来源 B | 取消 A 的请求;重置列表、搜索、分类和 cursor 后再请求 B | -| 选择 | 远程 manifest 包含 `selected`、`enabled`、`priority`、auth、header、script 或 install 字段 | Strict schema 拒绝该 manifest | -| 选择 | 首次运行时存在 DSH 1024Store adapter | 它作为选项可见,但在用户选择前保持未选择 | -| Query | 填充全部受支持参数 | URL encode 正确;`category`/`capability` 重复出现;其他字段只出现一次 | -| Query | 参数合法但不在 `query.supported` 中 | Host 针对该来源省略参数 | -| Query | `limit` 为 0、大于 200、非整数或超过 provider maximum | 拒绝非法值;合法但超过 `maxLimit` 的值在网络请求前收窄 | -| Query | Cursor 用于另一个来源或 filter 已改变 | 本地拒绝 cursor,不发送请求 | -| Query | 标准 response 超过有效请求值,或来源不支持 `limit` 时超过声明的 `defaultLimit` | 在更新 cache 或 UI 前拒绝 response | -| Query | 标准来源在有效 manifest limit 内合法返回 51–200 个条目 | 接受 response;50 只是当前发现页 UI 默认值,不是全局 contract 上限 | -| 完整索引 | 标准来源返回多个 cursor page | 每个 page 只校验一次;本地搜索与多分类 OR 筛选可以找到首个网络 page 之后的条目 | -| 远程分页 | 1024Store 有超过 100 个合法条目 | 每次请求最多标准化一个 provider page;**加载更多**使用下一枚不透明 cursor,绝不返回完整目录 | -| 分页 | 有界本地索引有超过 50 个匹配条目 | 首个可见 page 包含 50 条;**加载更多**通过 Host 自有本地 cursor 继续 | -| 刷新 | Page 或完整本地索引已被 cache,随后用户明确刷新 | 刷新绕过相关目录 HTTP cache,并替换适用的本地状态 | -| Schema | 合法 manifest、query、provider-page 和 snapshot fixture | 接受并 round-trip,不丢失已定义数据 | -| Schema | 包含未知字段或不支持的 major version | 拒绝对应 manifest/request/snapshot | -| Schema | Provider page 尝试提供 Host provenance | Strict wire schema 拒绝响应 | -| Schema | Provider page 包含合法 HTTPS `media.icon`;标准化 snapshot 包含安全 `assetRef` 与合法 role | 两份 fixture 都通过,并且标准化条目不包含远程 URL | -| Schema | 图标 URL 使用 HTTP/credential,或标准化 icon 用 URL/path/未知 role 代替不透明 `assetRef` | Strict schema 拒绝对应 response | -| Schema | Snapshot source record 或条目 provenance 与本地记录不同 | 按身份冒充拒绝整个来源结果 | -| Schema | 条目同时缺少 npm package 与 repository identity | 拒绝该条目/snapshot | -| Schema | Provider page 重复使用条目 `id`,或标准化后的 `provenance.itemId` 与条目 `id` 不同 | 拒绝整个来源 response | -| 标准化 | 1024Store fixture 使用其现有无 icon provider 格式 | Adapter 把 GitHub owner 头像解析为 `publisher-avatar`;同一已选来源条目有直接 provider icon 时优先使用直接图标 | -| 选择 | 已选来源 timeout | 展示该来源的安全错误和 Retry;不请求其他已保存来源作为兜底 | -| 身份 | 两个已保存来源列出同一规范 package | 来源切换前后仍保持隔离,绝不跨来源合并 | -| 身份 | 当前来源的两个条目只有名称相似 | 保持为不同 `{sourceRecordId, itemId}` 记录 | -| 安全 | URL 为 HTTP、带 URL credential、指向 loopback/private/link-local/metadata,或 redirect 到这些地址 | 在访问受保护资源前拒绝请求 | -| 安全 | DNS answer 变成禁止地址 | 阻止连接,并显示来源级安全错误 | -| 安全 | Body 过大、深度非法、非 JSON、过慢或含未知字段 | 中止/拒绝请求,不更新 cache 或 renderer | -| 安全 | 图标 redirect 到禁止 host、超过图片限制、media type 伪造或无法解码 | Asset 请求变为不可用,Renderer 改用本地占位图;Renderer 不接触远程 host,其余合法目录条目仍可用 | -| 安全 | 远程文本包含 HTML/script/Markdown injection | 作为惰性文本展示,不执行代码或 navigation | -| 安全 | 展示文本包含 control/Bidi 欺骗,或外部链接没有用户操作 | 拒绝/中和不安全文本;不自动打开链接 | -| 安全 | 来源尝试使用 cookie、auth、自定义 header 或远程 adapter 代码 | 该能力不存在,输入被拒绝 | -| 生命周期 | 请求中切换/清空选择或 dispose Host | Fetch abort,释放资源,适用时重置会话,迟到结果不能修改状态 | -| 安装 | Snapshot 包含 command-like string,或 URL query 被构造成命令 | 无法进入受管安装操作 | -| 安装 | 精确稳定 npm 版本或经过审核的 provider 验证缺失、非法,或在重新校验期间改变 | 安装保持禁用;不启动 package 操作 | -| 安装 | 目录 repository 与权威 npm package repository 的关系未验证或互相冲突 | 安装保持禁用;任何目录 claim 都不能压过权威复核 | -| 安装 | 用户选择当前来源中的一个条目 | 执行前确认展示精确来源、identity 和当前 profile | - -## 版本化扩展点 - -兼容的 v1 修订可以连同测试一起细化已经记录的 cache TTL、字节/条目预算、locale fallback 行为和 UI 布局,但不得弱化“同时最多一个已选来源”、用户明确选择、无默认、无兜底、strict validation、provenance 或远程数据不可执行等规则;改变这些边界必须经过契约修订评审。 diff --git a/dsh-community-market/docs/examples/catalog-provider-page.example.json b/dsh-community-market/docs/examples/catalog-provider-page.example.json deleted file mode 100644 index e0002a265d..0000000000 --- a/dsh-community-market/docs/examples/catalog-provider-page.example.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "schemaVersion": "1.0.0", - "generatedAt": "2026-08-17T08:00:00Z", - "revision": "2026-08-17T08:00:00Z", - "items": [ - { - "id": "better-sidebar", - "name": "dsh-plugin-better-sidebar", - "displayName": "Better Sidebar", - "summary": "Adds a configurable sidebar panel through declared UI capabilities.", - "homepage": "https://github.com/example/dsh-plugin-better-sidebar", - "latestVersion": "1.2.0", - "license": "MIT", - "categories": [ - "interface" - ], - "keywords": [ - "sidebar", - "panel" - ], - "repository": { - "url": "https://github.com/example/dsh-plugin-better-sidebar" - }, - "package": { - "registry": "npm", - "name": "dsh-plugin-better-sidebar" - }, - "publisher": { - "name": "Example Maintainers", - "url": "https://github.com/example" - }, - "media": { - "icon": { - "url": "https://plugins.example.org/assets/better-sidebar.png", - "alt": "Better Sidebar plugin icon" - } - }, - "capabilities": { - "required": [ - "ui.panel" - ], - "optional": [ - "storage.local" - ] - }, - "compatibility": { - "apiVersion": "1.x", - "hosts": [ - "gui>=2.0" - ] - }, - "updatedAt": "2026-08-16T10:30:00Z" - } - ], - "page": { - "nextCursor": "page_2", - "total": 42 - } -} diff --git a/dsh-community-market/docs/examples/catalog-provider-page.minimal.example.json b/dsh-community-market/docs/examples/catalog-provider-page.minimal.example.json deleted file mode 100644 index b47ca4384e..0000000000 --- a/dsh-community-market/docs/examples/catalog-provider-page.minimal.example.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "schemaVersion": "1.0.0", - "items": [ - { - "id": "better-sidebar", - "name": "dsh-plugin-better-sidebar", - "displayName": "Better Sidebar", - "summary": "Adds a configurable sidebar panel.", - "categories": [ - "interface" - ], - "package": { - "registry": "npm", - "name": "dsh-plugin-better-sidebar" - } - } - ], - "page": {} -} diff --git a/dsh-community-market/docs/examples/catalog-query.example.json b/dsh-community-market/docs/examples/catalog-query.example.json deleted file mode 100644 index 3b4d61864e..0000000000 --- a/dsh-community-market/docs/examples/catalog-query.example.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "q": "sidebar", - "category": [ - "interface" - ], - "limit": 50 -} diff --git a/dsh-community-market/docs/examples/catalog-snapshot.example.json b/dsh-community-market/docs/examples/catalog-snapshot.example.json deleted file mode 100644 index a19ccf75ad..0000000000 --- a/dsh-community-market/docs/examples/catalog-snapshot.example.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "schemaVersion": "1.0.0", - "source": { - "sourceRecordId": "018f1f77-a5c4-7b73-a9ae-0242ac120002", - "providerId": "org.example.community-catalog", - "adapterId": "market.standard-v1", - "registrationKind": "user-added", - "fetchedAt": "2026-08-17T08:00:01Z", - "finalUrl": "https://plugins.example.org/v1/plugins", - "providerGeneratedAt": "2026-08-17T08:00:00Z", - "providerRevision": "2026-08-17T08:00:00Z" - }, - "items": [ - { - "id": "better-sidebar", - "name": "dsh-plugin-better-sidebar", - "displayName": "Better Sidebar", - "summary": "Adds a configurable sidebar panel through declared UI capabilities.", - "homepage": "https://github.com/example/dsh-plugin-better-sidebar", - "latestVersion": "1.2.0", - "license": "MIT", - "categories": [ - "interface" - ], - "keywords": [ - "sidebar", - "panel" - ], - "repository": { - "url": "https://github.com/example/dsh-plugin-better-sidebar" - }, - "package": { - "registry": "npm", - "name": "dsh-plugin-better-sidebar" - }, - "publisher": { - "name": "Example Maintainers", - "url": "https://github.com/example" - }, - "media": { - "icon": { - "assetRef": "mktimg_0123456789abcdefghijklmnopqrstuv", - "role": "plugin-icon", - "alt": "Better Sidebar plugin icon" - } - }, - "capabilities": { - "required": [ - "ui.panel" - ], - "optional": [ - "storage.local" - ] - }, - "compatibility": { - "apiVersion": "1.x", - "hosts": [ - "gui>=2.0" - ] - }, - "updatedAt": "2026-08-16T10:30:00Z", - "provenance": { - "sourceRecordId": "018f1f77-a5c4-7b73-a9ae-0242ac120002", - "providerId": "org.example.community-catalog", - "itemId": "better-sidebar" - } - } - ], - "page": { - "nextCursor": "page_2", - "total": 42 - } -} diff --git a/dsh-community-market/docs/examples/catalog-source.example.json b/dsh-community-market/docs/examples/catalog-source.example.json deleted file mode 100644 index b6671e1a47..0000000000 --- a/dsh-community-market/docs/examples/catalog-source.example.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "manifestVersion": "1.0.0", - "providerId": "org.example.community-catalog", - "name": "Example Community Catalog", - "attribution": { - "name": "Example Plugin Community", - "url": "https://plugins.example.org/" - }, - "transport": { - "kind": "https-json", - "endpoint": "https://plugins.example.org/v1/plugins", - "method": "GET" - }, - "query": { - "supported": [ - "q", - "category", - "cursor", - "limit" - ], - "defaultLimit": 50, - "maxLimit": 50, - "sorts": [] - } -} diff --git a/dsh-community-market/docs/install-and-uninstall.i18n.yaml b/dsh-community-market/docs/install-and-uninstall.i18n.yaml deleted file mode 100644 index fa09fa6282..0000000000 --- a/dsh-community-market/docs/install-and-uninstall.i18n.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Bilingual-pair consistency record. Both languages carry equal authority. -# Update both files and record their Git blob hashes after editing either side. -install-and-uninstall.md: b8d19fb5678b03ac165ac485a073545c0da18a53 -install-and-uninstall.zh.md: a433e3dfa62fe255a392251d49460cf25325cda9 diff --git a/dsh-community-market/docs/install-and-uninstall.md b/dsh-community-market/docs/install-and-uninstall.md deleted file mode 100644 index b8d19fb567..0000000000 --- a/dsh-community-market/docs/install-and-uninstall.md +++ /dev/null @@ -1,68 +0,0 @@ -# Install and uninstall - -[中文说明](install-and-uninstall.zh.md) - -This historical draft describes the removed Market prototype. It is not an implementation guide for the current Desktop; `desktopPnpm` is no longer available. - -## Views - -| View | Source of truth | Available operations | -| --- | --- | --- | -| Discover | Normalized selected catalog source | Browse details and request install preview | -| Installable | Catalog entries with one npm package identity | Request install preview | -| Installed | Active Profile direct dependencies and bundle list | Uninstall removable dependencies; core bundles are read-only | -| Sources | User-owned catalog source settings | Add, select, order, and remove sources | - -Installed state is independent of the selected catalog and of which market performed the installation. - -## Install flow - -1. The user selects a catalog item. The Renderer sends only `sourceRecordId` and `itemId`. -2. The Host resolves the normalized npm package identity it previously observed. -3. The Host requests `https://registry.npmjs.org//latest` and requires the same package name, an exact stable version, and a valid `dsh.bundle.patch` declaration. -4. The confirmation shows the package, exact version, current Profile, and preview expiry. -5. On confirmation, the Host consumes the one-shot `previewId` and calls `desktopPnpm.run()` with Host-owned argv for an exact `pnpm add`. -6. The Host reconciles the package into `dsh.profile.bundles` and confirms that it is now a direct Profile dependency. - -The source's listed version is never used as the install target. Repository equality, deprecation metadata, lifecycle scripts, engine ranges, integrity metadata, and provider verification flags do not block the operation. Provider command strings are discarded. - -Market installation creates no receipt, checkpoint, retry, cleanup, or rollback operation. Desktop's ordinary Profile checkpoints cover the resulting state. - -## Automatic-install conditions - -A catalog entry can reach automatic install preview only when: - -- exactly one valid npm package name is normalized from the entry; -- the package is not `dsh-plugin-desktop` or `dsh-community-market`; -- npm `latest` is an exact stable version for that same package; and -- the npm manifest declares a safe relative DSH bundle patch path. - -Failure keeps the item browseable and may expose a display-only manual command. - -## Uninstall flow - -1. Desktop reads the active Profile's `dependencies` and `dsh.profile.bundles`. -2. Each direct bundle receives a generation-scoped opaque `bundleId`. Product-owned bundles are read-only; other direct dependencies are removable. -3. The Renderer submits only that `bundleId`. -4. The Host resolves it again against current inventory, verifies that the package is still a direct dependency, and returns a one-shot confirmation. -5. On confirmation, the Host calls `desktopPnpm.run(['remove', packageName])`, removes the bundle entry, and confirms that the Profile no longer references the package. - -This flow applies equally to plugins installed by Community Market, another plugin market, or the DSH CLI. Market offers no enable or disable action. - -## Manual fallback - -If automatic preview is unavailable, the Host may construct a bounded display-only npm command from normalized identity. **Open DSH Terminal** opens the terminal only; it sends no package command, path, or Profile and performs no mutation. - -## Failure behavior - -| Failure | Result | -| --- | --- | -| npm latest cannot be resolved or is not a stable DSH plugin | No package operation starts | -| Profile changes after preview | The one-shot preview is rejected | -| pnpm fails | The error is reported; Market performs no automatic cleanup or rollback | -| Profile reconciliation fails after pnpm | The error is reported for diagnosis or explicit Recovery checkpoint restore | - -For a pnpm process failure, the Host retains only a bounded tail of stdout and stderr. It records that diagnostic in the Desktop log and returns it to the local failure dialog. The dialog shows the verified package command and can open DSH Terminal, but never pastes, executes, or silently retries it. -| Renderer closes after confirmation | The Host-owned package operation continues; only the response may be lost | - -After a successful mutation, the user may restart now or later. Restart is never silent. diff --git a/dsh-community-market/docs/install-and-uninstall.zh.md b/dsh-community-market/docs/install-and-uninstall.zh.md deleted file mode 100644 index a433e3dfa6..0000000000 --- a/dsh-community-market/docs/install-and-uninstall.zh.md +++ /dev/null @@ -1,68 +0,0 @@ -# 安装与卸载 - -[English](install-and-uninstall.md) - -本文说明 DSH Community Market 的 package 操作边界。 - -## 视图 - -| 视图 | 事实来源 | 可用操作 | -| --- | --- | --- | -| 发现 | 当前目录来源的标准化数据 | 查看详情并请求安装 preview | -| 可安装 | 提供唯一 npm package 身份的目录条目 | 请求安装 preview | -| 已安装 | 当前 Profile 的直接依赖和 bundle 列表 | 卸载可移除依赖;核心 bundle 只读 | -| 来源 | 用户拥有的目录来源设置 | 添加、选择、排序和移除来源 | - -已安装状态与当前目录来源无关,也与最初由哪个市场安装无关。 - -## 安装流程 - -1. 用户选择目录条目,Renderer 只发送 `sourceRecordId` 和 `itemId`。 -2. Host 解析自己此前观察到的标准化 npm package 身份。 -3. Host 请求 `https://registry.npmjs.org//latest`,要求返回相同 package name、精确稳定版本和合法的 `dsh.bundle.patch` 声明。 -4. 确认框展示 package、精确版本、当前 Profile 和 preview 过期时间。 -5. 用户确认后,Host 消费一次性 `previewId`,使用自己拥有的 argv 调用 `desktopPnpm.run()`,执行精确版本的 `pnpm add`。 -6. Host 把 package 写入 `dsh.profile.bundles`,并确认它已经成为 Profile 直接依赖。 - -来源列出的版本永远不会成为安装目标。仓库是否一致、deprecated metadata、lifecycle script、engine 范围、integrity metadata 和 provider 验证标记都不会阻止操作;provider 命令字符串会被丢弃。 - -Market 安装不会创建 receipt、checkpoint、重试、清理或回滚 operation。结果状态由 Desktop 普通的 Profile checkpoint 统一覆盖。 - -## 自动安装条件 - -目录条目只有满足以下条件才能进入自动安装 preview: - -- 条目能标准化出且只标准化出一个合法 npm package name; -- package 不是 `dsh-plugin-desktop` 或 `dsh-community-market`; -- npm `latest` 对同一 package 返回精确稳定版本;以及 -- npm manifest 声明安全的相对 DSH bundle patch 路径。 - -失败时条目仍可浏览,也可以显示只用于展示的手动命令。 - -## 卸载流程 - -1. Desktop 读取当前 Profile 的 `dependencies` 和 `dsh.profile.bundles`。 -2. 每个直接 bundle 获得当前 generation 有效的不透明 `bundleId`。产品自有 bundle 只读,其他直接依赖可以移除。 -3. Renderer 只提交该 `bundleId`。 -4. Host 根据当前清单重新解析目标,确认 package 仍是直接依赖,并返回一次性确认。 -5. 用户确认后,Host 调用 `desktopPnpm.run(['remove', packageName])`,移除 bundle 条目,并确认 Profile 不再引用该 package。 - -无论插件由 Community Market、其他插件市场还是 DSH CLI 安装,都使用同一流程。Market 不提供启用或禁用操作。 - -## 手动兜底 - -自动 preview 不可用时,Host 可以根据标准化身份构造一条有界且只用于展示的 npm 命令。**打开 DSH 终端**只打开终端,不会提交 package 命令、路径或 Profile,也不会执行修改。 - -## 失败行为 - -| 故障 | 结果 | -| --- | --- | -| 无法解析 npm latest,或它不是稳定 DSH 插件 | 不启动 package 操作 | -| Preview 后 Profile 发生变化 | 拒绝一次性 preview | -| pnpm 失败 | 报告错误;Market 不自动清理或回滚 | -| pnpm 后 Profile reconcile 失败 | 报告错误,供诊断或显式恢复 checkpoint | - -pnpm 进程失败时,Host 只保留有上限的 stdout/stderr 尾部,将该诊断写入 Desktop 日志,并返回给本地失败弹窗。弹窗显示已验证 package 对应的命令,也可以打开 DSH 终端,但不会粘贴、执行或静默重试该命令。 -| 用户确认后 Renderer 关闭 | Host 持有的 package 操作继续,仅可能丢失响应 | - -修改成功后,用户可以立即重启或稍后重启;重启绝不会静默进行。 diff --git a/dsh-community-market/docs/market-shell.i18n.yaml b/dsh-community-market/docs/market-shell.i18n.yaml deleted file mode 100644 index 12c3c1a692..0000000000 --- a/dsh-community-market/docs/market-shell.i18n.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Bilingual-pair consistency record. Both languages carry equal authority. -# Update both files and record their Git blob hashes after editing either side. -market-shell.md: 895c089770fdd0f9802fd68eb8d376140be6022e -market-shell.zh.md: 0407e4183a44c8e65201ff11ca70aa1fd4641c85 diff --git a/dsh-community-market/docs/market-shell.md b/dsh-community-market/docs/market-shell.md deleted file mode 100644 index 895c089770..0000000000 --- a/dsh-community-market/docs/market-shell.md +++ /dev/null @@ -1,55 +0,0 @@ -# Community Market shell - -[中文说明](market-shell.zh.md) - -Status: historical design draft only. No Market runtime ships in the current Desktop. References to `desktopPnpm` and recovery services below describe the removed prototype; they are not available APIs. - -## Ownership - -`dsh-community-market` owns catalog-source settings, source adapters, normalized discovery, the Market Client surface, npm latest preview, and Profile package-operation orchestration. It does not own Profile storage, pnpm execution, recovery checkpoints, terminal windows, or restart implementation. - -## Runtime shape - -```text -catalog source -> Host adapter -> normalized catalog -> Market Client - | - +-> npm latest preview - -Desktop Profile inventory -> Installed view -> opaque bundleId -> pnpm remove -``` - -The Client receives normalized data and opaque operation identities. It never receives a package-manager capability. - -## Source behavior - -Standard cursor-based sources and dshfind are scanned into a bounded local index. Search, category filters, and visible pagination use that index. - -DSH 1024Store discovery uses the provider's current paginated v2 API for every query, including the unfiltered directory. Single-category filters are forwarded directly. Multi-category OR filters merge bounded provider-ranked prefixes and retain an opaque local cursor. Installable requests 200 remote registry entries per batch and keeps only direct npm targets from that page; the Client requests the next opaque cursor instead of materializing the complete directory in the Host or Renderer. - -Provider commands are discarded. The reviewed 1024Store adapter may parse one exact inert command shape solely to recover an npm package name; it never forwards or executes that command. A source can supply catalog identity but cannot supply executable argv, credentials, adapter code, source selection, or install authority. - -## Install behavior - -An installable catalog entry contributes only one npm package identity. Install preview resolves npm's official `latest` manifest and requires: - -- matching npm package name; -- exact stable version; and -- valid `dsh.bundle.patch`. - -The preview binds these facts, the observed catalog item, and the active Profile to a short-lived one-shot token. Execution calls only `desktopPnpm.run(argv)`, saves the exact version, and reconciles `dsh.profile.bundles`. - -There are no Market receipts or install-specific protection and recovery paths. Source version, repository matching, lifecycle scripts, deprecation, engine ranges, and provider verification badges do not gate installation. - -## Installed inventory and uninstall - -Desktop inventory reads direct Profile dependencies and bundles. It therefore includes plugins installed by this Market, other markets, and the DSH CLI. Product-owned bundles are read-only. Other direct plugin dependencies receive an opaque generation-scoped `bundleId` and offer uninstall only. - -The Host resolves `bundleId` immediately before preview and rechecks the direct dependency before execution. Uninstall uses `desktopPnpm.run(['remove', packageName])` and removes the bundle reference. The Market does not expose enable or disable. - -## Failure boundary - -Browsing remains available when Desktop package capabilities are absent. A source failure never blocks Desktop startup. Package-operation errors do not trigger automatic cleanup or rollback. Desktop's three healthy-start Profile checkpoints are the single recovery mechanism, and restoration remains an explicit Recovery-page action. - -## Headless requirements - -Contract generation, type checking, unit tests, package builds, export verification, and Loader smoke tests must remain headless-safe. Tests must not launch Electron or a graphical application. diff --git a/dsh-community-market/docs/market-shell.zh.md b/dsh-community-market/docs/market-shell.zh.md deleted file mode 100644 index 0407e4183a..0000000000 --- a/dsh-community-market/docs/market-shell.zh.md +++ /dev/null @@ -1,55 +0,0 @@ -# Community Market shell - -[English](market-shell.md) - -状态:仅保留历史设计草案。当前 Desktop 不包含 Market 运行代码。下文的 `desktopPnpm` 和恢复服务属于已删除的原型,不是可用接口。 - -## 归属边界 - -`dsh-community-market` 负责目录来源设置、来源 adapter、标准化发现、Market Client 界面、npm latest preview 和 Profile package 操作编排。它不拥有 Profile 存储、pnpm 执行、恢复 checkpoint、终端窗口或重启实现。 - -## Runtime 形态 - -```text -目录来源 -> Host adapter -> 标准化目录 -> Market Client - | - +-> npm latest preview - -Desktop Profile 清单 -> 已安装视图 -> 不透明 bundleId -> pnpm remove -``` - -Client 只接收标准化数据和不透明 operation 身份,永远不会获得 package-manager 能力。 - -## 来源行为 - -标准 cursor 来源和 dshfind 会被扫描为有界本地索引,搜索、分类筛选和可见分页使用该索引。 - -DSH 1024Store 的发现页对所有查询都使用 provider 当前的分页 v2 API,包括无筛选目录。单分类筛选直接转发;多分类 OR 筛选会合并有界的 provider 排序前缀,并使用本地不透明 cursor。“可安装”每批请求 200 条远程 registry 记录,并只保留该 page 里的直接 npm 目标;Client 通过下一枚不透明 cursor 继续加载,而不会在 Host 或 Renderer 中物化完整目录。 - -Provider 命令会被丢弃。经过审查的 1024Store adapter 只会解析一种严格的惰性命令形状来取得 npm package name,绝不会转发或执行该命令。来源可以提供目录身份,但不能提供可执行 argv、凭据、adapter 代码、来源选择或安装权限。 - -## 安装行为 - -可安装目录条目只贡献一个 npm package 身份。安装 preview 解析 npm 官方 `latest` manifest,并要求: - -- npm package name 一致; -- 版本精确且稳定;以及 -- `dsh.bundle.patch` 合法。 - -Preview 把这些事实、已经观察到的目录条目和当前 Profile 绑定到短时一次性 token。执行阶段只调用 `desktopPnpm.run(argv)`,保存精确版本并 reconcile `dsh.profile.bundles`。 - -Market 不再使用 receipt 或安装专用保护、恢复路径。来源版本、仓库匹配、lifecycle script、deprecated、engine 范围和 provider 验证徽章都不作为安装门槛。 - -## 已安装清单与卸载 - -Desktop 清单读取 Profile 的直接依赖和 bundle,因此会包含本 Market、其他市场和 DSH CLI 安装的插件。产品自有 bundle 只读;其他直接插件依赖获得当前 generation 有效的不透明 `bundleId`,并且只提供卸载。 - -Host 在 preview 前解析 `bundleId`,在执行前重新检查直接依赖。卸载使用 `desktopPnpm.run(['remove', packageName])` 并移除 bundle 引用。Market 不暴露启用或禁用。 - -## 故障边界 - -Desktop package 能力不可用时仍可浏览。来源故障不会阻止 Desktop 启动。Package 操作错误不会触发自动清理或回滚。Desktop 的三个健康启动 Profile checkpoint 是唯一恢复机制,恢复仍然是恢复页面中的显式操作。 - -## Headless 要求 - -合同生成、类型检查、单元测试、package build、export 验证和 Loader smoke test 都必须保持 headless-safe。测试不得启动 Electron 或图形应用。 diff --git a/dsh-community-market/docs/schemas/catalog-provider-page.schema.json b/dsh-community-market/docs/schemas/catalog-provider-page.schema.json deleted file mode 100644 index 30768743b1..0000000000 --- a/dsh-community-market/docs/schemas/catalog-provider-page.schema.json +++ /dev/null @@ -1,376 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "urn:dsh-community-market:schema:catalog-provider-page:1.0.0", - "title": "DSH Community Market standard provider page", - "description": "The untrusted page JSON returned by one standard HTTPS catalog endpoint. Only schemaVersion, items, and page are required. A page may contain at most 200 items and must also respect the effective requested or declared default limit. Host-observed provenance is intentionally absent and is injected only after validation.", - "type": "object", - "additionalProperties": false, - "required": [ - "schemaVersion", - "items", - "page" - ], - "properties": { - "schemaVersion": { - "const": "1.0.0" - }, - "generatedAt": { - "type": "string", - "format": "date-time" - }, - "revision": { - "type": "string", - "minLength": 1, - "maxLength": 160 - }, - "items": { - "type": "array", - "maxItems": 200, - "items": { - "$ref": "#/$defs/item" - } - }, - "page": { - "$ref": "#/$defs/page" - } - }, - "$defs": { - "identifier": { - "type": "string", - "minLength": 1, - "maxLength": 160, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+-]*$" - }, - "httpsUri": { - "type": "string", - "format": "uri", - "maxLength": 2048, - "pattern": "^https://(?![^/?#]*@)(?![^/?#]*:)[^#]+$" - }, - "plainText": { - "type": "string", - "pattern": "^[^\\u0000-\\u001F\\u007F-\\u009F\\u202A-\\u202E\\u2066-\\u2069]*$" - }, - "mediaAlt": { - "allOf": [ - { - "$ref": "#/$defs/plainText" - }, - { - "type": "string", - "minLength": 1, - "maxLength": 240 - } - ] - }, - "remoteIconCandidate": { - "type": "object", - "description": "A provider-declared remote plugin-icon candidate. The URL must share the final provider-page response origin. The Host resolves and validates it before it can enter a normalized snapshot; the Renderer never receives this URL.", - "additionalProperties": false, - "required": [ - "url" - ], - "properties": { - "url": { - "$ref": "#/$defs/httpsUri" - }, - "alt": { - "$ref": "#/$defs/mediaAlt" - } - } - }, - "media": { - "type": "object", - "description": "Optional provider-declared plugin media. In v1, only a direct plugin icon is standardized.", - "additionalProperties": false, - "required": [ - "icon" - ], - "properties": { - "icon": { - "$ref": "#/$defs/remoteIconCandidate" - } - } - }, - "page": { - "type": "object", - "description": "Use an empty object when there is no next page. nextCursor is opaque and belongs only to this source and effective query.", - "additionalProperties": false, - "properties": { - "nextCursor": { - "type": "string", - "minLength": 1, - "maxLength": 2048 - }, - "total": { - "type": "integer", - "minimum": 0 - } - } - }, - "repository": { - "type": "object", - "additionalProperties": false, - "required": [ - "url" - ], - "properties": { - "url": { - "$ref": "#/$defs/httpsUri" - }, - "subdirectory": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))[^\\\\]+$" - } - } - }, - "installSource": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "commit" - ], - "properties": { - "kind": { - "const": "github" - }, - "commit": { - "type": "string", - "pattern": "^[0-9a-f]{40}$" - } - } - }, - "package": { - "type": "object", - "additionalProperties": false, - "required": [ - "registry", - "name" - ], - "properties": { - "registry": { - "const": "npm" - }, - "name": { - "type": "string", - "minLength": 1, - "maxLength": 214, - "pattern": "^(?:@[a-z0-9][a-z0-9._-]*/)?[a-z0-9][a-z0-9._-]*$" - } - } - }, - "publisher": { - "type": "object", - "additionalProperties": false, - "required": [ - "name" - ], - "properties": { - "name": { - "allOf": [ - { - "$ref": "#/$defs/plainText" - }, - { - "type": "string", - "minLength": 1, - "maxLength": 120 - } - ] - }, - "url": { - "$ref": "#/$defs/httpsUri" - } - } - }, - "capabilityList": { - "type": "array", - "maxItems": 64, - "uniqueItems": true, - "items": { - "type": "string", - "minLength": 1, - "maxLength": 96, - "pattern": "^[a-z][a-z0-9-]*(?:\\.[a-z][a-z0-9-]*)+$" - } - }, - "categoryId": { - "type": "string", - "minLength": 1, - "maxLength": 64, - "pattern": "^[a-z0-9][a-z0-9._:-]*$" - }, - "capabilities": { - "type": "object", - "additionalProperties": false, - "properties": { - "required": { - "$ref": "#/$defs/capabilityList" - }, - "optional": { - "$ref": "#/$defs/capabilityList" - } - } - }, - "compatibility": { - "type": "object", - "additionalProperties": false, - "properties": { - "apiVersion": { - "type": "string", - "minLength": 1, - "maxLength": 64 - }, - "hosts": { - "type": "array", - "maxItems": 32, - "uniqueItems": true, - "items": { - "type": "string", - "minLength": 1, - "maxLength": 96 - } - } - } - }, - "item": { - "type": "object", - "additionalProperties": false, - "required": [ - "id", - "name", - "displayName", - "summary" - ], - "properties": { - "id": { - "$ref": "#/$defs/identifier" - }, - "name": { - "allOf": [ - { - "$ref": "#/$defs/plainText" - }, - { - "type": "string", - "minLength": 1, - "maxLength": 160 - } - ] - }, - "displayName": { - "allOf": [ - { - "$ref": "#/$defs/plainText" - }, - { - "type": "string", - "minLength": 1, - "maxLength": 120 - } - ] - }, - "summary": { - "allOf": [ - { - "$ref": "#/$defs/plainText" - }, - { - "type": "string", - "minLength": 1, - "maxLength": 1000 - } - ] - }, - "description": { - "allOf": [ - { - "$ref": "#/$defs/plainText" - }, - { - "type": "string", - "maxLength": 5000 - } - ] - }, - "homepage": { - "$ref": "#/$defs/httpsUri" - }, - "latestVersion": { - "type": "string", - "minLength": 1, - "maxLength": 64 - }, - "license": { - "type": "string", - "minLength": 1, - "maxLength": 80 - }, - "categories": { - "type": "array", - "maxItems": 32, - "uniqueItems": true, - "items": { - "$ref": "#/$defs/categoryId" - } - }, - "keywords": { - "type": "array", - "maxItems": 64, - "uniqueItems": true, - "items": { - "type": "string", - "minLength": 1, - "maxLength": 64 - } - }, - "repository": { - "$ref": "#/$defs/repository" - }, - "installSource": { - "$ref": "#/$defs/installSource" - }, - "package": { - "$ref": "#/$defs/package" - }, - "publisher": { - "$ref": "#/$defs/publisher" - }, - "media": { - "$ref": "#/$defs/media" - }, - "capabilities": { - "$ref": "#/$defs/capabilities" - }, - "compatibility": { - "$ref": "#/$defs/compatibility" - }, - "updatedAt": { - "type": "string", - "format": "date-time" - } - }, - "anyOf": [ - { - "properties": { - "repository": true - }, - "required": [ - "repository" - ] - }, - { - "properties": { - "package": true - }, - "required": [ - "package" - ] - } - ] - } - } -} diff --git a/dsh-community-market/docs/schemas/catalog-query.schema.json b/dsh-community-market/docs/schemas/catalog-query.schema.json deleted file mode 100644 index 91d42b827f..0000000000 --- a/dsh-community-market/docs/schemas/catalog-query.schema.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "urn:dsh-community-market:schema:catalog-query:1.0.0", - "title": "DSH Community Market normalized catalog query", - "description": "The normalized query accepted by the currently selected catalog adapter. The standard HTTPS endpoint encodes category and capability values as repeated parameters; repeated category values use OR semantics. The current discovery UI defaults limit to 50, while the contract permits values through 200.", - "type": "object", - "additionalProperties": false, - "properties": { - "q": { - "type": "string", - "minLength": 1, - "maxLength": 200, - "pattern": "^\\S(?:[\\s\\S]*\\S)?$" - }, - "category": { - "type": "array", - "description": "A multi-select OR filter: an item matches when it belongs to any requested category.", - "maxItems": 20, - "uniqueItems": true, - "items": { - "$ref": "#/$defs/categoryId" - } - }, - "capability": { - "type": "array", - "maxItems": 32, - "uniqueItems": true, - "items": { - "$ref": "#/$defs/capabilityId" - } - }, - "cursor": { - "type": "string", - "minLength": 1, - "maxLength": 2048 - }, - "limit": { - "type": "integer", - "minimum": 1, - "maximum": 200, - "default": 50 - }, - "sort": { - "enum": [ - "relevance", - "updated", - "name", - "downloads" - ], - "default": "relevance" - }, - "locale": { - "type": "string", - "minLength": 2, - "maxLength": 35, - "pattern": "^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$", - "description": "A BCP 47-like language tag." - } - }, - "$defs": { - "categoryId": { - "type": "string", - "minLength": 1, - "maxLength": 64, - "pattern": "^[a-z0-9][a-z0-9._:-]*$" - }, - "capabilityId": { - "type": "string", - "minLength": 3, - "maxLength": 96, - "pattern": "^[a-z][a-z0-9-]*(?:\\.[a-z][a-z0-9-]*)+$" - } - } -} diff --git a/dsh-community-market/docs/schemas/catalog-snapshot.schema.json b/dsh-community-market/docs/schemas/catalog-snapshot.schema.json deleted file mode 100644 index ffcda5f1e4..0000000000 --- a/dsh-community-market/docs/schemas/catalog-snapshot.schema.json +++ /dev/null @@ -1,467 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "urn:dsh-community-market:schema:catalog-snapshot:1.0.0", - "title": "DSH Community Market normalized catalog snapshot", - "description": "A normalized, non-executable page produced locally by a catalog adapter. Host-observed provenance is retained for every item.", - "type": "object", - "additionalProperties": false, - "required": [ - "schemaVersion", - "source", - "items", - "page" - ], - "properties": { - "schemaVersion": { - "const": "1.0.0" - }, - "source": { - "$ref": "#/$defs/source" - }, - "items": { - "type": "array", - "maxItems": 200, - "items": { - "$ref": "#/$defs/item" - } - }, - "page": { - "$ref": "#/$defs/page" - } - }, - "$defs": { - "providerId": { - "type": "string", - "minLength": 3, - "maxLength": 128, - "pattern": "^[a-z0-9]+(?:[.-][a-z0-9]+)+$" - }, - "sourceRecordId": { - "type": "string", - "format": "uuid", - "description": "Opaque local registration identity generated by the Host, never supplied by a provider." - }, - "identifier": { - "type": "string", - "minLength": 1, - "maxLength": 160, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+-]*$" - }, - "httpsUri": { - "type": "string", - "format": "uri", - "maxLength": 2048, - "pattern": "^https://(?![^/?#]*@)(?![^/?#]*:)[^#]+$" - }, - "plainText": { - "type": "string", - "pattern": "^[^\\u0000-\\u001F\\u007F-\\u009F\\u202A-\\u202E\\u2066-\\u2069]*$" - }, - "mediaAlt": { - "allOf": [ - { - "$ref": "#/$defs/plainText" - }, - { - "type": "string", - "minLength": 1, - "maxLength": 240 - } - ] - }, - "assetRef": { - "type": "string", - "description": "An opaque Host-managed media reference. It is neither a remote URL nor a filesystem path and is the only media locator exposed to the Renderer.", - "minLength": 39, - "maxLength": 39, - "pattern": "^mktimg_[A-Za-z0-9_-]{32}$" - }, - "resolvedIcon": { - "type": "object", - "description": "A Host-resolved icon safe for Renderer consumption. The role distinguishes a plugin-owned icon from a publisher-avatar fallback.", - "additionalProperties": false, - "required": [ - "assetRef", - "role" - ], - "properties": { - "assetRef": { - "$ref": "#/$defs/assetRef" - }, - "role": { - "enum": [ - "plugin-icon", - "publisher-avatar" - ] - }, - "alt": { - "$ref": "#/$defs/mediaAlt" - } - } - }, - "media": { - "type": "object", - "description": "Optional Host-resolved plugin media. Version 1 standardizes only the icon slot.", - "additionalProperties": false, - "required": [ - "icon" - ], - "properties": { - "icon": { - "$ref": "#/$defs/resolvedIcon" - } - } - }, - "source": { - "type": "object", - "additionalProperties": false, - "required": [ - "sourceRecordId", - "providerId", - "adapterId", - "registrationKind", - "fetchedAt", - "finalUrl" - ], - "properties": { - "sourceRecordId": { - "$ref": "#/$defs/sourceRecordId" - }, - "providerId": { - "$ref": "#/$defs/providerId" - }, - "adapterId": { - "type": "string", - "minLength": 1, - "maxLength": 96, - "pattern": "^[a-z0-9]+(?:[.-][a-z0-9]+)+$" - }, - "registrationKind": { - "enum": [ - "user-added", - "built-in" - ] - }, - "fetchedAt": { - "type": "string", - "format": "date-time" - }, - "finalUrl": { - "$ref": "#/$defs/httpsUri" - }, - "providerGeneratedAt": { - "type": "string", - "format": "date-time" - }, - "providerRevision": { - "type": "string", - "minLength": 1, - "maxLength": 160 - } - } - }, - "page": { - "type": "object", - "additionalProperties": false, - "properties": { - "nextCursor": { - "type": "string", - "minLength": 1, - "maxLength": 2048 - }, - "total": { - "type": "integer", - "minimum": 0 - } - } - }, - "provenance": { - "type": "object", - "additionalProperties": false, - "required": [ - "sourceRecordId", - "providerId", - "itemId" - ], - "properties": { - "sourceRecordId": { - "$ref": "#/$defs/sourceRecordId" - }, - "providerId": { - "$ref": "#/$defs/providerId" - }, - "itemId": { - "$ref": "#/$defs/identifier" - } - } - }, - "repository": { - "type": "object", - "additionalProperties": false, - "required": [ - "url" - ], - "properties": { - "url": { - "$ref": "#/$defs/httpsUri" - }, - "subdirectory": { - "type": "string", - "minLength": 1, - "maxLength": 240, - "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))[^\\\\]+$" - } - } - }, - "installSource": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "commit" - ], - "properties": { - "kind": { - "const": "github" - }, - "commit": { - "type": "string", - "pattern": "^[0-9a-f]{40}$" - } - } - }, - "package": { - "type": "object", - "additionalProperties": false, - "required": [ - "registry", - "name" - ], - "properties": { - "registry": { - "const": "npm" - }, - "name": { - "type": "string", - "minLength": 1, - "maxLength": 214, - "pattern": "^(?:@[a-z0-9][a-z0-9._-]*/)?[a-z0-9][a-z0-9._-]*$" - } - } - }, - "publisher": { - "type": "object", - "additionalProperties": false, - "required": [ - "name" - ], - "properties": { - "name": { - "allOf": [ - { - "$ref": "#/$defs/plainText" - }, - { - "type": "string", - "minLength": 1, - "maxLength": 120 - } - ] - }, - "url": { - "$ref": "#/$defs/httpsUri" - } - } - }, - "capabilities": { - "type": "object", - "additionalProperties": false, - "properties": { - "required": { - "$ref": "#/$defs/capabilityList" - }, - "optional": { - "$ref": "#/$defs/capabilityList" - } - } - }, - "capabilityList": { - "type": "array", - "maxItems": 64, - "uniqueItems": true, - "items": { - "type": "string", - "minLength": 1, - "maxLength": 96, - "pattern": "^[a-z][a-z0-9-]*(?:\\.[a-z][a-z0-9-]*)+$" - } - }, - "categoryId": { - "type": "string", - "minLength": 1, - "maxLength": 64, - "pattern": "^[a-z0-9][a-z0-9._:-]*$" - }, - "compatibility": { - "type": "object", - "additionalProperties": false, - "properties": { - "apiVersion": { - "type": "string", - "minLength": 1, - "maxLength": 64 - }, - "hosts": { - "type": "array", - "maxItems": 32, - "uniqueItems": true, - "items": { - "type": "string", - "minLength": 1, - "maxLength": 96 - } - } - } - }, - "item": { - "type": "object", - "additionalProperties": false, - "required": [ - "id", - "name", - "displayName", - "summary", - "provenance" - ], - "properties": { - "id": { - "$ref": "#/$defs/identifier" - }, - "name": { - "allOf": [ - { - "$ref": "#/$defs/plainText" - }, - { - "type": "string", - "minLength": 1, - "maxLength": 160 - } - ] - }, - "displayName": { - "allOf": [ - { - "$ref": "#/$defs/plainText" - }, - { - "type": "string", - "minLength": 1, - "maxLength": 120 - } - ] - }, - "summary": { - "allOf": [ - { - "$ref": "#/$defs/plainText" - }, - { - "type": "string", - "minLength": 1, - "maxLength": 1000 - } - ] - }, - "description": { - "allOf": [ - { - "$ref": "#/$defs/plainText" - }, - { - "type": "string", - "maxLength": 5000 - } - ] - }, - "homepage": { - "$ref": "#/$defs/httpsUri" - }, - "latestVersion": { - "type": "string", - "minLength": 1, - "maxLength": 64 - }, - "license": { - "type": "string", - "minLength": 1, - "maxLength": 80 - }, - "categories": { - "type": "array", - "maxItems": 32, - "uniqueItems": true, - "items": { - "$ref": "#/$defs/categoryId" - } - }, - "keywords": { - "type": "array", - "maxItems": 64, - "uniqueItems": true, - "items": { - "type": "string", - "minLength": 1, - "maxLength": 64 - } - }, - "repository": { - "$ref": "#/$defs/repository" - }, - "installSource": { - "$ref": "#/$defs/installSource" - }, - "package": { - "$ref": "#/$defs/package" - }, - "publisher": { - "$ref": "#/$defs/publisher" - }, - "media": { - "$ref": "#/$defs/media" - }, - "capabilities": { - "$ref": "#/$defs/capabilities" - }, - "compatibility": { - "$ref": "#/$defs/compatibility" - }, - "updatedAt": { - "type": "string", - "format": "date-time" - }, - "provenance": { - "$ref": "#/$defs/provenance" - } - }, - "anyOf": [ - { - "properties": { - "repository": true - }, - "required": [ - "repository" - ] - }, - { - "properties": { - "package": true - }, - "required": [ - "package" - ] - } - ] - } - } -} diff --git a/dsh-community-market/docs/schemas/catalog-source.schema.json b/dsh-community-market/docs/schemas/catalog-source.schema.json deleted file mode 100644 index 75bde57202..0000000000 --- a/dsh-community-market/docs/schemas/catalog-source.schema.json +++ /dev/null @@ -1,151 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "urn:dsh-community-market:schema:catalog-source:1.0.0", - "title": "DSH Community Market catalog source manifest", - "description": "A provider-neutral declaration for one user-selectable HTTPS JSON catalog endpoint. A user registers its manifest URL; selection and all other user-owned state stay local. The smallest recommended profile supports q, category, cursor, and limit and uses 50 as an example page size, not a global cap.", - "type": "object", - "additionalProperties": false, - "required": [ - "manifestVersion", - "providerId", - "name", - "attribution", - "transport", - "query" - ], - "properties": { - "manifestVersion": { - "const": "1.0.0" - }, - "providerId": { - "type": "string", - "minLength": 3, - "maxLength": 128, - "pattern": "^[a-z0-9]+(?:[.-][a-z0-9]+)+$", - "description": "Provider-claimed stable identifier, preferably in reverse-domain form. The Host generates a separate sourceRecordId for local identity." - }, - "name": { - "type": "string", - "minLength": 1, - "maxLength": 120, - "pattern": "^[^\\u0000-\\u001F\\u007F-\\u009F\\u202A-\\u202E\\u2066-\\u2069]*$" - }, - "description": { - "type": "string", - "maxLength": 500, - "pattern": "^[^\\u0000-\\u001F\\u007F-\\u009F\\u202A-\\u202E\\u2066-\\u2069]*$" - }, - "homepage": { - "type": "string", - "format": "uri", - "maxLength": 2048, - "pattern": "^https://(?![^/?#]*@)(?![^/?#]*:)[^#]+$" - }, - "attribution": { - "type": "object", - "additionalProperties": false, - "required": [ - "name", - "url" - ], - "properties": { - "name": { - "type": "string", - "minLength": 1, - "maxLength": 120, - "pattern": "^[^\\u0000-\\u001F\\u007F-\\u009F\\u202A-\\u202E\\u2066-\\u2069]*$" - }, - "url": { - "type": "string", - "format": "uri", - "maxLength": 2048, - "pattern": "^https://(?![^/?#]*@)(?![^/?#]*:)[^#]+$" - }, - "notice": { - "type": "string", - "maxLength": 500, - "pattern": "^[^\\u0000-\\u001F\\u007F-\\u009F\\u202A-\\u202E\\u2066-\\u2069]*$" - } - } - }, - "transport": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "endpoint", - "method" - ], - "properties": { - "kind": { - "const": "https-json" - }, - "endpoint": { - "type": "string", - "format": "uri", - "maxLength": 2048, - "pattern": "^https://(?![^/?#]*@)(?![^/?#]*:)[^/?#\\s]+(?:/[^?#\\s]*)?/v1/plugins$", - "description": "Absolute HTTPS endpoint on standard port 443 with no query or fragment. It must share the user-approved manifest origin, and the standard endpoint path ends in /v1/plugins." - }, - "method": { - "const": "GET" - } - } - }, - "query": { - "type": "object", - "description": "The endpoint query features advertised by this source. The minimal fixture uses supported=[q, category, cursor, limit], defaultLimit=50, maxLimit=50, and sorts=[]. Standard sources may declare limits through the Schema maximum of 200.", - "additionalProperties": false, - "required": [ - "supported", - "defaultLimit", - "maxLimit", - "sorts" - ], - "properties": { - "supported": { - "type": "array", - "minItems": 0, - "maxItems": 7, - "uniqueItems": true, - "items": { - "enum": [ - "q", - "category", - "capability", - "cursor", - "limit", - "sort", - "locale" - ] - } - }, - "defaultLimit": { - "type": "integer", - "minimum": 1, - "maximum": 200, - "default": 50 - }, - "maxLimit": { - "type": "integer", - "minimum": 1, - "maximum": 200, - "default": 50 - }, - "sorts": { - "type": "array", - "maxItems": 4, - "uniqueItems": true, - "items": { - "enum": [ - "relevance", - "updated", - "name", - "downloads" - ] - } - } - } - } - } -} diff --git a/dsh-community-market/package.json b/dsh-community-market/package.json deleted file mode 100644 index c38b9f1622..0000000000 --- a/dsh-community-market/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "dsh-community-market", - "version": "0.1.0-dev.0", - "private": true, - "description": "Private design documents for a future WorkDSH community market", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/techflag/workdsh.git", - "directory": "dsh-community-market" - }, - "homepage": "https://github.com/techflag/workdsh/tree/main/dsh-community-market#readme", - "bugs": { - "url": "https://github.com/techflag/workdsh/issues" - }, - "type": "module", - "files": [ - "docs/**", - "LICENSE", - "README.md", - "README.zh.md", - "README.i18n.yaml", - "SECURITY.md", - "SECURITY.zh.md", - "SECURITY.i18n.yaml" - ], - "engines": { - "node": "^22.19.0 || >=24.0.0" - }, - "scripts": { - "check": "node scripts/verify-docs.mjs" - }, - "keywords": [ - "deepseek", - "dsh", - "plugin", - "marketplace", - "community" - ], - "devDependencies": { - "ajv": "^8.20.0", - "ajv-formats": "^3.0.1" - } -} diff --git a/dsh-community-market/scripts/verify-docs.mjs b/dsh-community-market/scripts/verify-docs.mjs deleted file mode 100644 index 322ed17935..0000000000 --- a/dsh-community-market/scripts/verify-docs.mjs +++ /dev/null @@ -1,509 +0,0 @@ -import { execFileSync } from 'node:child_process' -import { existsSync, readFileSync } from 'node:fs' -import { dirname, resolve } from 'node:path' -import { fileURLToPath } from 'node:url' -import Ajv2020 from 'ajv/dist/2020.js' -import addFormats from 'ajv-formats' - -const packageRoot = dirname(dirname(fileURLToPath(import.meta.url))) -const fail = message => { throw new Error(`verify-market-docs: ${message}`) } -const read = path => readFileSync(resolve(packageRoot, path), 'utf8') -const readJson = path => { - try { - return JSON.parse(read(path)) - } catch (error) { - fail(`${path} is not valid JSON: ${error.message}`) - } -} -const manifest = JSON.parse(read('package.json')) - -if (manifest.name !== 'dsh-community-market') fail('package name must remain dsh-community-market') -if (manifest.private !== true) { - fail('the built-in Market workspace package must stay private to prevent unsupported standalone publication') -} -for (const field of ['main', 'types', 'exports', 'dsh', 'module', 'bin', 'optionalDependencies']) { - if (manifest[field] !== undefined) fail(`documentation scaffold must not declare ${field}`) -} - -const publicFiles = [ - 'LICENSE', - 'README.i18n.yaml', - 'README.md', - 'README.zh.md', - 'SECURITY.i18n.yaml', - 'SECURITY.md', - 'SECURITY.zh.md', - 'docs/catalog-adapter-guide.i18n.yaml', - 'docs/catalog-adapter-guide.md', - 'docs/catalog-adapter-guide.zh.md', - 'docs/catalog-provider-contract.i18n.yaml', - 'docs/catalog-provider-contract.md', - 'docs/catalog-provider-contract.zh.md', - 'docs/install-and-uninstall.i18n.yaml', - 'docs/install-and-uninstall.md', - 'docs/install-and-uninstall.zh.md', - 'docs/examples/catalog-query.example.json', - 'docs/examples/catalog-provider-page.example.json', - 'docs/examples/catalog-provider-page.minimal.example.json', - 'docs/examples/catalog-snapshot.example.json', - 'docs/examples/catalog-source.example.json', - 'docs/market-shell.i18n.yaml', - 'docs/market-shell.md', - 'docs/market-shell.zh.md', - 'docs/schemas/catalog-query.schema.json', - 'docs/schemas/catalog-provider-page.schema.json', - 'docs/schemas/catalog-snapshot.schema.json', - 'docs/schemas/catalog-source.schema.json', -] -for (const path of [...publicFiles, 'scripts/verify-docs.mjs']) { - if (!existsSync(resolve(packageRoot, path))) fail(`${path} is missing`) -} - -const expectedFiles = [ - 'docs/**', - 'LICENSE', - 'README.md', - 'README.zh.md', - 'README.i18n.yaml', - 'SECURITY.md', - 'SECURITY.zh.md', - 'SECURITY.i18n.yaml', -] -if (JSON.stringify(manifest.files) !== JSON.stringify(expectedFiles)) { - fail('package files must contain only the documentation surface') -} - -const pairs = [ - ['README.i18n.yaml', ['README.md', 'README.zh.md']], - ['SECURITY.i18n.yaml', ['SECURITY.md', 'SECURITY.zh.md']], - ['docs/catalog-adapter-guide.i18n.yaml', ['docs/catalog-adapter-guide.md', 'docs/catalog-adapter-guide.zh.md']], - ['docs/catalog-provider-contract.i18n.yaml', ['docs/catalog-provider-contract.md', 'docs/catalog-provider-contract.zh.md']], - ['docs/install-and-uninstall.i18n.yaml', ['docs/install-and-uninstall.md', 'docs/install-and-uninstall.zh.md']], - ['docs/market-shell.i18n.yaml', ['docs/market-shell.md', 'docs/market-shell.zh.md']], -] -for (const [recordPath, paths] of pairs) { - const lines = read(recordPath).split(/\r?\n/u) - for (const path of paths) { - const hash = execFileSync('git', ['hash-object', `--path=${path}`, path], { - cwd: packageRoot, - encoding: 'utf8', - stdio: ['ignore', 'pipe', 'pipe'], - }).trim() - const name = path.split('/').at(-1) - if (!lines.includes(`${name}: ${hash}`)) fail(`${recordPath} is stale for ${path}`) - } -} - -const schemaPaths = [ - 'docs/schemas/catalog-source.schema.json', - 'docs/schemas/catalog-query.schema.json', - 'docs/schemas/catalog-provider-page.schema.json', - 'docs/schemas/catalog-snapshot.schema.json', -] -const schemas = Object.fromEntries(schemaPaths.map(path => [path, readJson(path)])) -const assertClosedObjects = (value, path) => { - if (Array.isArray(value)) { - value.forEach((entry, index) => assertClosedObjects(entry, `${path}[${index}]`)) - return - } - if (value === null || typeof value !== 'object') return - if (value.type === 'object' && value.additionalProperties !== false) { - fail(`${path} declares an object without additionalProperties: false`) - } - for (const [key, entry] of Object.entries(value)) assertClosedObjects(entry, `${path}.${key}`) -} -for (const [path, schema] of Object.entries(schemas)) { - if (schema.$schema !== 'https://json-schema.org/draft/2020-12/schema') { - fail(`${path} must use JSON Schema Draft 2020-12`) - } - if (typeof schema.$id !== 'string' || schema.$id.length === 0) fail(`${path} must declare $id`) - if (schema.type !== 'object' || schema.additionalProperties !== false) { - fail(`${path} must define a closed top-level object`) - } - assertClosedObjects(schema, path) -} - -const ajv = new Ajv2020({ allErrors: true, strict: true }) -addFormats(ajv) -const validators = Object.fromEntries( - Object.entries(schemas).map(([path, schema]) => [path, ajv.compile(schema)]), -) -const validateFixture = (schemaPath, fixturePath) => { - const value = readJson(fixturePath) - const validate = validators[schemaPath] - if (!validate(value)) { - fail(`${fixturePath} does not satisfy ${schemaPath}: ${ajv.errorsText(validate.errors)}`) - } - return value -} -const expectInvalid = (schemaPath, value, label) => { - if (validators[schemaPath](value)) fail(`${label} must be rejected by ${schemaPath}`) -} - -const sourceSchema = schemas['docs/schemas/catalog-source.schema.json'] -const querySchema = schemas['docs/schemas/catalog-query.schema.json'] -const providerPageSchema = schemas['docs/schemas/catalog-provider-page.schema.json'] -const snapshotSchema = schemas['docs/schemas/catalog-snapshot.schema.json'] -const forbiddenSourceFields = ['default', 'enabled', 'selected', 'order', 'priority', 'auth', 'headers', 'secret', 'script', 'install', 'installCommand'] -for (const field of forbiddenSourceFields) { - if (Object.hasOwn(sourceSchema.properties, field)) fail(`catalog source schema must not declare ${field}`) -} -if (!sourceSchema.required.includes('providerId') || Object.hasOwn(sourceSchema.properties, 'id')) { - fail('catalog source schema must distinguish providerId from Host sourceRecordId') -} -const expectedQueryFields = ['q', 'category', 'capability', 'cursor', 'limit', 'sort', 'locale'] -if (JSON.stringify(Object.keys(querySchema.properties)) !== JSON.stringify(expectedQueryFields)) { - fail('catalog query schema must expose only q/category/capability/cursor/limit/sort/locale') -} -const itemSchema = snapshotSchema.$defs?.item -if (itemSchema?.additionalProperties !== false || !itemSchema.required?.includes('provenance')) { - fail('catalog snapshot items must be closed objects with required provenance') -} -const providerMediaSchema = providerPageSchema.$defs?.media -const providerIconSchema = providerPageSchema.$defs?.remoteIconCandidate -if ( - providerMediaSchema?.additionalProperties !== false - || JSON.stringify(providerMediaSchema.required) !== JSON.stringify(['icon']) - || providerIconSchema?.additionalProperties !== false - || JSON.stringify(providerIconSchema.required) !== JSON.stringify(['url']) -) { - fail('provider media must be an optional closed icon container with a required remote HTTPS candidate URL') -} -const normalizedMediaSchema = snapshotSchema.$defs?.media -const normalizedIconSchema = snapshotSchema.$defs?.resolvedIcon -const normalizedAssetRefSchema = snapshotSchema.$defs?.assetRef -if ( - normalizedMediaSchema?.additionalProperties !== false - || JSON.stringify(normalizedMediaSchema.required) !== JSON.stringify(['icon']) - || normalizedIconSchema?.additionalProperties !== false - || JSON.stringify(normalizedIconSchema.required) !== JSON.stringify(['assetRef', 'role']) - || JSON.stringify(normalizedIconSchema.properties?.role?.enum) !== JSON.stringify(['plugin-icon', 'publisher-avatar']) - || Object.hasOwn(normalizedIconSchema.properties ?? {}, 'url') -) { - fail('normalized media must expose only a Host asset reference with an explicit plugin-icon or publisher-avatar role') -} -if (sourceSchema.properties?.transport?.properties?.endpoint?.maxLength !== 2048) { - fail('standard source endpoints must keep the 2048-character transport limit') -} -if ( - normalizedAssetRefSchema?.pattern !== '^mktimg_[A-Za-z0-9_-]{32}$' - || normalizedAssetRefSchema?.minLength !== 39 - || normalizedAssetRefSchema?.maxLength !== 39 -) { - fail('normalized media references must match the exact Host route token format') -} -if ( - providerPageSchema.$defs?.httpsUri?.pattern !== snapshotSchema.$defs?.httpsUri?.pattern - || !providerIconSchema.properties?.url?.$ref?.endsWith('/httpsUri') -) { - fail('provider icon candidates must use the shared strict HTTPS URI constraint') -} -if (Object.hasOwn(itemSchema.properties, 'install') || Object.hasOwn(itemSchema.properties, 'installCommand')) { - fail('catalog snapshot items must not contain executable installation fields') -} -if (Object.hasOwn(providerPageSchema.$defs.item.properties, 'provenance')) { - fail('provider wire items must not be allowed to supply Host provenance') -} -if ( - querySchema.$defs.categoryId.pattern !== providerPageSchema.$defs.categoryId.pattern - || querySchema.$defs.categoryId.pattern !== snapshotSchema.$defs.categoryId.pattern -) { - fail('category identifiers must use the same constraint in queries, provider pages, and normalized snapshots') -} -if ( - querySchema.$defs.capabilityId.pattern !== providerPageSchema.$defs.capabilityList.items.pattern - || querySchema.$defs.capabilityId.pattern !== snapshotSchema.$defs.capabilityList.items.pattern -) { - fail('capability identifiers must use the same constraint in queries, provider pages, and normalized snapshots') -} -for (const field of ['sourceRecordId', 'providerId', 'adapterId', 'registrationKind', 'fetchedAt', 'finalUrl']) { - if (!snapshotSchema.$defs.source.required.includes(field)) fail(`normalized snapshots must require Host source field ${field}`) -} - -const sourceExample = validateFixture('docs/schemas/catalog-source.schema.json', 'docs/examples/catalog-source.example.json') -for (const field of forbiddenSourceFields) { - if (Object.hasOwn(sourceExample, field)) fail(`catalog source example must not contain ${field}`) -} -if (sourceExample.manifestVersion !== '1.0.0') fail('catalog source example has an unsupported manifestVersion') -if (sourceExample.transport?.kind !== 'https-json' || sourceExample.transport?.method !== 'GET') { - fail('catalog source example must use the standard https-json GET transport') -} -let endpoint -try { - endpoint = new URL(sourceExample.transport.endpoint) -} catch { - fail('catalog source example endpoint must be an absolute URL') -} -if ( - endpoint.protocol !== 'https:' - || endpoint.username - || endpoint.password - || endpoint.port - || endpoint.search - || endpoint.hash - || !endpoint.pathname.endsWith('/v1/plugins') -) { - fail('catalog source example endpoint must use standard HTTPS port 443, have no credentials, query, or fragment, and end in /v1/plugins') -} -const supportedQueryFields = sourceExample.query?.supported -if (!Array.isArray(supportedQueryFields) || supportedQueryFields.some(field => !expectedQueryFields.includes(field))) { - fail('catalog source example declares an unknown query field') -} -if (sourceExample.query.defaultLimit > sourceExample.query.maxLimit || sourceExample.query.maxLimit > 100) { - fail('catalog source example has inconsistent query limits') -} -if (sourceExample.query.supported.includes('sort') && sourceExample.query.sorts.length === 0) { - fail('catalog source example advertises sort without a supported sort value') -} - -const queryExample = validateFixture('docs/schemas/catalog-query.schema.json', 'docs/examples/catalog-query.example.json') -if (Object.keys(queryExample).some(field => !expectedQueryFields.includes(field))) { - fail('catalog query example contains an unknown field') -} -const allowedSorts = querySchema.properties.sort.enum -if (!Number.isInteger(queryExample.limit) || queryExample.limit < 1 || queryExample.limit > 100) { - fail('catalog query example limit must be an integer from 1 through 100') -} -if (queryExample.sort !== undefined && !allowedSorts.includes(queryExample.sort)) { - fail('catalog query example uses an unsupported sort') -} - -const minimalProviderPageExample = validateFixture( - 'docs/schemas/catalog-provider-page.schema.json', - 'docs/examples/catalog-provider-page.minimal.example.json', -) -if (minimalProviderPageExample.items.length > 50) { - fail('minimal catalog provider page example must stay within its recommended 50-item fixture limit') -} - -const providerPageExample = validateFixture( - 'docs/schemas/catalog-provider-page.schema.json', - 'docs/examples/catalog-provider-page.example.json', -) -if (providerPageExample.items.length > queryExample.limit) { - fail('catalog provider page example must not exceed the effective query limit') -} -const hasDuplicateIdentity = (items, identityOf) => { - const seen = new Set() - for (const item of items) { - const identity = identityOf(item) - if (seen.has(identity)) return true - seen.add(identity) - } - return false -} -if (hasDuplicateIdentity(providerPageExample.items, item => item.id)) { - fail('catalog provider page example contains duplicate item IDs') -} -if (!providerPageExample.items[0]?.media?.icon?.url?.startsWith('https://')) { - fail('catalog provider page example must demonstrate a direct HTTPS icon candidate') -} - -const snapshotExample = validateFixture('docs/schemas/catalog-snapshot.schema.json', 'docs/examples/catalog-snapshot.example.json') -if (snapshotExample.schemaVersion !== '1.0.0') fail('catalog snapshot example has an unsupported schemaVersion') -if (!Array.isArray(snapshotExample.items)) fail('catalog snapshot example items must be an array') -const identities = new Set() -for (const item of snapshotExample.items) { - if (!item.repository && !item.package) fail(`catalog snapshot item ${item.id ?? ''} lacks repository or package identity`) - if ( - item.provenance?.sourceRecordId !== snapshotExample.source?.sourceRecordId - || item.provenance?.providerId !== snapshotExample.source?.providerId - || item.provenance?.itemId !== item.id - ) { - fail(`catalog snapshot item ${item.id ?? ''} has mismatched provenance`) - } - const identity = `${item.provenance.sourceRecordId}\0${item.provenance.itemId}` - if (identities.has(identity)) fail(`catalog snapshot contains duplicate identity ${item.provenance.sourceRecordId}/${item.provenance.itemId}`) - identities.add(identity) - for (const field of ['install', 'installCommand', 'script', 'command']) { - if (Object.hasOwn(item, field)) fail(`catalog snapshot item ${item.id} must not contain executable field ${field}`) - } - if (item.media?.icon) { - if (typeof item.media.icon.assetRef !== 'string' || !/^mktimg_[A-Za-z0-9_-]{32}$/u.test(item.media.icon.assetRef)) { - fail(`catalog snapshot item ${item.id} media must contain a Host asset reference`) - } - if (Object.hasOwn(item.media.icon, 'url')) { - fail(`catalog snapshot item ${item.id} must not expose a remote media URL`) - } - } -} -if (snapshotExample.items[0]?.media?.icon?.role !== 'plugin-icon') { - fail('catalog snapshot example must demonstrate a resolved direct provider plugin icon') -} -if (!hasDuplicateIdentity( - [providerPageExample.items[0], { ...providerPageExample.items[0], displayName: 'Duplicate identity' }], - item => item.id, -)) { - fail('duplicate provider item identity guard is ineffective') -} - -expectInvalid( - 'docs/schemas/catalog-source.schema.json', - { ...sourceExample, enabled: true }, - 'a source manifest with local enabled state', -) -expectInvalid( - 'docs/schemas/catalog-source.schema.json', - { ...sourceExample, transport: { ...sourceExample.transport, endpoint: 'http://catalog.example/v1/plugins' } }, - 'an insecure source endpoint', -) -expectInvalid( - 'docs/schemas/catalog-source.schema.json', - { ...sourceExample, transport: { ...sourceExample.transport, endpoint: 'https://catalog.example:8443/v1/plugins' } }, - 'a source endpoint on a nonstandard port', -) -expectInvalid( - 'docs/schemas/catalog-source.schema.json', - { ...sourceExample, transport: { ...sourceExample.transport, endpoint: `https://catalog.example/${'a'.repeat(2_048)}/v1/plugins` } }, - 'an oversized source endpoint', -) -expectInvalid( - 'docs/schemas/catalog-source.schema.json', - { ...sourceExample, attribution: { ...sourceExample.attribution, url: 'https://user@example.org/' } }, - 'a credential-bearing attribution URL', -) -expectInvalid( - 'docs/schemas/catalog-query.schema.json', - { ...queryExample, limit: 0 }, - 'a zero query limit', -) -expectInvalid( - 'docs/schemas/catalog-query.schema.json', - { ...queryExample, unknown: true }, - 'an unknown query property', -) -expectInvalid( - 'docs/schemas/catalog-query.schema.json', - { ...queryExample, category: ['User Interface'] }, - 'an unstable category identifier', -) -expectInvalid( - 'docs/schemas/catalog-query.schema.json', - { ...queryExample, capability: ['UI:Panel'] }, - 'an invalid capability identifier', -) -expectInvalid( - 'docs/schemas/catalog-provider-page.schema.json', - { - ...providerPageExample, - items: providerPageExample.items.map(item => ({ ...item, install: 'pnpm add unsafe' })), - }, - 'a provider page with an executable install field', -) -expectInvalid( - 'docs/schemas/catalog-provider-page.schema.json', - { - ...providerPageExample, - items: providerPageExample.items.map(item => ({ ...item, displayName: 'Safe\u202Eexe.txt' })), - }, - 'a provider page with bidirectional spoofing controls', -) -expectInvalid( - 'docs/schemas/catalog-provider-page.schema.json', - { - ...providerPageExample, - items: providerPageExample.items.map(item => ({ - ...item, - media: { icon: { ...item.media.icon, url: 'http://plugins.example.org/unsafe.png' } }, - })), - }, - 'a provider page with an insecure icon candidate', -) -expectInvalid( - 'docs/schemas/catalog-provider-page.schema.json', - { - ...providerPageExample, - items: providerPageExample.items.map(item => ({ - ...item, - media: { icon: { ...item.media.icon, url: 'https://user@plugins.example.org/unsafe.png' } }, - })), - }, - 'a provider page with a credential-bearing icon candidate', -) -expectInvalid( - 'docs/schemas/catalog-provider-page.schema.json', - { - ...providerPageExample, - items: providerPageExample.items.map(item => ({ - ...item, - media: { icon: { ...item.media.icon, url: 'https://plugins.example.org:8443/icon.png' } }, - })), - }, - 'a provider page with an icon candidate on a nonstandard port', -) -expectInvalid( - 'docs/schemas/catalog-snapshot.schema.json', - { - ...snapshotExample, - items: snapshotExample.items.map(({ provenance, ...item }) => item), - }, - 'a normalized snapshot without Host provenance', -) -expectInvalid( - 'docs/schemas/catalog-snapshot.schema.json', - { - ...snapshotExample, - items: snapshotExample.items.map(({ repository, package: packageIdentity, ...item }) => item), - }, - 'a normalized snapshot without a package or repository identity', -) -expectInvalid( - 'docs/schemas/catalog-snapshot.schema.json', - { - ...snapshotExample, - items: snapshotExample.items.map(item => ({ - ...item, - media: { icon: { url: 'https://plugins.example.org/unsafe.png', role: 'plugin-icon' } }, - })), - }, - 'a normalized snapshot that exposes a remote icon URL to the Renderer', -) -expectInvalid( - 'docs/schemas/catalog-snapshot.schema.json', - { - ...snapshotExample, - items: snapshotExample.items.map(item => ({ - ...item, - media: { icon: { ...item.media.icon, assetRef: 'https://plugins.example.org/unsafe.png' } }, - })), - }, - 'a normalized snapshot that disguises a remote URL as an asset reference', -) -expectInvalid( - 'docs/schemas/catalog-snapshot.schema.json', - { - ...snapshotExample, - items: snapshotExample.items.map(item => ({ - ...item, - media: { icon: { ...item.media.icon, assetRef: 'mktimg_too-short' } }, - })), - }, - 'a normalized snapshot with a malformed Host asset reference', -) -expectInvalid( - 'docs/schemas/catalog-snapshot.schema.json', - { - ...snapshotExample, - items: snapshotExample.items.map(item => ({ - ...item, - media: { icon: { ...item.media.icon, role: 'provider-logo' } }, - })), - }, - 'a normalized snapshot with an unknown media role', -) - -const markdownFiles = publicFiles.filter(path => path.endsWith('.md')) -for (const path of markdownFiles) { - const source = read(path) - for (const match of source.matchAll(/\]\(([^)]+)\)/gu)) { - const target = match[1].trim().replace(/^<|>$/gu, '') - if (/^(?:https?:|mailto:|#)/u.test(target)) continue - const localPath = decodeURIComponent(target.split('#', 1)[0]) - if (!localPath) continue - if (!existsSync(resolve(packageRoot, dirname(path), localPath))) { - fail(`${path} links to missing ${localPath}`) - } - } -} - -process.stdout.write(`verify-market-docs: ${markdownFiles.length} Markdown files, ${pairs.length} bilingual pairs, and ${schemaPaths.length} schemas are consistent\n`) diff --git a/package.json b/package.json index b4493ef9fb..a7a5424017 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,7 @@ "node": "^22.19.0 || >=24.0.0" }, "workspaces": [ - "dsh-plugin-desktop", - "dsh-community-fabric", - "dsh-community-market" + "dsh-plugin-desktop" ], "resolutions": { "app-builder-lib@npm:26.15.7": "patch:app-builder-lib@npm%3A26.15.7#./patches/app-builder-lib@26.15.7.patch" @@ -33,7 +31,7 @@ "check:web-dsh-alignment": "node scripts/verify-web-desktop-dsh-alignment.mjs", "check:web-plan": "node scripts/check-local-web-plan.mjs", "check:layout": "yarn check:bilingual-docs && node scripts/verify-layout.mjs", - "check": "yarn check:layout && yarn check:desktop-dsh-alignment && yarn check:web-dsh-alignment && yarn check:web-plan && yarn workspace dsh-community-fabric check && yarn workspace dsh-community-market check && yarn workspace dsh-plugin-desktop check", + "check": "yarn check:layout && yarn check:desktop-dsh-alignment && yarn check:web-dsh-alignment && yarn check:web-plan && yarn workspace dsh-plugin-desktop check", "dev": "yarn workspace dsh-plugin-desktop dev", "start": "yarn workspace dsh-plugin-desktop start", "package:dir": "yarn workspace dsh-plugin-desktop package:dir", diff --git a/scripts/verify-desktop-dsh-alignment.mjs b/scripts/verify-desktop-dsh-alignment.mjs index 733e7a98cf..6de841c158 100644 --- a/scripts/verify-desktop-dsh-alignment.mjs +++ b/scripts/verify-desktop-dsh-alignment.mjs @@ -19,7 +19,7 @@ if (upstream.commit !== pinnedCommit || pinnedCommit !== checkoutCommit) { if (upstream.version !== checkout.version || DSH_VERSION !== checkout.version) { problems.push(`upstream.json=${upstream.version}, Desktop=${DSH_VERSION}, checkout=${checkout.version}`) } -for (const workspace of ['dsh-plugin-desktop', 'dsh-community-fabric', 'dsh-community-market']) { +for (const workspace of ['dsh-plugin-desktop']) { const manifest = readJson(`${workspace}/package.json`) for (const field of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) { for (const name of Object.keys(manifest[field] ?? {})) { diff --git a/scripts/verify-layout.mjs b/scripts/verify-layout.mjs index ed16d65ec7..02e92c13a3 100644 --- a/scripts/verify-layout.mjs +++ b/scripts/verify-layout.mjs @@ -14,8 +14,6 @@ const fail = message => { throw new Error(`verify-layout: ${message}`) } const workspace = readJson('package.json') const upstream = readJson('upstream.json') const stablePlugin = readJson('dsh-plugin-desktop/package.json') -const fabric = readJson('dsh-community-fabric/package.json') -const market = readJson('dsh-community-market/package.json') const upstreamPackage = readJson('deepseek-harness/package.json') if (stablePlugin.name !== 'dsh-plugin-desktop') fail('the stable Desktop workspace must retain dsh-plugin-desktop') @@ -26,32 +24,12 @@ if (typeof upstream.commit !== 'string' || typeof upstream.version !== 'string') if (workspace.packageManager !== 'yarn@4.18.0') { fail('the product workspace must pin yarn@4.18.0') } -if (JSON.stringify(workspace.workspaces) !== JSON.stringify([ - 'dsh-plugin-desktop', - 'dsh-community-fabric', - 'dsh-community-market', -])) { - fail('the root Yarn workspace must contain the desktop, community-fabric, and community-market packages') +if (JSON.stringify(workspace.workspaces) !== JSON.stringify(['dsh-plugin-desktop'])) { + fail('the root Yarn workspace must contain only the Desktop carrier') } -for (const [name, manifest] of [ - ['dsh-plugin-desktop', stablePlugin], - ['dsh-community-fabric', fabric], - ['dsh-community-market', market], -]) { +for (const [name, manifest] of [['dsh-plugin-desktop', stablePlugin]]) { if (manifest.packageManager !== undefined) fail(`${name} must inherit the root Yarn release`) } -if (fabric.name !== 'dsh-community-fabric') fail('the Fabric workspace must own dsh-community-fabric') -if (market.name !== 'dsh-community-market') fail('the market workspace must own dsh-community-market') -if (!market.private || market.main !== undefined || market.exports !== undefined || market.dsh !== undefined) { - fail('the market workspace must remain a private documentation scaffold') -} -if (Object.keys(market.dependencies ?? {}).length - || Object.keys(market.devDependencies ?? {}).some(name => !['ajv', 'ajv-formats'].includes(name))) { - fail('the market documentation scaffold may depend only on its schema validators') -} -if (run('git', ['ls-files', '--', 'dsh-community-market/src', 'dsh-community-market/tests'])) { - fail('the market documentation scaffold must not contain runtime source or tests') -} const claudePath = resolve(root, 'CLAUDE.md') const claudeStat = lstatSync(claudePath) // Windows checkouts materialize the symlink as a regular file holding the @@ -67,10 +45,6 @@ for (const legacyFile of [ 'pnpm-workspace.yaml', 'dsh-plugin-desktop/pnpm-lock.yaml', 'dsh-plugin-desktop/pnpm-workspace.yaml', - 'dsh-community-fabric/pnpm-lock.yaml', - 'dsh-community-fabric/pnpm-workspace.yaml', - 'dsh-community-market/pnpm-lock.yaml', - 'dsh-community-market/pnpm-workspace.yaml', ]) { if (existsSync(resolve(root, legacyFile))) fail(`${legacyFile} must not exist`) } @@ -87,8 +61,6 @@ if (typeof upstreamPackage.packageManager !== 'string' || !upstreamPackage.packa for (const [owner, manifest] of [ ['root', workspace], ['stable desktop', stablePlugin], - ['fabric', fabric], - ['market', market], ]) { for (const field of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies', 'resolutions']) { for (const [name, range] of Object.entries(manifest[field] ?? {})) { diff --git a/yarn.lock b/yarn.lock index 5448ac2949..ef04ad643d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1131,21 +1131,7 @@ __metadata: languageName: node linkType: hard -"ajv-formats@npm:^3.0.1": - version: 3.0.1 - resolution: "ajv-formats@npm:3.0.1" - dependencies: - ajv: "npm:^8.0.0" - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - checksum: 10c0/168d6bca1ea9f163b41c8147bae537e67bd963357a5488a1eaf3abe8baa8eec806d4e45f15b10767e6020679315c7e1e5e6803088dfb84efa2b4e9353b83dd0a - languageName: node - linkType: hard - -"ajv@npm:^8.0.0, ajv@npm:^8.18.0, ajv@npm:^8.20.0": +"ajv@npm:^8.18.0": version: 8.20.0 resolution: "ajv@npm:8.20.0" dependencies: @@ -1776,21 +1762,6 @@ __metadata: languageName: node linkType: hard -"dsh-community-fabric@workspace:dsh-community-fabric": - version: 0.0.0-use.local - resolution: "dsh-community-fabric@workspace:dsh-community-fabric" - languageName: unknown - linkType: soft - -"dsh-community-market@workspace:dsh-community-market": - version: 0.0.0-use.local - resolution: "dsh-community-market@workspace:dsh-community-market" - dependencies: - ajv: "npm:^8.20.0" - ajv-formats: "npm:^3.0.1" - languageName: unknown - linkType: soft - "dsh-plugin-desktop@workspace:dsh-plugin-desktop": version: 0.0.0-use.local resolution: "dsh-plugin-desktop@workspace:dsh-plugin-desktop"