Artifact 2f5a65a28aad0c2c8d053d941ba872f53e99414135309c82bf0a0b0efdf9e7b5:
- File
src/logstore_fuzz.zig
— part of check-in
[be67814135]
at
2026-07-30 03:50:10
on branch shun-implementation
— shun S3: the zit shun verb
Refuses by default and names what would be destroyed; --force proceeds; never prompts. Reclaims by default -- one rewrite for N hashes, which is why the usage takes HASH... --defer records the tombstone without rewriting, and is also gated by --force: the row is irreversible and S1's mask hides the content immediately, so it is not the safe half of the verb.
The check-in this checkout is on cannot be shunned at all, with no override.
runShun does NOT reuse runPack's plumbing verbatim as planned -- it diverges on the filter (the source's deferred rows UNION the newly named hashes), on writing tombstones into the new generation, and on its renderer. The shared body is extracted as rewriteAndSwap instead.
Fixes a dead concurrency guard in that shared body, which predates this slice. FileStorage.sizeImpl returns a cached watermark rather than stat'ing, so validateTail was comparing a key against the number it was derived from and could never abort. pack carried the same dead guard. rewriteAndSwap now stats fresh at the check point; without that, this slice's claim that an aborted --force leaves no tombstone was unprovable.
Consequence lines carry no counts or filenames: 'N descendant check-ins' and a blob's path both need a reverse F-card walk over every manifest, which is too much for the cheap refusal path. Deferred, not faked.
No sub-manifest row: that type and its draft are not on this branch. shunConsequence is a total switch, so a ninth type will not compile without its diagnostic. (user: dmestas, size: 36769) [annotate] [blame] [check-ins using] [more...]
//! Seeded, parameterized fuzz targets for the framed, content-addressed log //! (storage-track-plan.md §C/§E) over a deterministic in-memory backend. Each //! target takes its seed and round count from the caller (`zig build fuzz -- //! <target> [--seed N] [--rounds N]`); a fixed-seed smoke `test` per target //! (below) delegates to the same pub fn so the same invariant also runs under //! `zig build test`. Registered in fuzz_tests.zig. //! //! The disk is a zero-fault sim/FaultyFile (the healthy-disk default config, so //! nothing tears or flips), which makes the round-trip invariants deterministic //! and filesystem-free. delta is reached through the public `zit` module: a //! relative import would pull delta.zig into this compilation root a second //! time, colliding with the copy logstore.zig already holds. const std = @import("std"); const logstore = @import("zit").logstore; const faulty = @import("sim/faulty_file.zig"); const delta = @import("zit").delta; const container = @import("zit").container; const pack = @import("zit").pack; const artifact = @import("zit").artifact; /// A fresh in-memory backend with the healthy-disk default config — the /// precondition every round-trip below depends on. fn healthyFile(gpa: std.mem.Allocator, rand: std.Random) faulty.FaultyFile { const file = faulty.FaultyFile.init(gpa, rand); std.debug.assert(std.meta.eql(file.cfg, faulty.FaultCfg{})); return file; } /// A random `min_hash_len..max_hash_len` byte hash into `buf`. Hashes are /// opaque here (never verified against content), so arbitrary bytes are fine. fn randomHash(rand: std.Random, buf: *[logstore.max_hash_len]u8) []const u8 { const span = logstore.max_hash_len - logstore.min_hash_len + 1; const len = logstore.min_hash_len + rand.uintLessThan(usize, span); for (buf[0..len]) |*b| b.* = rand.int(u8); return buf[0..len]; } /// `open` never crashes on garbage appended after a valid header: it recovers a /// store or fails with one of its declared errors (storage-track-plan.md §C /// torn-tail recovery). /// /// Reachability, asserted rather than assumed (the `serve_fuzz` idea): a run /// must reach BOTH a recovered store (the torn tail truncated away) and a /// `Corrupt` rejection, or the generator has drifted so torn-tail recovery is /// never exercised. Measured at seed 1, 5_000 rounds: 197 recovered / 4_803 /// Corrupt (≈4% recover), and at 256 rounds recovery holds ≥8 across seeds 1-3 /// and the smoke seed, the floor. `OutOfMemory` and `Storage` are excluded on /// measured grounds: this target runs over a healthy file with a real /// allocator, so neither is reachable here (0/5_000) — they are pinned by the /// allocator-failure and `FaultyFile` storage sweeps. const open_garbage_min_rounds: usize = 256; pub fn openGarbage(gpa: std.mem.Allocator, seed: u64, rounds: usize) !void { var prng = std.Random.DefaultPrng.init(seed); const rand = prng.random(); var recovered: usize = 0; var corrupt: usize = 0; var round: usize = 0; while (round < rounds) : (round += 1) { var backend = healthyFile(gpa, rand); defer backend.deinit(); const file = backend.storageFile(); var seeded = try logstore.LogStore.open(gpa, file, 0); seeded.deinit(gpa); var garbage: [256]u8 = undefined; const n = rand.uintLessThan(usize, garbage.len + 1); rand.bytes(garbage[0..n]); try file.append(garbage[0..n]); if (logstore.LogStore.open(gpa, file, 0)) |store| { var opened = store; opened.deinit(gpa); recovered += 1; } else |err| switch (err) { error.OutOfMemory, error.Storage => {}, error.Corrupt => corrupt += 1, } } if (rounds >= open_garbage_min_rounds and (recovered == 0 or corrupt == 0)) { std.debug.print( "logstore_open_garbage: {d} rounds reached recovered={d} corrupt={d} — " ++ "the appended-garbage generator no longer straddles recovery and rejection\n", .{ rounds, recovered, corrupt }, ); return error.OutcomeUnreached; } } /// §C/sync §4.1 — putZlib then content round-trips the uncompressed bytes: the /// zlib container the store writes inflates back to exactly what went in. /// /// No reachability assertion: every round is a well-formed put/get round-trip, /// so success is the only outcome — no reject arm to partition. pub fn zlibRoundTrip(gpa: std.mem.Allocator, seed: u64, rounds: usize) !void { var prng = std.Random.DefaultPrng.init(seed); const rand = prng.random(); const max_raw: usize = 128; var hash_buf: [logstore.max_hash_len]u8 = undefined; var raw_buf: [max_raw]u8 = undefined; var round: usize = 0; while (round < rounds) : (round += 1) { var backend = healthyFile(gpa, rand); defer backend.deinit(); var store = try logstore.LogStore.open(gpa, backend.storageFile(), 0); defer store.deinit(gpa); const hash = randomHash(rand, &hash_buf); const rlen = rand.uintLessThan(usize, max_raw + 1); for (raw_buf[0..rlen]) |*b| b.* = rand.int(u8); const raw = raw_buf[0..rlen]; const rid = try store.putZlib(hash, raw); const got = (try store.content(gpa, rid)).?; defer gpa.free(got); try std.testing.expectEqualSlices(u8, raw, got); } } /// §C/delta §4 — putDelta then content applies the delta and round-trips the /// target: a delta frame reconstructs its target by walking to the base frame /// and applying via delta.apply. /// /// No reachability assertion: every round is a well-formed put/get round-trip, /// so success is the only outcome — no reject arm to partition. pub fn deltaRoundTrip(gpa: std.mem.Allocator, seed: u64, rounds: usize) !void { var prng = std.Random.DefaultPrng.init(seed); const rand = prng.random(); const max_len: usize = 128; var base_buf: [max_len]u8 = undefined; var target_buf: [max_len]u8 = undefined; var base_hash: [20]u8 = undefined; var target_hash: [20]u8 = undefined; var round: usize = 0; while (round < rounds) : (round += 1) { var backend = healthyFile(gpa, rand); defer backend.deinit(); var store = try logstore.LogStore.open(gpa, backend.storageFile(), 0); defer store.deinit(gpa); const blen = 1 + rand.uintLessThan(usize, max_len); for (base_buf[0..blen]) |*b| b.* = rand.int(u8); const base = base_buf[0..blen]; const tlen = 1 + rand.uintLessThan(usize, max_len); for (target_buf[0..tlen]) |*b| b.* = rand.int(u8); const target = target_buf[0..tlen]; // Distinct hashes: put keys on the hash, so a base/target collision // would alias the two frames and defeat the round-trip. for (&base_hash) |*b| b.* = rand.int(u8); for (&target_hash) |*b| b.* = rand.int(u8); base_hash[0] = 0; target_hash[0] = 1; const d = try delta.create(gpa, base, target); defer gpa.free(d); const base_rid = try store.put(&base_hash, base); const target_rid = try store.putDelta(&target_hash, base_rid, d); const got = (try store.content(gpa, target_rid)).?; defer gpa.free(got); try std.testing.expectEqualSlices(u8, target, got); } } /// §5 — pickBase/putArtifact over random content and random candidate sets /// preserves the invariant "storage is a cache of the log": whatever base /// policy chooses, `content` returns the exact bytes that went in. And any /// delta pickBase accepts both reconstructs its target and clears the 75% /// accept bar, against an eligible (non-phantom, under-cap) base. /// /// No reachability assertion: every round is a well-formed put/get round-trip /// with fixed accept invariants, so success is the only outcome — no reject arm /// to partition. pub fn pickBaseRoundTrip(gpa: std.mem.Allocator, seed: u64, rounds: usize) !void { var prng = std.Random.DefaultPrng.init(seed); const rand = prng.random(); const max_len: usize = 160; var buf: [max_len]u8 = undefined; var hash_buf: [logstore.max_hash_len]u8 = undefined; var thash_buf: [logstore.max_hash_len]u8 = undefined; var round: usize = 0; while (round < rounds) : (round += 1) { var backend = healthyFile(gpa, rand); defer backend.deinit(); var store = try logstore.LogStore.open(gpa, backend.storageFile(), 0); defer store.deinit(gpa); // Seed a handful of content frames — some sharing a long prefix so // profitable deltas exist, some pure noise — each put through the full // best-of-N write path with a random (possibly ineligible) base hint. // Every stored frame must round-trip regardless of the chosen encoding. const seed_frames = 1 + rand.uintLessThan(usize, 8); var rids: [8]logstore.Rid = undefined; var nr: usize = 0; while (nr < seed_frames) : (nr += 1) { const len = 1 + rand.uintLessThan(usize, max_len); for (buf[0..len]) |*b| b.* = rand.int(u8); if (nr > 0 and rand.boolean()) @memset(buf[0..@min(len, 48)], 0x5A); const hash = randomHash(rand, &hash_buf); // Content addressing: distinct content ⇒ distinct hash. `randomHash` // can emit a 1-byte hash that collides across seed frames, which // would (correctly) hit `putArtifact`'s idempotent short-circuit and // alias two different contents to one rid — a harness artifact, not a // store bug (real hashes never collide). Stamp the frame index into // byte 0 (`seed_frames <= 8`, and `hash.len >= 1`) so every seed // frame is a distinct key, mirroring the deltaRoundTrip guard above. hash_buf[0] = @intCast(nr); const hint: ?logstore.Rid = if (nr > 0 and rand.boolean()) rids[rand.uintLessThan(usize, nr)] else null; const rid = try store.putArtifact(hash, buf[0..len], hint); const got = (try store.content(gpa, rid)).?; defer gpa.free(got); try std.testing.expectEqualSlices(u8, buf[0..len], got); rids[nr] = rid; } // Now probe pickBase directly with a random candidate multiset (may // over-offer past max_base_candidates, repeat rids, or be empty). var cands: [12]logstore.Rid = undefined; const cn = rand.uintLessThan(usize, @min(nr, cands.len) + 1); for (cands[0..cn]) |*c| c.* = rids[rand.uintLessThan(usize, nr)]; const tlen = 1 + rand.uintLessThan(usize, max_len); for (buf[0..tlen]) |*b| b.* = rand.int(u8); if (rand.boolean()) @memset(buf[0..@min(tlen, 48)], 0x5A); const target = buf[0..tlen]; const thash = randomHash(rand, &thash_buf); if (try store.pickBase(gpa, thash, target, cands[0..cn])) |pick| { defer gpa.free(pick.delta); // Accepted ⇒ strictly under 75% of the uncompressed artifact. try std.testing.expect(@as(u64, pick.delta.len) * 4 < @as(u64, target.len) * 3); // Chosen base is eligible: content-bearing and under the write cap. try std.testing.expect(!store.isPhantom(pick.base)); try std.testing.expect(store.depthOf(pick.base) < logstore.delta_chain_write_max); // …and the delta reconstructs the target byte-for-byte. const base_bytes = (try store.content(gpa, pick.base)).?; defer gpa.free(base_bytes); const rebuilt = try delta.apply(gpa, base_bytes, pick.delta); defer gpa.free(rebuilt); try std.testing.expectEqualSlices(u8, target, rebuilt); } } } /// §6.1/§7.3 — pack-redeltify round-trip: a fuzzed store mixing every frame /// class pack must handle (raw / zlib / local delta / verbatim wire delta / /// interned phantom / config) is repacked through the REAL `zit.pack.packLog`, /// and EVERY artifact must decode-all byte-identically before and after the /// pack. Because `packLog` re-hashes each reconstructed frame against its name /// (§7.7), every content frame is keyed by `sha3-256(content)` — the digest a /// 64-hex name verifies against — so the fuzzer models real content addressing, /// not opaque keys. The packed base graph is chosen by our policy, so this pins /// that a whole-store re-basing never perturbs a single reconstructed byte. /// /// No reachability assertion: every round builds a store and repacks it, a /// decode-all round-trip whose only outcome is success — no reject arm to /// partition. pub fn packRoundTrip(gpa: std.mem.Allocator, seed: u64, rounds: usize) !void { var prng = std.Random.DefaultPrng.init(seed); const rand = prng.random(); const max_frames: usize = 20; const max_len: usize = 200; var round: usize = 0; while (round < rounds) : (round += 1) { var backend = healthyFile(gpa, rand); defer backend.deinit(); var src = try logstore.LogStore.open(gpa, backend.storageFile(), 0); defer src.deinit(gpa); // A live config sometimes (pack must carry exactly one through). if (rand.boolean()) { try src.putConfig(.{ .hash_policy = 2, .default_branch = "trunk", .project_id = "pid" }); } // Model every content frame written, in append order: its sha3 key, its // owned uncompressed bytes, and the src rid it landed at (a delta base // for later frames). Phantoms carry no content and are not modeled here. var keys: [max_frames][32]u8 = undefined; var contents: [max_frames][]u8 = undefined; var rids: [max_frames]logstore.Rid = undefined; var nf: usize = 0; defer for (contents[0..nf]) |c| gpa.free(c); const nframes = 1 + rand.uintLessThan(usize, max_frames); var i: usize = 0; while (i < nframes) : (i += 1) { const len = 1 + rand.uintLessThan(usize, max_len); const bytes = try gpa.alloc(u8, len); var freed = false; errdefer if (!freed) gpa.free(bytes); for (bytes) |*b| b.* = rand.int(u8); // Bias toward a shared run with an earlier frame so real (profitable) // deltas occur, not only literal-only ones. if (nf > 0 and rand.boolean()) { const src_frame = contents[rand.uintLessThan(usize, nf)]; const run = @min(len, src_frame.len); @memcpy(bytes[0..run], src_frame[0..run]); } var key: [32]u8 = undefined; std.crypto.hash.sha3.Sha3_256.hash(bytes, &key, .{}); // Content addressing: identical content ⇒ identical key ⇒ the store's // idempotent short-circuit would alias two model entries to one rid. // Skip a duplicate rather than model a phantom second frame. if (src.lookup(&key) != null) { gpa.free(bytes); freed = true; continue; } // Choose an on-disk form. Deltas need an existing content base. const variant: u8 = if (nf == 0) rand.uintLessThan(u8, 2) else rand.uintLessThan(u8, 4); const rid = switch (variant) { 0 => try src.put(&key, bytes), // raw 1 => try src.putZlib(&key, bytes), // zlib full form 2 => try src.putArtifact(&key, bytes, rids[rand.uintLessThan(usize, nf)]), // local delta (may fall back to full) 3 => blk: { // verbatim wire delta over an existing base const bi = rand.uintLessThan(usize, nf); const raw = delta.create(gpa, contents[bi], bytes) catch |e| switch (e) { error.OutOfMemory => return error.OutOfMemory, error.TargetTooLarge => unreachable, // len <= 200 }; defer gpa.free(raw); const cont = container.encode(gpa, raw, .default) catch |e| switch (e) { error.OutOfMemory => return error.OutOfMemory, error.InputTooLarge => unreachable, // a <=200-byte delta always encodes }; defer gpa.free(cont); break :blk try src.putDeltaContainer(&key, rids[bi], cont); }, else => unreachable, }; keys[nf] = key; contents[nf] = bytes; rids[nf] = rid; nf += 1; } // An interned phantom (no frame on disk) that pack must tolerate and not // resurrect. Keyed off a distinct label so it never collides with content. var phantom: [32]u8 = undefined; var label: [16]u8 = undefined; rand.bytes(&label); std.crypto.hash.sha3.Sha3_256.hash(&label, &phantom, .{}); const had_phantom = src.lookup(&phantom) == null; if (had_phantom) _ = try src.intern(&phantom); try src.commit(); // Pack through the REAL transform into a fresh healthy store. var pbackend = healthyFile(gpa, rand); defer pbackend.deinit(); var packed_store = try logstore.LogStore.open(gpa, pbackend.storageFile(), 0); defer packed_store.deinit(gpa); const stats = pack.packLog(gpa, &src, &packed_store, pack.Filter.keep_all, &.{}) catch |e| switch (e) { error.OutOfMemory => return error.OutOfMemory, error.Storage => return error.OutOfMemory, // a healthy sim never faults; treat as fatal error.Corrupt => return error.TestUnexpectedResult, // a §7.7 round-trip failure IS the bug }; try std.testing.expectEqual(nf, stats.artifacts); // Decode-all identical pre/post: every modeled artifact reconstructs to // its exact input bytes from BOTH the source and the repacked store. And // §6.1 per-frame monotonicity: NO packed frame is larger on disk than its // source frame — the guard keeps the smaller of {existing, re-encoding}. // Holds absolutely here because `max_frames` (20) keeps every chain below // the read cap, so `copySrcFrame`'s §3.3 deep-chain fallback (the one path // that can enlarge a KEPT frame) is never taken. for (keys[0..nf], contents[0..nf]) |k, want| { const srid = src.lookup(&k).?; const sb = (try src.content(gpa, srid)).?; defer gpa.free(sb); const prid = packed_store.lookup(&k) orelse return error.TestUnexpectedResult; const pb = (try packed_store.content(gpa, prid)).?; defer gpa.free(pb); try std.testing.expectEqualSlices(u8, want, sb); try std.testing.expectEqualSlices(u8, want, pb); try std.testing.expect(packed_store.frameSpanOf(prid) <= src.frameSpanOf(srid)); } // The phantom carries no content, so pack never writes a frame for it. if (had_phantom) try std.testing.expect(packed_store.lookup(&phantom) == null); // §6.1 whole-store monotonicity: the repack never regressed — per-frame // non-worsening plus dropped superseded configs make this exact. try std.testing.expect(stats.bytes_after <= stats.bytes_before); } } /// A one-file check-in manifest (`file_name` → `blob_name64`, optional primary /// parent `parent_name`, a root check-in carrying a propagating `branch=trunk` /// T card) — a valid parseable manifest so pack's §6.2 walk (`deriveCandidateHints` /// → `artifact.parseForImport`) recognizes it as a check-in and records the /// F-card blob under its path. Returns owned bytes; only OOM escapes. fn spCheckin( gpa: std.mem.Allocator, ts: []const u8, parent_name: ?[]const u8, file_name: []const u8, blob_name64: []const u8, ) error{OutOfMemory}![]u8 { var pbuf: [1]artifact.PCard = undefined; var p_slice: []const artifact.PCard = &.{}; if (parent_name) |pn| { pbuf[0] = .{ .parents = pn, .count = 1 }; p_slice = pbuf[0..1]; } const fcards = [_]artifact.FCard{ .{ .filename = file_name, .hash = blob_name64, .perm = null, .old_name = null, .mode = .regular }, }; const root_tags = [_]artifact.TCard{ .{ .sign = .propagate, .name = "branch", .target = null, .value = "trunk" }, }; var m: artifact.Manifest = .{ .arena_state = std.heap.ArenaAllocator.init(gpa), .kind = .manifest, .a = null, .b = null, .c = .{ .comment = "c" }, .d = .{ .timestamp = ts }, .e = null, .g = null, .h = null, .i = null, .k = null, .l = null, .n = null, .r = null, .u = .{ .login = "u" }, .z = null, .w = null, .f = &fcards, .j = &.{}, .m = &.{}, .p = p_slice, .q = &.{}, .t = if (parent_name == null) &root_tags else &.{}, }; defer m.deinit(); return artifact.create(&m, gpa); } /// The most tracked files / versions / poor bases one same-path round-trip /// builds — every loop and stack array below is sized to these. const sp_min_files: usize = 4; const sp_max_files: usize = 6; const sp_min_versions: usize = 2; const sp_max_versions: usize = 4; const sp_noise_blobs: usize = 2; /// files × versions blobs + the same count of manifests + noise blobs — the /// upper bound on modeled content frames one round produces. const sp_max_frames: usize = sp_max_files * sp_max_versions * 2 + sp_noise_blobs; /// §6.2/§7.3 — same-path pack round-trip: a fuzzed repository of several tracked /// files, each evolving over interleaved single-file check-ins (so a file's /// earlier version sits OUTSIDE the bounded chronological window of its later /// version — reachable only through pack's §6.2 same-path manifest walk), with /// blobs in a per-seed mix of full / local-delta / verbatim-wire-delta-over-a- /// poor-base encodings, is repacked through the REAL `zit.pack.packLog`. Every /// artifact (blobs AND manifests, incl. the trunk tip) must decode byte-identically /// before and after the pack, and no packed frame may exceed its source frame /// (§6.1 monotonic). Because `packLog` re-hashes each reconstructed frame against /// its name (§7.7), every frame is keyed by `sha3-256(content)` — the digest a /// 64-hex name verifies against — so the same-path walk resolves real F-card /// names, not opaque keys. /// /// No reachability assertion: every round builds a store and repacks it, a /// decode-all round-trip whose only outcome is success — no reject arm to /// partition. pub fn packSamePathRoundTrip(gpa: std.mem.Allocator, seed: u64, rounds: usize) !void { var prng = std.Random.DefaultPrng.init(seed); const rand = prng.random(); var round: usize = 0; while (round < rounds) : (round += 1) { var backend = healthyFile(gpa, rand); defer backend.deinit(); var src = try logstore.LogStore.open(gpa, backend.storageFile(), 0); defer src.deinit(gpa); try src.putConfig(.{ .hash_policy = 2, .default_branch = "trunk", .project_id = "pid" }); // Every modeled content frame in append order: its sha3 key + owned // uncompressed bytes (the ground truth every store must decode to). var keys: [sp_max_frames][32]u8 = undefined; var contents: [sp_max_frames][]u8 = undefined; var nf: usize = 0; defer for (contents[0..nf]) |c| gpa.free(c); // Poor bases first (lowest offset): full-form noise blobs a source wire // delta names and pack must discard for the same-path predecessor. var noise_rid: [sp_noise_blobs]logstore.Rid = undefined; var noise_ci: [sp_noise_blobs]usize = undefined; // index into contents[] { var n: usize = 0; while (n < sp_noise_blobs) : (n += 1) { var nb: std.ArrayList(u8) = .empty; errdefer nb.deinit(gpa); const rows = 40 + rand.uintLessThan(usize, 40); var j: usize = 0; while (j < rows) : (j += 1) try nb.print(gpa, "noise {d} row {d}\n", .{ n, j }); const nbytes = try nb.toOwnedSlice(gpa); var key: [32]u8 = undefined; std.crypto.hash.sha3.Sha3_256.hash(nbytes, &key, .{}); noise_rid[n] = try src.putZlib(&key, nbytes); keys[nf] = key; contents[nf] = nbytes; noise_ci[n] = nf; nf += 1; } } const nfiles = sp_min_files + rand.uintLessThan(usize, sp_max_files - sp_min_files + 1); const nvers = sp_min_versions + rand.uintLessThan(usize, sp_max_versions - sp_min_versions + 1); // Each tracked file gets its OWN random body, shared verbatim across that // file's versions (so a same-file version delta is tiny) but independent of // every other file's (so a cross-file delta is unprofitable — pack's // best-of-N never prefers a chronological neighbor over the same-path // predecessor, which the non-vacuity probe below relies on). Length is // well above pickBase's cheap length early-out so a base is never skipped. var bodies: [sp_max_files][]u8 = undefined; var nbodies: usize = 0; defer for (bodies[0..nbodies]) |b| gpa.free(b); { var f: usize = 0; while (f < nfiles) : (f += 1) { const blen = 200 + rand.uintLessThan(usize, 120); const body = try gpa.alloc(u8, blen); for (body) |*b| b.* = rand.int(u8); bodies[f] = body; nbodies += 1; } } var fname_buf: [sp_max_files][16]u8 = undefined; var prev_blob_rid: [sp_max_files]logstore.Rid = undefined; var prev_name: [64]u8 = undefined; var have_prev = false; var ci: usize = 0; // global check-in index — a valid distinct day (01..24) var forced_wire_done = false; // File 0's v0/v1 keys — the non-vacuity probe: v1 is forced to a wire // delta over a poor base and v0 sits beyond the chronological window, so // pack can only re-base v1 onto v0 through the §6.2 same-path walk. var f0v0_key: [32]u8 = undefined; var f0v1_key: [32]u8 = undefined; var have_f0v1 = false; var version: usize = 0; while (version < nvers) : (version += 1) { var file: usize = 0; while (file < nfiles) : (file += 1) { const fname = std.fmt.bufPrint(&fname_buf[file], "file{d}.txt", .{file}) catch unreachable; // This file's shared body + a per-version trailer: distinct content // ⇒ distinct sha3 key, while a same-file version delta stays tiny. var bbuf: std.ArrayList(u8) = .empty; errdefer bbuf.deinit(gpa); try bbuf.appendSlice(gpa, bodies[file]); try bbuf.print(gpa, "\ntrailer file {d} version {d}\n", .{ file, version }); const bbytes = try bbuf.toOwnedSlice(gpa); var freed = false; errdefer if (!freed) gpa.free(bbytes); var bkey: [32]u8 = undefined; std.crypto.hash.sha3.Sha3_256.hash(bbytes, &bkey, .{}); var bname64: [64]u8 = undefined; _ = artifact.artifactName(.sha3_256, bbytes, &bname64); // Source encoding. Version 0 of each file is FULL (a clean re-delta // target); a later version takes a per-seed form, and file 0's // first later version is forced to a wire delta so every round // exercises the re-basing path over a poor base. var blob_rid: logstore.Rid = undefined; if (version == 0) { blob_rid = try src.putZlib(&bkey, bbytes); } else { const force_wire = !forced_wire_done and file == 0; const variant: u8 = if (force_wire) 0 else rand.uintLessThan(u8, 3); if (force_wire) forced_wire_done = true; switch (variant) { 0 => { const pick = rand.uintLessThan(usize, sp_noise_blobs); const raw = try delta.create(gpa, contents[noise_ci[pick]], bbytes); defer gpa.free(raw); const cont = try container.encode(gpa, raw, .default); defer gpa.free(cont); blob_rid = try src.putDeltaContainer(&bkey, noise_rid[pick], cont); }, 1 => blob_rid = try src.putArtifact(&bkey, bbytes, prev_blob_rid[file]), else => blob_rid = try src.putZlib(&bkey, bbytes), } } prev_blob_rid[file] = blob_rid; if (file == 0 and version == 0) f0v0_key = bkey; if (file == 0 and version == 1) { f0v1_key = bkey; have_f0v1 = true; } keys[nf] = bkey; contents[nf] = bbytes; freed = true; // contents[] now owns bbytes nf += 1; // Manifest snapshotting `fname` -> this blob, parented into the // shared linear chain. Full form so pack's §6.1 parent hint and // §6.2 F-card walk both run over it. var ts_buf: [32]u8 = undefined; const ts = std.fmt.bufPrint(&ts_buf, "2020-01-{d:0>2}T00:00:00", .{ci + 1}) catch unreachable; const parent: ?[]const u8 = if (have_prev) prev_name[0..] else null; const mbytes = try spCheckin(gpa, ts, parent, fname, &bname64); var mfreed = false; errdefer if (!mfreed) gpa.free(mbytes); var mkey: [32]u8 = undefined; std.crypto.hash.sha3.Sha3_256.hash(mbytes, &mkey, .{}); _ = try src.putZlib(&mkey, mbytes); keys[nf] = mkey; contents[nf] = mbytes; mfreed = true; // contents[] now owns mbytes nf += 1; _ = artifact.artifactName(.sha3_256, mbytes, &prev_name); have_prev = true; ci += 1; } } const trunk_key = prev_name; // the tip check-in's 64-hex name var trunk_digest: [32]u8 = undefined; _ = std.fmt.hexToBytes(&trunk_digest, &trunk_key) catch unreachable; try src.commit(); // Pack through the REAL transform into a fresh healthy store. var pbackend = healthyFile(gpa, rand); defer pbackend.deinit(); var packed_store = try logstore.LogStore.open(gpa, pbackend.storageFile(), 0); defer packed_store.deinit(gpa); const stats = pack.packLog(gpa, &src, &packed_store, pack.Filter.keep_all, &.{}) catch |e| switch (e) { error.OutOfMemory => return error.OutOfMemory, error.Storage => return error.OutOfMemory, // a healthy sim never faults; treat as fatal error.Corrupt => return error.TestUnexpectedResult, // a §7.7 round-trip failure IS the bug }; try std.testing.expectEqual(nf, stats.artifacts); // Decode-all identical pre/post: every modeled artifact reconstructs to // its exact input bytes from BOTH the source and the repacked store, and // no packed frame is larger on disk than its source frame (§6.1 monotonic; // holds absolutely — the bounded corpus keeps every chain below the read // cap, so `copySrcFrame`'s deep-chain fallback is never taken). for (keys[0..nf], contents[0..nf]) |k, want| { const srid = src.lookup(&k).?; const sb = (try src.content(gpa, srid)).?; defer gpa.free(sb); const prid = packed_store.lookup(&k) orelse return error.TestUnexpectedResult; const pb = (try packed_store.content(gpa, prid)).?; defer gpa.free(pb); try std.testing.expectEqualSlices(u8, want, sb); try std.testing.expectEqualSlices(u8, want, pb); try std.testing.expect(packed_store.frameSpanOf(prid) <= src.frameSpanOf(srid)); } // The trunk tip check-in survives the pack (a dropped trunk is a bug). try std.testing.expect(packed_store.lookup(&trunk_digest) != null); try std.testing.expect(stats.bytes_after <= stats.bytes_before); // Non-vacuity: file 0's version-1 blob was stored as a verbatim wire delta // over a POOR noise base, and its ideal base (version 0) sits beyond the // bounded chronological window (≥ 2·(files-1) frames back). So it can pack // as a compact delta ONLY if the §6.2 same-path manifest walk offered // version 0. Assert pack re-based it onto exactly that same-path // predecessor — proof the step-4 lane fired, not that the frame packed full. if (have_f0v1) { const v1_rid = packed_store.lookup(&f0v1_key).?; try std.testing.expectEqual(logstore.FrameKind.delta, packed_store.kindOf(v1_rid)); var w = (try packed_store.wire(gpa, v1_rid)).?; defer w.deinit(gpa); try std.testing.expectEqualSlices(u8, &f0v0_key, w.delta.baseHash()); } } } /// §E — putConfig then readConfig round-trips hash_policy, branch, and project /// id. readConfig drives the file-private parseConfig; config strings are /// length-prefixed, so arbitrary bytes survive the round-trip. /// /// No reachability assertion: config strings are length-prefixed, so every /// round is a put/read round-trip whose only outcome is success — no reject arm /// to partition. pub fn parseConfig(gpa: std.mem.Allocator, seed: u64, rounds: usize) !void { var prng = std.Random.DefaultPrng.init(seed); const rand = prng.random(); const max_str: usize = 32; // <= logstore.max_config_str var branch_buf: [max_str]u8 = undefined; var pid_buf: [max_str]u8 = undefined; var round: usize = 0; while (round < rounds) : (round += 1) { var backend = healthyFile(gpa, rand); defer backend.deinit(); var store = try logstore.LogStore.open(gpa, backend.storageFile(), 0); defer store.deinit(gpa); const blen = rand.uintLessThan(usize, max_str + 1); for (branch_buf[0..blen]) |*b| b.* = rand.int(u8); const plen = rand.uintLessThan(usize, max_str + 1); for (pid_buf[0..plen]) |*b| b.* = rand.int(u8); const cfg: logstore.Config = .{ .hash_policy = rand.int(u8), .default_branch = branch_buf[0..blen], .project_id = pid_buf[0..plen], }; try store.putConfig(cfg); var oc = (try store.readConfig(gpa)).?; defer oc.deinit(gpa); try std.testing.expectEqual(cfg.hash_policy, oc.config.hash_policy); try std.testing.expectEqualSlices(u8, cfg.default_branch, oc.config.default_branch); try std.testing.expectEqualSlices(u8, cfg.project_id, oc.config.project_id); } } // Fixed-seed smoke tests: each delegates to its pub fn at a small round count so // the invariant runs under `zig build test`; deep runs pass `--rounds`. test "logstore fuzz: open never crashes on trailing garbage (smoke)" { try openGarbage(std.testing.allocator, 0xC0_FFEE, 32); } test "logstore §C/sync §4.1 fuzz: putZlib content round-trip (smoke)" { try zlibRoundTrip(std.testing.allocator, 0x21B_2117, 32); } test "logstore §C/delta §4 fuzz: putDelta content round-trip (smoke)" { try deltaRoundTrip(std.testing.allocator, 0xDE17A_04, 32); } test "logstore §5 fuzz: pickBase/putArtifact content round-trip + accept invariants (smoke)" { try pickBaseRoundTrip(std.testing.allocator, 0xB43E_5EED, 64); } test "logstore §6.1 fuzz: pack-redeltify of a mixed-frame store decode-all round-trip (smoke)" { try packRoundTrip(std.testing.allocator, 0xC0DE_F022, 48); } test "logstore §6.2 fuzz: same-path (multi-file interleaved) pack-redeltify decode-all round-trip (smoke)" { try packSamePathRoundTrip(std.testing.allocator, 0x5A3E_F022, 24); } test "logstore §E fuzz: putConfig/readConfig round-trip (smoke)" { try parseConfig(std.testing.allocator, 0xC0_F1_60, 32); }