You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 4cffd36
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: docs/specs/alert.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -409,7 +409,7 @@ Every rule above holds for both engines.
409
409
-**Must send exactly the `toSpokenText` label and the member's voice id**, never terminal content, a notification body, or a Session id.
410
410
-**Never let the voice token reach a renderer**; the host adds it to the request (rationale), and only managed-voice approval writes it (`docs/specs/hosted.md` -> "Managed voice"). Where it may go: `docs/specs/security-local.md` → "Persisted state".
411
411
-**Must bound the host request** at `MANAGED_VOICE_REQUEST_TIMEOUT_MS` (inside `SPEECH_ENGINE_TIMEOUT_MS`, so a fallback still fits) and `MAX_AUDIO_BYTES`, accepting only `audio/mpeg`, and store only a token of the `dmv_…` shape and a voice of the curated set (`MANAGED_VOICES`).
412
-
-**Must cache clips by voice and text in the host's memory, bounded in count and bytes, never persisted**; a hit makes no request, and a token change empties it. **A 403 latches `notEntitled`**, which bypasses the cache until a speak succeeds, so a lapsed member hears the system voice; **a 401 forgets the token**. An answer to an older token changes nothing.
412
+
-**Must cache clips by voice and text in the host's memory, bounded in count and bytes, never persisted**; a hit makes no request, and a token change empties it. **A 403 latches `notEntitled`**, which bypasses the cache until a speak succeeds, so a lapsed member hears the system voice; **a 401 forgets the token it refused, never one saved since**. An answer to an older token changes nothing.
413
413
-**Must disable managed voice in memory immediately on Disconnect, including cached and in-flight results, even if clearing its persisted token fails.** A failure stays visible and retryable; it must never report a successful disconnect while the token remains on disk.
414
414
415
415
Source of truth: `withFallback` in `lib/src/lib/speech-engine.ts`; `createManagedVoiceEngine` in `lib/src/lib/managed-voice-engine.ts`; `createManagedVoiceHost` in `lib/src/host/managed-voice-host.ts`; `MANAGED_VOICES` in `remote-lib-common/src/remote/managed-voice.ts`.
Copy file name to clipboardExpand all lines: docs/specs/remote-network.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -29,7 +29,7 @@
29
29
-**The Burrow service never opens the relay socket**, the enrollment held as above, so push, the device list, and setup codes make no request.
30
30
-**It refuses `enroll`, `enrollOffer`, and `beginHostedEnrollment` before any request**, the offer file unread, and a change to `nothing` ends a Hosted enrollment awaiting approval.
31
31
-**It offers no one-time link**: the resting state is `unavailable` with reason `network-off`, and `oneTimeOpen` is refused, as under `local` with no network allowed.
32
-
-**Managed voice asks the service before every speak and approval request**, refusing them under `nothing`; an awaiting voice approval ends at its next poll.
32
+
-**Managed voice asks the service before every speak and approval request** — a VS Code window without one reads the stored policy — refusing them under `nothing`; an awaiting voice approval ends at its next poll.
33
33
34
34
Source of truth: `lib/src/remote/network-policy.ts`; `BurrowService` in `lib/src/host/remote/service.ts`; `lib/src/host/remote/network-interfaces.ts`; `createManagedVoiceHost` in `lib/src/host/managed-voice-host.ts`.
Copy file name to clipboardExpand all lines: docs/specs/security-local.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -128,7 +128,7 @@ The attacker is another local account reading disk; what the remote stack leaves
128
128
129
129
**Standalone writes `recovery.json` beside its sessions directory**, under the state root, owner-only: one rebuilt agent-resume invocation per Surface, never a buffer, unlinked as it is read (`docs/compatible-agents.md` -> "Recovery record").
130
130
131
-
**The managed-voice token is a bearer credential at rest**, written only by managed-voice approval — `managed-voice.json` beside the Burrow's enrollment in the state dir, `0700`/`0600`, under the owner-only DACL `burrow_state_dir` applies on Windows before the sidecar spawns (rationale); `docs/specs/alert.md` → "Managed voice" keeps it from any webview. Where the token may go is `docs/specs/security-remote.md` -> "Relay origin".
131
+
**The managed-voice token is a bearer credential at rest**, written only by managed-voice approval — in VS Code, `SecretStorage` beside the enrollment (`docs/specs/vscode.md` -> "Managed voice"); in standalone, `managed-voice.json` beside the Burrow's enrollment in the state dir, `0700`/`0600`, under the owner-only DACL `burrow_state_dir` applies on Windows before the sidecar spawns (rationale); `docs/specs/alert.md` → "Managed voice" keeps it from any webview. Where the token may go is `docs/specs/security-remote.md` -> "Relay origin".
132
132
133
133
**VS Code persists pane structure in VS Code's own storage** — `workspaceState` and `vscode.setState()` — so the modes there are VS Code's, not ours, and no transcript reaches either (`docs/specs/vscode.md` -> "Serialization and restore"). Dormouse also writes `recovery.json` in extension storage, mode `0600` on Unix: one rebuilt agent-resume invocation per Surface, no buffer, unlinked as it is read (`docs/compatible-agents.md` -> "Recovery record").
Copy file name to clipboardExpand all lines: docs/specs/security-remote.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -52,7 +52,7 @@ Only one local confirmation of the phone's two digits mints an ACL record. The w
52
52
-**FAIL IF**`S194` a Hosted build's enrollment opens an account page other than the one `enrollVerificationUrl` in `lib/src/host/remote/service.ts` composes (a dev Hosted build's exception: `docs/specs/relay.md` -> "Burrow side"), `HOSTED_ACCOUNT_ORIGIN` in `lib/src/host/relay-origin.ts` is not exactly `https://hosted.dormouse.sh`, is read from anything a build or user sets, or is requested by the desktop, or the enrollment's device code reaches a webview (`HostedEnrollmentState`). Pinned by `lib/src/host/remote/service.test.ts`.
53
53
-**FAIL IF**`S195``assertRelayOriginBaked` is no longer called on the built bundle by both `standalone/scripts/build-sidecar-proxy.mjs` and `vscode-ext/scripts/esbuild.mjs` — including the **watch** branch of the VS Code script — `standalone/vite.config.ts` drops `relayDefineVitePlugin`, `resolveRelayOrigin` stops failing the build on any case `docs/specs/relay.md` -> "Relay origin" lists, or `bakedRelayMode` in `lib/src/host/relay-origin.ts` answers `'hosted'` for anything but a baked `hosted` (rationale). Pinned by `lib/src/host/relay-origin.test.ts` and `standalone/scripts/webview-relay-define.test.mjs`.
54
54
-**FAIL IF**`S196` a Burrow reaches or enrolls with a Relay at any origin but its baked one — taking one from a command, the offer file, or a stored enrollment — or connects on, or saves, an enrollment naming another (`docs/specs/relay.md` -> "Relay origin"; read `BurrowService` in `lib/src/host/remote/service.ts`), or `POST /api/burrow/enroll` in `relay/src/app.ts` reads the credential or touches `burrows.json` for a request naming another origin.
55
-
-**FAIL IF**`S197` a self-host build can reach `dormouse.sh` or any host under it unless the user clicks a link to it (`docs/specs/relay.md` -> "Relay origin"). `hostedOrigin` and `hostedVoiceOrigin` in `lib/src/host/relay-origin.ts` must answer `null` there and every caller do nothing on `null`; `standalone/vite.config.ts` must bake the webview through `resolveRelayOrigin`; `startUpdateCheck` in `standalone/src/updater.ts` must return before `check()` unless `bakedRelayMode()` is `'hosted'`; `managedVoicePortForBuild` in `standalone/src/managed-voice-port.ts` must give a self-host webview no port; and `standalone/scripts/tauri.mjs` must overlay a self-host `tauri build` with no updater endpoint. Search the rest of `lib/src/host/`, `standalone/`, and `vscode-ext/src/` for any other request to a `dormouse.sh` host.
55
+
-**FAIL IF**`S197` a self-host build can reach `dormouse.sh` or any host under it unless the user clicks a link to it (`docs/specs/relay.md` -> "Relay origin"). `hostedOrigin` and `hostedVoiceOrigin` in `lib/src/host/relay-origin.ts` must answer `null` there and every caller do nothing on `null`; `standalone/vite.config.ts` must bake the webview through `resolveRelayOrigin`; `startUpdateCheck` in `standalone/src/updater.ts` must return before `check()` unless `bakedRelayMode()` is `'hosted'`; `managedVoicePortForBuild` in `standalone/src/managed-voice-port.ts` must give a self-host webview no port, and `getWebviewHtml` in `vscode-ext/src/webview-html.ts` must set `MANAGED_VOICE_GLOBAL` only when `bakedRelayMode()` is `'hosted'`; and `standalone/scripts/tauri.mjs` must overlay a self-host `tauri build` with no updater endpoint. Search the rest of `lib/src/host/`, `standalone/`, and `vscode-ext/src/` for any other request to a `dormouse.sh` host.
56
56
-**FAIL IF**`S198` an enrollment exchange in `lib/src/remote/burrow/enrollment.ts` or `burrowRequest` in `lib/src/remote/burrow/burrow-fetch.ts` drops `redirect: 'error'`, or a Burrow→Relay HTTP request outside those exchanges bypasses `lib/src/remote/burrow/burrow-fetch.ts` (rationale).
Copy file name to clipboardExpand all lines: docs/specs/transport.md
+9-7Lines changed: 9 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -165,15 +165,17 @@ OSC parsing and stripping for those rows: `docs/specs/terminal-escapes.md` → "
165
165
166
166
### Managed voice
167
167
168
-
**Managed voice is one optional adapter member**, `managedVoice?: ManagedVoicePort`, present only in a Hosted standalone build (Tauri and the browser-dev harness); VS Code, Pocket, the website, and a self-host build omit it. Behavior: `docs/specs/alert.md` → "Managed voice".
168
+
**Managed voice is one optional adapter member**, `managedVoice?: ManagedVoicePort`, present only in a Hosted desktop build — standalone (Tauri and the browser-dev harness) and VS Code (`docs/specs/vscode.md` -> "Managed voice"); Pocket, the website, and a self-host build omit it. Behavior: `docs/specs/alert.md` → "Managed voice".
169
169
170
-
| Direction | Standalone carrier | Payload |
171
-
| --- | --- | --- |
172
-
| Webview → host |`managed_voice { payload }` → sidecar `voice:command`| op `status`, `configure` (the voice only), `beginEnrollment`, `cancelEnrollment`, `disconnect`, or `speak`|
173
-
| Host → webview | sidecar `voice:result` → invoke result | that op's answer |
174
-
| Host → every webview | sidecar `voice:status`| the status after each change: a saved voice, voice approval, disconnect (including persistence failure), or the `notEntitled` latch |
170
+
| Direction | Standalone carrier | VS Code carrier | Payload |
171
+
| --- | --- | --- | --- |
172
+
| Webview → host |`managed_voice { payload }` → sidecar `voice:command`|`voice:command { payload, requestId }`| op `status`, `configure` (the voice only), `beginEnrollment`, `cancelEnrollment`, `disconnect`, or `speak`|
173
+
| Host → webview | sidecar `voice:result` → invoke result |`voice:result { requestId, result }`| that op's answer |
174
+
| Host → every webview | sidecar `voice:status`|`voice:status { status }`, the window's webviews | the status after each change: a saved voice, voice approval, disconnect (including persistence failure), or the `notEntitled` latch |
175
+
176
+
**Every window caches the status**: it asks `status` once its `voice:status` listener is live, and again when the harness's event stream reconnects, then takes each broadcast; **an answer never overrides a broadcast that arrived after its request**.
175
177
176
-
Source of truth: `ManagedVoicePort` in `lib/src/lib/platform/managed-voice-types.ts`; `createManagedVoicePort` in `standalone/src/managed-voice-port.ts`; `createManagedVoiceHost` in `lib/src/host/managed-voice-host.ts`.
178
+
Source of truth: `ManagedVoicePort` in `lib/src/lib/platform/managed-voice-types.ts`; `createManagedVoicePortClient` in `lib/src/lib/platform/managed-voice-port.ts`; `createManagedVoiceHost` in `lib/src/host/managed-voice-host.ts`.
The policy starts from `default-src 'none'`; each grant below names what needs it. Every other grant is `webview.cspSource` alone (`style-src`, `font-src`, `img-src`, `connect-src`), plus `data: blob:` images and the loopback `ws:` below; nothing else.
90
+
The policy starts from `default-src 'none'`; each grant below names what needs it. Every other grant is `webview.cspSource` alone (`style-src`, `font-src`, `img-src`, `connect-src`), plus `data: blob:` images, `media-src blob:` for managed voice's clip ("Managed voice"), and the loopback `ws:` below; nothing else.
91
91
92
92
**`frame-src` is loopback-only**: `dor iframe` frames its target through the transparent proxy the extension host stands up, so the only origin ever embedded is loopback on an OS-assigned port (`docs/specs/dor-browser.md`).
93
93
@@ -174,6 +174,18 @@ Source of truth: `vscode-ext/src/burrow.ts`, `ensurePeerNet` in `vscode-ext/src/
174
174
175
175
Source of truth: `vscode-ext/scripts/stage-native-direct.mjs`; `initBurrow` in `vscode-ext/src/burrow.ts`; `createNativeDirectPeerFactory` in `lib/src/host/remote/native-direct-peer.ts`.
176
176
177
+
## Managed voice
178
+
179
+
**Every window's extension host runs its own managed-voice host**, the one standalone's sidecar runs (`docs/specs/alert.md` -> "Managed voice"), answering its own webviews (`docs/specs/transport.md` -> "Managed voice"); its clip cache and refusal latch are per window.
180
+
181
+
-**The token and the chosen voice live in `SecretStorage`, each under a key of its own**: the token is a bearer credential, and the voice rides there only because `secrets.onDidChange` reaches every window. **Every window re-reads on that change**, dropping its clips and latch when the token changed, so an approval or Disconnect in one window reaches all of them.
182
+
-**An approval runs in the window that asked**, its code shown only there; the token it saves reaches the rest by the re-read.
183
+
-**Never re-arm on a re-read a token this window's failed Disconnect left in the store**; a different token, a sibling's later approval, supersedes it.
184
+
-**A window asks the network policy before every speak and approval request**: the service in the broker, the stored policy elsewhere, a failed read counting as Nothing.
185
+
-**The webview has the port only when the host says so**: `getWebviewHtml` sets `MANAGED_VOICE_GLOBAL` from the extension bundle's baked mode, which the webview bundle does not carry.
186
+
187
+
Source of truth: `vscode-ext/src/managed-voice.ts`; `readInjectedManagedVoice` in `lib/src/lib/vscode-managed-voice-global.ts`; `VSCodeAdapter` in `lib/src/lib/platform/vscode-adapter.ts`; pinned by `vscode-ext/test/managed-voice.test.ts`.
188
+
177
189
## Peer surfaces
178
190
179
191
The service asks every webview what only it knows (`docs/specs/transport.md` → "Message protocol"), over two tiers. **Peers on both tiers may run different builds** — VS Code windows of one installation keep their build until reloaded, and `status.hostedEnrollment` is the one field a newer broker may extend.
0 commit comments