Implement phase-memory foundation
This commit is contained in:
parent
751da54052
commit
87f104781a
22 changed files with 1705 additions and 12 deletions
129
src/phase_memory/planner.py
Normal file
129
src/phase_memory/planner.py
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
"""Profile execution planning."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable
|
||||
|
||||
from .models import (
|
||||
Diagnostic,
|
||||
LifecycleAction,
|
||||
LifecycleActionKind,
|
||||
ProfileExecutionPlan,
|
||||
ProfileIntent,
|
||||
)
|
||||
|
||||
DEFAULT_LOCAL_ADAPTERS = {
|
||||
"local-event-log",
|
||||
"local-graph-store",
|
||||
"markitect-context-package",
|
||||
"markitect-context-registry",
|
||||
"markitect-memory-graph-fixture",
|
||||
"infospace-workflow-trace-fixtures",
|
||||
"infospace-artifact-neighborhood",
|
||||
}
|
||||
|
||||
|
||||
def plan_profile_execution(
|
||||
profile: ProfileIntent,
|
||||
*,
|
||||
available_adapters: Iterable[str] | None = None,
|
||||
) -> ProfileExecutionPlan:
|
||||
available = set(DEFAULT_LOCAL_ADAPTERS if available_adapters is None else available_adapters)
|
||||
required_adapters = tuple(sorted(set(profile.stores.values())))
|
||||
missing_adapters = tuple(adapter for adapter in required_adapters if adapter not in available)
|
||||
diagnostics: list[Diagnostic] = []
|
||||
actions: list[LifecycleAction] = []
|
||||
|
||||
if not profile.profile_id:
|
||||
diagnostics.append(Diagnostic("error", "missing_profile_id", "Profile execution requires a profile id."))
|
||||
|
||||
for adapter in missing_adapters:
|
||||
diagnostics.append(
|
||||
Diagnostic(
|
||||
"warn",
|
||||
"missing_adapter",
|
||||
"Required adapter is not available; plan will use fallback behavior.",
|
||||
"stores",
|
||||
{"adapter": adapter},
|
||||
)
|
||||
)
|
||||
actions.append(
|
||||
LifecycleAction(
|
||||
LifecycleActionKind.NO_OP,
|
||||
target_id=adapter,
|
||||
reason="adapter unavailable in dry-run planner",
|
||||
metadata={"fallback": _missing_store_fallback(profile)},
|
||||
)
|
||||
)
|
||||
|
||||
capabilities = tuple(sorted(_capabilities_for(profile)))
|
||||
policy_gates = tuple(_policy_gates(profile))
|
||||
observability_events = tuple(_observability_events(profile, missing_adapters))
|
||||
|
||||
return ProfileExecutionPlan(
|
||||
profile_id=profile.profile_id,
|
||||
enabled_memory_kinds=tuple(profile.memory_kinds),
|
||||
required_adapters=required_adapters,
|
||||
missing_adapters=missing_adapters,
|
||||
capabilities=capabilities,
|
||||
activation_budget=dict(profile.activation),
|
||||
policy_gates=policy_gates,
|
||||
observability_events=observability_events,
|
||||
fallback_behavior={
|
||||
"missing_runtime_store": _missing_store_fallback(profile),
|
||||
**dict(profile.failure),
|
||||
},
|
||||
actions=tuple(actions),
|
||||
diagnostics=tuple(diagnostics),
|
||||
metadata={
|
||||
"stores": dict(profile.stores),
|
||||
"limits": dict(profile.limits),
|
||||
"retention": dict(profile.retention),
|
||||
"refresh": dict(profile.refresh),
|
||||
"compaction": dict(profile.compaction),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _capabilities_for(profile: ProfileIntent) -> set[str]:
|
||||
capabilities = {"profile.inspect", "profile.plan"}
|
||||
if profile.retention:
|
||||
capabilities.add("retention.plan")
|
||||
if profile.refresh:
|
||||
capabilities.add("refresh.plan")
|
||||
if profile.compaction:
|
||||
capabilities.add("compaction.plan")
|
||||
if profile.activation:
|
||||
capabilities.add("activation.plan")
|
||||
if profile.policy:
|
||||
capabilities.add("policy.gate")
|
||||
return capabilities
|
||||
|
||||
|
||||
def _policy_gates(profile: ProfileIntent) -> list[str]:
|
||||
policy = profile.policy
|
||||
gates: list[str] = []
|
||||
for label in policy.get("required_labels", ()):
|
||||
gates.append(f"label:{label}")
|
||||
durable_writes = policy.get("durable_writes")
|
||||
if durable_writes:
|
||||
gates.append(f"durable_writes:{durable_writes}")
|
||||
if policy.get("secrets_allowed") is False:
|
||||
gates.append("secrets:denied")
|
||||
if policy.get("reauthorization"):
|
||||
gates.append(f"reauthorization:{policy['reauthorization']}")
|
||||
return gates
|
||||
|
||||
|
||||
def _observability_events(profile: ProfileIntent, missing_adapters: tuple[str, ...]) -> list[str]:
|
||||
observability = profile.observability
|
||||
events = list(observability.get("events") or observability.get("emit") or ())
|
||||
if observability.get("emit_events") is True and not events:
|
||||
events.append("phase_memory.profile.planned")
|
||||
if missing_adapters:
|
||||
events.append("phase_memory.adapter.missing")
|
||||
return events
|
||||
|
||||
|
||||
def _missing_store_fallback(profile: ProfileIntent) -> str:
|
||||
return str(profile.failure.get("missing_runtime_store") or "degrade-to-dry-run")
|
||||
Loading…
Add table
Add a link
Reference in a new issue