diff --git a/src/backend.rs b/src/backend.rs index 98cdbf5..5ca9b59 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -242,10 +242,16 @@ pub(crate) trait Backend: Send + Sync { /// EOF-clamping arithmetic of the cursor contract. Crate-internal: the /// engine cursor bodies behind [`Backend::Cursor`] compose this, so every /// engine's materializing and staging bodies clamp identically. +/// +/// The arithmetic runs in `u64` — the offset/len domain — and narrows to +/// `usize` only after clamping, so a truncated `as usize` cast can never +/// wrap a huge offset back onto a small slice position on narrow-pointer +/// targets. pub(crate) fn clamped(offset: u64, len: u64, total: usize) -> (usize, usize) { - let start = (offset as usize).min(total); - let end = start.saturating_add(len as usize).min(total); - (start, end) + let total = total as u64; + let start = offset.min(total); + let end = start.saturating_add(len).min(total); + (start as usize, end as usize) } #[cfg(test)] @@ -333,6 +339,16 @@ mod tests { Vec::::new(), "read at EOF is empty, never an error" ); + assert_eq!( + cursor.read_at(u64::MAX, 5).await.unwrap(), + Vec::::new(), + "an overflowing u64 offset clamps to EOF in u64 arithmetic — never wraps" + ); + assert_eq!( + cursor.read_at(0, u64::MAX).await.unwrap(), + whole, + "an overflowing u64 length clamps to the whole body" + ); assert_eq!( cursor.read_at(0, 0).await.unwrap(), Vec::::new(), diff --git a/src/dispatch.rs b/src/dispatch.rs index 105954c..4dc30df 100644 --- a/src/dispatch.rs +++ b/src/dispatch.rs @@ -1,7 +1,7 @@ //! Size-threshold dispatch between the kv and large tiers (ADR-003): //! routing as a pure function of content length. //! -//! **The routing seam the facade programs through.** [`route`] is the +//! **The routing seam the facade programs through.** `route` is the //! pure function — `route(len, threshold) → Tier`, **at-or-below the //! threshold stays on the kv tier** (the boundary's pinned reading: //! store-put-paths' "≤ threshold → kv; > threshold → large") — and diff --git a/src/error.rs b/src/error.rs index b2a89b4..2636792 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,11 +3,12 @@ //! **One enum, both altitudes.** This is a *single* enum serving both //! altitudes of the crate's surface. The [`Backend`][crate::backend] //! trait's methods return this family but its *effective* members are -//! [`Missing`], [`Verification`], [`Io`], and [`KeyInvalid`] — at the -//! trait boundary, keys arrive opaque (so `KeyInvalid` never fires +//! `Error::Missing`, `Error::Verification`, `Error::Io`, and +//! `Error::KeyInvalid` — at the +//! trait boundary, keys arrive opaque (so `Error::KeyInvalid` never fires //! there) and GC delete-arbitration answers flow up as data, not as -//! errors (so `GcRefuse` never fires below the facade). The facade's -//! public surface re-exposes this same family with [`GcRefuse`] added: +//! errors (so `Error::GcRefuse` never fires below the facade). The facade's +//! public surface re-exposes this same family with `Error::GcRefuse` added: //! the facade decides and owns the refusal. The altitude discipline is //! carried by doc comments and construction discipline, not by two //! separate types (named design point audited at the review gate; diff --git a/src/local.rs b/src/local.rs index 250f8dd..93a6a47 100644 --- a/src/local.rs +++ b/src/local.rs @@ -17,7 +17,7 @@ //! (`Error::Io` naming the path): a malformed list is as deadly as an //! incomplete one (ADR-003), and it is silent until a sweep deletes //! the wrong things. Only the engine's own staging directory -//! ([`staging_dir`], excluded from the walk) is a known non-shard +//! (`staging_dir`, excluded from the walk) is a known non-shard //! entry. //! //! **Concurrency posture:** every file operation — constructor, @@ -32,9 +32,10 @@ //! **Stage hygiene (store-api.md invariant 3):** publish is the //! commit-rename; every failure path converges on discard with zero //! residue — a failed commit removes the stage file itself (there is -//! no post-failure discard to drive), an explicit [`StagingCursor::discard`] -//! removes it, and a staging cursor dropped without either is -//! best-effort-cleaned by [`Drop`] (the engine's own cancellation +//! no post-failure discard to drive), an explicit +//! `StagingCursor::discard` removes it, and a staging cursor dropped +//! without either is best-effort-cleaned by [`Drop`] (the engine's own +//! cancellation //! story, per the wave-1 review's recorded observation; the hygiene //! tests below cover all three forms exactly). CAS is first-writer-wins //! everywhere a publish meets an existing entry: the committed file for @@ -43,7 +44,7 @@ //! //! The `#![allow(dead_code)]` below marks the narrow remaining scope: //! in default builds (no `mem`) nothing constructs the engine, so -//! [`LocalBackend::new`]/the staged-put bodies have no caller until +//! `LocalBackend::new`/the staged-put bodies have no caller until //! wave 3's facade constructors and `store-put-paths` name them. #![allow(dead_code)] diff --git a/src/sqlite.rs b/src/sqlite.rs index 9295e55..e0202d7 100644 --- a/src/sqlite.rs +++ b/src/sqlite.rs @@ -7,7 +7,7 @@ //! //! **Schema (POC #5 finding B5's row shape adapted to sqlite):** //! `blobs (key BLOB PRIMARY KEY, value BLOB, size INTEGER NOT NULL)`. -//! The dedicated `size` column is what makes [`Backend::size`] +//! The dedicated `size` column is what makes `Backend::size` //! metadata-only — `SELECT size … WHERE key = ?` rides the key's index //! and never loads the value blob (ADR-008's size discipline; //! plan-tested below). `has` is an EXISTS probe, never a value load. diff --git a/src/store.rs b/src/store.rs index bb2e3a0..c46ea48 100644 --- a/src/store.rs +++ b/src/store.rs @@ -19,8 +19,8 @@ //! would leak engine types through the public surface, force consumers //! to name engines they must never see (the facade is **engine-blind**, //! ADR-007/008/010), or push `dyn` onto the surface. The concrete type -//! holds two crate-internal engine-holding enums ([`KvEngine`], -//! [`LargeEngine`]) dispatching over the closed five-engine set; the +//! holds two crate-internal engine-holding enums (`KvEngine`, +//! `LargeEngine`) dispatching over the closed five-engine set; the //! conformance factory's binding note anticipated exactly this collapse //! (tests/conformance/factory.rs — "most plausibly the associated type //! collapsing to the concrete facade type across all factories"). The @@ -34,7 +34,7 @@ //! **Constructor modes** (ADR-011 §4; ADR-012 §6.3): //! //! - **dual-tier** (the default): kv + large, size-threshold dispatch -//! via [`crate::dispatch::route`]. The wave-2 constructible shape is +//! via `crate::dispatch::route`. The wave-2 constructible shape is //! `kv = mem` + `large = local` — the dispatch-coverage test shape //! ADR-012 §6.3 names (both tiers load-bearing); the wave-3 //! constructible shape is `kv = sqlite` + `large = local` — the @@ -61,9 +61,9 @@ //! **Read-path design points (recorded for the review gate):** //! //! - **The fall-through general rule (ADR-012 §6.1; POC #1 finding -//! 6).** Every key-addressed op consults [`KvEngine`] first, then -//! [`LargeEngine`] — there is no length routing on the read side and -//! [`Store::tier_for`] is never consulted by it (the dispatch seam's +//! 6).** Every key-addressed op consults `KvEngine` first, then +//! `LargeEngine` — there is no length routing on the read side and +//! `Store::tier_for` is never consulted by it (the dispatch seam's //! write-half routing is the put path's; `route` feeds nothing on //! reads). A miss on both tiers resolves per op's shape: `Ok(None)` //! on get/stat, `false` on has, `Err(Missing)` on read_range. @@ -71,14 +71,14 @@ //! returns the pinned `(len, stream)` shape as //! `Option<(u64, Blob)>` (store-api.md §Read path) — [`Blob`] is a //! bounded, re-entrant windowed reader over the crate-internal -//! [`AnyCursor`], which dispatches the engine cursor bodies by tier +//! `AnyCursor`, which dispatches the engine cursor bodies by tier //! (mem's materializing body for kv, `local`'s pread body for //! large). Windowed reads are the uniform stream shape across //! tiers; no chunk-iterator machinery was adopted — the cursor //! bodies *are* the streaming abstraction, and per-chunk `Stream`- //! wrapping would re-shape them without changing the underlying //! reads. Engine cursors never leak onto the public surface: -//! [`AnyCursor`] is `pub(crate)`, `Blob` is opaque but drivable +//! `AnyCursor` is `pub(crate)`, `Blob` is opaque but drivable //! (constructed only by the store core, ADR-010 §1). //! - **Verification-on-get (named design point, pinned against //! ADR-006's text).** The whole-blob hash-check fires where the @@ -190,11 +190,12 @@ //! machinery. //! //! **The dispatch seam the ops tasks program through:** tier selection -//! for a given length ([`Store::tier_for`] composing -//! [`crate::dispatch::route`] per mode), the fall-through consult +//! for a given length (`Store::tier_for` composing +//! `crate::dispatch::route` per mode), the fall-through consult //! order — **kv first, then large** — that every key-addressed op -//! consumes (ADR-012 §6.1's general rule; [`Store::kv`] then -//! [`Store::large`]), and the GC state home ([`crate::gc::GcHome`]) +//! consumes (ADR-012 §6.1's general rule; `Store::kv` then +//! `Store::large`), and the GC state home (`GcHome`, +//! crate-internal) //! the gc-protocol task fills. //! //! **The durable delete window hosts on the kv engine when it is @@ -208,7 +209,7 @@ //! form**, ADR-008 §2 staged re-arbitrated delete; ADR-012 §6.7 the //! executor is the next sweep) stages its candidates on the kv engine //! the constructor built. The form branch keys off the engine -//! representation ([`Store::window_host`] — one kv-engine-enum match +//! representation (`sweep_window`'s one kv-engine-enum match //! arm, never a flag): the durable form runs where the host is //! SQL-backed, the inline form verbatim on mem/local, so wave 5's //! pg-lo dormancy (the dual-SQL instance's large-engine window clauses @@ -275,7 +276,7 @@ pub const DEFAULT_DELETE_WINDOW_DELAY: Duration = Duration::from_secs(5 * 60); /// Engine-blind by construction: the engines are held in crate-internal /// enums dispatched statically (see the module's engine-representation /// design point); consumers name modes only. The GC state home -/// ([`crate::gc::GcHome`]) rides as a field — the store instance owns +/// (`GcHome`, crate-internal) rides as a field — the store instance owns /// all GC state (ADR-010 §2), which the gc-protocol task fills. pub struct Store { /// The kv-tier engine — the first consult in every fall-through.