"""Service-readiness contracts, config, health, and conformance helpers.""" from __future__ import annotations from dataclasses import dataclass, field from typing import Any from .adapters import ( AllowAllPolicyGateway, InMemoryMemoryEventLog, InMemoryMemoryGraphStore, InMemoryRuntimeRegistry, InMemorySemanticIndex, NoopContextPackageCompiler, RecordingAuditSink, ) from .models import Diagnostic, MemoryEvent, MemoryNode, PolicyDecision, ProfileIntent from .runtime import PhaseMemoryRuntime SERVICE_CONTRACT_SCHEMA = "phase_memory.service.contracts.v1" HEALTH_REPORT_SCHEMA = "phase_memory.health.report.v1" KONTEXTUAL_DELEGATION_SCHEMA = "phase_memory.kontextual.delegation.v1" SERVICE_OPERATIONS = { "profile.plan": {"request": ["profile"], "response": "runtime_envelope"}, "graph.import": {"request": ["graph"], "response": "runtime_envelope"}, "graph.lifecycle.plan": {"request": ["graph", "parameters"], "response": "runtime_envelope"}, "lifecycle.apply": {"request": ["actions", "review_record"], "response": "runtime_envelope"}, "graph.activation.plan": {"request": ["graph", "budget"], "response": "runtime_envelope"}, "package.compile": {"request": ["selection"], "response": "runtime_envelope"}, "audit.query": {"request": ["filters"], "response": "audit_events"}, "health.check": {"request": [], "response": "health_report"}, } @dataclass(frozen=True) class RuntimeConfig: local_store_path: str = ".phase-memory-local" adapter_registry: dict[str, str] = field(default_factory=lambda: {"graph_store": "memory", "event_log": "memory"}) policy_mode: str = "allow-all" audit_sink_mode: str = "recording" package_compiler_mode: str = "noop" semantic_index_mode: str = "disabled" dry_run_default: bool = True trust_zone_labels: tuple[str, ...] = ("local",) @classmethod def local_default(cls) -> "RuntimeConfig": return cls() def diagnostics(self) -> tuple[Diagnostic, ...]: diagnostics: list[Diagnostic] = [] if not self.local_store_path: diagnostics.append(Diagnostic("error", "missing_store_path", "Runtime config requires a local store path.", "local_store_path")) if self.policy_mode not in {"allow-all", "external"}: diagnostics.append(Diagnostic("error", "unsupported_policy_mode", "Unsupported policy mode.", "policy_mode", {"policy_mode": self.policy_mode})) if self.semantic_index_mode not in {"disabled", "external"}: diagnostics.append(Diagnostic("error", "unsupported_semantic_index_mode", "Unsupported semantic index mode.", "semantic_index_mode", {"semantic_index_mode": self.semantic_index_mode})) return tuple(diagnostics) def to_dict(self) -> dict[str, Any]: return { "local_store_path": self.local_store_path, "adapter_registry": dict(self.adapter_registry), "policy_mode": self.policy_mode, "audit_sink_mode": self.audit_sink_mode, "package_compiler_mode": self.package_compiler_mode, "semantic_index_mode": self.semantic_index_mode, "dry_run_default": self.dry_run_default, "trust_zone_labels": list(self.trust_zone_labels), } def service_contracts() -> dict[str, Any]: return {"schema_version": SERVICE_CONTRACT_SCHEMA, "operations": SERVICE_OPERATIONS} def runtime_from_config(config: RuntimeConfig | None = None) -> PhaseMemoryRuntime: config = config or RuntimeConfig.local_default() # First service-ready slice keeps adapters dependency-light. External # adapter resolution belongs behind the registry in later deployments. return PhaseMemoryRuntime() def health_report(runtime: PhaseMemoryRuntime, *, config: RuntimeConfig | None = None) -> dict[str, Any]: config = config or RuntimeConfig.local_default() nodes = runtime.graph_store.list_nodes() stale = [node for node in nodes if node.lifecycle.value == "stale"] pending_review = [node for node in nodes if node.lifecycle.value == "review_needed"] diagnostics = list(config.diagnostics()) return { "schema_version": HEALTH_REPORT_SCHEMA, "ok": not any(diagnostic.severity == "error" for diagnostic in diagnostics), "adapters": { "graph_store": runtime.graph_store.__class__.__name__, "event_log": runtime.event_log.__class__.__name__, "policy_gateway": runtime.policy_gateway.__class__.__name__, "audit_sink": runtime.audit_sink.__class__.__name__, "package_compiler": runtime.package_compiler.__class__.__name__, }, "config": config.to_dict(), "store": { "node_count": len(nodes), "stale_memory_count": len(stale), "pending_review_count": len(pending_review), }, "diagnostics": [diagnostic.to_dict() for diagnostic in diagnostics], } class LocalServiceRunner: """Minimal optional service runner shape without web framework dependency.""" def __init__(self, runtime: PhaseMemoryRuntime | None = None, config: RuntimeConfig | None = None) -> None: self.config = config or RuntimeConfig.local_default() self.runtime = runtime or runtime_from_config(self.config) def handle(self, operation: str, payload: dict[str, Any] | None = None) -> dict[str, Any]: payload = payload or {} if operation == "health.check": return health_report(self.runtime, config=self.config) if operation == "profile.plan": return self.runtime.plan_profile(payload["profile"], source_ref=payload.get("source_ref", "service")) if operation == "graph.import": return self.runtime.import_graph(payload["graph"], source_ref=payload.get("source_ref", "service")) if operation == "graph.activation.plan": budget = payload.get("budget", {}) return self.runtime.plan_activation( payload["graph"], max_items=int(budget["max_items"]), max_tokens=int(budget["max_tokens"]), profile_id=payload.get("profile_id"), ) raise ValueError(f"Unsupported service operation: {operation}") def kontextual_delegation_envelope( *, operation: str, graph_id: str = "", profile_id: str = "", policy_decision: dict[str, Any] | None = None, audit_ref: str = "", ) -> dict[str, Any]: return { "schema_version": KONTEXTUAL_DELEGATION_SCHEMA, "operation": operation, "phase_memory_owns": ["phase_policy", "lifecycle_planning", "activation_planning"], "kontextual_owns": ["durable_records", "permission_aware_retrieval", "long_lived_storage"], "graph_id": graph_id, "profile_id": profile_id, "policy_decision": dict(policy_decision or {}), "audit_ref": audit_ref, "imports": {"avoid_circular_imports": True, "exchange": "json_envelopes"}, } def assert_graph_store_conformance(store) -> None: profile = ProfileIntent(profile_id="conformance-profile") node = MemoryNode("node.conformance", "decision", "Conformance node") store.save_profile(profile) store.save_node(node) assert store.get_profile(profile.profile_id).profile_id == profile.profile_id assert store.get_node(node.node_id).node_id == node.node_id assert store.list_nodes(kind="decision") def assert_event_log_conformance(log) -> None: event = MemoryEvent("event.conformance", "recorded") log.append(event) assert log.list_events(kind="recorded")[0].event_id == event.event_id def assert_context_compiler_conformance(compiler) -> None: response = compiler.compile_selection({"id": "selection.conformance", "nodes": [], "events": []}) assert "package_id" in response or "package_ref" in response def assert_policy_gateway_conformance(gateway) -> None: decision = gateway.authorize(action="read", resource="node.conformance") assert isinstance(decision, PolicyDecision) def assert_audit_sink_conformance(sink) -> None: receipt = sink.record({"operation": "conformance"}) 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(), "event_log": InMemoryMemoryEventLog(), "context_compiler": NoopContextPackageCompiler(), "policy_gateway": AllowAllPolicyGateway(), "audit_sink": RecordingAuditSink(), "semantic_index": InMemorySemanticIndex(), "runtime_registry": InMemoryRuntimeRegistry(), }