feat(poc): rewind MCP, skill, and recover-ladder docs

Initial PoC: find_protection, tagged checkpoints, FLR, mutating catalog.
Lab 10.9 status enums (0=Initializing, 1=MeetingSLA). Credentials stay in gitignored config.json.
This commit is contained in:
2026-09-21 12:09:11 -04:00
commit 38ba9c1b50
24 changed files with 1811 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
"""FLR session helpers."""
from __future__ import annotations
import asyncio
from typing import Any
from zerto_rewind_mcp.client import ZertoClient, ZertoError
from zerto_rewind_mcp.util import pick
def session_id_from(payload: Any) -> str:
if payload is None:
raise ZertoError("FLR start returned empty body")
if isinstance(payload, str):
return payload.strip().strip('"')
if isinstance(payload, dict):
value = pick(
payload,
"sessionId",
"SessionId",
"flrSessionIdentifier",
"identifier",
"Identifier",
"id",
"Id",
)
if value:
return str(value)
raise ZertoError(f"Could not read FLR session id from {payload!r}")
def download_token_from(payload: Any) -> str:
if isinstance(payload, str):
return payload.strip().strip('"')
if isinstance(payload, dict):
value = pick(payload, "token", "Token", "downloadToken", "DownloadToken")
if value:
return str(value)
raise ZertoError(f"Could not read FLR download token from {payload!r}")
async def wait_flr_ready(
client: ZertoClient,
session_id: str,
*,
timeout_s: float = 180.0,
interval_s: float = 3.0,
) -> dict[str, Any]:
deadline = asyncio.get_event_loop().time() + timeout_s
last: Any = None
while asyncio.get_event_loop().time() < deadline:
last = await client.get_flr(session_id)
status = ""
if isinstance(last, dict):
status = str(pick(last, "Status", "status", "state", "State") or "")
if status.lower() in {"ready", "mounted", "available"}:
return last if isinstance(last, dict) else {"status": status}
if "mountinprogress" in status.lower() or "inprogress" in status.lower():
await asyncio.sleep(interval_s)
continue
if status.lower() in {"failed", "error"}:
raise ZertoError(f"FLR session {session_id} failed: {last}")
# some appliances omit status once mounted
if isinstance(last, dict) and not status:
return last
await asyncio.sleep(interval_s)
raise ZertoError(
f"FLR session {session_id} not ready within {timeout_s:.0f}s (last={last!r}). "
"Retry browse after a minute, or check EJC is not running and no clone/test is active."
)