# `libveloxcore` — public API **Status: M1 in progress.** `util/`, `net/` (http_client, probe, url, content_disposition), `io/` (sparse_file, write_buffer), `meta/veloxpart`, `segment/` (segmenter, budget), `rate/` (token_bucket), `task/`+`engine.hpp` (the download engine itself — `vdm::Engine`, `DownloadSpec`/`DownloadHandle`/`DownloadCallbacks`), and `rules/` (filename sanitization, collision policy, rule-table matching) are landed. `media/` is M4, not started — see [`core/docs/engine-api-m1.md`](../../docs/engine-api-m1.md) for the engine API's own DAEMON-review history. Layering (CLAUDE.md §3): this library knows nothing about JSON, SQL, Qt, or RPC. Input is a spec value; output is bytes on disk plus typed callbacks. DAEMON projects engine state onto the wire contract's `TaskSummary` / `TaskDetail` / events — see `core/docs/proto-requests-m1.md` for the shapes that projection needs frozen. Every header under `core/include/vdm/` compiles standalone (`-Wall -Wextra -Wpedantic -Werror`, C++23). Clean under ASan/UBSan and TSan. --- ## `util/` — foundations ### `vdm/util/error.hpp` `enum class Error` — the engine-wide failure taxonomy (network / HTTP / content / local I/O / metadata / probe / retry / internal). This is CORE's own vocabulary; it is **not** a wire type. `error_name(Error)` gives a stable snake_case string; `is_retryable(Error)` is the advisory retry hint the task policy consults. `struct ErrorInfo { Error code; std::string context; int http_status; bool retryable; Error cause; }` — the payload carried by every failed `Result`. `.to_string()` renders `": (HTTP )"`. ### `vdm/util/result.hpp` `Result` — return-based error channel, a thin wrapper over `std::expected`. Errors are **returned, never thrown**, on anything that runs during a transfer. - `Result r = 42;` / `Result r = Err{Error::timeout, "..."};` / `Result r = Error::not_found;` - `r.has_value()`, `explicit operator bool`, `r.value()` / `*r` / `r->`, `r.error()`, `r.code()`, `r.value_or(x)` - monadic `and_then` / `transform` / `transform_error` (forward to `std::expected`) - `Result` specialization; `vdm::ok()` success sentinel - `VDM_TRY(expr)` — return the error if `expr` failed - `VDM_TRY_ASSIGN(auto x, expr)` — bind the value or return the error ### `vdm/util/bytes.hpp` `Byte` / `ByteSpan` / `ConstByteSpan` aliases; `as_bytes(string_view)` / `as_chars(span)`. Little-endian fixed-width codec `load_le` / `store_le` and a bounds-checked sequential `ByteReader` (`.u8/.u16/.u32/.u64`, `.raw(n)`, `.lp_string()`, `.overran()`). Built for the `.veloxpart.meta` reader and the 4-byte NM framing; every read is bounds-checked and latches on overrun (reader-first, fuzz-ready). ### `vdm/util/event_bus.hpp` `EventBus` — typed, thread-safe in-process pub/sub. `subscribe(fn) -> Token`, `publish(ev)` (synchronous, calling thread, registration order), `unsubscribe(Token)`, and RAII `subscribe_scoped` returning a `Subscription`. Handlers may (un)subscribe or publish during dispatch. Handlers must not throw. Not a hot-path structure — progress is coalesced to ≤4 Hz upstream. ### `vdm/util/thread_pool.hpp` `ThreadPool` — fixed-size `std::jthread` pool for bounded off-loop work (hashing, fsync batches, DNS pre-resolve). `submit(fn, args...) -> std::future`; propagates exceptions through the future; drains already-queued tasks on destruction. **Not** the transfer loop — `net/` will own one `curl_multi` per dedicated worker. ### `vdm/util/log.hpp` Sink interface — core does no I/O itself. `LogSink` abstract base; DAEMON installs one via `set_log_sink()`, default discards. `CallbackSink` adapter (with a min-level filter). `VDM_LOG_{TRACE,DEBUG,INFO,WARN,ERROR}(category, fmt, args...)` — `std::format` syntax, only formatted when a sink is installed and wants the level. --- ## `rules/` — filename sanitization, collision policy, rule-table matching Pure functions only: no I/O, no filesystem access, no notion of the wire `Rule` type or its JSON/SQL representation. DAEMON owns the rule table (storage, `rules.upsert`, the generated `Rule` type) and decodes it into the plain structs below before calling in. ### `vdm/rules/filename.hpp` `sanitize_filename(raw, max_bytes = 255)` — turns a raw candidate (from `net::parse_content_disposition` or `net::url_filename`, neither of which is filesystem-safe by design — see their own headers) into one safe to create on ext4, APFS, and NTFS alike: strips separators/control bytes, folds NTFS-illegal characters to `_`, neutralizes reserved Windows device names (`CON`, `COM1`, ...), and clamps length on a UTF-8 boundary. Total: never empty, never throws. **Not** the path-traversal security boundary — that's DAEMON's `fs/safepath`, which runs after this and is the one that matters adversarially. ### `vdm/rules/collision.hpp` `resolve_collision(desired, exists, policy, max_attempts = 1000)` — given an existence predicate (DAEMON supplies a real one; tests supply an in-memory set), finds the next free name Explorer/Finder-style (`"name (1).ext"`, `"name (2).ext"`, ...) under `CollisionPolicy::rename`, or returns `desired` unchanged under `::overwrite`. Never fabricates a guaranteed-unique name past `max_attempts` — returns the last candidate tried and leaves "still colliding" for the caller to treat as a real error. ### `vdm/rules/match.hpp` `match_rules(rules, input) -> optional` — the evaluation half of `contracts/schema/types/Rule.schema.json`: tries rules in ascending `priority` order (ties keep table order), skips disabled rows, returns the first whose every *present* match clause (`extensions`, `mime_types`, `host_pattern`, `url_pattern`, `min_size_bytes`/`max_size_bytes`) is satisfied — an absent clause is not a constraint, and a size clause never matches speculatively when `MatchInput::size_bytes` is still unknown (pre-probe). `std::nullopt` means no rule matched; the caller's own default category applies. `glob_match(pattern, text)` — the `*`/`?` matcher `host_pattern` and `url_pattern` both use, case-insensitive, bounded work even on a pathological all-`*` pattern (iterative, not recursive).