Files
vdm/docs/agents/AGENT-DAEMON.md
T
samiandClaude Sonnet 5 eb72aa522c daemon: build velox-nmhost, systemd socket activation + units, velox(1) man page
Closes build order items 7 (the systemd half) and 9 (D11 in deferrals.md).

velox-nmhost (nmhost/src/main.cpp, 185 lines): a poll()-driven byte pump between
Firefox's native-messaging framing on stdio (4-byte native-byte-order length prefix)
and veloxd's own NDJSON framing on the Unix socket. Reframes each direction, no JSON
parsing, no retry/backoff (the extension relaunches a fresh host on its own
reconnect), exits the moment either side closes. Deliberately dependency-free — no
veloxd_* library, no nlohmann_json — since it runs unconfined outside Firefox's
sandbox regardless of packaging format.

Two real bugs found and fixed while getting the integration test to actually pass
rather than hang, both exactly the class of bug a "trivial pump" invites:
1. Never set the pumped fds non-blocking, so the "drain what's available" read loop
   blocked on its own second read() instead of returning to poll().
2. stdin and stdout are two different descriptors (0 and 1), not one — an early draft
   polled POLLOUT on fd 0, which is opened read-only, so EOF and writability were
   never both observable through the same pollfd entry.

packaging/nativehost/com.velox.host.json + its own README.md supersede
AGENT-DAEMON.md's stale "four locations" line: spike S1 / ADR 0003 found only three
manifest locations are real (~/.mozilla/native-messaging-hosts/ for BOTH deb/tarball
and snap Firefox, /usr/lib/mozilla/... for deb/tarball only, the flatpak sandbox path)
— the fourth, ~/snap/firefox/common/.mozilla/..., is not read by snap Firefox at all.
The README spells out the per-user-manifest / postinst enumeration implication for
PKG/QA (postinst runs once as root; the two ~/-relative locations are per-user) and
flags that docs/07-packaging.md's own install-layout line only shows the one
root-owned path.

Socket activation: rpc/systemd_activation.cpp is a from-scratch sd_listen_fds() (env
vars only — LISTEN_PID/LISTEN_FDS, fd 3 — no libsystemd link) that UdsServer::start()
checks first, skipping its own create/bind/chmod/listen when systemd already bound
the socket. packaging/systemd/velox.socket + velox.service are the unit pair,
verified both by systemd-analyze verify and by an actual fork/dup2/execve simulation
of the activation handshake — a real session.hello round-tripped over the handed-off
fd with no bind() ever called inside the daemon for that run. velox.service
deliberately skips ProtectSystem=/ProtectHome=/ReadWritePaths=: saveTo.allowedRoots is
user-configurable to anywhere on the filesystem, and a sandbox here would turn a
legitimately-configured save location into an opaque EROFS/EACCES instead of the
daemon's own clear -32011.

cli/man/velox.1 documents the CLI as it actually exists today (add/ls/pause/resume/rm,
--json) — the queue/settings subcommands AGENT-DAEMON.md's build order originally
sketched aren't implemented in cli/src/main.cpp yet, so the page doesn't claim they
are. Checked warning-free with groff -mandoc -ww -z.

Full ctest: 57/57 (excluding the pre-existing, unrelated conformance failure noted in
earlier commits).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GRDjHGgpYmMoPE2UFbe7pP
2026-09-12 22:46:52 +04:00

3.9 KiB
Raw Blame History

Agent brief — DAEMON (veloxd)

Starts with CORE. You are the only process that owns state.

You own

daemon/**    cli/**    nmhost/**    packaging/nativehost/**

Read contracts/, core/include/, docs/. Never write in core/, gui/, or extension/.

Read first

docs/01-architecture.md §2–§5, contracts/README.md, docs/05-extension-spec.md §4 (you implement the daemon half of pairing).

Build order

  1. RPC serverrpc/uds_server (NDJSON over $XDG_RUNTIME_DIR/velox/velox.sock, 0600, SO_PEERCRED same-UID check) and rpc/ws_server (bind 127.0.0.1 only, first free port in 5200052016, write the chosen port to ws.port). One dispatcher, generated from contracts/. Never block the RPC loop — disk and network work goes to CORE's pools.
  2. Auth & pairingsession.pair triggers a user prompt (GUI dialog if connected, else a desktop notification with actions). Tokens: 256-bit, stored hashed, per-install, revocable. Failed-auth rate limit 5/min then 60 s lockout. Enforce x-transports and x-privileged from the schema: privileged methods are refused over WS with -32003.
  3. Store — SQLite WAL. Tables: tasks, segments, categories, queues, rules, settings, history, pairings. Numbered migrations in store/migrations/, applied at startup, with a forward-only test from every released schema version. Credentials never go in SQLite — Secret Service via libsecret.
  4. Scheduler & queues — concurrency governor (global max active, per-queue max, per-host caps), time windows, days-of-week, one-shot vs periodic, "when queue completes" actions.
  5. Event fan-out — per-subscription filtering, and event.task.progress batched at ≤ 4 Hz into a single array message. Do not emit one message per task per tick; that is how you turn 20 downloads into a GUI that burns a core.
  6. Capture endpointcapture.offer must answer within 750 ms, always. Apply the rules table, resolve the category folder, dedupe against active tasks, return take/ignore. If anything internally is slow, answer ignore and let Firefox have it. Never make the browser wait.
  7. Integration — systemd user units (velox.service + velox.socket for socket activation), single-instance lock, XDG autostart, org.freedesktop.Notifications, graceful shutdown that flushes buffers and meta files.
  8. velox CLIadd, ls, pause, resume, rm, queue, settings, --json output. Build this early: it is how you test the daemon before the GUI exists.
  9. velox-nmhost — 4-byte-length-prefixed stdio ⇄ Unix socket pump. Under 300 lines, zero business logic, and it must exit cleanly when Firefox closes the pipe. Install manifests to the three real locations in docs/05 §4 / docs/adr/0003 — not four: spike S1 found ~/snap/firefox/common/.mozilla/native-messaging-hosts/ (the intuitive "inside the snap" path) is not actually read by snap Firefox, and corrected docs/05 §4 down from its original four-location list. packaging/nativehost/README.md has the current table.

Definition of done (M1)

  • Passes the full conformance suite as a server, over both transports.
  • Kill and restart the daemon mid-download: all tasks reload with correct state and resume.
  • 1 000 tasks in the DB: download.list with paging under 50 ms.
  • Pairing flow works from a real Firefox extension; unpair revokes immediately.
  • systemctl --user status velox clean; socket activation verified from cold.
  • Security review passed: no bind beyond loopback, no path traversal in saveDir (canonicalize and check against allowed roots → -32011), no plaintext secrets.

Do not

  • Do not put download logic here — that's CORE. You schedule and persist; CORE transfers.
  • Do not invent protocol fields. File a request with PROTO.