diff --git a/apps/api/src/api/v1/agents.py b/apps/api/src/api/v1/agents.py index 458d1f2b..8b8e9aeb 100644 --- a/apps/api/src/api/v1/agents.py +++ b/apps/api/src/api/v1/agents.py @@ -73,6 +73,9 @@ from src.services.ai_agent_critic_reviewer_result_capture import ( from src.services.ai_agent_deployment_layout import ( load_latest_ai_agent_deployment_layout, ) +from src.services.awoooi_status_cleanup_dashboard import ( + load_latest_awoooi_status_cleanup_dashboard, +) from src.services.ai_agent_failure_receipt_no_send_replay import ( load_latest_ai_agent_failure_receipt_no_send_replay, ) @@ -763,6 +766,36 @@ async def get_agent_deployment_layout() -> dict[str, Any]: ) from exc +@router.get( + "/awoooi-status-cleanup-dashboard", + response_model=dict[str, Any], + summary="取得 AWOOOI 狀態清理儀表板", + description=( + "讀取最新已提交的 AWOOOI Status Cleanup Dashboard 只讀快照;" + "此端點只呈現狀態清理完成度、owner gate、apply gate、artifact sync 與 Wazuh handoff 邊界。" + "它不更新 project_current_status / memory、不同步 raw Codex App DB / auth / conversations / sessions、" + "不呼叫 Wazuh live API、不執行 active response、不改主機、不修改 workflow / repo refs、" + "不執行 backup / restore / migration。" + ), +) +async def get_awoooi_status_cleanup_dashboard() -> dict[str, Any]: + """回傳最新 AWOOOI 狀態清理儀表板只讀快照。""" + try: + payload = await asyncio.to_thread(load_latest_awoooi_status_cleanup_dashboard) + return redact_public_lan_topology(payload) + except FileNotFoundError as exc: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=str(exc), + ) from exc + except (json.JSONDecodeError, ValueError) as exc: + logger.error("awoooi_status_cleanup_dashboard_invalid", error=str(exc)) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="AWOOOI 狀態清理儀表板快照無效", + ) from exc + + @router.get( "/agent-12-agent-war-room", response_model=dict[str, Any], diff --git a/apps/api/src/services/awoooi_status_cleanup_dashboard.py b/apps/api/src/services/awoooi_status_cleanup_dashboard.py new file mode 100644 index 00000000..7bf6ddeb --- /dev/null +++ b/apps/api/src/services/awoooi_status_cleanup_dashboard.py @@ -0,0 +1,276 @@ +""" +AWOOOI Status Cleanup dashboard snapshot. + +Loads the read-only dashboard that combines status cleanup preflight, owner +review, owner response preflight, dry-run execution plan, apply gate, and +artifact sync readback. This module never writes memory, syncs raw Codex app +data, fetches repos, modifies workflows, queries Wazuh live APIs, or authorizes +runtime writes. +""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +from src.services.snapshot_paths import default_operations_dir + +_DEFAULT_OPERATIONS_DIR = default_operations_dir(Path(__file__)) +_SNAPSHOT_PATTERN = "awoooi-status-cleanup-dashboard.snapshot.json" +_SCHEMA_VERSION = "awoooi_status_cleanup_dashboard_v1" + + +def load_latest_awoooi_status_cleanup_dashboard( + operations_dir: Path | None = None, +) -> dict[str, Any]: + """Load the committed AWOOOI Status Cleanup dashboard snapshot.""" + directory = operations_dir or _DEFAULT_OPERATIONS_DIR + candidates = sorted(directory.glob(_SNAPSHOT_PATTERN)) + if not candidates: + raise FileNotFoundError(f"no AWOOOI status cleanup dashboard found in {directory}") + + latest = candidates[-1] + with latest.open(encoding="utf-8") as handle: + payload = json.load(handle) + + if not isinstance(payload, dict): + raise ValueError(f"{latest}: expected JSON object") + _require_schema(payload, _SCHEMA_VERSION, str(latest)) + _require_read_only_boundaries(payload, str(latest)) + _require_summary(payload, str(latest)) + _require_cards(payload, str(latest)) + _require_risk_controls(payload, str(latest)) + _require_wazuh_boundary(payload, str(latest)) + _require_hard_gates(payload, str(latest)) + return payload + + +def _require_schema(payload: dict[str, Any], expected: str, label: str) -> None: + actual = payload.get("schema_version") + if actual != expected: + raise ValueError(f"{label}: expected schema_version={expected}, got {actual!r}") + + +def _require_read_only_boundaries(payload: dict[str, Any], label: str) -> None: + blocked_flags = { + "secret_values_collected", + "remote_write_performed", + "local_product_write_performed", + "execution_authorized", + "memory_write_authorized", + "wazuh_api_live_query_authorized", + "runtime_execution_authorized", + "ui_implementation_authorized", + } + invalid = sorted(flag for flag in blocked_flags if payload.get(flag) is not False) + if invalid: + raise ValueError(f"{label}: dashboard boundaries must remain false: {invalid}") + + operation_boundaries = payload.get("operation_boundaries") or {} + invalid_ops = sorted(key for key, value in operation_boundaries.items() if value is not False) + if invalid_ops: + raise ValueError(f"{label}: operation boundaries must remain false: {invalid_ops}") + + +def _require_summary(payload: dict[str, Any], label: str) -> None: + summary = payload.get("summary") or {} + gates = payload.get("gate_cards") or [] + blockers = payload.get("blocking_reasons") or [] + actions = payload.get("next_actions") or [] + if payload.get("target_route") != "/workspace/status-cleanup": + raise ValueError(f"{label}: target_route must remain /workspace/status-cleanup") + if summary.get("dashboard_status") != "blocked_status_cleanup_apply_not_authorized": + raise ValueError(f"{label}: dashboard_status must remain blocked") + if summary.get("gate_count") != len(gates): + raise ValueError(f"{label}: gate_count mismatch") + if summary.get("blocked_gate_count") != sum(1 for item in gates if item.get("blocked") is True): + raise ValueError(f"{label}: blocked_gate_count mismatch") + if summary.get("blocking_reason_count") != len(blockers): + raise ValueError(f"{label}: blocking_reason_count mismatch") + if summary.get("next_action_count") != len(actions): + raise ValueError(f"{label}: next_action_count mismatch") + for flag in ( + "apply_allowed", + "memory_write_authorized", + "wazuh_api_live_query_authorized", + "runtime_execution_authorized", + "ui_implementation_allowed", + ): + if summary.get(flag) is not False: + raise ValueError(f"{label}: summary.{flag} must remain false") + if summary.get("gate_count") != 5: + raise ValueError(f"{label}: dashboard must contain five status cleanup gates") + if summary.get("wazuh_handoff_status") != "blocked_not_released": + raise ValueError(f"{label}: Wazuh handoff status must remain blocked") + if summary.get("wazuh_handoff_base_commit") != "b540fc0c": + raise ValueError(f"{label}: Wazuh handoff base must remain b540fc0c") + if summary.get("wazuh_handoff_commit_count") != 7: + raise ValueError(f"{label}: Wazuh handoff commit count must be 7") + if summary.get("wazuh_handoff_patch_count") != 7: + raise ValueError(f"{label}: Wazuh handoff patch count must be 7") + zero_wazuh_summary = ( + "wazuh_release_owner_request_sent_count", + "wazuh_release_owner_response_accepted_count", + "wazuh_acknowledged_release_owner_ack_count", + "wazuh_accepted_evidence_count", + "wazuh_live_metadata_owner_count", + "wazuh_secret_metadata_count", + "wazuh_live_agent_registry_readback", + "iwooos_wazuh_runtime_gate", + "wazuh_active_response_count", + "wazuh_agent_visibility_runtime_gate_count", + "wazuh_push_gate_count", + "wazuh_deploy_gate_count", + "wazuh_readback_gate_count", + "wazuh_runtime_gate_count", + ) + non_zero_wazuh = sorted(key for key in zero_wazuh_summary if summary.get(key) != 0) + if non_zero_wazuh: + raise ValueError(f"{label}: Wazuh release summary gates must remain zero: {non_zero_wazuh}") + + +def _require_cards(payload: dict[str, Any], label: str) -> None: + gate_ids = {item.get("gate_id") for item in payload.get("gate_cards") or []} + required_gates = { + "status_cleanup_preflight", + "owner_review_package", + "owner_response_preflight", + "execution_plan", + "apply_gate", + } + missing_gates = sorted(required_gates - gate_ids) + if missing_gates: + raise ValueError(f"{label}: gate_cards missing required gates: {missing_gates}") + + metric_ids = {item.get("metric_id") for item in payload.get("metric_cards") or []} + required_metrics = { + "overall_completion", + "project_status_age", + "owner_flags", + "approved_sections", + "execution_preview", + "apply_confirmation", + "artifact_sync", + } + missing_metrics = sorted(required_metrics - metric_ids) + if missing_metrics: + raise ValueError(f"{label}: metric_cards missing required metrics: {missing_metrics}") + + unsafe = [ + item.get("gate_id") + for item in payload.get("gate_cards") or [] + if item.get("memory_write_authorized") is not False + or item.get("execution_authorized") is not False + ] + if unsafe: + raise ValueError(f"{label}: gate cards must remain read-only: {sorted(unsafe)}") + + +def _require_risk_controls(payload: dict[str, Any], label: str) -> None: + control_ids = {item.get("control_id") for item in payload.get("risk_controls") or []} + required = { + "memory_write", + "raw_codex_history", + "wazuh_runtime", + "workflow", + "repo_refs", + "host", + "backup_restore", + "ui", + } + missing = sorted(required - control_ids) + if missing: + raise ValueError(f"{label}: risk_controls missing controls: {missing}") + unsafe = sorted( + item.get("control_id") + for item in payload.get("risk_controls") or [] + if item.get("authorized") is not False + ) + if unsafe: + raise ValueError(f"{label}: risk controls must not authorize actions: {unsafe}") + + +def _require_wazuh_boundary(payload: dict[str, Any], label: str) -> None: + handoff = payload.get("wazuh_handoff") or {} + if handoff.get("status") != "blocked_not_released": + raise ValueError(f"{label}: Wazuh handoff status must remain blocked_not_released") + if handoff.get("base_commit") != "b540fc0c": + raise ValueError(f"{label}: Wazuh handoff base must remain b540fc0c") + if handoff.get("commit_count") != 7: + raise ValueError(f"{label}: Wazuh handoff commit_count must be 7") + if handoff.get("patch_count") != 7: + raise ValueError(f"{label}: Wazuh handoff patch_count must be 7") + zero_handoff_keys = ( + "release_owner_request_sent_count", + "release_owner_response_accepted_count", + "acknowledged_release_owner_ack_count", + "accepted_evidence_count", + "live_metadata_owner_count", + "secret_metadata_count", + "wazuh_live_agent_registry_readback", + "iwooos_wazuh_runtime_gate", + "active_response", + "push_gate_count", + "deploy_gate_count", + "readback_gate_count", + "runtime_gate_count", + ) + non_zero_handoff = sorted(key for key in zero_handoff_keys if handoff.get(key) != 0) + if non_zero_handoff: + raise ValueError(f"{label}: Wazuh handoff gates must remain zero: {non_zero_handoff}") + if handoff.get("runtime_execution_authorized") is not False: + raise ValueError(f"{label}: Wazuh runtime execution must remain false") + if handoff.get("wazuh_api_live_query_authorized") is not False: + raise ValueError(f"{label}: Wazuh live query must remain false") + if handoff.get("stored_api_secret_metadata_changed") is not False: + raise ValueError(f"{label}: Wazuh stored API secret metadata must remain unchanged") + if handoff.get("agent_visibility_status") != "blocked_waiting_manager_agent_registry_readback": + raise ValueError(f"{label}: Wazuh agent visibility must remain blocked") + visibility_false = ( + "manager_agent_registry_readback_passed", + "iwooos_live_route_readback_passed", + "dashboard_agent_list_recovered", + ) + invalid_visibility = sorted(key for key in visibility_false if handoff.get(key) is not False) + if invalid_visibility: + raise ValueError(f"{label}: Wazuh visibility flags must remain false: {invalid_visibility}") + if handoff.get("agent_visibility_runtime_gate_count") != 0: + raise ValueError(f"{label}: Wazuh visibility runtime gate count must remain zero") + boundary = str(handoff.get("boundary", "")) + for token in ( + "base=b540fc0c", + "38dc3c2f", + "9a53d3e1", + "e9972d47", + "758d419e", + "04db4b8a", + "8eec298e", + "325f262a", + "release_lane_preflight=ready0_acks0of6_evidence0of6_push0_deploy0_readback0_runtime0", + "owner_gate=request_sent0_response_accepted0_acks0of6_evidence0of6_push0_deploy0_readback0_runtime0", + "live_metadata_env_gate=owner0_secret_metadata0_push0_deploy0_readback0_runtime0", + "wazuh_live_agent_registry_readback=0", + "iwooos_wazuh_runtime_gate=0", + "active_response=0", + "manager_agent_registry_readback_passed=false", + "iwooos_live_route_readback_passed=false", + "dashboard_agent_list_recovered=false", + "push_blocked", + ): + if token not in boundary: + raise ValueError(f"{label}: Wazuh handoff boundary missing {token}") + + +def _require_hard_gates(payload: dict[str, Any], label: str) -> None: + gate_text = "\n".join(payload.get("hard_gates") or []) + for token in ( + "project_current_status", + "memory_write_authorized=false", + "raw Codex App", + "Wazuh", + ".gitea/workflows", + "apps/web", + ): + if token not in gate_text: + raise ValueError(f"{label}: hard_gates missing {token}") diff --git a/apps/api/src/services/snapshot_paths.py b/apps/api/src/services/snapshot_paths.py index 6640ca1a..d2b8071e 100644 --- a/apps/api/src/services/snapshot_paths.py +++ b/apps/api/src/services/snapshot_paths.py @@ -30,3 +30,8 @@ def resolve_repo_root(anchor: Path) -> Path: def default_evaluations_dir(anchor: Path) -> Path: """Resolve the default committed evaluations snapshot directory.""" return resolve_repo_root(anchor) / "docs" / "evaluations" + + +def default_operations_dir(anchor: Path) -> Path: + """Resolve the default committed operations snapshot directory.""" + return resolve_repo_root(anchor) / "docs" / "operations" diff --git a/apps/api/tests/test_awoooi_status_cleanup_dashboard.py b/apps/api/tests/test_awoooi_status_cleanup_dashboard.py new file mode 100644 index 00000000..0ce67af0 --- /dev/null +++ b/apps/api/tests/test_awoooi_status_cleanup_dashboard.py @@ -0,0 +1,112 @@ +from __future__ import annotations + +import copy +import json + +import pytest + +from src.services.awoooi_status_cleanup_dashboard import ( + load_latest_awoooi_status_cleanup_dashboard, +) + + +def test_load_latest_awoooi_status_cleanup_dashboard_reads_committed_snapshot(): + data = load_latest_awoooi_status_cleanup_dashboard() + + assert data["schema_version"] == "awoooi_status_cleanup_dashboard_v1" + assert data["summary"]["dashboard_status"] == "blocked_status_cleanup_apply_not_authorized" + assert data["summary"]["gate_count"] == 5 + assert data["summary"]["blocked_gate_count"] == 5 + assert data["summary"]["accepted_owner_flag_count"] == 0 + assert data["summary"]["required_owner_flag_count"] == 6 + assert data["summary"]["apply_allowed"] is False + assert data["summary"]["memory_write_authorized"] is False + assert data["summary"]["wazuh_api_live_query_authorized"] is False + assert data["summary"]["runtime_execution_authorized"] is False + assert data["summary"]["wazuh_handoff_status"] == "blocked_not_released" + assert data["summary"]["wazuh_handoff_base_commit"] == "b540fc0c" + assert data["summary"]["wazuh_handoff_commit_count"] == 7 + assert data["summary"]["wazuh_handoff_patch_count"] == 7 + assert data["summary"]["wazuh_live_metadata_owner_count"] == 0 + assert data["summary"]["wazuh_secret_metadata_count"] == 0 + assert data["summary"]["wazuh_live_agent_registry_readback"] == 0 + assert data["summary"]["iwooos_wazuh_runtime_gate"] == 0 + assert data["summary"]["wazuh_active_response_count"] == 0 + assert data["summary"]["wazuh_agent_visibility_status"] == "blocked_waiting_manager_agent_registry_readback" + assert data["summary"]["wazuh_manager_agent_registry_readback_passed"] is False + assert data["summary"]["wazuh_iwooos_live_route_readback_passed"] is False + assert data["summary"]["wazuh_dashboard_agent_list_recovered"] is False + assert data["summary"]["wazuh_agent_visibility_runtime_gate_count"] == 0 + assert data["summary"]["wazuh_runtime_gate_count"] == 0 + assert data["memory_write_authorized"] is False + assert data["runtime_execution_authorized"] is False + assert data["wazuh_handoff"]["wazuh_api_live_query_authorized"] is False + assert data["wazuh_handoff"]["base_commit"] == "b540fc0c" + assert data["wazuh_handoff"]["patch_count"] == 7 + assert data["wazuh_handoff"]["live_metadata_owner_count"] == 0 + assert data["wazuh_handoff"]["wazuh_live_agent_registry_readback"] == 0 + assert data["wazuh_handoff"]["active_response"] == 0 + assert data["wazuh_handoff"]["agent_visibility_status"] == "blocked_waiting_manager_agent_registry_readback" + assert data["wazuh_handoff"]["manager_agent_registry_readback_passed"] is False + assert "base=b540fc0c" in data["wazuh_handoff"]["boundary"] + assert "release_lane_preflight=ready0_acks0of6_evidence0of6_push0_deploy0_readback0_runtime0" in data["wazuh_handoff"]["boundary"] + assert "owner_gate=request_sent0_response_accepted0_acks0of6_evidence0of6_push0_deploy0_readback0_runtime0" in data["wazuh_handoff"]["boundary"] + assert "live_metadata_env_gate=owner0_secret_metadata0_push0_deploy0_readback0_runtime0" in data["wazuh_handoff"]["boundary"] + assert "wazuh_live_agent_registry_readback=0" in data["wazuh_handoff"]["boundary"] + assert "manager_agent_registry_readback_passed=false" in data["wazuh_handoff"]["boundary"] + assert {item["gate_id"] for item in data["gate_cards"]} >= { + "status_cleanup_preflight", + "owner_review_package", + "owner_response_preflight", + "execution_plan", + "apply_gate", + } + + +def test_awoooi_status_cleanup_dashboard_rejects_memory_write_authorization(tmp_path): + snapshot = _snapshot() + snapshot["memory_write_authorized"] = True + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="dashboard boundaries"): + load_latest_awoooi_status_cleanup_dashboard(tmp_path) + + +def test_awoooi_status_cleanup_dashboard_rejects_wazuh_live_query(tmp_path): + snapshot = _snapshot() + snapshot["wazuh_handoff"]["wazuh_api_live_query_authorized"] = True + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="Wazuh live query"): + load_latest_awoooi_status_cleanup_dashboard(tmp_path) + + +def test_awoooi_status_cleanup_dashboard_rejects_live_metadata_gate(tmp_path): + snapshot = _snapshot() + snapshot["summary"]["wazuh_live_metadata_owner_count"] = 1 + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="Wazuh release summary gates"): + load_latest_awoooi_status_cleanup_dashboard(tmp_path) + + +def test_awoooi_status_cleanup_dashboard_rejects_missing_risk_control(tmp_path): + snapshot = _snapshot() + snapshot["risk_controls"] = [ + item for item in snapshot["risk_controls"] if item["control_id"] != "host" + ] + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="risk_controls"): + load_latest_awoooi_status_cleanup_dashboard(tmp_path) + + +def _snapshot() -> dict: + return copy.deepcopy(load_latest_awoooi_status_cleanup_dashboard()) + + +def _write_snapshot(tmp_path, snapshot: dict) -> None: + (tmp_path / "awoooi-status-cleanup-dashboard.snapshot.json").write_text( + json.dumps(snapshot), + encoding="utf-8", + ) diff --git a/apps/api/tests/test_awoooi_status_cleanup_dashboard_api.py b/apps/api/tests/test_awoooi_status_cleanup_dashboard_api.py new file mode 100644 index 00000000..2a1af0e9 --- /dev/null +++ b/apps/api/tests/test_awoooi_status_cleanup_dashboard_api.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from src.api.v1.agents import router + + +def test_awoooi_status_cleanup_dashboard_endpoint_returns_snapshot(): + app = FastAPI() + app.include_router(router, prefix="/api/v1") + client = TestClient(app) + + response = client.get("/api/v1/agents/awoooi-status-cleanup-dashboard") + + assert response.status_code == 200 + data = response.json() + assert data["schema_version"] == "awoooi_status_cleanup_dashboard_v1" + assert data["summary"]["dashboard_status"] == "blocked_status_cleanup_apply_not_authorized" + assert data["summary"]["gate_count"] == 5 + assert data["summary"]["apply_allowed"] is False + assert data["summary"]["memory_write_authorized"] is False + assert data["summary"]["wazuh_api_live_query_authorized"] is False + assert data["memory_write_authorized"] is False + assert data["runtime_execution_authorized"] is False diff --git a/apps/web/src/lib/api-client.ts b/apps/web/src/lib/api-client.ts index 60430dc5..bdee5bac 100644 --- a/apps/web/src/lib/api-client.ts +++ b/apps/web/src/lib/api-client.ts @@ -329,6 +329,11 @@ export const apiClient = { return handleResponse(res) }, + async getAwoooIStatusCleanupDashboard() { + const res = await fetch(`${API_BASE_URL}/agents/awoooi-status-cleanup-dashboard`) + return handleResponse(res) + }, + async getAiAgent12AgentWarRoom() { const res = await fetch(`${API_BASE_URL}/agents/agent-12-agent-war-room`) return handleResponse(res) @@ -1349,6 +1354,43 @@ export interface AiAgentDeploymentLayoutSnapshot { > } +export interface AwoooIStatusCleanupDashboardSnapshot { + schema_version: 'awoooi_status_cleanup_dashboard_v1' + generated_at: string + target_route: '/workspace/status-cleanup' + summary: { + dashboard_status: string + overall_completion_percent: number + gate_count: number + blocked_gate_count: number + accepted_owner_flag_count: number + required_owner_flag_count: number + apply_allowed: false + memory_write_authorized: false + wazuh_api_live_query_authorized: false + runtime_execution_authorized: false + ui_implementation_allowed: false + [key: string]: unknown + } + metric_cards: Array> + gate_cards: Array> + workflow_rows: Array> + risk_controls: Array> + blocking_reasons: string[] + next_actions: string[] + wazuh_handoff: Record + hard_gates: string[] + operation_boundaries: Record + secret_values_collected: false + remote_write_performed: false + local_product_write_performed: false + execution_authorized: false + memory_write_authorized: false + wazuh_api_live_query_authorized: false + runtime_execution_authorized: false + ui_implementation_authorized: false +} + export interface AiAgent12AgentWarRoomSnapshot { schema_version: 'ai_agent_12_agent_war_room_v1' generated_at: string diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 9e9b3f58..a0d3f264 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -20,7 +20,22 @@ - 不可宣稱:full-stack green、MOMO data current、DR complete、credential escrow complete、或 110 live monitor 已同步 repo v1.42。Live 110 script parity 仍需獨立維護窗口。 **邊界**:本輪沒有主機寫入、沒有 `scp` live script、沒有 Docker / Nginx / firewall / K8s / ArgoCD 操作、沒有 Wazuh / SOC 修改、沒有讀取或保存 secret。 +## 2026-06-24|Status Cleanup Dashboard 繁中化與 read-only API 切片 +**本輪收斂範圍:** +- 新增 AWOOOI Status Cleanup Dashboard read-only service / schema / snapshot / Markdown / generator,將狀態清理完成度、owner gate、apply gate、artifact sync 與 Wazuh handoff 邊界整理成單一資料模型。 +- 人類可讀標題、卡片 caption、Markdown 區塊標題與 risk control 顯示文字使用繁體中文;保留 `metric_id`、`gate_id`、API status、schema key 等技術識別碼。 +- 修正 Wazuh visibility boundary:將 `manager_agent_registry_readback_passed=false`、`iwooos_live_route_readback_passed=false`、`dashboard_agent_list_recovered=false` 與 `agent_visibility_runtime_gate_count=0` 寫入 boundary,讓 read-model guard、報告文字與 API payload 對齊。 +- 新增 `GET /api/v1/agents/awoooi-status-cleanup-dashboard` 與 `getAwoooIStatusCleanupDashboard()`;端點回傳前會走既有 public LAN topology redaction。 + +**readback:** +- Status Cleanup Dashboard:`AWOOOI_STATUS_CLEANUP_DASHBOARD_OK status=blocked_status_cleanup_apply_not_authorized gates=5/5 owner_flags=0/6 apply_allowed=False memory_write=False`。 +- API / read-model tests:`PYTHONPATH=apps/api PYTHONDONTWRITEBYTECODE=1 apps/api/.venv/bin/python -m pytest apps/api/tests/test_awoooi_status_cleanup_dashboard.py apps/api/tests/test_awoooi_status_cleanup_dashboard_api.py -q` → `6 passed`。 + +**仍禁止 / 未完成:** +- 整體治理完成度維持 `41.9%`;Status Cleanup 仍 `gates=5/5 blocked`、`owner_flags=0/6`、`apply_allowed=false`、`memory_write_authorized=false`。 +- 本輪沒有更新 `project_current_status` / memory,沒有 Wazuh live query / active response / runtime deploy,沒有 host、Nginx、Docker、K8s、firewall、workflow、repo refs、backup、restore 或 migration 操作。 +- 本輪只交付 read-only API / client / report model,未宣稱可視 UI 已恢復或正式展示完成。 ## 2026-06-24|23:15 110 cold-start monitor live-sync gate readback **背景**:23:04 已把 MOMO source absence classifier 納入 repo-side cold-start v1.42,但這不等於 110 上 `/home/wooo/scripts/full-stack-cold-start-check.sh` 已更新。為避免下次重啟時 operator 以 live 110 舊腳本輸出做錯判,本輪只做部署 parity 的 read-only 驗證與 SOP gate 補強。 diff --git a/docs/operations/AWOOOI-STATUS-CLEANUP-DASHBOARD-2026-06-24.md b/docs/operations/AWOOOI-STATUS-CLEANUP-DASHBOARD-2026-06-24.md new file mode 100644 index 00000000..9bf3dcc7 --- /dev/null +++ b/docs/operations/AWOOOI-STATUS-CLEANUP-DASHBOARD-2026-06-24.md @@ -0,0 +1,121 @@ +# AWOOOI 狀態清理儀表板 + +- generated_at: `2026-06-24T15:39:52+00:00` +- dashboard_status: `blocked_status_cleanup_apply_not_authorized` +- overall_completion_percent: `41.9%` +- gates: `5/5 blocked` +- owner_flags: `0/6` +- sections: `0/7` +- confirmed_commands: `0/1` +- artifact_count: `47` +- wazuh_handoff: `blocked_not_released base=b540fc0c patches=7` +- wazuh_live_metadata: `owner=0 secret_metadata=0` +- wazuh_live_agent_registry_readback: `0` +- iwooos_wazuh_runtime_gate: `0` +- wazuh_active_response_count: `0` +- wazuh_agent_visibility_status: `blocked_waiting_manager_agent_registry_readback` +- memory_write_authorized: `False` +- runtime_execution_authorized: `False` +- wazuh_api_live_query_authorized: `False` + +## 指標卡片 + +| 指標 | 數值 | 狀態 | 說明 | +|--------|-------|--------|---------| +| `overall_completion` | `41.9%` | `orange` | 產品 Runtime 治理完成度計分卡 | +| `project_status_age` | `11 days` | `blocked` | stale_after_days=2 | +| `owner_flags` | `0/6` | `blocked` | pending_owner_response | +| `approved_sections` | `0/7` | `blocked` | targets=0/1 | +| `execution_preview` | `blocked_owner_response_preflight_not_ready` | `blocked` | dry_run_only=true | +| `apply_confirmation` | `0/1` | `blocked` | pending_final_confirmation | +| `artifact_sync` | `1/2` | `blocked` | artifacts=47 | + +## 閘門卡片 + +| 閘門 | 狀態 | 是否封鎖 | 證據 | +|------|------|----------|------| +| `status_cleanup_preflight` | `blocked_status_cleanup_required` | `True` | `docs/operations/codex-status-cleanup-preflight.snapshot.json` | +| `owner_review_package` | `owner_decision_required` | `True` | `docs/operations/codex-status-cleanup-owner-review-package.snapshot.json` | +| `owner_response_preflight` | `blocked_owner_response_required` | `True` | `docs/operations/codex-status-cleanup-owner-response-preflight.snapshot.json` | +| `execution_plan` | `blocked_owner_response_preflight_not_ready` | `True` | `docs/operations/codex-status-cleanup-execution-plan.snapshot.json` | +| `apply_gate` | `blocked_status_cleanup_apply_not_authorized` | `True` | `docs/operations/codex-status-cleanup-apply-gate.snapshot.json` | + +## 風險控制 + +| 控制項 | 狀態 | 已授權 | +|--------|------|--------| +| `memory_write` | `blocked_final_apply_gate_required` | `False` | +| `raw_codex_history` | `blocked_raw_history_never_synced` | `False` | +| `wazuh_runtime` | `blocked_iwooos_runtime_lane_only` | `False` | +| `workflow` | `blocked_workflow_authorization_required` | `False` | +| `repo_refs` | `blocked_source_control_approval_required` | `False` | +| `host` | `blocked_host_maintenance_required` | `False` | +| `backup_restore` | `blocked_data_owner_approval_required` | `False` | +| `ui` | `blocked_apps_web_readiness_required` | `False` | + +## 下一步 + +- 收齊 owner 旗標:0/6。 +- 收齊已批准章節:0/7。 +- 確認目標路徑:0/1。 +- 確認邊界 acknowledgement:0/5。 +- owner response preflight ready 後,重新產生 dry-run execution plan。 +- apply gate 目前 apply_allowed=False;未變 true 前不得寫 project_current_status。 + +## 封鎖原因 + +- `project_current_status 過期只代表需要狀態清理,不代表可修改 memory / runtime。` +- `不得把一般『批准繼續』解讀成 force push、workflow copy、workflow trigger、repo creation 或 refs sync 授權。` +- `不得同步 raw Codex App DB / auth / conversations / sessions。` +- `不得碰 MacBook Pro MOMO PRO protected legacy paths。` +- `不得新增 Wazuh / Kali / SOC runtime UI/API 或 live query。` +- `不得 SSH 修改主機、改 firewall / Nginx / Docker / K8s 或執行 active scan。` +- `Data / Backup coverage 是治理 evidence,不是 backup / restore / migration executor。` +- `owner_response_preflight:owner_response_status_not_approved_status_cleanup_preview` +- `owner_response_preflight:owner_flag_not_accepted:accept_status_cleanup_scope` +- `owner_response_preflight:owner_flag_not_accepted:approve_project_current_status_update_preview` +- `owner_response_preflight:owner_flag_not_accepted:confirm_latest_logbook_and_scorecard_as_sources` +- `owner_response_preflight:owner_flag_not_accepted:confirm_no_runtime_or_host_authorization` +- `owner_response_preflight:owner_flag_not_accepted:confirm_no_raw_codex_history_sync` +- `owner_response_preflight:owner_flag_not_accepted:confirm_iwooos_wazuh_boundary_preserved` +- `owner_response_preflight:update_section_not_approved:artifact_sync_gate` +- `owner_response_preflight:update_section_not_approved:completion_scorecard` +- `owner_response_preflight:update_section_not_approved:data_backup_gate` +- `owner_response_preflight:update_section_not_approved:gitea_warning_gate` +- `owner_response_preflight:update_section_not_approved:iwooos_wazuh_boundary` +- `owner_response_preflight:update_section_not_approved:latest_logbook_heading` +- `owner_response_preflight:update_section_not_approved:operation_boundaries` +- `owner_response_preflight:target_path_not_approved:/Users/ogt/.claude/projects/-Users-ogt-awoooi/memory/project_current_status.md` +- `owner_response_preflight:boundary_not_acknowledged:memory_write_authorized=false` +- `owner_response_preflight:boundary_not_acknowledged:refs_sync_authorized=false` +- `owner_response_preflight:boundary_not_acknowledged:repo_creation_authorized=false` +- `owner_response_preflight:boundary_not_acknowledged:runtime_execution_authorized=false` +- `owner_response_preflight:boundary_not_acknowledged:wazuh_api_live_query_authorized=false` +- `execution_plan_blocked_until:owner response preflight ready_for_status_cleanup_apply_gate=true` +- `execution_plan_blocked_until:all owner flags accepted` +- `execution_plan_blocked_until:all update sections approved` +- `execution_plan_blocked_until:target path approved` +- `execution_plan_blocked_until:all critical false boundaries acknowledged` +- `execution_plan_blocked_until:final status cleanup apply confirmation accepted` +- `apply_gate:execution_plan_not_ready_for_final_confirmation` +- `apply_gate:owner_response_preflight_not_ready` +- `apply_gate:final_confirmation_status_not_approved_status_cleanup_apply` +- `apply_gate:final_flag_not_accepted:confirm_status_cleanup_command_preview` +- `apply_gate:final_flag_not_accepted:confirm_project_current_status_target_path` +- `apply_gate:final_flag_not_accepted:confirm_post_apply_validation` +- `apply_gate:final_flag_not_accepted:confirm_no_runtime_or_wazuh_deploy` +- `apply_gate:command_preview_not_confirmed` +- `apply_gate:target_path_not_confirmed:/Users/ogt/.claude/projects/-Users-ogt-awoooi/memory/project_current_status.md` +- `wazuh_boundary:Wazuh route / production 404 由另一受控 branch 處理;branch=codex/iwooos-wazuh-boundary-guard-20260624 base=b540fc0c commits=38dc3c2f,9a53d3e1,e9972d47,758d419e,04db4b8a,8eec298e,325f262a patch_sha_1=08f8b36d7261b0dde6bfb0c47597bd0727d578dec3335c5ff7ded2bcaa2b7eb4 patch_sha_2=e6ec8f8d10e8a2bd711c399fa14ba0ab2dfb22f8ab6a733402944302eec7da7c patch_sha_3=7e99bd5284a25519313aea05bb314d3386454b91ce86241424385752d358900d patch_sha_4=f4ffbaecd94d3696660766cc6f4a6bd195762bc533d9502f8edfed2bb8379fab patch_sha_5=9035d6c411bf86d0857970b69dd33631f052aa90de27e52d82d448d4b8e4cec5 patch_sha_6=d3bb98711a3ebf91b9936b41bc232b689befc68a4a7cec38bf9cab4c8d015827 patch_sha_7=5aa3e69fee9624d0ff3f2bfad90595a81eb9306ad6387d640690a85a2f8038d7 apply_proof=release_apply_check_20260624_2248 release_gate=source1_push0_deploy0_readback0_runtime0 release_lane_preflight=ready0_acks0of6_evidence0of6_push0_deploy0_readback0_runtime0 owner_gate=request_sent0_response_accepted0_acks0of6_evidence0of6_push0_deploy0_readback0_runtime0 live_metadata_env_gate=owner0_secret_metadata0_push0_deploy0_readback0_runtime0;wazuh_live_agent_registry_readback=0 manager_agent_registry_readback_passed=false iwooos_live_route_readback_passed=false dashboard_agent_list_recovered=false iwooos_wazuh_runtime_gate=0 active_response=0;push_blocked=missing_noninteractive_gitea_https_credential;本視窗不改 runtime / Nginx / Docker / K8s / firewall / Wazuh secret。 agent_visibility_status=blocked_waiting_manager_agent_registry_readback agent_visibility_runtime_gate_count=0` + +## 強制閘門 + +- 本 dashboard 只整合 read-only snapshots,不代表 project_current_status / memory 已更新。 +- memory_write_authorized=false 時不得修改 project_current_status 或任何 Memory 檔案。 +- 不得同步 raw Codex App DB / auth / conversations / sessions。 +- 不得把一般批准繼續視為 owner response 或 final apply confirmation。 +- 不得新增 Wazuh / Kali / SOC runtime UI/API、live query、active response 或 host containment。 +- 不得修改 Nginx / Docker / K8s / firewall / Wazuh secret,也不得重啟 host。 +- 不得修改 .gitea/workflows、workflow_dispatch、repo refs、GitHub mirror 或 Gitea environment。 +- 不得執行 backup、restore、migration、kubectl、docker compose up 或資料庫寫入。 +- apps/web build/i18n 必要檔案未恢復前,本 dashboard 只可作 page model 資料,不代表 UI 實作授權。 diff --git a/docs/operations/AWOOOI-STATUS-CLEANUP-DASHBOARD.md b/docs/operations/AWOOOI-STATUS-CLEANUP-DASHBOARD.md new file mode 100644 index 00000000..9bf3dcc7 --- /dev/null +++ b/docs/operations/AWOOOI-STATUS-CLEANUP-DASHBOARD.md @@ -0,0 +1,121 @@ +# AWOOOI 狀態清理儀表板 + +- generated_at: `2026-06-24T15:39:52+00:00` +- dashboard_status: `blocked_status_cleanup_apply_not_authorized` +- overall_completion_percent: `41.9%` +- gates: `5/5 blocked` +- owner_flags: `0/6` +- sections: `0/7` +- confirmed_commands: `0/1` +- artifact_count: `47` +- wazuh_handoff: `blocked_not_released base=b540fc0c patches=7` +- wazuh_live_metadata: `owner=0 secret_metadata=0` +- wazuh_live_agent_registry_readback: `0` +- iwooos_wazuh_runtime_gate: `0` +- wazuh_active_response_count: `0` +- wazuh_agent_visibility_status: `blocked_waiting_manager_agent_registry_readback` +- memory_write_authorized: `False` +- runtime_execution_authorized: `False` +- wazuh_api_live_query_authorized: `False` + +## 指標卡片 + +| 指標 | 數值 | 狀態 | 說明 | +|--------|-------|--------|---------| +| `overall_completion` | `41.9%` | `orange` | 產品 Runtime 治理完成度計分卡 | +| `project_status_age` | `11 days` | `blocked` | stale_after_days=2 | +| `owner_flags` | `0/6` | `blocked` | pending_owner_response | +| `approved_sections` | `0/7` | `blocked` | targets=0/1 | +| `execution_preview` | `blocked_owner_response_preflight_not_ready` | `blocked` | dry_run_only=true | +| `apply_confirmation` | `0/1` | `blocked` | pending_final_confirmation | +| `artifact_sync` | `1/2` | `blocked` | artifacts=47 | + +## 閘門卡片 + +| 閘門 | 狀態 | 是否封鎖 | 證據 | +|------|------|----------|------| +| `status_cleanup_preflight` | `blocked_status_cleanup_required` | `True` | `docs/operations/codex-status-cleanup-preflight.snapshot.json` | +| `owner_review_package` | `owner_decision_required` | `True` | `docs/operations/codex-status-cleanup-owner-review-package.snapshot.json` | +| `owner_response_preflight` | `blocked_owner_response_required` | `True` | `docs/operations/codex-status-cleanup-owner-response-preflight.snapshot.json` | +| `execution_plan` | `blocked_owner_response_preflight_not_ready` | `True` | `docs/operations/codex-status-cleanup-execution-plan.snapshot.json` | +| `apply_gate` | `blocked_status_cleanup_apply_not_authorized` | `True` | `docs/operations/codex-status-cleanup-apply-gate.snapshot.json` | + +## 風險控制 + +| 控制項 | 狀態 | 已授權 | +|--------|------|--------| +| `memory_write` | `blocked_final_apply_gate_required` | `False` | +| `raw_codex_history` | `blocked_raw_history_never_synced` | `False` | +| `wazuh_runtime` | `blocked_iwooos_runtime_lane_only` | `False` | +| `workflow` | `blocked_workflow_authorization_required` | `False` | +| `repo_refs` | `blocked_source_control_approval_required` | `False` | +| `host` | `blocked_host_maintenance_required` | `False` | +| `backup_restore` | `blocked_data_owner_approval_required` | `False` | +| `ui` | `blocked_apps_web_readiness_required` | `False` | + +## 下一步 + +- 收齊 owner 旗標:0/6。 +- 收齊已批准章節:0/7。 +- 確認目標路徑:0/1。 +- 確認邊界 acknowledgement:0/5。 +- owner response preflight ready 後,重新產生 dry-run execution plan。 +- apply gate 目前 apply_allowed=False;未變 true 前不得寫 project_current_status。 + +## 封鎖原因 + +- `project_current_status 過期只代表需要狀態清理,不代表可修改 memory / runtime。` +- `不得把一般『批准繼續』解讀成 force push、workflow copy、workflow trigger、repo creation 或 refs sync 授權。` +- `不得同步 raw Codex App DB / auth / conversations / sessions。` +- `不得碰 MacBook Pro MOMO PRO protected legacy paths。` +- `不得新增 Wazuh / Kali / SOC runtime UI/API 或 live query。` +- `不得 SSH 修改主機、改 firewall / Nginx / Docker / K8s 或執行 active scan。` +- `Data / Backup coverage 是治理 evidence,不是 backup / restore / migration executor。` +- `owner_response_preflight:owner_response_status_not_approved_status_cleanup_preview` +- `owner_response_preflight:owner_flag_not_accepted:accept_status_cleanup_scope` +- `owner_response_preflight:owner_flag_not_accepted:approve_project_current_status_update_preview` +- `owner_response_preflight:owner_flag_not_accepted:confirm_latest_logbook_and_scorecard_as_sources` +- `owner_response_preflight:owner_flag_not_accepted:confirm_no_runtime_or_host_authorization` +- `owner_response_preflight:owner_flag_not_accepted:confirm_no_raw_codex_history_sync` +- `owner_response_preflight:owner_flag_not_accepted:confirm_iwooos_wazuh_boundary_preserved` +- `owner_response_preflight:update_section_not_approved:artifact_sync_gate` +- `owner_response_preflight:update_section_not_approved:completion_scorecard` +- `owner_response_preflight:update_section_not_approved:data_backup_gate` +- `owner_response_preflight:update_section_not_approved:gitea_warning_gate` +- `owner_response_preflight:update_section_not_approved:iwooos_wazuh_boundary` +- `owner_response_preflight:update_section_not_approved:latest_logbook_heading` +- `owner_response_preflight:update_section_not_approved:operation_boundaries` +- `owner_response_preflight:target_path_not_approved:/Users/ogt/.claude/projects/-Users-ogt-awoooi/memory/project_current_status.md` +- `owner_response_preflight:boundary_not_acknowledged:memory_write_authorized=false` +- `owner_response_preflight:boundary_not_acknowledged:refs_sync_authorized=false` +- `owner_response_preflight:boundary_not_acknowledged:repo_creation_authorized=false` +- `owner_response_preflight:boundary_not_acknowledged:runtime_execution_authorized=false` +- `owner_response_preflight:boundary_not_acknowledged:wazuh_api_live_query_authorized=false` +- `execution_plan_blocked_until:owner response preflight ready_for_status_cleanup_apply_gate=true` +- `execution_plan_blocked_until:all owner flags accepted` +- `execution_plan_blocked_until:all update sections approved` +- `execution_plan_blocked_until:target path approved` +- `execution_plan_blocked_until:all critical false boundaries acknowledged` +- `execution_plan_blocked_until:final status cleanup apply confirmation accepted` +- `apply_gate:execution_plan_not_ready_for_final_confirmation` +- `apply_gate:owner_response_preflight_not_ready` +- `apply_gate:final_confirmation_status_not_approved_status_cleanup_apply` +- `apply_gate:final_flag_not_accepted:confirm_status_cleanup_command_preview` +- `apply_gate:final_flag_not_accepted:confirm_project_current_status_target_path` +- `apply_gate:final_flag_not_accepted:confirm_post_apply_validation` +- `apply_gate:final_flag_not_accepted:confirm_no_runtime_or_wazuh_deploy` +- `apply_gate:command_preview_not_confirmed` +- `apply_gate:target_path_not_confirmed:/Users/ogt/.claude/projects/-Users-ogt-awoooi/memory/project_current_status.md` +- `wazuh_boundary:Wazuh route / production 404 由另一受控 branch 處理;branch=codex/iwooos-wazuh-boundary-guard-20260624 base=b540fc0c commits=38dc3c2f,9a53d3e1,e9972d47,758d419e,04db4b8a,8eec298e,325f262a patch_sha_1=08f8b36d7261b0dde6bfb0c47597bd0727d578dec3335c5ff7ded2bcaa2b7eb4 patch_sha_2=e6ec8f8d10e8a2bd711c399fa14ba0ab2dfb22f8ab6a733402944302eec7da7c patch_sha_3=7e99bd5284a25519313aea05bb314d3386454b91ce86241424385752d358900d patch_sha_4=f4ffbaecd94d3696660766cc6f4a6bd195762bc533d9502f8edfed2bb8379fab patch_sha_5=9035d6c411bf86d0857970b69dd33631f052aa90de27e52d82d448d4b8e4cec5 patch_sha_6=d3bb98711a3ebf91b9936b41bc232b689befc68a4a7cec38bf9cab4c8d015827 patch_sha_7=5aa3e69fee9624d0ff3f2bfad90595a81eb9306ad6387d640690a85a2f8038d7 apply_proof=release_apply_check_20260624_2248 release_gate=source1_push0_deploy0_readback0_runtime0 release_lane_preflight=ready0_acks0of6_evidence0of6_push0_deploy0_readback0_runtime0 owner_gate=request_sent0_response_accepted0_acks0of6_evidence0of6_push0_deploy0_readback0_runtime0 live_metadata_env_gate=owner0_secret_metadata0_push0_deploy0_readback0_runtime0;wazuh_live_agent_registry_readback=0 manager_agent_registry_readback_passed=false iwooos_live_route_readback_passed=false dashboard_agent_list_recovered=false iwooos_wazuh_runtime_gate=0 active_response=0;push_blocked=missing_noninteractive_gitea_https_credential;本視窗不改 runtime / Nginx / Docker / K8s / firewall / Wazuh secret。 agent_visibility_status=blocked_waiting_manager_agent_registry_readback agent_visibility_runtime_gate_count=0` + +## 強制閘門 + +- 本 dashboard 只整合 read-only snapshots,不代表 project_current_status / memory 已更新。 +- memory_write_authorized=false 時不得修改 project_current_status 或任何 Memory 檔案。 +- 不得同步 raw Codex App DB / auth / conversations / sessions。 +- 不得把一般批准繼續視為 owner response 或 final apply confirmation。 +- 不得新增 Wazuh / Kali / SOC runtime UI/API、live query、active response 或 host containment。 +- 不得修改 Nginx / Docker / K8s / firewall / Wazuh secret,也不得重啟 host。 +- 不得修改 .gitea/workflows、workflow_dispatch、repo refs、GitHub mirror 或 Gitea environment。 +- 不得執行 backup、restore、migration、kubectl、docker compose up 或資料庫寫入。 +- apps/web build/i18n 必要檔案未恢復前,本 dashboard 只可作 page model 資料,不代表 UI 實作授權。 diff --git a/docs/operations/awoooi-status-cleanup-dashboard.snapshot.json b/docs/operations/awoooi-status-cleanup-dashboard.snapshot.json new file mode 100644 index 00000000..42a4e504 --- /dev/null +++ b/docs/operations/awoooi-status-cleanup-dashboard.snapshot.json @@ -0,0 +1,399 @@ +{ + "schema_version": "awoooi_status_cleanup_dashboard_v1", + "generated_at": "2026-06-24T15:39:52+00:00", + "target_route": "/workspace/status-cleanup", + "source_reviews": { + "status_cleanup_preflight": "docs/operations/codex-status-cleanup-preflight.snapshot.json", + "owner_review_package": "docs/operations/codex-status-cleanup-owner-review-package.snapshot.json", + "owner_response_preflight": "docs/operations/codex-status-cleanup-owner-response-preflight.snapshot.json", + "execution_plan": "docs/operations/codex-status-cleanup-execution-plan.snapshot.json", + "apply_gate": "docs/operations/codex-status-cleanup-apply-gate.snapshot.json", + "artifact_sync_readback": "docs/operations/codex-workstation-artifact-sync-readback.snapshot.json", + "scorecard": "docs/operations/product-runtime-governance-completion-scorecard.snapshot.json", + "iwooos_wazuh_release_handoff": "docs/operations/iwooos-wazuh-release-handoff.snapshot.json" + }, + "summary": { + "dashboard_status": "blocked_status_cleanup_apply_not_authorized", + "overall_completion_percent": 41.9, + "p0_009_completion_percent": 87, + "p0_010_completion_percent": 50, + "p1_006_completion_percent": 83, + "p2_001_completion_percent": 41, + "p2_002_completion_percent": 72, + "gate_count": 5, + "blocked_gate_count": 5, + "project_current_status_age_days": 11, + "cleanup_required": true, + "accepted_owner_flag_count": 0, + "required_owner_flag_count": 6, + "approved_update_section_count": 0, + "required_update_section_count": 7, + "approved_target_path_count": 0, + "target_path_count": 1, + "boundary_ack_count": 0, + "required_boundary_ack_count": 5, + "execution_plan_status": "blocked_owner_response_preflight_not_ready", + "apply_gate_status": "blocked_status_cleanup_apply_not_authorized", + "confirmed_command_count": 0, + "planned_command_count": 1, + "artifact_count": 47, + "artifact_sync_blocked_count": 1, + "wazuh_handoff_status": "blocked_not_released", + "wazuh_handoff_base_commit": "b540fc0c", + "wazuh_handoff_commit_count": 7, + "wazuh_handoff_patch_count": 7, + "wazuh_release_owner_request_sent_count": 0, + "wazuh_release_owner_response_accepted_count": 0, + "wazuh_acknowledged_release_owner_ack_count": 0, + "wazuh_required_release_owner_ack_count": 6, + "wazuh_accepted_evidence_count": 0, + "wazuh_required_evidence_count": 6, + "wazuh_live_metadata_owner_count": 0, + "wazuh_secret_metadata_count": 0, + "wazuh_live_agent_registry_readback": 0, + "iwooos_wazuh_runtime_gate": 0, + "wazuh_active_response_count": 0, + "wazuh_agent_visibility_status": "blocked_waiting_manager_agent_registry_readback", + "wazuh_manager_agent_registry_readback_passed": false, + "wazuh_iwooos_live_route_readback_passed": false, + "wazuh_dashboard_agent_list_recovered": false, + "wazuh_agent_visibility_runtime_gate_count": 0, + "wazuh_push_gate_count": 0, + "wazuh_deploy_gate_count": 0, + "wazuh_readback_gate_count": 0, + "wazuh_runtime_gate_count": 0, + "blocking_reason_count": 43, + "next_action_count": 6, + "apply_allowed": false, + "memory_write_authorized": false, + "wazuh_api_live_query_authorized": false, + "runtime_execution_authorized": false, + "ui_implementation_allowed": false + }, + "metric_cards": [ + { + "metric_id": "overall_completion", + "title": "整體治理完成度", + "value": "41.9%", + "status": "orange", + "caption": "產品 Runtime 治理完成度計分卡" + }, + { + "metric_id": "project_status_age", + "title": "project_current_status 年齡", + "value": "11 days", + "status": "blocked", + "caption": "stale_after_days=2" + }, + { + "metric_id": "owner_flags", + "title": "Owner 旗標", + "value": "0/6", + "status": "blocked", + "caption": "pending_owner_response" + }, + { + "metric_id": "approved_sections", + "title": "已批准章節", + "value": "0/7", + "status": "blocked", + "caption": "targets=0/1" + }, + { + "metric_id": "execution_preview", + "title": "執行預覽", + "value": "blocked_owner_response_preflight_not_ready", + "status": "blocked", + "caption": "dry_run_only=true" + }, + { + "metric_id": "apply_confirmation", + "title": "套用確認", + "value": "0/1", + "status": "blocked", + "caption": "pending_final_confirmation" + }, + { + "metric_id": "artifact_sync", + "title": "安全交接 artifacts", + "value": "1/2", + "status": "blocked", + "caption": "artifacts=47" + } + ], + "gate_cards": [ + { + "gate_id": "status_cleanup_preflight", + "title": "狀態清理預檢", + "status": "blocked_status_cleanup_required", + "completion_label": "age_days=11 stale_after=2", + "evidence_ref": "docs/operations/codex-status-cleanup-preflight.snapshot.json", + "blocked": true, + "memory_write_authorized": false, + "execution_authorized": false + }, + { + "gate_id": "owner_review_package", + "title": "Owner 審核包", + "status": "owner_decision_required", + "completion_label": "7 sections / 6 owner flags", + "evidence_ref": "docs/operations/codex-status-cleanup-owner-review-package.snapshot.json", + "blocked": true, + "memory_write_authorized": false, + "execution_authorized": false + }, + { + "gate_id": "owner_response_preflight", + "title": "Owner 回覆預檢", + "status": "blocked_owner_response_required", + "completion_label": "0/6 flags, 0/7 sections", + "evidence_ref": "docs/operations/codex-status-cleanup-owner-response-preflight.snapshot.json", + "blocked": true, + "memory_write_authorized": false, + "execution_authorized": false + }, + { + "gate_id": "execution_plan", + "title": "Dry-run 執行計畫", + "status": "blocked_owner_response_preflight_not_ready", + "completion_label": "0/7 sections, 0/1 targets", + "evidence_ref": "docs/operations/codex-status-cleanup-execution-plan.snapshot.json", + "blocked": true, + "memory_write_authorized": false, + "execution_authorized": false + }, + { + "gate_id": "apply_gate", + "title": "最終套用閘門", + "status": "blocked_status_cleanup_apply_not_authorized", + "completion_label": "0/1 commands, 0/1 targets", + "evidence_ref": "docs/operations/codex-status-cleanup-apply-gate.snapshot.json", + "blocked": true, + "memory_write_authorized": false, + "execution_authorized": false + } + ], + "workflow_rows": [ + { + "step_id": "status_cleanup_preflight", + "title": "狀態清理預檢", + "status": "blocked_status_cleanup_required", + "evidence_ref": "docs/operations/codex-status-cleanup-preflight.snapshot.json", + "next_step": "owner_review_package", + "blocked": true, + "memory_write_authorized": false, + "execution_authorized": false + }, + { + "step_id": "owner_review_package", + "title": "Owner 審核包", + "status": "owner_decision_required", + "evidence_ref": "docs/operations/codex-status-cleanup-owner-review-package.snapshot.json", + "next_step": "owner_response_preflight", + "blocked": true, + "memory_write_authorized": false, + "execution_authorized": false + }, + { + "step_id": "owner_response_preflight", + "title": "Owner 回覆預檢", + "status": "blocked_owner_response_required", + "evidence_ref": "docs/operations/codex-status-cleanup-owner-response-preflight.snapshot.json", + "next_step": "execution_plan", + "blocked": true, + "memory_write_authorized": false, + "execution_authorized": false + }, + { + "step_id": "execution_plan", + "title": "Dry-run 執行計畫", + "status": "blocked_owner_response_preflight_not_ready", + "evidence_ref": "docs/operations/codex-status-cleanup-execution-plan.snapshot.json", + "next_step": "apply_gate", + "blocked": true, + "memory_write_authorized": false, + "execution_authorized": false + }, + { + "step_id": "apply_gate", + "title": "最終套用閘門", + "status": "blocked_status_cleanup_apply_not_authorized", + "evidence_ref": "docs/operations/codex-status-cleanup-apply-gate.snapshot.json", + "next_step": "manual_project_current_status_update_after_explicit_final_confirmation", + "blocked": true, + "memory_write_authorized": false, + "execution_authorized": false + } + ], + "risk_controls": [ + { + "control_id": "memory_write", + "title": "更新 project_current_status / memory", + "status": "blocked_final_apply_gate_required", + "authorized": false + }, + { + "control_id": "raw_codex_history", + "title": "同步 raw Codex App history", + "status": "blocked_raw_history_never_synced", + "authorized": false + }, + { + "control_id": "wazuh_runtime", + "title": "部署或查詢 live Wazuh runtime", + "status": "blocked_iwooos_runtime_lane_only", + "authorized": false + }, + { + "control_id": "workflow", + "title": "修改或觸發 CI/CD workflow", + "status": "blocked_workflow_authorization_required", + "authorized": false + }, + { + "control_id": "repo_refs", + "title": "建立 repo / branch / mirror refs", + "status": "blocked_source_control_approval_required", + "authorized": false + }, + { + "control_id": "host", + "title": "SSH / restart / firewall / Nginx / Docker / K8s", + "status": "blocked_host_maintenance_required", + "authorized": false + }, + { + "control_id": "backup_restore", + "title": "執行 backup / restore / migration", + "status": "blocked_data_owner_approval_required", + "authorized": false + }, + { + "control_id": "ui", + "title": "實作可見 AWOOOI UI", + "status": "blocked_apps_web_readiness_required", + "authorized": false + } + ], + "blocking_reasons": [ + "project_current_status 過期只代表需要狀態清理,不代表可修改 memory / runtime。", + "不得把一般『批准繼續』解讀成 force push、workflow copy、workflow trigger、repo creation 或 refs sync 授權。", + "不得同步 raw Codex App DB / auth / conversations / sessions。", + "不得碰 MacBook Pro MOMO PRO protected legacy paths。", + "不得新增 Wazuh / Kali / SOC runtime UI/API 或 live query。", + "不得 SSH 修改主機、改 firewall / Nginx / Docker / K8s 或執行 active scan。", + "Data / Backup coverage 是治理 evidence,不是 backup / restore / migration executor。", + "owner_response_preflight:owner_response_status_not_approved_status_cleanup_preview", + "owner_response_preflight:owner_flag_not_accepted:accept_status_cleanup_scope", + "owner_response_preflight:owner_flag_not_accepted:approve_project_current_status_update_preview", + "owner_response_preflight:owner_flag_not_accepted:confirm_latest_logbook_and_scorecard_as_sources", + "owner_response_preflight:owner_flag_not_accepted:confirm_no_runtime_or_host_authorization", + "owner_response_preflight:owner_flag_not_accepted:confirm_no_raw_codex_history_sync", + "owner_response_preflight:owner_flag_not_accepted:confirm_iwooos_wazuh_boundary_preserved", + "owner_response_preflight:update_section_not_approved:artifact_sync_gate", + "owner_response_preflight:update_section_not_approved:completion_scorecard", + "owner_response_preflight:update_section_not_approved:data_backup_gate", + "owner_response_preflight:update_section_not_approved:gitea_warning_gate", + "owner_response_preflight:update_section_not_approved:iwooos_wazuh_boundary", + "owner_response_preflight:update_section_not_approved:latest_logbook_heading", + "owner_response_preflight:update_section_not_approved:operation_boundaries", + "owner_response_preflight:target_path_not_approved:/Users/ogt/.claude/projects/-Users-ogt-awoooi/memory/project_current_status.md", + "owner_response_preflight:boundary_not_acknowledged:memory_write_authorized=false", + "owner_response_preflight:boundary_not_acknowledged:refs_sync_authorized=false", + "owner_response_preflight:boundary_not_acknowledged:repo_creation_authorized=false", + "owner_response_preflight:boundary_not_acknowledged:runtime_execution_authorized=false", + "owner_response_preflight:boundary_not_acknowledged:wazuh_api_live_query_authorized=false", + "execution_plan_blocked_until:owner response preflight ready_for_status_cleanup_apply_gate=true", + "execution_plan_blocked_until:all owner flags accepted", + "execution_plan_blocked_until:all update sections approved", + "execution_plan_blocked_until:target path approved", + "execution_plan_blocked_until:all critical false boundaries acknowledged", + "execution_plan_blocked_until:final status cleanup apply confirmation accepted", + "apply_gate:execution_plan_not_ready_for_final_confirmation", + "apply_gate:owner_response_preflight_not_ready", + "apply_gate:final_confirmation_status_not_approved_status_cleanup_apply", + "apply_gate:final_flag_not_accepted:confirm_status_cleanup_command_preview", + "apply_gate:final_flag_not_accepted:confirm_project_current_status_target_path", + "apply_gate:final_flag_not_accepted:confirm_post_apply_validation", + "apply_gate:final_flag_not_accepted:confirm_no_runtime_or_wazuh_deploy", + "apply_gate:command_preview_not_confirmed", + "apply_gate:target_path_not_confirmed:/Users/ogt/.claude/projects/-Users-ogt-awoooi/memory/project_current_status.md", + "wazuh_boundary:Wazuh route / production 404 由另一受控 branch 處理;branch=codex/iwooos-wazuh-boundary-guard-20260624 base=b540fc0c commits=38dc3c2f,9a53d3e1,e9972d47,758d419e,04db4b8a,8eec298e,325f262a patch_sha_1=08f8b36d7261b0dde6bfb0c47597bd0727d578dec3335c5ff7ded2bcaa2b7eb4 patch_sha_2=e6ec8f8d10e8a2bd711c399fa14ba0ab2dfb22f8ab6a733402944302eec7da7c patch_sha_3=7e99bd5284a25519313aea05bb314d3386454b91ce86241424385752d358900d patch_sha_4=f4ffbaecd94d3696660766cc6f4a6bd195762bc533d9502f8edfed2bb8379fab patch_sha_5=9035d6c411bf86d0857970b69dd33631f052aa90de27e52d82d448d4b8e4cec5 patch_sha_6=d3bb98711a3ebf91b9936b41bc232b689befc68a4a7cec38bf9cab4c8d015827 patch_sha_7=5aa3e69fee9624d0ff3f2bfad90595a81eb9306ad6387d640690a85a2f8038d7 apply_proof=release_apply_check_20260624_2248 release_gate=source1_push0_deploy0_readback0_runtime0 release_lane_preflight=ready0_acks0of6_evidence0of6_push0_deploy0_readback0_runtime0 owner_gate=request_sent0_response_accepted0_acks0of6_evidence0of6_push0_deploy0_readback0_runtime0 live_metadata_env_gate=owner0_secret_metadata0_push0_deploy0_readback0_runtime0;wazuh_live_agent_registry_readback=0 manager_agent_registry_readback_passed=false iwooos_live_route_readback_passed=false dashboard_agent_list_recovered=false iwooos_wazuh_runtime_gate=0 active_response=0;push_blocked=missing_noninteractive_gitea_https_credential;本視窗不改 runtime / Nginx / Docker / K8s / firewall / Wazuh secret。 agent_visibility_status=blocked_waiting_manager_agent_registry_readback agent_visibility_runtime_gate_count=0" + ], + "next_actions": [ + "收齊 owner 旗標:0/6。", + "收齊已批准章節:0/7。", + "確認目標路徑:0/1。", + "確認邊界 acknowledgement:0/5。", + "owner response preflight ready 後,重新產生 dry-run execution plan。", + "apply gate 目前 apply_allowed=False;未變 true 前不得寫 project_current_status。" + ], + "wazuh_handoff": { + "status": "blocked_not_released", + "source_ref": "docs/operations/iwooos-wazuh-release-handoff.snapshot.json", + "branch": "codex/iwooos-wazuh-boundary-guard-20260624", + "base_commit": "b540fc0c", + "commit_count": 7, + "patch_count": 7, + "release_owner_request_sent_count": 0, + "release_owner_response_accepted_count": 0, + "acknowledged_release_owner_ack_count": 0, + "required_release_owner_ack_count": 6, + "accepted_evidence_count": 0, + "required_evidence_count": 6, + "live_metadata_owner_count": 0, + "secret_metadata_count": 0, + "wazuh_live_agent_registry_readback": 0, + "iwooos_wazuh_runtime_gate": 0, + "active_response": 0, + "stored_api_secret_metadata_changed": false, + "agent_visibility_status": "blocked_waiting_manager_agent_registry_readback", + "manager_agent_registry_readback_passed": false, + "iwooos_live_route_readback_passed": false, + "dashboard_agent_list_recovered": false, + "agent_visibility_runtime_gate_count": 0, + "push_gate_count": 0, + "deploy_gate_count": 0, + "readback_gate_count": 0, + "runtime_gate_count": 0, + "production_readback_status": "predeploy_404", + "boundary": "Wazuh route / production 404 由另一受控 branch 處理;branch=codex/iwooos-wazuh-boundary-guard-20260624 base=b540fc0c commits=38dc3c2f,9a53d3e1,e9972d47,758d419e,04db4b8a,8eec298e,325f262a patch_sha_1=08f8b36d7261b0dde6bfb0c47597bd0727d578dec3335c5ff7ded2bcaa2b7eb4 patch_sha_2=e6ec8f8d10e8a2bd711c399fa14ba0ab2dfb22f8ab6a733402944302eec7da7c patch_sha_3=7e99bd5284a25519313aea05bb314d3386454b91ce86241424385752d358900d patch_sha_4=f4ffbaecd94d3696660766cc6f4a6bd195762bc533d9502f8edfed2bb8379fab patch_sha_5=9035d6c411bf86d0857970b69dd33631f052aa90de27e52d82d448d4b8e4cec5 patch_sha_6=d3bb98711a3ebf91b9936b41bc232b689befc68a4a7cec38bf9cab4c8d015827 patch_sha_7=5aa3e69fee9624d0ff3f2bfad90595a81eb9306ad6387d640690a85a2f8038d7 apply_proof=release_apply_check_20260624_2248 release_gate=source1_push0_deploy0_readback0_runtime0 release_lane_preflight=ready0_acks0of6_evidence0of6_push0_deploy0_readback0_runtime0 owner_gate=request_sent0_response_accepted0_acks0of6_evidence0of6_push0_deploy0_readback0_runtime0 live_metadata_env_gate=owner0_secret_metadata0_push0_deploy0_readback0_runtime0;wazuh_live_agent_registry_readback=0 manager_agent_registry_readback_passed=false iwooos_live_route_readback_passed=false dashboard_agent_list_recovered=false iwooos_wazuh_runtime_gate=0 active_response=0;push_blocked=missing_noninteractive_gitea_https_credential;本視窗不改 runtime / Nginx / Docker / K8s / firewall / Wazuh secret。 agent_visibility_status=blocked_waiting_manager_agent_registry_readback agent_visibility_runtime_gate_count=0", + "runtime_execution_authorized": false, + "wazuh_api_live_query_authorized": false + }, + "hard_gates": [ + "本 dashboard 只整合 read-only snapshots,不代表 project_current_status / memory 已更新。", + "memory_write_authorized=false 時不得修改 project_current_status 或任何 Memory 檔案。", + "不得同步 raw Codex App DB / auth / conversations / sessions。", + "不得把一般批准繼續視為 owner response 或 final apply confirmation。", + "不得新增 Wazuh / Kali / SOC runtime UI/API、live query、active response 或 host containment。", + "不得修改 Nginx / Docker / K8s / firewall / Wazuh secret,也不得重啟 host。", + "不得修改 .gitea/workflows、workflow_dispatch、repo refs、GitHub mirror 或 Gitea environment。", + "不得執行 backup、restore、migration、kubectl、docker compose up 或資料庫寫入。", + "apps/web build/i18n 必要檔案未恢復前,本 dashboard 只可作 page model 資料,不代表 UI 實作授權。" + ], + "operation_boundaries": { + "memory_write_authorized": false, + "raw_codex_history_sync_authorized": false, + "runtime_execution_authorized": false, + "active_scan_authorized": false, + "wazuh_api_live_query_authorized": false, + "wazuh_active_response_authorized": false, + "repo_creation_authorized": false, + "refs_sync_authorized": false, + "workflow_modification_authorized": false, + "workflow_trigger_authorized": false, + "host_update_authorized": false, + "backup_execution_authorized": false, + "restore_execution_authorized": false, + "migration_authorized": false + }, + "secret_values_collected": false, + "remote_write_performed": false, + "local_product_write_performed": false, + "execution_authorized": false, + "memory_write_authorized": false, + "wazuh_api_live_query_authorized": false, + "runtime_execution_authorized": false, + "ui_implementation_authorized": false +} diff --git a/docs/schemas/awoooi_status_cleanup_dashboard_v1.schema.json b/docs/schemas/awoooi_status_cleanup_dashboard_v1.schema.json new file mode 100644 index 00000000..379e7759 --- /dev/null +++ b/docs/schemas/awoooi_status_cleanup_dashboard_v1.schema.json @@ -0,0 +1,88 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "awoooi_status_cleanup_dashboard_v1", + "title": "AWOOOI 狀態清理儀表板", + "type": "object", + "required": [ + "schema_version", + "generated_at", + "target_route", + "source_reviews", + "summary", + "metric_cards", + "gate_cards", + "workflow_rows", + "risk_controls", + "blocking_reasons", + "next_actions", + "wazuh_handoff", + "hard_gates", + "operation_boundaries", + "secret_values_collected", + "remote_write_performed", + "local_product_write_performed", + "execution_authorized", + "memory_write_authorized", + "wazuh_api_live_query_authorized", + "runtime_execution_authorized", + "ui_implementation_authorized" + ], + "properties": { + "schema_version": {"const": "awoooi_status_cleanup_dashboard_v1"}, + "generated_at": {"type": "string"}, + "target_route": {"const": "/workspace/status-cleanup"}, + "source_reviews": {"type": "object", "additionalProperties": {"type": "string"}}, + "summary": { + "type": "object", + "required": [ + "dashboard_status", + "overall_completion_percent", + "gate_count", + "blocked_gate_count", + "accepted_owner_flag_count", + "required_owner_flag_count", + "approved_update_section_count", + "required_update_section_count", + "wazuh_handoff_status", + "wazuh_handoff_base_commit", + "wazuh_handoff_commit_count", + "wazuh_handoff_patch_count", + "wazuh_live_metadata_owner_count", + "wazuh_secret_metadata_count", + "wazuh_live_agent_registry_readback", + "iwooos_wazuh_runtime_gate", + "wazuh_active_response_count", + "wazuh_agent_visibility_status", + "wazuh_manager_agent_registry_readback_passed", + "wazuh_iwooos_live_route_readback_passed", + "wazuh_dashboard_agent_list_recovered", + "wazuh_agent_visibility_runtime_gate_count", + "wazuh_runtime_gate_count", + "apply_allowed", + "memory_write_authorized", + "wazuh_api_live_query_authorized", + "runtime_execution_authorized", + "ui_implementation_allowed" + ], + "additionalProperties": true + }, + "metric_cards": {"type": "array", "items": {"type": "object"}}, + "gate_cards": {"type": "array", "items": {"type": "object"}}, + "workflow_rows": {"type": "array", "items": {"type": "object"}}, + "risk_controls": {"type": "array", "items": {"type": "object"}}, + "blocking_reasons": {"type": "array", "items": {"type": "string"}}, + "next_actions": {"type": "array", "items": {"type": "string"}}, + "wazuh_handoff": {"type": "object", "additionalProperties": true}, + "hard_gates": {"type": "array", "items": {"type": "string"}}, + "operation_boundaries": {"type": "object", "additionalProperties": {"const": false}}, + "secret_values_collected": {"const": false}, + "remote_write_performed": {"const": false}, + "local_product_write_performed": {"const": false}, + "execution_authorized": {"const": false}, + "memory_write_authorized": {"const": false}, + "wazuh_api_live_query_authorized": {"const": false}, + "runtime_execution_authorized": {"const": false}, + "ui_implementation_authorized": {"const": false} + }, + "additionalProperties": false +} diff --git a/scripts/dev/awoooi-status-cleanup-dashboard.py b/scripts/dev/awoooi-status-cleanup-dashboard.py new file mode 100644 index 00000000..78637830 --- /dev/null +++ b/scripts/dev/awoooi-status-cleanup-dashboard.py @@ -0,0 +1,608 @@ +#!/usr/bin/env python3 +"""產生 AWOOOI Status Cleanup dashboard。 + +本工具只整合 status cleanup 的 read-only snapshots,提供未來 AWOOOI +Workspace / Delivery 頁面單一資料來源: +- 不修改 project_current_status / memory。 +- 不同步 raw Codex App DB / auth / conversations / sessions。 +- 不 fetch / pull / push。 +- 不呼叫 Wazuh / Kali / SOC runtime。 +- 不修改 workflow、repo、host、backup、restore 或 migration。 +""" + +from __future__ import annotations + +import argparse +import json +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + + +TARGET_ROUTE = "/workspace/status-cleanup" + + +def utc_now_iso() -> str: + return datetime.now(timezone.utc).replace(microsecond=0).isoformat() + + +def load_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) + + +def workplan_completion(scorecard: dict[str, Any], item_id: str) -> int: + for item in scorecard.get("workplan_items", []): + if item.get("id") == item_id: + return int(item.get("completion_percent", 0)) + return 0 + + +def unique_strings(*groups: list[str]) -> list[str]: + seen: set[str] = set() + output: list[str] = [] + for group in groups: + for item in group: + text = str(item) + if text and text not in seen: + seen.add(text) + output.append(text) + return output + + +def status_from_boolean(ok: bool) -> str: + return "green" if ok else "blocked" + + +def bool_token(value: Any) -> str: + return "true" if value is True else "false" + + +def append_boundary_tokens(boundary: str, tokens: list[str]) -> str: + parts = [boundary.strip()] if boundary.strip() else [] + parts.extend(token for token in tokens if token and token not in boundary) + return " ".join(parts) + + +def section_value(owner_package: dict[str, Any], section_id: str) -> str: + for item in owner_package.get("required_update_sections", []): + if item.get("section_id") == section_id: + return str(item.get("value", "")) + return "" + + +def gate_cards( + preflight: dict[str, Any], + owner_package: dict[str, Any], + owner_response_preflight: dict[str, Any], + execution_plan: dict[str, Any], + apply_gate: dict[str, Any], +) -> list[dict[str, Any]]: + preflight_status = preflight["status"] + owner_summary = owner_package["summary"] + response_summary = owner_response_preflight["summary"] + plan_summary = execution_plan["summary"] + apply_summary = apply_gate["summary"] + + return [ + { + "gate_id": "status_cleanup_preflight", + "title": "狀態清理預檢", + "status": preflight_status, + "completion_label": ( + f"age_days={preflight['project_current_status']['age_days']} " + f"stale_after={preflight['project_current_status']['stale_after_days']}" + ), + "evidence_ref": "docs/operations/codex-status-cleanup-preflight.snapshot.json", + "blocked": preflight_status != "ready_status_current", + "memory_write_authorized": False, + "execution_authorized": False, + }, + { + "gate_id": "owner_review_package", + "title": "Owner 審核包", + "status": owner_summary["review_status"], + "completion_label": ( + f"{owner_summary['required_update_section_count']} sections / " + f"{owner_summary['required_owner_flag_count']} owner flags" + ), + "evidence_ref": "docs/operations/codex-status-cleanup-owner-review-package.snapshot.json", + "blocked": owner_summary["ready_for_status_cleanup_apply_gate"] is not True, + "memory_write_authorized": False, + "execution_authorized": False, + }, + { + "gate_id": "owner_response_preflight", + "title": "Owner 回覆預檢", + "status": response_summary["preflight_status"], + "completion_label": ( + f"{response_summary['accepted_owner_flag_count']}/" + f"{response_summary['required_owner_flag_count']} flags, " + f"{response_summary['approved_update_section_count']}/" + f"{response_summary['required_update_section_count']} sections" + ), + "evidence_ref": "docs/operations/codex-status-cleanup-owner-response-preflight.snapshot.json", + "blocked": response_summary["ready_for_status_cleanup_apply_gate"] is not True, + "memory_write_authorized": False, + "execution_authorized": False, + }, + { + "gate_id": "execution_plan", + "title": "Dry-run 執行計畫", + "status": plan_summary["execution_status"], + "completion_label": ( + f"{plan_summary['approved_update_section_count']}/" + f"{plan_summary['required_update_section_count']} sections, " + f"{plan_summary['approved_target_path_count']}/" + f"{plan_summary['target_path_count']} targets" + ), + "evidence_ref": "docs/operations/codex-status-cleanup-execution-plan.snapshot.json", + "blocked": plan_summary["execution_status"] != "blocked_final_confirmation_required", + "memory_write_authorized": False, + "execution_authorized": False, + }, + { + "gate_id": "apply_gate", + "title": "最終套用閘門", + "status": apply_summary["apply_gate_status"], + "completion_label": ( + f"{apply_summary['confirmed_command_count']}/" + f"{apply_summary['planned_command_count']} commands, " + f"{apply_summary['confirmed_target_path_count']}/" + f"{apply_summary['target_path_count']} targets" + ), + "evidence_ref": "docs/operations/codex-status-cleanup-apply-gate.snapshot.json", + "blocked": apply_summary["apply_allowed"] is not True, + "memory_write_authorized": False, + "execution_authorized": False, + }, + ] + + +def metric_cards( + preflight: dict[str, Any], + owner_response_preflight: dict[str, Any], + execution_plan: dict[str, Any], + apply_gate: dict[str, Any], + artifact_sync: dict[str, Any], + scorecard: dict[str, Any], +) -> list[dict[str, Any]]: + project_status = preflight["project_current_status"] + response_summary = owner_response_preflight["summary"] + plan_summary = execution_plan["summary"] + apply_summary = apply_gate["summary"] + artifact_summary = artifact_sync["summary"] + return [ + { + "metric_id": "overall_completion", + "title": "整體治理完成度", + "value": f"{scorecard['overall_completion_percent']}%", + "status": scorecard.get("status", "orange"), + "caption": "產品 Runtime 治理完成度計分卡", + }, + { + "metric_id": "project_status_age", + "title": "project_current_status 年齡", + "value": f"{project_status['age_days']} days", + "status": status_from_boolean(project_status["cleanup_required"] is False), + "caption": f"stale_after_days={project_status['stale_after_days']}", + }, + { + "metric_id": "owner_flags", + "title": "Owner 旗標", + "value": f"{response_summary['accepted_owner_flag_count']}/{response_summary['required_owner_flag_count']}", + "status": status_from_boolean(response_summary["accepted_owner_flag_count"] == response_summary["required_owner_flag_count"]), + "caption": response_summary["owner_response_status"], + }, + { + "metric_id": "approved_sections", + "title": "已批准章節", + "value": f"{response_summary['approved_update_section_count']}/{response_summary['required_update_section_count']}", + "status": status_from_boolean(response_summary["approved_update_section_count"] == response_summary["required_update_section_count"]), + "caption": f"targets={response_summary['approved_target_path_count']}/{response_summary['target_path_count']}", + }, + { + "metric_id": "execution_preview", + "title": "執行預覽", + "value": plan_summary["execution_status"], + "status": status_from_boolean(plan_summary["preflight_ready"]), + "caption": "dry_run_only=true", + }, + { + "metric_id": "apply_confirmation", + "title": "套用確認", + "value": f"{apply_summary['confirmed_command_count']}/{apply_summary['planned_command_count']}", + "status": status_from_boolean(apply_summary["apply_allowed"]), + "caption": apply_summary["final_confirmation_status"], + }, + { + "metric_id": "artifact_sync", + "title": "安全交接 artifacts", + "value": f"{artifact_summary['synced_target_count']}/{artifact_summary['target_count']}", + "status": status_from_boolean(artifact_summary["blocked_target_count"] == 0), + "caption": f"artifacts={artifact_summary['artifact_count']}", + }, + ] + + +def workflow_rows(cards: list[dict[str, Any]]) -> list[dict[str, Any]]: + next_map = { + "status_cleanup_preflight": "owner_review_package", + "owner_review_package": "owner_response_preflight", + "owner_response_preflight": "execution_plan", + "execution_plan": "apply_gate", + "apply_gate": "manual_project_current_status_update_after_explicit_final_confirmation", + } + return [ + { + "step_id": card["gate_id"], + "title": card["title"], + "status": card["status"], + "evidence_ref": card["evidence_ref"], + "next_step": next_map[card["gate_id"]], + "blocked": card["blocked"], + "memory_write_authorized": False, + "execution_authorized": False, + } + for card in cards + ] + + +def risk_controls() -> list[dict[str, Any]]: + rows = [ + ("memory_write", "更新 project_current_status / memory", "blocked_final_apply_gate_required"), + ("raw_codex_history", "同步 raw Codex App history", "blocked_raw_history_never_synced"), + ("wazuh_runtime", "部署或查詢 live Wazuh runtime", "blocked_iwooos_runtime_lane_only"), + ("workflow", "修改或觸發 CI/CD workflow", "blocked_workflow_authorization_required"), + ("repo_refs", "建立 repo / branch / mirror refs", "blocked_source_control_approval_required"), + ("host", "SSH / restart / firewall / Nginx / Docker / K8s", "blocked_host_maintenance_required"), + ("backup_restore", "執行 backup / restore / migration", "blocked_data_owner_approval_required"), + ("ui", "實作可見 AWOOOI UI", "blocked_apps_web_readiness_required"), + ] + return [ + { + "control_id": control_id, + "title": title, + "status": status, + "authorized": False, + } + for control_id, title, status in rows + ] + + +def next_actions(owner_response_preflight: dict[str, Any], apply_gate: dict[str, Any]) -> list[str]: + response_summary = owner_response_preflight["summary"] + apply_summary = apply_gate["summary"] + return [ + f"收齊 owner 旗標:{response_summary['accepted_owner_flag_count']}/{response_summary['required_owner_flag_count']}。", + f"收齊已批准章節:{response_summary['approved_update_section_count']}/{response_summary['required_update_section_count']}。", + f"確認目標路徑:{response_summary['approved_target_path_count']}/{response_summary['target_path_count']}。", + f"確認邊界 acknowledgement:{response_summary['boundary_ack_count']}/{response_summary['required_boundary_ack_count']}。", + "owner response preflight ready 後,重新產生 dry-run execution plan。", + f"apply gate 目前 apply_allowed={apply_summary['apply_allowed']};未變 true 前不得寫 project_current_status。", + ] + + +def hard_gates() -> list[str]: + return [ + "本 dashboard 只整合 read-only snapshots,不代表 project_current_status / memory 已更新。", + "memory_write_authorized=false 時不得修改 project_current_status 或任何 Memory 檔案。", + "不得同步 raw Codex App DB / auth / conversations / sessions。", + "不得把一般批准繼續視為 owner response 或 final apply confirmation。", + "不得新增 Wazuh / Kali / SOC runtime UI/API、live query、active response 或 host containment。", + "不得修改 Nginx / Docker / K8s / firewall / Wazuh secret,也不得重啟 host。", + "不得修改 .gitea/workflows、workflow_dispatch、repo refs、GitHub mirror 或 Gitea environment。", + "不得執行 backup、restore、migration、kubectl、docker compose up 或資料庫寫入。", + "apps/web build/i18n 必要檔案未恢復前,本 dashboard 只可作 page model 資料,不代表 UI 實作授權。", + ] + + +def build_payload( + preflight_path: Path, + owner_package_path: Path, + owner_response_preflight_path: Path, + execution_plan_path: Path, + apply_gate_path: Path, + artifact_sync_path: Path, + scorecard_path: Path, + wazuh_handoff_path: Path, +) -> dict[str, Any]: + preflight = load_json(preflight_path) + owner_package = load_json(owner_package_path) + owner_response_preflight = load_json(owner_response_preflight_path) + execution_plan = load_json(execution_plan_path) + apply_gate = load_json(apply_gate_path) + artifact_sync = load_json(artifact_sync_path) + scorecard = load_json(scorecard_path) + wazuh_handoff = load_json(wazuh_handoff_path) + + cards = gate_cards( + preflight, + owner_package, + owner_response_preflight, + execution_plan, + apply_gate, + ) + response_summary = owner_response_preflight["summary"] + plan_summary = execution_plan["summary"] + apply_summary = apply_gate["summary"] + project_status = preflight["project_current_status"] + artifact_summary = artifact_sync["summary"] + wazuh_release_lane = wazuh_handoff["release_lane"] + wazuh_incident = wazuh_handoff.get("incident_sync", {}) + wazuh_visibility = wazuh_handoff.get("agent_visibility_runtime_gate", {}) + wazuh_boundary = append_boundary_tokens( + section_value(owner_package, "iwooos_wazuh_boundary"), + [ + "agent_visibility_status=" + + str(wazuh_visibility.get("status", "blocked_waiting_manager_agent_registry_readback")), + "manager_agent_registry_readback_passed=" + + bool_token(wazuh_visibility.get("manager_agent_registry_readback_passed", False)), + "iwooos_live_route_readback_passed=" + + bool_token(wazuh_visibility.get("iwooos_live_route_readback_passed", False)), + "dashboard_agent_list_recovered=" + + bool_token(wazuh_visibility.get("dashboard_agent_list_recovered", False)), + "agent_visibility_runtime_gate_count=" + + str(wazuh_visibility.get("runtime_gate_count", 0)), + ], + ) + blockers = unique_strings( + preflight.get("hard_gates", []), + [f"owner_response_preflight:{item}" for item in owner_response_preflight.get("blocking_reasons", [])], + [f"execution_plan_blocked_until:{item}" for item in execution_plan.get("blocked_until", [])], + [f"apply_gate:{item}" for item in apply_gate.get("blocking_reasons", [])], + [f"wazuh_boundary:{wazuh_boundary}"] if wazuh_boundary else [], + ) + actions = next_actions(owner_response_preflight, apply_gate) + metrics = metric_cards( + preflight, + owner_response_preflight, + execution_plan, + apply_gate, + artifact_sync, + scorecard, + ) + + return { + "schema_version": "awoooi_status_cleanup_dashboard_v1", + "generated_at": utc_now_iso(), + "target_route": TARGET_ROUTE, + "source_reviews": { + "status_cleanup_preflight": str(preflight_path), + "owner_review_package": str(owner_package_path), + "owner_response_preflight": str(owner_response_preflight_path), + "execution_plan": str(execution_plan_path), + "apply_gate": str(apply_gate_path), + "artifact_sync_readback": str(artifact_sync_path), + "scorecard": str(scorecard_path), + "iwooos_wazuh_release_handoff": str(wazuh_handoff_path), + }, + "summary": { + "dashboard_status": "blocked_status_cleanup_apply_not_authorized", + "overall_completion_percent": scorecard["overall_completion_percent"], + "p0_009_completion_percent": workplan_completion(scorecard, "P0-009"), + "p0_010_completion_percent": workplan_completion(scorecard, "P0-010"), + "p1_006_completion_percent": workplan_completion(scorecard, "P1-006"), + "p2_001_completion_percent": workplan_completion(scorecard, "P2-001"), + "p2_002_completion_percent": workplan_completion(scorecard, "P2-002"), + "gate_count": len(cards), + "blocked_gate_count": sum(1 for card in cards if card["blocked"]), + "project_current_status_age_days": project_status["age_days"], + "cleanup_required": project_status["cleanup_required"], + "accepted_owner_flag_count": response_summary["accepted_owner_flag_count"], + "required_owner_flag_count": response_summary["required_owner_flag_count"], + "approved_update_section_count": response_summary["approved_update_section_count"], + "required_update_section_count": response_summary["required_update_section_count"], + "approved_target_path_count": response_summary["approved_target_path_count"], + "target_path_count": response_summary["target_path_count"], + "boundary_ack_count": response_summary["boundary_ack_count"], + "required_boundary_ack_count": response_summary["required_boundary_ack_count"], + "execution_plan_status": plan_summary["execution_status"], + "apply_gate_status": apply_summary["apply_gate_status"], + "confirmed_command_count": apply_summary["confirmed_command_count"], + "planned_command_count": apply_summary["planned_command_count"], + "artifact_count": artifact_summary["artifact_count"], + "artifact_sync_blocked_count": artifact_summary["blocked_target_count"], + "wazuh_handoff_status": wazuh_handoff["status"], + "wazuh_handoff_base_commit": wazuh_handoff["base"]["commit"], + "wazuh_handoff_commit_count": len(wazuh_handoff["commits"]), + "wazuh_handoff_patch_count": len(wazuh_handoff["patch_set"]["patches"]), + "wazuh_release_owner_request_sent_count": wazuh_release_lane["release_owner_request_sent_count"], + "wazuh_release_owner_response_accepted_count": wazuh_release_lane["release_owner_response_accepted_count"], + "wazuh_acknowledged_release_owner_ack_count": wazuh_release_lane["acknowledged_release_owner_ack_count"], + "wazuh_required_release_owner_ack_count": wazuh_release_lane["required_release_owner_ack_count"], + "wazuh_accepted_evidence_count": wazuh_release_lane["accepted_evidence_count"], + "wazuh_required_evidence_count": wazuh_release_lane["required_evidence_count"], + "wazuh_live_metadata_owner_count": wazuh_release_lane["live_metadata_owner_count"], + "wazuh_secret_metadata_count": wazuh_release_lane["secret_metadata_count"], + "wazuh_live_agent_registry_readback": wazuh_incident.get("wazuh_live_agent_registry_readback", 0), + "iwooos_wazuh_runtime_gate": wazuh_incident.get("iwooos_wazuh_runtime_gate", 0), + "wazuh_active_response_count": wazuh_incident.get("active_response", 0), + "wazuh_agent_visibility_status": wazuh_visibility.get("status", "blocked_waiting_manager_agent_registry_readback"), + "wazuh_manager_agent_registry_readback_passed": wazuh_visibility.get("manager_agent_registry_readback_passed", False), + "wazuh_iwooos_live_route_readback_passed": wazuh_visibility.get("iwooos_live_route_readback_passed", False), + "wazuh_dashboard_agent_list_recovered": wazuh_visibility.get("dashboard_agent_list_recovered", False), + "wazuh_agent_visibility_runtime_gate_count": wazuh_visibility.get("runtime_gate_count", 0), + "wazuh_push_gate_count": wazuh_release_lane["push_gate_count"], + "wazuh_deploy_gate_count": wazuh_release_lane["deploy_gate_count"], + "wazuh_readback_gate_count": wazuh_release_lane["readback_gate_count"], + "wazuh_runtime_gate_count": wazuh_release_lane["runtime_gate_count"], + "blocking_reason_count": len(blockers), + "next_action_count": len(actions), + "apply_allowed": False, + "memory_write_authorized": False, + "wazuh_api_live_query_authorized": False, + "runtime_execution_authorized": False, + "ui_implementation_allowed": False, + }, + "metric_cards": metrics, + "gate_cards": cards, + "workflow_rows": workflow_rows(cards), + "risk_controls": risk_controls(), + "blocking_reasons": blockers, + "next_actions": actions, + "wazuh_handoff": { + "status": wazuh_handoff["status"], + "source_ref": str(wazuh_handoff_path), + "branch": wazuh_handoff["branch"], + "base_commit": wazuh_handoff["base"]["commit"], + "commit_count": len(wazuh_handoff["commits"]), + "patch_count": len(wazuh_handoff["patch_set"]["patches"]), + "release_owner_request_sent_count": wazuh_release_lane["release_owner_request_sent_count"], + "release_owner_response_accepted_count": wazuh_release_lane["release_owner_response_accepted_count"], + "acknowledged_release_owner_ack_count": wazuh_release_lane["acknowledged_release_owner_ack_count"], + "required_release_owner_ack_count": wazuh_release_lane["required_release_owner_ack_count"], + "accepted_evidence_count": wazuh_release_lane["accepted_evidence_count"], + "required_evidence_count": wazuh_release_lane["required_evidence_count"], + "live_metadata_owner_count": wazuh_release_lane["live_metadata_owner_count"], + "secret_metadata_count": wazuh_release_lane["secret_metadata_count"], + "wazuh_live_agent_registry_readback": wazuh_incident.get("wazuh_live_agent_registry_readback", 0), + "iwooos_wazuh_runtime_gate": wazuh_incident.get("iwooos_wazuh_runtime_gate", 0), + "active_response": wazuh_incident.get("active_response", 0), + "stored_api_secret_metadata_changed": wazuh_incident.get("stored_api_secret_metadata_changed", False), + "agent_visibility_status": wazuh_visibility.get("status", "blocked_waiting_manager_agent_registry_readback"), + "manager_agent_registry_readback_passed": wazuh_visibility.get("manager_agent_registry_readback_passed", False), + "iwooos_live_route_readback_passed": wazuh_visibility.get("iwooos_live_route_readback_passed", False), + "dashboard_agent_list_recovered": wazuh_visibility.get("dashboard_agent_list_recovered", False), + "agent_visibility_runtime_gate_count": wazuh_visibility.get("runtime_gate_count", 0), + "push_gate_count": wazuh_release_lane["push_gate_count"], + "deploy_gate_count": wazuh_release_lane["deploy_gate_count"], + "readback_gate_count": wazuh_release_lane["readback_gate_count"], + "runtime_gate_count": wazuh_release_lane["runtime_gate_count"], + "production_readback_status": wazuh_handoff["production_readback"]["status"], + "boundary": wazuh_boundary, + "runtime_execution_authorized": False, + "wazuh_api_live_query_authorized": False, + }, + "hard_gates": hard_gates(), + "operation_boundaries": preflight["operation_boundaries"], + "secret_values_collected": False, + "remote_write_performed": False, + "local_product_write_performed": False, + "execution_authorized": False, + "memory_write_authorized": False, + "wazuh_api_live_query_authorized": False, + "runtime_execution_authorized": False, + "ui_implementation_authorized": False, + } + + +def write_markdown(payload: dict[str, Any], path: Path) -> None: + summary = payload["summary"] + lines = [ + "# AWOOOI 狀態清理儀表板", + "", + f"- generated_at: `{payload['generated_at']}`", + f"- dashboard_status: `{summary['dashboard_status']}`", + f"- overall_completion_percent: `{summary['overall_completion_percent']}%`", + f"- gates: `{summary['blocked_gate_count']}/{summary['gate_count']} blocked`", + f"- owner_flags: `{summary['accepted_owner_flag_count']}/{summary['required_owner_flag_count']}`", + f"- sections: `{summary['approved_update_section_count']}/{summary['required_update_section_count']}`", + f"- confirmed_commands: `{summary['confirmed_command_count']}/{summary['planned_command_count']}`", + f"- artifact_count: `{summary['artifact_count']}`", + f"- wazuh_handoff: `{summary['wazuh_handoff_status']} base={summary['wazuh_handoff_base_commit']} patches={summary['wazuh_handoff_patch_count']}`", + f"- wazuh_live_metadata: `owner={summary['wazuh_live_metadata_owner_count']} secret_metadata={summary['wazuh_secret_metadata_count']}`", + f"- wazuh_live_agent_registry_readback: `{summary['wazuh_live_agent_registry_readback']}`", + f"- iwooos_wazuh_runtime_gate: `{summary['iwooos_wazuh_runtime_gate']}`", + f"- wazuh_active_response_count: `{summary['wazuh_active_response_count']}`", + f"- wazuh_agent_visibility_status: `{summary['wazuh_agent_visibility_status']}`", + f"- memory_write_authorized: `{payload['memory_write_authorized']}`", + f"- runtime_execution_authorized: `{payload['runtime_execution_authorized']}`", + f"- wazuh_api_live_query_authorized: `{payload['wazuh_api_live_query_authorized']}`", + "", + "## 指標卡片", + "", + "| 指標 | 數值 | 狀態 | 說明 |", + "|--------|-------|--------|---------|", + ] + for item in payload["metric_cards"]: + lines.append( + f"| `{item['metric_id']}` | `{item['value']}` | `{item['status']}` | {item['caption']} |" + ) + + lines.extend(["", "## 閘門卡片", "", "| 閘門 | 狀態 | 是否封鎖 | 證據 |", "|------|------|----------|------|"]) + for item in payload["gate_cards"]: + lines.append( + f"| `{item['gate_id']}` | `{item['status']}` | `{item['blocked']}` | `{item['evidence_ref']}` |" + ) + + lines.extend(["", "## 風險控制", "", "| 控制項 | 狀態 | 已授權 |", "|--------|------|--------|"]) + for item in payload["risk_controls"]: + lines.append(f"| `{item['control_id']}` | `{item['status']}` | `{item['authorized']}` |") + + lines.extend(["", "## 下一步", ""]) + lines.extend(f"- {item}" for item in payload["next_actions"]) + lines.extend(["", "## 封鎖原因", ""]) + lines.extend(f"- `{item}`" for item in payload["blocking_reasons"]) + lines.extend(["", "## 強制閘門", ""]) + lines.extend(f"- {item}" for item in payload["hard_gates"]) + lines.append("") + path.write_text("\n".join(lines), encoding="utf-8") + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument( + "--preflight", + default="docs/operations/codex-status-cleanup-preflight.snapshot.json", + ) + parser.add_argument( + "--owner-package", + default="docs/operations/codex-status-cleanup-owner-review-package.snapshot.json", + ) + parser.add_argument( + "--owner-response-preflight", + default="docs/operations/codex-status-cleanup-owner-response-preflight.snapshot.json", + ) + parser.add_argument( + "--execution-plan", + default="docs/operations/codex-status-cleanup-execution-plan.snapshot.json", + ) + parser.add_argument( + "--apply-gate", + default="docs/operations/codex-status-cleanup-apply-gate.snapshot.json", + ) + parser.add_argument( + "--artifact-sync", + default="docs/operations/codex-workstation-artifact-sync-readback.snapshot.json", + ) + parser.add_argument( + "--scorecard", + default="docs/operations/product-runtime-governance-completion-scorecard.snapshot.json", + ) + parser.add_argument( + "--wazuh-handoff", + default="docs/operations/iwooos-wazuh-release-handoff.snapshot.json", + ) + parser.add_argument("--output-json", required=True) + parser.add_argument("--output-md", required=True) + args = parser.parse_args() + + payload = build_payload( + Path(args.preflight), + Path(args.owner_package), + Path(args.owner_response_preflight), + Path(args.execution_plan), + Path(args.apply_gate), + Path(args.artifact_sync), + Path(args.scorecard), + Path(args.wazuh_handoff), + ) + Path(args.output_json).write_text( + json.dumps(payload, ensure_ascii=False, indent=2) + "\n", + encoding="utf-8", + ) + write_markdown(payload, Path(args.output_md)) + summary = payload["summary"] + print( + "AWOOOI_STATUS_CLEANUP_DASHBOARD_OK" + + f" status={summary['dashboard_status']}" + + f" gates={summary['blocked_gate_count']}/{summary['gate_count']}" + + f" owner_flags={summary['accepted_owner_flag_count']}/{summary['required_owner_flag_count']}" + + f" apply_allowed={summary['apply_allowed']}" + + f" memory_write={payload['memory_write_authorized']}" + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())