feat(space): InformationSpace orchestrator + integration; foundation slice complete (WP-0007 T7)

InformationSpace ties the slice together: conformance-gated attach → resolve →
read, with alias() recording coordination decisions in the log. Exposed from the
package root. End-to-end integration test (two folder shards → union read with
layered provenance + chorus + alias redirect + red-link + nonconformant-rejected).
pyflakes clean, 39 tests green. Flips WP-0007 done.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 10:12:38 +02:00
parent b44b2a74a4
commit 517cf1d282
6 changed files with 136 additions and 8 deletions

View File

@@ -17,7 +17,7 @@ Learnings update both SCOPE and INTENT where necessary.
| Layer | State | | Layer | State |
|-------|-------| |-------|-------|
| Code | Python package scaffold (`src/shard_wiki/`, smoke tests only) | | Code | Foundation slice implemented (SHARD-WP-0007): `provenance` + `policy` leaves, `model` (Identity/Placement/Span/Page/CapabilityProfile), `adapters` (contract + FolderAdapter + conformance suite), `coordination` (event-sourced DecisionLog), `union` (resolution + chorus), `InformationSpace` orchestrator — attach→resolve→read works; 39 tests green |
| Intent | `INTENT.md` established; authorization-in-core amendments drafted | | Intent | `INTENT.md` established; authorization-in-core amendments drafted |
| Research | yawex prior art; c2 origins; federation concepts; wikiengines overview (`research/260608-*/`); XWiki/TWiki/Foswiki deep dives (`research/260613-*/`); Xanadu + ZigZag + Roam + Obsidian + Notion + Joplin + Logseq + local-first workspaces (Anytype/AFFiNE/AppFlowy) + Trilium + Wiki.js + Federated Wiki + Wikibase + git-forge wikis + TiddlyWiki + ikiwiki + Quip + MojoMojo + Oddmuse + UseModWiki deep dives & shard-spectrum synthesis (`research/260614-*/`) | | Research | yawex prior art; c2 origins; federation concepts; wikiengines overview (`research/260608-*/`); XWiki/TWiki/Foswiki deep dives (`research/260613-*/`); Xanadu + ZigZag + Roam + Obsidian + Notion + Joplin + Logseq + local-first workspaces (Anytype/AFFiNE/AppFlowy) + Trilium + Wiki.js + Federated Wiki + Wikibase + git-forge wikis + TiddlyWiki + ikiwiki + Quip + MojoMojo + Oddmuse + UseModWiki deep dives & shard-spectrum synthesis (`research/260614-*/`) |
| Demand | NetKingdom integration asks captured, not yet negotiated | | Demand | NetKingdom integration asks captured, not yet negotiated |

View File

@@ -1,10 +1,16 @@
"""shard-wiki — Git-based Markdown wiki orchestrator and federation layer. """shard-wiki — Git-based Markdown wiki orchestrator and federation layer.
See INTENT.md for the authoritative specification of scope and boundaries. See INTENT.md for the authoritative specification of scope and boundaries, and
This package orchestrates wiki-shaped content across heterogeneous *shards*; spec/CoreArchitectureBlueprint.md for the architecture. This package orchestrates
it is not itself a wiki engine. wiki-shaped content across heterogeneous *shards*; it is not itself a wiki engine.
Foundation slice (SHARD-WP-0007): attach folder shard(s) to an
:class:`~shard_wiki.space.InformationSpace`, resolve a name through the union, and
read a page with layered provenance (chorus on ambiguity).
""" """
from shard_wiki.space import InformationSpace
__version__ = "0.0.0" __version__ = "0.0.0"
__all__ = ["__version__"] __all__ = ["__version__", "InformationSpace"]

View File

@@ -13,7 +13,7 @@ only stable data types plus the pure ``effective`` composition. Mechanism never
from __future__ import annotations from __future__ import annotations
import dataclasses import dataclasses
from dataclasses import dataclass, field from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from enum import Enum from enum import Enum

42
src/shard_wiki/space.py Normal file
View File

