From d7d046cac0056830dbc30c6a63e858fa9ba44806 Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 16 Jun 2026 02:14:32 +0200 Subject: [PATCH] test(incremental): delta maintenance == rebuild, retraction + split (WP-0011 T2) Verify change-driven maintenance keeps the equivalence index equal to a from-scratch rebuild under add / edit / remove: an edit into a new bucket retracts the stale edge, an edit into equivalence adds one, and removing a connector node propagates a retraction that splits a chorus. Equality checked against a fresh build() oracle on every operation. Co-Authored-By: Claude Opus 4.8 --- tests/test_incremental_maintenance.py | 84 +++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 tests/test_incremental_maintenance.py diff --git a/tests/test_incremental_maintenance.py b/tests/test_incremental_maintenance.py new file mode 100644 index 0000000..6a38457 --- /dev/null +++ b/tests/test_incremental_maintenance.py @@ -0,0 +1,84 @@ +"""Incremental maintenance == rebuild, with retraction + propagation (SHARD-WP-0011 T2).""" + +from shard_wiki.incremental import EquivalenceIndex +from shard_wiki.model import Identity, Page +from shard_wiki.provenance import ProvenanceEnvelope + + +def _page(shard, key, body): + return Page( + identity=Identity(shard, key), + body=body, + envelope=ProvenanceEnvelope(source_shard=shard), + ) + + +def _rebuilt(pages, curator=()): + idx = EquivalenceIndex() + idx.build(pages, curator) + return idx + + +def _equal(a, b): + return a.edges() == b.edges() and set(a.groups()) == set(b.groups()) + + +def test_add_keeps_index_equal_to_rebuild(): + pages = [_page("A", "Foo", "same content here"), _page("B", "Bar", "same content here")] + idx = EquivalenceIndex() + for p in pages: + idx.add(p) + assert _equal(idx, _rebuilt(pages)) + assert idx.groups() # the two collapse + + +def test_remove_keeps_index_equal_to_rebuild(): + pages = [ + _page("A", "Foo", "same content here"), + _page("B", "Bar", "same content here"), + _page("C", "Baz", "unrelated isolated material entirely"), + ] + idx = _rebuilt(pages) + idx.remove(Identity("B", "Bar")) + assert _equal(idx, _rebuilt([pages[0], pages[2]])) + + +def test_edit_into_new_bucket_retracts_stale_edge(): + a = _page("A", "Foo", "shared identical body text") + b = _page("B", "Bar", "shared identical body text") + idx = _rebuilt([a, b]) + assert idx.groups() # A ≡ B initially + # Edit B to something completely different: it exits A's buckets, the edge is retracted. + b2 = _page("B", "Bar", "now totally divergent unrelated prose about nothing") + idx.update(b2) + assert idx.groups() == () # stale edge gone + assert _equal(idx, _rebuilt([a, b2])) + + +def test_edit_into_equivalence_adds_edge(): + a = _page("A", "Foo", "target body to converge on later") + b = _page("B", "Bar", "initially completely separate writing here") + idx = _rebuilt([a, b]) + assert idx.groups() == () + b2 = _page("B", "Bar", "target body to converge on later") # now identical to A + idx.update(b2) + assert idx.equivalent_to(Identity("A", "Foo")) == frozenset( + {Identity("A", "Foo"), Identity("B", "Bar")} + ) + assert _equal(idx, _rebuilt([a, b2])) + + +def test_removing_connector_splits_a_chorus(): + # Curator chain A—B—C (no direct A—C): one group of three. + a, b, c = (_page("A", "X", "aaa"), _page("B", "Y", "bbb"), _page("C", "Z", "ccc")) + idx = EquivalenceIndex() + for p in (a, b, c): + idx.add(p) + idx.bind(a.identity, b.identity) + idx.bind(b.identity, c.identity) + assert idx.equivalent_to(a.identity) == {a.identity, b.identity, c.identity} + # Removing the connector B retracts/propagates: the chorus splits. + idx.remove(b.identity) + assert idx.groups() == () + chain = [(a.identity, b.identity), (b.identity, c.identity)] + assert _equal(idx, _rebuilt([a, c], curator=chain))