diff --git a/dsh-community-market/README.i18n.yaml b/dsh-community-market/README.i18n.yaml index 8271f25bf2..336ea18aee 100644 --- a/dsh-community-market/README.i18n.yaml +++ b/dsh-community-market/README.i18n.yaml @@ -1,4 +1,4 @@ # Bilingual-pair consistency record. Both languages carry equal authority. # Update both files and record their Git blob hashes after editing either side. -README.md: 4947c8f090e794e4258411a5f595358f4430d0f8 -README.zh.md: 27c5b19a57e100bf072376164c2f7316e5dc7131 +README.md: 092443d2facb1444582067b87e9d1c2e3268f36e +README.zh.md: ca02f87cf7ba58090f4fd44245df7ae0ebbb2629 diff --git a/dsh-community-market/README.md b/dsh-community-market/README.md index 4947c8f090..092443d2fa 100644 --- a/dsh-community-market/README.md +++ b/dsh-community-market/README.md @@ -11,7 +11,7 @@ DSH Community Market is the open plugin market built into [DSH Desktop](../READM The current interface has four views: 1. **Discover** shows all normalized listings loaded from the selected source. Clicking any card immediately opens the same action dialog, where Desktop checks for a managed installation before falling back to details or a safe, display-only manual hint. -2. **Installable** is a fail-closed local candidate list derived from the complete index. It requires reviewed provider verification with a `repository_backlink`, an exact stable npm version, and a canonical repository, and excludes blocked packages plus packages already present in the active profile or its Market receipts. Building this list does not query npm for every package; its cards use the same action dialog as Discover. +2. **Installable** is a fail-closed structural candidate list derived from the selected source's complete index. It requires reviewed provider verification with a `repository_backlink`, an exact stable npm version, and a canonical repository, and excludes product-blocked packages. Catalog membership is independent of whether a package is installed, receipted, disabled, or was later uninstalled locally, so those states do not remove its card. Building this list does not query npm for every package; preview and execution separately decide whether a local operation is allowed, and its cards use the same action dialog as Discover. 3. **Installed** reconciles valid Market receipts with Desktop's active-profile direct-bundle inventory. Receipt-owned bundles can be uninstalled; mutable bundles can be disabled and enabled again. A disabled receipt-owned bundle keeps both Enable and Uninstall. 4. **Sources** selects and manages catalog sources. Exactly one source is browsed at a time. @@ -23,7 +23,7 @@ The market remains open to ongoing cooperation with a wide range of plugin data Anyone can provide, integrate, and use a plugin data source. A conforming source only needs to publish a [`catalog-source` manifest](docs/schemas/catalog-source.schema.json) and return data matching the [`catalog-provider-page` schema](docs/schemas/catalog-provider-page.schema.json) from its `/v1/plugins` endpoint, with no custom Market code. An existing API can keep its own format and contact us to join as a cooperating source through a reviewed adapter shipped with Market. A source may provide `media.icon`; Desktop validates and proxies it before display. Sources without an icon remain valid and receive a local fallback. -Before presenting a selected source, the Host builds one complete, validated local index. A standard source is scanned through its declared cursor and page limits; the reviewed 1024Store adapter reads its full registry once and normalizes it in Schema-bounded chunks. The reviewed dshfind adapter walks its REST pages with one `data_version` fixed across the complete scan. Search, multi-category OR filtering, category choices, and pagination then run against that complete local index without refetching the provider for each interaction. Every visible page contains at most 50 items, and the category choices cover all categories in the index rather than only pages already shown. **Installable** is a fail-closed structural subset of the same index; authoritative npm verification begins only when the user previews one candidate. +Before presenting a selected source, the Host builds one complete, validated local index. A standard source is scanned through its declared cursor and page limits; the reviewed 1024Store adapter reads its full registry once and normalizes it in Schema-bounded chunks. The reviewed dshfind adapter walks its REST pages with one `data_version` fixed across the complete scan. Search, multi-category OR filtering, category choices, and pagination then run against that complete local index without refetching the provider for each interaction. Every visible page contains at most 50 items, and the category choices cover all categories in the index rather than only pages already shown. **Installable** is a fail-closed structural subset of the same index and does not change with local installed, receipt, or enabled/disabled state; authoritative npm and local-operation verification begin only when the user previews one candidate. The Host reuses a completed index until its cache expires (currently five minutes by default). When optional index metadata is returned, `scannedAt` identifies the completed scan, `expiresAt` its cache deadline, optional `providerRevision` the source revision observed consistently across the scan, and `cacheStatus` whether the response was freshly scanned or reused. An explicit refresh replaces the index and bypasses the underlying catalog-response cache; it is not merely a repaint of the current 50 items. @@ -39,7 +39,7 @@ All catalog data is remote and untrusted. A listing means only that a provider s - Background browsing never installs a package or executes repository code. - Installation starts only after an explicit user action and confirmation. -- **Installable** is a Host-produced, fail-closed structural candidate set, not a renderer guess or proof that npm was checked. A candidate needs reviewed provider verification with `repository_backlink`, an exact stable npm target, and a canonical repository, and must not already be installed, receipted, or locally blocked. Preview performs the first authoritative official-registry check for that one package; execution repeats mutable checks. +- **Installable** is a Host-produced, fail-closed structural candidate set from the selected catalog, not a renderer guess or proof that npm was checked. A candidate needs reviewed provider verification with `repository_backlink`, an exact stable npm target, and a canonical repository, and must not be product-blocked. Installation, receipt, uninstall history, and enabled/disabled state never grant or remove catalog membership. Preview performs the first authoritative official-registry and local-action check for that one package; execution repeats mutable checks. - The managed installer accepts exact stable npm versions only. It does not install GitHub URLs, mutable ranges or tags, deprecated packages, packages whose target manifest defines `preinstall`, `install`, `postinstall`, or `prepare`, or packages incompatible with the bundled DSH rc.7 or Node.js runtime. - Provider-supplied command strings, install snippets, and repository install instructions are discarded, never displayed as a Host manual hint, and never executed. A separate manual hint, when available, is an exact npm command reconstructed by the Host from normalized identity, marked as not fully verified, and displayed for the user to run at their own discretion. dshfind currently provides no exact version from which such a hint could be reconstructed. - The renderer submits only source/item or receipt identifiers for managed operations. The **Open DSH Terminal** action submits an empty request and never receives, copies, or executes the displayed manual command. diff --git a/dsh-community-market/README.zh.md b/dsh-community-market/README.zh.md index 27c5b19a57..ca02f87cf7 100644 --- a/dsh-community-market/README.zh.md +++ b/dsh-community-market/README.zh.md @@ -11,7 +11,7 @@ DSH Community Market 是 [DSH Desktop](../README.md) 内置的开放插件市场 当前界面分为四个视图: 1. **发现**展示当前来源已经加载并标准化的全部条目。点击任一卡片都会立即打开同一个操作弹窗:Desktop 先检查能否受管安装,不能时再显示详情或安全的只展示手动提示。 -2. **可安装**是从完整索引中以 fail-closed 方式生成的本地结构候选列表。条目必须具有经过审核的 provider 验证与 `repository_backlink`、精确稳定的 npm 版本和规范仓库,同时排除被阻止的 package,以及当前 profile 或 Market receipt 中已经存在的 package。生成列表时不会逐包请求 npm;这里的卡片与发现页共用同一个操作弹窗。 +2. **可安装**是从已选来源完整索引中以 fail-closed 方式生成的结构候选列表。条目必须具有经过审核的 provider 验证与 `repository_backlink`、精确稳定的 npm 版本和规范仓库,同时排除产品 blocklist 中的 package。目录成员资格与 package 是否已安装、已有 receipt、处于禁用状态或后来已在本地卸载无关,这些状态都不会移除卡片。生成列表时不会逐包请求 npm;preview 与执行阶段会另行判断本地操作是否允许,这里的卡片与发现页共用同一个操作弹窗。 3. **已安装**会把有效 Market receipt 与 Desktop 当前 profile 的 direct bundle 清单进行核对。Receipt 持有的 bundle 可以卸载;可变 bundle 可以禁用并重新启用。已禁用且由 receipt 持有的 bundle 会同时保留“启用”和“卸载”。 4. **来源**用于选择和管理目录来源;同一时间只浏览一个来源。 @@ -23,7 +23,7 @@ DSH Community Market 是 [DSH Desktop](../README.md) 内置的开放插件市场 任何人都可以提供、接入和使用插件数据源。符合规范的数据源只需发布一份 [`catalog-source` manifest](docs/schemas/catalog-source.schema.json),并由其 `/v1/plugins` 接口返回符合 [`catalog-provider-page` Schema](docs/schemas/catalog-provider-page.schema.json) 的数据,无需为 Market 编写自定义代码。已有 API 无需更换自己的格式,也可以联系我们,通过随 Market 发布的受审 adapter 作为合作数据源接入。来源可以提供 `media.icon`,Desktop 会先校验并代理图片再显示;没有图标的来源仍然合法,界面会使用本地 fallback。 -展示已选来源前,Host 会先建立一份完整、经过校验的本地索引。标准来源按照声明的 cursor 与 page limit 扫描;经过审核的 1024Store adapter 只读取一次完整 registry,再按 Schema 上限分块标准化;经过审核的 dshfind adapter 则遍历 REST 分页,并在整次扫描中固定同一个 `data_version`。之后的搜索、多分类 OR 筛选、分类选项和分页都在这份完整本地索引上进行,不会因为每次交互重新请求 provider。每个可见页面最多展示 50 条,分类选项覆盖索引中的全部分类,而不只是已经显示的页面。**可安装**是同一索引上 fail-closed 的结构子集;只有用户预览某个候选时,才开始权威 npm 复核。 +展示已选来源前,Host 会先建立一份完整、经过校验的本地索引。标准来源按照声明的 cursor 与 page limit 扫描;经过审核的 1024Store adapter 只读取一次完整 registry,再按 Schema 上限分块标准化;经过审核的 dshfind adapter 则遍历 REST 分页,并在整次扫描中固定同一个 `data_version`。之后的搜索、多分类 OR 筛选、分类选项和分页都在这份完整本地索引上进行,不会因为每次交互重新请求 provider。每个可见页面最多展示 50 条,分类选项覆盖索引中的全部分类,而不只是已经显示的页面。**可安装**是同一索引上 fail-closed 的结构子集,不随本地安装、receipt 或启用/禁用状态变化;只有用户预览某个候选时,才开始权威 npm 与本地操作复核。 Host 会在 cache 过期前复用已经完成的索引(当前默认五分钟)。如果 response 提供可选索引 metadata,`scannedAt` 表示扫描完成时间,`expiresAt` 表示 cache 截止时间,可选 `providerRevision` 表示整次扫描中一致观察到的来源 revision,`cacheStatus` 表示本次使用新扫描还是复用 cache。用户明确刷新时会替换索引并绕过底层目录 response cache,不只是重新绘制当前 50 条。 @@ -39,7 +39,7 @@ dshfind 当前会返回由提供方维护的安装结论和命令文本,但没 - 后台浏览不会安装任何包,也不会执行仓库代码。 - 只有用户明确点击并确认后,安装才会开始。 -- **可安装**是 Host 以 fail-closed 方式生成的结构候选集合,不是 renderer 猜测,也不表示 npm 已经复核。候选必须具有经过审核的 provider 验证与 `repository_backlink`、精确稳定的 npm 目标和规范仓库,而且不能已经安装、已有 receipt 或被本地策略阻止。Preview 才会针对这个 package 首次执行官方 registry 权威复核;执行前会再检查可变状态。 +- **可安装**是 Host 从已选目录以 fail-closed 方式生成的结构候选集合,不是 renderer 猜测,也不表示 npm 已经复核。候选必须具有经过审核的 provider 验证与 `repository_backlink`、精确稳定的 npm 目标和规范仓库,而且不能位于产品 blocklist。安装状态、receipt、卸载历史和启用/禁用状态都不会授予或移除目录成员资格。Preview 才会针对这个 package 首次执行官方 registry 与本地操作权威复核;执行前会再检查可变状态。 - 受管安装器只接受精确、稳定的 npm 版本。GitHub URL、可变版本范围或 tag、deprecated package、目标 manifest 中定义了 `preinstall`、`install`、`postinstall` 或 `prepare` 的 package,以及不兼容内置 DSH rc.7 或 Node.js runtime 的 package,都会被拒绝。 - 目录提供方返回的命令字符串、安装片段和仓库安装指令都会被丢弃,既不会作为 Host 手动提示展示,也绝不会执行。可用时,Host 会根据规范化身份单独重建一条精确 npm 手动提示;它会明确标为未完成全部验证,只供用户自行决定是否执行。dshfind 当前没有提供可用于重建该提示的精确版本。 - 受管操作中,renderer 只提交来源/条目或 receipt 标识。“打开 DSH 终端”提交的是空请求,不会接收、复制或执行界面展示的手动命令。 diff --git a/dsh-community-market/docs/install-and-uninstall.i18n.yaml b/dsh-community-market/docs/install-and-uninstall.i18n.yaml index a2d8de7b76..9caf1e558c 100644 --- a/dsh-community-market/docs/install-and-uninstall.i18n.yaml +++ b/dsh-community-market/docs/install-and-uninstall.i18n.yaml @@ -1,4 +1,4 @@ # 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: 47082e1aa980ae260bf09c88be144c37556097df -install-and-uninstall.zh.md: fe57f40abc8ecd0e87b65ca5b8fb760b68428c6b +install-and-uninstall.md: 221ed0ddd67318da5fd5d1179d8d0e06beb9c4a8 +install-and-uninstall.zh.md: eb5e39a3e1d97246ef9afd10c4b275b4f8cfc492 diff --git a/dsh-community-market/docs/install-and-uninstall.md b/dsh-community-market/docs/install-and-uninstall.md index 47082e1aa9..221ed0ddd6 100644 --- a/dsh-community-market/docs/install-and-uninstall.md +++ b/dsh-community-market/docs/install-and-uninstall.md @@ -11,7 +11,7 @@ This guide explains what users see and the boundary developers must preserve. Th | View | What it shows | What it does not mean | | --- | --- | --- | | **Discover** | Every normalized item in the selected source's complete local index, shown 50 at a time | A listing is not install approval, compatibility evidence, or endorsement | -| **Installable** | A fail-closed local structural subset requiring reviewed provider verification with `repository_backlink`, an exact stable npm target, and a canonical repository, while excluding blocked, installed, or receipted packages | Presence is not npm verification, compatibility proof, a code review, or endorsement | +| **Installable** | A fail-closed structural subset of the selected catalog requiring reviewed provider verification with `repository_backlink`, an exact stable npm target, and a canonical repository, while excluding product-blocked packages | Local install, receipt, uninstall, and enabled/disabled state do not remove a listing; presence is not permission to install, npm verification, compatibility proof, code review, or endorsement | | **Installed** | Host-reconciled direct bundles for the active profile | A valid matching Market receipt grants uninstall; disabled mutable bundles can be enabled, while external active bundles grant disable but never uninstall | | **Sources** | Saved source records and the one currently selected source | Changing source does not change the active profile or remove receipts | @@ -31,7 +31,7 @@ Optional catalog metadata reports `scannedAt`, cache `expiresAt`, optional `prov If managed preview is unavailable, the dialog remains a details view. For an exact stable npm identity, the Host may show a bounded display-only command reconstructed from normalized identity. It may differ from the command described in the repository, is not the provider's original command, and has not passed the managed installer's complete verification. **Open DSH Terminal** sends no command, path, or profile: it only opens Desktop's built-in terminal so the user can inspect the source and decide whether to copy and run the text. A `dsh plugin add` launched through that built-in terminal uses the same configuration-recovery record as a Market install. Direct `pnpm` or `npm` commands typed there, and commands run from an external system terminal, are outside this recovery boundary. A manual install creates no Market receipt and therefore grants no Market uninstall authority. -The **Installable** label means only “this listing is a local structural candidate for the current profile.” It does not mean npm has been contacted, compatibility is proven, or the code is approved or safe. Preview may still reject it, and a successful preview is not a promise that execution will succeed if registry, catalog, or profile state changes. +The **Installable** label means only “this listing is a structural candidate from the selected catalog.” It does not mean npm has been contacted, the current profile permits installation, compatibility is proven, or the code is approved or safe. Installed, receipted, disabled, or subsequently uninstalled packages remain listed while the catalog still contains them. Preview may still reject a local operation, and a successful preview is not a promise that execution will succeed if registry, catalog, or profile state changes. ## What the Host accepts @@ -46,7 +46,7 @@ The built-in managed installation boundary supports only an npm package when all - npm supplies an official HTTPS tarball with a valid SHA-512 integrity value; and - the package declares a safe DSH bundle patch, which is present and contained inside the installed package after the managed operation. -Building **Installable** does not perform per-package registry I/O. It additionally excludes blocked product packages and any package already referenced by the active profile or one of its Market receipts. Preview performs official-registry and active-profile verification for the selected candidate. Immediately before confirmed installation, execution repeats mutable checks; if integrity, tarball, bundle path, catalog candidate, or active profile changed, it refuses the operation. Only one Market package mutation runs at a time. +Building **Installable** does not perform per-package registry I/O. It excludes product-blocked packages, but does not read the active profile, Market receipts, or enabled/disabled state to decide catalog membership. Preview performs official-registry and active-profile verification for the selected candidate. Immediately before confirmed installation, execution repeats mutable checks; if integrity, tarball, bundle path, catalog candidate, or active profile changed, it refuses the operation. Only one Market package mutation runs at a time. ## Protected install recovery diff --git a/dsh-community-market/docs/install-and-uninstall.zh.md b/dsh-community-market/docs/install-and-uninstall.zh.md index fe57f40abc..eb5e39a3e1 100644 --- a/dsh-community-market/docs/install-and-uninstall.zh.md +++ b/dsh-community-market/docs/install-and-uninstall.zh.md @@ -11,7 +11,7 @@ | 视图 | 展示内容 | 不代表什么 | | --- | --- | --- | | **发现** | 当前已选来源完整本地索引中的全部标准化条目,每次展示 50 条 | 被收录不等于允许安装、兼容性证据或推荐 | -| **可安装** | 本地 fail-closed 结构子集:要求经过审核的 provider 验证与 `repository_backlink`、精确稳定的 npm 目标和规范仓库,同时排除被阻止、已经安装或已有 receipt 的 package | 出现在这里不等于 npm 已复核、兼容性证据、代码审核或推荐 | +| **可安装** | 已选目录的 fail-closed 结构子集:要求经过审核的 provider 验证与 `repository_backlink`、精确稳定的 npm 目标和规范仓库,同时排除产品 blocklist 中的 package | 本地安装、receipt、卸载和启用/禁用状态不会移除条目;出现在这里不等于允许安装、npm 已复核、兼容性证据、代码审核或推荐 | | **已安装** | Host 核对后的当前 profile direct bundle | 有效且匹配的 Market receipt 授予卸载;已禁用的可变 bundle 可以启用,外部 active bundle 只授予禁用、绝不授予卸载 | | **来源** | 已保存的来源记录,以及当前唯一选中的来源 | 切换来源不会切换当前 profile,也不会删除 receipt | @@ -31,7 +31,7 @@ 如果受管 preview 不可用,弹窗会保留为详情。对于精确稳定的 npm 身份,Host 可以展示一条根据规范化身份重建的、有界且只用于展示的命令。它可能与仓库中描述的命令不同,不是 provider 返回的原始命令,也没有通过受管安装器的全部验证。**打开 DSH 终端**不会提交命令、路径或 profile,只负责打开 Desktop 内置终端;用户需要先检查源码,再自行决定是否复制并运行文本。通过该内置终端运行的 `dsh plugin add` 会使用与 Market 安装相同的配置恢复记录;在其中直接执行 `pnpm`、`npm`,或在外部系统终端中运行命令,都不在这个恢复边界内。手动安装不会生成 Market receipt,因此也不会授予 Market 卸载权限。 -**可安装**只表示“这个条目是当前 profile 的本地结构候选”。它不表示已经联系 npm、兼容性已经证明,或代码已经获批、安全。Preview 仍可能拒绝它;即使 preview 成功,如果 registry、目录或 profile 状态发生变化,也不承诺执行一定成功。 +**可安装**只表示“这个条目是已选目录中的结构候选”。它不表示已经联系 npm、当前 profile 允许安装、兼容性已经证明,或代码已经获批、安全。只要目录仍然包含该条目,已经安装、已有 receipt、处于禁用状态或后来已卸载的 package 都会继续显示。Preview 仍可能拒绝本地操作;即使 preview 成功,如果 registry、目录或 profile 状态发生变化,也不承诺执行一定成功。 ## Host 接受什么 @@ -46,7 +46,7 @@ - npm 提供官方 HTTPS tarball 和合法 SHA-512 integrity;以及 - package 声明安全的 DSH bundle patch,受管操作结束后,该文件确实存在于安装 package 内且没有越出 package 目录。 -生成**可安装**列表时不会逐包访问 registry;它还会排除产品阻止的 package,以及当前 profile 或 Market receipt 中已经存在的 package。Preview 针对用户选中的候选完成官方 registry 与当前 profile 复核。用户确认后、真正安装前,执行阶段会立即重复可变检查;如果 integrity、tarball、bundle 路径、目录候选或当前 profile 发生变化,就会拒绝执行。同一时间只允许一个 Market package 修改操作。 +生成**可安装**列表时不会逐包访问 registry。它会排除产品 blocklist 中的 package,但不会读取当前 profile、Market receipt 或启用/禁用状态来决定目录成员资格。Preview 针对用户选中的候选完成官方 registry 与当前 profile 复核。用户确认后、真正安装前,执行阶段会立即重复可变检查;如果 integrity、tarball、bundle 路径、目录候选或当前 profile 发生变化,就会拒绝执行。同一时间只允许一个 Market package 修改操作。 ## 受保护安装恢复 diff --git a/dsh-community-market/docs/market-shell.i18n.yaml b/dsh-community-market/docs/market-shell.i18n.yaml index f7b3f51de0..cbc2577fa0 100644 --- a/dsh-community-market/docs/market-shell.i18n.yaml +++ b/dsh-community-market/docs/market-shell.i18n.yaml @@ -1,4 +1,4 @@ # 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: c4920dc75b0495e21e22942344f45ba0bc51723c -market-shell.zh.md: faea0432bbe60515510c51f8e1c79a0f306ae8bb +market-shell.md: cd8847c08939bdace51f1bcd226de375449bb8ef +market-shell.zh.md: 37e12da7676da14e214c799f3b13d256fd20e750 diff --git a/dsh-community-market/docs/market-shell.md b/dsh-community-market/docs/market-shell.md index c4920dc75b..cd8847c089 100644 --- a/dsh-community-market/docs/market-shell.md +++ b/dsh-community-market/docs/market-shell.md @@ -72,7 +72,7 @@ The published normative contract is the [catalog provider contract](catalog-prov The Host completes one full normalized scan for the selected source and current locale before serving catalog interactions. Standard sources are exhausted through their declared cursor and effective page limits. The reviewed 1024Store adapter instead performs one full-registry GET, normalizes every valid item, and emits Schema-bounded chunks of at most 100 items. The dshfind adapter walks REST pages of at most 100 items and fixes the first page's `data_version` across every later page. Because its published anonymous quota is lower than the current first-sync page count, that initial scan is deliberately throttled and may take longer. A stale-version or rate-limit failure never publishes a partial index. The 10,000-item Host limit, source identity, cancellation, provenance, and origin checks apply across the whole scan. -Search, sorting, multi-category OR filtering, category enumeration, and pagination operate only on this complete local index. The UI shows at most 50 matching items per page. **Load more** advances a Host-owned local cursor; it does not issue another filtered provider request. The category list is the complete set present in the index. **Installable** is a fail-closed structural subset of this same index, not a second provider feed and not the result of per-package registry requests. +Search, sorting, multi-category OR filtering, category enumeration, and pagination operate only on this complete local index. The UI shows at most 50 matching items per page. **Load more** advances a Host-owned local cursor; it does not issue another filtered provider request. The category list is the complete set present in the index. **Installable** is a fail-closed structural subset of this same index, not a second provider feed and not the result of per-package registry requests. Its catalog membership is independent of local installation, receipt, uninstall history, and enabled/disabled state. A completed index is cached for a bounded lifetime, currently five minutes by default. Optional response metadata may expose `scannedAt` (when the scan completed), `expiresAt` (its cache deadline), optional `providerRevision` (one revision observed consistently across all chunks), and `cacheStatus` (`fresh` for a completed scan or `cached` for a reused index). Explicit refresh invalidates the previous index and bypasses the underlying catalog HTTP cache before rebuilding it. Selecting another source cancels the old scan and starts a separate index. @@ -81,7 +81,7 @@ A completed index is cached for a bounded lifetime, currently five minutes by de The Market surface has four views: - **Discover** pages over every normalized item in the selected source's complete local index. Clicking a card opens the shared action dialog immediately; the Host either advances an eligible item into managed preview or keeps the dialog as details. -- **Installable** is derived locally and fail-closed. It requires reviewed provider verification with `repository_backlink`, an exact stable npm target, and a canonical repository, and excludes blocked packages plus packages already present in the active profile or its Market receipts. Its cards use the same dialog. Structural candidacy is not an npm verification, code review, or endorsement. Because dshfind currently provides no exact stable npm version, every dshfind item remains browse-only in **Discover**. +- **Installable** is derived from the selected source's complete index and is fail-closed. It requires reviewed provider verification with `repository_backlink`, an exact stable npm target, and a canonical repository, and excludes product-blocked packages. Installed, receipted, disabled, or subsequently uninstalled packages remain visible whenever the selected catalog still contains them. Its cards use the same dialog. Structural candidacy is not an npm verification, permission to perform a local operation, code review, or endorsement. Because dshfind currently provides no exact stable npm version, every dshfind item remains browse-only in **Discover**. - **Installed** reconciles the active profile's Host inventory with valid Market receipts. It never infers installed state from the catalog. - **Sources** manages saved sources and the one current selection. @@ -102,7 +102,7 @@ Loading the catalog never invokes a package manager, resolves a local executable ## Installation boundary -Clicking a card is an explicit request to inspect that item. The dialog opens synchronously, while the Host checks whether the exact normalized source/item is eligible for managed preview. The Host, not the renderer, owns candidate identity and performs the first authoritative verification against the official npm registry and active profile. Only a successful preview turns that same dialog into a confirmation showing: +Clicking a card is an explicit request to inspect that item. The dialog opens synchronously, while the Host checks whether the exact normalized source/item is eligible for managed preview. Catalog-derived structural candidacy and local action availability are separate: the Host may refuse installation because of current profile, receipt, or other local state without removing the catalog card. The Host, not the renderer, owns candidate identity and performs the first authoritative verification against the official npm registry and active profile. Only a successful preview turns that same dialog into a confirmation showing: - plugin name; - exact npm package name and stable version resolved by the Host; diff --git a/dsh-community-market/docs/market-shell.zh.md b/dsh-community-market/docs/market-shell.zh.md index faea0432bb..37e12da767 100644 --- a/dsh-community-market/docs/market-shell.zh.md +++ b/dsh-community-market/docs/market-shell.zh.md @@ -72,7 +72,7 @@ DSH Community Market 以开放方式与各种插件数据源合作。任何人 Host 会先针对已选来源和当前 locale 完成一次全量标准化扫描,再提供目录交互。标准来源按照声明的 cursor 和有效 page limit 扫描到结束;经过审核的 1024Store adapter 则只执行一次完整 registry GET,标准化每个合法条目,并按每块最多 100 条的 Schema 上限输出。dshfind adapter 会遍历每页最多 100 条的 REST 数据,并在所有后续分页中固定首页的 `data_version`。由于其公布的匿名配额低于当前首次同步所需的 page 数,首次扫描会主动节流,可能明显更慢;版本过期或限流失败时不会发布部分索引。10,000 条 Host 上限、来源身份、取消、provenance 和同源检查覆盖整次扫描。 -搜索、排序、多分类 OR 筛选、分类枚举和分页只在这份完整本地索引上运行。UI 每页最多展示 50 条匹配结果;**加载更多**推进 Host 拥有的本地 cursor,不会再次向 provider 发出带筛选的请求。分类列表是索引中存在的完整分类集合。**可安装**是同一索引上 fail-closed 的结构子集,不是第二个 provider feed,也不是逐包请求 registry 得出的结果。 +搜索、排序、多分类 OR 筛选、分类枚举和分页只在这份完整本地索引上运行。UI 每页最多展示 50 条匹配结果;**加载更多**推进 Host 拥有的本地 cursor,不会再次向 provider 发出带筛选的请求。分类列表是索引中存在的完整分类集合。**可安装**是同一索引上 fail-closed 的结构子集,不是第二个 provider feed,也不是逐包请求 registry 得出的结果。它的目录成员资格与本地安装、receipt、卸载历史及启用/禁用状态无关。 完成的索引会在有界时间内复用,当前默认五分钟。可选 response metadata 可以提供:`scannedAt`(扫描完成时间)、`expiresAt`(cache 截止时间)、可选 `providerRevision`(所有分块中一致观察到的 revision),以及 `cacheStatus`(完成新扫描时为 `fresh`,复用索引时为 `cached`)。明确刷新会使旧索引失效,并绕过底层目录 HTTP cache 后重新建立。选择另一个来源会取消旧扫描并建立独立索引。 @@ -81,7 +81,7 @@ Host 会先针对已选来源和当前 locale 完成一次全量标准化扫描 Market 界面包含四个视图: - **发现**对当前已选来源完整本地索引中的全部标准化条目进行分页。点击卡片会立即打开统一操作弹窗;Host 会让合格条目进入受管 preview,否则弹窗保持为详情。 -- **可安装**在本地以 fail-closed 方式生成。条目必须具有经过审核的 provider 验证与 `repository_backlink`、精确稳定的 npm 目标和规范仓库,同时排除被阻止的 package,以及当前 profile 或 Market receipt 中已经存在的 package。这里的卡片使用同一个弹窗。结构候选身份不等于 npm 复核、代码审核或推荐。由于 dshfind 当前没有提供精确稳定的 npm 版本,它的所有条目都只在**发现**中浏览。 +- **可安装**从已选来源的完整索引中以 fail-closed 方式生成。条目必须具有经过审核的 provider 验证与 `repository_backlink`、精确稳定的 npm 目标和规范仓库,同时排除产品 blocklist 中的 package。只要已选目录仍然包含条目,已经安装、已有 receipt、处于禁用状态或后来已卸载的 package 都会继续显示。这里的卡片使用同一个弹窗。结构候选身份不等于 npm 复核、本地操作许可、代码审核或推荐。由于 dshfind 当前没有提供精确稳定的 npm 版本,它的所有条目都只在**发现**中浏览。 - **已安装**会核对当前 profile 的 Host 清单与合法 Market receipt,绝不会根据目录猜测安装状态。 - **来源**管理已保存来源和唯一的当前选择。 @@ -102,7 +102,7 @@ Market 界面包含四个视图: ## 安装边界 -点击卡片表示用户明确要求检查该条目。弹窗会同步打开,同时由 Host 判断这个精确的标准化来源/条目能否进入受管 preview。候选身份由 Host 而不是 renderer 掌握;Host 会首次针对该 package 访问官方 npm registry,并结合当前 profile 做权威复核。只有 preview 成功后,同一个弹窗才会切换成确认框并展示: +点击卡片表示用户明确要求检查该条目。弹窗会同步打开,同时由 Host 判断这个精确的标准化来源/条目能否进入受管 preview。目录推导的结构候选身份与本地操作是否可用是两件事:Host 可以因为当前 profile、receipt 或其他本地状态拒绝安装,但不会因此移除目录卡片。候选身份由 Host 而不是 renderer 掌握;Host 会首次针对该 package 访问官方 npm registry,并结合当前 profile 做权威复核。只有 preview 成功后,同一个弹窗才会切换成确认框并展示: - 插件名称; - Host 解析出的精确 npm package 名与稳定版本; diff --git a/dsh-community-market/src/api-types.ts b/dsh-community-market/src/api-types.ts index db46ebf327..8ee1f161ce 100644 --- a/dsh-community-market/src/api-types.ts +++ b/dsh-community-market/src/api-types.ts @@ -141,7 +141,7 @@ export interface MarketInstallationsResponse { readonly installations: readonly MarketInstallationView[] } -/** Complete Host-preverified subset for the active catalog source. */ +/** Complete Host-derived structural subset; local install state never changes catalog membership. */ export interface MarketInstallableResponse { readonly source: MarketSourceView readonly items: CatalogSnapshot['items'] diff --git a/dsh-community-market/src/install/service.ts b/dsh-community-market/src/install/service.ts index 8eec64aa8b..998ce3a4a6 100644 --- a/dsh-community-market/src/install/service.ts +++ b/dsh-community-market/src/install/service.ts @@ -720,7 +720,6 @@ export class MarketInstallService { signal: AbortSignal, ): Promise { const operationSignal = this.operationSignal(signal) - await this.ensureRecoveredInstallReconciled() operationSignal.throwIfAborted() this.purge() const currentKeys = new Set(index.snapshots.flatMap(snapshot => ( @@ -733,26 +732,10 @@ export class MarketInstallService { } for (const snapshot of index.snapshots) this.observeCatalog(snapshot) operationSignal.throwIfAborted() - const profile = this.profile() - let profileManifest: JsonManifest - try { profileManifest = await readManifest(join(profile.dir, 'package.json')) } - catch { - throw new MarketInstallError('operation-failed', 'The active desktop profile could not be inspected.') - } - operationSignal.throwIfAborted() - const receiptPackages = new Set( - this.receipts() - .filter(receipt => receipt.profileName === profile.name) - .map(receipt => receipt.packageName), - ) - const disabledPackages = this.disabledPackages() const items = index.snapshots.flatMap(snapshot => snapshot.items).filter(item => { const candidate = this.candidates.get(candidateKey(index.source.sourceRecordId, item.id)) return candidate !== undefined && candidate.providerId === item.provenance.providerId - && !disabledPackages.has(candidate.packageName) - && !receiptPackages.has(candidate.packageName) - && !profileReferencesPlugin(profileManifest, candidate.packageName) }) return { source: index.source, diff --git a/dsh-community-market/tests/market-install.spec.ts b/dsh-community-market/tests/market-install.spec.ts index 02fd2c4b60..b28b45818e 100644 --- a/dsh-community-market/tests/market-install.spec.ts +++ b/dsh-community-market/tests/market-install.spec.ts @@ -435,44 +435,48 @@ describe('market install service', () => { expect(settings.receipts()).toEqual([]) }) - it('lists structurally installable, currently uninstalled candidates without registry fan-out', async () => { + it('lists structural catalog candidates independently of profile, receipt, and disabled state without registry fan-out', async () => { const profileDir = await createProfile() await writeFile(join(profileDir, 'package.json'), JSON.stringify({ name: 'fixture-profile', dependencies: { [`${packageName}-0`]: version }, dsh: { profile: { bundles: [`${packageName}-0`] } }, })) - let active = 0 - let maximumActive = 0 - const verifiedPackages: string[] = [] - const verifier = { - verify: vi.fn(async (candidate: { packageName: string }, signal: AbortSignal) => { - signal.throwIfAborted() - verifiedPackages.push(candidate.packageName) - active += 1 - maximumActive = Math.max(maximumActive, active) - try { - await new Promise(resolve => setTimeout(resolve, 5)) - if (candidate.packageName === `${packageName}-1`) { - throw new MarketInstallError('verification-failed', 'fixture rejection') - } - return verification - } finally { - active -= 1 - } - }), + const receipt: MarketInstallReceipt = { + receiptId: 'receipt:list-state-independent-0001', + profileName: 'web', + packageName: `${packageName}-1`, + version, + integrity, + bundlePatch: './cordis.patch.yml', + sourceRecordId: 'source-1', + providerId: DSH_1024STORE_PROVIDER_ID, + itemId: `example/${packageName}-1`, + displayName: 'Safe Plugin 1', + installedAt: '2026-08-18T00:00:00.000Z', } + const verifier = { + verify: vi.fn(async () => verification), + } + const recoveredInstallReceiptIds = vi.fn(async () => [receipt.receiptId]) + const acknowledgeRecoveredInstall = vi.fn(async () => {}) + const currentProfile = vi.fn(() => ({ name: 'web', dir: profileDir })) + const disabledPackageNames = vi.fn(() => [`${packageName}-2`]) const service = new MarketInstallService( - memoryScope().scope, - () => ({ name: 'web', dir: profileDir }), - runner(profileDir, []), + memoryScope([receipt]).scope, + currentProfile, + { + ...runner(profileDir, []), + recoveredInstallReceiptIds, + acknowledgeRecoveredInstall, + }, verifier, + { disabledPackageNames }, ) const index = fullIndex(snapshotWithCandidates(7)) const installable = await service.listInstallable(index, new AbortController().signal) - expect(maximumActive).toBe(0) - expect(verifiedPackages).toHaveLength(0) expect(installable.items.map(target => target.package?.name)).toEqual([ + `${packageName}-0`, `${packageName}-1`, `${packageName}-2`, `${packageName}-3`, @@ -481,11 +485,15 @@ describe('market install service', () => { `${packageName}-6`, ]) expect(installable.items[0]).toMatchObject({ - id: `example/${packageName}-1`, + id: `example/${packageName}-0`, latestVersion: version, }) await expect(service.listInstallable(index, new AbortController().signal)).resolves.toMatchObject({ items: installable.items }) expect(verifier.verify).not.toHaveBeenCalled() + expect(currentProfile).not.toHaveBeenCalled() + expect(disabledPackageNames).not.toHaveBeenCalled() + expect(recoveredInstallReceiptIds).not.toHaveBeenCalled() + expect(acknowledgeRecoveredInstall).not.toHaveBeenCalled() }) it('returns the complete local catalog beyond the former 2048-candidate cap', async () => {