generated from coulomb/repo-seed
feat(PMEM-WP-0017): federated store management CLI and activity reports
Add phase_memory.management for cross-store discovery and windowed activity reporting. Extend the phase-memory CLI with stores list and report commands, plus make report-7d for the default weekly operator view.
This commit is contained in:
33
tests/fixtures/management/federated-report-aggregate.json
vendored
Normal file
33
tests/fixtures/management/federated-report-aggregate.json
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"aggregate": {
|
||||
"active_store_count": 2,
|
||||
"audit_event_count": 1,
|
||||
"by_operation": {
|
||||
"audit.query": 1
|
||||
},
|
||||
"by_outcome": {
|
||||
"resolved": 1,
|
||||
"skipped": 1,
|
||||
"unknown": 1
|
||||
},
|
||||
"by_session_kind": {
|
||||
"lifecycle.apply": 1,
|
||||
"warden.agent.codex": 1,
|
||||
"warden.operator": 1
|
||||
},
|
||||
"episode_count": 3
|
||||
},
|
||||
"store_count": 2,
|
||||
"stores": [
|
||||
{
|
||||
"audit_event_count": 1,
|
||||
"episode_count": 1,
|
||||
"store_kind": "local_graph"
|
||||
},
|
||||
{
|
||||
"audit_event_count": 0,
|
||||
"episode_count": 2,
|
||||
"store_kind": "ops_warden_coordination"
|
||||
}
|
||||
]
|
||||
}
|
||||
2
tests/fixtures/management/local-store/audit.jsonl
vendored
Normal file
2
tests/fixtures/management/local-store/audit.jsonl
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
{"operation":"package.compile","operation_id":"op:audit-old","timestamp":"2026-06-02T08:00:00+00:00"}
|
||||
{"operation":"audit.query","operation_id":"op:audit-in-window","timestamp":"2026-07-02T10:00:00+00:00"}
|
||||
2
tests/fixtures/management/local-store/events.jsonl
vendored
Normal file
2
tests/fixtures/management/local-store/events.jsonl
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
{"id":"event:old","kind":"path.created","schema_version":"markitect.memory.event.v1","timestamp":"2026-06-01T08:00:00+00:00"}
|
||||
{"id":"event:in-window","kind":"lifecycle.apply","schema_version":"markitect.memory.event.v1","timestamp":"2026-07-02T09:00:00+00:00"}
|
||||
3
tests/fixtures/management/local-store/phase-memory.json
vendored
Normal file
3
tests/fixtures/management/local-store/phase-memory.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"schema_version": "phase_memory.local_store.v1"
|
||||
}
|
||||
4
tests/fixtures/management/ops-warden-store/events.jsonl
vendored
Normal file
4
tests/fixtures/management/ops-warden-store/events.jsonl
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{"agent_id":"grok","command":"route find","diagnostic_codes":[],"event_id":"ops-warden-event:old","need_fingerprint":"abc123","outcome":"resolved","recorded_at":"2026-06-01T10:00:00+00:00","route_id":"openrouter-llm-connect","schema_version":"phase_memory.ops_warden.session_event.v1","session_kind":"warden.agent.grok"}
|
||||
{"agent_id":"","command":"route find","diagnostic_codes":[],"event_id":"ops-warden-event:in-window-1","need_fingerprint":"def456","outcome":"resolved","recorded_at":"2026-07-01T10:00:00+00:00","route_id":"issue-core-ingestion-api-key","schema_version":"phase_memory.ops_warden.session_event.v1","session_kind":"warden.operator"}
|
||||
{"agent_id":"codex","command":"access","diagnostic_codes":[],"event_id":"ops-warden-event:in-window-2","need_fingerprint":"ghi789","outcome":"skipped","recorded_at":"2026-07-02T11:00:00+00:00","route_id":"openrouter-llm-connect","schema_version":"phase_memory.ops_warden.session_event.v1","session_kind":"warden.agent.codex"}
|
||||
{"agent_id":"","command":"memory.activate","diagnostic_codes":[],"event_id":"ops-warden-event:no-ts","need_fingerprint":"","outcome":"resolved","route_id":"","schema_version":"phase_memory.ops_warden.session_event.v1","session_kind":"warden.operator"}
|
||||
5
tests/fixtures/management/ops-warden-store/metadata.json
vendored
Normal file
5
tests/fixtures/management/ops-warden-store/metadata.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"created_at": "2026-07-01T10:00:00+00:00",
|
||||
"profile_id": "ops-warden-coordination",
|
||||
"schema_version": "phase_memory.ops_warden.runtime.v1"
|
||||
}
|
||||
13
tests/fixtures/management/registry.json
vendored
Normal file
13
tests/fixtures/management/registry.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"schema_version": "phase_memory.management.store_registry.v1",
|
||||
"stores": [
|
||||
{
|
||||
"store_id": "fixture-local-graph",
|
||||
"path": "local-store",
|
||||
"store_kind": "local_graph",
|
||||
"profile_id": "local-dev",
|
||||
"label": "Fixture local graph store",
|
||||
"registered_at": "2026-07-01T10:00:00+00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
7
tests/fixtures/public-api-snapshot.json
vendored
7
tests/fixtures/public-api-snapshot.json
vendored
@@ -17,6 +17,7 @@
|
||||
"EVALUATION_TREND_REGRESSION_GATE_SCHEMA",
|
||||
"EVALUATION_TREND_SCHEMA",
|
||||
"ExternalAdapterPack",
|
||||
"FEDERATED_REPORT_SCHEMA",
|
||||
"FakeExternalEventLog",
|
||||
"FakeExternalGraphStore",
|
||||
"FakeExternalPolicyGateway",
|
||||
@@ -76,6 +77,8 @@
|
||||
"RuntimeConfig",
|
||||
"SERVICE_APP_SCHEMA",
|
||||
"SERVICE_BINDING_SCHEMA",
|
||||
"STORE_LIST_SCHEMA",
|
||||
"STORE_REGISTRY_SCHEMA",
|
||||
"ServiceAppConfig",
|
||||
"ServiceBinding",
|
||||
"ServiceResponse",
|
||||
@@ -89,8 +92,11 @@
|
||||
"activation_quality_report",
|
||||
"adapter_pack_manifest",
|
||||
"branch_path",
|
||||
"build_federated_report",
|
||||
"build_service_binding",
|
||||
"build_session_event",
|
||||
"build_store_list",
|
||||
"classify_store",
|
||||
"compact_path",
|
||||
"create_path",
|
||||
"create_wsgi_app",
|
||||
@@ -99,6 +105,7 @@
|
||||
"credentialed_operator_report",
|
||||
"credentialed_telemetry_retention_drill",
|
||||
"default_memory_store_path",
|
||||
"discover_memory_stores",
|
||||
"evaluation_threshold_report",
|
||||
"evaluation_trend_artifact",
|
||||
"evaluation_trend_history",
|
||||
|
||||
139
tests/test_management_cli.py
Normal file
139
tests/test_management_cli.py
Normal file
@@ -0,0 +1,139 @@
|
||||
"""Tests for federated store management and reporting."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from phase_memory.cli import main
|
||||
from phase_memory.management import (
|
||||
FEDERATED_REPORT_SCHEMA,
|
||||
STORE_KIND_LOCAL_GRAPH,
|
||||
STORE_KIND_OPS_WARDEN,
|
||||
build_federated_report,
|
||||
build_store_list,
|
||||
classify_store,
|
||||
discover_memory_stores,
|
||||
resolve_store_reference,
|
||||
)
|
||||
|
||||
FIXTURES = Path(__file__).resolve().parent / "fixtures" / "management"
|
||||
WINDOW_END = datetime(2026, 7, 3, 12, 0, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def _fixture_environ(tmp_path: Path) -> dict[str, str]:
|
||||
registry = FIXTURES / "registry.json"
|
||||
ops_store = FIXTURES / "ops-warden-store"
|
||||
local_store = FIXTURES / "local-store"
|
||||
return {
|
||||
"WARDEN_MEMORY_STORE": str(ops_store),
|
||||
"PHASE_MEMORY_REGISTRY": str(registry),
|
||||
"PHASE_MEMORY_STORE_PATHS": str(local_store),
|
||||
"HOME": str(tmp_path),
|
||||
"XDG_DATA_HOME": str(tmp_path / "xdg"),
|
||||
}
|
||||
|
||||
|
||||
def test_classify_store_kinds() -> None:
|
||||
assert classify_store(FIXTURES / "ops-warden-store") == STORE_KIND_OPS_WARDEN
|
||||
assert classify_store(FIXTURES / "local-store") == STORE_KIND_LOCAL_GRAPH
|
||||
assert classify_store(FIXTURES / "registry.json") is None
|
||||
|
||||
|
||||
def test_discover_memory_stores_deduplicates_and_merges_registry(tmp_path: Path) -> None:
|
||||
environ = _fixture_environ(tmp_path)
|
||||
stores, diagnostics = discover_memory_stores(environ)
|
||||
assert not any(item.get("severity") == "error" for item in diagnostics)
|
||||
store_ids = {store.store_id for store in stores}
|
||||
assert "fixture-local-graph" in store_ids
|
||||
assert len({str(store.path) for store in stores}) == len(stores)
|
||||
|
||||
|
||||
def test_build_federated_report_aggregate_and_detail(tmp_path: Path) -> None:
|
||||
environ = _fixture_environ(tmp_path)
|
||||
report = build_federated_report(days=7, environ=environ, window_end=WINDOW_END)
|
||||
assert report["schema_version"] == FEDERATED_REPORT_SCHEMA
|
||||
assert report["valid"] is True
|
||||
assert report["store_count"] == 2
|
||||
aggregate = report["aggregate"]
|
||||
assert aggregate["episode_count"] == 3
|
||||
assert aggregate["audit_event_count"] == 1
|
||||
assert aggregate["active_store_count"] == 2
|
||||
assert aggregate["by_outcome"]["resolved"] == 1
|
||||
assert aggregate["by_outcome"]["skipped"] == 1
|
||||
assert aggregate["by_session_kind"]["warden.operator"] == 1
|
||||
assert aggregate["by_operation"]["audit.query"] == 1
|
||||
|
||||
detail = build_federated_report(
|
||||
days=7,
|
||||
focus_store_id="fixture-local-graph",
|
||||
environ=environ,
|
||||
window_end=WINDOW_END,
|
||||
)
|
||||
assert detail["focus_store_id"] == "fixture-local-graph"
|
||||
assert detail["store_count"] == 1
|
||||
assert detail["store_detail"]["episode_count"] == 1
|
||||
assert detail["store_detail"]["audit_event_count"] == 1
|
||||
|
||||
|
||||
def test_resolve_store_reference_by_id_and_path(tmp_path: Path) -> None:
|
||||
environ = _fixture_environ(tmp_path)
|
||||
stores, _ = discover_memory_stores(environ)
|
||||
by_id = resolve_store_reference("fixture-local-graph", stores, environ=environ)
|
||||
assert by_id is not None
|
||||
assert by_id.store_kind == STORE_KIND_LOCAL_GRAPH
|
||||
by_path = resolve_store_reference(str(FIXTURES / "ops-warden-store"), stores, environ=environ)
|
||||
assert by_path is not None
|
||||
assert by_path.store_kind == STORE_KIND_OPS_WARDEN
|
||||
|
||||
|
||||
def test_federated_report_aggregate_fixture(tmp_path: Path) -> None:
|
||||
environ = _fixture_environ(tmp_path)
|
||||
report = build_federated_report(days=7, environ=environ, window_end=WINDOW_END)
|
||||
payload = {
|
||||
"aggregate": report["aggregate"],
|
||||
"store_count": report["store_count"],
|
||||
"stores": [
|
||||
{key: store[key] for key in ("store_id", "store_kind", "episode_count", "audit_event_count")}
|
||||
for store in report["stores"]
|
||||
],
|
||||
}
|
||||
expected = json.loads((FIXTURES / "federated-report-aggregate.json").read_text(encoding="utf-8"))
|
||||
assert payload["aggregate"] == expected["aggregate"]
|
||||
assert payload["store_count"] == expected["store_count"]
|
||||
actual_stores = sorted(payload["stores"], key=lambda item: item["store_kind"])
|
||||
expected_stores = sorted(expected["stores"], key=lambda item: item["store_kind"])
|
||||
for actual, item in zip(actual_stores, expected_stores, strict=True):
|
||||
assert actual["store_kind"] == item["store_kind"]
|
||||
assert actual["episode_count"] == item["episode_count"]
|
||||
assert actual["audit_event_count"] == item["audit_event_count"]
|
||||
|
||||
|
||||
def test_cli_stores_list_and_report(tmp_path: Path, capsys, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
environ = _fixture_environ(tmp_path)
|
||||
monkeypatch.setenv("WARDEN_MEMORY_STORE", environ["WARDEN_MEMORY_STORE"])
|
||||
monkeypatch.setenv("PHASE_MEMORY_REGISTRY", environ["PHASE_MEMORY_REGISTRY"])
|
||||
monkeypatch.setenv("PHASE_MEMORY_STORE_PATHS", environ["PHASE_MEMORY_STORE_PATHS"])
|
||||
assert main(["stores", "list", "--format", "summary"]) == 0
|
||||
listed = capsys.readouterr().out
|
||||
assert "Fixture local graph store" in listed
|
||||
|
||||
assert main(["report", "--days", "7", "--format", "summary"]) == 0
|
||||
reported = capsys.readouterr().out
|
||||
assert "episodes=" in reported
|
||||
assert "active_stores=" in reported
|
||||
|
||||
|
||||
def test_build_store_list_from_registry_only(tmp_path: Path) -> None:
|
||||
environ = {
|
||||
"WARDEN_MEMORY_STORE": str(tmp_path / "missing-ops"),
|
||||
"PHASE_MEMORY_REGISTRY": str(FIXTURES / "registry.json"),
|
||||
"PHASE_MEMORY_STORE_PATHS": str(FIXTURES / "local-store"),
|
||||
}
|
||||
listing = build_store_list(environ=environ)
|
||||
assert listing["valid"] is True
|
||||
assert listing["store_count"] == 1
|
||||
assert listing["stores"][0]["store_id"] == "fixture-local-graph"
|
||||
Reference in New Issue
Block a user