Files
vdm/tools/mockd/README.md
T
samiandClaude Opus 5 53421d6cb8 proto: freeze the wire contract at 1.0.0
Schemas for the whole v1 surface: 38 methods, 9 events, 25 named types and the
JSON-RPC envelope, with x-privileged / x-transports / x-deadlineMs / x-errors
annotations that both generators emit as data rather than prose.

Four generators over one IR (contracts/codegen/schema_ir.py), so the C++ structs,
the TypeScript types and the OpenRPC document cannot disagree about what the
contract says:

  gen_cpp.py             -> core/generated/velox_proto.{hpp,cpp}
  gen_ts.py              -> extension/src/shared/protocol/
  gen_openrpc.py         -> contracts/openrpc.json
  gen_cpp_conformance.py -> tests/conformance/cpp/fixture_dispatcher.hpp

Inbound parsing never throws: parse<T>() returns std::expected<T, ParseError> and
nlohmann's throwing ADL from_json is deliberately not emitted. Schema constraints
(minimum, maxLength, pattern, ...) become real runtime checks in both languages —
the daemon does not trust the extension and the extension does not trust the
daemon.

59 golden fixtures: a success case per method, 12 error cases, 9 events. Replayed
by tests/conformance/ against both the generated C++ and a live server over both
transports. tools/mockd serves the same fixtures with unhappy-path flags so the
GUI and EXT lanes never wait for veloxd.

run.sh also proves capture.offer fails open: with a daemon answering slower than
750 ms the client gives up and lets Firefox take the download.

core/generated/ is libveloxproto, a separate target from libveloxcore, which
still never sees JSON — see docs/adr/0009.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012fgjnqFCS5h5L7gZTZo3rV
2026-09-09 19:55:54 +04:00

3.1 KiB

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
--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)

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.