Files
vdm/contracts/README.md
T
samiandClaude Opus 5 8bb683b09d scaffold: project structure, wire contract, roadmap and agent briefs
Lays out Velox Download Manager (IDM-class download manager for Ubuntu
26.04) as a monorepo ready for parallel lane development. No implementation
code by design.

- docs/: architecture, roadmap M0-M7, IDM-parity GUI spec, engine design,
  Firefox extension spec, risks/spikes, packaging
- contracts/: wire-contract skeleton (JSON Schema + fixture templates) —
  the single synchronization point between lanes
- docs/agents/: one brief per lane (PROTO, CORE, DAEMON, GUI, EXT, PKG/QA)
  with owned directories, build order and definition of done
- CLAUDE.md: rules of engagement — lane ownership, layering, non-negotiables
- CMake scaffolding with dev/tsan/release/ci presets

Two environment findings shape the design: Firefox here is the Mozilla snap
(native-messaging risk, so the extension carries a loopback-WebSocket
fallback), and Wayland forbids passive clipboard monitoring (so clipboard
capture is explicit-action-first).

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-09 18:21:11 +04:00

6.2 KiB

contracts/ — the wire contract

This directory is the interface between every lane. Owner: agent PROTO. Nobody else commits here. Everybody else generates from here.

contracts/
├── VERSION                     # protocol semver, e.g. 1.0.0
├── openrpc.json                # human-readable API doc (generated from schema/)
├── schema/
│   ├── envelope.schema.json    # JSON-RPC 2.0 envelope + our error codes
│   ├── types/                  # Task, Segment, Category, Queue, Settings, CaptureOffer…
│   ├── methods/                # one file per method: params + result
│   └── events/                 # one file per server→client notification
├── fixtures/                   # golden request/response pairs, replayed by conformance
└── codegen/
    ├── gen_cpp.py              # → core/generated/  (structs + to_json/from_json)
    └── gen_ts.py               # → extension/src/shared/protocol/ (types + client)

Rules

  1. Generated code is committed. No lane may be blocked because it can't run Python.
  2. Hand-editing generated files is a merge blocker. Fix the schema and regenerate.
  3. Every method needs at least one fixture — a success case and, where meaningful, an error case. A method with no fixture is not done.
  4. Versioning: adding an optional field or a new method → minor bump. Removing, renaming, retyping, or changing a default → major bump and a written migration note in docs/adr/. session.hello rejects a major mismatch with error -32001 and a message the GUI renders as "Velox needs updating".
  5. Changes arrive as a PR to contracts/ alone, containing: schema edit + fixtures + regenerated code + VERSION bump. Lanes rebase onto it. This is the only synchronization point in the whole project — keep it cheap and frequent rather than big and rare.

Transport framing

Client Transport Framing
GUI, CLI $XDG_RUNTIME_DIR/velox/velox.sock newline-delimited JSON (NDJSON)
nmhost ← Firefox stdio 4-byte little-endian length prefix (Firefox's format)
nmhost → daemon same Unix socket NDJSON
Extension (fallback) ws://127.0.0.1:520xx one JSON message per WS text frame

All four carry the same JSON-RPC 2.0 payloads. The framing differences stop at the transport layer; no method behaves differently depending on how it arrived — except that methods marked "privileged": true in the schema are refused over the WebSocket transport.

Method surface (v1.0.0 target — expand only via PR)

Session

Method Params → Result
session.hello {clientType, clientName, protocolVersion, token?}{daemonVersion, protocolVersion, capabilities[], sessionId}
session.pair {clientName, extensionId}{token, expiresAt} (WS only; triggers user prompt)
session.subscribe {events[]}{ok}

Downloads

Method Params → Result
download.probe {url, headers?, cookies?, referrer?, userAgent?}{filename, sizeBytes?, mime, resumable, effectiveUrl, suggestedCategoryId}
download.add {url, headers?, cookies?, referrer?, userAgent?, filename?, saveDir?, categoryId?, segments?, bufferBytes?, startMode:"now"|"later"|"queue", queueId?, description?, checksum?}{taskId, state}
download.addBatch {items[], defaults}{taskIds[]}
download.list {filter?, sort?, offset?, limit?}{total, items: TaskSummary[]}
download.get {taskId}TaskDetail (includes segments[])
download.start | .pause | .resume | .cancel {taskIds[]}{updated[]}
download.remove {taskIds[], deleteFile:bool}{removed[]}
download.update {taskId, patch:{filename?, saveDir?, categoryId?, queueId?, description?, segments?, bufferBytes?}}TaskSummary
download.refreshUrl {taskId, url, headers?}{ok} (IDM's "Refresh Download Address")

Organisation

category.list · category.upsert · category.remove · queue.list · queue.upsert · queue.start · queue.stop · queue.reorder · rules.list · rules.upsert · schedule.get · schedule.set

Settings & limits

settings.get {keys?} · settings.set {values} · limiter.get · limiter.set {globalBps?, enabled}

Browser integration

Method Notes
capture.offer {url, method, headers, cookies, contentType?, contentLength?, contentDisposition?, tabUrl, filename?}{action:"take"|"ignore", taskId?, reason?}must answer within 750 ms; the extension gives up and lets Firefox handle it otherwise
capture.getRules Extension mirrors the daemon's monitored types so the two never disagree
media.listVariants {manifestUrl, headers}{variants:[{id,resolution,bitrate,codec,sizeEstimate}]}
media.addVariant {manifestUrl, variantId, ...addParams}{taskId}

Grabber

grabber.start {startUrl, depth, includePatterns[], excludePatterns[], fileTypes[]}{jobId}; grabber.status {jobId}; grabber.harvest {jobId, select[]}{taskIds[]}

Events (server → client notifications)

Event Payload
event.task.added / .removed {taskId, summary?}
event.task.state {taskId, state, error?}
event.task.progress Batched array, emitted at ≤4 Hz: [{taskId, downloaded, speedBps, etaSec, segments:[{i,completed,speedBps}]}]
event.speed.global {downBps, activeCount}
event.auth.required {taskId, host, realm, scheme}
event.notify {level, title, body, taskId?}
event.settings.changed {keys[]}
event.grabber.progress {jobId, found, crawled, done}

Error codes

Code Meaning
-32600/-32601/-32602/-32603 Standard JSON-RPC
-32001 Protocol major version mismatch
-32002 Not paired / invalid token
-32003 Method not permitted on this transport
-32010 Task not found
-32011 Invalid destination path (outside allowed roots, or not writable)
-32012 Disk full
-32013 Probe failed (with data.httpStatus)
-32014 Rate limited (pairing brute-force lockout)