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

62 lines
3.1 KiB
Markdown

# 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.
```sh
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.