generated from coulomb/repo-seed
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:
2
SCOPE.md
2
SCOPE.md
@@ -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 |
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
@@ -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
42
src/shard_wiki/space.py
Normal 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
80
tests/test_integration.py
Normal 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
|
||||||
@@ -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"
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user