diff --git a/README.md b/README.md index a9a3fbf..3618a04 100644 --- a/README.md +++ b/README.md @@ -428,7 +428,7 @@ These exist in the schema but render no control — each is a literal, a marker, **Verified on DeepSeek Harness `0.2.1` — both the `alpha` and `rc` channels (Node 24+)** -The harness publishes two **channels**, `alpha` and `rc`, and they are parallel tracks rather than a version ordering: semver sorts `0.2.1-rc.2` _above_ `0.2.1-alpha.5`. The plugin targets two lines on both channels: **`0.2.1`**, the `rc` channel's latest, and the **`0.2.2`** prereleases the `alpha` channel now carries (the test install runs `0.2.2-alpha.2`). The `@deepseek-ai/dsh-typert-protocol` peer range says so exactly: +The harness publishes two **channels**, `alpha` and `rc`, and they are parallel tracks rather than a version ordering: semver sorts `0.2.1-rc.2` _above_ `0.2.1-alpha.5`. The plugin targets two lines on both channels: **`0.2.1`**, whose `rc` builds `dsh@latest` still carries, and the **`0.2.2`** prereleases on both channels: `alpha` is at `0.2.2-alpha.3`, and `rc` opened the line with `0.2.2-rc.1` (published under the `next` dist-tag; the test install runs `0.2.2-alpha.2`). The `@deepseek-ai/dsh-typert-protocol` peer range says so exactly: ``` >=0.2.1-alpha.1 <0.3.0 || >=0.2.2-0 <0.3.0 @@ -436,9 +436,16 @@ The harness publishes two **channels**, `alpha` and `rc`, and they are parallel The first clause covers the `0.2.1` line on both channels: its lower bound (`alpha.1`) sorts below every `rc` of the same line, so rc needs no separate entry. The second is **not** redundant — semver excludes prereleases unless a comparator in the same `major.minor.patch` tuple carries one, so `<0.3.0` alone would silently drop every `0.2.2-*` build, alpha and rc alike. Both clauses stop at `0.3.0`, which leaves the next line a separate decision rather than a silent widening. +The optional `@deepseek-ai/cordis` peer follows the typert range. Each line moved the host core: the `0.2.1` builds peer `~4.0.5` (or a `4.0.5` prerelease), the `0.2.2` alphas a `4.0.6` prerelease, and `0.2.2-rc.1` `~4.1.0`. A fresh install takes the cordis the **newest** admitted typert build peers on, so a cordis peer that misses it fails with `ERESOLVE`; one that misses an older line's cordis fails beside a host already installed on that line. The peer therefore admits every one of them and stops at the next tuple: + +``` +~4.0.4 || >=4.0.5-0 <4.1.0 || >=4.0.6-0 <4.1.0 || ~4.1.0 +``` + | Surface | Target | | :-- | :-- | | **Harness line** | **`0.2.1`** and the **`0.2.2`** prereleases, `alpha` and `rc` channels alike (peer: the range above) | +| **Host core** | `@deepseek-ai/cordis` `4.0.4` up to `4.1.x`, including the `4.0.5` and `4.0.6` prereleases (optional peer: the range above) | | **Plugin package** | [`dsh-opencode-patch`](https://www.npmjs.com/package/dsh-opencode-patch) | | **Host profile** | DSH Web profile (`patchReload: live`) | | **Routes claimed** | `opencode`, `opencode-go`, `opencode-responses`, `opencode-anthropic` | diff --git a/README.zh-CN.md b/README.zh-CN.md index 3075eac..c2ed8dd 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -422,7 +422,7 @@ Zen 路线上不出现任何 Go 的数字:根本不画圆环(没有可量的 **已在 DeepSeek Harness `0.2.1` 上验证 —— `alpha` 与 `rc` 两个通道(Node 24+)** -Harness 按 `alpha` 与 `rc` 两个**通道**发布,它们是平行的发布线而非版本先后:semver 中 `0.2.1-rc.2` 排序在 `0.2.1-alpha.5` **之上**。本插件在两个通道上都针对两条线:**`0.2.1`**(`rc` 通道的最新版本),以及 `alpha` 通道目前发布的 **`0.2.2`** 预发布版本(测试安装运行的是 `0.2.2-alpha.2`)。`@deepseek-ai/dsh-typert-protocol` 的 peer 范围正是这么写的: +Harness 按 `alpha` 与 `rc` 两个**通道**发布,它们是平行的发布线而非版本先后:semver 中 `0.2.1-rc.2` 排序在 `0.2.1-alpha.5` **之上**。本插件在两个通道上都针对两条线:**`0.2.1`**(`dsh@latest` 仍停留在它的 `rc` 构建上),以及两个通道上的 **`0.2.2`** 预发布版本:`alpha` 已到 `0.2.2-alpha.3`,`rc` 以 `0.2.2-rc.1` 开启了这条线(发布在 `next` dist-tag 下;测试安装运行的是 `0.2.2-alpha.2`)。`@deepseek-ai/dsh-typert-protocol` 的 peer 范围正是这么写的: ``` >=0.2.1-alpha.1 <0.3.0 || >=0.2.2-0 <0.3.0 @@ -430,9 +430,16 @@ Harness 按 `alpha` 与 `rc` 两个**通道**发布,它们是平行的发布 第一个子句覆盖两个通道上的 `0.2.1` 整条线:下界 `alpha.1` 排序低于同线的所有 `rc`,因此无需单独枚举 rc。第二个子句**并不多余**——除非同一 `major.minor.patch` 的某个比较符带有预发布号,semver 会把预发布版本排除在外,所以只写 `<0.3.0` 会悄悄丢掉所有 `0.2.2-*` 构建,alpha 与 rc 都一样。两个子句都止于 `0.3.0`,下一条线因此是一个单独的决定,而不是悄悄放宽。 +可选的 `@deepseek-ai/cordis` peer 跟随 typert 的范围。每条线都推进了宿主内核:`0.2.1` 的构建依赖 `~4.0.5`(或 `4.0.5` 的预发布版本),`0.2.2` 的 alpha 依赖 `4.0.6` 的预发布版本,`0.2.2-rc.1` 依赖 `~4.1.0`。全新安装会采用**最新**被接纳的 typert 构建所依赖的那个 cordis,所以 cordis peer 漏掉它就会以 `ERESOLVE` 失败;漏掉较早某条线的 cordis,则会在已安装该线宿主的环境旁安装失败。因此这个 peer 接纳其中每一个,并止于下一个版本元组: + +``` +~4.0.4 || >=4.0.5-0 <4.1.0 || >=4.0.6-0 <4.1.0 || ~4.1.0 +``` + | 层面 | 目标 | | :-- | :-- | | **Harness 版本线** | **`0.2.1`** 与 **`0.2.2`** 预发布版本,`alpha` 与 `rc` 通道均可(peer:见上方范围) | +| **宿主内核** | `@deepseek-ai/cordis` 从 `4.0.4` 到 `4.1.x`,包括 `4.0.5` 与 `4.0.6` 的预发布版本(可选 peer:见上方范围) | | **插件包** | [`dsh-opencode-patch`](https://www.npmjs.com/package/dsh-opencode-patch) | | **宿主 profile** | DSH Web profile(`patchReload: live`) | | **声明的路由** | `opencode`、`opencode-go`、`opencode-responses`、`opencode-anthropic` | diff --git a/package.json b/package.json index 82b45fd..21e767f 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "yaml": "2.9.1" }, "peerDependencies": { - "@deepseek-ai/cordis": "~4.0.4 || >=4.0.5-0 <4.1.0 || >=4.0.6-0 <4.1.0", + "@deepseek-ai/cordis": "~4.0.4 || >=4.0.5-0 <4.1.0 || >=4.0.6-0 <4.1.0 || ~4.1.0", "@deepseek-ai/dsh-typert-protocol": ">=0.2.1-alpha.1 <0.3.0 || >=0.2.2-0 <0.3.0" }, "peerDependenciesMeta": { diff --git a/test/harness-channel.test.ts b/test/harness-channel.test.ts index 7898328..4eef8ca 100644 --- a/test/harness-channel.test.ts +++ b/test/harness-channel.test.ts @@ -105,6 +105,7 @@ describe("harness channel alignment", () => { "0.2.1", // 0.2.2 line, both channels, and its stable release. "0.2.2-alpha.2", + "0.2.2-alpha.3", "0.2.2-rc.1", "0.2.2", ]; @@ -125,4 +126,111 @@ describe("harness channel alignment", () => { ).toBe(false); } }); + + it("the cordis peer admits every cordis an admitted typert build can install", () => { + // npm resolves the typert peer to the NEWEST build the range admits (the + // `latest` dist-tag, 0.1.0-rc.6, is outside it), then installs the newest + // PUBLISHED cordis that build peers on. If the plugin's own cordis peer + // excludes it, a fresh install fails with ERESOLVE — which is what + // happened the morning 0.2.2-rc.1 was published with `~4.1.0` while this + // peer stopped below 4.1.0. Beside a host already installed on an older + // line, the cordis is whichever published one that line's build admits. + // So every published cordis an admitted build admits must satisfy the + // cordis peer. + // + // Checked against concrete PUBLISHED versions, never by range overlap: + // `semver.intersects` would call `~4.0.4 || ~4.1.0` compatible with + // `~4.0.6-alpha.2` although no 4.0.6 prerelease satisfies it, because the + // prerelease-tuple rule npm resolves under is not part of an overlap. + // + // Both tables are recorded from the registry on 2026-11-04 (`npm view + // @deepseek-ai/dsh-typert-protocol@ peerDependencies` for every build + // on the 0.2.1 and 0.2.2 lines; `npm view @deepseek-ai/cordis versions`). + // A new typert build joins the first with its cordis range, and a new + // cordis release joins the second, when they are published. + const CORDIS_PUBLISHED = [ + "4.0.4", + "4.0.5-alpha.1", + "4.0.5-alpha.2", + "4.0.5", + "4.0.6-alpha.1", + "4.0.6-alpha.2", + "4.1.0", + ]; + const CORDIS_PEER_OF_TYPERT: Record = { + "0.2.1-alpha.1": "~4.0.5-alpha.1", + "0.2.1-alpha.2": "~4.0.5-alpha.1", + "0.2.1-alpha.3": "~4.0.5-alpha.2", + "0.2.1-alpha.4": "~4.0.5-alpha.2", + "0.2.1-alpha.5": "~4.0.5-alpha.2", + "0.2.1-rc.1": "~4.0.5", + "0.2.1-rc.2": "~4.0.5", + "0.2.1-rc.3": "~4.0.5", + "0.2.1-rc.4": "~4.0.5", + "0.2.1-rc.5": "~4.0.5", + "0.2.2-alpha.2": "~4.0.6-alpha.1", + "0.2.2-alpha.3": "~4.0.6-alpha.2", + "0.2.2-rc.1": "~4.1.0", + }; + const peers = manifest().peerDependencies ?? {}; + const typertPeer = peers["@deepseek-ai/dsh-typert-protocol"] ?? ""; + const cordisPeer = peers["@deepseek-ai/cordis"] ?? ""; + expect(cordisPeer).not.toBe(""); + + // Each `typert@build -> cordis@version` pair npm could install that the + // given cordis peer would refuse. + const refused = (peer: string): string[] => + Object.entries(CORDIS_PEER_OF_TYPERT).flatMap(([build, range]) => + CORDIS_PUBLISHED.filter( + (version) => + semver.satisfies(version, range) && !semver.satisfies(version, peer) + ).map((version) => `typert@${build} -> cordis@${version}`) + ); + + for (const [build, range] of Object.entries(CORDIS_PEER_OF_TYPERT)) { + // The table is about builds the typert peer admits, each of which has a + // published cordis to install; otherwise a row would check nothing. + expect( + semver.satisfies(build, typertPeer), + `typert ${build} is admitted by ${typertPeer}` + ).toBe(true); + expect( + semver.maxSatisfying(CORDIS_PUBLISHED, range), + `typert ${build} peers cordis ${range}, which has a published release` + ).not.toBeNull(); + } + expect(refused(cordisPeer)).toEqual([]); + + // Known answers, so the check above is shown to bite: the peer this + // repository shipped before 0.2.2-rc.1 refuses that build's cordis, and a + // peer without the 4.0.6 clause refuses the alpha line's prereleases + // (semver admits a prerelease only through a comparator in its own tuple). + expect(refused("~4.0.4 || >=4.0.5-0 <4.1.0 || >=4.0.6-0 <4.1.0")).toEqual([ + "typert@0.2.2-rc.1 -> cordis@4.1.0", + ]); + expect(refused("~4.0.4 || >=4.0.5-0 <4.1.0 || ~4.1.0")).toEqual( + expect.arrayContaining(["typert@0.2.2-alpha.3 -> cordis@4.0.6-alpha.2"]) + ); + + // The concrete cordis each channel ships is admitted, and the range still + // stops at the next tuple: a 4.1.x prerelease or 4.2 is a reviewed + // widening, not a silent one. + for (const version of [ + "4.0.5", + "4.0.6-alpha.1", + "4.0.6-alpha.2", + "4.1.0", + ]) { + expect( + semver.satisfies(version, cordisPeer), + `cordis ${version} is admitted` + ).toBe(true); + } + for (const version of ["4.1.1-alpha.1", "4.2.0", "5.0.0"]) { + expect( + semver.satisfies(version, cordisPeer), + `cordis ${version} is outside the peer` + ).toBe(false); + } + }); });