feat(scada): add calibrated monitoring and incident recovery

This commit is contained in:
2026-11-07 19:44:58 +08:00
parent 26fa5d1909
commit ff8aba4964
109 changed files with 9165 additions and 629 deletions
+60
View File
@@ -0,0 +1,60 @@
"""Spawn-safe HTTP/SSE fixture, controlled only through a test-owned directory."""
import asyncio
from contextlib import asynccontextmanager
import json
import os
from pathlib import Path
import time
from fastapi import FastAPI
from starlette.responses import StreamingResponse
@asynccontextmanager
async def lifespan(app: FastAPI):
directory = Path(os.environ["SCADA_HANDOFF_FIXTURE_DIR"])
pid = os.getpid()
app.state.directory = directory
app.state.version = (directory / "version").read_text().strip()
mode = (directory / "startup-mode").read_text().strip()
(directory / f"started-{pid}").write_text(app.state.version)
if mode == "fail":
raise RuntimeError("test candidate startup failed")
if mode == "delay":
await asyncio.sleep(0.3)
while mode == "wait" and not (directory / "allow-startup").exists():
await asyncio.sleep(0.02)
try:
yield
finally:
(directory / f"stopped-{pid}").write_text(app.state.version)
app = FastAPI(lifespan=lifespan)
async def readiness_check() -> bool:
directory = Path(os.environ["SCADA_HANDOFF_FIXTURE_DIR"])
ready = (directory / "readiness").read_text().strip() == "true"
(directory / f"checked-{os.getpid()}").write_text(str(ready).lower())
await asyncio.sleep(.05)
return ready
@app.get("/health")
async def health():
return {"pid": os.getpid(), "version": app.state.version}
@app.get("/events")
async def events():
async def stream():
directory = app.state.directory
pid = os.getpid()
while not (directory / f"release-{pid}").exists() and not (directory / "release-all").exists():
payload = {"pid": pid, "version": app.state.version, "time_ns": time.time_ns()}
yield f"data: {json.dumps(payload)}\n\n"
await asyncio.sleep(0.025)
yield f"event: complete\ndata: {json.dumps({'pid': pid})}\n\n"
return StreamingResponse(stream(), media_type="text/event-stream")