dsh-vision-router 2.3.0 → 3.0.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 (103) hide show
  1. package/README.md +12 -3
  2. package/README.zh.md +12 -3
  3. package/docs/architecture/3.0-architecture-plan.md +252 -0
  4. package/docs/architecture/3.0-r0-contract-ledger.md +137 -0
  5. package/docs/architecture/3.0-r1-source-artifact-boundary.md +98 -0
  6. package/docs/architecture/3.0-r10-final-correctness-closure.md +207 -0
  7. package/docs/architecture/3.0-r11-adversarial-review.md +441 -0
  8. package/docs/architecture/3.0-r5-browser-go-no-go.md +165 -0
  9. package/docs/architecture/3.0-r5-browser-typescript-gate.md +105 -0
  10. package/docs/architecture/3.0-r6-compat-retirement.md +203 -0
  11. package/docs/architecture/3.0-r7-public-api.md +151 -0
  12. package/docs/architecture/3.0-r8-ci-release.md +248 -0
  13. package/docs/architecture/3.0-r9-final-audit.md +370 -0
  14. package/docs/architecture/dsh-support-window.md +5 -3
  15. package/docs/releases/v3.0.0.md +85 -0
  16. package/entry.js +1 -1
  17. package/index.js +108 -71
  18. package/lib/agent-request-route-authority.js +1 -20
  19. package/lib/core-apply-lifecycle.js +37 -43
  20. package/lib/core-cache.js +87 -0
  21. package/lib/core-primitives.js +3 -143
  22. package/lib/core-vision-surface-domain.js +63 -0
  23. package/lib/core-vision-surface.js +11 -77
  24. package/lib/delegated-call-config.js +15 -24
  25. package/lib/doctor-cli.js +0 -0
  26. package/lib/doctor-runtime.js +4 -1
  27. package/lib/dsh-contract-compat.js +26 -230
  28. package/lib/dsh-host-capabilities.js +3 -9
  29. package/lib/dsh-settings-017-compat.js +17 -10
  30. package/lib/file-logger.js +8 -3
  31. package/lib/grounding-coordinate-runtime.js +1 -2
  32. package/lib/http-body-limit.js +97 -79
  33. package/lib/image-offload-compat.js +30 -21
  34. package/lib/image-resource-governor.js +210 -228
  35. package/lib/legacy-global-proxy-boundary.js +46 -3
  36. package/lib/live-model-client-prelude.js +12 -4
  37. package/lib/live-model-discovery.js +134 -43
  38. package/lib/local-vision-stabilizer.js +31 -17
  39. package/lib/maintenance-routes.js +1 -3
  40. package/lib/native-image-coexistence.js +15 -3
  41. package/lib/ollama-cold-start.js +87 -23
  42. package/lib/pi-ai-bridge-wire-compat.js +3 -2
  43. package/lib/proxy-dispatcher-pool.js +151 -92
  44. package/lib/proxy-routing.js +75 -51
  45. package/lib/proxy-url-compat.js +3 -10
  46. package/lib/public-config.js +23 -44
  47. package/lib/public-entry.d.ts +148 -0
  48. package/lib/public-entry.js +17 -4
  49. package/lib/replay-delegation.js +12 -7
  50. package/lib/replay-envelope-v2-compat.js +12 -7
  51. package/lib/runtime-composition.js +22 -4
  52. package/lib/screenshot-source-boundary.js +2 -2
  53. package/lib/session-affinity-runtime.js +113 -97
  54. package/lib/session-affinity.js +89 -76
  55. package/lib/session-event-history-compat.js +30 -0
  56. package/lib/session-query-readers.js +263 -0
  57. package/lib/session-surface-compat.js +30 -31
  58. package/lib/session-surface-policy-domain.js +85 -0
  59. package/lib/session-surface-policy.js +2 -111
  60. package/lib/session-turn-resolver.js +2 -6
  61. package/lib/session-vision-attachment-index.js +143 -0
  62. package/lib/session-vision-delegation-boundary.js +34 -0
  63. package/lib/session-vision-delegation.js +22 -0
  64. package/lib/session-vision-index.js +25 -107
  65. package/lib/session-vision-mode-authority.js +229 -192
  66. package/lib/session-vision-mode-boundary.js +65 -22
  67. package/lib/session-vision-policy-domain.js +304 -0
  68. package/lib/session-vision-policy.js +104 -0
  69. package/lib/session-vision-state.js +368 -378
  70. package/lib/settings-client-017-compat.js +9 -7
  71. package/lib/structured-flow-hardening.js +22 -4
  72. package/lib/tesseract-exec-compat.js +9 -1
  73. package/lib/turn-budget-context.js +47 -41
  74. package/lib/update-check.js +11 -2
  75. package/lib/vision-attachment-handle-runtime.js +176 -156
  76. package/lib/vision-backend-runtime-policy.js +16 -0
  77. package/lib/vision-background-benchmark.js +41 -4
  78. package/lib/vision-background-failure-policy.js +41 -41
  79. package/lib/vision-background-stop-store-core.js +217 -0
  80. package/lib/vision-background-stop-store.js +8 -179
  81. package/lib/vision-capability-benchmark.js +11 -61
  82. package/lib/vision-capability-evidence.js +35 -0
  83. package/lib/vision-capability-fingerprint.js +72 -0
  84. package/lib/vision-capability-identity.js +116 -61
  85. package/lib/vision-capability-router.js +13 -33
  86. package/lib/vision-chunk-assembler.js +95 -0
  87. package/lib/vision-diagnostics-routes.js +34 -4
  88. package/lib/vision-execution-order-apply.js +45 -55
  89. package/lib/vision-execution-order.js +19 -19
  90. package/lib/vision-execution-policy.js +12 -7
  91. package/lib/vision-model-registry.js +30 -17
  92. package/lib/vision-provider-transport.js +137 -147
  93. package/lib/vision-routing-authority.js +47 -48
  94. package/lib/vision-routing-product.js +26 -29
  95. package/lib/vision-routing-settings-prelude.js +8 -3
  96. package/lib/vision-runtime-performance-store.js +166 -0
  97. package/lib/vision-runtime-performance.js +13 -142
  98. package/lib/vision-tool-runtime-boundary.js +99 -30
  99. package/lib/web/dsh-settings-017-root-transport.js +27 -12
  100. package/lib/web/remote-settings-client.js +4 -7
  101. package/lib/web-capability-boundary.js +17 -7
  102. package/lib/wrapper-directory.js +14 -23
  103. package/package.json +37 -11
