test(coordination): cross-process read-your-writes + fold parity (WP-0009 T3)

Verify the git backend's fold reads the durable log into CoordinationState with
unchanged semantics, and that read-your-writes holds across separate handles and
separate OS processes against the same space ref (one test spawns a real
subprocess that appends, then reads it back). Cross-process fold equals the
in-memory fold for the same event sequence (derived = f(log)).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 01:47:08 +02:00
parent 34432c2e15
commit f0fee65cc0

View File

@@ -0,0 +1,83 @@
"""Cross-process read-your-writes over the git log + fold parity (SHARD-WP-0009 T3).
The git backend's value over the in-memory double is that the totally ordered log is durable and
shared: a write by one process/handle is immediately visible to another opening the same ref, and
the derived fold is identical to the in-memory fold of the same event sequence (derived = f(log)).
"""
import os
import subprocess
import sys
import textwrap
from pathlib import Path
from shard_wiki.coordination import (
DecisionLog,
EventType,
GitEventStore,
InMemoryEventStore,
)
_SRC = str(Path(__file__).resolve().parents[1] / "src")
def test_new_handle_sees_prior_writes(tmp_path):
repo = tmp_path / "coord"
writer = DecisionLog(GitEventStore(repo))
writer.append("s", EventType.ALIAS_SET, {"alias": "Home", "target": "shardA:Index"})
writer.append("s", EventType.BINDING_MADE, {"members": ["a", "b"]})
# A second, independent handle on the same repo — read-your-writes across handles.
reader = DecisionLog(GitEventStore(repo))
assert [e.seq for e in reader.events("s")] == [0, 1]
assert reader.fold("s").resolve_alias("Home") == "shardA:Index"
def test_append_in_separate_process_is_visible(tmp_path):
repo = tmp_path / "coord"
# Seed from this process so the repo exists.
DecisionLog(GitEventStore(repo)).append(
"s", EventType.ALIAS_SET, {"alias": "A", "target": "x:1"}
)
child = textwrap.dedent(
f"""
from shard_wiki.coordination import DecisionLog, EventType, GitEventStore
log = DecisionLog(GitEventStore({str(repo)!r}))
log.append("s", EventType.ALIAS_SET, {{"alias": "B", "target": "x:2"}})
"""
)
result = subprocess.run(
[sys.executable, "-c", child],
capture_output=True,
text=True,
env={"PYTHONPATH": _SRC, "PATH": os.environ.get("PATH", "")},
)
assert result.returncode == 0, result.stderr
# This process, with a fresh handle, sees the child's append in order.
reader = DecisionLog(GitEventStore(repo))
assert [e.payload["alias"] for e in reader.events("s")] == ["A", "B"]
assert [e.seq for e in reader.events("s")] == [0, 1]
def test_cross_process_fold_equals_in_memory_fold(tmp_path):
sequence = [
(EventType.ALIAS_SET, {"alias": "Home", "target": "shardA:Index"}),
(EventType.BINDING_MADE, {"members": ["a", "b"]}),
(EventType.BINDING_MADE, {"members": ["b", "c"]}),
(EventType.PAGE_FORKED, {"source": "p", "fork": "q"}),
(EventType.ALIAS_SET, {"alias": "Home", "target": "shardB:Main"}),
]
mem = DecisionLog(InMemoryEventStore())
for typ, payload in sequence:
mem.append("s", typ, payload)
repo = tmp_path / "coord"
DecisionLog(GitEventStore(repo)) # init repo
for typ, payload in sequence:
# Each append from a fresh handle to simulate distinct writers over time.
DecisionLog(GitEventStore(repo)).append("s", typ, payload)
git_state = DecisionLog(GitEventStore(repo)).fold("s")
mem_state = mem.fold("s")
assert git_state.aliases == mem_state.aliases
assert git_state.equivalence_groups == mem_state.equivalence_groups
assert git_state.equivalent_to("a") == frozenset({"a", "b", "c"})