generated from coulomb/repo-seed
Add profile-driven lifecycle rules
This commit is contained in:
@@ -47,6 +47,29 @@ def test_cli_graph_lifecycle_emits_dry_run_actions(capsys) -> None:
|
||||
assert [action["action"] for action in output["data"]["dry_run_actions"]][:2] == ["mark_stale", "refresh"]
|
||||
|
||||
|
||||
def test_cli_graph_lifecycle_can_use_profile_rules(capsys) -> None:
|
||||
code = main(
|
||||
[
|
||||
"graph",
|
||||
"lifecycle",
|
||||
str(FIXTURES / "memory-graph.json"),
|
||||
"--profile",
|
||||
str(FIXTURES / "memory-profile.json"),
|
||||
"--refresh-digest",
|
||||
"event.restart=new",
|
||||
]
|
||||
)
|
||||
|
||||
output = json.loads(capsys.readouterr().out)
|
||||
actions = {(action["target_id"], action["action"]) for action in output["data"]["dry_run_actions"]}
|
||||
assert code == 0
|
||||
assert output["operation"] == "graph.lifecycle.plan"
|
||||
assert output["data"]["profile_id"] == "phase-memory-fixture-profile"
|
||||
assert output["data"]["rule_config"]["retention_default"]["stale_after_days"] == 7
|
||||
assert ("event.restart", "mark_stale") in actions
|
||||
assert ("event.restart", "refresh") in actions
|
||||
|
||||
|
||||
def test_cli_graph_activate_emits_selection(capsys) -> None:
|
||||
code = main(
|
||||
[
|
||||
|
||||
@@ -1,7 +1,24 @@
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
from phase_memory.lifecycle import plan_compaction, plan_phase_transition, plan_refresh, plan_retention
|
||||
from phase_memory.models import LifecycleActionKind, LifecycleState, MemoryNode, MemoryPhase
|
||||
from phase_memory.contracts import graph_from_markitect
|
||||
from phase_memory.lifecycle import (
|
||||
LifecycleRuleConfig,
|
||||
plan_compaction,
|
||||
plan_lifecycle_from_profile,
|
||||
plan_phase_transition,
|
||||
plan_refresh,
|
||||
plan_retention,
|
||||
)
|
||||
from phase_memory.models import LifecycleActionKind, LifecycleState, MemoryGraph, MemoryNode, MemoryPhase
|
||||
|
||||
|
||||
FIXTURES = Path(__file__).parent / "fixtures"
|
||||
|
||||
|
||||
def _load(name: str):
|
||||
return json.loads((FIXTURES / name).read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def test_phase_transition_to_stabilized_requires_review() -> None:
|
||||
@@ -47,3 +64,61 @@ def test_compaction_and_refresh_are_reviewable_plans() -> None:
|
||||
assert refresh.action == LifecycleActionKind.REFRESH
|
||||
assert refresh.requires_review
|
||||
assert refresh.metadata["proposed_digest"] == "new"
|
||||
|
||||
|
||||
def test_profile_retention_rules_drive_lifecycle_plan_from_fixture() -> None:
|
||||
now = datetime(2026, 5, 18, tzinfo=timezone.utc)
|
||||
profile = _load("memory-profile.json")
|
||||
graph = graph_from_markitect(_load("memory-graph.json")).value
|
||||
config = LifecycleRuleConfig.from_profile(profile)
|
||||
|
||||
actions = plan_lifecycle_from_profile(
|
||||
graph,
|
||||
profile,
|
||||
refresh_digests={"event.restart": "new"},
|
||||
now=now,
|
||||
)
|
||||
|
||||
by_target_and_action = {(action.target_id, action.action): action for action in actions}
|
||||
assert config.retention_default.stale_after_days == 7
|
||||
assert by_target_and_action[("event.restart", LifecycleActionKind.MARK_STALE)].to_state == LifecycleState.STALE
|
||||
assert by_target_and_action[("event.restart", LifecycleActionKind.REFRESH)].requires_review
|
||||
|
||||
|
||||
def test_profile_transition_rules_promote_matching_nodes() -> None:
|
||||
now = datetime(2026, 5, 18, tzinfo=timezone.utc)
|
||||
graph = MemoryGraph(
|
||||
"graph.rules",
|
||||
nodes=(
|
||||
MemoryNode(
|
||||
"episode.old",
|
||||
"episode",
|
||||
"Useful trace",
|
||||
phase=MemoryPhase.FLUID,
|
||||
freshness={"updated_at": "2026-05-01T00:00:00+00:00"},
|
||||
),
|
||||
),
|
||||
)
|
||||
profile = {
|
||||
"schema_version": "markitect.memory.profile.v1",
|
||||
"id": "profile.rules",
|
||||
"metadata": {
|
||||
"phase_transitions": [
|
||||
{
|
||||
"kind": "episode",
|
||||
"from_phase": "fluid",
|
||||
"to_phase": "stabilized",
|
||||
"min_age_days": 7,
|
||||
"reason": "episode old enough to stabilize",
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
actions = plan_lifecycle_from_profile(graph, profile, now=now)
|
||||
|
||||
assert len(actions) == 1
|
||||
assert actions[0].action == LifecycleActionKind.TRANSITION_PHASE
|
||||
assert actions[0].target_id == "episode.old"
|
||||
assert actions[0].requires_review is True
|
||||
assert actions[0].metadata["to_phase"] == "stabilized"
|
||||
|
||||
@@ -58,6 +58,24 @@ def test_service_runner_handles_health() -> None:
|
||||
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_profile_driven_runtime_config_resolves_file_backed_adapters(tmp_path) -> None:
|
||||
config = RuntimeConfig.from_profile(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user