feat(iwooos): add Wazuh live metadata env gate

This commit is contained in:
ogt
2026-06-24 22:44:41 +08:00
parent 20748fe1ba
commit 80b8758a3d
5 changed files with 399 additions and 5 deletions

View File

@@ -232,6 +232,12 @@
- 完成度release owner request / acceptance artifact 與 guard `100%`;正式 owner response / release ready / push / deploy / production readback `0%`
- 邊界:本段沒有發送 request、沒有收件、沒有讀 credential、沒有推送、沒有部署、沒有 Wazuh live query、沒有 runtime action一般「批准繼續」仍不可當 release lane owner response。
**Live metadata env gate 補充22:42 Asia/Taipei**
- 新增 `scripts/security/wazuh-readonly-live-metadata-env-gate.py``docs/security/wazuh-readonly-live-metadata-env-gate.snapshot.json`,並接入 `security-mirror-progress-guard.py`
- Gate 固定 server-side env keys `4`、required owner fields `15`、reviewer checks `15`、outcome lanes `10`、blocked actions `23`;目前 production route readback passed `0`、live metadata owner accepted `0`、secret source metadata accepted `0`、Wazuh manager health accepted `0`、live query authorized `0`、runtime gate `0`
- 完成度live metadata env gate artifact / guard `100%`server-side env owner response、secret source metadata、post-enable readback、live query authorization 仍 `0%`
- 邊界:本段沒有讀 secret、沒有查 Wazuh API、沒有修改 K8s / ArgoCD / Docker / Nginx / firewall、沒有部署、沒有 active response、沒有 host write部署後 route 200 也不能直接代表可查 Wazuh live metadata。
## 2026-06-2421:04 recovery readback 與 MOMO V10.651 雙機基準收斂
**背景**:前一輪 MOMO workspace readback 指到 `V10.646`,但 21:04 live health 已回 `V10.651`。因此本輪重新比對 Gitea `wooo/ewoooc` `main`、正式站 `/health`、Mac Mini / MacBook Pro Codex workspace 與 full-stack cold-start避免「網站可用」和「版本 / 資料最新」互相混淆。

View File

