# DSH 0.2.1-rc.2 — fresh source compatibility validation > 历史报告说明:本报告记录 `0.11.0` 发布前、包版本仍为 `0.10.1` 时的源码兼容验证。`0.11.0` 现已发布到 npm;下文保留验证当时的版本与结果,不代表对最终发布包重新完成了测试或全流程验收。 > > Historical report: this records source compatibility validation before `0.11.0` was released, while the package version was still `0.10.1`. Version `0.11.0` is now published on npm. The versions and results below are preserved as recorded, not as a new test run or end-to-end acceptance of the final published package. ## Result / 结论 **PASS for the checks below, not full runtime/browser acceptance.** Plugin package version remains **0.10.1**, with the 0.2.1 adaptation under **Unreleased**. No version migration was repeated, no new release was published, and no formal DSH Profile or user data was changed. Fresh isolated installation: Node **24.21.0**, npm **11.19.0**, macOS (`darwin arm64`). Typecheck, build, all **43 test files (607 passed / 1 skipped / 0 failed)**, sidebar source/hash gate and publish gate passed. Focused lifecycle: **24 passed**. Machine-readable results: [0.2.1-rc.2-validation.json](0.2.1-rc.2-validation.json). 这次仅验证当前源码与 npm 上精确 DSH 包组合的兼容性,不是浏览器、AI、桌面或 Windows 全流程验收。旧组合的历史成功记录不被撤销,也不被转用为本组合的验收证据。 ## Installation and exact validation commands All source/config copies came from this project, not from an existing dependency tree. The new `.compat-validation/project` explicitly included NOTICE, LICENSE, both READMEs, CHANGELOG, the mount patch, package manifest, TypeScript/tsdown/Vitest configuration, and `src/tests/scripts/docs`. Neither source `node_modules` nor pnpm links were read/copied/followed. No `legacy-peer-deps`, guard bypass, lock update, dependency script execution, or external write was used. Only public npm dependency reads were needed. The project and npm cache were removed after evidence was recorded; no generated `lib`, isolated npm lock, logs or installed dependencies were copied back. Executed from the project root, then within the isolated project: ```sh mkdir -p .compat-validation/project .compat-validation/npm-cache cp -R NOTICE LICENSE README.md README.en.md CHANGELOG.md cordis.patch.yml package.json tsconfig.json tsdown.config.ts vitest.config.ts src tests scripts docs .compat-validation/project/ cd .compat-validation/project npm install --ignore-scripts --bin-links=false --install-strategy=hoisted --no-audit --no-fund --cache ../npm-cache # First: reproduce before changing production lifecycle. node node_modules/vitest/vitest.mjs run tests/workspace-provider-lifecycle.test.ts # After copying the lifecycle fix and focused tests into the isolated copy: node node_modules/vitest/vitest.mjs run tests/workspace-provider-lifecycle.test.ts # Equivalent to the project's pnpm scripts, with direct Node entry points: node node_modules/typescript/bin/tsc --noEmit node scripts/clean-lib.mjs && node node_modules/typescript/bin/tsc && node node_modules/tsdown/dist/run.mjs node node_modules/vitest/vitest.mjs run node scripts/check-workspace-sidebar-version.mjs node scripts/check-publish.mjs ``` | Check | Exit | Observed result | | --- | ---: | --- | | Isolated npm install | 0 | 275 packages; only deprecation/update notices; no peer bypass | | Lifecycle before fix | 1 | 22 tests: 19 reported passed, 2 graph failures, plus PrettyFormat suite error obscuring the final test | | Production fix, before correcting the retained-fiber assertion | 1 | Original graph failures resolved; 21 reported passed, remaining PrettyFormat suite error | | Final focused lifecycle | 0 | 1 file, 24 passed | | Typecheck | 0 | No diagnostics | | Build | 0 | Clean TypeScript output and tsdown browser closure generated | | Full tests | 0 | 43 files, 607 passed, 1 skipped (608 total), no failed files/tests | | Sidebar version gate | 0 | Workspace 0.2.1-rc.2 + alpha.2 + next rc.2 SHA-256 gates and locale 0.2.1-rc.2 | | Publish gate | 0 | 13 Host singletons, 12 export targets, 23 strict Remote methods, patch/NOTICE and browser ModuleLoader/lazy-picker checks | The sole skip is the existing Windows-only long-path test at `tests/git-unborn.test.ts:253`, not a newly skipped lifecycle assertion. The publish script was run unchanged with NOTICE/LICENSE present; its Git-cleanliness release gate is intentionally deferred without `--require-clean`. This managed shadow has no Git checkout metadata: release cleanliness, pack/install into a DSH Profile, and publishing are not claimed. ### Actual installed versions | Package/tool | Version | | --- | --- | | `@deepseek-ai/cordis` | 4.0.5 | | `@deepseek-ai/cordis-plugin-loader` | 1.0.6 | | `@deepseek-ai/cordis-plugin-include` | 1.0.10 | | `@deepseek-ai/schemastery` | 3.18.5 | | `@deepseek-ai/dsh-agent` | 0.2.1-rc.2 | | `@deepseek-ai/dsh-client-ui-workspace` | 0.2.1-rc.2 | | `@deepseek-ai/dsh-client-locale` | 0.2.1-rc.2 | | TypeScript / Vitest / tsdown | 5.9.3 / 2.1.9 / 0.17.2 | Current DSH peers are exact `0.2.1-rc.2`; Cordis/loader peers are `~4.0.5` / `~1.0.6`. The table records actual resolutions, not a claim that every permitted peer patch was tested. The existing pnpm lock and workspace configuration were not modified this round. ## Reproduced failures and verified root causes The first focused run reproduced both handoff failures with real Cordis Loader and Web registry: 1. Live replacement disable left `graph=[]` instead of `[official]`, even after the existing `vi.waitFor` timeout. 2. Failed replacement activation left `[official, worktree]` instead of `[official]`, even after explicitly awaiting the activation fiber. 3. The apparent teardown `PrettyFormatPluginError` was the final slow-disable test trying to format a retained Cordis fiber during `expect(official.fiber).toBeUndefined()`. Loader 1.0.6 retains this reference; Cordis Context rejects pretty-format's uninjected `$$typeof` lookup. After fixing production, both graph tests passed while this error remained. Replacing the stale object-shape assumption with the real public disposal condition (`fiber.uid === null`) made the remaining test pass. No errors were swallowed and no teardown cleanup was disabled. Inspected **only the newly installed** package implementations and declarations: - `@deepseek-ai/cordis-plugin-loader@1.0.6/lib/index.js:361-391`: `refresh()` immediately returns whenever `entry.fiber` exists; disabled `update()` schedules `fiber.dispose()` without awaiting it; enabled `update()` can initialize a new generation when the old uid is cleared. `init()` is public (`lib/types/config/entry.d.ts:56`), and `fiber?: Fiber` is a public writable field (`:25`). - Loader `lib/index.js:429-465`: `init()` schedules activation; the fiber owns its settled outcome. Loader `await()` tracks import/lifecycle work (`:150-160`). - `@deepseek-ai/cordis@4.0.5/lib/index.js:1351-1403`: failed apply unloads effects without clearing the fiber uid; `_unload()` uses `Promise.all` over effects, so registration order is not sequential LIFO teardown. The declaration at `lib/types/fiber.d.ts:67-74` confirms `UNLOADING=5`; no new numeric state was guessed. - `@deepseek-ai/dsh-client-modules@0.2.1-rc.2/lib/index.js:545-557,851-860`: the registry dirties names on `internal/plugin` and includes enabled entries whenever `entry.fiber` exists, without checking uid/FAILED state. There is no public rescan method for arbitrary lifecycle changes. ### Production changes this round At `src/workspace-provider-lifecycle.ts:66-101`, the existing bounded/in-flight operation now waits for the captured disabled fiber's lifecycle, rather than just the scheduling promise. Restore uses public `entry.init()` when `uid === null`; otherwise it retains `refresh()`. No raw disabled flags are changed. At `src/workspace-provider-lifecycle.ts:124-153`, cleanup explicitly awaits the idempotent marker disposer before checking the official expression. It withdraws only the **identical owning replacement entry/fiber reference**, then emits the owning `internal/plugin` event consumed by the registry. Disposed fibers can withdraw immediately. A still-live fiber is withdrawn only when its eventual `await()` rejects, not on successful same-fiber restart. That wait is deliberately not awaited inside its own disposer (which would self-deadlock). Root/Include unload guards and the per-entry in-flight guard prevent resurrection or overlapping Loader mutation. This narrow writable-Entry/registry-notification compatibility boundary was explicitly escalated and approved by the parent before implementation. No Host dependency source, Web registry implementation, user Profile or persistence behavior was patched. Future upstream changes to this boundary require renewed review. ### Focused regression coverage `tests/workspace-provider-lifecycle.test.ts:166-234` now covers five consecutive disable/re-enable changes, actual new fiber identities, no official-options mutation and no tree write, a successful same-fiber `restart()`, concurrent disable/re-enable while the old generation cleans up, and failed activation restoring the sole official graph while leaving replacement configuration enabled and clearing its stale reference/marker. Existing tests retain root disposal, both boot orders, disabled owning groups, market replay, delayed imports/Includes, explicit user overrides, timeout and slow-operation cases (`:234-290`). No test was skipped, arbitrary wait increased, or graph assertion weakened to hide a production failure. The slow-disable fixture's final assertion (`:286-289`) now checks the public disposed uid, not absence of the old Loader reference; its final **exactly `[worktree]`** graph assertion remains. The new concurrency fixture was initially drafted with direct `fiber.dispose()` on an enabled entry and failed because Loader intentionally persists a disable for that user action (23 passed / 1 failed); a targeted diagnostic confirmed the new uid/marker survived but the entry was disabled. The corrected fixture uses actual disabled/enabled `entry.update()` calls, exercising the production path without overriding persistence or changing production code. Final focused and complete checks were then rerun, all passing. The pre-existing never-settling fixture deliberately expects both rows while the fake official operation refuses to settle: it proves startup is not held hostage, not successful election. Timeout is not cancellation. Normal settled disable/failure paths now assert exactly one provider; a permanently wedged upstream operation remains a residual limitation rather than an unconditional no-overlap guarantee. ## Read-only review handoff: retained upgrade and exact source anchors These are current-source review anchors for the already-accepted upgrade plus this round's fix; they are not a claim that all those files were modified again this round. | Area | Exact review entry points | Actual behavior/change | | --- | --- | --- | | Manifest/baseline | `package.json:48-57,89-146`; `pnpm-lock.yaml` | Existing 0.2.1 DSH peer/dev upgrade, Cordis/loader/include pins, official remote-access Client dependency; plugin version still 0.10.1. Lock retained unchanged this round. | | Browser source provenance | `scripts/workspace-sidebar-upstream.mjs:5-25,53-61`; `scripts/check-workspace-sidebar-version.mjs`; `src/host-compat.ts:6-12`; `tests/host-compat.test.ts:11-20` | New exact 0.2.1 Browser SHA `52765aacb6c7f96327da5d8709d8cbb30dd2957d4744a42da04c18e07fe264e5`, mandatory managed Fork seam updated to preserve upstream options/allowMigration; old alpha/next aliases retained, modern not reused for 0.2.0. | | Lazy picker / no boot cycle | `src/client/console-remote/index.ts:23-29`; `src/client/workspace-sidebar/index.tsx:179-184,246-260`; `tests/client-bundle.test.ts:84-90,184-254`; `scripts/check-publish.mjs:214-225` | Entry does not wait for `remote.directoryPicker`; managed-context facade resolves it on calls after `uiWorkspace` exists. No regression to eager injection. | | Managed actions | `src/client/workspace-sidebar/index.tsx:17,148-150`; `scripts/workspace-sidebar-upstream.mjs:58-61`; `tests/client-workspace-sidebar-actions.test.ts:134-135` | Verified read-only `session-id` joins safe official actions; managed Fork remains guarded, ordinary Fork retains options. | | Remote access | `tests/console-remote.test.ts:311,332-355`; `tests/support/official-client.ts:10` | Official owner Peer and actual browser-condition exports exercised; non-owner calls, including unrelated grants, are denied before dispatch. No new sharing policy. | | Preset/draft/attachments | `src/client/pre-session/controller.ts:99-103,126-161`; `src/client/pre-session/harness-input.ts:3-17`; `tests/client-pre-session.test.tsx:142-182,528-562` | Recorded agentPreset forwarded; absent preset left to Host default; retained target reference, draft and attachment handoff preserved. Official Browser factory remains embedded rather than replacing its draft initialization. | | Lifecycle fix | `src/workspace-provider-lifecycle.ts:66-101,124-153`; `tests/workspace-provider-lifecycle.test.ts:166-234,286-289` | The production restoration/withdrawal changes and regressions described above. | | Documentation | `README.md:74-80`; `README.en.md:74-80`; `CHANGELOG.md:5-12`; `docs/COMPATIBILITY.md`; `src/client/workspace-sidebar/UPSTREAM.md` | Current baseline/peer/source-gate statements corrected; old combinations remain historical evidence, not current claims. | The already-accepted singleton, picker, managed Fork, non-owner denial, preset and draft/attachment behavior was retained; the full suite includes their real artifact/official-callback regressions (client bundle 5, Remote 17, pre-session 40, managed actions 3). ## Remaining acceptance / 未验证范围 - No real `0.2.1-rc.2` browser startup/render, menu/keyboard, provider switching or hover-button acceptance. - No API-key-enabled AI conversation or complete review/preview/delivery/rollback/recovery/cleanup run in a real Host. - No DSH Profile install/startup, desktop or Windows end-to-end run. This source-only check is not equivalent to previous 0.2.0 Chromium evidence. - No release Git-cleanliness assertion, commit, push, publish, deploy or migration. No credentials/session contents were read. - Unknown Host versions still retain the official provider; old browser aliases do not broaden current exact DSH peers. A future Loader/registry API change, or a permanently wedged official operation, needs separate review.