diff --git a/apps/api/src/services/iwooos_wazuh_manager_registry_reviewer_validation.py b/apps/api/src/services/iwooos_wazuh_manager_registry_reviewer_validation.py index 9ba655c8..766fa105 100644 --- a/apps/api/src/services/iwooos_wazuh_manager_registry_reviewer_validation.py +++ b/apps/api/src/services/iwooos_wazuh_manager_registry_reviewer_validation.py @@ -284,7 +284,6 @@ def _require_boundaries(payload: dict[str, Any]) -> None: summary = _summary(payload) for key in ( "manager_registry_accepted_count", - "post_enable_readback_passed_count", "runtime_gate_count", "host_write_authorized_count", "active_response_authorized_count", @@ -299,7 +298,8 @@ def _require_boundaries(payload: dict[str, Any]) -> None: passed = _int(summary.get("reviewer_validation_passed_count")) failed = _int(summary.get("reviewer_validation_failed_count")) quarantined = _int(summary.get("reviewer_validation_quarantined_count")) - if any(value < 0 for value in (received, accepted, ready, passed, failed, quarantined)): + post_enable = _int(summary.get("post_enable_readback_passed_count")) + if any(value < 0 for value in (received, accepted, ready, passed, failed, quarantined, post_enable)): raise ValueError("Wazuh manager registry reviewer validation counters 不得為負數") if accepted > received: raise ValueError("owner_registry_export_accepted_count 不得大於 received_count") @@ -307,6 +307,8 @@ def _require_boundaries(payload: dict[str, Any]) -> None: raise ValueError("reviewer_validation_ready_count 不得大於 received_count") if passed > accepted: raise ValueError("reviewer_validation_passed_count 不得大於 accepted_count") + if post_enable > passed: + raise ValueError("post_enable_readback_passed_count 不得大於 reviewer_validation_passed_count") if failed and passed: raise ValueError("reviewer_validation_failed_count 與 passed_count 不得同時為正") if quarantined and accepted: diff --git a/apps/api/tests/test_iwooos_wazuh_manager_registry_reviewer_validation.py b/apps/api/tests/test_iwooos_wazuh_manager_registry_reviewer_validation.py index ccadc9c3..dc607c3d 100644 --- a/apps/api/tests/test_iwooos_wazuh_manager_registry_reviewer_validation.py +++ b/apps/api/tests/test_iwooos_wazuh_manager_registry_reviewer_validation.py @@ -83,19 +83,20 @@ def test_iwooos_wazuh_manager_registry_reviewer_validation_contract_has_passed_r assert payload["schema_version"] == "iwooos_wazuh_manager_registry_reviewer_validation_readback_v1" assert payload["source_schema_version"] == "wazuh_manager_registry_reviewer_validation_v1" - assert payload["status"] == "accepted_for_readonly_posture_only" - assert payload["mode"] == "committed_validation_passed_readback_no_runtime_no_secret_collection" + assert payload["status"] == "post_enable_iwooos_readback_passed_no_runtime_no_secret_collection" + assert payload["mode"] == "committed_post_enable_iwooos_readback_passed_no_runtime_no_secret_collection" assert payload["summary"]["expected_scope_alias_count"] == 6 assert payload["summary"]["required_owner_field_count"] == 28 assert payload["summary"]["per_host_required_field_count"] == 9 assert payload["summary"]["reviewer_validation_check_count"] == 10 - assert payload["summary"]["outcome_lane_count"] == 13 + assert payload["summary"]["outcome_lane_count"] == 14 assert payload["summary"]["evidence_slot_count"] == 6 assert payload["summary"]["forbidden_payload_count"] == 27 assert payload["summary"]["owner_registry_export_received_count"] == 1 assert payload["summary"]["owner_registry_export_accepted_count"] == 1 assert payload["summary"]["reviewer_validation_ready_count"] == 1 assert payload["summary"]["reviewer_validation_passed_count"] == 1 + assert payload["summary"]["post_enable_readback_passed_count"] == 1 assert payload["summary"]["reviewer_validation_quarantined_count"] == 0 assert payload["summary"]["manager_registry_accepted_count"] == 0 assert payload["summary"]["runtime_gate_count"] == 0 @@ -115,7 +116,7 @@ def test_iwooos_wazuh_manager_registry_reviewer_validation_evidence_slots_are_ac assert all(item["received"] is True for item in payload["evidence_slots"]) assert all(item["accepted"] is True for item in payload["evidence_slots"]) assert all(item["quarantined"] is False for item in payload["evidence_slots"]) - assert all(item["next_gate"] == "post_enable_iwooos_readback" for item in payload["evidence_slots"]) + assert all(item["next_gate"] == "manager_registry_acceptance_evidence_review" for item in payload["evidence_slots"]) assert "managed_core_node_a" in payload["expected_scope_aliases"] assert "manager_registry_agent_counts" in [item["slot_id"] for item in payload["evidence_slots"]] @@ -129,6 +130,7 @@ def test_iwooos_wazuh_manager_registry_reviewer_validation_api_is_public_safe() assert data["summary"]["owner_registry_export_received_count"] == 1 assert data["summary"]["owner_registry_export_accepted_count"] == 1 assert data["summary"]["reviewer_validation_passed_count"] == 1 + assert data["summary"]["post_enable_readback_passed_count"] == 1 assert data["summary"]["manager_registry_accepted_count"] == 0 assert data["summary"]["runtime_gate_count"] == 0 assert len(data["reviewer_validation_checks"]) == 10 @@ -145,6 +147,10 @@ def test_iwooos_wazuh_manager_registry_reviewer_validation_api_is_public_safe() marker == "wazuh_manager_registry_reviewer_validation_passed_count=1" for marker in data["boundary_markers"] ) + assert any( + marker == "wazuh_manager_registry_reviewer_validation_post_enable_readback_passed_count=1" + for marker in data["boundary_markers"] + ) assert any( marker == "wazuh_manager_registry_reviewer_validation_manager_registry_accepted_count=0" for marker in data["boundary_markers"] @@ -202,6 +208,7 @@ def test_iwooos_wazuh_manager_registry_owner_export_validation_api_does_not_pers assert readback["summary"]["owner_registry_export_received_count"] == 1 assert readback["summary"]["owner_registry_export_accepted_count"] == 1 assert readback["summary"]["reviewer_validation_passed_count"] == 1 + assert readback["summary"]["post_enable_readback_passed_count"] == 1 assert readback["summary"]["manager_registry_accepted_count"] == 0 assert readback["summary"]["runtime_gate_count"] == 0 diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 0e1a6c98..951a64a6 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -20872,6 +20872,10 @@ "label": "Reviewer passed", "detail": "一筆脫敏 owner export refs 已通過 no-persist reviewer validation。" }, + "postEnable": { + "label": "Post-enable", + "detail": "正式 API 與前台已讀回 reviewer passed;這不是 live Wazuh 查詢授權。" + }, "received": { "label": "已收 export", "detail": "已收到一筆 owner-provided redacted registry export refs。" diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 0e1a6c98..951a64a6 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -20872,6 +20872,10 @@ "label": "Reviewer passed", "detail": "一筆脫敏 owner export refs 已通過 no-persist reviewer validation。" }, + "postEnable": { + "label": "Post-enable", + "detail": "正式 API 與前台已讀回 reviewer passed;這不是 live Wazuh 查詢授權。" + }, "received": { "label": "已收 export", "detail": "已收到一筆 owner-provided redacted registry export refs。" diff --git a/apps/web/src/app/[locale]/iwooos/page.tsx b/apps/web/src/app/[locale]/iwooos/page.tsx index a0929668..c6b2380c 100644 --- a/apps/web/src/app/[locale]/iwooos/page.tsx +++ b/apps/web/src/app/[locale]/iwooos/page.tsx @@ -2479,7 +2479,7 @@ const wazuhManagerRegistryReviewerValidationBoundaries = [ 'wazuh_manager_registry_reviewer_validation_required_owner_field_count=28', 'wazuh_manager_registry_reviewer_validation_per_host_required_field_count=9', 'wazuh_manager_registry_reviewer_validation_check_count=10', - 'wazuh_manager_registry_reviewer_validation_outcome_lane_count=13', + 'wazuh_manager_registry_reviewer_validation_outcome_lane_count=14', 'wazuh_manager_registry_reviewer_validation_evidence_slot_count=6', 'wazuh_manager_registry_reviewer_validation_forbidden_payload_count=27', 'wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count=1', @@ -2487,7 +2487,7 @@ const wazuhManagerRegistryReviewerValidationBoundaries = [ 'wazuh_manager_registry_reviewer_validation_passed_count=1', 'wazuh_manager_registry_reviewer_validation_quarantined_count=0', 'wazuh_manager_registry_reviewer_validation_manager_registry_accepted_count=0', - 'wazuh_manager_registry_reviewer_validation_post_enable_readback_passed_count=0', + 'wazuh_manager_registry_reviewer_validation_post_enable_readback_passed_count=1', 'wazuh_manager_registry_reviewer_validation_runtime_gate_count=0', 'wazuh_api_live_query_authorized=false', 'wazuh_agent_reenroll_authorized=false', @@ -9822,6 +9822,12 @@ function IwoooSWazuhManagerRegistryReviewerValidationBoard() { icon: ClipboardCheck, tone: 'steady', }, + { + key: 'postEnable', + value: summary ? String(summary.post_enable_readback_passed_count) : loading ? '...' : '1', + icon: SearchCheck, + tone: summary?.post_enable_readback_passed_count ? 'steady' : 'locked', + }, { key: 'received', value: summary ? String(summary.owner_registry_export_received_count) : loading ? '...' : '1', diff --git a/docs/security/wazuh-manager-registry-reviewer-validation.snapshot.json b/docs/security/wazuh-manager-registry-reviewer-validation.snapshot.json index edbb69b9..c9ff32a0 100644 --- a/docs/security/wazuh-manager-registry-reviewer-validation.snapshot.json +++ b/docs/security/wazuh-manager-registry-reviewer-validation.snapshot.json @@ -2,7 +2,7 @@ "evidence_slots": [ { "accepted": true, - "next_gate": "post_enable_iwooos_readback", + "next_gate": "manager_registry_acceptance_evidence_review", "quarantined": false, "received": true, "required_fields": [ @@ -17,7 +17,7 @@ }, { "accepted": true, - "next_gate": "post_enable_iwooos_readback", + "next_gate": "manager_registry_acceptance_evidence_review", "quarantined": false, "received": true, "required_fields": [ @@ -30,7 +30,7 @@ }, { "accepted": true, - "next_gate": "post_enable_iwooos_readback", + "next_gate": "manager_registry_acceptance_evidence_review", "quarantined": false, "received": true, "required_fields": [ @@ -45,7 +45,7 @@ }, { "accepted": true, - "next_gate": "post_enable_iwooos_readback", + "next_gate": "manager_registry_acceptance_evidence_review", "quarantined": false, "received": true, "required_fields": [ @@ -58,7 +58,7 @@ }, { "accepted": true, - "next_gate": "post_enable_iwooos_readback", + "next_gate": "manager_registry_acceptance_evidence_review", "quarantined": false, "received": true, "required_fields": [ @@ -74,7 +74,7 @@ }, { "accepted": true, - "next_gate": "post_enable_iwooos_readback", + "next_gate": "manager_registry_acceptance_evidence_review", "quarantined": false, "received": true, "required_fields": [ @@ -150,10 +150,11 @@ "firewall_change", "nginx_reload" ], - "generated_at": "2026-06-27T20:42:31+08:00", - "mode": "committed_validation_passed_readback_no_runtime_no_secret_collection", + "generated_at": "2026-06-27T21:45:00+08:00", + "mode": "committed_post_enable_iwooos_readback_passed_no_runtime_no_secret_collection", "no_false_green_rules": [ "reviewer validation passed 只代表脫敏 owner export refs 通過 no-persist 驗證。", + "post-enable IwoooS readback passed 只代表 production API / 前台已讀回 reviewer passed,不代表 live Wazuh 查詢或 runtime action。", "owner registry export accepted 不代表 manager_registry_accepted_count 可增加。", "Dashboard 可見、index pattern 三綠勾、HTTP 200 或 transport observed 不可替代 manager registry counts。", "reviewer accepted 只可更新只讀 posture;active response、agent restart、reenroll、host write、secret rotation 或掃描仍需獨立 runtime gate。" @@ -171,7 +172,8 @@ "reject_runtime_action_request", "ready_for_reviewer_validation", "accepted_for_readonly_posture_only", - "waiting_post_enable_iwooos_readback" + "post_enable_iwooos_readback_passed", + "manager_registry_acceptance_evidence_review" ], "per_host_required_fields": [ "node_alias", @@ -271,9 +273,9 @@ }, { "check_id": "RV-10", - "failure_lane": "waiting_post_enable_iwooos_readback", - "required_evidence": "即使 reviewer 未來接受 evidence,也只能進 read-only posture;必須另有 post-enable readback 才能更新 runtime truth。", - "title": "Post-enable IwoooS readback 仍是下一關" + "failure_lane": "post_enable_iwooos_readback_passed_no_runtime", + "required_evidence": "production API 與前台 smoke 已讀回 reviewer passed;此讀回只更新 read-only posture,不查 live Wazuh、不保存 raw payload、不開 runtime gate。", + "title": "Post-enable IwoooS readback 已讀回但不開 runtime" } ], "schema_version": "wazuh_manager_registry_reviewer_validation_v1", @@ -282,7 +284,7 @@ "docs/security/wazuh-agent-visibility-owner-evidence-preflight.snapshot.json", "docs/security/wazuh-managed-host-coverage-gate.snapshot.json" ], - "status": "accepted_for_readonly_posture_only", + "status": "post_enable_iwooos_readback_passed_no_runtime_no_secret_collection", "summary": { "active_response_authorized_count": 0, "evidence_slot_count": 6, @@ -291,11 +293,11 @@ "forbidden_payload_count": 27, "host_write_authorized_count": 0, "manager_registry_accepted_count": 0, - "outcome_lane_count": 13, + "outcome_lane_count": 14, "owner_registry_export_accepted_count": 1, "owner_registry_export_received_count": 1, "per_host_required_field_count": 9, - "post_enable_readback_passed_count": 0, + "post_enable_readback_passed_count": 1, "required_owner_field_count": 28, "reviewer_validation_check_count": 10, "reviewer_validation_failed_count": 0, diff --git a/scripts/security/security-mirror-progress-guard.py b/scripts/security/security-mirror-progress-guard.py index 766b52a9..e57a57e5 100755 --- a/scripts/security/security-mirror-progress-guard.py +++ b/scripts/security/security-mirror-progress-guard.py @@ -29577,6 +29577,7 @@ def validate(root: Path) -> None: "wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count=1", "wazuh_manager_registry_reviewer_validation_owner_registry_export_accepted_count=1", "wazuh_manager_registry_reviewer_validation_passed_count=1", + "wazuh_manager_registry_reviewer_validation_post_enable_readback_passed_count=1", "wazuh_manager_registry_reviewer_validation_manager_registry_accepted_count=0", "wazuh_manager_registry_reviewer_validation_runtime_gate_count=0", ]: @@ -29606,6 +29607,7 @@ def validate(root: Path) -> None: "wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count=1", "wazuh_manager_registry_reviewer_validation_owner_registry_export_accepted_count=1", "wazuh_manager_registry_reviewer_validation_passed_count=1", + "wazuh_manager_registry_reviewer_validation_post_enable_readback_passed_count=1", "wazuh_manager_registry_reviewer_validation_manager_registry_accepted_count=0", "wazuh_manager_registry_reviewer_validation_runtime_gate_count=0", ]: diff --git a/scripts/security/wazuh-manager-registry-reviewer-validation.py b/scripts/security/wazuh-manager-registry-reviewer-validation.py index d934250b..ac438819 100644 --- a/scripts/security/wazuh-manager-registry-reviewer-validation.py +++ b/scripts/security/wazuh-manager-registry-reviewer-validation.py @@ -128,9 +128,9 @@ REVIEWER_VALIDATION_CHECKS = [ }, { "check_id": "RV-10", - "title": "Post-enable IwoooS readback 仍是下一關", - "required_evidence": "即使 reviewer 未來接受 evidence,也只能進 read-only posture;必須另有 post-enable readback 才能更新 runtime truth。", - "failure_lane": "waiting_post_enable_iwooos_readback", + "title": "Post-enable IwoooS readback 已讀回但不開 runtime", + "required_evidence": "production API 與前台 smoke 已讀回 reviewer passed;此讀回只更新 read-only posture,不查 live Wazuh、不保存 raw payload、不開 runtime gate。", + "failure_lane": "post_enable_iwooos_readback_passed_no_runtime", }, ] @@ -147,7 +147,8 @@ OUTCOME_LANES = [ "reject_runtime_action_request", "ready_for_reviewer_validation", "accepted_for_readonly_posture_only", - "waiting_post_enable_iwooos_readback", + "post_enable_iwooos_readback_passed", + "manager_registry_acceptance_evidence_review", ] EVIDENCE_SLOTS = [ @@ -292,8 +293,8 @@ def build_snapshot(generated_at: str) -> dict[str, Any]: return { "schema_version": SCHEMA_VERSION, "generated_at": generated_at, - "status": "accepted_for_readonly_posture_only", - "mode": "committed_validation_passed_readback_no_runtime_no_secret_collection", + "status": "post_enable_iwooos_readback_passed_no_runtime_no_secret_collection", + "mode": "committed_post_enable_iwooos_readback_passed_no_runtime_no_secret_collection", "scope": "wazuh_manager_registry_owner_export_reviewer_validation", "source_refs": [ "docs/security/wazuh-agent-visibility-owner-evidence-preflight.snapshot.json", @@ -315,7 +316,7 @@ def build_snapshot(generated_at: str) -> dict[str, Any]: "reviewer_validation_failed_count": 0, "reviewer_validation_quarantined_count": 0, "manager_registry_accepted_count": 0, - "post_enable_readback_passed_count": 0, + "post_enable_readback_passed_count": 1, "runtime_gate_count": 0, "host_write_authorized_count": 0, "active_response_authorized_count": 0, @@ -332,7 +333,7 @@ def build_snapshot(generated_at: str) -> dict[str, Any]: "received": True, "accepted": True, "quarantined": False, - "next_gate": "post_enable_iwooos_readback", + "next_gate": "manager_registry_acceptance_evidence_review", } for slot in EVIDENCE_SLOTS ], @@ -355,6 +356,7 @@ def build_snapshot(generated_at: str) -> dict[str, Any]: }, "no_false_green_rules": [ "reviewer validation passed 只代表脫敏 owner export refs 通過 no-persist 驗證。", + "post-enable IwoooS readback passed 只代表 production API / 前台已讀回 reviewer passed,不代表 live Wazuh 查詢或 runtime action。", "owner registry export accepted 不代表 manager_registry_accepted_count 可增加。", "Dashboard 可見、index pattern 三綠勾、HTTP 200 或 transport observed 不可替代 manager registry counts。", "reviewer accepted 只可更新只讀 posture;active response、agent restart、reenroll、host write、secret rotation 或掃描仍需獨立 runtime gate。", @@ -365,8 +367,12 @@ def build_snapshot(generated_at: str) -> dict[str, Any]: def validate(root: Path) -> None: snapshot = load_json(root / SNAPSHOT_PATH) assert_equal("schema_version", snapshot.get("schema_version"), SCHEMA_VERSION) - assert_equal("status", snapshot.get("status"), "accepted_for_readonly_posture_only") - assert_equal("mode", snapshot.get("mode"), "committed_validation_passed_readback_no_runtime_no_secret_collection") + assert_equal("status", snapshot.get("status"), "post_enable_iwooos_readback_passed_no_runtime_no_secret_collection") + assert_equal( + "mode", + snapshot.get("mode"), + "committed_post_enable_iwooos_readback_passed_no_runtime_no_secret_collection", + ) assert_equal("scope", snapshot.get("scope"), "wazuh_manager_registry_owner_export_reviewer_validation") assert_equal("expected_scope_aliases", snapshot.get("expected_scope_aliases"), EXPECTED_SCOPE_ALIASES) assert_equal("required_owner_fields", snapshot.get("required_owner_fields"), REQUIRED_OWNER_FIELDS) @@ -394,13 +400,13 @@ def validate(root: Path) -> None: "owner_registry_export_accepted_count", "reviewer_validation_ready_count", "reviewer_validation_passed_count", + "post_enable_readback_passed_count", ]: assert_equal(f"summary.{key}", summary.get(key), 1) for key in [ "reviewer_validation_failed_count", "reviewer_validation_quarantined_count", "manager_registry_accepted_count", - "post_enable_readback_passed_count", "runtime_gate_count", "host_write_authorized_count", "active_response_authorized_count", @@ -415,7 +421,11 @@ def validate(root: Path) -> None: assert_equal(f"evidence_slots.{slot.get('slot_id')}.received", slot.get("received"), True) assert_equal(f"evidence_slots.{slot.get('slot_id')}.accepted", slot.get("accepted"), True) assert_false(f"evidence_slots.{slot.get('slot_id')}.quarantined", slot.get("quarantined")) - assert_equal(f"evidence_slots.{slot.get('slot_id')}.next_gate", slot.get("next_gate"), "post_enable_iwooos_readback") + assert_equal( + f"evidence_slots.{slot.get('slot_id')}.next_gate", + slot.get("next_gate"), + "manager_registry_acceptance_evidence_review", + ) boundaries = snapshot.get("execution_boundaries", {}) for key, value in boundaries.items(): @@ -454,6 +464,7 @@ def main() -> None: f"slots={summary['evidence_slot_count']} " f"received={summary['owner_registry_export_received_count']} " f"accepted={summary['owner_registry_export_accepted_count']} " + f"post_enable={summary['post_enable_readback_passed_count']} " f"runtime_gate={summary['runtime_gate_count']}" )