@@ -0,0 +1,42 @@
"""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 shard_wiki.adapters import ShardAdapter, assert_conformant
from shard_wiki.coordination import DecisionLog, EventType
from shard_wiki.model import Page
from shard_wiki.policy import DEFAULT_POLICY, Policy
from shard_wiki.union import Resolution, UnionGraph
__all__ = ["InformationSpace"]
class InformationSpace:
def __init__(self, space_id: str, policy: Policy = DEFAULT_POLICY) -> None:
self.space_id = space_id
self.log = DecisionLog()
self.union = UnionGraph(space_id, log=self.log, policy=policy)
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)
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()

80
tests/test_integration.py Normal file
View File

@@ -0,0 +1,80 @@
"""End-to-end slice test (SHARD-WP-0007 T7): attach folder shards → resolve → read union."""
import shard_wiki
from shard_wiki import InformationSpace
from shard_wiki.adapters import ConformanceError, FolderAdapter, ShardAdapter
from shard_wiki.model import CapabilityProfile, Identity, Page
from shard_wiki.provenance import ProvenanceEnvelope, Staleness
from shard_wiki.union import ResolutionKind
def _folder(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_public_api_surface():
assert isinstance(shard_wiki.__version__, str) and shard_wiki.__version__
assert shard_wiki.InformationSpace is InformationSpace
def test_attach_resolve_read_union_with_provenance_and_chorus(tmp_path):
space = InformationSpace("team")
space.attach(_folder(tmp_path, "wikiA", {"Home.md": "A home", "OnlyA.md": "a"}))
space.attach(_folder(tmp_path, "wikiB", {"Home.md": "B home", "OnlyB.md": "b"}))
# unique page → single, with provenance attributing the source shard
only_a = space.read("OnlyA")
assert only_a.body == "a"
assert only_a.envelope.source_shard == "wikiA"
assert only_a.envelope.staleness is Staleness.FRESH
assert only_a.envelope.observed_at is not None
# same name in both shards → chorus, divergence recorded both ways (union without erasure)
res = space.resolve("Home")
assert res.kind is ResolutionKind.CHORUS
assert {p.envelope.source_shard for p in res.pages} == {"wikiA", "wikiB"}
for p in res.pages:
assert len(p.envelope.divergence) == 1
# missing → red-link
assert space.resolve("Ghost").is_red_link
def test_alias_redirects_across_the_union(tmp_path):
space = InformationSpace("team")
space.attach(_folder(tmp_path, "wikiA", {"Index.md": "the index"}))
space.alias("Start", "wikiA:Index")
assert space.read("Start").body == "the index"
class _LyingShard(ShardAdapter):
def __init__(self, profile: CapabilityProfile) -> None:
self._p = profile
@property
def shard_id(self) -> str:
return "liar"
def profile(self) -> CapabilityProfile:
return self._p
def keys(self):
return ["X"]
def read(self, key: str) -> Page:
return Page(Identity("not-liar", key), "x", ProvenanceEnvelope(source_shard="x"))
def test_attach_rejects_nonconformant_shard(tmp_path):
good_profile = _folder(tmp_path, "wikiA", {"Home.md": "x"}).profile()
space = InformationSpace("team")
try:
space.attach(_LyingShard(good_profile))
raise AssertionError("expected ConformanceError")
except ConformanceError:
pass

View File

@@ -4,7 +4,7 @@ type: workplan
title: "foundation implementation — model, contract, decision log, union read" title: "foundation implementation — model, contract, decision log, union read"
domain: whynot domain: whynot
repo: shard-wiki repo: shard-wiki
status: active status: done
owner: tegwick owner: tegwick
topic_slug: whynot topic_slug: whynot
created: "2026-06-15" created: "2026-06-15"
@@ -131,7 +131,7 @@ the same name → chorus; alias from the log redirects resolution.
```task ```task
id: SHARD-WP-0007-T7 id: SHARD-WP-0007-T7
status: todo status: done
priority: medium priority: medium
state_hub_task_id: "86c0b255-e373-460d-859d-a39bf6ea4ffb" state_hub_task_id: "86c0b255-e373-460d-859d-a39bf6ea4ffb"
``` ```