Implement live-shaped readiness workplan

This commit is contained in:
2026-05-19 01:06:41 +02:00
parent 3a52b3df41
commit 635d999621
21 changed files with 1507 additions and 54 deletions

View File

@@ -3,13 +3,19 @@
from __future__ import annotations
import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from .models import Diagnostic, MemoryEdge, MemoryEvent, MemoryGraph, MemoryNode, MemoryPath, PolicyDecision, ProfileIntent
from .utils import parse_iso_datetime, stable_digest, utc_now_iso
LOCAL_STORE_SCHEMA = "phase_memory.local_store.v1"
LOCAL_STORE_METADATA_FILE = "phase-memory.json"
LOCAL_STORE_MIGRATION_PLAN_SCHEMA = "phase_memory.local_store.migration_plan.v1"
LOCAL_STORE_MIGRATION_RESULT_SCHEMA = "phase_memory.local_store.migration_result.v1"
AUDIT_EXPORT_BATCH_SCHEMA = "phase_memory.audit.export_batch.v1"
AUDIT_RETENTION_PLAN_SCHEMA = "phase_memory.audit.retention_plan.v1"
class InMemoryMemoryGraphStore:
@@ -145,6 +151,109 @@ class FileBackedMemoryGraphStore:
def metadata(self) -> dict[str, Any]:
return _read_json(self.root / LOCAL_STORE_METADATA_FILE)
def migration_plan(self) -> dict[str, Any]:
metadata_path = self.root / LOCAL_STORE_METADATA_FILE
diagnostics = list(self.metadata_diagnostics())
metadata: dict[str, Any] = {}
schema_version = ""
if metadata_path.exists():
try:
metadata = _read_json(metadata_path)
schema_version = str(metadata.get("schema_version") or "")
except json.JSONDecodeError:
pass
actions: list[dict[str, Any]] = []
if not any(diagnostic.code == "corrupt_store_metadata" for diagnostic in diagnostics):
if schema_version != LOCAL_STORE_SCHEMA:
actions.append(
{
"id": "set_schema_version",
"action": "set_schema_version",
"from_schema_version": schema_version,
"to_schema_version": LOCAL_STORE_SCHEMA,
}
)
planned = metadata.get("planned_migrations") or metadata.get("migrations") or ()
for item in planned:
migration_id = str(item)
actions.append(
{
"id": f"complete_planned:{migration_id}",
"action": "complete_planned_migration",
"migration": migration_id,
}
)
plan_id = f"store-migration:{stable_digest([str(self.root), schema_version, actions])}"
return {
"schema_version": LOCAL_STORE_MIGRATION_PLAN_SCHEMA,
"id": plan_id,
"store_path": str(self.root),
"metadata_path": str(metadata_path),
"current_schema_version": schema_version,
"target_schema_version": LOCAL_STORE_SCHEMA,
"valid": not any(diagnostic.severity == "error" for diagnostic in diagnostics),
"dry_run": True,
"actions": actions,
"diagnostics": [diagnostic.to_dict() for diagnostic in diagnostics],
}
def apply_migration_plan(self, plan: dict[str, Any] | None = None, *, actor: str = "local") -> dict[str, Any]:
plan = dict(plan or self.migration_plan())
diagnostics = [dict(item) for item in plan.get("diagnostics", ())]
errors = [item for item in diagnostics if item.get("severity") == "error"]
if errors:
return {
"schema_version": LOCAL_STORE_MIGRATION_RESULT_SCHEMA,
"plan_id": plan.get("id", ""),
"store_path": str(self.root),
"applied": False,
"changed": False,
"actions": [],
"diagnostics": diagnostics,
}
metadata_path = self.root / LOCAL_STORE_METADATA_FILE
try:
metadata = _read_json(metadata_path) if metadata_path.exists() else {}
except json.JSONDecodeError:
metadata = {}
actions = [dict(item) for item in plan.get("actions", ())]
completed = list(metadata.get("completed_migrations") or ())
for action in actions:
if action.get("action") == "set_schema_version":
metadata["schema_version"] = LOCAL_STORE_SCHEMA
if action.get("action") == "complete_planned_migration":
completed.append(str(action.get("migration") or ""))
if actions:
metadata["schema_version"] = LOCAL_STORE_SCHEMA
metadata["migrations"] = []
metadata.pop("planned_migrations", None)
if completed:
metadata["completed_migrations"] = sorted({item for item in completed if item})
metadata["last_migration"] = {
"plan_id": str(plan.get("id") or ""),
"actor": actor,
"applied_at": utc_now_iso(),
"actions": [str(action.get("id") or "") for action in actions],
}
_write_json(metadata_path, metadata)
return {
"schema_version": LOCAL_STORE_MIGRATION_RESULT_SCHEMA,
"plan_id": plan.get("id", ""),
"store_path": str(self.root),
"applied": True,
"changed": bool(actions),
"actions": actions,
"metadata": metadata,
"diagnostics": diagnostics,
}
def repair_diagnostics(self, *, events: list[MemoryEvent] | None = None) -> tuple[Diagnostic, ...]:
diagnostics: list[Diagnostic] = []
nodes, node_diagnostics = _read_records(self.nodes_dir, MemoryNode.from_mapping, record_type="node")
@@ -323,6 +432,13 @@ class RecordingAuditSink:
def retention_metadata(self) -> dict[str, Any]:
return {"mode": "in_memory", "retention_days": None}
def retention_plan(self, *, retention_days: int | None = None, now: datetime | None = None) -> dict[str, Any]:
return audit_retention_plan(self.events, retention_days=retention_days, now=now, retention=self.retention_metadata())
def export_batch(self, **filters: Any) -> dict[str, Any]:
events = self.query(**filters)
return audit_export_batch(events, filters=filters, retention=self.retention_metadata())
class JsonlAuditSink:
def __init__(self, path: str | Path) -> None:
@@ -352,6 +468,13 @@ class JsonlAuditSink:
def retention_metadata(self) -> dict[str, Any]:
return {"mode": "jsonl", "path": str(self.path), "retention_days": None}
def retention_plan(self, *, retention_days: int | None = None, now: datetime | None = None) -> dict[str, Any]:
return audit_retention_plan(self.query(), retention_days=retention_days, now=now, retention=self.retention_metadata())
def export_batch(self, **filters: Any) -> dict[str, Any]:
events = self.query(**filters)
return audit_export_batch(events, filters=filters, retention=self.retention_metadata())
class InMemorySemanticIndex:
def __init__(self) -> None:
@@ -429,6 +552,54 @@ def filter_audit_events(events: list[dict[str, Any]], **filters: Any) -> list[di
return [dict(event) for event in events if _audit_event_matches(event, filters)]
def audit_export_batch(
events: list[dict[str, Any]],
*,
filters: dict[str, Any] | None = None,
retention: dict[str, Any] | None = None,
) -> dict[str, Any]:
return {
"schema_version": AUDIT_EXPORT_BATCH_SCHEMA,
"id": f"audit-export:{stable_digest([filters or {}, events])}",
"filters": dict(filters or {}),
"count": len(events),
"events": [dict(event) for event in events],
"retention": dict(retention or {}),
}
def audit_retention_plan(
events: list[dict[str, Any]],
*,
retention_days: int | None = None,
now: datetime | None = None,
retention: dict[str, Any] | None = None,
) -> dict[str, Any]:
retention = dict(retention or {})
if retention_days is None:
retention_days = retention.get("retention_days")
now = now or datetime.now(timezone.utc)
eligible: list[str] = []
retained: list[str] = []
for event in events:
event_id = str(event.get("operation_id") or event.get("id") or stable_digest(event))
age = _event_age_days(event, now=now)
if retention_days is not None and age is not None and age >= int(retention_days):
eligible.append(event_id)
else:
retained.append(event_id)
return {
"schema_version": AUDIT_RETENTION_PLAN_SCHEMA,
"id": f"audit-retention:{stable_digest([retention_days, eligible, retained])}",
"retention_days": retention_days,
"eligible_count": len(eligible),
"retained_count": len(retained),
"eligible_operation_ids": eligible,
"retained_operation_ids": retained,
"retention": retention,
}
def _audit_event_matches(event: dict[str, Any], filters: dict[str, Any]) -> bool:
operation = filters.get("operation")
if operation is not None and event.get("operation") != operation:
@@ -455,3 +626,10 @@ def _audit_event_matches(event: dict[str, Any], filters: dict[str, Any]) -> bool
if allowed is not None and bool(event.get("allowed")) is not bool(allowed):
return False
return True
def _event_age_days(event: dict[str, Any], *, now: datetime) -> int | None:
timestamp = parse_iso_datetime(str(event.get("timestamp") or ""))
if timestamp is None:
return None
return max((now - timestamp).days, 0)