Files
awoooi/scripts/dev/awoooi-status-cleanup-dashboard.py
Your Name 9c5acc0360
Some checks failed
Code Review / ai-code-review (push) Successful in 13s
CD Pipeline / tests (push) Successful in 1m51s
CD Pipeline / build-and-deploy (push) Successful in 4m24s
CD Pipeline / post-deploy-checks (push) Failing after 1m36s
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled
fix(governance): 避免狀態清理儀表板曝光本機路徑
2026-06-24 23:49:26 +08:00

626 lines
30 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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"
PRIVATE_PROJECT_STATUS_PATH = (
"/Users/ogt/.claude/projects/-Users-ogt-awoooi/memory/project_current_status.md"
)
PUBLIC_PROJECT_STATUS_REF = "awoooi_memory/project_current_status.md"
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 public_blocker_text(value: str) -> str:
return value.replace(PRIVATE_PROJECT_STATUS_PATH, PUBLIC_PROJECT_STATUS_REF)
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 = [
public_blocker_text(item)
for item in 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())