generated from coulomb/repo-seed
feat(views): wire derived views onto InformationSpace + integration (WP-0010 T5)
Expose backlinks(name), recent_changes(), all_pages(), site_map() on InformationSpace. Integration test exercises all four over two shards (BackLinks aggregate across shards, AllPages/SiteMap span the union, RecentChanges merges an alias decision with shard edits). SCOPE updated; WP-0010 done. 152 tests green. 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 |
|
||||
|-------|-------|
|
||||
| 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, overlay-aware), `InformationSpace` orchestrator. Write path added (SHARD-WP-0008): writable adapter, overlay engine (draft→patch→apply-under-drift), edit() unifies write-through + overlay-before-mutation. Native engine implemented (SHARD-WP-0014): `engine` (kernel + typed-extension runtime + per-shard activation [ADR-0001] + capability-profile-from-extensions + EngineShardAdapter + the `ext.struct` built-in) — an engine shard attaches to an InformationSpace as a canonical-mode shard. Git-backed coordination log (SHARD-WP-0009): `DecisionLog` storage factored behind an `EventStore`; `GitEventStore` makes the log git-addressable (each space a ref, append = immutable CAS-guarded commit), a per-space `AppendAuthority` (lease) gives a single-writer total order with re-grantable HA hand-off, cross-process read-your-writes verified, and a verbatim one-time importer (`migrate_space`/JSONL) replays in-memory logs into git; `InformationSpace.git_backed(...)` wires it. 128 tests green, ~97% coverage |
|
||||
| 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, overlay-aware), `InformationSpace` orchestrator. Write path added (SHARD-WP-0008): writable adapter, overlay engine (draft→patch→apply-under-drift), edit() unifies write-through + overlay-before-mutation. Native engine implemented (SHARD-WP-0014): `engine` (kernel + typed-extension runtime + per-shard activation [ADR-0001] + capability-profile-from-extensions + EngineShardAdapter + the `ext.struct` built-in) — an engine shard attaches to an InformationSpace as a canonical-mode shard. Git-backed coordination log (SHARD-WP-0009): `DecisionLog` storage factored behind an `EventStore`; `GitEventStore` makes the log git-addressable (each space a ref, append = immutable CAS-guarded commit), a per-space `AppendAuthority` (lease) gives a single-writer total order with re-grantable HA hand-off, cross-process read-your-writes verified, and a verbatim one-time importer (`migrate_space`/JSONL) replays in-memory logs into git; `InformationSpace.git_backed(...)` wires it. Derived views (SHARD-WP-0010): `views` (wikilink + red-link model, BackLinks, RecentChanges, AllPages/SiteMap) — recomputable, provenance-carrying, presentation-free, exposed via `InformationSpace.backlinks/recent_changes/all_pages/site_map`. 152 tests green, ~97% coverage |
|
||||
| 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-*/`) |
|
||||
| Demand | NetKingdom integration asks captured, not yet negotiated |
|
||||
|
||||
@@ -23,6 +23,16 @@ from shard_wiki.coordination import (
|
||||
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"]
|
||||
|
||||
@@ -92,3 +102,21 @@ class InformationSpace:
|
||||
draft as local truth (I-5: overlay before mutation, always)."""
|
||||
overlay = self.overlay(name, body, actor=actor)
|
||||
return self.apply_overlay(overlay.overlay_id)
|
||||
|
||||
# --- 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, chorus/equivalence-collapsed with divergence noted."""
|
||||
return all_pages(self.union)
|
||||
|
||||
def site_map(self) -> SiteMapNode:
|
||||
"""The union namespace tree built from page placements."""
|
||||
return site_map(self.union)
|
||||
|
||||
52
tests/test_views_integration.py
Normal file
52
tests/test_views_integration.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""Integration: derived views exposed on InformationSpace over two shards (SHARD-WP-0010 T5)."""
|
||||
|
||||
from shard_wiki.adapters import FolderAdapter
|
||||
from shard_wiki.model import Identity
|
||||
from shard_wiki.space import InformationSpace
|
||||
|
||||
|
||||
def _shard(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 _space(tmp_path):
|
||||
space = InformationSpace("space")
|
||||
space.attach(
|
||||
_shard(tmp_path, "wiki", {"Home.md": "welcome, see [[Guide]]", "Guide.md": "the guide"})
|
||||
)
|
||||
space.attach(_shard(tmp_path, "notes", {"Daily.md": "today I read [[Guide]]"}))
|
||||
return space
|
||||
|
||||
|
||||
def test_backlinks_across_two_shards(tmp_path):
|
||||
space = _space(tmp_path)
|
||||
sources = {bl.source for bl in space.backlinks("Guide")}
|
||||
assert sources == {Identity("wiki", "Home"), Identity("notes", "Daily")}
|
||||
|
||||
|
||||
def test_all_pages_and_site_map_over_union(tmp_path):
|
||||
space = _space(tmp_path)
|
||||
names = {e.name for e in space.all_pages()}
|
||||
assert names == {"Home", "Guide", "Daily"}
|
||||
leaves = {p.key for p in space.site_map().pages}
|
||||
assert {"Home", "Guide", "Daily"} <= leaves
|
||||
|
||||
|
||||
def test_recent_changes_includes_alias_and_edits(tmp_path):
|
||||
space = _space(tmp_path)
|
||||
space.alias("Start", "wiki:Home", actor="ana")
|
||||
feed = space.recent_changes()
|
||||
kinds = {e.kind for e in feed}
|
||||
assert "alias" in kinds and "edit" in kinds
|
||||
alias = next(e for e in feed if e.kind == "alias")
|
||||
assert alias.source == "coordination" and alias.actor == "ana"
|
||||
|
||||
|
||||
def test_red_link_creates_no_backlink_via_space(tmp_path):
|
||||
space = _space(tmp_path)
|
||||
assert space.backlinks("Nonexistent") == ()
|
||||
@@ -4,7 +4,7 @@ type: workplan
|
||||
title: "derived views — wikilinks, BackLinks, RecentChanges, AllPages/SiteMap"
|
||||
domain: whynot
|
||||
repo: shard-wiki
|
||||
status: active
|
||||
status: done
|
||||
owner: tegwick
|
||||
topic_slug: whynot
|
||||
created: "2026-06-15"
|
||||
@@ -36,7 +36,7 @@ later by SHARD-WP-0011) and carry provenance. Presentation stays out of core (L6
|
||||
|
||||
```task
|
||||
id: SHARD-WP-0010-T1
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "792660c3-9be9-4771-9f51-69d01f0c7f13"
|
||||
```
|
||||
@@ -51,7 +51,7 @@ red-link, CamelCase opt-in.
|
||||
|
||||
```task
|
||||
id: SHARD-WP-0010-T2
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "431a54c3-82b5-4b08-b3f0-762624d4c91d"
|
||||
```
|
||||
@@ -65,7 +65,7 @@ chorus pages aggregate.
|
||||
|
||||
```task
|
||||
id: SHARD-WP-0010-T3
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "270c1c31-0445-42b9-9a49-92d32c298eb2"
|
||||
```
|
||||
@@ -79,7 +79,7 @@ alias both appear, newest-first; per-shard attribution present.
|
||||
|
||||
```task
|
||||
id: SHARD-WP-0010-T4
|
||||
status: todo
|
||||
status: done
|
||||
priority: low
|
||||
state_hub_task_id: "898ba43e-cdef-4ce8-9fa3-4ce60ebb4fdd"
|
||||
```
|
||||
@@ -92,7 +92,7 @@ collapses to one entry with divergence noted; sitemap reflects paths.
|
||||
|
||||
```task
|
||||
id: SHARD-WP-0010-T5
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "7157544b-5d3b-45a2-ba5a-c32244c59323"
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user