feat(store): the put seam — two internal paths, the three numbered rules, Batch, kv-only rejection

- `put(len: Option<u64>, stream: impl AsyncRead + Unpin + Send) -> (Key, Pin)`:
  known-length one-pass flow; unknown-length buffer-to-threshold flow with
  the mid-stream fresh-stage flush (ADR-003 option (d), three numbered rules:
  flush-and-continue, derivation over the eventual stored artifact as a
  whole via buffer form and StagingCursor read-back form, zero residue on
  stream error)
- staged-put seam amended (operator sign-off): `put_staged()` keyless
  handout, `StagingCursor::commit(self, key)` publishes under the
  store-core-resolved key — the canonical key completes at stream end and
  the wave-1 handout shape could not express the commit-rename flow
- pin-before-publish rides the arbitration lock on both paths and both
  tiers; dedup re-puts refcount the existing entry
- Batch seam: `Store::batch()` + `put_in_batch()`, members publish pin-
  before-publish, group pins drop together on `Batch::commit` (new public
  verb) or drop
- kv-only rejection (ADR-012 §6.2): known-length over-threshold rejects
  immediately (stream never read), unknown-length at mid-stream overflow;
  zero residue; error carrier `Io(String)` (named design point)
- declared-len mismatch rejects `Verification` zero-residue (named design
  point); stream shape generic-no-dyn (named design point)
- incremental BlobHasher (pinned byte-equal to Algorithm::derive);
  AnyStaging cursor dispatch; git-interop put-path leg against
  git hash-object; eleven put-path behavior tests

Verification: cargo test (default/--features mem/--all-features) green;
clippy --all-targets -D warnings green in all three feature shapes;
cargo fmt --check green; cargo doc --no-deps at the 24-warning baseline.
This commit is contained in:
glm-5.3-flash committed 2026-10-11 23:00:14 +00:00
1 parent 889be4cada
commit 539dabcbf7
8 files changed
+1723 -105

No files matched your search

+74
View File
@@ -162,6 +162,80 @@ fn external_oid_round_trips_into_pool_keys() {
}
}
/// The facade put seam against git (store-api.md §Invariants 1's
/// source-of-truth leg): both internal put paths (known-length,
/// unknown-length) key entries git-identically on both tiers, and the
/// get path round-trips git's content byte-exactly. mem-gated: the
/// wave-2 constructible shape (`dual_tier_mem_local`).
#[cfg(feature = "mem")]
#[tokio::test]
async fn put_seam_keys_match_git_hash_object_on_both_tiers_and_paths() {
use std::io::Cursor;
use alkblobs::Store;
let root = scratch_dir("store-root");
let store = Store::dual_tier_mem_local(root.join("root"), 32)
.await
.expect("dual-tier store constructs");
let small = b"small git-interop blob";
let big = (0..200).map(|i| (i % 253) as u8).collect::<Vec<u8>>();
let (repo, oid) = git_hash_object(small, true);
drop_dir(&repo);
let expected_small: Key = Digest::from_git_oid_hex(&oid)
.expect("git oid decodes")
.into();
let (repo, oid) = git_hash_object(&big, true);
drop_dir(&repo);
let expected_big: Key = Digest::from_git_oid_hex(&oid)
.expect("git oid decodes")
.into();
let (key, pin) = store
.put(Some(small.len() as u64), Cursor::new(&small[..]))
.await
.expect("known-length put");
assert_eq!(key, expected_small, "kv put keys git-identical");
drop(pin);
let (key, pin) = store
.put(None, Cursor::new(&small[..]))
.await
.expect("unknown-length put");
assert_eq!(key, expected_small, "buffered form keys git-identical");
let (len, blob) = store
.get(&expected_small.as_bytes())
.await
.expect("get succeeds")
.expect("entry present");
assert_eq!(blob.read_at(0, len).await.unwrap(), &small[..]);
drop(pin);
let (key, pin) = store
.put(Some(big.len() as u64), Cursor::new(&big[..]))
.await
.expect("known-length large put");
assert_eq!(key, expected_big, "staged put keys git-identical");
drop(pin);
let (key, pin) = store
.put(None, Cursor::new(&big[..]))
.await
.expect("unknown-length large put");
assert_eq!(
key, expected_big,
"staged-bytes derivation keys git-identical"
);
let (len, blob) = store
.get(&expected_big.as_bytes())
.await
.expect("get succeeds")
.expect("entry present");
assert_eq!(blob.read_at(0, len).await.unwrap(), big);
drop(pin);
let _ = fs::remove_dir_all(root);
}
/// Tagged-key coexistence (POC finding 5): git oids of both formats
/// address the same flat key universe from one external producer.
#[test]