const std = @import("std");
const repostate = @import("repostate.zig");
const Rid = repostate.Rid;
const Mtime = repostate.Mtime;
const TagType = repostate.TagType;
const MemStore = repostate.MemStore;
const RepoState = repostate.RepoState;
const parseMtime = repostate.parseMtime;
const t = std.testing;
const h_a = "a" ** 64;
const h_b = "b" ** 64;
const h_c = "c" ** 64;
const h_d = "d" ** 64;
const h_e = "e" ** 64;
const h_g = "0" ** 64;
fn countLeaves(rs: *const RepoState) usize {
var it = rs.leaves();
var n: usize = 0;
while (it.next()) |_| n += 1;
return n;
}
test "repostate §2: intern assigns one stable rid per hash" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
const s = ms.store();
const ra = try s.intern(h_a);
const rb = try s.intern(h_b);
try t.expect(ra != rb);
try t.expectEqual(ra, try s.intern(h_a));
try t.expectEqualStrings(h_a, s.hashOf(ra));
try t.expectEqualStrings(h_b, s.hashOf(rb));
}
test "repostate §3.2: count enumerates dense rids so a peer can classify each entry as phantom or held" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
const s = ms.store();
try t.expectEqual(@as(usize, 0), s.count());
_ = try s.intern(h_a); // phantom
_ = try s.put(h_b, "held"); // content present
_ = try s.intern(h_c); // phantom
try t.expectEqual(@as(usize, 3), s.count());
// Walk 1..=count and classify via content(rid): two phantoms, one held.
var phantoms: usize = 0;
var held: usize = 0;
var rid: Rid = 1;
while (rid <= s.count()) : (rid += 1) {
if (s.content(rid) == null) phantoms += 1 else held += 1;
}
try t.expectEqual(@as(usize, 2), phantoms);
try t.expectEqual(@as(usize, 1), held);
}
test "repostate §4.2: an interned but absent artifact is a phantom with no content" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
const s = ms.store();
const rid = try s.intern(h_a);
try t.expectEqual(@as(?[]const u8, null), s.content(rid));
// The referenced artifact arrives: the phantom upgrades in place.
try t.expectEqual(rid, try s.put(h_a, "payload"));
try t.expectEqualStrings("payload", s.content(rid).?);
// A repeat put of the same hash is a no-op.
try t.expectEqual(rid, try s.put(h_a, "payload"));
try t.expectEqualStrings("payload", s.content(rid).?);
}
test "repostate §5.1: tag types encode cancel=0, singleton=1, propagate=2" {
try t.expectEqual(0, @intFromEnum(TagType.cancel));
try t.expectEqual(1, @intFromEnum(TagType.singleton));
try t.expectEqual(2, @intFromEnum(TagType.propagate));
}
test "repostate §3.1: a repeated parent reference collapses to one linkage row" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
const rb = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{ h_a, h_a } });
try t.expectEqual(@as(usize, 1), rs.parentCount(rb));
try t.expectEqual(@as(?Rid, ra), rs.primaryParentOf(rb));
try t.expect(!rs.isLeaf(ra));
try t.expect(rs.isLeaf(rb));
}
test "repostate §3.2: the first listed parent is the primary parent" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
_ = try rs.crosslink(.{ .hash = h_b, .mtime = 2 });
const rc = try rs.crosslink(.{ .hash = h_c, .mtime = 3, .parents = &.{ h_a, h_b } });
try t.expectEqual(@as(usize, 2), rs.parentCount(rc));
try t.expectEqual(@as(?Rid, ra), rs.primaryParentOf(rc));
}
test "repostate §3.3: parentsOf yields the primary and every merge parent once" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
const rb = try rs.crosslink(.{ .hash = h_b, .mtime = 2 });
// A merge child of two independent roots: primary ra, merge rb (§3.2).
const rc = try rs.crosslink(.{ .hash = h_c, .mtime = 3, .parents = &.{ h_a, h_b, h_a } });
// §3.1: the repeated parent collapses, so both parents are yielded once.
var seen_a: usize = 0;
var seen_b: usize = 0;
var n: usize = 0;
var it = rs.parentsOf(rc);
while (it.next()) |p| {
n += 1;
if (p == ra) seen_a += 1;
if (p == rb) seen_b += 1;
}
try t.expectEqual(@as(usize, 2), n);
try t.expectEqual(@as(usize, 1), seen_a);
try t.expectEqual(@as(usize, 1), seen_b);
try t.expectEqual(rs.parentCount(rc), n); // §3.3: same set parentCount counts
// A root yields nothing.
var root_it = rs.parentsOf(ra);
try t.expectEqual(@as(?Rid, null), root_it.next());
}
test "repostate §3.2: a sole parent is the primary parent" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
const rb = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a} });
try t.expectEqual(@as(?Rid, ra), rs.primaryParentOf(rb));
}
test "repostate §4.2: re-crosslinking a linked artifact is a linkage no-op" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
const rb = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a} });
const rb_again = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a} });
try t.expectEqual(rb, rb_again);
try t.expectEqual(@as(usize, 1), rs.parentCount(rb));
try t.expectEqual(@as(usize, 1), countLeaves(&rs));
try t.expect(rs.isLeaf(rb));
}
test "repostate §4.2: an absent parent hash yields a phantom, resolved when it arrives" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
// The child arrives first; its parent hash resolves to a phantom
// placeholder rather than a fatal error.
const rb = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a} });
const ra = rs.primaryParentOf(rb).?;
try t.expectEqual(@as(?Mtime, null), rs.mtimeOf(ra));
// A phantom is not a check-in: it never enters the leaf table.
try t.expect(!rs.isLeaf(ra));
try t.expect(rs.isLeaf(rb));
try t.expectEqual(@as(usize, 1), countLeaves(&rs));
// The referenced artifact arrives and is crosslinked: the recorded
// linkage becomes visible in the derived views.
try t.expectEqual(ra, try rs.crosslink(.{ .hash = h_a, .mtime = 1 }));
try t.expectEqual(@as(?Mtime, 1), rs.mtimeOf(ra));
try t.expect(!rs.isLeaf(ra));
try t.expect(rs.isLeaf(rb));
try t.expectEqual(@as(usize, 1), countLeaves(&rs));
}
test "repostate §7.2: a child reached only via a merge edge disqualifies the leaf" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
const rb = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a} });
const rc = try rs.crosslink(.{ .hash = h_c, .mtime = 3, .parents = &.{h_a} });
const rd = try rs.crosslink(.{ .hash = h_d, .mtime = 4, .parents = &.{ h_b, h_c } });
// rc's only child rd is reached via a non-primary (merge) edge; the
// canonical test applies no primary/merge filter.
try t.expect(!rs.isLeaf(rb));
try t.expect(!rs.isLeaf(rc));
try t.expect(rs.isLeaf(rd));
try t.expectEqual(@as(usize, 1), countLeaves(&rs));
}
test "repostate §7.3: a root check-in with no children is a leaf" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
try t.expect(rs.isLeaf(ra));
var it = rs.leaves();
try t.expectEqual(@as(?Rid, ra), it.next());
try t.expectEqual(@as(?Rid, null), it.next());
}
test "repostate §7.3: a new child retroactively removes its parent's leaf status" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
try t.expect(rs.isLeaf(ra));
const rb = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a} });
try t.expect(!rs.isLeaf(ra));
try t.expect(rs.isLeaf(rb));
}
test "repostate §7.3: full rebuild reproduces the incrementally maintained table" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
const rb = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a} });
const rc = try rs.crosslink(.{ .hash = h_c, .mtime = 3, .parents = &.{h_a} });
_ = try rs.crosslink(.{ .hash = h_d, .mtime = 4, .parents = &.{ h_b, h_c } });
// An independent root with no children: a candidate the rebuild MUST
// include even though it appears in no parent-link.
const re = try rs.crosslink(.{ .hash = h_e, .mtime = 5 });
var before: [8]Rid = undefined;
const n_before = collectLeaves(&rs, &before);
try rs.rebuildLeaves();
var after: [8]Rid = undefined;
const n_after = collectLeaves(&rs, &after);
try t.expectEqual(n_before, n_after);
std.mem.sort(Rid, before[0..n_before], {}, std.sort.asc(Rid));
std.mem.sort(Rid, after[0..n_after], {}, std.sort.asc(Rid));
try t.expectEqualSlices(Rid, before[0..n_before], after[0..n_after]);
try t.expect(rs.isLeaf(re));
try t.expect(!rs.isLeaf(rb));
try t.expect(!rs.isLeaf(rc));
}
test "repostate §7.2/§6.1: a fork onto a new branch leaves the parent a leaf of its own branch" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
const rb = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a}, .tags = &.{
.{ .type = .propagate, .name = "branch", .target = null, .value = "feature" },
} });
try t.expect(rs.isLeaf(ra));
try t.expect(rs.isLeaf(rb));
}
test "repostate §7.6: directly applying a branch tag re-evaluates leaf status" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
_ = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a} });
try t.expect(!rs.isLeaf(ra)); // same branch, has a child
_ = try rs.crosslink(.{ .hash = h_c, .mtime = 3, .tags = &.{
.{ .type = .propagate, .name = "branch", .target = h_b, .value = "feature" },
} });
try t.expect(rs.isLeaf(ra));
}
test "repostate §7.6: branch propagation re-evaluates a descendant's leaf status" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
const rb = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a} });
// C carries its own direct branch tag, so it blocks any further
// propagation past it (Section 5.5: a nonzero-srcid row is never
// overwritten by an inherited one).
const rc = try rs.crosslink(.{ .hash = h_c, .mtime = 3, .parents = &.{h_b}, .tags = &.{
.{ .type = .propagate, .name = "branch", .target = null, .value = "trunk" },
} });
try t.expect(!rs.isLeaf(ra));
try t.expect(!rs.isLeaf(rb)); // same branch (trunk) as its child C
try t.expect(rs.isLeaf(rc));
// A, a pre-existing check-in with a child already in place, now gets a
// branch tag. The walk reaches B — B carries no direct branch row of
// its own, so it inherits "feature" — while C's direct "trunk" row
// blocks the walk from reaching any further. B did not itself get
// crosslinked by this call, so nothing but the propagation step re-
// evaluates it. Derivation Section 7.6 second bullet: "when a branch
// tag propagates to descendants, each descendant that gains or loses
// the propagating tag MUST have its leaf status re-evaluated."
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 10, .tags = &.{
.{ .type = .propagate, .name = "branch", .target = null, .value = "feature" },
} });
try t.expect(!rs.isLeaf(ra)); // A still shares a branch with child B
try t.expect(rs.isLeaf(rb)); // B's only child C stayed on trunk: B gained leaf status
try t.expect(rs.isLeaf(rc));
}
test "repostate §7.4: a leaf carrying an active closed marker is not an open leaf" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .singleton, .name = "closed", .target = null, .value = null },
} });
try t.expect(rs.isLeaf(ra));
try t.expect(!rs.isOpenLeaf(ra));
}
test "repostate §7.4: cancelling the closed marker reopens the leaf" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 5, .tags = &.{
.{ .type = .singleton, .name = "closed", .target = null, .value = null },
} });
try t.expect(!rs.isOpenLeaf(ra));
_ = try rs.crosslink(.{ .hash = h_b, .mtime = 6, .tags = &.{
.{ .type = .cancel, .name = "closed", .target = h_a, .value = null },
} });
try t.expect(rs.isOpenLeaf(ra));
}
test "repostate §6.1: branchOf returns the active branch-tag value" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "branch", .target = null, .value = "trunk" },
} });
try t.expectEqualStrings("trunk", rs.branchOf(ra, "main"));
}
test "repostate §6.1/§6.4: a check-in with no branch tag falls back to the configured main-branch" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
// "main", not the literal "trunk" — proves the configured value is
// read, not a hardcoded default (§6.1 "MUST NOT assume a fixed string").
try t.expectEqualStrings("main", rs.branchOf(ra, "main"));
}
test "repostate §6.4: a cancelled branch tag is not active and falls back" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .cancel, .name = "branch", .target = null, .value = null },
} });
try t.expectEqualStrings("main", rs.branchOf(ra, "main"));
}
test "repostate §8.2: resolves to the active sym-name carrier with the greatest event.mtime" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "sym-trunk", .target = null, .value = "trunk" },
} });
const rb = try rs.crosslink(.{ .hash = h_b, .mtime = 5, .parents = &.{h_a} });
// B inherits the propagating sym-trunk tag; resolution keys on each
// carrier's own event.mtime (Section 8.2), not the tag row's stored
// mtime, so B's later commit time outranks the tag's origin A.
try t.expectEqual(rb, try rs.resolveName("trunk"));
}
test "repostate §8.2: closed-ness does not filter resolution" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "sym-trunk", .target = null, .value = "trunk" },
} });
const rb = try rs.crosslink(.{ .hash = h_b, .mtime = 5, .parents = &.{h_a}, .tags = &.{
.{ .type = .singleton, .name = "closed", .target = null, .value = null },
} });
try t.expect(rs.isLeaf(rb));
try t.expect(!rs.isOpenLeaf(rb));
try t.expectEqual(rb, try rs.resolveName("trunk"));
}
test "repostate §8.2: leaf-ness does not filter resolution" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
// Singleton keeps the tag off B, isolating "a non-leaf still resolves"
// from Section 5.4 inheritance mechanics.
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 10, .tags = &.{
.{ .type = .singleton, .name = "sym-trunk", .target = null, .value = "trunk" },
} });
_ = try rs.crosslink(.{ .hash = h_b, .mtime = 5, .parents = &.{h_a} });
try t.expect(!rs.isLeaf(ra));
try t.expectEqual(ra, try rs.resolveName("trunk"));
}
test "repostate §8.2: an unknown symbolic name is rejected as NotFound" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
try t.expectError(error.NotFound, rs.resolveName("nope"));
}
test "repostate §8.2: a name whose only sym-tag row is cancelled is NotFound" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .cancel, .name = "sym-trunk", .target = null, .value = null },
} });
try t.expectError(error.NotFound, rs.resolveName("trunk"));
}
test "repostate §9/§2: current-trunk resolves the configured default branch name" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "sym-trunk", .target = null, .value = "trunk" },
} });
try t.expectEqual(ra, try rs.currentTrunk("trunk"));
try t.expectEqual(try rs.resolveName("trunk"), try rs.currentTrunk("trunk"));
}
test "repostate §9: current-trunk of an unknown default branch is NotFound" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
try t.expectError(error.NotFound, rs.currentTrunk("nope"));
}
test "repostate §9: the open leaf carries an active branch tag, is a leaf, and is not closed" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "branch", .target = null, .value = "trunk" },
} });
try t.expectEqual(@as(?Rid, ra), rs.openLeafOf("trunk"));
}
test "repostate §9/§6.1: a tagless leaf is not the open leaf of the default branch" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
// Attributed to "trunk" only through the default-branch fallback: not
// the open leaf of trunk (§6.1's fallback caveat).
try t.expectEqual(@as(?Rid, null), rs.openLeafOf("trunk"));
}
test "repostate §9: a non-leaf on the branch is not an open leaf" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "branch", .target = null, .value = "trunk" },
} });
// B, a same-branch child, disqualifies A under condition (b) even
// though A meets (a) and (c); B — the actual leaf — wins instead.
const rb = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a} });
try t.expect(!rs.isLeaf(ra));
try t.expectEqual(@as(?Rid, rb), rs.openLeafOf("trunk"));
}
test "repostate §9: a closed leaf is not an open leaf" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "branch", .target = null, .value = "trunk" },
.{ .type = .singleton, .name = "closed", .target = null, .value = null },
} });
try t.expect(rs.isLeaf(ra));
try t.expectEqual(@as(?Rid, null), rs.openLeafOf("trunk"));
}
test "repostate §9: with several open leaves the greatest mtime is selected" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "branch", .target = null, .value = "trunk" },
} });
const rb = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a} });
const rc = try rs.crosslink(.{ .hash = h_c, .mtime = 3, .parents = &.{h_a} });
try t.expect(rs.isLeaf(rb));
try t.expect(rs.isLeaf(rc));
try t.expectEqual(@as(?Rid, rc), rs.openLeafOf("trunk"));
}
test "repostate §9: a branch with no qualifying check-in has no open leaf" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "branch", .target = null, .value = "trunk" },
.{ .type = .singleton, .name = "closed", .target = null, .value = null },
} });
try t.expectEqual(@as(?Rid, null), rs.openLeafOf("trunk"));
}
test "repostate §10.1: fork at trunk with one closed leaf — resolution and open-leaf agree" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "branch", .target = null, .value = "trunk" },
.{ .type = .propagate, .name = "sym-trunk", .target = null, .value = "trunk" },
} });
const open_leaf = try rs.crosslink(.{ .hash = h_b, .mtime = 3, .parents = &.{h_a} });
const closed_leaf = try rs.crosslink(.{ .hash = h_c, .mtime = 2, .parents = &.{h_a}, .tags = &.{
.{ .type = .singleton, .name = "closed", .target = null, .value = null },
} });
_ = try rs.crosslink(.{ .hash = h_d, .mtime = 4, .parents = &.{h_a}, .tags = &.{
.{ .type = .cancel, .name = "sym-trunk", .target = null, .value = null },
.{ .type = .propagate, .name = "branch", .target = null, .value = "feature" },
.{ .type = .propagate, .name = "sym-feature", .target = null, .value = "feature" },
} });
try t.expect(rs.isLeaf(open_leaf));
try t.expect(rs.isOpenLeaf(open_leaf));
try t.expect(rs.isLeaf(closed_leaf));
try t.expect(!rs.isOpenLeaf(closed_leaf));
// §10.1: leaves lists the open trunk leaf while omitting the closed
// sibling; whatis trunk agrees with that same open leaf here.
try t.expectEqual(open_leaf, try rs.resolveName("trunk"));
try t.expectEqual(@as(?Rid, open_leaf), rs.openLeafOf("trunk"));
}
test "repostate §10.2: a closed leaf newer than the open leaf — resolution and open-leaf diverge" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "branch", .target = null, .value = "trunk" },
.{ .type = .propagate, .name = "sym-trunk", .target = null, .value = "trunk" },
} });
const open_leaf = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a} });
const closed_leaf = try rs.crosslink(.{ .hash = h_c, .mtime = 3, .parents = &.{h_a}, .tags = &.{
.{ .type = .singleton, .name = "closed", .target = null, .value = null },
} });
try t.expect(rs.isOpenLeaf(open_leaf));
try t.expect(rs.isLeaf(closed_leaf));
try t.expect(!rs.isOpenLeaf(closed_leaf));
// §10.2: whatis trunk reports the closed, newer check-in even though
// the default open-leaf view omits it — resolution and open-leaf
// enumeration diverge on this graph.
try t.expectEqual(closed_leaf, try rs.resolveName("trunk"));
try t.expectEqual(@as(?Rid, open_leaf), rs.openLeafOf("trunk"));
try t.expect((try rs.resolveName("trunk")) != rs.openLeafOf("trunk").?);
}
fn collectLeaves(rs: *const RepoState, out: []Rid) usize {
var it = rs.leaves();
var n: usize = 0;
while (it.next()) |rid| : (n += 1) out[n] = rid;
return n;
}
fn crosslinkNoLeak(gpa: std.mem.Allocator) !void {
var ms = MemStore.init(gpa);
defer ms.deinit();
var rs = RepoState.init(gpa, ms.store());
defer rs.deinit();
// Self-branch root plus a tag on a not-yet-stored (phantom) target, so
// internTag/applyDirect/propagate all allocate.
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "branch", .target = null, .value = "feature" },
.{ .type = .propagate, .name = "t1", .target = h_g, .value = "v" },
} });
_ = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a} });
// Phantom parent plus a merge, so every allocation site is exercised.
_ = try rs.crosslink(.{ .hash = h_d, .mtime = 4, .parents = &.{ h_b, h_c } });
// A cancel and a singleton (+closed), exercising the value-clear and
// downgrade-at-repropagation paths.
_ = try rs.crosslink(.{ .hash = h_c, .mtime = 3, .parents = &.{h_a}, .tags = &.{
.{ .type = .cancel, .name = "t1", .target = null, .value = null },
.{ .type = .singleton, .name = "closed", .target = null, .value = null },
} });
_ = try rs.crosslink(.{ .hash = h_g, .mtime = 5 }); // the phantom target arrives
try rs.rebuildLeaves();
}
test "repostate §4.2: crosslink survives allocation failure without leaking" {
try t.checkAllAllocationFailures(t.allocator, crosslinkNoLeak, .{});
}
/// Fork topology reused across the root:/start:/merge-in:/branch-type
/// tests: A (self-tagged trunk) <- B (plain, inherits trunk) <- C
/// (self-tagged feature, diverging from trunk at B) <- D (plain, inherits
/// feature).
const TopoT = struct { a: Rid, b: Rid, c: Rid, d: Rid };
fn buildTopoT(rs: *RepoState) !TopoT {
const a = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "sym-trunk", .target = null, .value = "trunk" },
.{ .type = .propagate, .name = "branch", .target = null, .value = "trunk" },
} });
const b = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a} });
const c = try rs.crosslink(.{ .hash = h_c, .mtime = 3, .parents = &.{h_b}, .tags = &.{
.{ .type = .propagate, .name = "sym-feature", .target = null, .value = "feature" },
.{ .type = .propagate, .name = "branch", .target = null, .value = "feature" },
} });
const d = try rs.crosslink(.{ .hash = h_d, .mtime = 4, .parents = &.{h_c} });
return .{ .a = a, .b = b, .c = c, .d = d };
}
test "repostate §8.3: rid: resolves a numeric RID to a known check-in" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const topo = try buildTopoT(&rs);
var buf: [32]u8 = undefined;
const name = try std.fmt.bufPrint(&buf, "rid:{d}", .{topo.b});
try t.expectEqual(topo.b, try rs.resolve(name, .checkin));
}
test "repostate §8.3: rid: an unknown or non-numeric RID is NotFound" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
const cases = [_][]const u8{ "rid:999", "rid:xyz" };
for (cases) |name| {
try t.expectError(error.NotFound, rs.resolve(name, .checkin));
}
}
test "repostate §8.3: tag: forces literal tag resolution over hash interpretation" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const h_hash_match = "dead" ++ "2" ** 60;
const rx = try rs.crosslink(.{ .hash = h_hash_match, .mtime = 1 });
const ry = try rs.crosslink(.{ .hash = h_c, .mtime = 2, .tags = &.{
.{ .type = .propagate, .name = "sym-dead", .target = null, .value = "dead" },
} });
try t.expectEqual(rx, try rs.resolve("dead", .checkin));
try t.expectEqual(ry, try rs.resolve("tag:dead", .checkin));
try t.expect((try rs.resolve("dead", .checkin)) != (try rs.resolve("tag:dead", .checkin)));
}
test "repostate §8.3: tip resolves the global maximum event.mtime check-in" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const topo = try buildTopoT(&rs);
try t.expectEqual(topo.d, try rs.resolve("tip", .checkin));
}
test "repostate §8.3/§8.4: root: rewrites to the first ancestor off the branch" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const topo = try buildTopoT(&rs);
// Walk D -> C[feature] -> B[trunk]: B is the first ancestor off feature.
try t.expectEqual(topo.b, try rs.resolve("root:feature", .checkin));
}
test "repostate §8.3/§8.4: start: rewrites to the earliest check-in on the branch" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const topo = try buildTopoT(&rs);
try t.expectEqual(topo.c, try rs.resolve("start:feature", .checkin));
}
test "repostate §8.3: merge-in: rewrites to the youngest parent-branch ancestor" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const topo = try buildTopoT(&rs);
// Section 8.4's two-variant walk gives merge-in: the same node as
// root: here: the youngest ancestor on the parent branch is exactly
// the first ancestor found off the diverging branch.
try t.expectEqual(topo.b, try rs.resolve("merge-in:feature", .checkin));
}
test "repostate §8.3: branch-type request post-processes to the branch root" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const topo = try buildTopoT(&rs);
try t.expectEqual(topo.c, try rs.resolve("feature", .branch));
}
test "repostate §8.3: an ambiguous hash prefix is rejected" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const h1 = "abcd" ++ "1" ** 60;
const h2 = "abcd" ++ "2" ** 60;
_ = try rs.crosslink(.{ .hash = h1, .mtime = 1 });
_ = try rs.crosslink(.{ .hash = h2, .mtime = 2 });
try t.expectError(error.Ambiguous, rs.resolve("abcd", .checkin));
}
test "repostate §8.3: a unique hash prefix resolves to its single check-in" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
try t.expectEqual(ra, try rs.resolve("aaaa", .checkin));
}
test "repostate §8.3: a trailing punctuation character is stripped and retried" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "sym-trunk", .target = null, .value = "trunk" },
} });
try t.expectEqual(try rs.resolve("trunk", .checkin), try rs.resolve("trunk.", .checkin));
}
test "repostate §8.3: a name with no matching form is NotFound" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
// "zzz": no form matches and no punctuation to strip.
try t.expectError(error.NotFound, rs.resolve("zzz", .checkin));
// "..": the punct-retry exhausts down to an empty slice.
try t.expectError(error.NotFound, rs.resolve("..", .checkin));
}
test "repostate §8.3: plain name falls through to the §8.2 Rule" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "sym-trunk", .target = null, .value = "trunk" },
} });
try t.expectEqual(try rs.resolveName("trunk"), try rs.resolve("trunk", .checkin));
}
test "repostate §8.4: branch-walk is bounded by the primary-parent chain" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "sym-trunk", .target = null, .value = "trunk" },
.{ .type = .propagate, .name = "branch", .target = null, .value = "trunk" },
} });
// A root-only branch: root:trunk never finds an ancestor off-branch, so
// the walk falls back to the branch root itself.
try t.expectEqual(ra, try rs.resolve("root:trunk", .checkin));
}
test "repostate §6.3: enumerates one entry per distinct active branch value" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try buildTopoT(&rs);
const entries = try rs.branches(t.allocator);
defer t.allocator.free(entries);
try t.expectEqual(@as(usize, 2), entries.len);
try t.expectEqualStrings("feature", entries[0].name);
try t.expectEqualStrings("trunk", entries[1].name);
}
test "repostate §6.3: a branch entry's representative is its greatest-mtime member" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const topo = try buildTopoT(&rs);
const entries = try rs.branches(t.allocator);
defer t.allocator.free(entries);
var trunk: ?RepoState.BranchEntry = null;
for (entries) |e| {
if (std.mem.eql(u8, e.name, "trunk")) trunk = e;
}
try t.expectEqual(topo.b, trunk.?.tip);
try t.expectEqual(@as(usize, 2), trunk.?.count);
try t.expectEqual(@as(Mtime, 2), trunk.?.mtime);
}
test "repostate §6.3: listing-closed reflects the representative row, diverging from §9 open-leaf" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "branch", .target = null, .value = "trunk" },
} });
const older_leaf = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a} });
const newer_leaf = try rs.crosslink(.{ .hash = h_c, .mtime = 3, .parents = &.{h_a} });
_ = try rs.crosslink(.{ .hash = h_d, .mtime = 5, .tags = &.{
.{ .type = .singleton, .name = "closed", .target = h_c, .value = null },
} });
const entries = try rs.branches(t.allocator);
defer t.allocator.free(entries);
try t.expectEqual(@as(usize, 1), entries.len);
try t.expectEqual(newer_leaf, entries[0].tip);
try t.expect(entries[0].closed);
try t.expectEqual(@as(?Rid, older_leaf), rs.openLeafOf("trunk"));
}
test "repostate §6.3: branches survives allocation failure without leaking" {
try t.checkAllAllocationFailures(t.allocator, branchesNoLeak, .{});
}
fn branchesNoLeak(gpa: std.mem.Allocator) !void {
var ms = MemStore.init(gpa);
defer ms.deinit();
var rs = RepoState.init(gpa, ms.store());
defer rs.deinit();
_ = try buildTopoT(&rs);
const entries = try rs.branches(gpa);
gpa.free(entries);
}
test "repostate §7.5: a non-leaf is never an ambiguous leaf" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
const rb = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a} });
_ = try rs.crosslink(.{ .hash = h_c, .mtime = 3, .parents = &.{h_b} });
try t.expect(!rs.isAmbiguousLeaf(rb));
}
test "repostate §7.5: a lone open leaf is not ambiguous" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
try t.expect(!rs.isAmbiguousLeaf(ra));
}
test "repostate §7.5: two open leaves on a branch are each ambiguous" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
const rb1 = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a} });
const rb2 = try rs.crosslink(.{ .hash = h_c, .mtime = 3, .parents = &.{h_a} });
try t.expect(rs.isAmbiguousLeaf(rb1));
try t.expect(rs.isAmbiguousLeaf(rb2));
}
test "repostate §7.5: a closed sibling does not make a leaf ambiguous" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
const rb1 = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a} });
_ = try rs.crosslink(.{ .hash = h_c, .mtime = 3, .parents = &.{h_a}, .tags = &.{
.{ .type = .singleton, .name = "closed", .target = null, .value = null },
} });
try t.expect(!rs.isAmbiguousLeaf(rb1));
}
test "repostate §7.5: open-leaf listing is mtime-descending" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "branch", .target = null, .value = "trunk" },
} });
const rb1 = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a} });
const rb2 = try rs.crosslink(.{ .hash = h_c, .mtime = 3, .parents = &.{h_a} });
const leaves_out = try rs.openLeavesOf(t.allocator, "trunk");
defer t.allocator.free(leaves_out);
try t.expectEqualSlices(Rid, &.{ rb2, rb1 }, leaves_out);
}
test "repostate §7.5: openLeavesOf survives allocation failure without leaking" {
try t.checkAllAllocationFailures(t.allocator, openLeavesOfNoLeak, .{});
}
fn openLeavesOfNoLeak(gpa: std.mem.Allocator) !void {
var ms = MemStore.init(gpa);
defer ms.deinit();
var rs = RepoState.init(gpa, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "branch", .target = null, .value = "trunk" },
} });
_ = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a} });
_ = try rs.crosslink(.{ .hash = h_c, .mtime = 3, .parents = &.{h_a} });
const leaves_out = try rs.openLeavesOf(gpa, "trunk");
gpa.free(leaves_out);
}
test "repostate §8.3 fuzz: dispatch never crashes on adversarial name bytes" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try buildTopoT(&rs);
var prng = std.Random.DefaultPrng.init(0xF055_D47E);
const random = prng.random();
// Every internal parser this loop can reach (rid:, hash-prefix,
// expandTimestamp, punct-retry) sees adversarial bytes, NUL included
// per the wire-data contract; `resolve`'s own postcondition assert
// doubles as the invariant check — any bad candidate rid traps under
// Debug/ReleaseSafe rather than the assertion being separately
// reasserted here.
const alphabet = "abcdef0123456789-: .ABCXYZ_/\\\x00";
var buf: [40]u8 = undefined;
var i: usize = 0;
const iterations = 2000;
while (i < iterations) : (i += 1) {
const len = random.uintLessThan(usize, buf.len) + 1;
for (buf[0..len]) |*c| c.* = alphabet[random.uintLessThan(usize, alphabet.len)];
_ = rs.resolve(buf[0..len], .checkin) catch |err| switch (err) {
error.NotFound, error.Ambiguous => {},
};
}
}
test "repostate §4.2: control named target changes branch without a carrier event or leaf" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const parent = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
const manifest = try rs.crosslink(.{ .hash = h_c, .mtime = 2, .parents = &.{h_a} });
const control = try rs.ingestControlTags(h_b, 3, &.{
.{ .type = .singleton, .name = "branch", .target = h_c, .value = "release" },
});
try t.expectEqualStrings("release", rs.branchOf(manifest, "trunk"));
try t.expect(rs.isLeaf(parent));
try t.expectEqual(manifest, rs.openLeafOf("release").?);
try t.expect(rs.mtimeOf(control) == null);
try t.expectEqual(@as(usize, 0), rs.parentCount(control));
try t.expect(!rs.isLeaf(control));
try t.expect(!rs.isOpenLeaf(control));
}
test "repostate §8.3: parseMtime accepts direct and canonical D-card datetimes" {
const direct = parseMtime("2026-07-10 08:30:15.007").?;
try t.expectEqual(direct, parseMtime("2026-07-10T08:30:15.007").?);
try t.expectEqual(@as(Mtime, 0), parseMtime("1970-01-01").?);
try t.expect(parseMtime("1970-01-01T00:00") == null);
try t.expect(parseMtime("1969-12-31T23:59:59.999") == null);
}
test "repostate §5.1: tagsOf reports singleton/propagate/cancel per T-card sign" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
// §5.1: the leading sign selects the type — `+`→singleton, `*`→propagate,
// `-`→cancel. A cancel row is inactive (tagtype 0) and is not surfaced.
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .singleton, .name = "alpha", .target = null, .value = "av" },
.{ .type = .cancel, .name = "beta", .target = null, .value = null },
.{ .type = .propagate, .name = "gamma", .target = null, .value = "gv" },
} });
const tags = try rs.tagsOf(t.allocator, ra);
defer t.allocator.free(tags);
try t.expectEqual(@as(usize, 2), tags.len);
try t.expectEqualStrings("alpha", tags[0].name);
try t.expectEqual(TagType.singleton, tags[0].tagtype);
try t.expectEqualStrings("av", tags[0].value.?);
try t.expectEqualStrings("gamma", tags[1].name);
try t.expectEqual(TagType.propagate, tags[1].tagtype);
try t.expectEqualStrings("gv", tags[1].value.?);
}
test "repostate §5.1/§2: a later-mtime cancel drops the tag from tagsOf and tagNames" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "sym-rel", .target = null, .value = "rel" },
} });
// §5.1: only an active (tagtype > 0) tag is in effect. A later-mtime
// cancel (tagtype 0) supersedes the active row and takes it out of use.
_ = try rs.ingestControlTags(h_b, 2, &.{
.{ .type = .cancel, .name = "sym-rel", .target = h_a, .value = null },
});
const tags = try rs.tagsOf(t.allocator, ra);
defer t.allocator.free(tags);
try t.expectEqual(@as(usize, 0), tags.len);
const names = try rs.tagNames(t.allocator);
defer t.allocator.free(names);
try t.expectEqual(@as(usize, 0), names.len);
}
test "repostate §5.2: a later write supersedes; tagsOf shows one row with the newer value" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "sym-x", .target = null, .value = "old" },
} });
// §5.2 UNIQUE (rid, tag): a later write for the same (rid, tag) supersedes
// the earlier one — one live entry, carrying the newer value.
_ = try rs.ingestControlTags(h_b, 2, &.{
.{ .type = .propagate, .name = "sym-x", .target = h_a, .value = "new" },
});
const tags = try rs.tagsOf(t.allocator, ra);
defer t.allocator.free(tags);
try t.expectEqual(@as(usize, 1), tags.len);
try t.expectEqualStrings("sym-x", tags[0].name);
try t.expectEqualStrings("new", tags[0].value.?);
}
test "repostate §5.2: tagsOf surfaces each active row's stored value payload" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .singleton, .name = "note", .target = null, .value = "hello world" },
} });
const tags = try rs.tagsOf(t.allocator, ra);
defer t.allocator.free(tags);
try t.expectEqual(@as(usize, 1), tags.len);
try t.expectEqualStrings("note", tags[0].name);
try t.expectEqualStrings("hello world", tags[0].value.?);
}
test "repostate §4.2: a tag on an absent UUID is invisible until that artifact arrives" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1 }); // plain root, no tags
// §4.2 step 1: a tag targeting an absent artifact interns a phantom
// placeholder. Its only holder is a phantom, so the name is not yet in use.
_ = try rs.ingestControlTags(h_c, 2, &.{
.{ .type = .propagate, .name = "sym-rel", .target = h_b, .value = "rel" },
});
{
const names = try rs.tagNames(t.allocator);
defer t.allocator.free(names);
try t.expectEqual(@as(usize, 0), names.len);
}
// The referenced artifact arrives and is crosslinked: the tag on it
// becomes visible (§4.2 step 1).
_ = try rs.crosslink(.{ .hash = h_b, .mtime = 3 });
{
const names = try rs.tagNames(t.allocator);
defer t.allocator.free(names);
try t.expectEqual(@as(usize, 1), names.len);
try t.expectEqualStrings("sym-rel", names[0]);
}
}
test "repostate §4.2/§5.4: a * tag surfaces on the primary child, a + tag does not" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "sym-star", .target = null, .value = "sv" },
.{ .type = .singleton, .name = "sym-solo", .target = null, .value = "so" },
} });
const rb = try rs.crosslink(.{ .hash = h_b, .mtime = 2, .parents = &.{h_a} });
// §5.4: the propagating (*) tag reaches the primary child; the singleton
// (+) tag affects only its target and triggers no walk.
const child = try rs.tagsOf(t.allocator, rb);
defer t.allocator.free(child);
try t.expectEqual(@as(usize, 1), child.len);
try t.expectEqualStrings("sym-star", child[0].name);
// The origin carries both its own singleton and propagating tags.
const origin = try rs.tagsOf(t.allocator, ra);
defer t.allocator.free(origin);
try t.expectEqual(@as(usize, 2), origin.len);
try t.expectEqualStrings("sym-solo", origin[0].name);
try t.expectEqualStrings("sym-star", origin[1].name);
}
test "repostate §6.4: tagsOf surfaces the active tagid-8 branch tag with its value" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "branch", .target = null, .value = "trunk" },
} });
// §6.4: branch membership is the active tagid-8 tag. tagsOf surfaces it
// with its stored value (the branch name).
const tags = try rs.tagsOf(t.allocator, ra);
defer t.allocator.free(tags);
try t.expectEqual(@as(usize, 1), tags.len);
try t.expectEqualStrings("branch", tags[0].name);
try t.expectEqual(TagType.propagate, tags[0].tagtype);
try t.expectEqualStrings("trunk", tags[0].value.?);
}
test "repostate §2: tagNames returns raw reserved-prefix names; only sym- is user-facing" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
// §2 reserves the 'sym-'/'wiki-'/'tkt-'/'event-' prefixes and the fixed
// 'branch'/'closed' names. tagNames returns them raw — presentation (the
// 'sym-' stripping that makes a name user-facing) is the caller's job.
_ = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "branch", .target = null, .value = "trunk" },
.{ .type = .propagate, .name = "sym-trunk", .target = null, .value = null },
.{ .type = .singleton, .name = "wiki-Home", .target = null, .value = null },
} });
const names = try rs.tagNames(t.allocator);
defer t.allocator.free(names);
try t.expectEqual(@as(usize, 3), names.len);
try t.expectEqualStrings("branch", names[0]);
try t.expectEqualStrings("sym-trunk", names[1]);
try t.expectEqualStrings("wiki-Home", names[2]);
}
test "repostate §4.2: a crosslinked artifact carries no implicit cluster tag (content-type unmodeled)" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
var rs = RepoState.init(t.allocator, ms.store());
defer rs.deinit();
// §4.2 step 3 applies an implicit singleton cluster tag to cluster
// artifacts. The crosslink layer does not yet carry artifact content-type
// on a Record, so no cluster tag is ever created; this codifies that a
// plain crosslinked artifact surfaces none.
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1 });
const tags = try rs.tagsOf(t.allocator, ra);
defer t.allocator.free(tags);
try t.expectEqual(@as(usize, 0), tags.len);
const names = try rs.tagNames(t.allocator);
defer t.allocator.free(names);
try t.expectEqual(@as(usize, 0), names.len);
}
test "repostate §5.2: tagsOf and tagNames survive allocation failure without leaking" {
try t.checkAllAllocationFailures(t.allocator, tagsNoLeak, .{});
}
fn tagsNoLeak(gpa: std.mem.Allocator) !void {
var ms = MemStore.init(gpa);
defer ms.deinit();
var rs = RepoState.init(gpa, ms.store());
defer rs.deinit();
const ra = try rs.crosslink(.{ .hash = h_a, .mtime = 1, .tags = &.{
.{ .type = .propagate, .name = "branch", .target = null, .value = "trunk" },
.{ .type = .propagate, .name = "sym-trunk", .target = null, .value = null },
} });
const tags = try rs.tagsOf(gpa, ra);
gpa.free(tags);
const names = try rs.tagNames(gpa);
gpa.free(names);
}
// ---------------------------------------------------------------------------
// Verbatim wire storage (sync §4.1/§7.2): a MemStore entry may hold the stored
// §4.1 container it retransmits, materialising its plaintext lazily.
// ---------------------------------------------------------------------------
const flate = std.compress.flate;
/// A §4.1 container (4-byte big-endian uncompressed length + RFC1950 zlib) —
/// the test's own encoder, mirroring the storage layer's, so a test never
/// depends on a production encode entry point.
fn makeContainer(gpa: std.mem.Allocator, bytes: []const u8) ![]u8 {
var aw = std.Io.Writer.Allocating.init(gpa);
defer aw.deinit();
try aw.ensureUnusedCapacity(64);
try aw.writer.writeInt(u32, @intCast(bytes.len), .big);
const window = try gpa.alloc(u8, flate.max_window_len);
defer gpa.free(window);
var c = try flate.Compress.init(&aw.writer, window, .zlib, .level_6);
try c.writer.writeAll(bytes);
try c.finish();
return try aw.toOwnedSlice();
}
test "repostate sync §4.1/§7.2: putWire stores the container; wire() serves it, content() inflates lazily and memoises" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
const content = "the stored container's plaintext, materialised on first read\n";
const container = try makeContainer(t.allocator, content);
defer t.allocator.free(container);
const rid = try ms.putWire(h_a, container);
// wire() returns the stored container verbatim (borrowed).
const wf = ms.store().wire(rid).?;
try t.expectEqualSlices(u8, container, wf.container);
// content() inflates the container to the original bytes, and memoises: a
// second read returns the very same slice (the borrowed-slice contract).
const first = ms.store().content(rid).?;
try t.expectEqualStrings(content, first);
const second = ms.store().content(rid).?;
try t.expectEqual(first.ptr, second.ptr); // memoised, not re-inflated
}
test "repostate sync §7.2: wire() is null for a raw put and for a phantom" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
const raw_rid = try ms.store().put(h_a, "raw bytes"); // no stored container
try t.expect(ms.store().wire(raw_rid) == null);
const ph_rid = try ms.store().intern(h_b); // phantom
try t.expect(ms.store().wire(ph_rid) == null);
try t.expect(ms.store().content(ph_rid) == null);
}
test "repostate sync §4.1: putWire is idempotent and keeps the first stored container" {
var ms = MemStore.init(t.allocator);
defer ms.deinit();
const content = "idempotent putWire keeps the first container";
const c1 = try makeContainer(t.allocator, content);
defer t.allocator.free(c1);
const c2 = try makeContainer(t.allocator, content);
defer t.allocator.free(c2);
const rid1 = try ms.putWire(h_a, c1);
const rid2 = try ms.putWire(h_a, c2); // same hash: no-op
try t.expectEqual(rid1, rid2);
try t.expectEqualStrings(content, ms.store().content(rid1).?);
}
fn putWireOom(gpa: std.mem.Allocator, container: []const u8) !void {
var ms = MemStore.init(gpa);
defer ms.deinit();
_ = try ms.putWire(h_a, container);
}
test "repostate sync §4.1: putWire propagates allocation failure" {
const container = try makeContainer(t.allocator, "payload for the putWire allocation-failure probe");
defer t.allocator.free(container);
try t.checkAllAllocationFailures(t.allocator, putWireOom, .{container});
}