generated from coulomb/repo-seed
feat(engine): ext.struct typed-records built-in; close engine implementation (WP-0014 T6)
engine/extensions/struct.py: ext.struct (typed records) — in-text frontmatter parse + ON_WRITE validation (allowed-fields, content-preserving), ON_READ tags PageShape.TYPED_RECORD, ON_PROFILE raises structured-payload. Proves the framework: feature absent when off (opaque prose, honest profile), present + profile-reflected when on; works through InformationSpace edit. SCOPE updated. 6 tests, 107 total, ~97% coverage, pyflakes clean. Marks T6 + SHARD-WP-0014 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 |
|
||||
|-------|-------|
|
||||
| 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. attach→resolve→read + edit/overlay/apply work; 64 tests green |
|
||||
| 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. 107 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 |
|
||||
|
||||
10
src/shard_wiki/engine/extensions/__init__.py
Normal file
10
src/shard_wiki/engine/extensions/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
||||
"""engine/extensions/ — built-in typed extensions for the wiki engine.
|
||||
|
||||
Each is a typed :class:`~shard_wiki.engine.extension.Extension` a shard activates only if needed.
|
||||
``ext.struct`` (typed records) is the first; more (views, addressing, computational, authz) follow
|
||||
the same pattern.
|
||||
"""
|
||||
|
||||
from shard_wiki.engine.extensions.struct import StructExt, parse_frontmatter
|
||||
|
||||
__all__ = ["StructExt", "parse_frontmatter"]
|
||||
81
src/shard_wiki/engine/extensions/struct.py
Normal file
81
src/shard_wiki/engine/extensions/struct.py
Normal file
@@ -0,0 +1,81 @@
|
||||
"""ext.struct — typed records, a first built-in extension (WikiEngineCoreArchitecture X-STRUCT).
|
||||
|
||||
Demonstrates the typed-extension framework end-to-end. A page may carry a leading in-text
|
||||
frontmatter block (`---` … `---`, `key: value` lines — git-diffable structure, blueprint T12).
|
||||
With this extension **active**, the engine:
|
||||
|
||||
- **ON_WRITE** validates the structured block (optionally against an allowed-field set) — a
|
||||
malformed/disallowed structured page is rejected; the body is otherwise unchanged
|
||||
(content-preserving, so write conformance holds);
|
||||
- **ON_READ** tags such pages as `PageShape.TYPED_RECORD`;
|
||||
- **ON_PROFILE** raises the shard's profile with the `structured-payload` verb (E-5).
|
||||
|
||||
With the extension **inactive**, the kernel treats the same page as opaque prose — the feature
|
||||
is genuinely absent (honest profile). This is "activate only what you need" in action.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
from collections.abc import Iterable, Mapping
|
||||
from typing import Any
|
||||
|
||||
from shard_wiki.engine.extension import Extension, Hook
|
||||
from shard_wiki.engine.profile import ProfileContribution
|
||||
from shard_wiki.model import Page, PageShape, Verb
|
||||
|
||||
__all__ = ["StructExt", "parse_frontmatter"]
|
||||
|
||||
|
||||
def parse_frontmatter(body: str) -> tuple[dict[str, str], bool]:
|
||||
"""Parse a leading ``---`` … ``---`` block of ``key: value`` lines.
|
||||
|
||||
Returns ``(fields, has_block)``. An unterminated opening ``---`` is *not* a valid block.
|
||||
"""
|
||||
lines = body.splitlines()
|
||||
if not lines or lines[0].strip() != "---":
|
||||
return {}, False
|
||||
fields: dict[str, str] = {}
|
||||
for line in lines[1:]:
|
||||
if line.strip() == "---":
|
||||
return fields, True
|
||||
if ":" in line:
|
||||
key, _, value = line.partition(":")
|
||||
fields[key.strip()] = value.strip()
|
||||
return {}, False # no closing fence → not a frontmatter block
|
||||
|
||||
|
||||
class StructExt(Extension):
|
||||
id = "ext.struct"
|
||||
declares_types = ("record",)
|
||||
provides = ("capability.wiki.page-model",)
|
||||
|
||||
def __init__(self, allowed_fields: Iterable[str] | None = None) -> None:
|
||||
self._allowed: set[str] | None = set(allowed_fields) if allowed_fields is not None else None
|
||||
|
||||
def hooks(self) -> Mapping[Hook, Any]:
|
||||
return {
|
||||
Hook.ON_WRITE: self._on_write,
|
||||
Hook.ON_READ: self._on_read,
|
||||
Hook.ON_PROFILE: self._on_profile,
|
||||
}
|
||||
|
||||
def _on_write(self, body: str, ctx: Any) -> str:
|
||||
fields, has_block = parse_frontmatter(body)
|
||||
if has_block and self._allowed is not None:
|
||||
disallowed = set(fields) - self._allowed
|
||||
if disallowed:
|
||||
raise ValueError(f"ext.struct: disallowed fields {sorted(disallowed)}")
|
||||
return body # structure stays in-text (git-diffable); body unchanged
|
||||
|
||||
def _on_read(self, page: Page, ctx: Any) -> Page:
|
||||
_, has_block = parse_frontmatter(page.body)
|
||||
return dataclasses.replace(page, shape=PageShape.TYPED_RECORD) if has_block else page
|
||||
|
||||
def _on_profile(self, payload: Any, ctx: Any) -> ProfileContribution:
|
||||
return ProfileContribution(verbs_add=frozenset({Verb.STRUCTURED_PAYLOAD}))
|
||||
|
||||
@staticmethod
|
||||
def fields(body: str) -> dict[str, str]:
|
||||
"""Parsed structured fields of a page body (empty if it has no frontmatter block)."""
|
||||
return parse_frontmatter(body)[0]
|
||||
64
tests/test_ext_struct.py
Normal file
64
tests/test_ext_struct.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""Tests for the ext.struct built-in extension (SHARD-WP-0014 T6)."""
|
||||
|
||||
import pytest
|
||||
|
||||
from shard_wiki import InformationSpace
|
||||
from shard_wiki.adapters import assert_conformant
|
||||
from shard_wiki.engine import ExtensionRuntime, build_engine_shard
|
||||
from shard_wiki.engine.extensions import StructExt, parse_frontmatter
|
||||
from shard_wiki.model import PageShape, Verb
|
||||
|
||||
_STRUCT_PAGE = "---\ntitle: Spec\nstatus: draft\n---\nbody text"
|
||||
|
||||
|
||||
def test_parse_frontmatter():
|
||||
fields, has = parse_frontmatter(_STRUCT_PAGE)
|
||||
assert has and fields == {"title": "Spec", "status": "draft"}
|
||||
assert parse_frontmatter("just prose") == ({}, False)
|
||||
assert parse_frontmatter("---\nunterminated") == ({}, False)
|
||||
|
||||
|
||||
def _runtime(allowed=None):
|
||||
rt = ExtensionRuntime()
|
||||
rt.register(StructExt(allowed_fields=allowed))
|
||||
return rt
|
||||
|
||||
|
||||
def test_feature_absent_when_extension_off():
|
||||
shard = build_engine_shard("off", ExtensionRuntime(), activate=set())
|
||||
shard.write("Spec", _STRUCT_PAGE)
|
||||
assert shard.read("Spec").shape is PageShape.PROSE # kernel: opaque prose
|
||||
assert not shard.profile().supports(Verb.STRUCTURED_PAYLOAD) # honest absence
|
||||
|
||||
|
||||
def test_feature_present_when_extension_on():
|
||||
shard = build_engine_shard("on", _runtime(), activate={"ext.struct"})
|
||||
shard.write("Spec", _STRUCT_PAGE)
|
||||
assert shard.read("Spec").shape is PageShape.TYPED_RECORD # tagged by ext.struct
|
||||
assert shard.read("Spec").body.endswith("body text") # content preserved (in-text)
|
||||
assert shard.profile().supports(Verb.STRUCTURED_PAYLOAD) # profile reflects activation (E-5)
|
||||
assert_conformant(shard) # still conformant
|
||||
|
||||
|
||||
def test_plain_page_is_not_tagged_even_when_on():
|
||||
shard = build_engine_shard("on", _runtime(), activate={"ext.struct"})
|
||||
shard.write("Plain", "no frontmatter here")
|
||||
assert shard.read("Plain").shape is PageShape.PROSE
|
||||
|
||||
|
||||
def test_allowed_fields_validation_rejects_disallowed():
|
||||
shard = build_engine_shard("v", _runtime(allowed={"title"}), activate={"ext.struct"})
|
||||
with pytest.raises(ValueError, match="disallowed fields"):
|
||||
shard.write("Bad", "---\ntitle: ok\nsecret: no\n---\nx")
|
||||
shard.write("Good", "---\ntitle: ok\n---\nx") # allowed field passes
|
||||
assert shard.read("Good").shape is PageShape.TYPED_RECORD
|
||||
|
||||
|
||||
def test_through_information_space_edit():
|
||||
space = InformationSpace("team")
|
||||
space.attach(build_engine_shard("wikiE", _runtime(), activate={"ext.struct"}))
|
||||
space.union.shard("wikiE").write("Doc", "---\ntitle: T\n---\nv1")
|
||||
res = space.edit("Doc", "---\ntitle: T2\n---\nv2") # overlay→apply→write-through
|
||||
assert res.status.value == "applied"
|
||||
page = space.read("Doc")
|
||||
assert page.shape is PageShape.TYPED_RECORD and "v2" in page.body
|
||||
@@ -4,7 +4,7 @@ type: workplan
|
||||
title: "wiki-engine implementation — kernel + typed-extension runtime + activation"
|
||||
domain: whynot
|
||||
repo: shard-wiki
|
||||
status: active
|
||||
status: done
|
||||
owner: tegwick
|
||||
topic_slug: whynot
|
||||
created: "2026-06-15"
|
||||
@@ -124,7 +124,7 @@ integration: engine shard passes `assert_conformant`; attach → resolve → edi
|
||||
|
||||
```task
|
||||
id: SHARD-WP-0014-T6
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "b88d1640-9afa-4957-aec3-a7264b09494c"
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user