generated from coulomb/repo-seed
Route InformationSpace.all_pages through a maintained UnionIndex: equivalence is served from the incrementally maintained index (curator bindings re-synced live from the log fold + detected content edges), exposed in decision-log string form so results are a behaviour-preserving superset. The index is built lazily and rebuilt (bounded fallback) when the union mutates (attach/edit invalidate it); reindex() forces a rebuild and verify_index() runs the I-2 self-healing checker. all_pages() gains an optional equivalence_groups source (default = fold) so direct callers are unaffected. SCOPE updated; WP-0011 done. 173 tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
92 lines
3.6 KiB
Python
92 lines
3.6 KiB
Python
"""UnionIndex — the maintained derived tier wired behind resolution + views (SHARD-WP-0011 T4).
|
||
|
||
Wraps a :class:`UnionGraph` + decision log with an incrementally maintained
|
||
:class:`EquivalenceIndex`. Content equivalence is kept fresh by deltas (``note_change`` /
|
||
``note_removed``); curator bindings are re-synced live from the log fold. A full :meth:`rebuild`
|
||
is the bounded fallback. :meth:`verify` runs the I-2 consistency-checker over the live source.
|
||
|
||
Consumer-visible results are unchanged — equivalence groups are exposed in the same string form the
|
||
decision-log fold uses, a *superset* that additionally collapses genuine content duplicates — only
|
||
freshness and cost differ (recompute-on-read becomes change-driven).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from shard_wiki.coordination import DecisionLog
|
||
from shard_wiki.incremental.equivalence import EquivalenceIndex
|
||
from shard_wiki.incremental.verification import (
|
||
ConsistencyChecker,
|
||
ConsistencyReport,
|
||
derived_digest,
|
||
)
|
||
from shard_wiki.model import Identity, Page
|
||
from shard_wiki.union import UnionGraph
|
||
|
||
__all__ = ["UnionIndex"]
|
||
|
||
|
||
def _identity(token: str) -> Identity:
|
||
shard, _, key = token.partition(":")
|
||
return Identity(shard, key)
|
||
|
||
|
||
class UnionIndex:
|
||
"""An incrementally maintained equivalence index over a union, with a rebuild fallback."""
|
||
|
||
def __init__(self, union: UnionGraph, log: DecisionLog, space: str) -> None:
|
||
self._union = union
|
||
self._log = log
|
||
self._space = space
|
||
self._eq = EquivalenceIndex()
|
||
self.rebuild()
|
||
|
||
def rebuild(self) -> None:
|
||
"""The bounded fallback: re-derive the whole index from current union pages + bindings."""
|
||
self._eq.build(self._union.iter_pages())
|
||
self._sync_curator()
|
||
|
||
def note_change(self, page: Page) -> None:
|
||
"""Change-driven update for one added/edited page (the operational path)."""
|
||
self._eq.update(page)
|
||
|
||
def note_removed(self, identity: Identity) -> None:
|
||
self._eq.remove(identity)
|
||
|
||
def _sync_curator(self) -> None:
|
||
"""Re-sync curator equivalence from the live decision-log fold (cheap, always correct)."""
|
||
groups = self._log.fold(self._space).equivalence_groups
|
||
edges: list[tuple[Identity, Identity]] = []
|
||
for group in groups:
|
||
members = [_identity(m) for m in group]
|
||
edges.extend((members[0], other) for other in members[1:])
|
||
self._eq.set_curator_edges(edges)
|
||
|
||
def equivalence_groups(self) -> tuple[frozenset[str], ...]:
|
||
"""Equivalence groups in decision-log string form (curator ∪ content), for the views."""
|
||
self._sync_curator()
|
||
return tuple(
|
||
frozenset(str(identity) for identity in group) for group in self._eq.groups()
|
||
)
|
||
|
||
def digest(self) -> str:
|
||
"""The Merkle-style digest of the maintained derived tier (I-2)."""
|
||
self._sync_curator()
|
||
return derived_digest(self._eq)
|
||
|
||
def verify(self) -> ConsistencyReport:
|
||
"""Check the maintained index against a from-scratch fold of the live source; self-heal."""
|
||
self._sync_curator()
|
||
checker = ConsistencyChecker(
|
||
self._eq,
|
||
pages=lambda: list(self._union.iter_pages()),
|
||
curator_edges=self._curator_pairs,
|
||
)
|
||
return checker.check_and_repair()
|
||
|
||
def _curator_pairs(self) -> list[tuple[Identity, Identity]]:
|
||
pairs: list[tuple[Identity, Identity]] = []
|
||
for group in self._log.fold(self._space).equivalence_groups:
|
||
members = [_identity(m) for m in group]
|
||
pairs.extend((members[0], other) for other in members[1:])
|
||
return pairs
|