"""InformationSpace — the thin orchestrator entry tying the slice together (blueprint §3 L6). A root entity / information space: shards attach to it (conformance-gated, TSD §A.2), a coordination decision log records its canonical decisions, and a derived union resolves names to pages. This is the L6 consumer surface for the foundation slice (attach → resolve → read); a network API is a later workplan. """ from __future__ import annotations from pathlib import Path from shard_wiki.adapters import ShardAdapter, assert_conformant from shard_wiki.coordination import ( ApplyResult, DecisionLog, EventStore, EventType, GitEventStore, Overlay, OverlayEngine, ) from shard_wiki.incremental import ConsistencyReport, UnionIndex from shard_wiki.model import Page from shard_wiki.policy import DEFAULT_POLICY, Policy from shard_wiki.union import Resolution, UnionGraph from shard_wiki.views import ( AllPagesEntry, BackLink, ChangeEntry, SiteMapNode, all_pages, build_backlinks, recent_changes, site_map, ) __all__ = ["InformationSpace"] class InformationSpace: def __init__( self, space_id: str, policy: Policy = DEFAULT_POLICY, *, store: EventStore | None = None, ) -> None: """Tie the slice together. ``store`` selects the coordination-log backend: the default in-memory store (tests) or a git-addressable one. Use :meth:`git_backed` for the latter.""" self.space_id = space_id self.log = DecisionLog(store) self.union = UnionGraph(space_id, log=self.log, policy=policy) self.overlays = OverlayEngine(space_id, self.log) self._index: UnionIndex | None = None # maintained derived tier, built lazily self._index_stale = True @classmethod def git_backed( cls, space_id: str, repo_path: str | Path, policy: Policy = DEFAULT_POLICY, ) -> InformationSpace: """An information space whose coordination log is git-addressable (history/patch/review/ backup — I-6). The decision log lives in the git repo at ``repo_path``.""" return cls(space_id, policy, store=GitEventStore(repo_path)) def attach(self, adapter: ShardAdapter) -> None: """Attach a shard — only if it passes conformance (verified profile, I-3/§6.6).""" assert_conformant(adapter) self.union.attach(adapter) self._index_stale = True def alias(self, name: str, target: str, actor: str | None = None) -> None: """Record a coordination-canonical alias (``name`` → ``"shard:key"``) in the log.""" self.log.append( self.space_id, EventType.ALIAS_SET, {"alias": name, "target": target}, actor=actor ) def resolve(self, name: str) -> Resolution: return self.union.resolve(name) def read(self, name: str) -> Page: """Resolve and return the page (or the canonical pick of a chorus). KeyError if red-link.""" return self.union.resolve(name).single() def overlay(self, name: str, body: str, actor: str | None = None) -> Overlay: """Draft a non-destructive overlay against the resolved page (overlay-before-mutation).""" page = self.read(name) return self.overlays.draft(page.identity, body, page.envelope.source_rev, actor=actor) def apply_overlay(self, overlay_id: str) -> ApplyResult: """Apply a draft overlay to its target shard (apply-under-drift, §8.6).""" overlay = self.overlays.get(overlay_id) if overlay is None: raise KeyError(overlay_id) adapter = self.union.shard(overlay.target.shard) if adapter is None: raise KeyError(overlay.target.shard) return self.overlays.apply(overlay_id, adapter) def edit(self, name: str, body: str, actor: str | None = None) -> ApplyResult: """Edit a page through the one principled path: draft an overlay, then apply it. A write-through-capable target fast-forwards (write-through); a read-only target keeps the draft as local truth (I-5: overlay before mutation, always).""" overlay = self.overlay(name, body, actor=actor) result = self.apply_overlay(overlay.overlay_id) self._index_stale = True # the applied edit changes the derived tier return result # --- maintained derived tier (SHARD-WP-0011): incremental-first, rebuild as fallback --- @property def index(self) -> UnionIndex: """The maintained equivalence index (built lazily; rebuilt when the union has changed).""" if self._index is None: self._index = UnionIndex(self.union, self.log, self.space_id) elif self._index_stale: self._index.rebuild() # bounded fallback after a mutation self._index_stale = False return self._index def reindex(self) -> None: """Force a full rebuild of the maintained derived tier (the explicit fallback path).""" self.index.rebuild() def verify_index(self) -> ConsistencyReport: """Run the I-2 consistency-checker over the maintained tier; self-heal any drift.""" return self.index.verify() # --- derived views (SHARD-WP-0010): recomputable, provenance-carrying, presentation-free --- def backlinks(self, name: str, *, camelcase: bool = False) -> tuple[BackLink, ...]: """Pages across the union that link to ``name`` (UC-18).""" return build_backlinks(self.union, camelcase=camelcase).to(name) def recent_changes(self, *, limit: int | None = None) -> tuple[ChangeEntry, ...]: """The merged newest-first change feed: coordination journal + shard signals (UC-17).""" return recent_changes(self.union, self.log, self.space_id, limit=limit) def all_pages(self) -> tuple[AllPagesEntry, ...]: """The union's distinct pages, collapsed via the maintained equivalence index.""" return all_pages(self.union, equivalence_groups=self.index.equivalence_groups()) def site_map(self) -> SiteMapNode: """The union namespace tree built from page placements.""" return site_map(self.union)