feat(scada): require complete authorized investigation evidence
Generic Container CI/CD / test-build-publish (push) Successful in 5m52s
Server CI/CD v2 / build-test-publish-and-deploy (push) Successful in 5m52s

This commit is contained in:
2026-11-07 21:15:01 +08:00
parent 2552a9b40d
commit 06d8539aa8
12 changed files with 498 additions and 30 deletions
@@ -0,0 +1,40 @@
"""Require persisted complete SCADA input before final investigation conclusions."""
from datetime import datetime
from app.domain.domain_errors import require
def require_complete_scada_evidence(task: dict, evidence: list[dict], refs: list[str]) -> None:
replay = task.get("source", {}).get("replay_context")
if task.get("workflow") != "scada_anomaly" and not replay:
return
target = datetime.fromisoformat(
replay["knowledge_cutoff"] if replay else task["trigger"]["occurred_at"]
)
for item in evidence:
if item["evidence_id"] not in refs:
continue
snapshot = item.get("data_snapshot") or {}
if snapshot.get("source") != "query_scada_readings" or snapshot.get("truncated"):
continue
result = snapshot.get("data") or {}
data = result.get("data") if isinstance(result, dict) else None
coverage = data.get("coverage") if isinstance(data, dict) else None
if not coverage or coverage.get("mode") != "all_scada" or coverage.get("query_complete") is not True:
continue
window = result.get("window") or {}
if not window.get("start") or not window.get("end"):
continue
start, end = (datetime.fromisoformat(window[key]) for key in ("start", "end"))
if not start <= target <= end:
continue
if replay and (coverage.get("replay_id") != replay["replay_id"]
or coverage.get("batch_sequence") != replay["batch_sequence"]
or end != target):
continue
return
require(False, "SCADA_COVERAGE_REQUIRED",
"最终SCADA报告必须引用本轮全量读取证据。调用query_scada_readings并省略device_ids,"
"在任务时间窗内读取全部授权设备、完整属性与观测,核对coverage和缺测清单后再发布。",
status=409)