622 lines
24 KiB
Python
622 lines
24 KiB
Python
"""Service-readiness contracts, config, health, and conformance helpers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from .adapters import (
|
|
AllowAllPolicyGateway,
|
|
FileBackedMemoryGraphStore,
|
|
InMemoryMemoryEventLog,
|
|
InMemoryMemoryGraphStore,
|
|
InMemoryRuntimeRegistry,
|
|
InMemorySemanticIndex,
|
|
JsonlAuditSink,
|
|
JsonlMemoryEventLog,
|
|
NoopContextPackageCompiler,
|
|
RecordingAuditSink,
|
|
)
|
|
from .models import Diagnostic, MemoryEvent, MemoryNode, PolicyDecision, ProfileIntent
|
|
from .runtime import PhaseMemoryRuntime
|
|
|
|
SERVICE_CONTRACT_SCHEMA = "phase_memory.service.contracts.v1"
|
|
HEALTH_REPORT_SCHEMA = "phase_memory.health.report.v1"
|
|
KONTEXTUAL_DELEGATION_SCHEMA = "phase_memory.kontextual.delegation.v1"
|
|
|
|
|
|
SERVICE_OPERATIONS = {
|
|
"profile.plan": {"request": ["profile"], "response": "runtime_envelope"},
|
|
"graph.import": {"request": ["graph"], "response": "runtime_envelope"},
|
|
"graph.lifecycle.plan": {"request": ["graph", "parameters", "profile?"], "response": "runtime_envelope"},
|
|
"lifecycle.apply": {"request": ["actions", "review_record"], "response": "runtime_envelope"},
|
|
"graph.activation.plan": {"request": ["graph", "budget"], "response": "runtime_envelope"},
|
|
"package.compile": {"request": ["selection"], "response": "runtime_envelope"},
|
|
"audit.query": {"request": ["filters"], "response": "audit_events"},
|
|
"health.check": {"request": [], "response": "health_report"},
|
|
}
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RuntimeConfig:
|
|
local_store_path: str = ".phase-memory-local"
|
|
adapter_registry: dict[str, str] = field(
|
|
default_factory=lambda: {
|
|
"graph_store": "memory",
|
|
"event_log": "memory",
|
|
"policy_gateway": "allow-all",
|
|
"audit_sink": "recording",
|
|
"package_compiler": "noop",
|
|
"semantic_index": "disabled",
|
|
"runtime_registry": "memory",
|
|
}
|
|
)
|
|
policy_mode: str = "allow-all"
|
|
audit_sink_mode: str = "recording"
|
|
package_compiler_mode: str = "noop"
|
|
semantic_index_mode: str = "disabled"
|
|
runtime_registry_mode: str = "memory"
|
|
dry_run_default: bool = True
|
|
trust_zone_labels: tuple[str, ...] = ("local",)
|
|
|
|
@classmethod
|
|
def local_default(cls) -> "RuntimeConfig":
|
|
return cls()
|
|
|
|
@classmethod
|
|
def from_profile(cls, profile: ProfileIntent | dict[str, Any], *, local_store_path: str | None = None) -> "RuntimeConfig":
|
|
profile_intent = profile if isinstance(profile, ProfileIntent) else ProfileIntent.from_mapping(profile)
|
|
base = cls.local_default()
|
|
runtime_metadata = _runtime_metadata(profile_intent)
|
|
stores = dict(profile_intent.stores)
|
|
policy = dict(profile_intent.policy)
|
|
activation = dict(profile_intent.activation)
|
|
observability = dict(profile_intent.observability)
|
|
|
|
store_path = str(
|
|
local_store_path
|
|
or runtime_metadata.get("local_store_path")
|
|
or profile_intent.metadata.get("local_store_path")
|
|
or stores.get("local_store_path")
|
|
or base.local_store_path
|
|
)
|
|
policy_mode = _mode_from(policy, ("mode", "policy_mode", "gateway"), default=base.policy_mode)
|
|
audit_sink_mode = _mode_from(observability, ("audit_sink", "audit_sink_mode"), default=_store_mode(stores, ("audit_sink", "audit"), base.audit_sink_mode))
|
|
package_compiler_mode = _mode_from(
|
|
runtime_metadata,
|
|
("package_compiler", "package_compiler_mode"),
|
|
default=_store_mode(stores, ("package_compiler", "context_package", "package"), base.package_compiler_mode),
|
|
)
|
|
semantic_index_mode = _mode_from(
|
|
activation,
|
|
("semantic_index", "semantic_index_mode"),
|
|
default=_store_mode(stores, ("semantic_index", "semantic"), base.semantic_index_mode),
|
|
)
|
|
runtime_registry_mode = _mode_from(
|
|
observability,
|
|
("runtime_registry", "runtime_registry_mode"),
|
|
default=_mode_from(runtime_metadata, ("runtime_registry", "runtime_registry_mode"), default=base.runtime_registry_mode),
|
|
)
|
|
graph_store_mode = _infer_graph_store_mode(stores, default=base.adapter_registry["graph_store"])
|
|
event_log_mode = _infer_event_log_mode(stores, default=base.adapter_registry["event_log"])
|
|
dry_run_default = _bool_from(policy, "dry_run_default", default=base.dry_run_default)
|
|
trust_zone_labels = _string_tuple(
|
|
policy.get("trust_zone_labels")
|
|
or policy.get("trust_zones")
|
|
or policy.get("required_labels")
|
|
or base.trust_zone_labels
|
|
)
|
|
|
|
return cls(
|
|
local_store_path=store_path,
|
|
adapter_registry={
|
|
"graph_store": graph_store_mode,
|
|
"event_log": event_log_mode,
|
|
"policy_gateway": policy_mode,
|
|
"audit_sink": audit_sink_mode,
|
|
"package_compiler": package_compiler_mode,
|
|
"semantic_index": semantic_index_mode,
|
|
"runtime_registry": runtime_registry_mode,
|
|
},
|
|
policy_mode=policy_mode,
|
|
audit_sink_mode=audit_sink_mode,
|
|
package_compiler_mode=package_compiler_mode,
|
|
semantic_index_mode=semantic_index_mode,
|
|
runtime_registry_mode=runtime_registry_mode,
|
|
dry_run_default=dry_run_default,
|
|
trust_zone_labels=trust_zone_labels,
|
|
)
|
|
|
|
def adapter_mode(self, adapter: str) -> str:
|
|
legacy = {
|
|
"graph_store": "memory",
|
|
"event_log": "memory",
|
|
"policy_gateway": self.policy_mode,
|
|
"audit_sink": self.audit_sink_mode,
|
|
"package_compiler": self.package_compiler_mode,
|
|
"semantic_index": self.semantic_index_mode,
|
|
"runtime_registry": self.runtime_registry_mode,
|
|
}
|
|
return _normalize_mode(self.adapter_registry.get(adapter) or legacy.get(adapter) or "")
|
|
|
|
def diagnostics(self) -> tuple[Diagnostic, ...]:
|
|
diagnostics: list[Diagnostic] = []
|
|
if not self.local_store_path:
|
|
diagnostics.append(Diagnostic("error", "missing_store_path", "Runtime config requires a local store path.", "local_store_path"))
|
|
for adapter, allowed_modes in _SUPPORTED_ADAPTER_MODES.items():
|
|
mode = self.adapter_mode(adapter)
|
|
if mode not in allowed_modes:
|
|
diagnostics.append(
|
|
Diagnostic(
|
|
"error",
|
|
"unsupported_adapter_mode",
|
|
"Unsupported adapter mode.",
|
|
f"adapter_registry.{adapter}",
|
|
{"adapter": adapter, "mode": mode, "allowed_modes": sorted(allowed_modes)},
|
|
)
|
|
)
|
|
elif mode == "external":
|
|
diagnostics.append(
|
|
Diagnostic(
|
|
"warn",
|
|
"external_adapter_declared",
|
|
"External adapter mode is declared and must be supplied by the caller.",
|
|
f"adapter_registry.{adapter}",
|
|
{"adapter": adapter},
|
|
)
|
|
)
|
|
return tuple(diagnostics)
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"local_store_path": self.local_store_path,
|
|
"adapter_registry": dict(self.adapter_registry),
|
|
"policy_mode": self.policy_mode,
|
|
"audit_sink_mode": self.audit_sink_mode,
|
|
"package_compiler_mode": self.package_compiler_mode,
|
|
"semantic_index_mode": self.semantic_index_mode,
|
|
"runtime_registry_mode": self.runtime_registry_mode,
|
|
"dry_run_default": self.dry_run_default,
|
|
"trust_zone_labels": list(self.trust_zone_labels),
|
|
}
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RuntimeAdapterBundle:
|
|
graph_store: Any
|
|
event_log: Any
|
|
package_compiler: Any
|
|
policy_gateway: Any
|
|
audit_sink: Any
|
|
semantic_index: Any | None = None
|
|
runtime_registry: Any | None = None
|
|
diagnostics: tuple[Diagnostic, ...] = ()
|
|
|
|
def to_runtime(self) -> PhaseMemoryRuntime:
|
|
errors = [diagnostic for diagnostic in self.diagnostics if diagnostic.severity == "error"]
|
|
if errors:
|
|
codes = ", ".join(diagnostic.code for diagnostic in errors)
|
|
raise ValueError(f"Runtime adapters are not ready: {codes}")
|
|
return PhaseMemoryRuntime(
|
|
graph_store=self.graph_store,
|
|
event_log=self.event_log,
|
|
package_compiler=self.package_compiler,
|
|
policy_gateway=self.policy_gateway,
|
|
audit_sink=self.audit_sink,
|
|
)
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"graph_store": self.graph_store.__class__.__name__,
|
|
"event_log": self.event_log.__class__.__name__,
|
|
"package_compiler": self.package_compiler.__class__.__name__,
|
|
"policy_gateway": self.policy_gateway.__class__.__name__,
|
|
"audit_sink": self.audit_sink.__class__.__name__,
|
|
"semantic_index": self.semantic_index.__class__.__name__ if self.semantic_index is not None else "disabled",
|
|
"runtime_registry": self.runtime_registry.__class__.__name__ if self.runtime_registry is not None else "disabled",
|
|
"diagnostics": [diagnostic.to_dict() for diagnostic in self.diagnostics],
|
|
}
|
|
|
|
|
|
def service_contracts() -> dict[str, Any]:
|
|
return {"schema_version": SERVICE_CONTRACT_SCHEMA, "operations": SERVICE_OPERATIONS}
|
|
|
|
|
|
def resolve_runtime_adapters(config: RuntimeConfig | None = None, *, external_adapters: dict[str, Any] | None = None) -> RuntimeAdapterBundle:
|
|
config = config or RuntimeConfig.local_default()
|
|
external_adapters = dict(external_adapters or {})
|
|
diagnostics = list(config.diagnostics())
|
|
root = Path(config.local_store_path)
|
|
|
|
graph_store = _resolve_adapter(
|
|
"graph_store",
|
|
config.adapter_mode("graph_store"),
|
|
external_adapters,
|
|
diagnostics,
|
|
local_factories={
|
|
"memory": InMemoryMemoryGraphStore,
|
|
"file": lambda: FileBackedMemoryGraphStore(root),
|
|
},
|
|
)
|
|
event_log = _resolve_adapter(
|
|
"event_log",
|
|
config.adapter_mode("event_log"),
|
|
external_adapters,
|
|
diagnostics,
|
|
local_factories={
|
|
"memory": InMemoryMemoryEventLog,
|
|
"jsonl": lambda: JsonlMemoryEventLog(root / "events.jsonl"),
|
|
},
|
|
)
|
|
package_compiler = _resolve_adapter(
|
|
"package_compiler",
|
|
config.adapter_mode("package_compiler"),
|
|
external_adapters,
|
|
diagnostics,
|
|
local_factories={"noop": NoopContextPackageCompiler},
|
|
)
|
|
policy_gateway = _resolve_adapter(
|
|
"policy_gateway",
|
|
config.adapter_mode("policy_gateway"),
|
|
external_adapters,
|
|
diagnostics,
|
|
local_factories={"allow-all": AllowAllPolicyGateway},
|
|
)
|
|
audit_sink = _resolve_adapter(
|
|
"audit_sink",
|
|
config.adapter_mode("audit_sink"),
|
|
external_adapters,
|
|
diagnostics,
|
|
local_factories={
|
|
"recording": RecordingAuditSink,
|
|
"jsonl": lambda: JsonlAuditSink(root / "audit.jsonl"),
|
|
},
|
|
)
|
|
semantic_index = _resolve_adapter(
|
|
"semantic_index",
|
|
config.adapter_mode("semantic_index"),
|
|
external_adapters,
|
|
diagnostics,
|
|
local_factories={
|
|
"disabled": lambda: None,
|
|
"memory": InMemorySemanticIndex,
|
|
},
|
|
)
|
|
runtime_registry = _resolve_adapter(
|
|
"runtime_registry",
|
|
config.adapter_mode("runtime_registry"),
|
|
external_adapters,
|
|
diagnostics,
|
|
local_factories={"memory": InMemoryRuntimeRegistry},
|
|
)
|
|
return RuntimeAdapterBundle(
|
|
graph_store=graph_store,
|
|
event_log=event_log,
|
|
package_compiler=package_compiler,
|
|
policy_gateway=policy_gateway,
|
|
audit_sink=audit_sink,
|
|
semantic_index=semantic_index,
|
|
runtime_registry=runtime_registry,
|
|
diagnostics=tuple(diagnostics),
|
|
)
|
|
|
|
|
|
def runtime_from_config(config: RuntimeConfig | None = None, *, external_adapters: dict[str, Any] | None = None) -> PhaseMemoryRuntime:
|
|
return resolve_runtime_adapters(config, external_adapters=external_adapters).to_runtime()
|
|
|
|
|
|
def health_report(runtime: PhaseMemoryRuntime, *, config: RuntimeConfig | None = None) -> dict[str, Any]:
|
|
config = config or RuntimeConfig.local_default()
|
|
nodes = runtime.graph_store.list_nodes()
|
|
stale = [node for node in nodes if node.lifecycle.value == "stale"]
|
|
pending_review = [node for node in nodes if node.lifecycle.value == "review_needed"]
|
|
diagnostics = list(config.diagnostics())
|
|
return {
|
|
"schema_version": HEALTH_REPORT_SCHEMA,
|
|
"ok": not any(diagnostic.severity == "error" for diagnostic in diagnostics),
|
|
"adapters": {
|
|
"graph_store": runtime.graph_store.__class__.__name__,
|
|
"event_log": runtime.event_log.__class__.__name__,
|
|
"policy_gateway": runtime.policy_gateway.__class__.__name__,
|
|
"audit_sink": runtime.audit_sink.__class__.__name__,
|
|
"package_compiler": runtime.package_compiler.__class__.__name__,
|
|
},
|
|
"config": config.to_dict(),
|
|
"store": {
|
|
"node_count": len(nodes),
|
|
"stale_memory_count": len(stale),
|
|
"pending_review_count": len(pending_review),
|
|
},
|
|
"diagnostics": [diagnostic.to_dict() for diagnostic in diagnostics],
|
|
}
|
|
|
|
|
|
class LocalServiceRunner:
|
|
"""Minimal optional service runner shape without web framework dependency."""
|
|
|
|
SUPPORTED_OPERATIONS = tuple(SERVICE_OPERATIONS)
|
|
|
|
def __init__(
|
|
self,
|
|
runtime: PhaseMemoryRuntime | None = None,
|
|
config: RuntimeConfig | None = None,
|
|
*,
|
|
external_adapters: dict[str, Any] | None = None,
|
|
) -> None:
|
|
self.config = config or RuntimeConfig.local_default()
|
|
self.runtime = runtime or runtime_from_config(self.config, external_adapters=external_adapters)
|
|
|
|
@classmethod
|
|
def supported_operations(cls) -> tuple[str, ...]:
|
|
return cls.SUPPORTED_OPERATIONS
|
|
|
|
def handle(self, operation: str, payload: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
payload = payload or {}
|
|
if operation == "health.check":
|
|
return health_report(self.runtime, config=self.config)
|
|
if operation == "profile.plan":
|
|
return self.runtime.plan_profile(payload["profile"], source_ref=payload.get("source_ref", "service"))
|
|
if operation == "graph.import":
|
|
return self.runtime.import_graph(payload["graph"], source_ref=payload.get("source_ref", "service"))
|
|
if operation == "graph.lifecycle.plan":
|
|
parameters = payload.get("parameters", {})
|
|
if payload.get("profile"):
|
|
return self.runtime.plan_lifecycle_with_profile(
|
|
payload["profile"],
|
|
payload["graph"],
|
|
source_ref=payload.get("source_ref", "service"),
|
|
profile_source_ref=payload.get("profile_source_ref", "service-profile"),
|
|
refresh_digests=dict(parameters.get("refresh_digests") or {}),
|
|
compact_node_ids=tuple(parameters.get("compact_node_ids") or ()),
|
|
)
|
|
return self.runtime.plan_lifecycle(
|
|
payload["graph"],
|
|
source_ref=payload.get("source_ref", "service"),
|
|
stale_after_days=parameters.get("stale_after_days"),
|
|
delete_after_days=parameters.get("delete_after_days"),
|
|
refresh_digests=dict(parameters.get("refresh_digests") or {}),
|
|
compact_node_ids=tuple(parameters.get("compact_node_ids") or ()),
|
|
)
|
|
if operation == "graph.activation.plan":
|
|
budget = payload.get("budget", {})
|
|
return self.runtime.plan_activation(
|
|
payload["graph"],
|
|
max_items=int(budget["max_items"]),
|
|
max_tokens=int(budget["max_tokens"]),
|
|
profile_id=payload.get("profile_id"),
|
|
priority_node_ids=tuple(payload.get("priority_node_ids") or ()),
|
|
include_events=bool(payload.get("include_events", True)),
|
|
policy_context=dict(payload.get("policy_context") or {}),
|
|
)
|
|
if operation == "package.compile":
|
|
return self.runtime.compile_package(
|
|
payload["selection"],
|
|
source_ref=payload.get("source_ref", "service"),
|
|
)
|
|
if operation == "lifecycle.apply":
|
|
return self.runtime.apply_lifecycle_actions(
|
|
payload["actions"],
|
|
approval_marker=str(payload.get("approval_marker") or ""),
|
|
review_record=payload.get("review_record"),
|
|
source_ref=payload.get("source_ref", "service"),
|
|
)
|
|
if operation == "audit.query":
|
|
return self.runtime.query_audit(
|
|
dict(payload.get("filters") or {}),
|
|
source_ref=payload.get("source_ref", "service"),
|
|
)
|
|
raise ValueError(f"Unsupported service operation: {operation}")
|
|
|
|
|
|
def kontextual_delegation_envelope(
|
|
*,
|
|
operation: str,
|
|
graph_id: str = "",
|
|
profile_id: str = "",
|
|
policy_decision: dict[str, Any] | None = None,
|
|
audit_ref: str = "",
|
|
) -> dict[str, Any]:
|
|
return {
|
|
"schema_version": KONTEXTUAL_DELEGATION_SCHEMA,
|
|
"operation": operation,
|
|
"phase_memory_owns": ["phase_policy", "lifecycle_planning", "activation_planning"],
|
|
"kontextual_owns": ["durable_records", "permission_aware_retrieval", "long_lived_storage"],
|
|
"graph_id": graph_id,
|
|
"profile_id": profile_id,
|
|
"policy_decision": dict(policy_decision or {}),
|
|
"audit_ref": audit_ref,
|
|
"imports": {"avoid_circular_imports": True, "exchange": "json_envelopes"},
|
|
}
|
|
|
|
|
|
def assert_graph_store_conformance(store) -> None:
|
|
profile = ProfileIntent(profile_id="conformance-profile")
|
|
node = MemoryNode("node.conformance", "decision", "Conformance node")
|
|
store.save_profile(profile)
|
|
store.save_node(node)
|
|
assert store.get_profile(profile.profile_id).profile_id == profile.profile_id
|
|
assert store.get_node(node.node_id).node_id == node.node_id
|
|
assert store.list_nodes(kind="decision")
|
|
|
|
|
|
def assert_event_log_conformance(log) -> None:
|
|
event = MemoryEvent("event.conformance", "recorded")
|
|
log.append(event)
|
|
assert log.list_events(kind="recorded")[0].event_id == event.event_id
|
|
|
|
|
|
def assert_context_compiler_conformance(compiler) -> None:
|
|
response = compiler.compile_selection({"id": "selection.conformance", "nodes": [], "events": []})
|
|
assert "package_id" in response or "package_ref" in response
|
|
|
|
|
|
def assert_policy_gateway_conformance(gateway) -> None:
|
|
decision = gateway.authorize(action="read", resource="node.conformance")
|
|
assert isinstance(decision, PolicyDecision)
|
|
|
|
|
|
def assert_audit_sink_conformance(sink) -> None:
|
|
receipt = sink.record({"operation": "conformance"})
|
|
assert receipt.get("recorded") is True
|
|
assert sink.query(operation="conformance")[0]["operation"] == "conformance"
|
|
|
|
|
|
def assert_semantic_index_conformance(index) -> None:
|
|
node = MemoryNode("node.semantic", "decision", "Conformance search target", metadata={"graph_id": "graph.conformance"})
|
|
receipt = index.upsert_nodes([node])
|
|
results = index.query(graph_id="graph.conformance", query="search target", limit=5)
|
|
assert receipt.get("upserted") == 1
|
|
assert results and results[0]["id"] == node.node_id
|
|
|
|
|
|
def assert_runtime_registry_conformance(registry) -> None:
|
|
envelope = {"operation_id": "op.conformance", "operation": "conformance"}
|
|
receipt = registry.publish_runtime_envelope(envelope)
|
|
fetched = registry.fetch_runtime_envelope(receipt["reference"])
|
|
assert receipt["published"] is True
|
|
assert fetched["operation_id"] == "op.conformance"
|
|
|
|
|
|
def default_conformance_adapters() -> dict[str, Any]:
|
|
return {
|
|
"graph_store": InMemoryMemoryGraphStore(),
|
|
"event_log": InMemoryMemoryEventLog(),
|
|
"context_compiler": NoopContextPackageCompiler(),
|
|
"policy_gateway": AllowAllPolicyGateway(),
|
|
"audit_sink": RecordingAuditSink(),
|
|
"semantic_index": InMemorySemanticIndex(),
|
|
"runtime_registry": InMemoryRuntimeRegistry(),
|
|
}
|
|
|
|
|
|
_SUPPORTED_ADAPTER_MODES = {
|
|
"graph_store": {"memory", "file", "external"},
|
|
"event_log": {"memory", "jsonl", "external"},
|
|
"policy_gateway": {"allow-all", "external"},
|
|
"audit_sink": {"recording", "jsonl", "external"},
|
|
"package_compiler": {"noop", "external"},
|
|
"semantic_index": {"disabled", "memory", "external"},
|
|
"runtime_registry": {"memory", "external"},
|
|
}
|
|
|
|
|
|
_MODE_ALIASES = {
|
|
"allow_all": "allow-all",
|
|
"allowall": "allow-all",
|
|
"local-allow-all": "allow-all",
|
|
"file-backed": "file",
|
|
"file-backed-graph-store": "file",
|
|
"local-graph-store": "file",
|
|
"local-json": "file",
|
|
"json-lines": "jsonl",
|
|
"local-event-log": "jsonl",
|
|
"jsonl-event-log": "jsonl",
|
|
"recording-audit": "recording",
|
|
"jsonl-audit": "jsonl",
|
|
"noop-compiler": "noop",
|
|
"markitect-context-package": "noop",
|
|
"in-memory": "memory",
|
|
"local-memory": "memory",
|
|
"off": "disabled",
|
|
"none": "disabled",
|
|
"false": "disabled",
|
|
"true": "memory",
|
|
}
|
|
|
|
|
|
def _resolve_adapter(
|
|
adapter: str,
|
|
mode: str,
|
|
external_adapters: dict[str, Any],
|
|
diagnostics: list[Diagnostic],
|
|
*,
|
|
local_factories: dict[str, Any],
|
|
) -> Any:
|
|
if mode == "external":
|
|
if adapter in external_adapters:
|
|
return external_adapters[adapter]
|
|
diagnostics.append(
|
|
Diagnostic(
|
|
"error",
|
|
"missing_external_adapter",
|
|
"External adapter mode was requested but no adapter instance was supplied.",
|
|
f"adapter_registry.{adapter}",
|
|
{"adapter": adapter},
|
|
)
|
|
)
|
|
return None
|
|
factory = local_factories.get(mode)
|
|
if factory is None:
|
|
diagnostics.append(
|
|
Diagnostic(
|
|
"error",
|
|
"unresolved_adapter_mode",
|
|
"Adapter mode cannot be resolved by the local runtime.",
|
|
f"adapter_registry.{adapter}",
|
|
{"adapter": adapter, "mode": mode},
|
|
)
|
|
)
|
|
return None
|
|
return factory()
|
|
|
|
|
|
def _runtime_metadata(profile: ProfileIntent) -> dict[str, Any]:
|
|
runtime = profile.metadata.get("runtime")
|
|
return dict(runtime) if isinstance(runtime, dict) else {}
|
|
|
|
|
|
def _mode_from(mapping: dict[str, Any], keys: tuple[str, ...], *, default: str) -> str:
|
|
for key in keys:
|
|
if key in mapping:
|
|
return _normalize_mode(mapping[key])
|
|
return _normalize_mode(default)
|
|
|
|
|
|
def _store_mode(stores: dict[str, str], keys: tuple[str, ...], default: str) -> str:
|
|
for key in keys:
|
|
if key in stores:
|
|
return _normalize_mode(stores[key])
|
|
return _normalize_mode(default)
|
|
|
|
|
|
def _infer_graph_store_mode(stores: dict[str, str], *, default: str) -> str:
|
|
specific = _store_mode(stores, ("graph_store", "memory_graph", "graph"), "")
|
|
if specific:
|
|
return specific
|
|
values = {_normalize_mode(value) for value in stores.values()}
|
|
if "file" in values:
|
|
return "file"
|
|
return _normalize_mode(default)
|
|
|
|
|
|
def _infer_event_log_mode(stores: dict[str, str], *, default: str) -> str:
|
|
specific = _store_mode(stores, ("event_log", "events", "conversation"), "")
|
|
if specific:
|
|
return specific
|
|
values = {_normalize_mode(value) for value in stores.values()}
|
|
if "jsonl" in values:
|
|
return "jsonl"
|
|
return _normalize_mode(default)
|
|
|
|
|
|
def _normalize_mode(value: Any) -> str:
|
|
text = str(value).strip().lower().replace("_", "-")
|
|
return _MODE_ALIASES.get(text, text)
|
|
|
|
|
|
def _bool_from(mapping: dict[str, Any], key: str, *, default: bool) -> bool:
|
|
if key not in mapping:
|
|
return default
|
|
value = mapping[key]
|
|
if isinstance(value, bool):
|
|
return value
|
|
return str(value).strip().lower() in {"1", "true", "yes", "on"}
|
|
|
|
|
|
def _string_tuple(value: Any) -> tuple[str, ...]:
|
|
if value is None:
|
|
return ()
|
|
if isinstance(value, str):
|
|
return (value,)
|
|
return tuple(str(item) for item in value)
|