# Compatibility audit — DSH 0.1.7-rc.1 > **Method: the tests are the audit.** The probes in `tests/config.spec.ts`, > `tests/integration.spec.ts`, `tests/package-layout.spec.ts` and > `scripts/verify-host.mjs` drive this plugin's real execution paths against the > **real** `@deepseek-ai/*` packages — not against hand-written doubles that > agree with whatever the plugin happens to call. `scripts/build.mjs` carries the > artifact-side half of the same discipline. > > Targeted version: `@deepseek-ai/*@0.1.7-rc.1`, declared as the single peer tuple > `^0.1.7-rc.1` with `dsh.engines.dsh = ">=0.1.7-rc.1"`. > Source references are to the upstream monorepo > [github.com/deepseek-ai/deepseek-harness](https://github.com/deepseek-ai/deepseek-harness) > at tag `dsh-v0.1.7-rc.1`, or to the published packages under > `@deepseek-ai/dsh@0.1.7-rc.1/node_modules`. ## 1. Version alignment: one line at a time `peerDependencies` declare **one** `^X.Y.Z-pre` tuple per harness package, and `dsh.engines.dsh` repeats its floor. A second `||` term would re-declare a DSH line this plugin does not track. Two different semver rule sets are in play, and confusing them is easy: | Rule set | `^0.1.5-rc.1` vs `0.1.7-rc.1` | | --- | --- | | npm's default prerelease matching | **false** — a prerelease candidate only matches when the range has a comparator on the *same* `[major, minor, patch]` tuple, so a new tuple needs a new `||` term | | DSH's startup admission gate (`semver.satisfies(rt, range, { includePrerelease: true })`) | **true** — one caret term on any `0.1.x-pre` already covers the whole `0.1` line | So the previous multi-tuple union (`^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-rc.1 || ^0.1.5-rc.1`) was **admitted** on 0.1.7 and then failed at the API level — and no peer spelling within `0.1.x` could have stopped it, because the gate cannot distinguish "verified on 0.1.5" from "verified on 0.1.7". Narrowing to one tuple is therefore a statement of what was actually verified (and it does make the gate refuse *older* lines, which is the truthful outcome), not a way to make the gate catch this class of break. `node scripts/check-dsh-version.mjs` guards the declaration itself: identical tuple sequence across every harness peer, engine floor in sync with the oldest floor, and a report when the registry publishes above the declared window. ## 2. What actually broke (and why nobody saw it) Three independent failures were in play. **Any one of them was sufficient to make the plugin do nothing**, and all three were silent. ### 2.1 The host half mounted and then threw `src/index.ts` called `scope.settings.register(...)` inside its `ctx.inject(['settings','commands','fs'], …)` callback. DSH 0.1.7 removed `SettingsForms.register` entirely. cordis catches an inject callback's throw, logs it, and disposes the child fiber — so `apply()` itself did not throw. Reproduced by mounting the built artifact against the real `SettingsForms.prototype`: ``` real SettingsForms.prototype: constructor, importLegacyDocument, configure, invalidate, writable, documentPath, prepareDocument, describe, update, replace, mutate, write, schema has .register? undefined apply() threw synchronously: no errors surfaced through ctx.logger: 1 - TypeError: scope.settings.register is not a function tools/pre-execute baseline reached: true; decision={"kind":"allow"} ← no listener ``` The commands and the `tools/pre-execute` listener were registered *after* the throw, so **neither ever mounted**: every edit ran unapproved, with one log line as the only trace. ### 2.2 The client half never ran `inject` named `settingsScope`, which exists in **0 of the 277 packages** of 0.1.7. cordis leaves a fiber with an unsatisfied inject inactive — no log, no throw: ``` apply ran with an unsatisfiable inject present: false apply ran when every inject is satisfiable: true ``` So the browser half contributed nothing at all: no diff rendering, no collapse button, no focus restore, no settings rows, not even the injected `