Files
vdm/tools/mockd
samiandClaude Sonnet 5 2d36e9fef0 proto: land F2 — download.provideAuth (1.2.0)
The last contract gap blocking an M1 definition-of-done item: CORE's "401
handled" has no return path without it, and B2a's sibling F2 was accepted in
proto-answers-m1.md but never actually landed.

download.provideAuth {taskId, username, password, save?} -> {ok}, exactly as
proposed there. Privileged and Unix-socket-only: a credential-bearing method
must never be reachable from the browser, which is the other half of the
promise event.auth.required's own description already makes ("never back
through this event, never into a log"). It answers the challenge; it does not
itself resume the task -- the daemon retries with the credential attached and
the ordinary event.task.state reports the task leaving retry_wait, the same
as any other state change.

save only tells the daemon whether to persist the credential in the Secret
Service for next time, or use it for this attempt alone -- it never touches
SQLite or a log either way, in keeping with CLAUDE.md's secrets rule.

Three fixtures: the success path, -32010 for a task that no longer exists
(credentials submitted for it are simply discarded), and -32003 confirming
the extension has no path to this method under any transport.

mockd gets a real handler rather than falling through to the generic fixture
responder: it validates the taskId exists (so the -32010 fixture is
replayable) and actually transitions the task out of retry_wait.

Minor bump, 1.1.0 -> 1.2.0: additive method, no existing type touched.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012fgjnqFCS5h5L7gZTZo3rV
2026-09-10 00:01:58 +04:00
..

tools/mockd — a fake veloxd

Serves contracts/fixtures over both transports, keeps just enough state that adding and pausing a download does something visible, and fakes progress events at 4 Hz.

The GUI and extension lanes develop against this from day one and never wait for the real daemon. Its unhappy-path flags exist so those lanes can test the cases that are hard to arrange on purpose — a slow daemon, a flaky one, a dropped socket, a refused pairing.

cd tools/mockd
npm install
npm start -- --help
npm start                      # both transports, default paths

Defaults: $XDG_RUNTIME_DIR/velox/velox.sock and ws://127.0.0.1:52000.

Flags

Flag Effect
--uds <path> / --no-uds Unix socket path, or don't listen
--ws-port <n> / --no-ws loopback WebSocket port, or don't listen
--progress-hz <n> progress event rate (default 4, the contract's ceiling)
--speed <bytes> synthetic per-task speed
--tasks <n> seed n plausible synthetic tasks instead of the fixture's two — see below
--active-cap <n> ceiling on concurrently-"downloading" synthetic tasks (default 24)
--seed <n> PRNG seed for --tasks, so a run is exactly reproducible (default 1337)
--slow <ms> delay every reply. Past 750 ms capture.offer must fail open.
--flaky <0..1> answer this fraction of calls with -32603
--drop-connection <s> terminate every connection every N seconds
--refuse-pairing session.pair fails, as if the user clicked Deny
--lockout session.pair answers -32014, as if the brute-force lockout tripped
--allowed-root <dir> add a root that download.add's saveDir may resolve inside
--allow-any-origin skip the moz-extension:// Origin check (debugging only)
--no-validate stop validating params (to see what a client actually sends)

--tasks — load testing the GUI's table

GUI's M1 definition of done is "10 000 synthetic rows scroll at 60 fps with flat memory over 10 minutes (mockd --tasks 10000)". That takes more than 10 000 identical rows:

npm start -- --tasks 10000

Seeds a plausible population — varied state, size, category, queue position and description, drawn from the same category.list / queue.list fixtures the rest of mockd serves, so nothing here can name a category or queue those methods don't also return. Roughly 55% land complete, the rest split across failed, cancelled, paused, retry_wait and queued, plus a bounded pool (--active-cap, default 24) seeded straight into downloading.

That pool is rotating, not fixed: as an active task finishes, the next one is promoted from its queue's FIFO — with the rest of that queue's queuePosition renumbered, as a real scheduler would — and a small fraction of "finishing" active tasks fail instead and cycle through retry_wait before rejoining. Over a ten-minute run this means hundreds of distinct rows have shown live progress by the time it ends, not the same handful forever, while at any instant the active count stays realistic. A manual download.add is always admitted immediately regardless of --active-cap — a human driving the GUI by hand is never made to wait behind synthetic load.

tick() only ever walks the active pool plus whatever retry-wait entries just came due, never the full task list, so the per-tick cost stays flat regardless of --tasks.

--seed makes a run reproducible: the same seed always produces the same table, which matters when a GUI bug only shows up at a particular row.

What is real and what is faked

Real, because a client's correctness depends on it:

  • transport and privilege rules, taken from the generated METHODS table — so a privileged method is refused with -32003 over the WebSocket exactly as veloxd must;
  • param validation, through the generated validators, including range and length checks;
  • saveDir canonicalization against the allowed roots, so -32011 is reachable;
  • the capture.offer decision — monitored types, minimum size, excluded hosts — so both the take and the ignore paths get exercised;
  • pairing: session.hello accepts only a token this process actually issued;
  • task state, so add / pause / resume / cancel / remove do what a client expects to see.

Faked: bytes advance on a clock, not from a socket. There is no network, no disk, and no engine. Anything not listed above is answered from its golden fixture.

Why it imports the generated protocol code

mockd uses extension/src/shared/protocol/ — the generated TypeScript — rather than types of its own. A mock with hand-written types is a third source of truth, and it drifts. This way a schema change that breaks a client breaks mockd in the same commit.

It imports the individual generated modules (types.js, methods.js, …) rather than index.js, because the extension/ tree has no package.json of its own for Node to resolve a star re-export through. That is a quirk of running from outside that package, not a problem with the generated code; the extension's own bundler is unaffected.