Files
phase-memory/tests/test_file_backed_runtime.py

123 lines
5.2 KiB
Python

import json
from pathlib import Path
from phase_memory.adapters import FileBackedMemoryGraphStore, JsonlAuditSink, JsonlMemoryEventLog
from phase_memory.lifecycle import plan_compaction, plan_retention
from phase_memory.models import LifecycleAction, LifecycleActionKind, LifecycleState, MemoryEdge, MemoryEvent, MemoryNode
from phase_memory.paths import abandon_path, branch_path, create_path, merge_path, path_event
from phase_memory.runtime import PhaseMemoryRuntime
FIXTURES = Path(__file__).parent / "fixtures"
def _load(name: str):
return json.loads((FIXTURES / name).read_text(encoding="utf-8"))
def test_file_backed_store_round_trips_and_exports_graph(tmp_path) -> None:
store = FileBackedMemoryGraphStore(tmp_path)
event_log = JsonlMemoryEventLog(tmp_path / "events.jsonl")
runtime = PhaseMemoryRuntime(graph_store=store, event_log=event_log, audit_sink=JsonlAuditSink(tmp_path / "audit.jsonl"))
runtime.import_profile(_load("memory-profile.json"), source_ref="profile")
runtime.import_graph(_load("memory-graph.json"), source_ref="graph")
assert store.get_profile("phase-memory-fixture-profile").profile_id == "phase-memory-fixture-profile"
assert store.get_node("decision.boundary").kind == "decision"
assert store.list_edges(source="decision.boundary")[0].target == "artifact.profile"
exported = runtime.export_graph(graph_id="exported")["data"]["graph"]
assert exported["schema_version"] == "markitect.memory.graph.v1"
assert exported["id"] == "exported"
assert len(exported["nodes"]) == 4
assert len(exported["edges"]) == 2
assert len(exported["events"]) == 1
def test_jsonl_event_log_detects_duplicates_and_corruption(tmp_path) -> None:
log = JsonlMemoryEventLog(tmp_path / "events.jsonl")
event = MemoryEvent("event.a", "recorded", timestamp="2026-05-18T00:00:00+00:00")
log.append(event)
try:
log.append(event)
except ValueError as exc:
assert "Duplicate memory event id" in str(exc)
else:
raise AssertionError("duplicate event id should fail")
with (tmp_path / "events.jsonl").open("a", encoding="utf-8") as handle:
handle.write("{not-json}\n")
handle.write(json.dumps({"schema_version": "unknown.event.v9", "id": "event.b", "kind": "recorded"}) + "\n")
diagnostics = log.diagnostics()
assert [diagnostic.code for diagnostic in diagnostics] == ["malformed_event_log_line", "unknown_event_schema"]
assert log.list_events(kind="recorded")[0].event_id == "event.a"
def test_memory_paths_model_branch_merge_and_abandon_as_events(tmp_path) -> None:
store = FileBackedMemoryGraphStore(tmp_path)
root = create_path("path.root", event_ids=("event.root",))
branch = branch_path(root, "path.branch", event_ids=("event.branch",))
merged = merge_path(branch, "path.root")
abandoned = abandon_path(branch, "superseded by main path")
store.save_path(root)
store.save_path(merged)
assert store.get_path("path.branch").merged_into == "path.root"
assert path_event(merged, "path.merged").metadata["path_state"] == "merged"
assert abandoned.abandoned_reason == "superseded by main path"
def test_repair_diagnostics_report_missing_edges_and_orphaned_path_events(tmp_path) -> None:
store = FileBackedMemoryGraphStore(tmp_path)
log = JsonlMemoryEventLog(tmp_path / "events.jsonl")
runtime = PhaseMemoryRuntime(graph_store=store, event_log=log)
store.save_node(MemoryNode("node.a", "decision"))
store.save_edge(MemoryEdge("edge.bad", "depends_on", "node.a", "missing"))
store.save_path(create_path("path.root", event_ids=("missing.event",)))
envelope = runtime.repair_diagnostics(source_ref=str(tmp_path))
assert envelope["valid"] is False
assert [diagnostic["code"] for diagnostic in envelope["diagnostics"]] == ["missing_edge_target", "orphaned_path_event"]
def test_lifecycle_apply_requires_approval_for_reviewable_actions(tmp_path) -> None:
store = FileBackedMemoryGraphStore(tmp_path)
runtime = PhaseMemoryRuntime(graph_store=store, event_log=JsonlMemoryEventLog(tmp_path / "events.jsonl"))
node = store.save_node(MemoryNode("node.a", "episode", "Trace text"))
compact = plan_compaction([node])
denied = runtime.apply_lifecycle_actions([compact])
assert denied["valid"] is False
assert denied["data"]["denied"][0]["reason"] == "review_required"
assert [node.node_id for node in store.list_nodes()] == ["node.a"]
applied = runtime.apply_lifecycle_actions([compact], approval_marker="review:local")
assert applied["valid"] is True
assert store.get_node(compact.target_id).kind == "summary"
def test_lifecycle_apply_can_mark_non_review_actions(tmp_path) -> None:
store = FileBackedMemoryGraphStore(tmp_path)
runtime = PhaseMemoryRuntime(graph_store=store, event_log=JsonlMemoryEventLog(tmp_path / "events.jsonl"))
store.save_node(MemoryNode("stale", "episode", freshness={"updated_at": "2026-05-01T00:00:00+00:00"}))
action = LifecycleAction(
LifecycleActionKind.MARK_STALE,
"stale",
from_state=LifecycleState.ACTIVE,
to_state=LifecycleState.STALE,
)
envelope = runtime.apply_lifecycle_actions([action])
assert envelope["valid"] is True
assert store.get_node("stale").lifecycle == LifecycleState.STALE