- §Decision 1's invariant walked clean per surface (facade write seam / key decode / wire ingress); the grep audit found no length-routed, default-derivation, or option-typed constructor; §Decisions 2/4/5/6/7 verified line by line; the §Decision 8 litmus walked with one named, dispositioned residue (the schema gate's width enumeration rides a novel-width member's admission ADR — the ADR's own literal text, not the defect class) - the Key::derive decomposition-time reading confirmed (the fold into the explicit Algorithm::derive; Key::from(Digest) the only Key-from-digest route) - inline fixes: put_known_as's mangled duplicate doc sentences removed; the CHANGELOG's [0.2.0] section reordered newest-first (alkcall's convention) - stamps: wave 14 completed in the implementation plan; AGENTS.md's status line updated (the superseded-shape warning retired) and its fuzzing paragraph's key_encoding target description aligned to ADR-020's shrunk surface - the review task completed with the gate record in its notes Verification: cargo test default 156/54/7/2; cargo test --all-features server-less 341/125/7/3 (clean skips) and against the live pglo-poc harness (the pg columns ran for real); cargo clippy --all-targets -- -D warnings and --all-features --all-targets -- -D warnings; cargo fmt --check; cargo doc --no-deps; the fuzz corpus-replay gate 7/7; cargo publish --dry-run --allow-dirty (0.2.0 packages)
13 KiB
Changelog
All notable changes to this crate are documented here. The format is based on Keep a Changelog, and this crate adheres to Semantic Versioning.
0.2.0 - 2026-10-19
The family-wire correction (ADR-020): the wire speaks keys and the facade folds to one derivation-explicit put seam. Two breaks — the wire's and the facade's — shipped together under 0.x's minor-bump semantics, with no shim and no dual-form period. Resolves review-003's coherence finding: the ops surface's addressable classes are now exactly the store's — every registered algorithm, not one family.
Changed
- The digest wire form is tag-prefixed key hex (ADR-020
§Decision 2) — every digest-bearing field on the ops surface
(
blobs/fetch,blobs/stat,blobs/have,blobs/delete,blobs/putoffers) carries the physical key encoding verbatim:[registry byte] + digest bytes, hex-encoded (42 hex for 21-byte keys, 66 hex for 33-byte keys), decoded by the persistence boundary's rejects-never-guesses parse (the tag validated, the digest length checked per tag, never inferred). Bare digest hex is no longer a wire form — no implicit default-family reading survives; the schema gate is^[0-9a-f]{42}$|^[0-9a-f]{66}$. - The put offer's claim is mandatory (ADR-020 §Decision 3) —
blobs/puttakes{namespace, key, len}with bothkeyandlenrequired; the wire never discovers addresses. The claim check has one moment — pre-EOF — for every family; the deferred post-check arm is gone. - The put response echoes the key (ADR-020 §Decision 4, amending
ADR-012 §2) —
{token, key}replaces{token, digest}, so the caller addresses what landed in the vocabulary it offered. Renewal, confirm, andhavesemantics are unchanged. - The op set stays closed (ADR-020 §Decision 7) — payload parameterization under unchanged op names; family support is never family-suffixed op variants.
Added
put_as(algorithm, len: Option<u64>, stream)— the one derivation-explicit put seam (ADR-020 §Decision 5) and the facade's entire write surface: the caller names the derivation, the same bytes have multiple equally valid addresses, one per registry member, and the crate never picks one for its caller.put_in_batchtakes the algorithm parameter. The unknown-length arm (None) is retained on the facade — "stream in, get the key back" stays an in-process posture; only the wire's unknown-length put dies.
Removed
- The canonical-default
putand the family-namedput_git_object(ADR-020 §Decision 5) — one seam replaces both; no canonical default is blessed. - The length-routed constructor
Digest::from_git_oid_hex— family-explicit oid-bytes construction isKey::from_bytesover the physical encoding. - The silent canonical constructors
Digest::deriveandKey::derive— derivation is the explicitAlgorithm::derive.
Verified
- The full verification block against the live harness
(
postgres:16-alpineon localhost:15432): 219 tests pass on the default feature set (mem+sqlite— unit 156, conformance 54, git-interop 7, README examples 2); 476 pass under--all-featuresagainst the live harness (unit 341, conformance 125, git-interop 7, README examples 3 — the pg columns' server-less default is a clean skip); both clippy configurations (--all-targets, and--all-features—-D warnings),cargo fmt --check, andcargo doc --no-depsare clean;cargo publish --dry-run --allow-dirtypackages 0.2.0; the fuzz corpus-replay gate passes (cargo test --manifest-path fuzz/shared/Cargo.toml, 7/7) — campaigns run viafuzz/run-detached.shperfuzz/README.md. The wire's family-explicit payloads are pinned end to end by the wave-14 ops tests (a facade-published family entry fetchable, stat-able, and have-servable over the ops surface; a wire-offered family put landing under its own oid).
0.1.0 - 2026-10-18
Initial release of the alkblobs family: content-addressed blob storage with one canonical content hash, tiered pluggable engines, GC built in, and the network ops surface riding alkcall as a feature.
The crate is the store layer only — no transport, no wire protocol
in-core (ADR-001). Store never learns where bytes came from;
transport, framing, and serialization live above the facade (in the
feature-gated ops module) or nowhere. Keyed under the git-family
oid derivation ("blob <len>\0" + content) — SHA-256 canonically,
SHA-1 tolerated for existing repos (ADR-002) — with the git-object
derivation family for git's non-blob objects (ADR-018) — and
addressed through an algorithm-tagged Key encoding that is the
crate's one-way door: parsers validate and reject, never guess.
Added
- The
Storefacade —put/get/stat/read_range/has/list, with the write verifying the stored artifact as a whole before it publishes and verification-on-get on the kv tier's materialized reads (the windows themselves carry no check;read_rangereturns the ADR-006 slice digest for the caller to carry out-of-band) (ADR-006). Known-length and unknown-length puts share one signature (the unknown-length path buffers past the threshold in memory, then continues into the large tier's stage — a non-restartable source is a normal case, never an error). Nonew: the store is built through named constructors declaring one of three ADR-pinned modes —mem_only,kv_only, ordual_tier— with size-threshold dispatch the pure function routing between a store's kv tier and its large tier (default threshold 128 KiB, constructor-tunable; ADR-003). - Pins and batches — pin-before-publish: an entry cannot appear in
a sweep's view without already carrying its pin. The put's RAII
Pinguard releases on drop;Batchgroups a multi-put write (manifest writes are the shape) under one batch-scoped pin group, committed or dropped together (ADR-005). - The GC protocol —
sweep(mark phase / delete window / commit phase, aborts report as data with nothing deleted), directdelete, and liveness registration in two forms (ADR-005, ADR-008): the in-process callback and, for fleets, the named liveness table the constructor declares. - Five engines over two tiers (ADR-003, ADR-007, ADR-009) —
features gate engines, each pulling only its own driver cluster
(ADR-013); tiers are constructor composition, not features:
- kv
sqlite(featuresqlite, default-on): WAL +synchronous=NORMAL, prepared statements, read paths tolerate the no-tables-yet database; the durable single-host tier. - kv
postgres(featurepostgres): pooled connections, prepared statements,ON CONFLICT DO NOTHINGCAS — the fleet host. - kv
mem(featuremem, default-on): dependency-free ephemeral implementation; never a production story, never fleet-valid — the contract-reference engine every other engine's conformance tests mirror. - large
local(ungated, dependency-free): flat sharded files, stage-then-commit-rename, pread reads; the publish is fsync- durable and no-replace (ADR-015). - large
pg-lo(featurepg-lo): postgres Large Objects — descriptorless windowed reads, LO creation transactional (zero crash orphans), staged into a held transaction; one large-object consumer per database is the co-tenancy constraint theorphan_reap_permitteddeclaration carries (ADR-016).
- kv
- Fleet GC — DB-backed pins, sweep-window rows, and the sweep
lease host on the fleet's kv-tier engine (postgres): the lease is
an atomic test-and-set advisory lock (at most one sweep in flight
per pool; ADR-008, ADR-013); deletes are windowed and
re-arbitrated at execution so the sweep-vs-put race resolves
without data loss; constructors declare
fleet: trueexplicitly (never inferred) against the composite fleet-validity predicate (ADR-012). - The
opsmodule (featureops, default-off) — the network surface riding alkcall (ADR-001):blobs/fetch(the binary data-planeSubop, ranged-fetch addressing over the fanout registry) andblobs/put(the verified offer/stream/commitSinkop), plus the JSON control-op family (stat,have,delete) and the pin-token layer: the token is a CSPRNG capability over the pin, minted custodial (non-fleet) or renewable (fleet), renewed and confirmed through the store core's renewal access — the one crate-internal seam the module consumes. Authorization rides alkcall'sAccessControlseam — no in-band invented scheme (ADR-014, ADR-017). - Fuzzing —
fuzz/, a sidecar workspace excluded from the root workspace, pinning nightly inside itself: two libFuzzer targets over the parse surfaces (key_encodingover the key/algorithm parser cluster,ops_ingressover the ranged-fetch bounds ingress and token decode), and a stable-toolchain companion crate (fuzz/shared) replaying the committed corpora as a plaincargo testgate. - The git-object derivation family — the hash registry extends
with
0x03(git-object-sha-1) and0x04(git-object-sha-256) (ADR-018): the preimage-identity derivation over git's own loose-object form (<type> <len>\0+ content stored as the value), so the digest is byte-identical to git's oid of the object — trees, commits, and tags included — with verification-on-get, dedup, and external-oid addressing intact. Lands as the publicput_git_object(algorithm, len, stream)put seam (known-length only, family members only), threaded through the fleet put paths uniformly, and CLI-validated byte-exact against realgit hash-object. - The token registry's admission posture (ADR-019) — an
admission cap at the mint boundary
(
OpsConfig::token_registry_max_entries, default 65,536 entries,usize::MAXthe uncapped form), refusing — never evicting — with the newError::RegistryFullrefusal member and theblobs/putdeclared answerMINT_REFUSED(the committed digest in the details; no token, no custody taken). Bounds the non-confirming upload loop (unbounded registry growth plus never-expiring custody on a non-fleet store) at the store; the non-fleet custody posture is documented embedder-facing, the fleet TTL named as the safe posture for doors that cannot prove confirm. - Borrowed-key overloads —
Key::key_bytes()returning the fixed-capacityKeyBytes(a stackCopywrapper deref-coercing to&[u8], byte-identical toas_bytes) drops the per-call heap allocation from the serving paths' re-encode sites.
Changed
- sqlite contention posture explicit —
PRAGMA busy_timeout = 5000is set at open as a pinned constant (matching the family's explicit alkstore-sqlite value), joining the durability posture as stated contract: co-tenant assemblies on one sqlite file no longer ride rusqlite's implicit default by coincidence. alkcallbumped to 0.10 (the gateway ownership-seam release; additive for this crate's native-wire use — no code changes).
Verified
- The full verification block against the live harness
(
postgres:16-alpineon localhost:15432): 218 tests pass on the default feature set (mem+sqlite— unit 155, conformance 54, git-interop 7, README examples 2); 473 pass under--all-features— the pg columns run against the live harness (their server-less default is a clean skip); both clippy configurations (--all-targets --all-features,-D warnings),cargo fmt --check, andcargo doc --no-depsare clean; the fuzz corpus-replay gate passes (cargo test --manifest-path fuzz/shared/Cargo.toml, 7/7) — campaigns run viafuzz/run-detached.shperfuzz/README.md.