dsh-plugin-wsl-env 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +181 -0
  2. package/CONTRIBUTING.md +29 -7
  3. package/README.md +124 -138
  4. package/SECURITY.md +11 -1
  5. package/SUPPORT.md +29 -2
  6. package/cordis.patch.yml +17 -19
  7. package/docs/ARCHITECTURE.md +30 -29
  8. package/docs/CONFIGURATION.md +81 -21
  9. package/docs/LIMITATIONS.md +71 -6
  10. package/docs/MIGRATION.md +98 -0
  11. package/docs/PARITY.md +1 -1
  12. package/docs/PERFORMANCE.md +60 -0
  13. package/docs/QUICKSTART.md +125 -0
  14. package/docs/README.zh.md +117 -137
  15. package/docs/ROADMAP-1.0.md +780 -0
  16. package/docs/ROADMAP.md +4 -0
  17. package/docs/UPSTREAM-FILE-REFERENCE-SEAM.md +80 -0
  18. package/docs/UPSTREAM-LSP-SEAM.md +90 -0
  19. package/docs/UPSTREAM-SESSION-NOTICE.md +95 -0
  20. package/lib/agent-errors.d.ts +19 -0
  21. package/lib/agent-errors.js +22 -0
  22. package/lib/agent-exec.js +2 -2
  23. package/lib/agent.d.ts +68 -5
  24. package/lib/agent.js +129 -22
  25. package/lib/distro-route.d.ts +75 -0
  26. package/lib/distro-route.js +86 -0
  27. package/lib/fs-routing.d.ts +4 -1
  28. package/lib/index.d.ts +166 -37
  29. package/lib/index.js +353 -75
  30. package/lib/paths.d.ts +15 -0
  31. package/lib/paths.js +28 -0
  32. package/lib/preflight.d.ts +46 -3
  33. package/lib/preflight.js +83 -14
  34. package/lib/shell-env.d.ts +9 -3
  35. package/lib/shell-env.js +121 -43
  36. package/lib/subprocess.d.ts +12 -3
  37. package/lib/subprocess.js +21 -3
  38. package/package.json +23 -15
  39. package/scripts/diagnose.sh +9 -88
  40. package/scripts/doctor.mjs +548 -0
  41. package/scripts/profile-preset.mjs +254 -0
  42. package/lib/workspace-files-route.d.ts +0 -136
  43. package/lib/workspace-files-route.js +0 -225
  44. package/lib/workspace-files-wsl.d.ts +0 -257
  45. package/lib/workspace-files-wsl.js +0 -418
