Files
alkblobs/src/dispatch.rs
T
glm-5.3-flash f1f505a910 feat(store): the read paths — get/stat/read_range/has/list on the fall-through rule
- Public `(len, stream)` shape: `Store::get` returns
  `Option<(u64, Blob)>`; `Blob` (src/facade.rs) is a bounded, re-entrant
  windowed reader over the new crate-internal `AnyCursor` tier dispatch
  (mem materializing / local pread body families) — engine cursors
  never leak onto the surface; no chunk-iterator machinery.
- Verification-on-get pinned against ADR-006: kv-tier materialized
  reads whole-blob hash-check at get time (mismatch = Err(Verification),
  never a further fall-through); the large tier's streaming open is
  metadata + handle only (put-path verification + slice digests are its
  story); `read_range` carries slice digests only, on both tiers.
- `stat` rides the trait's `size` probe (never a content load) into
  `EntryMeta { len, algorithm }`; `has` is the pure existence probe;
  both fall through kv -> large like every key-addressed op (ADR-012
  §6.1) — consult order, not routing (never consults `tier_for`).
- Miss shapes enforced (the error module's behavioral note): both-tier
  misses are Ok(None) on get/stat, false on has, Err(Missing) on
  read_range; `src/error.rs` wave-2 deferral notes updated to enforced
  state.
- `list` decided Vec<Key>: materialized, sorted total-order union of
  both tiers (the sweep consumes the engines' enumeration directly);
  malformed pool bytes are fatal `Err(KeyInvalid)`, never a silent
  skip.
- `KeyInvalid` at the facade boundary: ops take opaque key bytes and
  convert via `Key::from_bytes` once (`boundary_key`); engines never
  see typed keys.
- Twelve read-path tests: virgin-store shapes per mode (dual-tier,
  mem-only, kv-only), the `(len, stream)` windows, fall-through,
  kv-first (decoy content in the large tier), both verification
  postures, list union/sort/pool-fatality, and malformed-key
  rejection on every op.

Task: tasks/store-read-paths.md (design points recorded in Notes for
the wave-2 review gate).

Verification: cargo test (default), cargo test --features mem, cargo
test --all-features green; cargo clippy --all-targets -- -D warnings
green in all three feature shapes; cargo fmt --check green; cargo doc
--no-deps clean beyond the pre-existing private-intra-doc-links class.
2026-10-11 22:28:01 +00:00

124 lines
5.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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
//! 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
//! [`DEFAULT_DISPATCH_THRESHOLD`] is the shipped default, 128 KiB, the
//! conservative edge of the measured ~128–256 KiB crossover zone (POC #3
//! A4). Purity is the ADR-003 invariant: same content ⇒ same length ⇒
//! same tier, re-puts are deterministic, no content ever migrates.
//!
//! The threshold is **constructor-tunable** (a per-deployment media
//! re-tuning, never an API change): the facade's mode constructors take
//! it explicitly and default their docs to this constant. Read-side
//! consult order — kv first, then large — is the facade's fall-through
//! general rule (ADR-012 §6.1), carried on the `Store` facade type
//! ([`crate::store`]); mode-specific behavior (kv-only's put-time
//! rejection, mem-only's no-threshold posture) lives on the facade's
//! mode machinery, not here: this module routes lengths, it does not
//! own modes.
//!
//! The `#![allow(dead_code)]` below marks the seam's scope: `route` is
//! driven by the facade's write-side tier selection (`Store::tier_for`),
//! whose own caller is the put seam (store-put-paths); the allow shrinks
//! as those land. Read-side ops do not consult `route` — the
//! fall-through consult order is the facade's (kv first, then large;
//! ADR-012 §6.1).
#![allow(dead_code)]
/// The default dispatch threshold in bytes: 128 KiB — the conservative
/// edge of the measured ~128–256 KiB crossover zone (ADR-003; POC #3
/// finding A4's first-party benchmark). Constructor-tunable per
/// deployment media; retuning is a constructor parameter, never an API
/// change.
pub const DEFAULT_DISPATCH_THRESHOLD: u64 = 128 * 1024;
/// The two tiers a content length routes between. `pub(crate)`: the
/// enum is the routing *answer*, not the engine — tier names the
/// routing class (ADR-011 §1), and which engine backs the tier is the
/// constructor's choice, never this type's business.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Tier {
/// The kv tier — at-or-below-threshold content.
Kv,
/// The large tier — over-threshold content.
Large,
}
/// The pure routing function: over the threshold goes to the large
/// tier; **at or below** (exactly-at-threshold included) stays on the
/// kv tier. The boundary reading is pinned here and boundary-tested
/// below — `store-put-paths` and the read paths consume this exact
/// reading through the facade's seam.
///
/// Total purity discipline (ADR-003): a pure function of `len` — no
/// engine input, no namespace, no state. Same length is the same tier
/// forever, across mixed put paths, re-puts, and restarts.
pub(crate) fn route(len: u64, threshold: u64) -> Tier {
if len > threshold {
Tier::Large
} else {
Tier::Kv
}
}
#[cfg(test)]
mod tests {
use super::*;
/// The shipped default is 128 KiB — pinned numerically (131072).
#[test]
fn default_threshold_is_128_kib() {
assert_eq!(DEFAULT_DISPATCH_THRESHOLD, 131_072);
assert_eq!(DEFAULT_DISPATCH_THRESHOLD, 128 * 1024);
}
/// The pinned boundary reading: exactly-at-threshold routes to the
/// kv tier (≤ threshold → kv); one over routes large.
#[test]
fn exactly_at_threshold_routes_kv_one_over_routes_large() {
assert_eq!(route(4096, 4096), Tier::Kv);
assert_eq!(route(4095, 4096), Tier::Kv);
assert_eq!(route(4097, 4096), Tier::Large);
}
/// Boundary values across the domain: zero length, the default
/// threshold, and the degenerate tunings a constructor may pass —
/// all route deterministically.
#[test]
fn boundary_values_route_deterministically() {
assert_eq!(route(0, DEFAULT_DISPATCH_THRESHOLD), Tier::Kv);
assert_eq!(route(131_071, DEFAULT_DISPATCH_THRESHOLD), Tier::Kv);
assert_eq!(route(131_072, DEFAULT_DISPATCH_THRESHOLD), Tier::Kv);
assert_eq!(route(131_073, DEFAULT_DISPATCH_THRESHOLD), Tier::Large);
assert_eq!(route(0, 0), Tier::Kv, "zero-length content always kv");
assert_eq!(
route(1, 0),
Tier::Large,
"threshold 0: everything else large"
);
assert_eq!(route(0, u64::MAX), Tier::Kv, "max threshold: nothing large");
assert_eq!(
route(u64::MAX, u64::MAX),
Tier::Kv,
"at-threshold is kv even at MAX"
);
}
/// Purity (the ADR-003 invariant): the same length is the same tier
/// on every call — routing derives from inputs alone.
#[test]
fn routing_is_a_pure_function_of_length() {
let len = 103_487;
let first = route(len, DEFAULT_DISPATCH_THRESHOLD);
for _ in 0..32 {
assert_eq!(route(len, DEFAULT_DISPATCH_THRESHOLD), first);
}
}
}