67 lines
2.0 KiB
Python
67 lines
2.0 KiB
Python
"""Deterministic recovery assertions used by clean-room restore tests."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable, Mapping
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RecoveryReport:
|
|
checks: Mapping[str, bool]
|
|
details: Mapping[str, str]
|
|
|
|
@property
|
|
def passed(self) -> bool:
|
|
return all(self.checks.values())
|
|
|
|
|
|
class RecoveryProbe:
|
|
"""Prove recovery semantics without requiring a production credential."""
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
login: Callable[[], bool],
|
|
discovery: Callable[[], bool],
|
|
tool_call: Callable[[], bool],
|
|
negative_auth: Callable[[], bool],
|
|
audit_continuity: Callable[[], bool],
|
|
) -> None:
|
|
self._checks = {
|
|
"login": login,
|
|
"discovery": discovery,
|
|
"toolCall": tool_call,
|
|
"negativeAuth": negative_auth,
|
|
"auditContinuity": audit_continuity,
|
|
}
|
|
|
|
def run(self) -> RecoveryReport:
|
|
results: dict[str, bool] = {}
|
|
details: dict[str, str] = {}
|
|
for name, check in self._checks.items():
|
|
try:
|
|
result = bool(check())
|
|
except Exception as exc: # noqa: BLE001
|
|
result = False
|
|
details[name] = type(exc).__name__
|
|
results[name] = result
|
|
details.setdefault(name, "passed" if result else "failed")
|
|
return RecoveryReport(results, details)
|
|
|
|
|
|
def connector_isolation_report(statuses: Mapping[str, str]) -> dict[str, Any]:
|
|
"""A failed connector degrades only itself and never grants access elsewhere."""
|
|
|
|
normalized = {str(name): str(status) for name, status in statuses.items()}
|
|
failed = sorted(
|
|
name for name, status in normalized.items() if status not in {"healthy", "ready"}
|
|
)
|
|
return {
|
|
"status": "degraded" if failed else "healthy",
|
|
"failedConnectors": failed,
|
|
"isolated": True,
|
|
"redisAuthoritative": False,
|
|
}
|