generated from coulomb/repo-seed
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>
70 lines
2.6 KiB
Python
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
|