# 06 — Risks and spikes Each spike is a **timeboxed M0/M1 investigation with a written answer in `docs/adr/`**. Do not let any of these be discovered in M6. --- ## R1 — Snap-packaged Firefox blocks native messaging ⚠ HIGH **Evidence gathered on this machine (2026-09-09):** ``` $ snap list firefox → firefox 154.0 mozilla** $ snap connections firefox → home connected network connected network-bind connected personal-files (dot-mozilla-firefox) ``` Ubuntu ships Firefox as a strictly-confined snap. Executing a native-messaging host binary that lives outside the snap's confinement has a long history of breaking, and even when the manifest is found, the host runs under the snap's constraints. **Why it's already handled:** `network` and `network-bind` are connected, so a loopback WebSocket to `127.0.0.1` is available regardless. The dual-transport design in `docs/05-extension-spec.md` §4 is not belt-and-braces engineering for its own sake — it is the direct consequence of this finding. **Spike S1 (2 days, M0, lane EXT):** on a clean 26.04 VM, install a trivial native host into each of the four manifest locations and record exactly which ones snap Firefox can launch, and whether the launched process can reach `$XDG_RUNTIME_DIR`. Write the result to `docs/adr/0003-native-messaging-under-snap.md`. **Decision rule:** if native messaging works → prefer it, keep WS as fallback. If it does not → WS becomes the primary path, `velox-nmhost` still ships for deb/flatpak/tarball Firefox users, and the installer detects the snap and configures pairing automatically. Either way, M2 ships. The *installer* must detect which Firefox is in use and say so. --- ## R2 — Wayland clipboard monitoring ⚠ HIGH (feature-shaping) Ubuntu 26.04 defaults to GNOME on Wayland. A Wayland client **cannot** passively observe clipboard changes made by other applications — that is a deliberate security property, not a bug, and it is the mechanism IDM's clipboard capture relies on. **Spike S2 (2 days, M1, lane GUI):** test, on this exact desktop, (a) whether Qt receives `QClipboard::dataChanged` for copies made in another app, (b) whether Mutter exposes `ext-data-control-v1` / `wlr-data-control`, (c) whether `org.freedesktop.portal.GlobalShortcuts` gives a reliable "grab clipboard now" hotkey, (d) XWayland fallback behaviour. **Ship-regardless design:** the extension context menu covers the browser case (where almost all copied download links come from), the portal global shortcut covers explicit capture, and the Add-URL dialog pre-fills from the clipboard when opened. Background monitoring is a bonus if the spike says yes. **Do not let this block the release, and do not promise it in the UI before S2 answers.** **Spike S2, item (c) answered — verified live, not the full spike:** on this desktop (GNOME/Mutter, Ubuntu 26.04, plain non-Flatpak/non-snap process), `CreateSession` on `org.freedesktop.portal.GlobalShortcuts` refuses every caller with `"An app id is required"` — reproduced two ways: `gui/src/clipboard/GlobalShortcut.cpp`'s real async D-Bus call, and a bare `busctl --user call … CreateSession` from an interactive shell (no Qt involved at all), both under a real Wayland session (`WAYLAND_DISPLAY` set), not just offscreen. Because the second reproduction has no Qt/app-level identity to configure at all and still fails identically, this looks like the portal requiring the caller's *bus connection* to already carry a sandboxed app id (Flatpak/snap portal-confined) — something no amount of `QGuiApplication::setDesktopFileName()` or similar can supply from an unconfined process. **Practical read:** the global-shortcut explicit path likely does not work at all for Velox as a traditionally-packaged (.deb/AppImage) app on stock GNOME — only if/when it ships confined. The code is still in (best-effort, fails silent exactly like this, never advertised — see the file's own header), since it costs nothing and activates automatically the day that changes. Items (a) `QClipboard::dataChanged` cross-app, (b) `wlr-data-control`, and (d) XWayland fallback are **still unanswered** — this was one item of S2's four, not the full spike. --- ## R3 — AMO review friction 🟠 MEDIUM `` + `webRequestBlocking` + `nativeMessaging` is a heavyweight permission set; a download manager legitimately needs it, but reviews take longer and can bounce. **Mitigation:** run `web-ext lint` in CI from day one, no remote code execution *at all* (no CDN scripts, no `eval`), ship readable source with a build-reproduction script, write the permission justification in M1 rather than at submission, and submit an early unlisted build in M3 to shake out review problems while there's still time. --- ## R4 — Servers that lie about ranges 🟠 MEDIUM `Accept-Ranges: bytes` present but `206` never delivered; ETags that change per request; CDNs that 403 a second connection; signed URLs that expire mid-download. **Mitigation:** resumability is *proven* by an actual 206 with a matching `Content-Range` (`docs/04` §2); `tools/testserver` implements each of these as an explicit hostile mode and CORE's DoD requires passing all of them; `download.refreshUrl` exists so the user can paste a fresh signed URL into a running task. --- ## R5 — Qt 6 LGPL compliance 🟢 LOW but do it right Dynamic linking against unmodified system Qt satisfies LGPLv3. **Do not** static-link Qt into the AppImage without reading the terms; if the AppImage bundles Qt, bundle it as shared objects and ship the relink information. Record in `docs/adr/0002-qt-licensing.md`. --- ## R6 — ffmpeg/libav licensing for the media grabber 🟢 LOW Depend on the distro's ffmpeg rather than bundling; keep the muxer behind a runtime check so the app degrades gracefully when ffmpeg is absent. Never bundle a GPL build into a package whose licence conflicts. --- ## R7 — Contract drift between lanes 🟠 MEDIUM The classic parallel-development failure: GUI and extension each "fix" the protocol in their own tree and integration in M2 becomes a rewrite. **Mitigation:** the entire `contracts/` discipline — single owner, generated code, golden fixtures, conformance suite as a merge gate. If a lane finds the contract wrong, it opens a `contracts/`-only PR; it does **not** work around it locally. This is the single most important process rule in the project. --- ## R8 — Scope creep into a browser-agnostic product 🟢 LOW Chrome/Chromium support means losing blocking `webRequest` and rebuilding capture on `declarativeNetRequest` + `downloads.onDeterminingFilename`, which is a different design. Ship Firefox 1.0 first. Revisit after M7 as its own project, not as an M3 side quest.