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:
2026-06-16 00:56:22 +02:00
parent 8393a9c55d
commit a165cced33
5 changed files with 158 additions and 3 deletions

View File

@@ -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 |

View 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"]

View 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
View 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

View File

@@ -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"
```