Some checks failed
Code Review / ai-code-review (push) Successful in 13s
CD Pipeline / tests (push) Successful in 1m45s
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
504 lines
20 KiB
Python
504 lines
20 KiB
Python
#!/usr/bin/env python3
|
||
"""IwoooS 資安資產控制總帳產生器。
|
||
|
||
本工具只彙整 repo 內已提交的資安清冊、snapshot 與規範,將主機、
|
||
公開入口、版本來源、workflow、Wazuh、Kali、備份、監控、AI provider、
|
||
產品 runtime 與自動化資產沉澱狀態收斂成 P0-A 可驗證總帳。
|
||
|
||
它不連線主機、不讀 live log、不呼叫 Wazuh / Kali / Gitea / GitHub、
|
||
不做 active scan、不讀 secret、不修改 Nginx / firewall / K8s / workflow。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import json
|
||
import subprocess
|
||
import sys
|
||
from datetime import datetime, timedelta, timezone
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
|
||
TAIPEI = timezone(timedelta(hours=8))
|
||
|
||
ASSET_GROUPS = [
|
||
{
|
||
"group_id": "public_gateway_nginx",
|
||
"label": "Nginx / Public Gateway / Route",
|
||
"priority": "P0",
|
||
"tier": "C0",
|
||
"scope": "公開入口、API、WebSocket、ACME、admin route、Ollama proxy",
|
||
"evidence_refs": [
|
||
"docs/security/PUBLIC-GATEWAY-PREFLIGHT-INVENTORY.md",
|
||
"docs/security/public-gateway-preflight-inventory.snapshot.json",
|
||
"docs/security/PUBLIC-GATEWAY-POST-INCIDENT-READBACK-PLAN.md",
|
||
"docs/security/public-gateway-post-incident-readback-plan.snapshot.json",
|
||
],
|
||
"next_owner_action": "補 owner-provided live conf、rendered diff、nginx -t、route smoke、rollback owner。",
|
||
},
|
||
{
|
||
"group_id": "dns_tls_certbot",
|
||
"label": "DNS / TLS / Certbot",
|
||
"priority": "P0",
|
||
"tier": "C0",
|
||
"scope": "domain、certificate path、ACME、renewal owner、TLS route",
|
||
"evidence_refs": [
|
||
"docs/security/DOMAIN-TLS-CERTBOT-INVENTORY.md",
|
||
"docs/security/domain-tls-certbot-inventory.snapshot.json",
|
||
"docs/security/DOMAIN-TLS-CERTBOT-OWNER-RESPONSE-ACCEPTANCE.md",
|
||
"docs/security/domain-tls-certbot-owner-response-acceptance.snapshot.json",
|
||
],
|
||
"next_owner_action": "補 SAN / wildcard 覆蓋依據、expiry metadata、renewal owner、ACME route owner。",
|
||
},
|
||
{
|
||
"group_id": "host_service_runtime",
|
||
"label": "Docker / systemd / Host Service",
|
||
"priority": "P0",
|
||
"tier": "C0",
|
||
"scope": "Docker Compose、systemd、repair-bot、port binding、process / persistence baseline",
|
||
"evidence_refs": [
|
||
"docs/security/HOST-SERVICE-CONFIG-INVENTORY.md",
|
||
"docs/security/host-service-config-inventory.snapshot.json",
|
||
"docs/security/HOST-SERVICE-POST-INCIDENT-READBACK-PLAN.md",
|
||
"docs/security/host-service-post-incident-readback-plan.snapshot.json",
|
||
],
|
||
"next_owner_action": "補 live hash metadata、incident readback、restart window、rollback owner、post-check。",
|
||
},
|
||
{
|
||
"group_id": "ssh_firewall_network",
|
||
"label": "SSH / Firewall / WireGuard / NodePort",
|
||
"priority": "P0",
|
||
"tier": "C0",
|
||
"scope": "SSH target、known_hosts、sudoers、firewall、WireGuard、NetworkPolicy、NodePort",
|
||
"evidence_refs": [
|
||
"docs/security/SSH-NETWORK-ACCESS-INVENTORY.md",
|
||
"docs/security/ssh-network-access-inventory.snapshot.json",
|
||
"docs/security/SSH-NETWORK-POST-INCIDENT-READBACK-PLAN.md",
|
||
"docs/security/ssh-network-post-incident-readback-plan.snapshot.json",
|
||
],
|
||
"next_owner_action": "補 actor、before / after state、impact、operator notification、restoration evidence。",
|
||
},
|
||
{
|
||
"group_id": "k8s_argocd_gitops",
|
||
"label": "K8s / ArgoCD / GitOps",
|
||
"priority": "P0",
|
||
"tier": "C0",
|
||
"scope": "K8s manifests、ArgoCD app、RBAC、NetworkPolicy、CronJob、Velero",
|
||
"evidence_refs": [
|
||
"docs/security/K8S-ARGOCD-MANIFEST-INVENTORY.md",
|
||
"docs/security/k8s-argocd-manifest-inventory.snapshot.json",
|
||
"docs/security/K8S-ARGOCD-POST-INCIDENT-READBACK-PLAN.md",
|
||
"docs/security/k8s-argocd-post-incident-readback-plan.snapshot.json",
|
||
],
|
||
"next_owner_action": "補 ArgoCD revision、health / sync、rendered manifest diff、rollback revision、postcheck owner。",
|
||
},
|
||
{
|
||
"group_id": "workflow_runner_secret",
|
||
"label": "Gitea Workflow / Runner / Secret Metadata",
|
||
"priority": "P0",
|
||
"tier": "C0",
|
||
"scope": "workflow、runner label、deploy key、webhook、secret name parity、redaction guard",
|
||
"evidence_refs": [
|
||
"docs/security/SOURCE-CONTROL-WORKFLOW-SECRET-NAME-INVENTORY.md",
|
||
"docs/security/source-control-workflow-secret-name-inventory.snapshot.json",
|
||
"docs/security/CD-RUNNER-SECRET-INJECTION-POST-INCIDENT-READBACK-PLAN.md",
|
||
"docs/security/cd-runner-secret-injection-post-incident-readback-plan.snapshot.json",
|
||
],
|
||
"next_owner_action": "補 runner attestation、workspace cleanup、secret injection route、log redaction、Gitea run readback。",
|
||
},
|
||
{
|
||
"group_id": "source_control_repo_visibility",
|
||
"label": "Gitea / GitHub / Source Control",
|
||
"priority": "P0",
|
||
"tier": "C0",
|
||
"scope": "repo visibility、canonical refs、GitHub primary readiness、branch / tag / workflow boundary",
|
||
"evidence_refs": [
|
||
"docs/security/GITEA-GITHUB-MIGRATION-INVENTORY.md",
|
||
"docs/security/SOURCE-CONTROL-PRIMARY-READINESS-GATE.md",
|
||
"docs/security/GITHUB-TARGET-OWNER-DECISION-RESPONSE.md",
|
||
"docs/security/source-control-primary-readiness-gate.snapshot.json",
|
||
],
|
||
"next_owner_action": "補 owner response;不得建立 repo、改 visibility、sync refs 或切 primary。",
|
||
},
|
||
{
|
||
"group_id": "wazuh_endpoint_siem",
|
||
"label": "Wazuh / Endpoint / SIEM",
|
||
"priority": "P0",
|
||
"tier": "C0",
|
||
"scope": "Wazuh manager、agent、FIM、rule / decoder、event ref、active response dry-run boundary",
|
||
"evidence_refs": [
|
||
"docs/security/WAZUH-IWOOOS-INTRUSION-READBACK-PLAN.md",
|
||
"docs/security/wazuh-iwooos-intrusion-readback-plan.snapshot.json",
|
||
"docs/security/SOC-SIEM-KALI-WAZUH-INTEGRATION-CONTROL.md",
|
||
"docs/security/soc-siem-kali-wazuh-integration-control.snapshot.json",
|
||
],
|
||
"next_owner_action": "補 Wazuh health refs、agent refs、event refs、rule / decoder readback 與 no-raw-payload attestation。",
|
||
},
|
||
{
|
||
"group_id": "kali_112_assessment",
|
||
"label": "Kali 112 / Assessment Tooling",
|
||
"priority": "P0",
|
||
"tier": "C0",
|
||
"scope": "Kali health、tool version、scope、finding envelope、maintenance window",
|
||
"evidence_refs": [
|
||
"docs/security/KALI-INTEGRATION-STATUS.md",
|
||
"docs/security/kali-integration-status.snapshot.json",
|
||
"docs/security/KALI-112-MAINTENANCE-WINDOW-DRAFT.md",
|
||
"docs/security/kali-112-maintenance-window-draft.snapshot.json",
|
||
],
|
||
"next_owner_action": "補 scope ref、health ref、normalized finding envelope;active scan 與 /execute 仍獨立批准。",
|
||
},
|
||
{
|
||
"group_id": "monitoring_alerting_observability",
|
||
"label": "Monitoring / Alerting / Observability",
|
||
"priority": "P0",
|
||
"tier": "C0",
|
||
"scope": "Prometheus、Alertmanager、Telegram route、SigNoz、Sentry、Langfuse、no-false-green",
|
||
"evidence_refs": [
|
||
"docs/security/MONITORING-ALERTING-OBSERVABILITY-INVENTORY.md",
|
||
"docs/security/monitoring-alerting-observability-inventory.snapshot.json",
|
||
"docs/security/MONITORING-POST-INCIDENT-READBACK-PLAN.md",
|
||
"docs/security/monitoring-post-incident-readback-plan.snapshot.json",
|
||
],
|
||
"next_owner_action": "補 route owner、receiver diff、receipt evidence、noise budget、reload owner 與 no-false-green。",
|
||
},
|
||
{
|
||
"group_id": "backup_restore_dr",
|
||
"label": "Backup / Restore / DR / Escrow",
|
||
"priority": "P0",
|
||
"tier": "C0",
|
||
"scope": "backup scripts、restic、offsite、credential escrow、Velero、restore drill、retention",
|
||
"evidence_refs": [
|
||
"docs/security/BACKUP-RESTORE-ESCROW-INVENTORY.md",
|
||
"docs/security/backup-restore-escrow-inventory.snapshot.json",
|
||
"docs/security/BACKUP-RESTORE-POST-INCIDENT-READBACK-PLAN.md",
|
||
"docs/security/backup-restore-post-incident-readback-plan.snapshot.json",
|
||
],
|
||
"next_owner_action": "補 restore drill、offsite sync ref、escrow non-secret proof、retention runway、DR scorecard。",
|
||
},
|
||
{
|
||
"group_id": "container_registry_supply_chain",
|
||
"label": "Harbor / Registry / SBOM / Supply Chain",
|
||
"priority": "P0",
|
||
"tier": "C0",
|
||
"scope": "Harbor、registry、image tag、SBOM、Cosign、SLSA、dependency drift、CVE / KEV",
|
||
"evidence_refs": [
|
||
"docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md",
|
||
"docs/security/SECURITY-SUPPLY-CHAIN-CONTRACT-MANIFEST.md",
|
||
"docs/security/security-supply-chain-contract-manifest.snapshot.json",
|
||
"docs/evaluations/dependency_supply_chain_drift_monitor_2026-06-18.json",
|
||
],
|
||
"next_owner_action": "補 SBOM / VEX / provenance intake、image signing、KEV / EPSS / exposure SLA。",
|
||
},
|
||
{
|
||
"group_id": "public_admin_api_runtime",
|
||
"label": "Public / Admin / API / Frontend Runtime",
|
||
"priority": "P0",
|
||
"tier": "C0",
|
||
"scope": "public URL、CORS、auth boundary、middleware、webhook、frontend env、i18n redaction",
|
||
"evidence_refs": [
|
||
"docs/security/PUBLIC-RUNTIME-CONFIG-CHANGE-EVIDENCE-ACCEPTANCE.md",
|
||
"docs/security/public-runtime-config-change-evidence-acceptance.snapshot.json",
|
||
"docs/security/public-frontend-sensitive-surface-guard.snapshot.json",
|
||
"docs/HARD_RULES.md",
|
||
],
|
||
"next_owner_action": "補 route owner、API contract readback、CORS diff、desktop / mobile smoke、bundle sensitive scan。",
|
||
},
|
||
{
|
||
"group_id": "ai_provider_agent_runtime",
|
||
"label": "AI Provider / Model Router / Agent Runtime",
|
||
"priority": "P0",
|
||
"tier": "C0",
|
||
"scope": "OpenClaw、Ollama、NemoTron、Hermes、Gemini、MCP / A2A、tool allowlist、cost / privacy",
|
||
"evidence_refs": [
|
||
"docs/security/AI-PROVIDER-OWNER-RESPONSE-ACCEPTANCE.md",
|
||
"docs/security/ai-provider-owner-response-acceptance.snapshot.json",
|
||
"docs/ai",
|
||
"docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md",
|
||
],
|
||
"next_owner_action": "補 dry-run、benchmark、cost review、privacy review、fallback order 與 rollback owner。",
|
||
},
|
||
{
|
||
"group_id": "product_surface_runtime_routes",
|
||
"label": "Product Surface / Runtime Route",
|
||
"priority": "P1",
|
||
"tier": "C1",
|
||
"scope": "AWOOOI、AwoooP、IwoooS、VibeWork、agent-bounty-protocol、StockPlatform、Tsenyang、Bitan",
|
||
"evidence_refs": [
|
||
"docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md",
|
||
"docs/security/VIBEWORK-IWOOOS-ONBOARDING-HANDOFF.md",
|
||
"docs/security/AGENT-BOUNTY-IWOOOS-ONBOARDING-HANDOFF.md",
|
||
"docs/security/agent-bounty-iwooos-onboarding-handoff.snapshot.json",
|
||
],
|
||
"next_owner_action": "補逐產品 owner、route、admin、API、backup、webhook、rollback 與 validation 指標。",
|
||
},
|
||
{
|
||
"group_id": "automation_asset_sedimentation",
|
||
"label": "KM / PlayBook / Script / Schedule / Verifier",
|
||
"priority": "P1",
|
||
"tier": "C1",
|
||
"scope": "incident、approval、repair candidate、manual handoff 的自動化資產沉澱",
|
||
"evidence_refs": [
|
||
"docs/LOGBOOK.md",
|
||
"apps/api/src/services/repair_candidate_service.py",
|
||
"apps/api/src/services/telegram_gateway.py",
|
||
"apps/web/src/app/[locale]/awooop/work-items/page.tsx",
|
||
],
|
||
"next_owner_action": "補 incident / approval / manual handoff writeback contract,讓資產總帳從可視化進入可追蹤閉環。",
|
||
},
|
||
]
|
||
|
||
REQUIRED_OWNER_FIELDS = [
|
||
"asset_group_id",
|
||
"asset_alias",
|
||
"owner_role",
|
||
"owner_team",
|
||
"business_impact",
|
||
"technical_scope",
|
||
"affected_routes_or_services",
|
||
"data_classification",
|
||
"redacted_evidence_refs",
|
||
"source_of_truth_ref",
|
||
"live_state_ref",
|
||
"config_diff_ref",
|
||
"monitoring_signal_ref",
|
||
"wazuh_or_siem_ref",
|
||
"kali_scope_ref",
|
||
"backup_restore_ref",
|
||
"supply_chain_ref",
|
||
"secret_absence_attestation",
|
||
"raw_payload_absence_attestation",
|
||
"maintenance_window",
|
||
"rollback_owner",
|
||
"postcheck_owner",
|
||
"followup_owner",
|
||
"decision_reason",
|
||
]
|
||
|
||
REVIEWER_CHECKS = [
|
||
"asset alias 不得暴露個人 namespace",
|
||
"owner role / team 必填",
|
||
"source-of-truth 必須可追溯",
|
||
"live evidence 只能收脫敏 ref",
|
||
"secret value / hash / partial token 一律拒收",
|
||
"raw Wazuh / raw Kali output 一律拒收",
|
||
"Nginx / firewall / K8s / workflow 變更需獨立 runtime approval",
|
||
"route 200 不得當資安完成",
|
||
"dashboard 可見不得當 remediation 完成",
|
||
"backup 存在不得當 restore drill 完成",
|
||
"Kali 納入範圍不得當 active scan 授權",
|
||
"Wazuh event 可見不得當 active response 授權",
|
||
"repo snapshot 不得當 live host truth",
|
||
"事故後回讀需含 actor、before / after、impact、postcheck",
|
||
"no-false-green 必須明確標示",
|
||
"owner response received / accepted 必須維持 0,除非有實際收件",
|
||
"runtime gate 必須維持 0",
|
||
"action button 必須維持 0",
|
||
"Gitea / GitHub refs 不得自動同步",
|
||
"GitHub primary 不得自動切換",
|
||
"Telegram 不得實發",
|
||
"SOAR 不得 auto block",
|
||
"AI agent 不得讀 secret 或 host write",
|
||
"LOGBOOK 不得包含內部對話逐字稿",
|
||
]
|
||
|
||
OUTCOME_LANES = [
|
||
"waiting_asset_owner_packet",
|
||
"request_gateway_supplement",
|
||
"request_host_service_supplement",
|
||
"request_network_supplement",
|
||
"request_wazuh_kali_supplement",
|
||
"request_backup_restore_supplement",
|
||
"request_supply_chain_supplement",
|
||
"request_product_surface_supplement",
|
||
"quarantine_secret_or_raw_payload",
|
||
"ready_for_security_reviewer_review",
|
||
]
|
||
|
||
BLOCKED_ACTIONS = [
|
||
"ssh_to_host",
|
||
"read_live_host_log",
|
||
"write_host_file",
|
||
"nginx_reload",
|
||
"certbot_renew",
|
||
"dns_change",
|
||
"firewall_change",
|
||
"wireguard_change",
|
||
"nodeport_change",
|
||
"networkpolicy_apply",
|
||
"kubectl_action",
|
||
"argocd_sync",
|
||
"helm_upgrade",
|
||
"docker_restart",
|
||
"docker_compose_up",
|
||
"systemctl_restart",
|
||
"repair_bot_execute",
|
||
"ansible_apply",
|
||
"wazuh_active_response",
|
||
"wazuh_rule_change",
|
||
"kali_active_scan",
|
||
"kali_execute",
|
||
"nmap_scan",
|
||
"nuclei_scan",
|
||
"trivy_live_scan",
|
||
"package_upgrade",
|
||
"workflow_modify",
|
||
"workflow_dispatch",
|
||
"runner_restart",
|
||
"secret_read",
|
||
"secret_rotate",
|
||
"webhook_modify",
|
||
"deploy_key_modify",
|
||
"refs_sync",
|
||
"force_push",
|
||
"github_primary_switch",
|
||
"backup_run",
|
||
"restore_run",
|
||
"offsite_sync",
|
||
"remote_delete",
|
||
"telegram_send",
|
||
"soar_case_create",
|
||
"auto_block",
|
||
"production_write",
|
||
]
|
||
|
||
|
||
def get_git_commit(root: Path) -> str:
|
||
try:
|
||
return subprocess.check_output(["git", "rev-parse", "--short=8", "HEAD"], cwd=root, text=True).strip()
|
||
except Exception:
|
||
return "unknown"
|
||
|
||
|
||
def build_snapshot(root: Path, generated_at: str) -> dict[str, Any]:
|
||
enriched_groups = []
|
||
evidence_refs: set[str] = set()
|
||
existing_refs: set[str] = set()
|
||
missing_refs: set[str] = set()
|
||
|
||
for group in ASSET_GROUPS:
|
||
refs = group["evidence_refs"]
|
||
evidence_refs.update(refs)
|
||
ref_states = []
|
||
for ref in refs:
|
||
exists = (root / ref).exists()
|
||
ref_states.append({"ref": ref, "exists": exists})
|
||
if exists:
|
||
existing_refs.add(ref)
|
||
else:
|
||
missing_refs.add(ref)
|
||
enriched_groups.append(
|
||
{
|
||
**group,
|
||
"owner_packet_status": "waiting_asset_owner_packet",
|
||
"runtime_gate": 0,
|
||
"action_button": 0,
|
||
"owner_response_received": 0,
|
||
"owner_response_accepted": 0,
|
||
"live_evidence_accepted": 0,
|
||
"redacted_evidence_refs_required": True,
|
||
"secret_value_collection_allowed": False,
|
||
"raw_payload_allowed": False,
|
||
"evidence_ref_states": ref_states,
|
||
}
|
||
)
|
||
|
||
summary = {
|
||
"asset_group_count": len(enriched_groups),
|
||
"p0_asset_group_count": sum(1 for group in enriched_groups if group["priority"] == "P0"),
|
||
"p1_asset_group_count": sum(1 for group in enriched_groups if group["priority"] == "P1"),
|
||
"c0_asset_group_count": sum(1 for group in enriched_groups if group["tier"] == "C0"),
|
||
"c1_asset_group_count": sum(1 for group in enriched_groups if group["tier"] == "C1"),
|
||
"evidence_ref_count": len(evidence_refs),
|
||
"existing_evidence_ref_count": len(existing_refs),
|
||
"missing_evidence_ref_count": len(missing_refs),
|
||
"required_owner_field_count": len(REQUIRED_OWNER_FIELDS),
|
||
"reviewer_check_count": len(REVIEWER_CHECKS),
|
||
"outcome_lane_count": len(OUTCOME_LANES),
|
||
"blocked_action_count": len(BLOCKED_ACTIONS),
|
||
"owner_packet_required_count": len(enriched_groups),
|
||
"owner_response_received_count": 0,
|
||
"owner_response_accepted_count": 0,
|
||
"live_evidence_accepted_count": 0,
|
||
"runtime_gate_count": 0,
|
||
"action_button_count": 0,
|
||
"host_write_authorized_count": 0,
|
||
"active_scan_authorized_count": 0,
|
||
"wazuh_active_response_authorized_count": 0,
|
||
"kali_execute_authorized_count": 0,
|
||
"soar_action_authorized_count": 0,
|
||
"auto_block_authorized_count": 0,
|
||
"secret_value_collected_count": 0,
|
||
"raw_payload_stored_count": 0,
|
||
"security_asset_control_ledger_completion_percent": 100,
|
||
"iwooos_headline_progress_percent": 64,
|
||
}
|
||
|
||
return {
|
||
"schema_version": "security_asset_control_ledger_v1",
|
||
"status": "security_asset_control_ledger_ready_no_runtime_action",
|
||
"generated_at": generated_at,
|
||
"git_commit": get_git_commit(root),
|
||
"summary": summary,
|
||
"asset_groups": enriched_groups,
|
||
"required_owner_fields": REQUIRED_OWNER_FIELDS,
|
||
"reviewer_checks": REVIEWER_CHECKS,
|
||
"outcome_lanes": OUTCOME_LANES,
|
||
"blocked_actions": BLOCKED_ACTIONS,
|
||
"execution_boundaries": {
|
||
"not_authorization": True,
|
||
"runtime_execution_authorized": False,
|
||
"host_write_authorized": False,
|
||
"ssh_read_authorized": False,
|
||
"nginx_reload_authorized": False,
|
||
"firewall_change_authorized": False,
|
||
"argocd_sync_authorized": False,
|
||
"workflow_modification_authorized": False,
|
||
"secret_value_collection_allowed": False,
|
||
"wazuh_active_response_authorized": False,
|
||
"kali_active_scan_authorized": False,
|
||
"kali_execute_authorized": False,
|
||
"telegram_send_authorized": False,
|
||
"soar_action_authorized": False,
|
||
"auto_block_authorized": False,
|
||
"production_write_authorized": False,
|
||
"action_buttons_allowed": False,
|
||
},
|
||
}
|
||
|
||
|
||
def main(argv: list[str]) -> int:
|
||
parser = argparse.ArgumentParser()
|
||
parser.add_argument("--root", default=".")
|
||
parser.add_argument("--output")
|
||
parser.add_argument("--generated-at")
|
||
args = parser.parse_args(argv)
|
||
|
||
root = Path(args.root).resolve()
|
||
generated_at = args.generated_at or datetime.now(TAIPEI).replace(microsecond=0).isoformat()
|
||
snapshot = build_snapshot(root, generated_at)
|
||
|
||
if args.output:
|
||
output = Path(args.output)
|
||
if not output.is_absolute():
|
||
output = root / output
|
||
output.parent.mkdir(parents=True, exist_ok=True)
|
||
output.write_text(json.dumps(snapshot, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||
else:
|
||
print(json.dumps(snapshot, ensure_ascii=False, indent=2, sort_keys=True))
|
||
|
||
summary = snapshot["summary"]
|
||
print(
|
||
"SECURITY_ASSET_CONTROL_LEDGER_OK "
|
||
f"groups={summary['asset_group_count']} "
|
||
f"p0={summary['p0_asset_group_count']} "
|
||
f"evidence_refs={summary['evidence_ref_count']} "
|
||
f"runtime_gate={summary['runtime_gate_count']}"
|
||
)
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main(sys.argv[1:]))
|