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
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
METHODStable — so a privileged method is refused with-32003over the WebSocket exactly asveloxdmust; - param validation, through the generated validators, including range and length checks;
saveDircanonicalization against the allowed roots, so-32011is reachable;- the
capture.offerdecision — monitored types, minimum size, excluded hosts — so both the take and the ignore paths get exercised; - pairing:
session.helloaccepts 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.