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
+9 -8
View File
@@ -48,9 +48,10 @@ async def test_all_worker_operations_share_investigation_creation_scope():
assert request.url.params["task_type"] == "event_investigation"
assert request.url.params["task_created_from"] == boundary
return httpx.Response(200, json={"items": []})
assert request.url.path.endswith("leases/recover")
assert request.url.path.endswith(("leases/recover", "recovery/reconcile"))
assert json.loads(request.content) == {
"limit": 100, "task_type": "event_investigation", "task_created_from": boundary,
"limit": 20 if request.url.path.endswith("recovery/reconcile") else 100,
"task_type": "event_investigation", "task_created_from": boundary,
}
return httpx.Response(200, json={"task_ids": []})
@@ -58,7 +59,7 @@ async def test_all_worker_operations_share_investigation_creation_scope():
bearer="TEST_ONLY", task_created_from=boundary)
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
assert await OperationalWorker(settings, client).tick() == 0
assert len(requests) == 5
assert len(requests) == 6
@pytest.mark.anyio
@@ -68,7 +69,7 @@ async def test_worker_reconciles_messages_at_capacity_and_preserves_transport_re
def handler(request):
path = request.url.path
if path.endswith("leases/recover"):
if path.endswith(("leases/recover", "recovery/reconcile")):
return httpx.Response(200, json={"items": []})
if path.endswith("agent-messages/pending"):
return httpx.Response(200, json={"items": pending})
@@ -129,7 +130,7 @@ async def test_persisted_queue_retries_with_backend_execution_identity():
return httpx.Response(200, json={"ok": True, "agent_instance_id": "agent-a"})
assert request.headers["Authorization"] == "Bearer TEST_ONLY"
assert not request.url.path.endswith("schedules/dispatch-due")
if request.url.path.endswith("leases/recover"):
if request.url.path.endswith(("leases/recover", "recovery/reconcile")):
return httpx.Response(200, json={"items": []})
if request.method == "GET":
if request.url.params.get("status") != "queued":
@@ -191,7 +192,7 @@ async def test_bounded_queue_continues_after_one_agent_handoff_fails():
limits.append(int(request.url.params["limit"]))
return httpx.Response(200, json={"items": runs})
if request.url.host == "server.test":
if request.url.path.endswith("leases/recover"):
if request.url.path.endswith(("leases/recover", "recovery/reconcile")):
return httpx.Response(200, json={"items": []})
failures.append(json.loads(request.content))
return httpx.Response(200, json={})
@@ -269,7 +270,7 @@ async def test_worker_refreshes_running_agent_credentials_after_restart():
def handler(request):
if request.url.path == "/health":
return httpx.Response(200, json={"ok": True, "agent_instance_id": "agent-a"})
if request.url.path.endswith("leases/recover"):
if request.url.path.endswith(("leases/recover", "recovery/reconcile")):
return httpx.Response(200, json={"items": []})
if request.method == "GET":
items = [{
@@ -300,7 +301,7 @@ async def test_worker_only_restores_sessions_owned_by_target_agent_instance():
def handler(request):
if request.url.path == "/health":
return httpx.Response(200, json={"ok": True, "agent_instance_id": "agent-a"})
if request.url.path.endswith("leases/recover"):
if request.url.path.endswith(("leases/recover", "recovery/reconcile")):
return httpx.Response(200, json={"items": []})
if request.method == "GET":
items = [{