diff --git a/.github/workflows/check-dsh-plugin.yml b/.github/workflows/check-dsh-plugin.yml index a25a2ce85..808e5ce43 100644 --- a/.github/workflows/check-dsh-plugin.yml +++ b/.github/workflows/check-dsh-plugin.yml @@ -136,6 +136,11 @@ jobs: matrix: dsh: ['0.2.1-rc.2'] node: ['24.2.0', '24'] + include: + - dsh: '0.2.1-rc.5' + node: '24.2.0' + - dsh: '0.2.1-rc.5' + node: '24' runs-on: ubuntu-latest timeout-minutes: 30 steps: @@ -173,13 +178,24 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] dsh: ['0.2.0-rc.2', '0.2.1-rc.2'] + node: ['24.2.0'] + include: + - os: ubuntu-latest + dsh: '0.2.1-rc.5' + node: '24' + - os: macos-latest + dsh: '0.2.1-rc.5' + node: '24' + - os: windows-latest + dsh: '0.2.1-rc.5' + node: '24' runs-on: ${{ matrix.os }} timeout-minutes: 30 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: '24.2.0' + node-version: ${{ matrix.node }} - uses: oven-sh/setup-bun@v2 with: bun-version: '1.3.13' @@ -188,7 +204,7 @@ jobs: run: bun install --frozen-lockfile - uses: actions/download-artifact@v4 with: - name: skillhub-dsh-development-package-${{ matrix.os }}-node-24.2.0 + name: skillhub-dsh-development-package-${{ matrix.os }}-node-${{ matrix.node }} path: plugins/dsh/artifacts - name: Validate public download cache before restore working-directory: plugins/dsh diff --git a/openspec/changes/add-dsh-native-plugin/compatibility-design.md b/openspec/changes/add-dsh-native-plugin/compatibility-design.md index 4194b1c07..8c40e1abe 100644 --- a/openspec/changes/add-dsh-native-plugin/compatibility-design.md +++ b/openspec/changes/add-dsh-native-plugin/compatibility-design.md @@ -218,3 +218,20 @@ The candidate branch adds `0.2.1-rc.2` alongside retained `0.2.0-rc.2`; this sup Upstream npm artifacts for the two releases were compared: user-questions declarations/implementation are unchanged; Gateway now requires an explicit Peer for direct in-process calls, while the plugin uses generated Remote bindings. The integration observers must use the official Connection Peer API on the new release. The retained generator emits the shared RPC contract while source checks use current Host/Client declarations. Both generators enforce registered workspace boundaries for merged declarations. The new generator also emits an import from `@deepseek-ai/dsh-typert-protocol/remote-info`, a subpath absent from the retained RC. Keeping the retained compiler preserves the common generated format; it has no runtime protocol dependency and does not downgrade the current protocol module. CI preserves the old RC's OS/Node coverage and retained Cordis smoke, adding an independent new-RC official Host matrix at Node 24.2.0 and current Node 24 on Linux. Native initialization, package installation, runtime-baseline and plugin-lifecycle probes must pass without peer overrides on fresh graphs. These probes do not replace browser/settings/search/install/upgrade/uninstall acceptance, macOS/Windows native operations, or real-model evidence. Final verification records passed and pending dimensions separately in acceptance.md. + + +## 2026-11-04:新增RC5候选与保留基线 + +本轮目标为验证并适配官方 `0.2.1-rc.5`,保留已支持的 `0.2.0-rc.2` 与 `0.2.1-rc.2` 回归。此处是候选实施范围,不表示公开npm支持声明已改变。精确版本支持必须绑定实际依赖组合、Node条件、Host/Remote协议和Client行为;不仅增加peer字符串,不以本机插件副本掩盖宿主混装。alpha不自动纳入本轮。 + +先对照已发布包的清单、声明和实际实现,完成原候选在RC5上的阻断复现,再确定适配文件。生成器保持现有共同协议,除非具体不兼容证据证明必须变更;若需要变更,应同时验证旧Host而不是只验证新版本。会话导航的空白对话复用与草稿接管行为须在真实RC5中单独核对。 + +新增版本仍遵守可信上下文、逐次重新核验解析图、精确目标、确认、幂等、既有事务恢复和库存v2兼容。依赖不受支持时新写入保持拒绝,原操作查询不丢失。SaaS API、技能创作、UI重设计及发布不在本轮实施范围。实际结果按第53章逐项记录,平台、浏览器、模型及发布证据分开。 + +本轮实施结果:精确peer与Host/Client实际解析图增加RC5,Node范围维持 `>=24.2.0 <25`;两个0.2.1候选仅接受Cordis 4.0.5完整家族,旧0.2.0候选保留两组家族。已发布包比对表明使用到的Remote协议、技能文件系统及路径接口一致,因此保留生成器0.2.0-rc.2及现有新建会话API,不引入导航或事务重写。新增源码回归覆盖RC5、不同发行版混装、未知RC/alpha、不可读和变化的清单,以及未验证状态中的支持版本说明。 + +Linux隔离验收使用官方RC5依赖图;原候选被官方插件安装门禁拒绝,适配后的私有候选无需 `allow-version`、风险或peer覆盖即可安装。Node 24.2.0和24.17.0分别执行真实Host探针;两个旧RC2在24.2.0复验。旧宿主依赖图只读复用,新Profile、HOME和库存隔离,不把该回归描述为旧宿主全新下载。首次RC5冷安装下载超时后,在独立环境使用现有公共镜像和下载缓存完成重试;未修改全局npm配置。 + +CI保留原14个组合,扩展为19个:6个构建检查、4个官方Host组合、9个启停组合。RC5官方Host包含Node 24.2.0及当前24,启停覆盖三个OS;这只是工作流配置和矩阵校验,远程CI尚未运行。原生Windows/macOS、RC5真实模型和生产SaaS未在本轮执行,发布后置。 + +浏览器需区分新建会话与返回空白草稿:官方RC5侧栏不展示非当前空白会话,原生“新会话”也不保证定位精确旧草稿。因此不得声称原生侧栏返回已经通过;验收驱动仅保存会话ID,通过公开 `uiWorkspace.openSession` 返回原会话,再检查真实编辑器和附件。该驱动只位于外部验收目录,不进入插件,不保活会话、不修改草稿、不发送模型消息。 diff --git a/openspec/changes/add-dsh-native-plugin/specs/dsh-native-experience/spec.md b/openspec/changes/add-dsh-native-plugin/specs/dsh-native-experience/spec.md index 2912814d6..040d3dde8 100644 --- a/openspec/changes/add-dsh-native-plugin/specs/dsh-native-experience/spec.md +++ b/openspec/changes/add-dsh-native-plugin/specs/dsh-native-experience/spec.md @@ -627,3 +627,22 @@ The plugin SHALL provide conversation tools and compact cards for listing curren - **WHEN** multiple grouped skills are shown in a wide tool container - **THEN** cards appear in at most two columns, each with its own installation selection and relevant actions - **AND** narrow containers use one column and a single skill stays compact; opened management controls remain visible and operable within their card + +### Requirement: Explicit RC5 candidate with retained RC2 compatibility + +The source candidate SHALL enumerate `0.2.0-rc.2`, `0.2.1-rc.2` and `0.2.1-rc.5` as exact prerelease targets rather than accepting an unverified future-version range. Host anchors, required Host modules and injected Client owners SHALL belong to one coherent release. Both `0.2.1` candidates SHALL require the complete verified Cordis 4.0.5 family; `0.2.0-rc.2` SHALL retain its separately verified families. The candidate SHALL preserve Node `>=24.2.0 <25`, the shared inventory v2 contract and the existing installation transaction rules. Candidate acceptance SHALL NOT change the support declaration of the published npm package. + +#### Scenario: Install on the RC5 target +- **WHEN** the candidate is installed on a coherent `0.2.1-rc.5` Host +- **THEN** normal plugin installation and native Host/Remote calls work without a version-risk override or a peer-dependency bypass +- **AND** user/project targets, explicit confirmation, installation, upgrade, uninstall and activation use the existing protected flows + +#### Scenario: Retained hosts and an incoherent upgrade +- **WHEN** the candidate runs on either retained RC2 target or an environment containing an unverified RC, mixed release graph, unreadable manifest or unsupported Cordis family +- **THEN** a coherent retained target continues to pass its regression +- **AND** an unverified or incoherent graph rejects new mutations while preserving original operation queries and stored data + +#### Scenario: Lower-bound and platform evidence +- **WHEN** RC5 support is evaluated for release +- **THEN** Node 24.2.0 and current Node 24, native Host behavior, browser interaction and platform CI are recorded separately +- **AND** a local Linux pass does not imply native Windows/macOS, model execution or published-package acceptance diff --git a/openspec/changes/add-dsh-native-plugin/tasks.md b/openspec/changes/add-dsh-native-plugin/tasks.md index 30538410f..ee4594a1f 100644 --- a/openspec/changes/add-dsh-native-plugin/tasks.md +++ b/openspec/changes/add-dsh-native-plugin/tasks.md @@ -461,3 +461,18 @@ - [x] 52.4 核心/Host/UI 回归、恶意 ZIP 拒绝、兼容验证和实际浏览器验收;更新文档与验证边界。 第52章验证边界:Linux CLI 完整877测试、DSH完整722测试及build/typecheck/lint通过;真实DSH 0.2.1-rc.2浏览器完成项目/用户导入、导出字节往返、原生案例填入、启停卸载、可用性、明暗/窄屏及键盘焦点。公开旧DSH0.6.1核心启动/查询/普通受理与旧CLI0.2.0查询补验通过;新增导入两阶段Linux真实SIGKILL恢复通过。未运行新增功能的RC0.2.0实际Host、原生Windows/macOS、真实模型或PR CI;未提交、合并或发布,不能据此宣称发布验收完成。 + + +## 53. DSH 0.2.1-rc.5 兼容验证与适配 + +- [x] 53.1 核对已发布RC5与两个保留RC2的Node、Cordis、Host/Client及Remote协议差异,复现未适配候选的具体阻断;不把放宽版本声明计作兼容通过。 +- [x] 53.2 按证实的差异最小调整适配、精确依赖和运行时守卫,保留混装/未知/不可读图拒写及原请求查询,不改变库存v2与文件事务语义。 +- [x] 53.3 以隔离原生Host验证RC5干净安装、生命周期、RPC、项目/用户技能安装/升级/卸载/启停,补使用技能新对话和草稿保护的真实浏览器证据。 +- [x] 53.4 保留两个RC2的有意义回归,补兼容/协议/脚本边界用例与CI入口;新基线的Node下界以实际宿主要求及验收确定,不盲目沿用或提升旧基线。 +- [x] 53.5 独立评审、源码及打包产物校验、更新兼容矩阵与验证边界;发布后置,未执行的平台、模型及CI单独记录。 + +第53章验证结果(2026-11-04):最终源码构建、类型检查、lint及731测试/17877断言通过;OpenSpec严格校验与diff检查通过,独立源码评审未发现blocker/HIGH。私有候选通过官方插件安装门禁,未使用版本风险或peer覆盖;最终安装包Host bundle与构建逐字节一致。Linux真实Host共18项探针通过:RC5在Node 24.17.0的7项、在24.2.0的5项,以及两个旧RC2在24.2.0各3项。原生RC5覆盖生命周期、Remote调用、可用性、项目/用户安装状态、候选升级拒绝与正常升级、启停及watch-off;旧依赖图只读复用并隔离HOME/Profile/库存,不声称旧宿主全新下载。 + +Chromium 145.0.7632.6连接真实RC5 Host与本地Registry夹具,验证目录/版本、用户级安装、项目级ZIP导入、案例取消和填入、新会话使用、原草稿文字与附件保留、390px及深浅主题,页面异常为0。原附件上传HTTP200且原生UI无上传中/失败状态。返回原空白会话由公开SDK验收驱动完成,原生侧栏隐藏空白会话的限制未解决;未发送模型消息。驱动不保活、不改写草稿,不进入发布包。 + +准备阶段首次官方npm下载超时,公共镜像与缓存重试后成功;首次复用同一HOME的installed-state探针因前序不同命名空间占用用户技能目标而被覆盖保护拒绝,改用逐探针独立HOME后全部通过,未因此放宽产品保护。CI19组合配置与制品匹配已校验,但远程CI、原生Windows/macOS、真实模型、SaaS及发布未执行。外部验收记录包含final-dsh-check.log、verified-installs.json、final-*-matrix.json、browser-acceptance.json、final-review.json和截图;临时预览在验收后关闭。 diff --git a/plugins/dsh/README.en.md b/plugins/dsh/README.en.md index 0d10698a9..1d24341e8 100644 --- a/plugins/dsh/README.en.md +++ b/plugins/dsh/README.en.md @@ -35,17 +35,19 @@ In the detail's installation management panel, each location's Manage menu expor | Component | Requirement or scope | | --- | --- | -| DSH | `0.2.0-rc.2` or `0.2.1-rc.2`; Host, app-boot and injected Client modules must share the exact release | -| Cordis | Old RC: both families below; `0.2.1-rc.2`: only `4.0.5 / 1.0.6 / 1.0.10 / 1.1.7` | +| DSH | source candidate: `0.2.0-rc.2`, `0.2.1-rc.2`, or `0.2.1-rc.5`; Host, app-boot and injected Client modules must share the exact release | +| Cordis | Old RC: both families below; `0.2.1-rc.2` and `0.2.1-rc.5`: only `4.0.5 / 1.0.6 / 1.0.10 / 1.1.7` | | Host Node | `>=24.2.0 <25`; Node 24.0.0 is unsuitable | | Build package manager | Bun `1.3.13`, using frozen lockfiles | | UI | Chinese, DSH light/dark themes and narrow layouts | -| Verified environments | Both Linux DSH Hosts: strict installation, native probes and browser user/project install, upgrade, removal and cancellation; Windows browser UI and interactions against a real Host | -| Validation boundary | The feature implementation has real-model and cross-platform CI evidence; built artifacts and public packages have separate acceptance records. These results do not substitute for desktop clients, native Windows/macOS manual interactions or SaaS acceptance | +| Verified environments | Historical dual-RC2 acceptance remains separately recorded. This Linux round passes native RC5 probes on Node 24.2.0/24.17.0 and both RC2 regressions on Node 24.2.0; task 53 records browser evidence separately | +| Validation boundary | Historical evidence does not automatically cover RC5. Remote CI, native Windows/macOS, real models and SaaS have not run in this round; candidate tarballs and public npm packages have separate acceptance | Windows browser acceptance does not establish complete native Windows Host filesystem support. CI results do not replace desktop, model, or target-instance acceptance. See the repository's [compatibility design](../../openspec/changes/add-dsh-native-plugin/compatibility-design.md) and [acceptance criteria](../../openspec/changes/add-dsh-native-plugin/acceptance.md). -Version `0.3.1` includes the merged [Issue #981 compatibility fix](https://github.com/iflytek/skillhub/pull/988). Published plugin `0.3.0` does not include it. Unknown releases and mixed Host/Client graphs reject new writes. Optional `userQuestions` is enabled by Host service availability; it is not part of the mandatory write-version gate. Its API is identical in the two enumerated RCs, both of which the peer declaration permits. An absent service falls back to the plugin confirmation dialog. +Version `0.3.1` includes the merged [Issue #981 compatibility fix](https://github.com/iflytek/skillhub/pull/988). Published plugin `0.3.0` does not include it. Unknown releases and mixed Host/Client graphs reject new writes. Optional `userQuestions` is enabled by Host service availability; it is not part of the mandatory write-version gate. Its API is identical in the three enumerated RCs, each of which the peer declaration permits. An absent service falls back to the plugin confirmation dialog. + +The source candidate adds `0.2.1-rc.5`; compatibility and acceptance are tracked in the [compatibility design](../../openspec/changes/add-dsh-native-plugin/compatibility-design.md) and task 53 verification. This does not extend the published npm `0.6.1` support declaration. Platform, Node boundary, browser, and model evidence remain separate; release is a separate step. ## Build and package @@ -60,7 +62,7 @@ bun run check npm pack --dry-run --ignore-scripts ``` -`dsh-typert-generator` remains pinned to `0.2.0-rc.2` to emit the common RPC contract for both Hosts; other DSH development modules use `0.2.1-rc.2` for type checking. This compiler pin does not bypass the runtime release guard. +`dsh-typert-generator` remains pinned to `0.2.0-rc.2` to emit the common RPC contract for the enumerated Hosts; other DSH development modules use `0.2.1-rc.2` for type checking. This compiler pin does not bypass the runtime release guard. `check` builds Host/Client, generated Remote bindings and declarations, then runs type checking, lint and tests. Lint uses the locked tools in `cli/`. Bun is a build dependency; the packaged Host runs under Node. React and React DOM are supplied by DSH. diff --git a/plugins/dsh/README.md b/plugins/dsh/README.md index 4035b19fa..3b36eb8f6 100644 --- a/plugins/dsh/README.md +++ b/plugins/dsh/README.md @@ -35,17 +35,19 @@ SkillHub 的 DSH 原生插件。在 DSH 侧栏打开 **技能中心**,匿名 | 项目 | 要求或范围 | | --- | --- | -| DSH | `0.2.0-rc.2` 或 `0.2.1-rc.2`;Host、app-boot 与注入 Client 模块必须属于同一精确发行版 | -| Cordis | `0.2.0-rc.2` 保留以下两组完整组合;`0.2.1-rc.2` 仅使用 `4.0.5 / 1.0.6 / 1.0.10 / 1.1.7` | +| DSH | 源码候选支持 `0.2.0-rc.2`、`0.2.1-rc.2`、`0.2.1-rc.5`;Host、app-boot 与注入 Client 模块必须属于同一精确发行版 | +| Cordis | `0.2.0-rc.2` 保留以下两组完整组合;`0.2.1-rc.2` 与 `0.2.1-rc.5` 仅使用 `4.0.5 / 1.0.6 / 1.0.10 / 1.1.7` | | Host Node | `>=24.2.0 <25`;不能使用 Node 24.0.0 | | 开发包管理器 | Bun `1.3.13`,按锁文件安装 | | 当前 UI | 中文;支持 DSH 深浅主题和窄屏 | -| 已验证环境 | Linux 两版 DSH Host 严格安装、原生探针及浏览器用户/项目安装、升级、卸载与取消;Windows 浏览器访问真实 Host 的界面和交互 | -| 验证边界 | 功能实现已进行真实模型对话及跨平台 CI;构建产物与公开包的验收分别记录。真实桌面客户端、原生 Windows/macOS 人工交互和 SaaS 验收不由这些结果替代 | +| 已验证环境 | 历史双RC2验收见既有记录。本轮Linux:RC5在Node 24.2.0/24.17.0原生探针通过,两个RC2在Node 24.2.0回归通过;浏览器及其他维度按第53节分别记录 | +| 验证边界 | 历史功能证据不自动覆盖RC5。本轮远程CI、原生Windows/macOS、真实模型和SaaS未执行;候选包与公开npm包分开验收 | Windows 浏览器验证不等于 Windows 原生 Host 的全部文件操作已经验证。CI 的平台矩阵也不替代真实桌面、模型及目标实例验收。完整支持边界见仓库内的 [兼容设计](../../openspec/changes/add-dsh-native-plugin/compatibility-design.md) 和 [验收标准](../../openspec/changes/add-dsh-native-plugin/acceptance.md)。 -`0.3.1` 包含已合并的 [Issue #981 兼容修复](https://github.com/iflytek/skillhub/pull/988);已发布的 `0.3.0` 不包含此修复。其他 RC、未来发行版、混装模块和 Host/Client 版本不一致均拒绝新写入。可选 `userQuestions` 按宿主服务是否可用启用,缺失时仍回退插件确认框;其版本不属于必需写入门禁。两个已列 RC 的问答 API 一致,peer 声明允许二者,不据此承诺其他版本兼容。 +`0.3.1` 包含已合并的 [Issue #981 兼容修复](https://github.com/iflytek/skillhub/pull/988);已发布的 `0.3.0` 不包含此修复。其他 RC、未来发行版、混装模块和 Host/Client 版本不一致均拒绝新写入。可选 `userQuestions` 按宿主服务是否可用启用,缺失时仍回退插件确认框;其版本不属于必需写入门禁。三个已列 RC 的问答 API 一致,peer 声明允许这些精确版本,不据此承诺其他版本兼容。 + +源码候选新增 `0.2.1-rc.5` 的支持范围与验收按 [兼容设计](../../openspec/changes/add-dsh-native-plugin/compatibility-design.md) 及任务第53节的验证记录维护;新增版本不表示公开 npm `0.6.1` 已具备同等支持。平台、Node边界、浏览器与模型验收分别记录,发布另行安排。 ## 构建与打包 @@ -60,7 +62,7 @@ bun run check npm pack --dry-run --ignore-scripts ``` -`dsh-typert-generator` 固定在 `0.2.0-rc.2` 以生成两个宿主共用的 RPC 契约;其他 DSH 开发依赖按 `0.2.1-rc.2` 类型检查。生成器版本不是宿主版本绕过条件。 +`dsh-typert-generator` 固定在 `0.2.0-rc.2` 以生成已列宿主共用的 RPC 契约;其他 DSH 开发依赖按 `0.2.1-rc.2` 类型检查。生成器版本不是宿主版本绕过条件。 `check` 依次构建 Host、Client、Remote 绑定与类型声明,执行 typecheck、lint 和测试。插件 lint 使用 `cli/` 中锁定的工具。Bun 仅用于开发构建,包内 Host 在 Node 上运行;React/React DOM 由 DSH 提供。 diff --git a/plugins/dsh/bun.lock b/plugins/dsh/bun.lock index e11e41815..7d9d72d18 100644 --- a/plugins/dsh/bun.lock +++ b/plugins/dsh/bun.lock @@ -62,16 +62,16 @@ }, "peerDependencies": { "@deepseek-ai/cordis": "4.0.4 || 4.0.5", - "@deepseek-ai/dsh-fs": "0.2.0-rc.2 || 0.2.1-rc.2", - "@deepseek-ai/dsh-home-paths": "0.2.0-rc.2 || 0.2.1-rc.2", - "@deepseek-ai/dsh-sandbox": "0.2.0-rc.2 || 0.2.1-rc.2", - "@deepseek-ai/dsh-sandbox-policy": "0.2.0-rc.2 || 0.2.1-rc.2", - "@deepseek-ai/dsh-session": "0.2.0-rc.2 || 0.2.1-rc.2", - "@deepseek-ai/dsh-session-query": "0.2.0-rc.2 || 0.2.1-rc.2", - "@deepseek-ai/dsh-skill-filesystem": "0.2.0-rc.2 || 0.2.1-rc.2", - "@deepseek-ai/dsh-tools": "0.2.0-rc.2 || 0.2.1-rc.2", - "@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.2 || 0.2.1-rc.2", - "@deepseek-ai/dsh-user-questions": "0.2.0-rc.2 || 0.2.1-rc.2", + "@deepseek-ai/dsh-fs": "0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.5", + "@deepseek-ai/dsh-home-paths": "0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.5", + "@deepseek-ai/dsh-sandbox": "0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.5", + "@deepseek-ai/dsh-sandbox-policy": "0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.5", + "@deepseek-ai/dsh-session": "0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.5", + "@deepseek-ai/dsh-session-query": "0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.5", + "@deepseek-ai/dsh-skill-filesystem": "0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.5", + "@deepseek-ai/dsh-tools": "0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.5", + "@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.5", + "@deepseek-ai/dsh-user-questions": "0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.5", }, "optionalPeers": [ "@deepseek-ai/dsh-user-questions", diff --git a/plugins/dsh/package.json b/plugins/dsh/package.json index d817d5536..28fc1a3ea 100644 --- a/plugins/dsh/package.json +++ b/plugins/dsh/package.json @@ -77,16 +77,16 @@ }, "peerDependencies": { "@deepseek-ai/cordis": "4.0.4 || 4.0.5", - "@deepseek-ai/dsh-fs": "0.2.0-rc.2 || 0.2.1-rc.2", - "@deepseek-ai/dsh-home-paths": "0.2.0-rc.2 || 0.2.1-rc.2", - "@deepseek-ai/dsh-sandbox": "0.2.0-rc.2 || 0.2.1-rc.2", - "@deepseek-ai/dsh-sandbox-policy": "0.2.0-rc.2 || 0.2.1-rc.2", - "@deepseek-ai/dsh-session": "0.2.0-rc.2 || 0.2.1-rc.2", - "@deepseek-ai/dsh-session-query": "0.2.0-rc.2 || 0.2.1-rc.2", - "@deepseek-ai/dsh-skill-filesystem": "0.2.0-rc.2 || 0.2.1-rc.2", - "@deepseek-ai/dsh-tools": "0.2.0-rc.2 || 0.2.1-rc.2", - "@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.2 || 0.2.1-rc.2", - "@deepseek-ai/dsh-user-questions": "0.2.0-rc.2 || 0.2.1-rc.2" + "@deepseek-ai/dsh-fs": "0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.5", + "@deepseek-ai/dsh-home-paths": "0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.5", + "@deepseek-ai/dsh-sandbox": "0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.5", + "@deepseek-ai/dsh-sandbox-policy": "0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.5", + "@deepseek-ai/dsh-session": "0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.5", + "@deepseek-ai/dsh-session-query": "0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.5", + "@deepseek-ai/dsh-skill-filesystem": "0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.5", + "@deepseek-ai/dsh-tools": "0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.5", + "@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.5", + "@deepseek-ai/dsh-user-questions": "0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.5" }, "dependencies": { "@deepseek-ai/schemastery": "3.18.4", diff --git a/plugins/dsh/scripts/smoke-native-host.mjs b/plugins/dsh/scripts/smoke-native-host.mjs index 0c47f4626..1fb89f855 100644 --- a/plugins/dsh/scripts/smoke-native-host.mjs +++ b/plugins/dsh/scripts/smoke-native-host.mjs @@ -7,10 +7,10 @@ import { delimiter, dirname, isAbsolute, join, relative, resolve, sep } from 'no import { fileURLToPath } from 'node:url'; import { parseArgs } from 'node:util'; -export const supportedDshVersions = ['0.2.0-rc.2', '0.2.1-rc.2']; +export const supportedDshVersions = ['0.2.0-rc.2', '0.2.1-rc.2', '0.2.1-rc.5']; export function selectDshVersion(version, family = 'fresh') { if (!supportedDshVersions.includes(version)) throw new Error('Unsupported DSH release.'); - if (version === '0.2.1-rc.2' && family === 'retained') throw new Error('DSH 0.2.1 requires the current Cordis family.'); + if (version !== '0.2.0-rc.2' && family === 'retained') throw new Error('DSH 0.2.1 requires the current Cordis family.'); return version; } export function scenarioProbes(scenario = 'smoke') { @@ -328,7 +328,7 @@ async function findNpm() { async function main() { const { values } = parseArgs({ options: { 'download-cache': { type: 'string' }, 'dsh-version': { type: 'string', default: '0.2.0-rc.2' }, artifact: { type: 'string' }, report: { type: 'string' }, help: { type: 'boolean' }, 'cordis-family': { type: 'string', default: 'fresh' }, scenario: { type: 'string', default: 'smoke' } } }); if (values.help) { - console.log('node scripts/smoke-native-host.mjs --artifact --report [--download-cache ] [--scenario smoke|skill-toggle|skill-toggle-watch-off] [--cordis-family fresh|retained] [--dsh-version 0.2.0-rc.2|0.2.1-rc.2]'); + console.log('node scripts/smoke-native-host.mjs --artifact --report [--download-cache ] [--scenario smoke|skill-toggle|skill-toggle-watch-off] [--cordis-family fresh|retained] [--dsh-version 0.2.0-rc.2|0.2.1-rc.2|0.2.1-rc.5]'); return; } if (!values.artifact || !values.report) throw new Error('Specify --artifact and --report; see --help.'); diff --git a/plugins/dsh/src/host/compatibility.ts b/plugins/dsh/src/host/compatibility.ts index a06eba7e5..369106992 100644 --- a/plugins/dsh/src/host/compatibility.ts +++ b/plugins/dsh/src/host/compatibility.ts @@ -1,6 +1,6 @@ import { createRequire } from 'node:module'; -export const supportedDshVersions = ['0.2.0-rc.2', '0.2.1-rc.2'] as const; +export const supportedDshVersions = ['0.2.0-rc.2', '0.2.1-rc.2', '0.2.1-rc.5'] as const; export type SupportedDshVersion = typeof supportedDshVersions[number]; /** Retained Host dependency baseline; a match is necessary, not full product acceptance. */ @@ -42,7 +42,7 @@ export function assertHostDependencies(readManifest: (name: string) => unknown = if (name === '@deepseek-ai/cordis') cordis = manifest.version; else release = manifest.version; } - if (release === '0.2.1-rc.2' && cordis !== '4.0.5') { + if (release !== '0.2.0-rc.2' && cordis !== '4.0.5') { throw new Error('Host dependency baseline does not match; local writes are disabled.'); } return release as SupportedDshVersion; diff --git a/plugins/dsh/src/host/index.ts b/plugins/dsh/src/host/index.ts index 4d2601415..6e5c7122b 100644 --- a/plugins/dsh/src/host/index.ts +++ b/plugins/dsh/src/host/index.ts @@ -30,6 +30,7 @@ import type { RegistryConnectionTest, CatalogContext, CatalogLabels, CatalogPage import { RegistryAdapter, RegistryError } from './registry-adapter.js'; import { LocalRuntime } from './local-runtime.js'; import { assertRuntimeCompatibility } from './runtime-compatibility.js'; +import { supportedDshVersions } from './compatibility.js'; import type { HostCompatibility } from './types.js'; import { InstallationTargets } from './installation-targets.js'; import { ConfirmationPlans, UpgradePlans, RemovalPlans, TogglePlans } from './upgrade-plans.js'; @@ -199,7 +200,7 @@ export class SkillHubService extends TypertRemoteService { const candidateDshVersion = this.assertRuntimeBaseline(); return { dependencyBaseline: 'matched', candidateDshVersion, localWritesAllowed: true }; } catch { - return { dependencyBaseline: 'unverified', candidateDshVersion: '0.2.0-rc.2 / 0.2.1-rc.2', localWritesAllowed: false }; + return { dependencyBaseline: 'unverified', candidateDshVersion: supportedDshVersions.join(' / '), localWritesAllowed: false }; } } diff --git a/plugins/dsh/src/host/runtime-compatibility.ts b/plugins/dsh/src/host/runtime-compatibility.ts index 02e9920d6..fb57a4ff3 100644 --- a/plugins/dsh/src/host/runtime-compatibility.ts +++ b/plugins/dsh/src/host/runtime-compatibility.ts @@ -79,7 +79,7 @@ export function assertRuntimeCompatibility( const family = cordisFamilies.find(candidate => Object.entries(candidate).every(([name, version]) => packages.get(name)?.version === version)); const release = packages.get('@deepseek-ai/dsh')?.version; if (!supportedDshVersions.some(version => version === release)) throw new Error('Unsupported DSH release'); - if (!family || (release === '0.2.1-rc.2' && family['@deepseek-ai/cordis'] !== '4.0.5')) throw new Error('Runtime Cordis family mismatch'); + if (!family || (release !== '0.2.0-rc.2' && family['@deepseek-ai/cordis'] !== '4.0.5')) throw new Error('Runtime Cordis family mismatch'); for (const [name, version] of Object.entries({ ...runtimePackageBaseline, ...family })) { const expected = name.startsWith('@deepseek-ai/dsh') ? release : version; if (packages.get(name)?.version !== expected) throw new Error('Runtime package mismatch'); diff --git a/plugins/dsh/test/compatibility-node.test.ts b/plugins/dsh/test/compatibility-node.test.ts index 2ed554d8a..3edbdf5fd 100644 --- a/plugins/dsh/test/compatibility-node.test.ts +++ b/plugins/dsh/test/compatibility-node.test.ts @@ -58,6 +58,19 @@ console.log('verified'); await writeFile(path, JSON.stringify({ name, version })); } await probe('matched'); + for (const name of Object.keys(hostDependencyBaseline)) { + await writeFile(manifestPath(name), JSON.stringify({ name, version: name === '@deepseek-ai/cordis' ? '4.0.5' : '0.2.1-rc.5' })); + } + await probe('matched'); + await writeFile(manifestPath('@deepseek-ai/cordis'), JSON.stringify({ name: '@deepseek-ai/cordis', version: '4.0.4' })); + await probe('Host dependency baseline does not match; local writes are disabled.'); + await writeFile(manifestPath('@deepseek-ai/cordis'), JSON.stringify({ name: '@deepseek-ai/cordis', version: '4.0.5' })); + for (const name of Object.keys(hostDependencyBaseline).filter(name => name !== '@deepseek-ai/cordis')) { + await writeFile(manifestPath(name), JSON.stringify({ name, version: '0.2.1-rc.2' })); + await probe('Host dependency baseline does not match; local writes are disabled.'); + await writeFile(manifestPath(name), JSON.stringify({ name, version: '0.2.1-rc.5' })); + } + await probe('matched'); } finally { await rm(root, { recursive: true, force: true }); } diff --git a/plugins/dsh/test/compatibility.test.ts b/plugins/dsh/test/compatibility.test.ts index 47ded5945..549c00953 100644 --- a/plugins/dsh/test/compatibility.test.ts +++ b/plugins/dsh/test/compatibility.test.ts @@ -5,6 +5,7 @@ import { assertHostDependencies, hostDependencyBaseline, supportedDshVersions } test('Host dependency guard matches exact peer versions and installed development artifacts', async () => { const manifest = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8')); expect(manifest.peerDependenciesMeta).toEqual({ '@deepseek-ai/dsh-user-questions': { optional: true } }); + expect(manifest.peerDependencies['@deepseek-ai/dsh-user-questions']).toBe(supportedDshVersions.join(' || ')); expect(Object.fromEntries(Object.entries(manifest.peerDependencies).filter(([name]) => !manifest.peerDependenciesMeta?.[name]?.optional))).toEqual(Object.fromEntries(Object.keys(hostDependencyBaseline).map(name => [name, name === '@deepseek-ai/cordis' ? '4.0.4 || 4.0.5' : supportedDshVersions.join(' || ')]))); expect(() => assertHostDependencies()).not.toThrow(); }); @@ -24,14 +25,25 @@ for (const release of supportedDshVersions) { test(`coherent Host dependencies accept ${release} and reject mixed releases`, () => { const manifest = (name: string) => ({ name, version: name === '@deepseek-ai/cordis' ? '4.0.5' : release }); expect(assertHostDependencies(manifest)).toBe(release); - for (const target of Object.keys(hostDependencyBaseline).filter(name => name !== '@deepseek-ai/cordis')) { - expect(() => assertHostDependencies(name => name === target - ? { name, version: release === '0.2.0-rc.2' ? '0.2.1-rc.2' : '0.2.0-rc.2' } : manifest(name))).toThrow('does not match'); + for (const other of supportedDshVersions.filter(candidate => candidate !== release)) { + for (const target of Object.keys(hostDependencyBaseline).filter(name => name !== '@deepseek-ai/cordis')) { + expect(() => assertHostDependencies(name => name === target + ? { name, version: other } : manifest(name))).toThrow('does not match'); + } } }); } -test('new RC cannot use the retained Cordis core even outside the complete runtime check', () => { - expect(() => assertHostDependencies(name => ({ name, version: name === '@deepseek-ai/cordis' ? '4.0.4' : '0.2.1-rc.2' }))) - .toThrow('does not match'); +for (const release of ['0.2.1-rc.2', '0.2.1-rc.5']) { + test(`${release} cannot use the retained Cordis core even outside the complete runtime check`, () => { + expect(() => assertHostDependencies(name => ({ name, version: name === '@deepseek-ai/cordis' ? '4.0.4' : release }))) + .toThrow('does not match'); + }); +} + +test('unvalidated intermediate RCs and future releases fail closed even as coherent graphs', () => { + for (const release of ['0.2.1-rc.3', '0.2.1-rc.4', '0.2.2-alpha.3']) { + expect(() => assertHostDependencies(name => ({ name, version: name === '@deepseek-ai/cordis' ? '4.0.5' : release }))) + .toThrow('does not match'); + } }); diff --git a/plugins/dsh/test/package.test.ts b/plugins/dsh/test/package.test.ts index 945a1edf0..2b4918adb 100644 --- a/plugins/dsh/test/package.test.ts +++ b/plugins/dsh/test/package.test.ts @@ -47,6 +47,11 @@ describe('native M0 package', () => { const fiber = ctx.plugin(SkillHubService); await fiber; expect(ctx.skillhub.getStatus()).toBe('SkillHub: connected'); + expect(ctx.skillhub.getCompatibility()).toEqual({ + dependencyBaseline: 'unverified', + candidateDshVersion: '0.2.0-rc.2 / 0.2.1-rc.2 / 0.2.1-rc.5', + localWritesAllowed: false, + }); expect(ctx.skillhub.typertRemote.namespace).toBe('skillhub'); await expect(ctx.skillhub.listInstallTargets('forged-detail', new AbortController().signal)).rejects.toThrow('Conversation unavailable'); await expect(ctx.skillhub.getOperation('forged', 'forged')).rejects.toThrow('Conversation unavailable'); diff --git a/plugins/dsh/test/runtime-compatibility.test.ts b/plugins/dsh/test/runtime-compatibility.test.ts index d0b2312a6..1fdf186d4 100644 --- a/plugins/dsh/test/runtime-compatibility.test.ts +++ b/plugins/dsh/test/runtime-compatibility.test.ts @@ -2,6 +2,7 @@ import { expect, test } from 'bun:test'; import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; +import { supportedDshVersions } from '../src/host/compatibility.js'; import { assertNodeRuntime, nodeRuntimeRange, assertRuntimeCompatibility, clientPackageBaseline, runtimePackageBaseline } from '../src/host/runtime-compatibility.js'; function fixture() { @@ -150,7 +151,7 @@ test('plugin peer copies cannot mask mismatched or absent Host dependency resolu } }); -for (const release of ['0.2.0-rc.2', '0.2.1-rc.2'] as const) { +for (const release of supportedDshVersions) { test(`complete ${release} Host and Client graph matches; every mixed release is rejected`, () => { const { packages, clients, check } = fixture(); for (const value of packages.values()) if (value.name.startsWith('@deepseek-ai/dsh')) value.version = release; @@ -160,25 +161,41 @@ for (const release of ['0.2.0-rc.2', '0.2.1-rc.2'] as const) { packages.get('@deepseek-ai/cordis-plugin-timer')!.version = '1.1.7'; for (const name of clients.keys()) clients.set(name, { name, version: release }); expect(check()).toBe(release); - const other = release === '0.2.0-rc.2' ? '0.2.1-rc.2' : '0.2.0-rc.2'; - for (const value of packages.values()) { - if (!value.name.startsWith('@deepseek-ai/dsh')) continue; - value.version = other; - expect(check).toThrow('local writes are disabled'); - value.version = release; - expect(check()).toBe(release); - } - for (const name of clients.keys()) { - clients.set(name, { name, version: other }); - expect(check).toThrow('local writes are disabled'); - clients.set(name, { name, version: release }); + for (const other of supportedDshVersions.filter(candidate => candidate !== release)) { + for (const value of packages.values()) { + if (!value.name.startsWith('@deepseek-ai/dsh')) continue; + value.version = other; + expect(check).toThrow('local writes are disabled'); + value.version = release; + expect(check()).toBe(release); + } + for (const name of clients.keys()) { + clients.set(name, { name, version: other }); + expect(check).toThrow('local writes are disabled'); + clients.set(name, { name, version: release }); + } } }); } -test('new RC rejects a complete retained Cordis family below its official minimum', () => { +for (const release of ['0.2.1-rc.2', '0.2.1-rc.5']) { + test(`${release} rejects a complete retained Cordis family below its official minimum`, () => { + const { packages, clients, check } = fixture(); + for (const value of packages.values()) if (value.name.startsWith('@deepseek-ai/dsh')) value.version = release; + for (const name of clients.keys()) clients.set(name, { name, version: release }); + expect(check).toThrow('local writes are disabled'); + }); +} + +test('coherent but unvalidated intermediate RCs are not implicitly supported', () => { const { packages, clients, check } = fixture(); - for (const value of packages.values()) if (value.name.startsWith('@deepseek-ai/dsh')) value.version = '0.2.1-rc.2'; - for (const name of clients.keys()) clients.set(name, { name, version: '0.2.1-rc.2' }); - expect(check).toThrow('local writes are disabled'); + packages.get('@deepseek-ai/cordis')!.version = '4.0.5'; + packages.get('@deepseek-ai/cordis-plugin-loader')!.version = '1.0.6'; + packages.get('@deepseek-ai/cordis-plugin-include')!.version = '1.0.10'; + packages.get('@deepseek-ai/cordis-plugin-timer')!.version = '1.1.7'; + for (const release of ['0.2.1-rc.3', '0.2.1-rc.4', '0.2.2-alpha.3']) { + for (const value of packages.values()) if (value.name.startsWith('@deepseek-ai/dsh')) value.version = release; + for (const name of clients.keys()) clients.set(name, { name, version: release }); + expect(check).toThrow('local writes are disabled'); + } }); diff --git a/plugins/dsh/test/smoke-native-host.test.ts b/plugins/dsh/test/smoke-native-host.test.ts index 35fcc789d..074294504 100644 --- a/plugins/dsh/test/smoke-native-host.test.ts +++ b/plugins/dsh/test/smoke-native-host.test.ts @@ -168,10 +168,13 @@ test('native smoke accepts only explicit DSH releases and rejects incompatible r const manifest = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8')); expect(manifest.peerDependencies['@deepseek-ai/dsh-fs']).toBe(supportedDshVersions.join(' || ')); for (const version of supportedDshVersions) expect(selectDshVersion(version)).toBe(version); - for (const version of ['latest', 'next', '0.2.1', '0.2.2-rc.1', '0.2.1-rc.2 --ignore-scripts']) { + for (const version of ['latest', 'next', 'alpha', '0.2.1', '0.2.1-rc.3', '0.2.1-rc.4', '0.2.2-rc.1', '0.2.1-rc.2 --ignore-scripts', '0.2.1-rc.5 --ignore-scripts']) { expect(() => selectDshVersion(version)).toThrow('Unsupported DSH release'); } - expect(() => selectDshVersion('0.2.1-rc.2', 'retained')).toThrow('requires the current Cordis family'); + expect(selectDshVersion('0.2.0-rc.2', 'retained')).toBe('0.2.0-rc.2'); + for (const version of ['0.2.1-rc.2', '0.2.1-rc.5']) { + expect(() => selectDshVersion(version, 'retained')).toThrow('requires the current Cordis family'); + } });