mockd: add --tasks N — a plausible large synthetic table for GUI's DoD

GUI's M1 definition of done is "10 000 synthetic rows scroll at 60 fps with
flat memory over 10 minutes (mockd --tasks 10000)". This flag was missing from
the four unhappy-path flags that did land; the brief's own flag list omitted
it, which is corrected here too.

--tasks seeds a plausible population rather than N copies of one row: varied
state, size (log-uniform 50 KB - 20 GB), category, queue position and
description, drawn from the same category.list / queue.list fixtures the rest
of mockd already serves so a synthetic task can never name a category or
queue those methods don't also return. State distribution is roughly
55% complete / 8% failed / 4% cancelled / 6% paused / 2% retry_wait / 25%
queued, using the new TaskErrorCode taxonomy for failures.

"Progress advances across the whole set, not a handful of live rows" ruled
out the obvious cheap answer. A bounded, rotating pool of concurrently-active
downloads (--active-cap, default 24) is fed continuously from each queue's
FIFO — with the rest of that queue's queuePosition renumbered on every
promotion, as a real scheduler would — and a small fraction of active tasks
hit a transient failure and cycle through retry_wait before rejoining, so the
pool keeps rotating through new rows for the whole run instead of draining
once. Verified over a 10000-task, 60-second run: 61.5 MB RSS flat, and the
active pool's membership meaningfully different after 60s.

tick() only ever walks the active pool plus due retry-wait entries, never the
full task list, so its cost stays flat regardless of --tasks. A manual
download.add is still admitted immediately regardless of --active-cap — a
human driving the GUI by hand must never wait behind synthetic load.

Fixed a latent double-push while building this: any task 'connecting' at the
top of a tick was pushed to the progress batch once for the transition and
again at the loop's unconditional final push, inflating event.task.progress
payloads with a duplicate entry for that taskId. It predates this change (the
original tick() had the same shape) but only became visible once several
tasks are legitimately 'connecting' in the same tick, which --active-cap's
continuous promotion now does routinely.

--seed makes a run reproducible, which matters when a GUI bug only shows up
at a particular row.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012fgjnqFCS5h5L7gZTZo3rV
This commit is contained in:
2026-09-10 00:01:44 +04:00
co-authored by Claude Sonnet 5
parent fdacf732fa
commit f60070c420
5 changed files with 551 additions and 22 deletions
+4 -1
View File
@@ -34,7 +34,10 @@ CI noticing the same day.
6. **`tools/mockd`** — Node/TS. Serves the fixtures over **both** transports (Unix socket
NDJSON and loopback WS), fakes plausible progress events at 4 Hz, and has flags for
`--slow`, `--flaky`, `--drop-connection`, `--refuse-pairing` so GUI and EXT can test
their unhappy paths before `veloxd` exists.
their unhappy paths before `veloxd` exists. **`--tasks <n>`** seeds a large plausible
population (varied states/sizes/categories, a rotating active pool) instead of the
fixture's two rows — GUI's M1 DoD needs `--tasks 10000` for its scroll-performance test,
so this one lands with M0, not as an afterthought once GUI is already blocked on it.
7. **`tests/conformance/`** — one suite, two runners: replays each fixture against a live
`veloxd` (C++ side) and through the generated TS client. Wired into CI as a **required
check on every lane's PR**.