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:
1 parent
889be4cada
commit
539dabcbf7
8 files changed
+1723
-105
No files matched your search
@@ -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]
|
||||
|
||||
Reference in new issue
Block a user