Files
shard-wiki/tests/test_union.py
tegwick b44b2a74a4 feat(policy,union): policy leaf + UnionGraph resolution with chorus (WP-0007 T6)
policy/ leaf (CanonicalSource presets, default chorus). union/ UnionGraph:
identity-keyed resolve (alias-redirect via log fold → union lookup → chorus →
red-link); chorus records divergent peers in each page's provenance envelope
(union without erasure); designated-canonical orders the pick. Imports down only.
6 tests green. (blueprint §8.4, ADR-01)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 10:02:09 +02:00

70 lines
2.6 KiB
Python

"""Tests for union resolution (SHARD-WP-0007 T6)."""
from shard_wiki.adapters import FolderAdapter
from shard_wiki.coordination import DecisionLog, EventType
from shard_wiki.policy import CanonicalSource, Policy
from shard_wiki.union import ResolutionKind, UnionGraph
def _shard(tmp_path, name, files):
root = tmp_path / name
for rel, text in files.items():
p = root / rel
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(text, encoding="utf-8")
return FolderAdapter(name, root)
def test_single_resolution(tmp_path):
u = UnionGraph("space")
u.attach(_shard(tmp_path, "shardA", {"Home.md": "A home"}))
res = u.resolve("Home")
assert res.kind is ResolutionKind.SINGLE
assert res.single().body == "A home"
def test_red_link_when_absent(tmp_path):
u = UnionGraph("space")
u.attach(_shard(tmp_path, "shardA", {"Home.md": "x"}))
assert u.resolve("Nope").is_red_link
def test_chorus_on_ambiguity_records_divergence(tmp_path):
u = UnionGraph("space")
u.attach(_shard(tmp_path, "shardA", {"Home.md": "A home"}))
u.attach(_shard(tmp_path, "shardB", {"Home.md": "B home"}))
res = u.resolve("Home")
assert res.kind is ResolutionKind.CHORUS
assert {p.body for p in res.pages} == {"A home", "B home"}
# Each page names its divergent peer — union without erasure.
a = next(p for p in res.pages if p.identity.shard == "shardA")
assert a.envelope.divergence == ("shardB:Home",)
def test_designated_canonical_orders_first(tmp_path):
policy = Policy(canonical_source=CanonicalSource.DESIGNATED_CANONICAL, designated_shard="shardB")
u = UnionGraph("space", policy=policy)
u.attach(_shard(tmp_path, "shardA", {"Home.md": "A"}))
u.attach(_shard(tmp_path, "shardB", {"Home.md": "B"}))
res = u.resolve("Home")
assert res.kind is ResolutionKind.CHORUS
assert res.single().identity.shard == "shardB" # designated wins the canonical pick
def test_alias_from_log_redirects(tmp_path):
log = DecisionLog()
log.append("space", EventType.ALIAS_SET, {"alias": "Start", "target": "shardA:Index"})
u = UnionGraph("space", log=log)
u.attach(_shard(tmp_path, "shardA", {"Index.md": "the index"}))
res = u.resolve("Start")
assert res.kind is ResolutionKind.SINGLE
assert res.single().body == "the index"
def test_dangling_alias_falls_through_to_red_link(tmp_path):
log = DecisionLog()
log.append("space", EventType.ALIAS_SET, {"alias": "Start", "target": "shardA:Missing"})
u = UnionGraph("space", log=log)
u.attach(_shard(tmp_path, "shardA", {"Index.md": "x"}))
assert u.resolve("Start").is_red_link