From 850979ccf1e5d96b7b6ef5cbc353dbb8d8a96399 Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 18 May 2026 20:38:00 +0200 Subject: [PATCH] Complete adapter conformance coverage --- docs/service-readiness.md | 2 + src/phase_memory/adapters.py | 37 +++++++++++++++++++ src/phase_memory/service.py | 28 +++++++++++++- tests/test_service_readiness.py | 4 ++ ...service-readiness-and-external-adapters.md | 3 +- 5 files changed, 72 insertions(+), 2 deletions(-) diff --git a/docs/service-readiness.md b/docs/service-readiness.md index feecc46..7a70365 100644 --- a/docs/service-readiness.md +++ b/docs/service-readiness.md @@ -56,8 +56,10 @@ The service module includes reusable conformance helpers for: - graph stores - event logs - context package compilers +- semantic indexes - policy gateways - audit sinks +- runtime registries External adapters should pass these helpers before being wired into a runtime. diff --git a/src/phase_memory/adapters.py b/src/phase_memory/adapters.py index 89a34fe..5c22476 100644 --- a/src/phase_memory/adapters.py +++ b/src/phase_memory/adapters.py @@ -260,6 +260,43 @@ class JsonlAuditSink: return {"recorded": True, "index": index, "event": stored} +class InMemorySemanticIndex: + def __init__(self) -> None: + self._nodes_by_graph: dict[str, list[MemoryNode]] = {} + + def upsert_nodes(self, nodes: list[MemoryNode]) -> dict[str, Any]: + for node in nodes: + graph_id = str(node.metadata.get("graph_id") or "local") + existing = [item for item in self._nodes_by_graph.get(graph_id, []) if item.node_id != node.node_id] + existing.append(node) + self._nodes_by_graph[graph_id] = sorted(existing, key=lambda item: item.node_id) + return {"upserted": len(nodes)} + + def query(self, *, graph_id: str, query: str, limit: int = 10) -> list[dict[str, Any]]: + terms = {term.lower() for term in query.split() if term.strip()} + results: list[dict[str, Any]] = [] + for node in self._nodes_by_graph.get(graph_id, []): + text = f"{node.kind} {node.text}".lower() + score = sum(1 for term in terms if term in text) + if score: + results.append({"id": node.node_id, "score": score, "kind": node.kind}) + return sorted(results, key=lambda item: (-item["score"], item["id"]))[:limit] + + +class InMemoryRuntimeRegistry: + def __init__(self) -> None: + self._envelopes: dict[str, dict[str, Any]] = {} + + def publish_runtime_envelope(self, envelope: dict[str, Any]) -> dict[str, Any]: + reference = str(envelope.get("operation_id") or envelope.get("id") or f"envelope:{len(self._envelopes)}") + stored = dict(envelope) + self._envelopes[reference] = stored + return {"published": True, "reference": reference, "envelope": stored} + + def fetch_runtime_envelope(self, reference: str) -> dict[str, Any]: + return dict(self._envelopes[reference]) + + def _safe_name(identifier: str) -> str: safe = "".join(char if char.isalnum() or char in ("-", "_", ".") else "_" for char in identifier) return safe or "anonymous" diff --git a/src/phase_memory/service.py b/src/phase_memory/service.py index 145904e..95859d7 100644 --- a/src/phase_memory/service.py +++ b/src/phase_memory/service.py @@ -5,7 +5,15 @@ from __future__ import annotations from dataclasses import dataclass, field from typing import Any -from .adapters import AllowAllPolicyGateway, InMemoryMemoryEventLog, InMemoryMemoryGraphStore, NoopContextPackageCompiler, RecordingAuditSink +from .adapters import ( + AllowAllPolicyGateway, + InMemoryMemoryEventLog, + InMemoryMemoryGraphStore, + InMemoryRuntimeRegistry, + InMemorySemanticIndex, + NoopContextPackageCompiler, + RecordingAuditSink, +) from .models import Diagnostic, MemoryEvent, MemoryNode, PolicyDecision, ProfileIntent from .runtime import PhaseMemoryRuntime @@ -179,6 +187,22 @@ def assert_audit_sink_conformance(sink) -> None: assert receipt.get("recorded") is True +def assert_semantic_index_conformance(index) -> None: + node = MemoryNode("node.semantic", "decision", "Conformance search target", metadata={"graph_id": "graph.conformance"}) + receipt = index.upsert_nodes([node]) + results = index.query(graph_id="graph.conformance", query="search target", limit=5) + assert receipt.get("upserted") == 1 + assert results and results[0]["id"] == node.node_id + + +def assert_runtime_registry_conformance(registry) -> None: + envelope = {"operation_id": "op.conformance", "operation": "conformance"} + receipt = registry.publish_runtime_envelope(envelope) + fetched = registry.fetch_runtime_envelope(receipt["reference"]) + assert receipt["published"] is True + assert fetched["operation_id"] == "op.conformance" + + def default_conformance_adapters() -> dict[str, Any]: return { "graph_store": InMemoryMemoryGraphStore(), @@ -186,4 +210,6 @@ def default_conformance_adapters() -> dict[str, Any]: "context_compiler": NoopContextPackageCompiler(), "policy_gateway": AllowAllPolicyGateway(), "audit_sink": RecordingAuditSink(), + "semantic_index": InMemorySemanticIndex(), + "runtime_registry": InMemoryRuntimeRegistry(), } diff --git a/tests/test_service_readiness.py b/tests/test_service_readiness.py index 9fda6aa..bdf97c5 100644 --- a/tests/test_service_readiness.py +++ b/tests/test_service_readiness.py @@ -10,6 +10,8 @@ from phase_memory.service import ( assert_event_log_conformance, assert_graph_store_conformance, assert_policy_gateway_conformance, + assert_runtime_registry_conformance, + assert_semantic_index_conformance, default_conformance_adapters, health_report, kontextual_delegation_envelope, @@ -53,6 +55,8 @@ def test_default_adapter_conformance_helpers() -> None: assert_context_compiler_conformance(adapters["context_compiler"]) assert_policy_gateway_conformance(adapters["policy_gateway"]) assert_audit_sink_conformance(adapters["audit_sink"]) + assert_semantic_index_conformance(adapters["semantic_index"]) + assert_runtime_registry_conformance(adapters["runtime_registry"]) def test_kontextual_delegation_envelope_is_explicit() -> None: diff --git a/workplans/PMEM-WP-0007-service-readiness-and-external-adapters.md b/workplans/PMEM-WP-0007-service-readiness-and-external-adapters.md index cf58414..75810ca 100644 --- a/workplans/PMEM-WP-0007-service-readiness-and-external-adapters.md +++ b/workplans/PMEM-WP-0007-service-readiness-and-external-adapters.md @@ -66,7 +66,8 @@ Implemented outputs: - Health reports expose adapter availability, config diagnostics, stale memory counts, pending review counts, and store counts. - Adapter conformance helpers cover graph stores, event logs, context package - compilers, policy gateways, and audit sinks. + compilers, semantic indexes, policy gateways, audit sinks, and runtime + registries. - `kontextual_delegation_envelope` documents the JSON boundary that avoids circular imports while preserving ownership between phase-memory and `kontextual-engine`.