generated from coulomb/repo-seed
feat(incremental): wire maintained tier behind views; rebuild fallback (WP-0011 T4)
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>
This commit is contained in:
91
src/shard_wiki/incremental/union_index.py
Normal file
91
src/shard_wiki/incremental/union_index.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user