Files
phase-memory/tests/test_service_readiness.py

212 lines
8.3 KiB
Python

import json
from pathlib import Path
from phase_memory.lifecycle import plan_compaction
from phase_memory.models import LifecycleAction, LifecycleActionKind, LifecycleState, MemoryNode
from phase_memory.service import (
HEALTH_REPORT_SCHEMA,
KONTEXTUAL_DELEGATION_SCHEMA,
SERVICE_CONTRACT_SCHEMA,
LocalServiceRunner,
RuntimeConfig,
assert_audit_sink_conformance,
assert_context_compiler_conformance,
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,
resolve_runtime_adapters,
runtime_from_config,
service_contracts,
)
FIXTURES = Path(__file__).parent / "fixtures"
def _load(name: str):
return json.loads((FIXTURES / name).read_text(encoding="utf-8"))
def test_service_contracts_list_runtime_operations() -> None:
contracts = service_contracts()
assert contracts["schema_version"] == SERVICE_CONTRACT_SCHEMA
assert "profile.plan" in contracts["operations"]
assert "health.check" in contracts["operations"]
def test_runtime_config_validation_and_health_report() -> None:
runner = LocalServiceRunner()
runner.runtime.graph_store.save_node(MemoryNode("node.stale", "episode", lifecycle=LifecycleState.STALE))
report = health_report(runner.runtime, config=RuntimeConfig.local_default())
assert report["schema_version"] == HEALTH_REPORT_SCHEMA
assert report["ok"] is True
assert report["store"]["stale_memory_count"] == 1
assert report["adapters"]["graph_store"] == "InMemoryMemoryGraphStore"
def test_service_runner_handles_health() -> None:
runner = LocalServiceRunner()
response = runner.handle("health.check")
assert response["schema_version"] == HEALTH_REPORT_SCHEMA
assert response["ok"] is True
def test_service_runner_handles_profile_driven_lifecycle_plan() -> None:
runner = LocalServiceRunner()
response = runner.handle(
"graph.lifecycle.plan",
{
"profile": _load("memory-profile.json"),
"graph": _load("memory-graph.json"),
"parameters": {"refresh_digests": {"event.restart": "new"}},
},
)
actions = {(action["target_id"], action["action"]) for action in response["data"]["dry_run_actions"]}
assert response["operation"] == "graph.lifecycle.plan"
assert response["data"]["profile_id"] == "phase-memory-fixture-profile"
assert ("event.restart", "refresh") in actions
def test_service_runner_handles_package_compile_and_audit_query() -> None:
runner = LocalServiceRunner()
selection = {
"schema_version": "markitect.memory.selection.v1",
"id": "selection.service",
"nodes": ["decision.boundary"],
"events": ["event.activation"],
}
compiled = runner.handle("package.compile", {"selection": selection, "source_ref": "service-test"})
audit = runner.handle("audit.query", {"filters": {"operation": "package.compile"}})
assert compiled["operation"] == "package.compile"
assert compiled["data"]["package_response"]["package_ref"] == "package:selection.service"
assert audit["operation"] == "audit.query"
assert audit["count"] == 1
assert audit["events"][0]["source"]["ref"] == "service-test"
assert audit["retention"]["mode"] == "in_memory"
def test_service_runner_handles_review_gated_lifecycle_apply() -> None:
runner = LocalServiceRunner()
node = runner.runtime.graph_store.save_node(MemoryNode("node.review", "episode", "Review gated content"))
compact = plan_compaction([node]).to_dict()
denied = runner.handle("lifecycle.apply", {"actions": [compact]})
applied = runner.handle("lifecycle.apply", {"actions": [compact], "approval_marker": "review:service"})
audit = runner.handle("audit.query", {"filters": {"operation": "lifecycle.apply", "dry_run": False}})
assert denied["valid"] is False
assert denied["data"]["denied"][0]["reason"] == "review_required"
assert applied["valid"] is True
assert runner.runtime.graph_store.get_node(applied["data"]["applied"][0]["target_id"]).kind == "summary"
assert audit["count"] == 2
def test_service_runner_handles_non_review_lifecycle_apply() -> None:
runner = LocalServiceRunner()
runner.runtime.graph_store.save_node(MemoryNode("node.stale.service", "episode"))
action = LifecycleAction(
LifecycleActionKind.MARK_STALE,
"node.stale.service",
from_state=LifecycleState.ACTIVE,
to_state=LifecycleState.STALE,
)
applied = runner.handle("lifecycle.apply", {"actions": [action.to_dict()]})
assert applied["valid"] is True
assert runner.runtime.graph_store.get_node("node.stale.service").lifecycle == LifecycleState.STALE
def test_profile_driven_runtime_config_resolves_file_backed_adapters(tmp_path) -> None:
config = RuntimeConfig.from_profile(
{
"schema_version": "markitect.memory.profile.v1",
"id": "profile.config",
"stores": {
"graph_store": "file",
"event_log": "jsonl",
},
"activation": {"semantic_index": "memory"},
"policy": {"mode": "allow-all", "trust_zones": ["local", "team"]},
"observability": {"audit_sink": "jsonl", "runtime_registry": "memory"},
"metadata": {"runtime": {"local_store_path": str(tmp_path / "memory-store")}},
}
)
bundle = resolve_runtime_adapters(config)
runtime = runtime_from_config(config)
runtime.graph_store.save_node(MemoryNode("node.config", "decision", "Config-driven node"))
assert config.adapter_mode("graph_store") == "file"
assert config.adapter_mode("event_log") == "jsonl"
assert config.trust_zone_labels == ("local", "team")
assert bundle.to_dict()["graph_store"] == "FileBackedMemoryGraphStore"
assert bundle.to_dict()["event_log"] == "JsonlMemoryEventLog"
assert bundle.to_dict()["semantic_index"] == "InMemorySemanticIndex"
assert (tmp_path / "memory-store" / "nodes" / "node.config.json").exists()
def test_runtime_config_from_fixture_profile_understands_local_aliases() -> None:
config = RuntimeConfig.from_profile(_load("memory-profile.json"), local_store_path=".phase-memory-test")
assert config.adapter_mode("graph_store") == "file"
assert config.adapter_mode("event_log") == "jsonl"
assert config.adapter_mode("package_compiler") == "noop"
assert config.trust_zone_labels == ("project-local",)
def test_missing_external_adapter_blocks_runtime_resolution() -> None:
registry = RuntimeConfig.local_default().adapter_registry.copy()
registry["policy_gateway"] = "external"
config = RuntimeConfig(adapter_registry=registry, policy_mode="external")
bundle = resolve_runtime_adapters(config)
assert "external_adapter_declared" in [diagnostic.code for diagnostic in bundle.diagnostics]
assert "missing_external_adapter" in [diagnostic.code for diagnostic in bundle.diagnostics]
try:
runtime_from_config(config)
except ValueError as exc:
assert "missing_external_adapter" in str(exc)
else:
raise AssertionError("runtime_from_config should require supplied external adapters")
def test_default_adapter_conformance_helpers() -> None:
adapters = default_conformance_adapters()
assert_graph_store_conformance(adapters["graph_store"])
assert_event_log_conformance(adapters["event_log"])
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:
envelope = kontextual_delegation_envelope(
operation="persist_graph",
graph_id="graph.a",
profile_id="profile.a",
policy_decision={"allowed": True},
audit_ref="audit.a",
)
assert envelope["schema_version"] == KONTEXTUAL_DELEGATION_SCHEMA
assert "phase_policy" in envelope["phase_memory_owns"]
assert "durable_records" in envelope["kontextual_owns"]
assert envelope["imports"]["avoid_circular_imports"] is True