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
3.9 KiB
3.9 KiB
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
- RPC server —
rpc/uds_server(NDJSON over$XDG_RUNTIME_DIR/velox/velox.sock, 0600,SO_PEERCREDsame-UID check) andrpc/ws_server(bind127.0.0.1only, first free port in 52000–52016, write the chosen port tows.port). One dispatcher, generated fromcontracts/. Never block the RPC loop — disk and network work goes to CORE's pools. - Auth & pairing —
session.pairtriggers 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. Enforcex-transportsandx-privilegedfrom the schema: privileged methods are refused over WS with-32003. - Store — SQLite WAL. Tables:
tasks,segments,categories,queues,rules,settings,history,pairings. Numbered migrations instore/migrations/, applied at startup, with a forward-only test from every released schema version. Credentials never go in SQLite — Secret Service via libsecret. - 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.
- Event fan-out — per-subscription filtering, and
event.task.progressbatched 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. - Capture endpoint —
capture.offermust answer within 750 ms, always. Apply the rules table, resolve the category folder, dedupe against active tasks, returntake/ignore. If anything internally is slow, answerignoreand let Firefox have it. Never make the browser wait. - Integration — systemd user units (
velox.service+velox.socketfor socket activation), single-instance lock, XDG autostart,org.freedesktop.Notifications, graceful shutdown that flushes buffers and meta files. veloxCLI —add,ls,pause,resume,rm,queue,settings,--jsonoutput. Build this early: it is how you test the daemon before the GUI exists.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 indocs/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 correcteddocs/05§4 down from its original four-location list.packaging/nativehost/README.mdhas 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.listwith paging under 50 ms. - Pairing flow works from a real Firefox extension; unpair revokes immediately.
systemctl --user status veloxclean; 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.