core: add util layer — Result, Error taxonomy, bytes, event bus, pool, log
util/ carries no wire surface, so it lands before the contract freeze. - error: enum class Error, the engine-wide failure taxonomy; is_retryable enumerates every value (no default:) so -Wswitch forces the retry decision on each future addition. ErrorInfo carries context/http_status. - result: Result<T> over std::expected<T, ErrorInfo>, Result<void>, VDM_TRY / VDM_TRY_ASSIGN. Errors returned, never thrown, on the transfer path. - bytes: span aliases, LE load_le/store_le (debug-asserted precondition, not input validation), and a bounds-checked latching ByteReader for the .veloxpart.meta reader. - event_bus: typed thread-safe pub/sub; header states plainly that unsubscribe is not a quiesce point and download_task will need its own drain. - thread_pool: std::jthread pool; dtor joins in the body before members die (fixed a use-after-destruction on cv_/mu_). Header notes shutdown is drain-only and DAEMON will need a cancel mode. - log: sink interface (core does no I/O); DAEMON installs one. Tested: -Werror clean, 6 binaries green under plain / ASan+UBSan / TSan. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01HPPSGhiArbvQgwC2DNiURS
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
# `libveloxcore` — public API
|
||||
|
||||
**Status: M1 in progress.** Only `util/` is landed. The download-facing API
|
||||
(`DownloadSpec`, `DownloadTask`, probe, typed callbacks) arrives with later stages and
|
||||
is reviewed by DAEMON before M2 (AGENT-CORE DoD).
|
||||
|
||||
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
|
||||
`"<name>: <context> (HTTP <n>)"`.
|
||||
|
||||
### `vdm/util/result.hpp`
|
||||
|
||||
`Result<T>` — return-based error channel, a thin wrapper over
|
||||
`std::expected<T, ErrorInfo>`. Errors are **returned, never thrown**, on anything that
|
||||
runs during a transfer.
|
||||
|
||||
- `Result<int> r = 42;` / `Result<int> r = Err{Error::timeout, "..."};` /
|
||||
`Result<T> 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<void>` 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<T>` / `store_le<T>` 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<E>(fn) -> Token`,
|
||||
`publish<E>(ev)` (synchronous, calling thread, registration order), `unsubscribe(Token)`,
|
||||
and RAII `subscribe_scoped<E>` 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<R>`; 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.
|
||||
Reference in New Issue
Block a user