@@ -0,0 +1,441 @@
1
+ # R11 — Adversarial final audit (3.0)
2
+
3
+ Status: in progress. This document is the working record for R11. It is **not** a
4
+ release declaration: no version bump, tag, publish or GitHub Release is part of
5
+ R11, and nothing here claims 3.0 is release-ready.
6
+
7
+ Baseline for R11: `05e7483` (`docs(3.0): mark R10 integrated`), branch
8
+ `r11-adversarial-final-audit`.
9
+
10
+ ## 1. Why every review round keeps finding more defects
11
+
12
+ The defects found across R0–R11 are not randomly distributed. They cluster into a
13
+ small number of structural classes, and each class lives at a boundary that
14
+ ordinary behaviour tests do not exercise:
15
+
16
+ | Class | Shape of the defect | R0–R11 examples |
17
+ | --- | --- | --- |
18
+ | A. Dynamic Host boundary read as a typed value | A `ctx.*` seam is typed/generic, but the Host may hand a partial, null, throwing or replaced value at runtime | `tools.register(null)`, `attachments` access, credential/attachment runtime guards |
19
+ | B. Async lifecycle ownership | A resource is created before its disposer is registered, or the disposer does not await retirement | `storageDomain.open()` before `ctx.effect(close)`, `void dispose()`, async teardown with no observer |
20
+ | C. HMR generation leak | Generation-scoped state (WeakMap caches, timers, listeners, in-flight work) outlives its generation and is reused by the next one | stale wrapper WeakMaps, background benchmark ticks, Ollama warmup, file logger sink |
21
+ | D. Unordered async completion | State that is written by several async paths has no ordering or admission rule | Session Vision policy `set A` / `set B`, hydrate vs set vs delete |
22
+ | E. Optional capability materialised as a dependency | An optional service is read through the dependency-enforcing property path | `ctx.attachments`, `ctx.sessionQuery`, `ctx.sessionProjections`, `ctx.systemPrompt` |
23
+ | F. Global/process state without an owner | A process-wide seam is read or wrapped per call instead of with an explicit owner and restoration | `globalThis.fetch` capture (R10), dispatcher pool, `Symbol.for` registries, `process.env` |
24
+ | G. Public contract vs runtime drift | `.d.ts` / docs / the packed sample describe something the runtime does not do | `apply(ctx, config, runtime?)`, `installHostSettingsCompatibility`, `sessionSurfaceReplacementIntent` |
25
+ | H. Build/client/release topology drift | The artifact/release state machine does not match what the code assumes | generated-artifact triggers, tag-before-pack, provider transport typing |
26
+
27
+ Two second-order causes make this recursive:
28
+
29
+ 1. **A fix can create the next defect.** The R10 provider-transport bug was
30
+ introduced by the TypeScript migration itself: `globalThis.fetch.bind(globalThis)`
31
+ became `(input, init) => globalThis.fetch(input, init)`, which is semantically
32
+ different. R11 found the mirror image: wrapping a Host contract call inside a
33
+ Cordis `effect()` factory changes *when* it runs, and a Host that does not
34
+ eagerly execute the factory never subscribes to live Settings at all.
35
+ 2. **"Verified" was defined as "tests are green".** Coverage went up while the
36
+ discovery process stayed manual, so each review pass inspected a different
37
+ dimension and the total never converged. The stopping rule has to be
38
+ structural, not "we ran out of ideas".
39
+
40
+ ## 2. Stopping rule for R11
41
+
42
+ R11 is complete when all of the following hold, not when the suite is green:
43
+
44
+ 1. Every effect/registration/disposer/timer/listener/global-mutation site in
45
+ `src/**` has been read and classified against the checklist in §3.
46
+ 2. Every dynamic Host boundary has an adversarial test that feeds
47
+ `null`/`undefined`/primitive/throwing/partial/late/replaced values.
48
+ 3. Every async teardown returns an awaitable retirement and its failure path is
49
+ either propagated or explicitly documented.
50
+ 4. The release state machine can reach every state from every partial state, or
51
+ refuses deterministically with a clear operator instruction.
52
+ 5. The mechanical invariants in §4 are enforced in CI, so a fixed regression
53
+ cannot silently return.
54
+ 6. The real DSH Desktop matrix in §6 has been executed by hand and recorded here.
55
+
56
+ ### 2.0 Current status per condition (2026-10-02, final landing)`
57
+
58
+ | # | condition | state |
59
+ | --- | --- | --- |
60
+ | 1 | every site classified | **met** — §3 sweep counted and classified 56 effect, 22 inject, 22 host `ctx.on`, 3 `globalThis.fetch` installs, 1 `child_process` patch, 35 timers, 58 listeners, 28 mutable caches, 42 disposers, 0 literal `ctx[name]` reads; yielded findings 18–19 |
61
+ | 2 | every dynamic Host boundary adversarially tested | **met** — boundary × hostile-shape audit added two `launchEnvironment` cases; remaining thin boundaries are guarded probes with negative cases in neighbouring suites |
62
+ | 3 | async teardowns awaitable, failure paths surfaced | **met** — all 11 async disposers return awaitables; swallowing disposers are deliberate and documented; finding 17 made the Ollama warmup failure observable |
63
+ | 4 | release state machine total or deterministically refusing | **met offline** — 11 CLI-level state tests plus the draft→attach→verify→publish ordering gate (negative control verified); an actual GitHub Actions run still needs a tag, which is outside this session's authority |
64
+ | 5 | §4 invariants enforced in CI | **met** — `ci.yml` runs `pnpm lint` (source ownership + lifecycle invariants L1/L2) and `pnpm test`, which now includes the release state-machine suite |
65
+ | 6 | real Desktop matrix executed and recorded | **met with one accepted residual** (operator decision, 2026-10-02: *accept and close*) — 14 of 15 rows are `[x]`; the remaining `[~]` row covers two *interactive* delegation sub-cases that DSH provides no UI for (a subagent session opens read-only, with no composer or mode toggle), while the underlying inheritance behaviour they would exercise is verified at request time on the real host |
66
+
67
+ The final battery on the landing commit `a564159`
68
+ (`.debug/r11/evidence/29-final-head-gates.log`): LINT / TYPECHECK / BUILD:CHECK /
69
+ SOURCE:ARTIFACT / BUILD:REPRODUCIBLE / CLIENT:CHECK / TEST all pass — 1728 tests,
70
+ 1727 pass, 0 fail. Earlier landing points are recorded in
71
+ `.debug/r11/evidence/11/13/18/21/22/24/25/26/28-*.log`.
72
+
73
+ ### 2.1 Triage rule that makes the stopping rule reachable
74
+
75
+ "It keeps finding defects every round" is itself a finding: it means the
76
+ verification loop had no structural closure. So every defect found from here on
77
+ is triaged, not just fixed:
78
+
79
+ 1. Assign it to one of the classes in §1 → fix it, then ask whether that class
80
+ now has a gate (lint rule, adversarial test, negative contract test). If not,
81
+ add one in the same change.
82
+ 2. If it fits no class → the taxonomy has a hole: extend §1 and this stopping
83
+ rule **first**, then fix the instance. Fixing only the instance guarantees the
84
+ class returns in a new shape next round.
85
+ 3. "Look at it more carefully" is not a convergence mechanism. The list above is.
86
+
87
+ ## 3. Review checklist (per site)
88
+
89
+ For every resource or seam, answer explicitly:
90
+
91
+ - Who creates it?
92
+ - Who owns it (which generation / fiber)?
93
+ - Who retires it, and is the retirement awaited?
94
+ - What happens to a late completion after unload?
95
+ - Can an old generation write new-generation state?
96
+ - Is the read path a declared dependency, an optional probe, or an owned mutation?
97
+ - Is the input trusted because a type says so, or validated because it crosses a boundary?
98
+ - Does a failure here leave the Host in a half-installed state?
99
+
100
+ ## 4. Mechanically enforced invariants
101
+
102
+ `scripts/lint-lifecycle-invariants.mjs` (wired into `pnpm lint`, see
103
+ `tests/lifecycle-invariants.test.js`):
104
+
105
+ - **L1 sealed-process-global** — a process-wide global (today `globalThis.fetch`)
106
+ read at call time instead of captured once at module load.
107
+ - **L2 optional-capability-direct-read** — a bare `ctx.<optionalCapability>` read
108
+ outside a declared dependency or a guarded probe.
109
+
110
+ Both rules are proven on the shipped tree and on positive/negative fixtures. The
111
+ remaining classes are checked by reading, because deciding them needs the owning
112
+ module's semantics.
113
+
114
+ ## 5. Findings closed in R11
115
+
116
+ | # | Class | Finding | Evidence |
117
+ | --- | --- | --- | --- |
118
+ | 1 | F | Ollama warmup, update check and maintenance routes still read `globalThis.fetch` per call, re-admitting DVR's own later-installed process-wide wrappers | `src/lib/ollama-cold-start.js`, `src/lib/update-check.js`, `src/lib/maintenance-routes.js` now capture once at module load, matching the R10 provider-transport contract; guarded by `tests/ollama-cold-start.test.js`, `tests/update-check.test.js`, `tests/runtime-boundary-fixes.test.js` |
119
+ | 2 | B | Background capability profiler: a tick rejection had no observer and `stop()` did not return the in-flight tick | `src/lib/vision-background-benchmark.js`; guarded by `tests/vision-background-benchmark.test.js` |
120
+ | 3 | C | Live settings never reached the tool runtime when the context had no `effect()`: the generation token gated *all* config writes, so a watch silently did nothing and a tool call kept a stale `visionTaskTimeoutMs` | `tests/vision-tool-settings-generation.test.js` |
121
+ | 4 | C | Re-activating the same Host settings scope minted a new token and stacked lifecycle owners | same, plus `wiredScopes` idempotency in `src/lib/vision-tool-runtime-boundary.js`; guarded by `tests/vision-tool-settings-generation.test.js` |
122
+ | 5 | H | The uncommitted settings seam moved `settings.register()` **and** `scope.watch()` inside the `effect()` factory, so live Settings only worked on Hosts that eagerly execute the factory | `src/index.js`; caught by `tests/runtime-e2e.test.js` |
123
+ | 6 | H | Release: an existing tag was "recovered", which is the one case where the OIDC `SourceRepositoryDigest` cannot match the release commit | `.github/workflows/release.yml`: an existing tag is now refused; guarded by `tests/bundle-defaults.test.js` |
124
+ | 7 | H | Release: provider-compatibility adoption could publish registry bytes that were not the verified pack | adoption branch removed; a pre-existing registry version is refused; guarded by `tests/release-registry-identity.test.js` and `tests/release-state-machine.test.js` |
125
+ | 8 | H | Release: `gh release create` published the immutable release before attaching assets, so a partial failure could not be repaired | created as a draft, assets attached and verified, then published; ordering pinned by `tests/release-state-machine.test.js` |
126
+ | 9 | G | `apply`'s declaration accepted a lifecycle-less context that the runtime rejects; the packed consumer sample asserted the wrong shape and was only type-checked | `src/lib/public-entry.d.ts`, `scripts/verify-packed-public-api.mjs` now also executes the contract (it imports the packed entry and asserts a lifecycle-less `apply()` is rejected); run by the CI `packed public API` job and the release preflight |
127
+ | 10 | H | `tests/bundle-defaults.test.js` pinned the removed recovery flow; the release contract now pins the invariants instead | `tests/bundle-defaults.test.js` |
128
+ | 11 | C | `vision-execution-policy`, `replay-delegation`, `replay-envelope-v2-compat`, `vision-backend-runtime-policy`, `vision-tool-runtime-boundary` left generation-scoped wrapper caches behind when effect registration failed | closed by the R11 WIP, retained here; guarded by `tests/vision-execution-policy.test.js` and `tests/replay-delegation.test.js` |
129
+ | 12 | B | Session Vision policy domain: pre-close admitted mutations were dropped on unload; hydration could overwrite a newer mutation | closed by the R11 WIP, retained here; guarded by `tests/session-vision-policy-domain.test.js` and `tests/subagent-vision-cold-resume.test.js` |
130
+ | 13 | G | `pnpm typecheck` checked the published `.d.ts` under `skipLibCheck: true`, so a semantic error inside the declaration only surfaced on a packed run | `tsconfig.declaration.json` + `tests/types/declaration/public-entry-probe.ts`, wired into `pnpm typecheck:declaration`; negative control (injected `name: 123`) fails the gate |
131
+ | 14 | A | The `installHostSettingsCompatibility` / `protectHostProviderOwnership` throw contracts had no runtime negative tests | `tests/dsh-host-compat.test.js`: non-injectable context, foreign settings namespace, single host-owned route without touching the registrar |
132
+ | 15 | H | README announcement-bar / CHANGELOG release gates were `::warning::` advisories | `.github/workflows/release.yml` now exits 1 and scopes the README check to the top `[!WARNING]` bar; `tests/bundle-defaults.test.js` pins the hard-failure contract |
133
+ | 16 | E | Five `ctx[name]` direct-property fallbacks in the 0.1.7 compatibility facades were dead code on every production call site | retired in `dsh-host-capabilities`, `dsh-settings-017-compat`, `settings-client-017-compat`, `web/remote-settings-client`, `web/dsh-settings-017-root-transport`; guarded by `tests/dsh-host-capabilities.test.js`, `tests/dsh017-client-loader-activation.cases.js`, `tests/dsh017-settings-session-compat.cases.js` and the L2 lint |
134
+ | 17 | B | The Ollama cold-start guard was installed without the DVR file logger, and the host supplies no `ctx.logger` service, so every warmup failure path was silent — the `background()` warning was dead code | `runtime-composition.js` now passes `logging.logger`; `ollama-cold-start.js` accepts `options.logger`; `tests/ollama-cold-start.test.js` pins the observable failure; real host logs `local Ollama warmup skipped/failed ... reason=probe-failed` when the service is unreachable |
135
+ | 18 | E | `grounding-coordinate-runtime.js` kept a computed `ctx?.[name]` capability fallback, so `vision_ground`/`vision_detect` could materialise `attachments`/`fs` through the dependency-enforcing property path; the L2 lint matched only literal `ctx.name` reads, which is why it survived finding 16's retirement | get-only probe in `grounding-coordinate-runtime.js`; the L2 rule now also flags computed Context reads (`ctx?.['attachments']`, `ctx?.[name]`) while Symbol keys stay allowed; negative controls verified (re-added fallback fails the shipped-tree gate); guarded by `tests/lifecycle-invariants.test.js` and the new L2 rule `L2-optional-capability-computed-read` |
136
+ | 19 | B | `activateDeepTools()` set its `deepActive` latch before registering, and a mid-loop registrar failure left the earlier tools mounted while later calls reported "already mounted"; `vision_activate` itself was registered with its disposer discarded, so a caller-owned Host registry would keep it into the next generation | the mount loop rolls back partial registrations, resets the latch and rethrows; `vision_activate` now registers inside its own `ctx.effect`; `tests/runtime-e2e.test.js` pins both with a faithful registrar (negative control: removing the rollback fails the test) |
137
+ | 20 | G | Live model discovery read provider transports only from `settings.get('llm-pi-ai').providers`, which is **empty on a real DSH 0.2.0-rc.2 host**: provider fields live in the profile configuration (the Models page writes them back to the profile patch) and the API key lives in the credentials store. `configuredProviderTransports()` returned `[]`, so the `/models` probe never ran and the route only ever served `configuredOnly` entries — slow-endpoint / credential-change / URL-change handling was unreachable. Unit tests passed because they inject a fake settings service | **fixed**: `piProfilesState` now falls back to the Host `configEditor.configuration()` row for `llm-pi-ai` (inherited + override providers, settings still wins when it lists providers), with four unit tests; real host now probes and serves `mock-vision [live]` / `mock-text [live]` with `stale:false`, and a 9 s `/models` delay keeps the previous evidence without hanging (evidence in `.debug/r11/evidence/20-live-discovery-dead-path.md`); guarded by `tests/live-model-discovery.test.js` and `tests/live-model-credential-resolution.test.js` plus the real-host evidence |
138
+
139
+ ## 6. Real Desktop verification matrix
140
+
141
+ Rows marked `[x]` were executed on 2026-10-02 against an isolated real DSH host
142
+ (the app's own bundled runtime in Electron Node mode with an isolated `DSH_HOME`
143
+ under `.debug/r11/desktop-e2e/`, profile `web`/`mock`/`packed`/`desktop` carrying
144
+ the local R11 build) driven over CDP, plus a second real Desktop window launched
145
+ with `CFFIXED_USER_HOME` and its own web port. One row stays `[~]` with the
146
+ residual recorded inline; every other row has host-side plus UI-side evidence,
147
+ indexed in §8.
148
+
149
+ - [x] Cold start: real app generation at 2026-10-02T07:55:44Z has 0 WARN / 0 ERROR; the isolated host boots one generation with no plugin errors
150
+ - [x] Settings opens; model list renders (VR cards, configured vision models, provider catalog, third-party mock provider)
151
+ - [x] Vision toggle OFF→ON→OFF, rapid toggling (clicks during `aria-busy` are dropped, state settles consistent), toggling while a turn is in flight, new Session resets to OFF
152
+ - [x] Cold-resumed Session: page reload and host-process restart both restore the session's vision mode (persisted policy `enabled: true`, UI toggle true) and its transcript, and a cold-load flicker probe with the shipped client artifacts sampled once per frame for ~6 s shows the toggle first appearing at t≈861 ms already `aria-pressed="true"` with `Mock Vision` selected (334 present samples, exactly one value — no wrong-state or empty→wrong→right frame)
153
+ - [x] Electron-shell first paint: a genuine second Desktop window ran the local build with an isolated `DSH_HOME` (`CFFIXED_USER_HOME` moves Electron's userData so the single-instance lock no longer collides, and the profile patch moves the web port to 19490 because the running app owns 19387). 30 frames captured from launch: frames 01–07 the window is absent, 08 shows `loading plugins…`, 09 has the composer input but **not** the toolbar row, and **10 is the first frame with the vision toggle, already in its final OFF state — 21 byte-identical frames afterwards**, i.e. no visible-but-wrong toggle frame (evidence `.debug/r11/evidence/24-electron-shell-flicker.md`)
154
+ - [x] Model selection: the mock provider is chosen as the vision model and serves a real image turn; **provider unavailable** (500) retried 5/5 with visible progress then surfaced `本轮运行失败 500: …` with 0 unhandled rejections; **model changed** — rewriting DVR's chain through the real `local-settings` route moved DVR's own single-message probes to the new entry while the attachment turn kept the session's selected model, and a text-only model is correctly **not** offered by the composer's vision-model picker; **model removed upstream** — a `404 model_not_found` from the provider surfaces `处理失败 / 本轮运行失败 404: … / INVALID_REQUEST` in ~1 s with a single attempt and 0 unhandled rejections; **recovery** — the next turn succeeds once the model is back (evidence `.debug/r11/evidence/23-model-selection-row.md`)
155
+ - [x] Progressive tools (N1 answer): baseline exposes only `vision_activate`; a `vision_activate` call mounts the full 15-tool deep set, which persists across turns while the generation lives and is **gone after a settings-driven generation reload** — the Host tool registry is caller-owned, and the effect ownership added for finding 19 is what keeps the turnover clean (evidence `.debug/r11/evidence/21-n1-tool-ownership.md`)
156
+ - [~] Delegation (real tool-calling mock): root OFF→child and root ON→child each persist `{source:"delegation", inheritedFrom:<parent>}` with the inherited value; a parent switch afterwards never mutates either child record, repeated delegations never overwrite one, and the inheritance is now verified at **request time** on the real host — a child of an ON parent is handed the 40-tool vision surface while a child of an OFF parent gets the 26-tool base surface, across generation turnovers (evidence `.debug/r11/evidence/25-delegation-runtime-inheritance.md`). Only the two *interactive* sub-cases stay `[~]`: the header's `N 个子智能体` popover can open the child transcript with `在侧边栏打开`, but that panel has **no composer and no mode toggle**, so a child's mode cannot be changed from the UI; those paths remain unit-covered (`tests/session-vision-delegation.test.js`)
157
+ - [x] Settings: open, save, double-save (one generation per save, no duplicate patch entry/listener), save then plugin reload (generation turnover), reopen after reload
158
+ - [x] Proxy: configured override routes scoped host-owned benchmark traffic through a local proxy (CONNECT observed); a request in flight during a generation reload yields one clean generation turnover with 0 unhandled rejections and a fresh fetch wrapper identity; a foreign wrapper stacked on top is preserved and still reachable beneath the new DVR wrapper (ownership-aware restore)
159
+ - [x] Ollama: cold start issues `/api/ps` → `/api/generate` (`prompt:""`, `keep_alive:"30m"`); already-loaded skips generate; unreachable logs a bounded `probe-failed` warning without crashing; disabling mid-warmup yields a clean generation turnover with 0 unhandled rejections; repeated host startup re-runs the cold path (isolated real host, fake Ollama)
160
+ - [x] Live model discovery (after finding 20's fix): the route really probes — `mock-vision [live]` / `mock-text [live]` with `discoveredAt` set and `stale:false`, probe visible in the provider log; **credential rotation** (a new key saved in the Models page → the next probe authenticates with the newly saved value, not the previous one); **provider removed** (snapshot becomes `providers: []` and the cache file is rewritten to `{"version":2,"providers":[]}`); **URL change** (new route fingerprint → old live evidence not reused, `configuredOnly` + `stale`, host alive, 0 unhandled); **slow endpoint** (9 s delay past the 6 s timeout answered in 18 ms from retained evidence); **reload during request** (one generation turnover with the refresh in flight, promise settled, 0 unhandled rejections)
161
+ - [x] Background benchmark: idle start issued an image probe; an in-flight probe yielded cleanly on authorization revocation with 0 unhandled rejections and one generation turnover; foreground request / provider removal not yet
162
+ - [x] File logging: three back-to-back generation reloads wrote three complete generation blocks; the log read immediately after the last reload has 0 malformed lines and ends on a complete newline (flush-on-dispose)
163
+ - [x] Packed tarball: `pnpm pack` output is byte-identical to the worktree build, installs into an isolated profile without a worktree link, boots, renders settings, completes a real image turn, installs the scoped fetch wrapper and writes one `taskSource=user` generation from a settings save
164
+
165
+ ## 7. Open items / not yet verified
166
+
167
+ - Desktop matrix above: 14 of 15 rows `[x]` (see §6); the only `[~]` row is the two
168
+ delegation sub-cases, with the concrete reason recorded inline.
169
+ - Release: the registry/GitHub-release state machine is now exercised offline
170
+ (11 CLI-level state tests + the draft→attach→verify→publish ordering gate, with
171
+ a negative control); what remains unexecuted is an actual GitHub Actions run,
172
+ which needs a tag, and the OIDC `SourceRepositoryDigest` behaviour, which stays a
173
+ GitHub-side fact not observable from this repository.
174
+ - Packed-tarball Desktop gate: **executed** and then **re-run at the final HEAD**
175
+ (`fe3957a`) because the first run predated the finding 18/19/20 fixes — the
176
+ artifact is byte-identical to the repository build, carries the finding-20 fix
177
+ (`configEditorProviders`), boots, renders the card, serves a real image turn,
178
+ probes live models (`[live]` entries) and turns over exactly one generation with
179
+ `taskSource=user` on save
180
+ (`.debug/r11/evidence/16-packed-tarball-gate.md`,
181
+ `.debug/r11/evidence/26-packed-gate-rerun.md`).
182
+ - Systematic §3 sweep: **executed** 2026-10-02 (read-only, full `src/**`
183
+ enumeration): 56 `ctx.effect` sites, 22 `inject` sites, 22 host `ctx.on` sites,
184
+ 3 `globalThis.fetch` installs (all through `fetch-wrapper-lifecycle.js`),
185
+ 1 `child_process` patch file, 35 timers, 58 listeners, 28 mutable module caches,
186
+ 42 disposers (11 async, all awaitable) and 0 literal `ctx[name]` reads were read
187
+ and classified. Yield: findings 18–19. **N1 is now answered on a real host**:
188
+ the Host tool registry is caller-owned (the mounted deep set persists until DVR
189
+ disposes it), which is exactly why the finding-19 effect ownership is required.
190
+ **N4** — 14/14 real DVR routes answer (200/405) immediately after apply with no
191
+ settings write, so effect factories are eager on DSH 0.2.0-rc.2. **N2** — a
192
+ generation turnover without a page reload leaves the injected prelude, the
193
+ `__ModuleLoader__` wrapper and the client timers untouched (no re-execution, no
194
+ wrapper stacking). **N3** — the 2.5 s background-refresh interval is created at
195
+ prelude install and cleared only by the client generation's `dispose()`, not when
196
+ the Settings panel closes; reviewed as by design (it drives background benchmark
197
+ status shown outside the panel, and it is `unref()`-ed). Remaining Host-semantics
198
+ question: whether the fiber awaits an async disposer on every supported Host line
199
+ (all 11 async disposers return awaitables and the DSH line under test honours
200
+ them in the teardown paths exercised here). Evidence:
201
+ `.debug/r11/evidence/22-client-facts.md`, `.debug/r11/evidence/21-n1-tool-ownership.md`.
202
+
203
+ - Finding 20 is fixed by reading the Host `configEditor` profile configuration
204
+ when the settings section carries no providers; its edge cases (credential
205
+ rotation, provider removed, URL change, reload during request, slow endpoint)
206
+ were then re-run on the real host with evidence.
207
+
208
+ - Stopping-rule condition 2 (every dynamic Host boundary has an adversarial test):
209
+ audited 2026-10-02 with a boundary × hostile-shape matrix over `tests/*.js`
210
+ (`null` / `undefined` / throwing / primitive / partial / late / replaced) for
211
+ settings, llm, effect, tools, remote, attachments, webServer, fs, connection,
212
+ sessions, slots, credentials, modelDirectories, configEditor, skills and
213
+ launchEnvironment. Every boundary has hostile-input tests except
214
+ `launchEnvironment`, which had no throwing/partial cases → **2 adversarial tests
215
+ added** (`tests/live-model-credential-resolution.test.js`: a throwing
216
+ `launchEnvironment.get` and a primitive `launchEnvironment` must both defer
217
+ instead of falling through to an ambient `process.env` key). The remaining thin
218
+ boundaries (`skills`, `configEditor`, `modelDirectories`) are guarded by
219
+ `objectLike` / `hasFunction` probes whose negative cases live in the neighbouring
220
+ suites.
221
+
222
+ - Consumer-path check (2026-10-02): a clean clone of the branch (tree clean, HEAD
223
+ `6efd671`, 49 commits ahead of `origin.3.0`) passes the dependency-free gates and
224
+ follows the README literally — the documented `plugin add` command installs
225
+ `dsh-vision-router@2.3.0`, the documented `--dump-config | grep vision-router`
226
+ verification matches four times, and a fresh isolated host renders the Vision
227
+ Router card and serves a real image turn. Two honest notes recorded in
228
+ `.debug/r11/evidence/27-consumer-path.md`: a **brand-new** profile created by
229
+ `plugin add` is base-only (the documented command targets an existing `web`
230
+ profile, so the flow is correct), and the settings *list* shows the card while
231
+ model names render only after entering it.
232
+
233
+ - Commit hygiene audit (2026-10-02, all 48 commits of the branch): every subject
234
+ is conventional and English, no commit message or diff carries a local path,
235
+ machine name, debug artifact or credential value, the 48 commits touch only
236
+ `src/`, `tests/`, `scripts/`, `docs/`, `.github/`, `package.json` and
237
+ `tsconfig.declaration.json`, and the one credential-looking string in this
238
+ document was reworded. Test fixtures that embed `/Users/...` are generic
239
+ placeholders used by negative assertions, not machine information.
240
+
241
+ - Findings cross-check (2026-10-02): every row of §5 now names the artifact that
242
+ guards it, and a table checker over the section confirms **20/20 rows reference an
243
+ existing test, lint rule or verification script** (no row rests on a prose claim
244
+ alone). Negative controls were executed this session for the two highest-risk
245
+ fixes — the deep-tool rollback (removing `deepDisposers.splice` fails
246
+ `tests/runtime-e2e.test.js`) and the release ordering (removing `--draft` fails
247
+ `tests/release-state-machine.test.js`). The `ctx[name]` retirement (finding 16) is
248
+ guarded by focused suites plus the L2 lint rather than a dedicated test, because
249
+ the failure mode was dead code.
250
+
251
+ - Stopping-rule condition 4 (release state machine): **exercised offline**.
252
+ `tests/release-state-machine.test.js` drives the real `release-registry-identity`
253
+ CLI through every reachable state with a stubbed registry (fixture
254
+ `tests/fixtures/registry-fetch-stub.mjs`, 11 tests): unpublished → publish
255
+ permitted; exact published identity → publish skipped; a published but different
256
+ tarball → fail closed; transient 500s → settle on a real identity; `401` → fail;
257
+ a network error → never becomes a publish permission; post-publish wait → a
258
+ deterministic timeout error; `inspect` distinguishes a never-published version
259
+ from a registry failure. The GitHub Release phase ordering is pinned in the same
260
+ suite: draft first, `--clobber` re-runs, attached-asset verification before
261
+ `--draft=false`, an existing draft reused, npm publish gated on
262
+ `already_published == 'false'`, and the post-publish identity wait
263
+ unconditional. Negative control verified (removing `--draft` fails the gate).
264
+ **Still not executed**: an actual GitHub Actions run of the workflow, which needs
265
+ a tag and is outside this session's authority.
266
+
267
+ - Stopping-rule condition 6: one row stays `[~]` — the two delegation sub-cases
268
+ unreachable through the UI (the child session opens as a read-only panel with no
269
+ composer/toggle). The model-selection row and the Electron-shell first-paint row
270
+ are complete (`.debug/r11/evidence/23-model-selection-row.md`,
271
+ `.debug/r11/evidence/24-electron-shell-flicker.md`). The operator reviewed the
272
+ residual on 2026-10-02 and chose to **accept it and close**, on the basis that the
273
+ DVR-side inheritance behaviour is verified on a real host and the missing piece is
274
+ a DSH affordance rather than a defect in this package. The README install section
275
+ was updated in the same round to explain which profile to install into (`web`,
276
+ the official Desktop app's `desktop`, and Oh-DSH's `~/.ohdsh` Desktop), including
277
+ the brand-new-profile caveat found by the consumer-path check.
278
+
279
+ ### 7.1 Product decisions recorded (2026-10-02)
280
+
281
+ - `ctx[name]` compatibility fallbacks: **retired** (finding 16).
282
+ - `apply()` public shape: **strict** — a context without a callable `effect()` is
283
+ rejected before anything is published; the packed consumer executes the
284
+ negative case.
285
+ - README/CHANGELOG release gates: **hard failures**, scoped to the top
286
+ `[!WARNING]` announcement bar; curated notes or a matching CHANGELOG section
287
+ are required.
288
+
289
+ ## 8. Evidence index
290
+
291
+ Every artifact below lives under `.debug/r11/` (outside the repository, per the
292
+ workspace rules); paths are relative to that directory.
293
+
294
+ | artifact | what it proves |
295
+ | --- | --- |
296
+ | `evidence/01-live-save-150000.json`, `02-live-save-160000.json`, `03-double-save-170000.json` | live settings save, value change and double-save snapshots (one generation, one patch entry) |
297
+ | `evidence/04-settings-open.json` | Vision Router settings dialog payload incl. route ownership |
298
+ | `evidence/05-route-ownership-after-restart.json` | route ownership survives a host restart (exactly the expected routes) |
299
+ | `evidence/06-generation-count.txt`, `07-real-app-warn-error-count.txt` | generation count and real-app WARN/ERROR count for the cold-start row |
300
+ | `evidence/08-dvr-log-after-midflight-revoke.log`, `09-background-benchmark-lines.txt`, `10-dvr-log-inflight-revoke-verified.log` | in-flight revocation and background-benchmark deferral logs |
301
+ | `evidence/11-full-gates.log`, `13-post-commit-gates.log`, `18-post-sweep-gates.log`, `21-discovery-fix-gates.log`, `22-discovery-edges-gates.log`, `24-boundary-coverage-gates.log`, `25-release-state-machine-gates.log`, `26-final-head-gates.log` | full gate batteries (lint / typecheck / build / client / test) at each landing point, ending at the final HEAD |
302
+ | `evidence/12-cluster-plan.json`, `baseline-blobs.tsv`, `14-commit-landing.md` | WIP cluster plan (64/64 coverage), reconstructed blobs and the 16-commit landing record |
303
+ | `evidence/15-proxy-global-fetch.md` | proxy override, in-flight reload, ownership-aware fetch restore |
304
+ | `evidence/16-packed-tarball-gate.md` | `pnpm pack` content identity, isolated install, image turn, settings save |
305
+ | `evidence/17-delegation-row.md` | delegation inheritance records and the UI-unreachable sub-cases |
306
+ | `evidence/19-cold-load-flicker.md` | per-frame renderer cold-load probe (334 samples, one value) |
307
+ | `evidence/20-live-discovery-dead-path.md` | finding 20, its fix and every discovery edge case |
308
+ | `evidence/21-n1-tool-ownership.md` | Host tool registry is caller-owned; the deep set unmounts on generation reload |
309
+ | `evidence/22-client-facts.md`, `23-client-facts.json` | N2/N3/N4 probes (prelude re-execution, client timers, route eagerness) |
310
+ | `evidence/23-model-selection-row.md` | provider unavailable / model changed / model removed + recovery |
311
+ | `evidence/24-electron-shell-flicker.md`, `evidence/shell-frames/` | second real Desktop window, its cold-start frames and the composer strip |
312
+ | `evidence/25-delegation-runtime-inheritance.md` | child requests track the parent's vision surface (40 vs 26 tools) across generation turnovers |
313
+ | `evidence/26-packed-gate-rerun.md` | packed artifact at the final HEAD: identity, boot, image turn, live probe, one-generation save |
314
+ | `evidence/27-consumer-path.md` | clean clone + documented install/verification commands + real-host image turn |
315
+ | `evidence/28-delivery-head-gates.log`, `evidence/29-final-head-gates.log` | full gate batteries at the delivery HEAD (`a564159` is the final one) |
316
+ | `evidence/30-activation-shim-gates.log` | gate battery for the bundle-patch activation-shim guard |
317
+ | `evidence/31-proxy-probe-gates.log`, `evidence/32-proxy-probe-gates.log` | gate batteries for the provider-probe proxy work (the 32 log is the compliant final one) |
318
+ | `evidence/32-proxy-probe.md` | provider probes follow the proxy path: design, unit tests and the real-host A/B |
319
+ | `evidence/SUMMARY-2026-10-02.md` | the running summary written during the audit |
320
+ | `RIG.md` | exact restart recipe for the mock services, isolated hosts and client drivers |
321
+
322
+ ## 9. Delivery state (2026-10-02)
323
+
324
+ | item | value |
325
+ | --- | --- |
326
+ | branch | `r11-adversarial-final-audit`, pushed, PR [#625](https://github.com/ysr666/dsh-vision-router/pull/625) → `3.0`, all 76 checks green |
327
+ | commits ahead of `origin/3.0` | 63 (including the CI fixes and the operator's source-entry follow-up); no tag, no publish, no merge |
328
+ | findings | 20 recorded, all closed, each naming its guarding artifact |
329
+ | gate battery at HEAD | LINT / TYPECHECK / BUILD:CHECK / SOURCE:ARTIFACT / BUILD:REPRODUCIBLE / CLIENT:CHECK / TEST — 1728 tests, 1727 pass, 0 fail |
330
+ | Desktop matrix | 14 of 15 rows `[x]`; the 15th carries one operator-accepted residual |
331
+ | packed artifact | byte-identical to the repository build at the delivery HEAD, boots and serves a real image turn |
332
+ | consumer path | clean clone + documented commands install, verify and run this build |
333
+ | open external dependency | DSH would need to give subagent sessions a composer / mode toggle for the two interactive delegation sub-cases |
334
+
335
+ ## 10. Cross-line state: `3.0` vs `main` (2026-10-02)
336
+
337
+ `origin/3.0` is a **layout migration line**: 182 files under `src/`, no committed
338
+ build output, while `origin/main` still carries 164 hand-written files under
339
+ `lib/`. `origin/3.0` contains `origin/main` entirely (231 ahead, 0 behind) and this
340
+ audit's branch adds 53 more.
341
+
342
+ Every open fix PR targets `main`, so each one was checked against this line's
343
+ `src/`:
344
+
345
+ | open PR (base `main`) | issue | already present on `3.0`? |
346
+ | --- | --- | --- |
347
+ | #624 provider transport through background vision paths | #623 | **yes by construction** — on this line `vision-background-benchmark.js` / `local-vision-stabilizer.js` perform no network I/O, the benchmark wraps its provider stream in `streamWithLegacyGlobalProxyScope`, and `vision-provider-transport.ts` captures the host fetch at module load by design |
348
+ | #622 Desktop settings + model-picker boundaries | #619, #620 | **yes** — `syntheticBinder` handles the legacy settings view in `src/lib/settings-client-017-compat.js` and the `result !== loader` loader guard is in `src/lib/vision-toggle-root-hardening.js` (covered by `tests/vision-toggle-root-hardening.test.js`) |
349
+ | #615 vision-selection recovery latest-only | — | **yes** — `selectionGenerations` latest-selection-wins is in `src/lib/vision-toggle-root-hardening.js` |
350
+ | #614 remote authorization isolated per RPC | — | **yes** — `authorizationByRpc` per-RPC WeakMap is in `src/lib/remote-settings-risk-client-boundary.js` |
351
+
352
+ So those four PRs are **backports for `main`**, not missing work on this line.
353
+ Still open against this line:
354
+
355
+ - **#604** (composer model picker flashes a one-frame chevron → spinner on every
356
+ model switch) — adjacent to, but not covered by, the R11 flicker work, which
357
+ measured the **vision toggle's** first paint; the picker artifact is a separate
358
+ surface and has no open PR.
359
+ - **#448** (track the upstream DSH modules → webServer activation dependency and
360
+ retire the DVR compatibility overlay) — partially advanced by findings 16/18,
361
+ which retired the `ctx[name]` fallbacks and added the computed-read lint rule.
362
+ - #615 / #614 conflict with `main` and would need a rebase before they can merge;
363
+ #624 / #622 are mergeable with green checks, and #600 is a Dependabot bump.
364
+
365
+ ### 10.1 Upstream-gated trackers (re-verified 2026-10-02)
366
+
367
+ - **#604** (composer model picker flashes a one-frame chevron → spinner): the
368
+ plugin-side part was fixed in #603; the remaining artifact is the Host client's
369
+ own model-selection pending presentation (`aria-busy` plus its spinner node), and
370
+ it reproduces with the stock picker without this plugin loaded. Closing it needs
371
+ either an upstream presentation change or a design pass that makes Vision mode a
372
+ session-scoped authority instead of an `x` ↔ `x-vision` model switch. Nothing can
373
+ be landed safely on the plugin side, so the issue stays a Host/architecture
374
+ tracker.
375
+ - **#448** (retire the `modules -> webServer` compatibility overlay): re-checked
376
+ against the **newest runtime available here, DSH `0.2.0-rc.2`**, which is newer
377
+ than the issue's last check at `0.1.7-rc.2`. The official Web bundle still
378
+ declares only
379
+ `- id: modules / name: '@deepseek-ai/dsh-client-modules'` with **no `inject:`
380
+ field at all**, and this project's own gate classifies it as **`shim-required`**
381
+ ("official Web modules row still declares no activation dependencies"). The
382
+ overlay therefore stays justified and the issue stays open as an upstream
383
+ adoption tracker.
384
+ - The tracker is self-closing by construction: `dsh-upstream-web-modules-watch.yml`
385
+ (plus the 0.1.7 contract, 0.2-rc.2 validation and alpha source-contract
386
+ workflows) pins `DSH_MODULES_WEBSERVER_EXPECTED: shim-required`, so the moment
387
+ upstream adopts the dependency the gate returns `retire-ready`, the workflows
388
+ fail loudly and the retirement work surfaces without anyone watching manually.
389
+
390
+ ### 10.2 Do previously reported issues exist on this line, and could they return?
391
+
392
+ Checked 2026-10-02 by resolving each issue's closing PR through the GitHub timeline
393
+ and testing whether the tests that PR introduced still exist here. Result: **none of
394
+ the closed issues' defects are present in this line's `src/`, and every one of them
395
+ keeps a guarding test here** except the two noted under residual risk.
396
+
397
+ | issue | state | present here? | recurrence guard here |
398
+ | --- | --- | --- | --- |
399
+ | #623 background/exact-image-check proxy bypass | open (upstream-of-this-line residue) | no — the benchmark wraps its provider stream in `streamWithLegacyGlobalProxyScope`, and `vision-background-benchmark.js` / `local-vision-stabilizer.js` perform no network I/O at all | `scripts/dsh-proxy-egress-contract.mjs`, `tests/fetch-wrapper-composition.test.js`, plus the real-host proxy row |
400
+ | #620 duplicate `+ 自动识图` picker groups | open (main-targeted) | no | `tests/issue-284-model-visibility.test.js`, `tests/vision-toggle-root-hardening.test.js` |
401
+ | #619 Desktop "Remote settings channel unavailable" | open (main-targeted) | no — `syntheticBinder` keeps the legacy view as a delegate | `tests/dsh017-settings-session-compat.cases.js` |
402
+ | #604 one-frame chevron→spinner | open, Host-owned | plugin-side part fixed in #603; the rest is the Host's own pending presentation | `tests/vision-toggle-root-hardening.test.js` (chip width/opacity/idle-state) |
403
+ | #448 retire the `modules -> webServer` overlay | open, upstream-gated | overlay intentionally still present (`cordis.patch.yml` row with `inject: [webServer]`); upstream `0.2.0-rc.2` still declares none | `dsh-upstream-web-modules-watch.yml` fails loudly on adoption |
404
+ | #610 proxy coverage after the transport refactor | closed | no | `tests/p3-entry-composition.test.js`, `tests/qa-cancellation-publication.test.js` |
405
+ | #606 `format v4 message requires a producer-owned source kind` | closed | no | `tests/core.test.js`, `tests/depth-quota-behavior.test.js` |
406
+ | #596 `localOnlyVision` description still reached the session LLM | closed | no | `tests/local-only-vision.test.js`, `tests/manifest-dependencies.test.js` |
407
+ | #587 Windows Electron Tesseract stdin compat silently failed | closed | no | `tests/tesseract-node24-boot.test.js` |
408
+ | #576 ~10 s startup read amplification | closed | no | `tests/dsh017-settings-session-compat.cases.js`, `tests/local-vision-stabilizer.test.js` |
409
+ | #559 / #558 Desktop `dsh-app://` boot injection never carried the 0.1.7 shim | closed | no | `tests/client-script-carrier.test.js` (Desktop-only / Web-only / dual / repeated permutations, one-shot fence recovery) |
410
+ | #546 invalid-JSON failure cached for the whole TTL | closed | no | `tests/vision-resilience.test.js` |
411
+ | #536 feed-backed surface repair had no backfill | closed | no | `tests/session-vision-event-feed.test.js`, `tests/session-vision-index.test.js` |
412
+ | #520 `pixel_diff` `worstRegions` mapping | closed | no | `tests/pixel-diff-stream.test.js` |
413
+ | #519 `globalThis.fetch` wrapping loop with fetch-pipeline plugins | closed | no | `tests/fetch-wrapper-composition.test.js`, `tests/fetch-wrapper-lifecycle.test.js` |
414
+ | #524 per-agent-step deep clone of the whole session | closed | no — the runtime now has one explicit `SessionVisionRuntime` and no per-step clone seam | `tests/session-runtime-core-wiring.test.js` (architecture assertions); the `rc6-rc7-compat.test.js` guard was **deliberately retired** with the Host support floor (commits `441ca43`, `b605b27`) |
415
+
416
+ **Residual recurrence risk (two honest gaps):**
417
+
418
+ 1. ~~#547-class boot/activation race had no unit test~~ — **closed in this round**
419
+ by `tests/activation-shim-contract.test.js`: it parses the shipped
420
+ `cordis.patch.yml` and fails when (a) the `modules` / `connection` merge overlays
421
+ lose their pinned identity assertion or their `webServer` activation dependency,
422
+ (b) the plugin insert loses the id-addressed `disabled: false` recompose
423
+ re-enable, or (c) the patch disables a Host row. The suite ships four negative
424
+ controls, and the guard was verified against the real file by removing each
425
+ mechanism in turn (both edits fail the test, restoring passes). It is wired into
426
+ `pnpm test`, so a regression now fails in the ordinary battery instead of only in
427
+ a real-Host desktop gate.
428
+ 2. ~~conditional proxy bypass in the provider probes~~ — **closed in this round**:
429
+ the two probes now run inside the configured-pair proxy scope and take the
430
+ transport the proxy boundary currently owns (with a module-load capture as the
431
+ fallback), so a configured `proxy` reaches them exactly as it reaches vision
432
+ turns. The first attempt resolved `globalThis.fetch` at call time and was
433
+ **rejected by the project's own `L1-sealed-process-global` rule** — the same
434
+ class as findings 1 and #519 — so the fix instead has the boundary own and
435
+ advertise its wrapper (`currentLegacyGlobalProxyFetch()`), keeping every
436
+ consumer out of the process global. Unit tests pin both halves, and the real
437
+ host shows the difference: with `proxy` configured and the discovery cache
438
+ deleted, a cache-cleared boot produces **1** proxy CONNECT on the fixed build
439
+ and **0** on the pre-change build (evidence
440
+ `.debug/r11/evidence/32-proxy-probe.md`).
441
+
@@ -0,0 +1,165 @@
1
+ # DVR 3.0 R5 — Browser typed-source Go/No-Go
2
+
3
+ Status: **complete — production switch No-Go**
4
+
5
+ Baseline reviewed:
6
+
7
+ - branch: `3.0`
8
+ - experiment baseline: `8bda618ae7ebb2a5ee2ec9a2dd2f8868fb833a1c`
9
+ - browser hard gate: `262144` bytes
10
+ - canonical browser source: `src/lib/client.js`
11
+ - package artifact: `lib/client.js`
12
+
13
+ ## Decision
14
+
15
+ DVR 3.0 will **not** convert the production browser client to TypeScript and will
16
+ not introduce a browser bundler merely to increase TypeScript coverage.
17
+
18
+ The existing JavaScript browser source remains canonical for 3.0.
19
+
20
+ This is a deliberate architecture decision, not deferred cleanup. The R5 success
21
+ criterion was never a `.ts` percentage; a production switch was allowed only if
22
+ typed generation stayed deterministic and transparent, preserved the supported DSH
23
+ loader model, and remained comfortably inside the frozen single-file review bound.
24
+
25
+ ## Current size evidence
26
+
27
+ At the R5 checkpoint:
28
+
29
+ ```text
30
+ src/lib/client.js 261672 bytes
31
+ lib/client.js 261672 bytes
32
+ hard gate 262144 bytes
33
+ headroom 472 bytes
34
+ ```
35
+
36
+ The existing gate remains owned by
37
+ `tests/index-modularization.test.js` and is unchanged.
38
+
39
+ R5 does not:
40
+
41
+ - raise the 256 KiB gate;
42
+ - add minification as an architecture fix;
43
+ - add a browser bundler to hide source growth;
44
+ - inline source maps;
45
+ - merge the Host and Client TypeScript worlds.
46
+
47
+ ## Whole-client migration
48
+
49
+ Whole-client TypeScript conversion is **No-Go**.
50
+
51
+ The browser entry is intentionally a self-contained DSH client-loader module built
52
+ by hand rather than a normal application bundle. Converting the whole file would
53
+ create a new build/runtime transformation at the most size-constrained package
54
+ boundary while providing little new ownership information: the mature browser
55
+ surface already has explicit presentation, Settings and remote-action boundaries.
56
+
57
+ With only 472 bytes of review headroom, this is the wrong place to spend architecture
58
+ budget merely to change the source language.
59
+
60
+ ## Incremental embedded-owner experiment
61
+
62
+ R5 also tested the smallest credible typed migration rather than assuming that the
63
+ whole-client result applies to every browser owner.
64
+
65
+ The existing client contains one generated block:
66
+
67
+ ```text
68
+ <generated:client-maintenance-actions>
69
+ ```
70
+
71
+ Its source owner is `src/lib/client-maintenance-actions.js`; the deterministic
72
+ assembler is `scripts/sync-client-embedded-modules.mjs`.
73
+
74
+ A temporary, uncommitted TypeScript version of that owner was compiled under the
75
+ actual Client compiler world:
76
+
77
+ - `module: ESNext`
78
+ - `moduleResolution: Bundler`
79
+ - `lib: ES2024 + DOM + DOM.Iterable`
80
+ - `types: []`
81
+ - repository strict options including `noUncheckedIndexedAccess` and
82
+ `exactOptionalPropertyTypes`
83
+
84
+ The owner could be made strict-type-correct without Node ambient types.
85
+
86
+ However, using the TypeScript-emitted function with the existing
87
+ `Function#toString()` deterministic inliner produced:
88
+
89
+ ```text
90
+ current generated function 4099 bytes
91
+ TypeScript-emitted function 4133 bytes
92
+ delta +34 bytes
93
+
94
+ current client.js 261672 bytes
95
+ projected client.js 261706 bytes
96
+ projected headroom 438 bytes
97
+ ```
98
+
99
+ The 34-byte increase by itself does not violate the hard gate, but it consumes
100
+ scarce browser review headroom for a low-risk owner.
101
+
102
+ ## Build-lifecycle cost
103
+
104
+ The more important blocker is source/artifact ownership.
105
+
106
+ Today the sync script can import the canonical JavaScript owner directly from
107
+ `src/`. A TypeScript owner cannot be executed directly by the supported Node 22
108
+ repository lifecycle. A correct production switch would therefore need a separate
109
+ Client emit before synchronization, or would have to synchronize from a generated
110
+ `lib/` artifact.
111
+
112
+ That changes the lifecycle from the current simple relation:
113
+
114
+ ```text
115
+ canonical JS owner
116
+ -> deterministic inline sync
117
+ -> canonical client.js
118
+ -> package build
119
+ ```
120
+
121
+ to a multi-stage relation such as:
122
+
123
+ ```text
124
+ canonical TS owner
125
+ -> Client compiler emit
126
+ -> generated JS owner
127
+ -> deterministic inline sync
128
+ -> canonical client.js
129
+ -> package build
130
+ ```
131
+
132
+ This would require additional build ordering and reproducibility ownership for one
133
+ small browser helper. It is not justified by the coupling or lifecycle risk reduced.
134
+
135
+ Using the Host compiler to emit the browser owner was rejected: Client code must
136
+ continue to be checked in a browser-only world with `types: []`; Node ambient types
137
+ must not become an accidental browser source authority.
138
+
139
+ ## Why this is not negative progress
140
+
141
+ The R5 experiment confirms that the current browser boundary is already the safer
142
+ architecture for 3.0:
143
+
144
+ - the 256 KiB review bound stays frozen;
145
+ - loader/runtime behavior stays transparent;
146
+ - no bundler or minifier becomes a new supply-chain/build owner;
147
+ - Host and Client compiler worlds stay separate;
148
+ - generated browser synchronization stays simple and deterministic;
149
+ - 3.0 effort remains focused on contracts where TypeScript materially reduces
150
+ Host drift, lifecycle or ownership risk.
151
+
152
+ ## Reopen conditions
153
+
154
+ R5 should be reopened only if at least one of these becomes true:
155
+
156
+ 1. a real browser ownership extraction materially reduces `client.js` size before
157
+ TypeScript is introduced;
158
+ 2. the supported DSH browser/package contract provides a typed build path that does
159
+ not require DVR to invent a new bundling lifecycle;
160
+ 3. a browser owner develops a concrete contract/lifecycle risk large enough to
161
+ justify a separate Client emit stage;
162
+ 4. the resulting production artifact remains under the existing 262144-byte gate
163
+ without minification or a gate increase.
164
+
165
+ A future desire for a higher TypeScript percentage is **not** a reopen condition.