generated from coulomb/repo-seed
feat(engine): EngineShardAdapter — engine as a canonical-mode shard (WP-0014 T5)
engine/adapter.py: EngineShardAdapter implements adapters.ShardAdapter (read/write run extension transform hooks; profile derived from active extensions, E-5; current_rev for apply-under-drift) + build_engine_shard() helper (explicit ids or activation provider). runtime.available() added. Engine shard passes assert_conformant and attaches to an InformationSpace — resolve + edit (overlay->apply->write-through) work, and the declared profile reflects the active extensions. 5 tests green, pyflakes clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ from shard_wiki.engine.extension import (
|
||||
ExtensionRuntime,
|
||||
Hook,
|
||||
)
|
||||
from shard_wiki.engine.adapter import EngineShardAdapter, build_engine_shard
|
||||
from shard_wiki.engine.kernel import EngineKernel
|
||||
from shard_wiki.engine.links import extract_wikilinks
|
||||
from shard_wiki.engine.profile import (
|
||||
@@ -43,4 +44,6 @@ __all__ = [
|
||||
"engine_base_profile",
|
||||
"ProfileContribution",
|
||||
"derive_profile",
|
||||
"EngineShardAdapter",
|
||||
"build_engine_shard",
|
||||
]
|
||||
|
||||
82
src/shard_wiki/engine/adapter.py
Normal file
82
src/shard_wiki/engine/adapter.py
Normal file
@@ -0,0 +1,82 @@
|
||||
"""EngineShardAdapter — the engine exposed as a canonical-mode shard (WikiEngineCoreArchitecture
|
||||
§6, E-1/E-5).
|
||||
|
||||
The engine is *one shard*: the orchestrator consumes it only through this `ShardAdapter`. The
|
||||
adapter is backed by the kernel (T1) + a composed extension set (T2/T3); its §A capability
|
||||
profile is **derived from the active extensions** (T4), so the orchestrator sees an honest,
|
||||
conformance-verifiable profile that reflects exactly what is activated. Read/write run the
|
||||
extensions' transform hooks; everything above this stays in the orchestrator (no union/projection
|
||||
import).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable
|
||||
|
||||
from shard_wiki.adapters import ShardAdapter
|
||||
from shard_wiki.engine.activation import ActivationContext, ActivationProvider, ActivationResolver
|
||||
from shard_wiki.engine.extension import ActiveExtensions, ExtensionRuntime, Hook
|
||||
from shard_wiki.engine.kernel import EngineKernel
|
||||
from shard_wiki.engine.profile import derive_profile
|
||||
from shard_wiki.model import CapabilityProfile, NotSupported, Page, Verb
|
||||
|
||||
__all__ = ["EngineShardAdapter", "build_engine_shard"]
|
||||
|
||||
|
||||
class EngineShardAdapter(ShardAdapter):
|
||||
def __init__(
|
||||
self,
|
||||
kernel: EngineKernel,
|
||||
active: ActiveExtensions,
|
||||
base_profile: CapabilityProfile | None = None,
|
||||
) -> None:
|
||||
self._kernel = kernel
|
||||
self._active = active
|
||||
self._profile = derive_profile(active, base_profile) # validated (E-5)
|
||||
|
||||
@property
|
||||
def shard_id(self) -> str:
|
||||
return self._kernel.shard_id
|
||||
|
||||
def profile(self) -> CapabilityProfile:
|
||||
return self._profile
|
||||
|
||||
def keys(self) -> Iterable[str]:
|
||||
return self._kernel.keys()
|
||||
|
||||
def read(self, key: str) -> Page:
|
||||
page = self._kernel.read(key)
|
||||
return self._active.dispatch_transform(Hook.ON_READ, page, {"shard_id": self.shard_id})
|
||||
|
||||
def current_rev(self, key: str) -> str | None:
|
||||
return self._kernel.current_rev(key)
|
||||
|
||||
def write(self, key: str, body: str) -> Page:
|
||||
if not self._profile.supports(Verb.WRITE):
|
||||
raise NotSupported(f"{type(self).__name__} ({self.shard_id}) is read-only")
|
||||
body = self._active.dispatch_transform(
|
||||
Hook.ON_WRITE, body, {"shard_id": self.shard_id, "key": key}
|
||||
)
|
||||
return self._kernel.write(key, body)
|
||||
|
||||
|
||||
def build_engine_shard(
|
||||
shard_id: str,
|
||||
runtime: ExtensionRuntime,
|
||||
*,
|
||||
activate: Iterable[str] | None = None,
|
||||
provider: ActivationProvider | None = None,
|
||||
context: ActivationContext | None = None,
|
||||
base_profile: CapabilityProfile | None = None,
|
||||
) -> EngineShardAdapter:
|
||||
"""Stand up an engine shard: resolve which extensions are active (explicit ``activate`` ids,
|
||||
or via an activation ``provider`` over the runtime's available set), compose them, and wrap a
|
||||
fresh kernel as a `ShardAdapter`.
|
||||
"""
|
||||
if provider is not None:
|
||||
ctx = context or ActivationContext(shard_id)
|
||||
ids = ActivationResolver(provider).active_extensions(runtime.available(), ctx)
|
||||
else:
|
||||
ids = set(activate or ())
|
||||
active = runtime.activate(ids)
|
||||
return EngineShardAdapter(EngineKernel(shard_id), active, base_profile)
|
||||
@@ -86,6 +86,10 @@ class ExtensionRuntime:
|
||||
def __init__(self) -> None:
|
||||
self._registered: dict[str, Extension] = {}
|
||||
|
||||
def available(self) -> frozenset[str]:
|
||||
"""Ids of all registered extensions (the candidate set for activation)."""
|
||||
return frozenset(self._registered)
|
||||
|
||||
def register(self, ext: Extension) -> Extension:
|
||||
"""Register an extension after structural verification (mirrors §6.6)."""
|
||||
if not ext.id or not ext.id.startswith("ext."):
|
||||
|
||||
80
tests/test_engine_adapter.py
Normal file
80
tests/test_engine_adapter.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""Tests for EngineShardAdapter (SHARD-WP-0014 T5): engine as a canonical-mode shard."""
|
||||
|
||||
from shard_wiki import InformationSpace
|
||||
from shard_wiki.adapters import assert_conformant
|
||||
from shard_wiki.engine import (
|
||||
Extension,
|
||||
ExtensionRuntime,
|
||||
Hook,
|
||||
ProfileContribution,
|
||||
StaticProvider,
|
||||
build_engine_shard,
|
||||
)
|
||||
from shard_wiki.engine.activation import ActivationContext
|
||||
from shard_wiki.model import Verb
|
||||
|
||||
|
||||
class StructProfileExt(Extension):
|
||||
"""Profile-only extension (no body transform → write stays content-preserving)."""
|
||||
|
||||
id = "ext.struct"
|
||||
|
||||
def hooks(self):
|
||||
return {
|
||||
Hook.ON_PROFILE: lambda p, c: ProfileContribution(
|
||||
verbs_add=frozenset({Verb.STRUCTURED_PAYLOAD})
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def _runtime():
|
||||
rt = ExtensionRuntime()
|
||||
rt.register(StructProfileExt())
|
||||
return rt
|
||||
|
||||
|
||||
def test_kernel_only_engine_shard_is_conformant():
|
||||
shard = build_engine_shard("eng", ExtensionRuntime(), activate=set())
|
||||
shard.write("Home", "hi")
|
||||
report = assert_conformant(shard) # read + positive write probe
|
||||
assert report.ok
|
||||
assert shard.profile().supports(Verb.WRITE)
|
||||
assert not shard.profile().supports(Verb.STRUCTURED_PAYLOAD)
|
||||
|
||||
|
||||
def test_profile_reflects_activated_extensions():
|
||||
off = build_engine_shard("a", _runtime(), activate=set())
|
||||
on = build_engine_shard("b", _runtime(), activate={"ext.struct"})
|
||||
assert not off.profile().supports(Verb.STRUCTURED_PAYLOAD)
|
||||
assert on.profile().supports(Verb.STRUCTURED_PAYLOAD) # E-5
|
||||
assert_conformant(on)
|
||||
|
||||
|
||||
def test_activation_via_provider():
|
||||
provider = StaticProvider(flags={"ext.struct": True})
|
||||
shard = build_engine_shard("c", _runtime(), provider=provider, context=ActivationContext("c"))
|
||||
assert shard.profile().supports(Verb.STRUCTURED_PAYLOAD)
|
||||
|
||||
|
||||
def test_attach_resolve_edit_through_engine_shard(tmp_path):
|
||||
space = InformationSpace("team")
|
||||
space.attach(build_engine_shard("wikiE", ExtensionRuntime(), activate=set()))
|
||||
# seed a page directly via the shard, then read + edit through the orchestrator
|
||||
space.union.shard("wikiE").write("Home", "v1")
|
||||
assert space.read("Home").body == "v1"
|
||||
result = space.edit("Home", "v2") # overlay -> apply-under-drift -> write-through
|
||||
assert result.status.value == "applied"
|
||||
assert space.read("Home").body == "v2"
|
||||
|
||||
|
||||
def test_on_write_transform_runs():
|
||||
class Upper(Extension):
|
||||
id = "ext.upper"
|
||||
def hooks(self):
|
||||
return {Hook.ON_WRITE: lambda body, ctx: body.upper()}
|
||||
|
||||
rt = ExtensionRuntime()
|
||||
rt.register(Upper())
|
||||
shard = build_engine_shard("u", rt, activate={"ext.upper"})
|
||||
shard.write("P", "quiet")
|
||||
assert shard.read("P").body == "QUIET" # extension transformed the write
|
||||
@@ -110,7 +110,7 @@ profile; the derived profile is valid and conformance-passes.
|
||||
|
||||
```task
|
||||
id: SHARD-WP-0014-T5
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "2fbf498c-efe9-400a-8a13-7f1b521b3534"
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user