@@ -0,0 +1,780 @@
1
+ # Roadmap 1.0: the WSL plugin the DSH ecosystem should have
2
+
3
+ Status: working plan, 2026-10-19. Baseline: `0.12.0`.
4
+ Audience: maintainers and contributors. This is a plan, not a release promise.
5
+ The short version: **from "the only WSL plugin" to "the default WSL plugin"** —
6
+ first run works, every distro and workspace is served by its own environment,
7
+ every DSH surface has a story, and every claim has a test or a probe.
8
+
9
+ ## 1. What "must-have" means
10
+
11
+ "Best" is not "most features". It is five acceptance lines:
12
+
13
+ 1. **Five-minute first run.** On a clean Windows 11 + WSL 2 + Ubuntu machine,
14
+ the first command runs in the right directory, in the right distro, under
15
+ the sandbox, within five minutes (p50; p90 within fifteen).
16
+ 2. **Any distro, any workspace.** Opening a folder in distro A runs in distro A
17
+ even when distro B is the WSL default; two distros can run concurrently
18
+ without crossing streams.
19
+ 3. **Full surface coverage.** Shell, files, search, git, terminal, picker, GUI
20
+ previews, ports and `@` references do not fall back to 9p for a WSL
21
+ workspace. Where an upstream seam is missing, the gap is named and measured.
22
+ 4. **Explainable failure.** Every refusal has a code and a remedy; no silent
23
+ wrong-tree execution and no fail-open.
24
+ 5. **Sustainable.** Every public promise is covered by a unit test or a
25
+ real-machine probe; aarch64 is executed, not only built; documentation has
26
+ no known stale claim.
27
+
28
+ North-star metrics:
29
+
30
+ | Metric | Target |
31
+ |---|---|
32
+ | Tier 1 first-run success | >= 95% |
33
+ | Time to first successful WSL command | p50 <= 5 min, p90 <= 15 min |
34
+ | Real-machine probe suite | 100% on Tier 1, nightly |
35
+ | Known documentation drift | 0 |
36
+
37
+ ## 2. Where 0.12.0 stands
38
+
39
+ > WSL-101 is implemented in the working tree (2026-10-19): the distro follows the
40
+ > workspace and the per-distro caches are in place. The table below records the
41
+ > 0.12.0 baseline that motivated the plan.
42
+
43
+ | Area | Strong today | Gap to "must-have" |
44
+ |---|---|---|
45
+ | Execution | Native agent is the default; systemd session scope; background processes survive | The distro is pinned by configuration. The picker lists every distro, but a workspace in a non-default distro hits the cross-distro guard ([`src/index.ts:838`](../src/index.ts), [`src/paths.ts:227`](../src/paths.ts)) |
46
+ | Files | ext4 substrate, root-plane `fs-routing`, kernel-enforced confined writes | GUI previews take roughly four to six agent round trips ([`LIMITATIONS.md:122`](LIMITATIONS.md)); `@` completion still reads through 9p |
47
+ | Sandbox | `bubblewrap`, fail-closed, honest `enforcement: partial`, `maskWindowsDrive` | systemd and Linger are default prerequisites, but neither `diagnose` nor `bootstrap` checks them |
48
+ | Onboarding | `bootstrap`, `diagnose`, session-start preflight, package-family install commands | No single "check and fix" command; the preflight only probes bwrap; the preset override is a trap |
49
+ | Configuration | Every shipped key is documented | `wsl-shell` and `wsl-fs` are nested in `preset-wsl`; an id patch only logs a warning and changes nothing ([`CONFIGURATION.md:64`](CONFIGURATION.md)) |
50
+ | Quality | 33 unit-test files, 12 real-machine probes, build/type/coverage gates | Probes are manual; aarch64 is build-only ([`ROADMAP.md:45`](ROADMAP.md)); `fs-routing` is outside `test:unit`/`test:coverage` ([`REQUIREMENTS.zh.md:72`](REQUIREMENTS.zh.md)) |
51
+ | Ecosystem | npm package, bundle patch, bilingual locale, substantial docs | The upstream seams are fragile: the `ensureSession` monkey patch, argv-shape search interception, and the missing `FileReferenceTraversal` seam ([`upstream/promotion-map.md`](upstream/promotion-map.md)) |
52
+
53
+ The engineering base is already in the top tier of comparable projects. The
54
+ remaining gap is productization: **multi-distro correctness, first-run
55
+ friction, configuration ergonomics, and ecosystem presence**.
56
+
57
+ ## 3. Design principles
58
+
59
+ 1. **Coordinate routing over per-profile pinning.** A path already names its
60
+ distro. The environment should follow that coordinate; an explicit pin
61
+ remains an override, not the default mental model.
62
+ 2. **Explicit over implicit, but not silent.** A fallback must be reported in
63
+ the result. A failure must name the remedy. Never turn an unsupported case
64
+ into a wrong-tree success.
65
+ 3. **No new service, no listening port.** The resident agent remains a
66
+ transport, not a daemon. Uninstall must stay "remove the cache".
67
+ 4. **Measure before optimizing.** v6 FS concurrency and speculative caches wait
68
+ for a measured consumer. The A6/A7 numbers are the model: fix the measured
69
+ bottleneck, not the imagined one.
70
+ 5. **Upstream-ready, not upstream-dependent.** The plugin must remain fully
71
+ usable if upstream never accepts a seam. Promotion work is packaging and
72
+ proof, not a blocking dependency.
73
+ 6. **Every promise has a test or a probe.** A behavior that cannot be verified
74
+ is either documented as an explicit boundary or not shipped.
75
+
76
+ ## 4. Workstreams
77
+
78
+ | Workstream | Question it answers |
79
+ |---|---|
80
+ | **A. Zero-friction onboarding** | Can a new user get from install to a working command without reading the whole manual? |
81
+ | **B. Coordinate-correct multi-distro** | Does the environment follow the workspace, including multiple distros at once? |
82
+ | **C. Full-surface coverage** | Do all DSH consumers of a WSL workspace stay inside the distro? |
83
+ | **D. Trust, performance, reliability** | When something breaks, is it visible, recoverable and fast enough? |
84
+ | **E. Ecosystem presence** | Is this the obvious, documented, supportable choice in the DSH ecosystem? |
85
+
86
+ ## 5. Phases
87
+
88
+ ### P0 — make "it works" true (0-4 weeks)
89
+
90
+ | ID | Outcome | Acceptance gate | Size |
91
+ |---|---|---|---|
92
+ | P0.1 | `distro: auto` coordinate routing across shell, fs, terminal, shell-env and the session runtime | Default Ubuntu + Debian workspace runs in Debian; two distro sessions concurrent; explicit pin still wins; cross-distro paths still refuse | L |
93
+ | P0.2 | One `doctor`/`setup` path covering bwrap, systemd, Linger, optional tools, peers and config | Clean Win11 + Ubuntu passes in five minutes; missing bwrap, non-systemd, stopped distro and wrong peer each produce a copy-paste remedy | M |
94
+ | P0.3 | Safe `preset-wsl` override helper and validation | Changing `sessionSandbox` or `distro` works in one step; an invalid nested patch fails loudly instead of warning | M |
95
+ | P0.4 | Documentation and gate hygiene | The three known drifts are fixed; `fs-routing` enters the test lists; `probe:fs-routing` exists; links resolve | S |
96
+ | P0.5 | Preflight visible before the first command | A missing bwrap or systemd/Linger is visible in the session UI, not only in `logger.warn` | M |
97
+
98
+ P0 is not a performance phase. It is the difference between "the plugin is
99
+ correct once configured" and "the plugin is correct on first contact".
100
+
101
+ ### P1 — make "every day" complete (1-3 months)
102
+
103
+ | ID | Outcome | Acceptance gate | Size |
104
+ |---|---|---|---|
105
+ | P1.1 | `sessionSandbox: auto` for distros without systemd | Non-systemd distro runs the first command under per-command bwrap and reports the narrower scope; explicit `true` still refuses | M |
106
+ | P1.2 | Compatibility matrix and real validation | Tier 1 (Ubuntu/Debian, systemd, x86_64) green; aarch64 spike runs under qemu; Tier 2 status documented | L |
107
+ | P1.3 | Install/upgrade/uninstall E2E | Temporary `DSH_HOME`, packed tarball, `dsh plugin add`, `--dump-config`, remove; upgrade from the previous minor | M |
108
+ | P1.4 | `@` references seam | Measure the 9p cost; land the upstream `FileReferenceTraversal` seam or a declared, tested fallback | L |
109
+ | P1.5 | LSP provider spike | A documented plan and prototype for `ctx.lsp` over fs + subprocess, conditional on the desktop composition mounting LSP | M |
110
+ | P1.6 | GUI preview performance | Baseline measured; p95 <= 1s/MiB for a representative page; v6 only if the measurement justifies it | M |
111
+ | P1.7 | Agent recovery and status | In-flight death recovers within 2s or fails with a named state; `doctor`/status shows agent and scope health | M |
112
+ | P1.8 | Security posture | Strict preset documented and probed; interop visibility/mask matrix; no claim beyond `partial` | S |
113
+ | P1.9 | Peer upgrade canary | A scheduled check against the latest DSH line reports breakage before users do | S |
114
+
115
+ ### P2 — make it the ecosystem default (3-6 months)
116
+
117
+ | ID | Outcome | Acceptance gate | Size |
118
+ |---|---|---|---|
119
+ | P2.1 | Upstream promotion pack | Package layout, real-composition boot test, RFCs and PR-ready seams; the plugin still works if none land | L |
120
+ | P2.2 | Discoverability | A docs site or one-page entry, compatibility page, demo, recipes; marketplace directory if DSH has one | M |
121
+ | P2.3 | Community and support | Feature-request and setup-report templates, public roadmap, predictable release cadence, triage target | S |
122
+ | P2.4 | 1.0 readiness | Config schema frozen, migration/deprecation policy, Tier 1 green, aarch64 executed, no P0 bugs for 30 days | M |
123
+
124
+ ## 6. Ninety-day sequence
125
+
126
+ | Week | Focus | Output |
127
+ |---|---|---|
128
+ | 1-2 | Baseline and hygiene | Ten clean-machine timings; P0.4; doctor specification |
129
+ | 3-6 | Multi-distro | P0.1 implementation, unit tests, two-distro probe, migration note |
130
+ | 7-8 | First-run loop | P0.2, P0.3, P0.5 |
131
+ | 9-10 | Compatibility and proof | P1.2, P1.3, nightly probe runner |
132
+ | 11-12 | Surface and performance | P1.4/P1.5 spike, P1.6 baseline, P1.7 fault injection |
133
+ | 13+ | Ecosystem and 1.0 | P2.1-P2.4, release gates |
134
+
135
+ The sequence assumes one maintainer-equivalent. P0 is not cuttable; P1 items
136
+ may move if the compatibility matrix exposes a higher-severity failure.
137
+
138
+ ## 7. Metrics
139
+
140
+ | Metric | Current | Target | How measured |
141
+ |---|---|---|---|
142
+ | Time to first command | Not systematically measured | p50 <= 5 min, p90 <= 15 min | Ten clean-machine runs |
143
+ | Tier 1 first-run success | Not measured | >= 95% | Compatibility matrix |
144
+ | Real-machine probes | 14, manual | 100%, nightly | Self-hosted Windows runner |
145
+ | FS single-op p95 | ~22ms host round trip | <= 30ms | `fs-queue.mjs` |
146
+ | 16 concurrent reads p95 | 2.4-8.2ms | <= 20ms | `fs-queue.mjs` |
147
+ | GUI preview p95 | Backend 63.5 ms/MiB (service round trips on top) | <= 1s/MiB | `probe:gui-preview` |
148
+ | Agent recovery | One rebuild | < 2s | Fault injection |
149
+ | Documentation drift | Three known | 0 | Release checklist |
150
+ | Release cadence | Ad hoc | Every 2-4 weeks | Calendar |
151
+ | Adoption (lagging) | Not established | +50% per quarter | npm download stats |
152
+
153
+ No default telemetry. Facts come from `doctor --json`, issue templates and
154
+ probe reports.
155
+
156
+ ## 8. Risks, non-goals and decision rules
157
+
158
+ | Risk | Mitigation |
159
+ |---|---|
160
+ | Upstream never lands a seam | Keep the plugin independent; promotion work is packaging and proof, not a dependency |
161
+ | Multi-distro refactor destabilizes routing | Introduce `distro: auto` behind tests; keep explicit pins; run two-distro probes before release |
162
+ | `sessionSandbox: auto` hides a semantic change | Still use bwrap; report the scope mode in the result; explicit `true` keeps the strict behavior |
163
+ | Real-machine CI is unavailable | Self-hosted Windows nightly; qemu for the aarch64 binary; manual suite before release |
164
+ | Solo maintenance and bus factor | Freeze scope for 1.0; document every seam; keep the issue templates and probes current |
165
+ | Security overclaim | Keep `enforcement: partial`; the strict preset narrows, never closes, the interop hole |
166
+ | GUI regression repeats | Never replace the shipped `workspace-files` row again; only route and optimize |
167
+
168
+ **Non-goals:** WSL 1, macOS/Linux hosts, a bundled daemon or listening port,
169
+ full isolation from Windows interop, uniform support for every distro, an
170
+ editor/LSP rewrite without an upstream seam, and default telemetry.
171
+
172
+ **Decision rules:**
173
+
174
+ - A P1 item moves only with a measured need or a concrete consumer.
175
+ - A compatibility claim enters the matrix only with a test or a probe.
176
+ - A security statement never exceeds what the sandbox actually enforces.
177
+ - A release does not ship without the P0 gates; a native change also needs the
178
+ aarch64 spike.
179
+
180
+ ## 9. Issue-ready breakdown
181
+
182
+ The blocks below were filed as GitHub issues #5-#22 on 2026-10-19. The
183
+ acceptance list in each issue is the contract; filter by priority:
184
+ [P0](https://github.com/Big-Dao/dsh-plugin-wsl-env/issues?q=is%3Aissue+label%3Apriority%3AP0),
185
+ [P1](https://github.com/Big-Dao/dsh-plugin-wsl-env/issues?q=is%3Aissue+label%3Apriority%3AP1),
186
+ [P2](https://github.com/Big-Dao/dsh-plugin-wsl-env/issues?q=is%3Aissue+label%3Apriority%3AP2).
187
+
188
+ ### WSL-101 — Multi-distro coordinate routing
189
+
190
+ **Status:** implemented in the working tree (2026-10-19); unit-covered, with the
191
+ real-machine two-distro probe still to run.
192
+
193
+ **Priority:** P0 · **Size:** L · **Labels:** `area:multi-distro`,
194
+ `type:feature`, `priority:P0`
195
+
196
+ **Why:** The picker lists every distro, but the preset pins one. A workspace in
197
+ another distro is refused instead of served.
198
+
199
+ **Acceptance:**
200
+
201
+ - [ ] `distro: "auto"` resolves the distro from the workspace UNC; an explicit
202
+ name still pins; `"default"` forces the WSL default.
203
+ - [ ] `wsl-shell`, `wsl-fs`, `fs-routing`, `subprocess-wsl` and `shell-env`
204
+ all follow the same coordinate rule.
205
+ - [ ] The shared agent and session runtime are keyed by distro; two distros run
206
+ concurrently.
207
+ - [ ] A workspace in its own distro never raises `FS_OUTSIDE_DISTRO`; another
208
+ distro's path still does.
209
+ - [ ] Migration note documents the `""`/`auto`/`default` semantics.
210
+
211
+ **Tests/probes:** unit tests for the resolver; two-distro real-machine probe;
212
+ cross-distro refusal leg; composition test through the service proxy.
213
+
214
+ **Depends on:** none.
215
+
216
+ ### WSL-102 — Doctor and one-command setup
217
+
218
+ **Status:** implemented (2026-10-19): [`scripts/doctor.mjs`](../scripts/doctor.mjs)
219
+ is the one-command check, exposed as `pnpm run doctor`; `diagnose` is its
220
+ read-only alias (`--read-only` refuses `--fix`/`--install`). The unit suite runs
221
+ the doctor as a subprocess against a fake `wsl.exe`/`cmd.exe` on PATH: a healthy
222
+ machine, missing bubblewrap, no systemd, a stopped distro and stale scopes are
223
+ each asserted end to end. The clean-machine five-minute stopwatch has not been
224
+ run (this machine has one, already-configured distro); it stays on the release
225
+ checklist.
226
+
227
+ **Priority:** P0 · **Size:** M · **Labels:** `area:onboarding`,
228
+ `type:feature`, `priority:P0`
229
+
230
+ **Why:** The default path has two prerequisites (bwrap, systemd/Linger) and the
231
+ current diagnostics do not cover the second.
232
+
233
+ **Acceptance:**
234
+
235
+ - [x] One command reports Windows/WSL versions, distro, shell, bwrap
236
+ usability, systemd PID 1, `systemd-run`, Linger, rg/git/inotifywait,
237
+ peer version, agent cache and stale scopes.
238
+ - [x] Human-readable output and `--json`; each failure includes one
239
+ copy-paste remedy.
240
+ - [x] `--fix` performs only safe actions; package installation requires
241
+ explicit confirmation and never runs silently.
242
+ - [x] Missing bwrap, non-systemd, stopped distro and wrong peer are each
243
+ classified (fake-`wsl.exe` tests).
244
+ - [ ] Clean Win11 + Ubuntu first run is <= 5 minutes (release checklist; not
245
+ run on this single configured machine).
246
+
247
+ **Tests/probes:** process-level tests against fake `wsl.exe` outputs
248
+ ([`test/doctor.test.mjs`](../test/doctor.test.mjs)); real-machine run on the
249
+ Tier 1 image; `diagnose` remains a read-only alias.
250
+
251
+ **Depends on:** none.
252
+
253
+ ### WSL-103 — Preset override guardrail
254
+
255
+ **Priority:** P0 · **Size:** M · **Labels:** `area:configuration`,
256
+ `type:feature`, `priority:P0`
257
+
258
+ **Status:** implemented (2026-10-19): [`scripts/profile-preset.mjs`](../scripts/profile-preset.mjs)
259
+ (`pnpm run preset`) prints the complete `preset-wsl` row, applies `--distro`,
260
+ `--session-sandbox`, `--sandbox`, `--mask-windows-drive` and `--agent-backend`,
261
+ and writes a profile layer with `--out`. `--check` fails on a top-level
262
+ `- id: wsl-shell`/`wsl-fs` entry and prints the exact block to paste. The unit
263
+ suite compares the printed block byte-for-byte with the shipped row the app
264
+ loads and exercises every CLI path.
265
+
266
+ **Why:** `wsl-shell` and `wsl-fs` are nested inside `preset-wsl`; an id patch
267
+ logs a warning and does nothing, which is the worst kind of configuration
268
+ failure.
269
+
270
+ **Acceptance:**
271
+
272
+ - [x] A helper prints the complete, copy-paste `preset-wsl` block for the
273
+ installed version.
274
+ - [x] The helper can apply a small set of common edits (`distro`,
275
+ `sessionSandbox`, `sandbox`, `maskWindowsDrive`, `agentBackend`).
276
+ - [x] An invalid nested patch fails with a message that includes the exact
277
+ block to paste; no silent no-op.
278
+ - [x] The helper is covered by a unit test and documented in CONFIGURATION.
279
+
280
+ **Tests/probes:** generated YAML compared byte-for-byte against the shipped
281
+ block (the row the real composition loads); the CLI's print/write/check paths
282
+ run in [`test/profile-preset.test.mjs`](../test/profile-preset.test.mjs).
283
+
284
+ **Depends on:** none.
285
+
286
+ ### WSL-104 — Documentation and gate hygiene
287
+
288
+ **Status:** implemented (2026-10-19): the three drifts are fixed, `fs-routing`
289
+ and `distro-route` are in both test lists, `scripts/check-links.mjs` gates
290
+ relative links from `pnpm test`, and `probe:fs-routing` is written and wired
291
+ into the real-machine suite. The routing probe has not been executed yet (it
292
+ needs the app closed); its first run stays on the release checklist.
293
+
294
+ **Priority:** P0 · **Size:** S · **Labels:** `area:docs`, `area:quality`,
295
+ `priority:P0`
296
+
297
+ **Why:** Three user-visible claims are stale, and `fs-routing` is outside the
298
+ test lists despite the repository's own rule.
299
+
300
+ **Acceptance:**
301
+
302
+ - [x] `VALUE.zh` no longer says watch is polling-only; `PARITY` no longer says
303
+ "a poll, not inotify"; the `cordis.patch.yml` substrate comment no
304
+ longer calls `share` an opt-out.
305
+ - [x] `test/fs-routing.test.mjs` is in `test:unit` and `test:coverage`.
306
+ - [x] A `probe:fs-routing` exists and is wired into the real-machine suite
307
+ (first execution pending: the suite needs the app closed).
308
+ - [x] Markdown links resolve (`scripts/check-links.mjs`, in `pnpm test`);
309
+ version references in the roadmap/docs are checked at release time.
310
+
311
+ **Tests/probes:** `pnpm test`, `pnpm run test:coverage`, `probe:fs-routing`.
312
+
313
+ **Depends on:** none.
314
+
315
+ ### WSL-105 — Visible preflight and systemd/Linger coverage
316
+
317
+ **Priority:** P0 · **Size:** M · **Labels:** `area:onboarding`,
318
+ `area:sandbox`, `priority:P0`
319
+
320
+ **Status:** implemented (2026-10-19): `wsl-preflight` runs the shared
321
+ session-runtime probe after the bwrap probe and warns with the existing
322
+ remedy for `no-systemd`, `no-user-manager`, `no-systemd-run` and `no-linger`
323
+ (`sessionSandbox: false` on the row skips the session half). The advisory is
324
+ logged **and** published as a session-scoped `wsl-preflight` runtime-context
325
+ entry, so it is visible in the session transcript rather than only in the app
326
+ log. DSH 0.2.1 has no toast/banner seam; the exact ask is written up (not yet
327
+ filed — no issue channel here) in
328
+ [UPSTREAM-SESSION-NOTICE.md](UPSTREAM-SESSION-NOTICE.md), and
329
+ [LIMITATIONS.md](LIMITATIONS.md#session-start-advisories) records the boundary.
330
+ Unit coverage drives every probe verdict through a fake `wsl.exe`; the
331
+ real-machine missing-bwrap and non-systemd legs are still manual.
332
+
333
+ **Why:** The session-start preflight currently only probes bwrap and writes to
334
+ `logger.warn`; the other default prerequisite is discovered at the first
335
+ command.
336
+
337
+ **Acceptance:**
338
+
339
+ - [x] The preflight checks bwrap, systemd PID 1, `systemd-run` and Linger.
340
+ - [x] The warning is visibly attached to the session through the
341
+ runtime-context entry (not only logs).
342
+ - [ ] A GUI notice/banner renders it: upstream seam missing; the filing payload
343
+ is ready at [proposal-session-notice.md](upstream/proposal-session-notice.md)
344
+ and has not been submitted (a maintainer action).
345
+ - [x] The warning never installs anything and never changes fail-closed
346
+ behavior.
347
+ - [x] A session with a usable environment produces no warning.
348
+
349
+ **Tests/probes:** preflight unit tests with fake probe output (every verdict);
350
+ real-machine missing-bwrap and non-systemd sessions.
351
+
352
+ **Depends on:** WSL-102 for the shared probe logic; UI seam may be upstream.
353
+
354
+ ### WSL-201 — `sessionSandbox: auto`
355
+
356
+ **Priority:** P1 · **Size:** M · **Labels:** `area:sandbox`,
357
+ `type:feature`, `priority:P1`
358
+
359
+ **Status:** implemented (2026-10-19): `sessionSandbox` accepts `true`, `false`
360
+ or `"auto"` on both `wsl-shell` and `wsl-fs`. `auto` probes the session runtime
361
+ and, when the platform cannot host it, falls back to the per-command `bwrap`
362
+ profile — still confined — and the command result reports
363
+ `sessionScope: "session" | "per-command"`. A failed `auto` verdict is re-probed
364
+ at most once a minute, so following the remedy upgrades without a restart.
365
+ `true` still refuses with the preflight remedy; `false` is unchanged. Unit
366
+ tests cover auto-usable, auto-fallback (shell and fs) and the `true` refusal;
367
+ the non-systemd real-machine leg has not been run (this machine has systemd and
368
+ only one distro).
369
+
370
+ **Why:** systemd is a hard default prerequisite. A distro without it should
371
+ still get a safe, sandboxed first command instead of a refusal.
372
+
373
+ **Acceptance:**
374
+
375
+ - [x] `sessionSandbox: auto` uses the scope when systemd and Linger are
376
+ available, otherwise falls back to per-command bwrap and reports the
377
+ narrower scope.
378
+ - [x] `true` still refuses with the existing remedy; `false` is unchanged.
379
+ - [x] The result exposes whether the scope was active (`sessionScope`);
380
+ CONFIGURATION explains the `/tmp` and background-process difference.
381
+ - [x] The fallback never runs unconfined.
382
+
383
+ **Tests/probes:** `test/provider.test.mjs` (auto-usable, auto-fallback for the
384
+ shell and the filesystem, the `true` refusal); a non-systemd distro real-machine
385
+ probe remains unrun (no non-systemd distro here).
386
+
387
+ **Depends on:** WSL-102, WSL-105.
388
+
389
+ ### WSL-202 — Compatibility matrix and aarch64
390
+
391
+ **Priority:** P1 · **Size:** L · **Labels:** `area:compatibility`,
392
+ `area:quality`, `priority:P1`
393
+
394
+ **Why:** The support matrix is prose, and the aarch64 binary is built but never
395
+ executed.
396
+
397
+ **Acceptance:**
398
+
399
+ - [ ] Tier 1 and Tier 2 are defined and tested: Ubuntu LTS, Debian stable,
400
+ Fedora, Arch, Alpine (musl), openSUSE; systemd and non-systemd;
401
+ x86_64 and aarch64; Windows 10/11; current WSL.
402
+ - [ ] The native 18-check suite runs for aarch64 under qemu in CI.
403
+ - [ ] The shell agent runs in Linux containers for the non-Ubuntu families.
404
+ - [ ] The support table states "supported", "partial" and "unsupported" with
405
+ evidence.
406
+
407
+ **Tests/probes:** container matrix, qemu spike, self-hosted nightly.
408
+
409
+ **Depends on:** WSL-201 for non-systemd; WSL-203 for packaging.
410
+
411
+ ### WSL-203 — Install/upgrade/uninstall E2E
412
+
413
+ **Priority:** P1 · **Size:** M · **Labels:** `area:release`,
414
+ `area:quality`, `priority:P1`
415
+
416
+ **Status:** implemented (2026-10-19): [`test/e2e/install.sh`](../test/e2e/install.sh)
417
+ (`pnpm run e2e:install`) packs the tree, creates a fresh `DSH_HOME` under
418
+ `%TEMP%`, initializes a profile from the shipped `web` template, installs the
419
+ tarball, asserts `--dump-config` composes the plugin rows with no skipped-patch
420
+ warnings, removes it and asserts every row is gone. With network it also
421
+ installs the previous minor (0.11.0), plants a marker in the profile layer,
422
+ upgrades to the packed version, and asserts the marker survives, the installed
423
+ version is current, and the installed CHANGELOG carries the migration note.
424
+ The run passed 10/10 on this machine (the app may stay running: plugin
425
+ management is local). [`test/packaging.test.mjs`](../test/packaging.test.mjs)
426
+ guards the packed file list in `pnpm test`. A self-hosted Windows runner job is
427
+ still unconfigured; the local command is the documented path.
428
+
429
+ **Why:** The install path is manual and has no automated guard; an upgrade can
430
+ silently leave an obsolete workaround behind.
431
+
432
+ **Acceptance:**
433
+
434
+ - [x] A script creates a temporary `DSH_HOME`, installs a packed tarball,
435
+ checks `--dump-config`, removes the plugin and asserts cleanup.
436
+ - [x] Upgrading from the previous minor preserves the profile and prints the
437
+ migration note (asserted against the installed CHANGELOG).
438
+ - [x] The E2E runs via a documented local command (`pnpm run e2e:install`);
439
+ the self-hosted runner job remains to be wired.
440
+
441
+ **Tests/probes:** bash E2E (`test/e2e/install.sh`), packaging test
442
+ (`test/packaging.test.mjs`), release checklist.
443
+
444
+ **Depends on:** none.
445
+
446
+ ### WSL-204 — `@` references seam
447
+
448
+ **Priority:** P1 · **Size:** L · **Labels:** `area:file-reference`,
449
+ `type:feature`, `priority:P1`
450
+
451
+ **Status:** measured and deferred (2026-10-19):
452
+ [UPSTREAM-FILE-REFERENCE-SEAM.md](UPSTREAM-FILE-REFERENCE-SEAM.md) records the
453
+ cost (20 dirs / 520 files: 9p walk median 84.7 ms vs ext4 1.5 ms, ~56×; 9p
454
+ samples 63–192 ms) measured by `pnpm run probe:file-reference`. The seam
455
+ contract (three functions, Dirent/error/coordinate semantics) is already
456
+ written in [rfc-wsl-workspaces.md](upstream/rfc-wsl-workspaces.md); the
457
+ consumer [`src/file-reference-wsl.ts`](../src/file-reference-wsl.ts) is built
458
+ and unit-tested but deliberately unwired. The fallback is to keep the 9p walk
459
+ until upstream exposes the funnel; the trigger and the follow-up probe are
460
+ recorded.
461
+
462
+ **Why:** `@` completion still walks the 9p share; the WSL traversal module is
463
+ built but unwired.
464
+
465
+ **Acceptance:**
466
+
467
+ - [x] The 9p cost is measured on a representative tree (84.7 ms vs 1.5 ms).
468
+ - [x] The upstream `FileReferenceTraversal` seam is proposed with the three
469
+ function contract and the coordinate/error semantics.
470
+ - [x] The seam did not land, so the limitation is stated with the measured cost
471
+ and a fallback decision (keep the 9p walk; wire on the trigger).
472
+
473
+ **Tests/probes:** `probe:file-reference` (9p-vs-ext4 walk);
474
+ `test/file-reference-wsl.test.mjs` (the consumer); no live completion probe
475
+ until the seam exists.
476
+
477
+ **Depends on:** upstream seam; no hard block for 1.0 if documented.
478
+
479
+ ### WSL-205 — LSP provider spike
480
+
481
+ **Priority:** P1 · **Size:** M · **Labels:** `area:lsp`, `type:research`,
482
+ `priority:P1`
483
+
484
+ **Status:** researched and deferred (2026-10-19):
485
+ [UPSTREAM-LSP-SEAM.md](UPSTREAM-LSP-SEAM.md) records that `dsh-lsp`,
486
+ `dsh-lsp-stdio` and `dsh-tool-lsp` exist in the harness but are **not** mounted
487
+ in the `0.2.1-rc.2` base composition (verified against the app's bundled
488
+ `dsh-base/cordis.patch.yml`). The stdio provider injects `fs` + `subprocess` and
489
+ assumes one execution world; a WSL session's `ctx.fs` is the distro's while
490
+ `ctx.subprocess` is the host's, so the gap is a preset-scoped subprocess
491
+ provider plus URI/path translation. The prototype is deferred with an explicit
492
+ trigger (the composition mounts the seam, or upstream adds the provider seam).
493
+
494
+ **Why:** The desktop composition does not mount LSP today, but the WSL family
495
+ would be the natural place to provide it.
496
+
497
+ **Acceptance:**
498
+
499
+ - [x] Document whether the current DSH line mounts `ctx.lsp` and what a
500
+ WSL-backed provider would need (paired fs + subprocess).
501
+ - [x] A prototype proves the shape or is explicitly deferred with a trigger.
502
+ - [x] No user-facing claim until the composition actually mounts it.
503
+
504
+ **Tests/probes:** composition inspection (documented in UPSTREAM-LSP-SEAM.md);
505
+ no prototype while the trigger is unmet.
506
+
507
+ **Depends on:** upstream composition; not a 1.0 blocker.
508
+
509
+ ### WSL-206 — GUI preview performance
510
+
511
+ **Priority:** P1 · **Size:** M · **Labels:** `area:performance`,
512
+ `area:gui`, `priority:P1`
513
+
514
+ **Status:** implemented (2026-10-19): [`test/probe/gui-preview.sh`](../test/probe/gui-preview.sh)
515
+ (`pnpm run probe:gui-preview`) measures the three routed backend operations a
516
+ preview pays — `stat`, `listDir`, and a 1 MiB `readByteRange` — with p50/p95 and
517
+ budgets (stat 100 ms, listing 500 ms, page 1 s/MiB). Measured on this machine
518
+ (20 iterations): stat p95 2.7 ms, listDir p95 5.1 ms, 1 MiB page read p95
519
+ 63.5 ms/MiB. The service's own round trips are not part of this number; they
520
+ stack on top, and the earlier "four to six" remains the qualitative shape. No
521
+ optimization is justified; the shipped `workspace-files` row is untouched, so
522
+ there is no speculative v6.
523
+
524
+ **Why:** The 0.12.0 correction restored previews at the cost of four to six
525
+ agent round trips. Correctness first, but the latency needs a measured budget.
526
+
527
+ **Acceptance:**
528
+
529
+ - [x] A probe measures listing, stat and page-read latency for a representative
530
+ tree and a 1 MiB file.
531
+ - [x] p95 <= 1s/MiB for the page read (63.5 ms/MiB); listings within the agreed
532
+ budget (5.1 ms).
533
+ - [x] Any optimization is justified by the probe; no speculative v6.
534
+ - [x] The shipped `workspace-files` row is not replaced again.
535
+
536
+ **Tests/probes:** `test/probe/gui-preview.sh` (new); `fs-queue.mjs` for the
537
+ agent queue; the service-level round-trip count remains qualitative.
538
+
539
+ **Depends on:** WSL-101 for per-distro routing.
540
+
541
+ ### WSL-207 — Agent recovery and status
542
+
543
+ **Priority:** P1 · **Size:** M · **Labels:** `area:reliability`,
544
+ `type:feature`, `priority:P1`
545
+
546
+ **Status:** implemented (2026-10-19): a resident that spends its one rebuild
547
+ enters a bounded revival (2/4/8 s backoff, one fresh transport and one rebuild
548
+ per attempt); after three deaths the refusal is permanent and names the restart
549
+ remedy. `WslAgent.status()` exposes state, pid, lastError, deaths and pending.
550
+ `pnpm run doctor` now probes a fresh shell resident (`agent.handshake`: pid,
551
+ protocol, script digest) and still reports the agent cache and live/stale
552
+ `dsh-wsl-*.scope` counts. The live app resident's last error has no IPC channel
553
+ to the doctor, so the doctor reports its own probe failure and the app log
554
+ keeps the live last error — documented in LIMITATIONS.
555
+
556
+ **Why:** An in-flight agent death can take the filesystem substrate out until
557
+ the app restarts; users experience "everything suddenly fails".
558
+
559
+ **Acceptance:**
560
+
561
+ - [x] A failed agent is re-established on the next call with bounded backoff,
562
+ or fails with a named state and a restart remedy.
563
+ - [x] A status/doctor view reports the agent, scope and last error (doctor:
564
+ fresh-agent handshake + scopes + probe error; `status()`: live snapshot;
565
+ the live resident's last error stays in the app log).
566
+ - [x] Fault injection covers in-flight EXEC, FS read and FS write.
567
+ - [x] Recovery does not replay mutation frames.
568
+
569
+ **Tests/probes:** `test/agent.test.mjs` (revival, budget exhaustion, status,
570
+ EXEC/read/write fault injection); `test/doctor.test.mjs` (handshake, digest
571
+ mismatch); a real-machine kill/restart probe is still unrun.
572
+
573
+ **Depends on:** WSL-102 for status output.
574
+
575
+ ### WSL-208 — Security strict profile and interop matrix
576
+
577
+ **Priority:** P1 · **Size:** S · **Labels:** `area:security`,
578
+ `area:docs`, `priority:P1`
579
+
580
+ **Status:** implemented except the distro-disabled leg (2026-10-19):
581
+ [CONFIGURATION](../docs/CONFIGURATION.md#the-strict-profile) documents the strict
582
+ profile, its trade-offs and the visible/masked/disabled matrix;
583
+ `pnpm run probe:sandbox` now also runs the masked profile (14 expectations:
584
+ `/mnt` hidden, the Windows binary unreachable, the workspace still writable). The
585
+ visible leg's INFO still records the interop escape, and the `partial`
586
+ enforcement fact is untouched. The disabled leg (`[interop] enabled=false` +
587
+ `wsl --shutdown`) needs a distro-level change and is documented but not probed
588
+ automatically.
589
+
590
+ **Why:** The sandbox is honest about `partial`, but users need a clear "strict"
591
+ configuration and evidence for each interop state.
592
+
593
+ **Acceptance:**
594
+
595
+ - [x] A documented strict preset (`maskWindowsDrive: true`, `sandbox: true`)
596
+ with its trade-offs.
597
+ - [x] A probe matrix covers interop visible and masked on a real machine; the
598
+ disabled leg is documented and marked manual (needs a distro-level change).
599
+ - [x] No document claims full isolation; the `partial` fact is unchanged.
600
+ - [x] The strict preset is covered by a sandbox probe (`probe:sandbox`).
601
+
602
+ **Tests/probes:** `probe:sandbox` (visible + masked); the disabled leg is a
603
+ manual distro-level check recorded in CONFIGURATION.
604
+
605
+ **Depends on:** none.
606
+
607
+ ### WSL-209 — Peer upgrade canary
608
+
609
+ **Priority:** P1 · **Size:** S · **Labels:** `area:release`,
610
+ `area:compatibility`, `priority:P1`
611
+
612
+ **Status:** implemented (2026-10-19): `.github/workflows/peer-canary.yml` runs
613
+ weekly and on demand; `scripts/peer-canary.mjs` resolves the `next` dist-tag for
614
+ every pinned peer, compares it with the pins, and (on a moved line) installs the
615
+ tagged peers into a scratch copy and runs `pnpm test` plus the composition boot
616
+ test. The summary table goes to `$GITHUB_STEP_SUMMARY` and a failed upgrade fails
617
+ the run with the delta. `SUPPORT.md` points the supported-version row at that
618
+ summary. Local `--check` on this machine resolved the line with no delta; the
619
+ workflow itself has not run in this environment (no Actions runner here), and
620
+ opening an issue on a red run was left out because the workflow has no issue
621
+ permission — the failing run plus its delta is the signal.
622
+
623
+ **Why:** The peer pins are exact; a DSH release can break the plugin before a
624
+ user reports it.
625
+
626
+ **Acceptance:**
627
+
628
+ - [x] A scheduled workflow checks the latest `@deepseek-ai/dsh-*` line,
629
+ installs it in a scratch tree and runs `pnpm test` plus the composition
630
+ boot test.
631
+ - [x] A failure fails the scheduled run with the exact version delta (step
632
+ summary + failing step; no auto-filed issue).
633
+ - [x] The supported-version table is updated from the result (`SUPPORT.md`
634
+ points at the canary summary).
635
+
636
+ **Tests/probes:** `.github/workflows/peer-canary.yml`;
637
+ `test/peer-canary.test.mjs`; composition test.
638
+
639
+ **Depends on:** none.
640
+
641
+ ### WSL-301 — Upstream promotion pack
642
+
643
+ **Priority:** P2 · **Size:** L · **Labels:** `area:upstream`,
644
+ `type:research`, `priority:P2`
645
+
646
+ **Why:** The plugin is standalone, but upstream seams would remove the most
647
+ fragile parts.
648
+
649
+ **Status:** packaging plan written (2026-10-19):
650
+ [docs/upstream/promotion-pack.md](upstream/promotion-pack.md) records the
651
+ `packages/wsl/*` layout (ten capability packages plus a thin integration
652
+ package, each with its existing tests), the seam/RFC index with the current
653
+ state of each seam, what `test/composition.test.mjs` boots today and the gaps a
654
+ real-composition boot test would close, and the install proof
655
+ (`e2e:install`, CI on both platforms). The LSP pair is explicitly deferred
656
+ (`UPSTREAM-LSP-SEAM.md`), so the "RFCs/prototypes for all four seams" item is
657
+ satisfied for three and documented as deferred for LSP.
658
+
659
+ **Acceptance:**
660
+
661
+ - [x] A `packages/wsl`-shaped layout is documented, with the package-level
662
+ test split and real-composition boot test (the current one; gaps listed).
663
+ - [x] RFCs/prototypes exist for the session environment resolver
664
+ (promotion-map), the `FileReferenceTraversal` seam (RFC addendum ① plus the
665
+ built consumer), the search provider (promotion-map), and the LSP pair
666
+ (deferred note with trigger).
667
+ - [x] The plugin remains installable and green with none of them accepted
668
+ (`e2e:install` 10/10; CI green on both platforms).
669
+
670
+ **Tests/probes:** `test/composition.test.mjs`; `test/e2e/install.sh`;
671
+ `docs/upstream/promotion-pack.md`.
672
+
673
+ **Depends on:** WSL-101, WSL-204, WSL-205.
674
+
675
+ ### WSL-302 — Discoverability and docs entry
676
+
677
+ **Priority:** P2 · **Size:** M · **Labels:** `area:docs`,
678
+ `area:community`, `priority:P2`
679
+
680
+ **Status:** implemented except the visual demo (2026-10-19):
681
+ [docs/QUICKSTART.md](QUICKSTART.md) is the one-page entry (prerequisites,
682
+ install, first session, recipes for Node/Python/Rust/Go and a monorepo, an
683
+ honest Tier 1/Tier 2 table); [docs/PERFORMANCE.md](PERFORMANCE.md) is the
684
+ 9p-vs-agent page with the A6/A7 numbers and the GUI-preview baseline, and
685
+ `pnpm run probe:gui-preview` / `fs-queue.mjs` reproduce the latter. No GIF or
686
+ screenshot exists yet (capturing the GUI is not something this repository has
687
+ recorded), so that acceptance item is explicitly open. No DSH plugin
688
+ marketplace was identified, so nothing was submitted.
689
+
690
+ **Why:** "Best" also means easy to find and evaluate.
691
+
692
+ **Acceptance:**
693
+
694
+ - [x] A one-page entry with a five-minute quickstart, compatibility table and
695
+ screenshots (text only; screenshots are the open item).
696
+ - [ ] A demo or GIF showing the picker, terminal, preview and sandbox fact
697
+ (not produced; recorded here and in QUICKSTART).
698
+ - [x] Recipes for Node, Python, Rust/Go and a monorepo; a 9p-vs-plugin
699
+ benchmark page.
700
+ - [ ] The DSH plugin directory/marketplace is updated if one exists (none
701
+ identified).
702
+
703
+ **Tests/probes:** `pnpm run lint:links`; the quickstart's commands are the
704
+ documented Tier 1 path, not executed by CI.
705
+
706
+ **Depends on:** WSL-102, WSL-202.
707
+
708
+ ### WSL-303 — Community and support loop
709
+
710
+ **Priority:** P2 · **Size:** S · **Labels:** `area:community`,
711
+ `priority:P2`
712
+
713
+ **Status:** implemented (2026-10-19): `.github/ISSUE_TEMPLATE/` now carries a
714
+ feature-request form, a setup-report form that **requires** the `doctor --json`
715
+ output, and a chooser config (blank issues disabled; private security and
716
+ quickstart links). `SUPPORT.md` documents the public roadmap, the
717
+ three-business-day P1 triage target and the two-to-four-week release cadence;
718
+ the `good first issue` label already exists, and `CONTRIBUTING.md` lists the
719
+ usual shapes and how to claim one.
720
+
721
+ **Acceptance:**
722
+
723
+ - [x] Feature-request and setup-report issue templates; setup reports ask for
724
+ `doctor --json`.
725
+ - [x] Public roadmap and a predictable release cadence (ROADMAP-1.0 + SUPPORT).
726
+ - [x] Triage target for P1 issues (three days, best effort).
727
+ - [x] A "good first issue" label and a contributor path for probes/docs.
728
+
729
+ **Tests/probes:** `pnpm run lint:links`; a process check in the release review.
730
+
731
+ **Depends on:** WSL-102.
732
+
733
+ ### WSL-304 — 1.0 readiness
734
+
735
+ **Priority:** P2 · **Size:** M · **Labels:** `area:release`,
736
+ `priority:P2`
737
+
738
+ **Status:** policy subset implemented (2026-10-19): [MIGRATION.md](MIGRATION.md)
739
+ records the configuration freeze (new keys are minors with preserving defaults,
740
+ removals need a boot-time refusal, meaning changes need a migration note; exact
741
+ peer pins are tested by the canary), the deprecation path with the
742
+ `substrate: "share"` and `distro: ''` cases, and the release-gate table with the
743
+ manual legs marked unrun. RELEASING and SUPPORT link it. The empirical legs
744
+ (Tier 1 matrix, aarch64 execution, 30 days without a P0) and the final `1.0.0`
745
+ release notes remain open.
746
+
747
+ **Acceptance:**
748
+
749
+ - [x] Configuration schema frozen; new keys require a minor.
750
+ - [x] Migration and deprecation policy documented.
751
+ - [ ] Tier 1 green, aarch64 executed, install E2E green, no P0 bugs for 30
752
+ days (install E2E is 10/10; the other three are open/unrun).
753
+ - [ ] `1.0.0` release notes list the supported matrix and the known
754
+ limitations (draft waits for the matrix).
755
+
756
+ **Tests/probes:** [MIGRATION.md](MIGRATION.md#gates-a-release-must-clear);
757
+ release gate; full probe suite.
758
+
759
+ **Depends on:** WSL-101, WSL-102, WSL-202, WSL-203.
760
+
761
+ ## 10. Release gates
762
+
763
+ A release is ready when:
764
+
765
+ 1. `pnpm test` and `pnpm run test:coverage` pass.
766
+ 2. The real-machine suite passes on the Tier 1 image.
767
+ 3. A native-agent change also passes the aarch64 spike.
768
+ 4. The supported-version table and the changelog are updated.
769
+ 5. No known documentation drift and no open P0 issue.
770
+
771
+ A 1.0 release additionally requires the WSL-304 checklist.
772
+
773
+ ## Evidence and related documents
774
+
775
+ - Current architecture: [ARCHITECTURE.md](ARCHITECTURE.md)
776
+ - Known boundaries: [LIMITATIONS.md](LIMITATIONS.md)
777
+ - Current technical roadmap: [ROADMAP.md](ROADMAP.md)
778
+ - Requirements traceability: [REQUIREMENTS.zh.md](REQUIREMENTS.zh.md)
779
+ - Upstream mapping: [upstream/promotion-map.md](upstream/promotion-map.md)
780
+ - Real-machine suite: [`test/probe/run-all-when-closed.sh`](../test/probe/run-all-when-closed.sh)