@@ -33,11 +33,13 @@
- `scripts/security/wazuh-readonly-release-lane-preflight.py`
- `scripts/security/wazuh-readonly-release-owner-request.py`
- `scripts/security/wazuh-readonly-release-owner-response-acceptance.py`
- `scripts/security/wazuh-readonly-live-metadata-env-gate.py`
- `scripts/security/security-mirror-progress-guard.py`
- `docs/security/wazuh-readonly-release-gate.snapshot.json`
- `docs/security/wazuh-readonly-release-lane-preflight.snapshot.json`
- `docs/security/wazuh-readonly-release-owner-request.snapshot.json`
- `docs/security/wazuh-readonly-release-owner-response-acceptance.snapshot.json`
- `docs/security/wazuh-readonly-live-metadata-env-gate.snapshot.json`
- `docs/LOGBOOK.md`
完成內容:
@@ -54,6 +56,7 @@
- 新增 release gate snapshot 與 guard固定 source-side 已完成、Gitea push / production deploy / production readback 尚未完成,避免後續把 predeploy 404 誤判成通過。
- 新增 release lane preflight snapshot 與 guard固定正式 release 前必須選擇 `formal_gitea_merge``formal_patch_apply``maintainer_local_push_with_safe_credential` 其中一條合規 lane且 owner ack / evidence 未到齊前不得 push、deploy、force push、使用明文 token workaround 或改 runtime。
- 新增 release owner request 草稿與 owner response acceptance 帳本,將 required ack flags、required evidence fields、allowed release methods、blocked actions、forbidden payloads 與 reviewer checks 機器可讀化;目前 request sent、response received / accepted、release ready、runtime gate 全部維持 `0`
- 新增 live metadata env gate固定部署後要先通過 production route readback、server-side env owner response、secret source metadata、Wazuh manager health ref、readonly account scope、post-enable readback、rollback 與 no-secret / no-raw-payload attestation目前 live query authorized 仍為 `0`
## 已完成驗證
@@ -66,9 +69,10 @@ python3 scripts/security/wazuh-readonly-release-gate.py --root .
python3 scripts/security/wazuh-readonly-release-lane-preflight.py --root .
python3 scripts/security/wazuh-readonly-release-owner-request.py --root .
python3 scripts/security/wazuh-readonly-release-owner-response-acceptance.py --root .
python3 scripts/security/wazuh-readonly-live-metadata-env-gate.py --root .
python3 scripts/security/security-mirror-progress-guard.py --root .
python3 scripts/ops/doc-secrets-sanity-check.py docs apps/api/src/api/v1/iwooos.py apps/web/src/app/api/iwooos/wazuh/route.ts scripts/security/wazuh-readonly-route-boundary-guard.py scripts/security/wazuh-readonly-production-readback.py scripts/security/wazuh-readonly-release-gate.py scripts/security/wazuh-readonly-release-lane-preflight.py scripts/security/wazuh-readonly-release-owner-request.py scripts/security/wazuh-readonly-release-owner-response-acceptance.py
python3 -m py_compile apps/api/src/api/v1/iwooos.py scripts/security/wazuh-readonly-route-boundary-guard.py scripts/security/wazuh-readonly-production-readback.py scripts/security/wazuh-readonly-release-gate.py scripts/security/wazuh-readonly-release-lane-preflight.py scripts/security/wazuh-readonly-release-owner-request.py scripts/security/wazuh-readonly-release-owner-response-acceptance.py scripts/security/security-mirror-progress-guard.py
python3 scripts/ops/doc-secrets-sanity-check.py docs apps/api/src/api/v1/iwooos.py apps/web/src/app/api/iwooos/wazuh/route.ts scripts/security/wazuh-readonly-route-boundary-guard.py scripts/security/wazuh-readonly-production-readback.py scripts/security/wazuh-readonly-release-gate.py scripts/security/wazuh-readonly-release-lane-preflight.py scripts/security/wazuh-readonly-release-owner-request.py scripts/security/wazuh-readonly-release-owner-response-acceptance.py scripts/security/wazuh-readonly-live-metadata-env-gate.py
python3 -m py_compile apps/api/src/api/v1/iwooos.py scripts/security/wazuh-readonly-route-boundary-guard.py scripts/security/wazuh-readonly-production-readback.py scripts/security/wazuh-readonly-release-gate.py scripts/security/wazuh-readonly-release-lane-preflight.py scripts/security/wazuh-readonly-release-owner-request.py scripts/security/wazuh-readonly-release-owner-response-acceptance.py scripts/security/wazuh-readonly-live-metadata-env-gate.py scripts/security/security-mirror-progress-guard.py
git diff --check
```
@@ -80,8 +84,9 @@ git diff --check
- `wazuh-readonly-release-lane-preflight``ready=0 acks=0/6 evidence=0/6 runtime_gate=0`
- `wazuh-readonly-release-owner-request``drafts=1 sent=0 accepted=0 runtime_gate=0`
- `wazuh-readonly-release-owner-response-acceptance``received=0 accepted=0 acks=0/6 evidence=0/6 runtime_gate=0`
- `wazuh-readonly-live-metadata-env-gate``route_readback=0 owner=0 secret_meta=0 live_query=0 runtime_gate=0`
- `security-mirror-progress-guard``SECURITY_MIRROR_PROGRESS_GUARD_OK`
- `doc-secrets-sanity-check``DOC_SECRET_SANITY_OK scanned_files=972`
- `doc-secrets-sanity-check``DOC_SECRET_SANITY_OK scanned_files=973`
- `py_compile`:通過。
- `git diff --check`:通過。
@@ -103,7 +108,7 @@ git am /private/tmp/awoooi-iwooos-wazuh-boundary-release-patch-<timestamp>/*.pat
- `python3 scripts/security/wazuh-readonly-release-gate.py --root .``WAZUH_READONLY_RELEASE_GATE_OK source=1 push=0 deploy=0 readback=0 runtime_gate=0`
- `python3 scripts/security/wazuh-readonly-release-lane-preflight.py --root .``WAZUH_READONLY_RELEASE_LANE_PREFLIGHT_OK ready=0 acks=0/6 evidence=0/6 runtime_gate=0`
- `python3 scripts/security/security-mirror-progress-guard.py --root .``SECURITY_MIRROR_PROGRESS_GUARD_OK`
- `python3 scripts/ops/doc-secrets-sanity-check.py ...``DOC_SECRET_SANITY_OK scanned_files=972`
- `python3 scripts/ops/doc-secrets-sanity-check.py ...``DOC_SECRET_SANITY_OK scanned_files=973`
- `python3 -m py_compile ...`:通過。
- `git diff --check`:通過。
@@ -179,6 +184,7 @@ python3 scripts/security/wazuh-readonly-production-readback.py --json
| Wazuh release gate snapshot / guard | `100%` | 已完成;固定 push/deploy/readback 仍 blocked |
| Wazuh release lane preflight | `100%` | 已完成owner acks `0/6`、evidence `0/6`、正式 release ready `0` |
| Wazuh release owner request / acceptance | `100%` | 已完成只讀草稿與收件帳本request sent `0`、response accepted `0` |
| Wazuh live metadata env gate | `100%` | 已完成只讀 gateroute readback / owner / secret metadata / live query 仍 `0` |
| 乾淨套用 proof | `100%` | patch set 可落在最新 `gitea/main` 並通過同組 guard最終 hash 以 release 前 readback 為準 |
| Gitea push | `0%` | 受控 workspace HTTPS credential 缺失 |
| Production deploy / readback | `0%` | 等待 release lane |
@@ -191,6 +197,6 @@ python3 scripts/security/wazuh-readonly-production-readback.py --json
1. 先依 `wazuh-readonly-release-owner-request.snapshot.json` 補 release lane owner response選擇 formal merge、formal patch apply 或安全 credential push並補 6 個 ack 與 6 個 evidence 欄位。
2. 解決受控 workspace Gitea HTTPS push 認證,或由正式 release lane 合併 `codex/iwooos-wazuh-boundary-guard-20260624` 分支 HEAD。
3. 部署後先驗證 `/api/iwooos/wazuh` 不再 404且預設 disabled 邊界正確。
4.開 owner gate 決定是否啟用 server-side Wazuh read-only metadata query。
4.`wazuh-readonly-live-metadata-env-gate.snapshot.json` 補 server-side env owner response、secret source metadata、Wazuh manager health ref、readonly account scope 與 post-enable readback才可考慮啟用 Wazuh read-only metadata query。
5. 收件 Wazuh manager health ref、agent status ref、event refs、host forensic refs 與 containment / recovery proof。
6. 仍禁止 active response、host write、firewall / Nginx / Docker / K8s runtime action、Kali active scan、secret 明文收集。

View File

@@ -0,0 +1,145 @@
{
"blocked_actions": [
"collect_wazuh_password",
"collect_wazuh_token",
"collect_wazuh_raw_payload",
"hardcode_wazuh_base_url",
"hardcode_wazuh_username",
"hardcode_wazuh_password",
"disable_tls_verification",
"enable_env_before_production_route_readback",
"enable_env_without_secret_owner",
"enable_wazuh_live_metadata_without_owner_gate",
"enable_wazuh_active_response",
"wazuh_manager_restart",
"wazuh_rule_change",
"wazuh_decoder_change",
"k8s_secret_manual_patch",
"argocd_manual_sync",
"docker_restart",
"nginx_or_gateway_workaround_for_404",
"firewall_change",
"host_write",
"kali_active_scan",
"production_deploy_without_release_lane",
"mark_predeploy_404_as_passed_readback"
],
"execution_boundaries": {
"argocd_sync_authorized": false,
"docker_restart_authorized": false,
"firewall_change_authorized": false,
"host_write_authorized": false,
"k8s_secret_patch_authorized": false,
"kali_active_scan_authorized": false,
"nginx_gateway_workaround_authorized": false,
"not_authorization": true,
"production_deploy_authorized": false,
"raw_wazuh_payload_storage_allowed": false,
"repo_write_authorized": false,
"runtime_execution_authorized": false,
"secret_value_collection_allowed": false,
"wazuh_active_response_authorized": false,
"wazuh_api_live_query_authorized": false
},
"generated_at": "2026-06-24T22:42:00+08:00",
"live_metadata_candidate": {
"candidate_id": "iwooos_wazuh_readonly_live_metadata_env",
"not_authorization": true,
"owner_response_accepted": false,
"owner_response_received": false,
"post_enable_readback_command": "python3 scripts/security/wazuh-readonly-production-readback.py --json",
"production_route_readback_ref": null,
"readonly_account_scope_ref": null,
"runtime_gate": false,
"secret_source_metadata_ref": null,
"server_side_env_keys": [
"IWOOOS_WAZUH_READONLY_ENABLED",
"WAZUH_API_BASE_URL",
"WAZUH_API_USERNAME",
"WAZUH_API_PASSWORD"
],
"status": "waiting_release_readback_and_live_metadata_owner_response",
"wazuh_active_response_authorized": false,
"wazuh_api_live_query_authorized": false,
"wazuh_manager_health_ref": null
},
"mode": "repo_gate_no_secret_no_runtime_no_wazuh_query",
"operator_interpretation": [
"此 gate 不代表 Wazuh live metadata 已啟用,只代表啟用前欄位與禁止動作已固定。",
"Production route 必須先不加 --allow-predeploy-404 readback 通過,才能考慮 server-side env enable。",
"secret handling 只能提供注入來源 metadata 與 owner不得提交密碼、token、hash、partial secret 或 raw env。",
"Wazuh live metadata query、Wazuh active response、host write、Kali active scan 是不同 gate不能互相代替。"
],
"outcome_lanes": [
"waiting_release_readback",
"waiting_live_metadata_owner_response",
"request_secret_source_metadata_supplement",
"request_wazuh_manager_health_supplement",
"request_readonly_account_scope_supplement",
"quarantine_secret_or_raw_payload",
"reject_runtime_workaround",
"ready_for_live_metadata_reviewer_validation",
"waiting_post_enable_readback",
"waiting_runtime_gate"
],
"required_owner_fields": [
"wazuh_live_metadata_owner",
"release_readback_ref",
"secret_injection_owner",
"secret_source_metadata_ref",
"wazuh_manager_health_ref",
"wazuh_api_tls_validation_ref",
"readonly_account_scope_ref",
"agent_alias_mapping_policy",
"post_enable_readback_command",
"rollback_owner",
"maintenance_window",
"validation_plan",
"no_secret_value_attestation",
"no_raw_payload_attestation",
"active_response_separate_gate_ack"
],
"reviewer_checks": [
"production_route_readback_passed_before_env_enable",
"server_side_env_keys_present_as_metadata_only",
"secret_value_absent",
"secret_source_metadata_ref_present",
"wazuh_api_base_url_https_only",
"readonly_account_scope_present",
"wazuh_manager_health_ref_present",
"agent_alias_mapping_policy_present",
"post_enable_readback_command_present",
"rollback_owner_present",
"maintenance_window_present",
"validation_plan_present",
"no_raw_wazuh_payload",
"active_response_gate_separate",
"runtime_gate_stays_zero_until_reviewer_acceptance"
],
"schema_version": "iwooos_wazuh_readonly_live_metadata_env_gate_v1",
"server_side_env_keys": [
"IWOOOS_WAZUH_READONLY_ENABLED",
"WAZUH_API_BASE_URL",
"WAZUH_API_USERNAME",
"WAZUH_API_PASSWORD"
],
"status": "blocked_waiting_release_readback_and_live_metadata_owner_response",
"summary": {
"blocked_action_count": 23,
"host_write_authorized_count": 0,
"live_metadata_owner_response_accepted_count": 0,
"live_metadata_owner_response_received_count": 0,
"outcome_lane_count": 10,
"post_enable_readback_passed_count": 0,
"production_route_readback_passed_count": 0,
"readonly_account_scope_accepted_count": 0,
"required_owner_field_count": 15,
"reviewer_check_count": 15,
"runtime_gate_count": 0,
"secret_source_metadata_accepted_count": 0,
"server_side_env_key_count": 4,
"wazuh_active_response_authorized_count": 0,
"wazuh_api_live_query_authorized_count": 0,
"wazuh_manager_health_ref_accepted_count": 0
}
}

View File

@@ -107,6 +107,10 @@ def validate(root: Path) -> None:
str(root / "scripts" / "security" / "wazuh-readonly-release-owner-response-acceptance.py")
)
wazuh_readonly_release_owner_response_acceptance["validate"](root)
wazuh_readonly_live_metadata_env_gate = runpy.run_path(
str(root / "scripts" / "security" / "wazuh-readonly-live-metadata-env-gate.py")
)
wazuh_readonly_live_metadata_env_gate["validate"](root)
telegram_alert_readability_guard = runpy.run_path(
str(root / "scripts" / "security" / "telegram-alert-readability-guard.py")
)

View File

@@ -0,0 +1,233 @@
#!/usr/bin/env python3
"""
IwoooS Wazuh 只讀 live metadata env gate。
本工具只固定 server-side env / secret 注入 / production readback 的
owner gate。它不讀 secret value、不查 Wazuh API、不改 K8s / ArgoCD /
Docker / Nginx / firewall、不部署、不推送也不啟用 active response。
"""
from __future__ import annotations
import argparse
import json
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any
TAIPEI = timezone(timedelta(hours=8))
SNAPSHOT_PATH = Path("docs/security/wazuh-readonly-live-metadata-env-gate.snapshot.json")
SERVER_SIDE_ENV_KEYS = [
"IWOOOS_WAZUH_READONLY_ENABLED",
"WAZUH_API_BASE_URL",
"WAZUH_API_USERNAME",
"WAZUH_API_PASSWORD",
]
REQUIRED_OWNER_FIELDS = [
"wazuh_live_metadata_owner",
"release_readback_ref",
"secret_injection_owner",
"secret_source_metadata_ref",
"wazuh_manager_health_ref",
"wazuh_api_tls_validation_ref",
"readonly_account_scope_ref",
"agent_alias_mapping_policy",
"post_enable_readback_command",
"rollback_owner",
"maintenance_window",
"validation_plan",
"no_secret_value_attestation",
"no_raw_payload_attestation",
"active_response_separate_gate_ack",
]
REVIEWER_CHECKS = [
"production_route_readback_passed_before_env_enable",
"server_side_env_keys_present_as_metadata_only",
"secret_value_absent",
"secret_source_metadata_ref_present",
"wazuh_api_base_url_https_only",
"readonly_account_scope_present",
"wazuh_manager_health_ref_present",
"agent_alias_mapping_policy_present",
"post_enable_readback_command_present",
"rollback_owner_present",
"maintenance_window_present",
"validation_plan_present",
"no_raw_wazuh_payload",
"active_response_gate_separate",
"runtime_gate_stays_zero_until_reviewer_acceptance",
]
OUTCOME_LANES = [
"waiting_release_readback",
"waiting_live_metadata_owner_response",
"request_secret_source_metadata_supplement",
"request_wazuh_manager_health_supplement",
"request_readonly_account_scope_supplement",
"quarantine_secret_or_raw_payload",
"reject_runtime_workaround",
"ready_for_live_metadata_reviewer_validation",
"waiting_post_enable_readback",
"waiting_runtime_gate",
]
BLOCKED_ACTIONS = [
"collect_wazuh_password",
"collect_wazuh_token",
"collect_wazuh_raw_payload",
"hardcode_wazuh_base_url",
"hardcode_wazuh_username",
"hardcode_wazuh_password",
"disable_tls_verification",
"enable_env_before_production_route_readback",
"enable_env_without_secret_owner",
"enable_wazuh_live_metadata_without_owner_gate",
"enable_wazuh_active_response",
"wazuh_manager_restart",
"wazuh_rule_change",
"wazuh_decoder_change",
"k8s_secret_manual_patch",
"argocd_manual_sync",
"docker_restart",
"nginx_or_gateway_workaround_for_404",
"firewall_change",
"host_write",
"kali_active_scan",
"production_deploy_without_release_lane",
"mark_predeploy_404_as_passed_readback",
]
def now_iso() -> str:
return datetime.now(TAIPEI).replace(microsecond=0).isoformat()
def build_report(generated_at: str | None = None) -> dict[str, Any]:
return {
"schema_version": "iwooos_wazuh_readonly_live_metadata_env_gate_v1",
"generated_at": generated_at or now_iso(),
"status": "blocked_waiting_release_readback_and_live_metadata_owner_response",
"mode": "repo_gate_no_secret_no_runtime_no_wazuh_query",
"summary": {
"server_side_env_key_count": len(SERVER_SIDE_ENV_KEYS),
"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),
"production_route_readback_passed_count": 0,
"live_metadata_owner_response_received_count": 0,
"live_metadata_owner_response_accepted_count": 0,
"secret_source_metadata_accepted_count": 0,
"wazuh_manager_health_ref_accepted_count": 0,
"readonly_account_scope_accepted_count": 0,
"post_enable_readback_passed_count": 0,
"wazuh_api_live_query_authorized_count": 0,
"wazuh_active_response_authorized_count": 0,
"host_write_authorized_count": 0,
"runtime_gate_count": 0,
},
"server_side_env_keys": SERVER_SIDE_ENV_KEYS,
"required_owner_fields": REQUIRED_OWNER_FIELDS,
"reviewer_checks": REVIEWER_CHECKS,
"outcome_lanes": OUTCOME_LANES,
"blocked_actions": BLOCKED_ACTIONS,
"live_metadata_candidate": {
"candidate_id": "iwooos_wazuh_readonly_live_metadata_env",
"status": "waiting_release_readback_and_live_metadata_owner_response",
"production_route_readback_ref": None,
"server_side_env_keys": SERVER_SIDE_ENV_KEYS,
"secret_source_metadata_ref": None,
"wazuh_manager_health_ref": None,
"readonly_account_scope_ref": None,
"post_enable_readback_command": "python3 scripts/security/wazuh-readonly-production-readback.py --json",
"owner_response_received": False,
"owner_response_accepted": False,
"wazuh_api_live_query_authorized": False,
"wazuh_active_response_authorized": False,
"runtime_gate": False,
"not_authorization": True,
},
"execution_boundaries": {
"repo_write_authorized": False,
"production_deploy_authorized": False,
"runtime_execution_authorized": False,
"secret_value_collection_allowed": False,
"wazuh_api_live_query_authorized": False,
"wazuh_active_response_authorized": False,
"raw_wazuh_payload_storage_allowed": False,
"host_write_authorized": False,
"kali_active_scan_authorized": False,
"k8s_secret_patch_authorized": False,
"argocd_sync_authorized": False,
"docker_restart_authorized": False,
"nginx_gateway_workaround_authorized": False,
"firewall_change_authorized": False,
"not_authorization": True,
},
"operator_interpretation": [
"此 gate 不代表 Wazuh live metadata 已啟用,只代表啟用前欄位與禁止動作已固定。",
"Production route 必須先不加 --allow-predeploy-404 readback 通過,才能考慮 server-side env enable。",
"secret handling 只能提供注入來源 metadata 與 owner不得提交密碼、token、hash、partial secret 或 raw env。",
"Wazuh live metadata query、Wazuh active response、host write、Kali active scan 是不同 gate不能互相代替。",
],
}
def validate(root: Path) -> None:
snapshot_path = root / SNAPSHOT_PATH
if not snapshot_path.exists():
raise SystemExit(f"BLOCKED Wazuh live metadata env gate snapshot missing: {SNAPSHOT_PATH}")
snapshot = json.loads(snapshot_path.read_text(encoding="utf-8"))
expected = build_report(snapshot.get("generated_at"))
for key in ("schema_version", "status", "mode"):
if snapshot.get(key) != expected[key]:
raise SystemExit(f"BLOCKED Wazuh live metadata env gate {key} mismatch")
for key, expected_value in expected["summary"].items():
actual = snapshot.get("summary", {}).get(key)
if actual != expected_value:
raise SystemExit(
f"BLOCKED Wazuh live metadata env gate summary.{key}: "
f"expected {expected_value!r}, got {actual!r}"
)
for key, value in snapshot.get("execution_boundaries", {}).items():
if key == "not_authorization":
if value is not True:
raise SystemExit("BLOCKED Wazuh live metadata env gate not_authorization must be true")
elif value is not False:
raise SystemExit(f"BLOCKED Wazuh live metadata env gate execution_boundaries.{key}: expected false")
def main() -> int:
parser = argparse.ArgumentParser(description="IwoooS Wazuh 只讀 live metadata env gate")
parser.add_argument("--root", default=".", help="repository root")
parser.add_argument("--output", help="寫出 JSON 報告")
parser.add_argument("--generated-at", help="固定報告時間,供 committed snapshot 使用")
args = parser.parse_args()
root = Path(args.root).resolve()
report = build_report(args.generated_at)
if args.output:
output = Path(args.output)
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8")
validate(root)
summary = report["summary"]
print(
"WAZUH_READONLY_LIVE_METADATA_ENV_GATE_OK "
f"route_readback={summary['production_route_readback_passed_count']} "
f"owner={summary['live_metadata_owner_response_accepted_count']} "
f"secret_meta={summary['secret_source_metadata_accepted_count']} "
f"live_query={summary['wazuh_api_live_query_authorized_count']} "
f"runtime_gate={summary['runtime_gate_count']}"
)
return 0
if __name__ == "__main__":
sys.exit(main())