From 86e90922184f44e506d512190a6f4042e85aad19 Mon Sep 17 00:00:00 2001 From: ogt Date: Thu, 25 Jun 2026 10:18:52 +0800 Subject: [PATCH] feat(iwooos): add Wazuh owner evidence preflight --- docs/LOGBOOK.md | 24 ++ ...APPEARANCE-INCIDENT-READBACK-2026-06-24.md | 4 +- ...ENT-VISIBILITY-OWNER-EVIDENCE-PREFLIGHT.md | 77 ++++++ ...ity-owner-evidence-preflight.snapshot.json | 92 +++++++ .../security-mirror-progress-guard.py | 4 + ...ent-visibility-owner-evidence-preflight.py | 255 ++++++++++++++++++ 6 files changed, 455 insertions(+), 1 deletion(-) create mode 100644 docs/security/WAZUH-AGENT-VISIBILITY-OWNER-EVIDENCE-PREFLIGHT.md create mode 100644 docs/security/wazuh-agent-visibility-owner-evidence-preflight.snapshot.json create mode 100644 scripts/security/wazuh-agent-visibility-owner-evidence-preflight.py diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 3f26eef8..7cd074fa 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,27 @@ +## 2026-06-25|Wazuh agent registry owner evidence 收件預檢 + +**背景**:Wazuh 用戶端消失事故的下一個關鍵不是再看 Dashboard,而是讓 owner 能用脫敏、可驗收的格式提供 manager registry truth。若沒有收件 preflight,agent 數字、截圖、raw log 或口頭回覆都容易被誤當成驗收證據。 + +**完成**: +- 新增 `scripts/security/wazuh-agent-visibility-owner-evidence-preflight.py`。 +- 新增 `docs/security/wazuh-agent-visibility-owner-evidence-preflight.snapshot.json`。 +- 新增人讀版 `docs/security/WAZUH-AGENT-VISIBILITY-OWNER-EVIDENCE-PREFLIGHT.md`。 +- 收件欄位固定為 18 項:owner、decision、scope、agent counts、last_seen window、manager health ref、dashboard API status ref、redacted evidence refs、followup、rollback 與 postcheck。 +- reviewer checks 固定為 7 項,outcome lanes 固定為 5 條,forbidden payloads 固定為 18 項。 +- `security-mirror-progress-guard.py` 已納入此 preflight;`received / accepted / runtime_gate` 全部仍為 `0`。 + +**驗證**: +- `python3 scripts/security/wazuh-agent-visibility-owner-evidence-preflight.py --root .`:`fields=18 checks=7 received=0 accepted=0 runtime_gate=0`。 +- `python3 scripts/security/security-mirror-progress-guard.py --root .`:待提交前總驗證。 + +**完成度同步**: +- Wazuh agent visibility owner evidence preflight:`100%` source-side。 +- Wazuh P0-A manager registry 只讀驗收:`48% -> 56%` source-side、`0%` live registry accepted。 +- SOC / Wazuh no-false-green 納管:`66% -> 69%`。 +- production deploy、live Wazuh registry accepted、Dashboard stored API 修復、owner response accepted、active response / host write:仍維持 `0%`。 + +**邊界**:本輪沒有查 Wazuh、沒有 SSH、沒有讀 secret、沒有保存 raw log、沒有重新註冊 agent、沒有送 Telegram、沒有部署,也沒有啟用 active response。 + ## 2026-06-25|IwoooS Wazuh 正式路由只讀讀回前台 **背景**:IwoooS 前台已有 Wazuh 入侵回讀與環境閘門,但缺少直接顯示 `/api/iwooos/wazuh` 目前讀回狀態的卡片;operator 仍可能把「Wazuh 已建置」誤解成「正式路由已部署、agent registry 已驗收」。 diff --git a/docs/security/WAZUH-AGENT-DISAPPEARANCE-INCIDENT-READBACK-2026-06-24.md b/docs/security/WAZUH-AGENT-DISAPPEARANCE-INCIDENT-READBACK-2026-06-24.md index 5f80bba5..00c3fdf8 100644 --- a/docs/security/WAZUH-AGENT-DISAPPEARANCE-INCIDENT-READBACK-2026-06-24.md +++ b/docs/security/WAZUH-AGENT-DISAPPEARANCE-INCIDENT-READBACK-2026-06-24.md @@ -58,6 +58,8 @@ 本輪再補 IwoooS Wazuh API source-side no-false-green:`/api/iwooos/wazuh` 與 `/api/v1/iwooos/wazuh` 在 `agent_total=0` 時回 `wazuh_agent_registry_empty`,在低於 server-side `IWOOOS_WAZUH_EXPECTED_MIN_AGENT_COUNT` 時回 `wazuh_agent_registry_below_expected`。這只改判讀與 metadata,不代表 production 已部署、manager registry 已驗收或 active response 已授權。 +本輪另新增 Wazuh agent visibility owner evidence preflight:`scripts/security/wazuh-agent-visibility-owner-evidence-preflight.py`、`docs/security/wazuh-agent-visibility-owner-evidence-preflight.snapshot.json` 與人讀版 `docs/security/WAZUH-AGENT-VISIBILITY-OWNER-EVIDENCE-PREFLIGHT.md`。它固定 owner 必要欄位、reviewer 檢查、拒收分流與禁止 payload;`received / accepted / runtime_gate` 仍全部為 `0`。 + ## 4. 立即凍結邊界 在 manager 端 agent registry 被只讀驗收前,以下全部維持禁止: @@ -73,7 +75,7 @@ | 優先 | 工作 | 驗收條件 | 目前完成度 | |------|------|----------|------------| -| P0-A | Wazuh manager agent registry 只讀驗收 | owner 提供脫敏 `agent_total / active / disconnected / last_seen` ref,或經 server-side secret metadata 啟用 IwoooS 只讀 API;source-side 已能把 registry 空或低於預期判成退化 | `48%` source-side、`0%` live registry accepted | +| P0-A | Wazuh manager agent registry 只讀驗收 | owner 提供脫敏 `agent_total / active / disconnected / last_seen` ref,或經 server-side secret metadata 啟用 IwoooS 只讀 API;source-side 已能把 registry 空或低於預期判成退化,並新增 owner evidence 收件預檢 | `56%` source-side、`0%` live registry accepted | | P0-B | Dashboard stored API / rate-limit / TLS trust 修復 gate | 查明 `/api/check-stored-api` 429/500 根因;維修前有 owner、rollback、postcheck;維修後 Dashboard 與 API count 一致 | `35%` | | P0-C | IwoooS live metadata route 正式部署 | `/api/iwooos/wazuh` 不再 404,回傳 schema `iwooos_wazuh_readonly_status_v1`,不洩漏 agent identity / internal IP / secret | `55%` source-side、`0%` production | | P0-D | Wazuh agent disappearance alert card | 產出 `ai_automation_alert_card_v1`,包含 agent count delta、Dashboard API status、manager health、next gate、owner;本輪已新增 `wazuh_dashboard_api_readback_degraded` formatter / test / guard、AwoooP `/runs/ai-alert-cards` delivery readback contract 與 Runs 前台面板 | `92%` source-side、`0%` production receipt | diff --git a/docs/security/WAZUH-AGENT-VISIBILITY-OWNER-EVIDENCE-PREFLIGHT.md b/docs/security/WAZUH-AGENT-VISIBILITY-OWNER-EVIDENCE-PREFLIGHT.md new file mode 100644 index 00000000..d12a13a6 --- /dev/null +++ b/docs/security/WAZUH-AGENT-VISIBILITY-OWNER-EVIDENCE-PREFLIGHT.md @@ -0,0 +1,77 @@ +# Wazuh Agent Visibility Owner Evidence 收件預檢 + +> 狀態:source-side preflight ready。這不是 owner evidence received、不是 accepted、不是 runtime authorization。 + +## 目的 + +Wazuh 用戶端消失事故不能用 Dashboard 畫面、代理服務在線、TCP 連線存在或口頭宣稱結案。IwoooS 需要的是 Wazuh manager registry 的脫敏計數與時間窗,才能判斷代理清單是否真的恢復。 + +本預檢定義 owner 要提供的欄位、reviewer 要檢查的項目、拒收分流與禁止 payload。它不查 Wazuh、不 SSH、不讀 secret、不保存 raw log、不啟用 active response,也不開任何主機寫入。 + +## 必要欄位 + +- `owner_role` +- `team` +- `decision` +- `decision_reason` +- `affected_scope` +- `agent_total` +- `agent_active` +- `agent_disconnected` +- `agent_never_connected` +- `last_seen_window_start` +- `last_seen_window_end` +- `registry_collected_at` +- `manager_health_ref` +- `dashboard_api_status_ref` +- `redacted_evidence_refs` +- `followup_owner` +- `rollback_owner` +- `postcheck_plan` + +## Reviewer 檢查 + +- 欄位齊全且皆為脫敏 metadata。 +- `agent_total` 不可小於 `agent_active + agent_disconnected + agent_never_connected`。 +- `last_seen` 時間窗需能覆蓋事故觀察區間。 +- manager health ref 與 dashboard API status ref 不可互相替代。 +- redacted evidence refs 不得包含 raw payload、截圖原文或主機完整輸出。 +- owner decision 不可直接授權 active response、host write 或 secret rotation。 +- rollback owner 與 postcheck plan 必須存在。 + +## 拒收與隔離 + +以下內容不得進 repo、前台、LOGBOOK 或 AwoooP 訊息本文: + +- raw Wazuh payload、raw log、完整 journal、未脫敏截圖。 +- agent 原名、內網位址、完整主機輸出。 +- authorization header、token、basic auth、password、cookie、private key、client key。 +- 夾帶 active response、host write、firewall change、Nginx reload 或其他 runtime 操作要求。 + +## 現況計數 + +| 項目 | 計數 | +|---|---:| +| 必要欄位 | `18` | +| Reviewer 檢查 | `7` | +| Outcome lanes | `5` | +| Forbidden payloads | `18` | +| Owner evidence received | `0` | +| Owner evidence accepted | `0` | +| Runtime gate | `0` | + +## 驗證 + +```bash +python3 scripts/security/wazuh-agent-visibility-owner-evidence-preflight.py --root . +``` + +預期: + +```text +WAZUH_AGENT_VISIBILITY_OWNER_EVIDENCE_PREFLIGHT_OK fields=18 checks=7 received=0 accepted=0 runtime_gate=0 +``` + +## 邊界 + +本預檢通過只代表收件格式和安全邊界已固定,不代表 Wazuh agent registry 已驗收、不代表 Dashboard stored API 已修復、不代表 IwoooS production route 已部署、不代表主機乾淨,也不授權 active response、host write、Kali active scan、secret rotation、Nginx / Docker / K8s / firewall 操作。 diff --git a/docs/security/wazuh-agent-visibility-owner-evidence-preflight.snapshot.json b/docs/security/wazuh-agent-visibility-owner-evidence-preflight.snapshot.json new file mode 100644 index 00000000..ceddeefa --- /dev/null +++ b/docs/security/wazuh-agent-visibility-owner-evidence-preflight.snapshot.json @@ -0,0 +1,92 @@ +{ + "execution_boundaries": { + "agent_identity_public_display_allowed": false, + "host_write_authorized": false, + "internal_ip_public_display_allowed": false, + "not_authorization": true, + "raw_wazuh_payload_storage_allowed": false, + "runtime_execution_authorized": false, + "secret_value_collection_allowed": false, + "wazuh_active_response_authorized": false, + "wazuh_api_live_query_authorized": false + }, + "forbidden_payloads": [ + "raw_wazuh_payload", + "raw_log", + "full_journal", + "unredacted_screenshot", + "agent_name", + "internal_ip", + "authorization_header", + "bearer_token", + "basic_auth", + "password", + "cookie", + "private_key", + "client_keys", + "active_response_enable", + "host_write", + "firewall_change", + "nginx_reload" + ], + "mode": "redacted_metadata_only_no_secret_no_wazuh_query", + "operator_interpretation": [ + "這是 Wazuh agent registry 脫敏證據收件前的預檢,不代表已收到或已接受 owner evidence。", + "agent service active、TCP 連線存在、Dashboard 可見或口頭宣稱都不可替代 manager registry counts。", + "若 evidence 夾帶 raw log、未脫敏截圖、內網位址、agent 原名或 secret,必須隔離,不得渲染到前台。", + "任何 active response、host write、firewall、Nginx、Docker、K8s 或 secret 變更都要切獨立人工批准。" + ], + "outcome_lanes": [ + "waiting_owner_evidence", + "request_missing_fields", + "quarantine_sensitive_payload", + "reject_runtime_action_request", + "ready_for_reviewer_validation" + ], + "required_fields": [ + "owner_role", + "team", + "decision", + "decision_reason", + "affected_scope", + "agent_total", + "agent_active", + "agent_disconnected", + "agent_never_connected", + "last_seen_window_start", + "last_seen_window_end", + "registry_collected_at", + "manager_health_ref", + "dashboard_api_status_ref", + "redacted_evidence_refs", + "followup_owner", + "rollback_owner", + "postcheck_plan" + ], + "reviewer_checks": [ + "欄位齊全且皆為脫敏 metadata。", + "agent_total 不可小於 active + disconnected + never_connected。", + "last_seen 時間窗需能覆蓋事故觀察區間。", + "manager health ref 與 dashboard API status ref 不可互相替代。", + "redacted evidence refs 不得包含 raw payload、截圖原文或主機完整輸出。", + "owner decision 不可直接授權 active response、host write 或 secret rotation。", + "rollback owner 與 postcheck plan 必須存在。" + ], + "schema_version": "wazuh_agent_visibility_owner_evidence_preflight_v1", + "scope": "wazuh_agent_visibility_registry_truth", + "status": "owner_evidence_preflight_ready_no_runtime_action", + "summary": { + "active_response_authorized_count": 0, + "forbidden_payload_count": 17, + "host_write_authorized_count": 0, + "outcome_lane_count": 5, + "owner_evidence_accepted_count": 0, + "owner_evidence_quarantined_count": 0, + "owner_evidence_received_count": 0, + "owner_evidence_rejected_count": 0, + "required_field_count": 18, + "reviewer_check_count": 7, + "runtime_gate_count": 0, + "secret_value_collection_allowed_count": 0 + } +} diff --git a/scripts/security/security-mirror-progress-guard.py b/scripts/security/security-mirror-progress-guard.py index 15d8463b..c0963cf9 100755 --- a/scripts/security/security-mirror-progress-guard.py +++ b/scripts/security/security-mirror-progress-guard.py @@ -115,6 +115,10 @@ def validate(root: Path) -> None: str(root / "scripts" / "security" / "wazuh-agent-visibility-runtime-gate.py") ) wazuh_agent_visibility_runtime_gate["validate"](root) + wazuh_agent_visibility_owner_evidence_preflight = runpy.run_path( + str(root / "scripts" / "security" / "wazuh-agent-visibility-owner-evidence-preflight.py") + ) + wazuh_agent_visibility_owner_evidence_preflight["validate"](root) telegram_alert_readability_guard = runpy.run_path( str(root / "scripts" / "security" / "telegram-alert-readability-guard.py") ) diff --git a/scripts/security/wazuh-agent-visibility-owner-evidence-preflight.py b/scripts/security/wazuh-agent-visibility-owner-evidence-preflight.py new file mode 100644 index 00000000..a9e4b373 --- /dev/null +++ b/scripts/security/wazuh-agent-visibility-owner-evidence-preflight.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python3 +""" +Wazuh agent visibility owner evidence 收件預檢。 + +本工具只驗證 repo 內 committed snapshot;不查 Wazuh、不 SSH、不讀 +secret、不保存 raw log、不重新註冊 agent,也不啟用 active response。 +""" + +from __future__ import annotations + +import argparse +import json +import re +from pathlib import Path +from typing import Any + + +SNAPSHOT_PATH = Path("docs/security/wazuh-agent-visibility-owner-evidence-preflight.snapshot.json") +SCHEMA_VERSION = "wazuh_agent_visibility_owner_evidence_preflight_v1" + +REQUIRED_FIELDS = [ + "owner_role", + "team", + "decision", + "decision_reason", + "affected_scope", + "agent_total", + "agent_active", + "agent_disconnected", + "agent_never_connected", + "last_seen_window_start", + "last_seen_window_end", + "registry_collected_at", + "manager_health_ref", + "dashboard_api_status_ref", + "redacted_evidence_refs", + "followup_owner", + "rollback_owner", + "postcheck_plan", +] + +REVIEWER_CHECKS = [ + "欄位齊全且皆為脫敏 metadata。", + "agent_total 不可小於 active + disconnected + never_connected。", + "last_seen 時間窗需能覆蓋事故觀察區間。", + "manager health ref 與 dashboard API status ref 不可互相替代。", + "redacted evidence refs 不得包含 raw payload、截圖原文或主機完整輸出。", + "owner decision 不可直接授權 active response、host write 或 secret rotation。", + "rollback owner 與 postcheck plan 必須存在。", +] + +OUTCOME_LANES = [ + "waiting_owner_evidence", + "request_missing_fields", + "quarantine_sensitive_payload", + "reject_runtime_action_request", + "ready_for_reviewer_validation", +] + +FORBIDDEN_PAYLOADS = [ + "raw_wazuh_payload", + "raw_log", + "full_journal", + "unredacted_screenshot", + "agent_name", + "internal_ip", + "authorization_header", + "bearer_token", + "basic_auth", + "password", + "cookie", + "private_key", + "client_keys", + "active_response_enable", + "host_write", + "firewall_change", + "nginx_reload", +] + +FORBIDDEN_TEXT_PATTERNS = [ + re.compile(r"\b(?:10|127|172\.(?:1[6-9]|2\d|3[01])|192\.168)\.\d{1,3}\.\d{1,3}\b"), + re.compile(r"Authorization\s*:", re.IGNORECASE), + re.compile(r"Bearer\s+[A-Za-z0-9._-]{10,}", re.IGNORECASE), + re.compile(r"Basic\s+[A-Za-z0-9+/=]{10,}", re.IGNORECASE), + re.compile(r"password\s*[:=]\s*['\"][^'\"]+['\"]", re.IGNORECASE), + re.compile(r"token\s*[:=]\s*['\"][^'\"]+['\"]", re.IGNORECASE), + re.compile(r"cookie\s*[:=]\s*['\"][^'\"]+['\"]", re.IGNORECASE), + re.compile(r"client\.keys", re.IGNORECASE), + re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----"), +] + + +def load_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) + + +def assert_equal(label: str, actual: Any, expected: Any) -> None: + if actual != expected: + raise SystemExit(f"BLOCKED {label}: expected {expected!r}, got {actual!r}") + + +def assert_false(label: str, actual: Any) -> None: + assert_equal(label, actual, False) + + +def assert_zero(label: str, actual: Any) -> None: + assert_equal(label, actual, 0) + + +def collect_string_values(value: Any) -> list[str]: + if isinstance(value, str): + return [value] + if isinstance(value, list): + values: list[str] = [] + for item in value: + values.extend(collect_string_values(item)) + return values + if isinstance(value, dict): + values = [] + for item in value.values(): + values.extend(collect_string_values(item)) + return values + return [] + + +def validate_no_forbidden_text(snapshot: dict[str, Any]) -> None: + for text in collect_string_values(snapshot): + for pattern in FORBIDDEN_TEXT_PATTERNS: + if pattern.search(text): + raise SystemExit( + "BLOCKED wazuh_agent_visibility_owner_evidence_preflight: " + "snapshot contains forbidden sensitive text" + ) + + +def build_snapshot() -> dict[str, Any]: + return { + "schema_version": SCHEMA_VERSION, + "status": "owner_evidence_preflight_ready_no_runtime_action", + "mode": "redacted_metadata_only_no_secret_no_wazuh_query", + "scope": "wazuh_agent_visibility_registry_truth", + "required_fields": REQUIRED_FIELDS, + "reviewer_checks": REVIEWER_CHECKS, + "outcome_lanes": OUTCOME_LANES, + "forbidden_payloads": FORBIDDEN_PAYLOADS, + "summary": { + "required_field_count": len(REQUIRED_FIELDS), + "reviewer_check_count": len(REVIEWER_CHECKS), + "outcome_lane_count": len(OUTCOME_LANES), + "forbidden_payload_count": len(FORBIDDEN_PAYLOADS), + "owner_evidence_received_count": 0, + "owner_evidence_accepted_count": 0, + "owner_evidence_rejected_count": 0, + "owner_evidence_quarantined_count": 0, + "runtime_gate_count": 0, + "active_response_authorized_count": 0, + "host_write_authorized_count": 0, + "secret_value_collection_allowed_count": 0, + }, + "execution_boundaries": { + "wazuh_api_live_query_authorized": False, + "wazuh_active_response_authorized": False, + "host_write_authorized": False, + "secret_value_collection_allowed": False, + "raw_wazuh_payload_storage_allowed": False, + "agent_identity_public_display_allowed": False, + "internal_ip_public_display_allowed": False, + "runtime_execution_authorized": False, + "not_authorization": True, + }, + "operator_interpretation": [ + "這是 Wazuh agent registry 脫敏證據收件前的預檢,不代表已收到或已接受 owner evidence。", + "agent service active、TCP 連線存在、Dashboard 可見或口頭宣稱都不可替代 manager registry counts。", + "若 evidence 夾帶 raw log、未脫敏截圖、內網位址、agent 原名或 secret,必須隔離,不得渲染到前台。", + "任何 active response、host write、firewall、Nginx、Docker、K8s 或 secret 變更都要切獨立人工批准。", + ], + } + + +def validate(root: Path) -> None: + snapshot = load_json(root / SNAPSHOT_PATH) + assert_equal("schema_version", snapshot.get("schema_version"), SCHEMA_VERSION) + assert_equal( + "status", + snapshot.get("status"), + "owner_evidence_preflight_ready_no_runtime_action", + ) + assert_equal( + "mode", + snapshot.get("mode"), + "redacted_metadata_only_no_secret_no_wazuh_query", + ) + assert_equal("required_fields", snapshot.get("required_fields"), REQUIRED_FIELDS) + assert_equal("reviewer_checks", snapshot.get("reviewer_checks"), REVIEWER_CHECKS) + assert_equal("outcome_lanes", snapshot.get("outcome_lanes"), OUTCOME_LANES) + assert_equal("forbidden_payloads", snapshot.get("forbidden_payloads"), FORBIDDEN_PAYLOADS) + + summary = snapshot.get("summary", {}) + assert_equal("summary.required_field_count", summary.get("required_field_count"), len(REQUIRED_FIELDS)) + assert_equal("summary.reviewer_check_count", summary.get("reviewer_check_count"), len(REVIEWER_CHECKS)) + assert_equal("summary.outcome_lane_count", summary.get("outcome_lane_count"), len(OUTCOME_LANES)) + assert_equal("summary.forbidden_payload_count", summary.get("forbidden_payload_count"), len(FORBIDDEN_PAYLOADS)) + for key in [ + "owner_evidence_received_count", + "owner_evidence_accepted_count", + "owner_evidence_rejected_count", + "owner_evidence_quarantined_count", + "runtime_gate_count", + "active_response_authorized_count", + "host_write_authorized_count", + "secret_value_collection_allowed_count", + ]: + assert_zero(f"summary.{key}", summary.get(key)) + + boundaries = snapshot.get("execution_boundaries", {}) + for key, value in boundaries.items(): + if key == "not_authorization": + assert_equal(f"execution_boundaries.{key}", value, True) + else: + assert_false(f"execution_boundaries.{key}", value) + validate_no_forbidden_text(snapshot) + + +def main() -> None: + parser = argparse.ArgumentParser(description="Wazuh agent visibility owner evidence 收件預檢") + parser.add_argument("--root", type=Path, default=Path.cwd()) + parser.add_argument("--output", type=Path) + parser.add_argument("--json", action="store_true") + args = parser.parse_args() + + root = args.root.resolve() + if args.output: + snapshot = build_snapshot() + output = args.output if args.output.is_absolute() else root / args.output + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text(json.dumps(snapshot, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + validate(root) + snapshot = load_json(root / SNAPSHOT_PATH) + if args.json: + print(json.dumps(snapshot, ensure_ascii=False, sort_keys=True)) + return + summary = snapshot["summary"] + print( + "WAZUH_AGENT_VISIBILITY_OWNER_EVIDENCE_PREFLIGHT_OK " + f"fields={summary['required_field_count']} " + f"checks={summary['reviewer_check_count']} " + f"received={summary['owner_evidence_received_count']} " + f"accepted={summary['owner_evidence_accepted_count']} " + f"runtime_gate={summary['runtime_gate_count']}" + ) + + +if __name__ == "__main__": + main()