fix(agent): fail closed on incomplete Wazuh context

This commit is contained in:
ogt
2026-07-11 11:57:38 +08:00
parent f4e85fac38
commit 0fe6bd3ba0
2 changed files with 270 additions and 29 deletions

View File

@@ -338,15 +338,106 @@ async def test_wazuh_posture_scheduler_retries_failed_check_once_per_deploy(
assert result["queued"] == 1
assert result["retry_of_failed_check_mode"] is True
recorded = recorder.await_args.kwargs
assert recorded["incident"]["incident_id"] == (
"IWZ-POSTURE-2026071100-ABCDEF123456"
assert recorded["incident"]["incident_id"].startswith(
"IWZ-P-2026071100-"
)
assert len(recorded["incident"]["incident_id"]) == 29
assert recorded["incident"]["source_receipt_ref"] == (
"scheduled-wazuh-manager-posture:2026071100-ABCDEF123456"
)
assert recorded["automation_run_id"] == result["automation_run_id"]
@pytest.mark.asyncio
async def test_wazuh_posture_scheduler_queues_one_context_retry_per_deploy(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.jobs import awooop_ansible_candidate_backfill_job as job
existing_row = {
"op_id": "00000000-0000-0000-0000-000000000322",
"candidate_status": "success",
"automation_run_id": "00000000-0000-0000-0000-000000000322",
"source_receipt_ref": "scheduled-wazuh-manager-posture:2026071100",
"latest_check_status": "success",
"predecision_evidence_ready": False,
}
class _Mappings:
def first(self):
return existing_row
class _Result:
def mappings(self):
return _Mappings()
class _Db:
async def execute(self, _statement, _params):
return _Result()
@asynccontextmanager
async def fake_db_context(_project_id: str):
yield _Db()
recorder = AsyncMock(return_value=True)
monkeypatch.setattr(job, "get_db_context", fake_db_context)
monkeypatch.setattr(
job.settings,
"ENABLE_IWOOOS_WAZUH_MANAGER_POSTURE_EXECUTOR",
True,
)
monkeypatch.setattr(
job.settings,
"IWOOOS_WAZUH_MANAGER_POSTURE_FRESHNESS_HOURS",
6,
)
monkeypatch.setattr(
job,
"now_taipei",
MagicMock(return_value=datetime.fromisoformat("2026-07-11T05:59:59+08:00")),
)
monkeypatch.setenv(
"AWOOOI_BUILD_COMMIT_SHA",
"abcdef1234567890abcdef1234567890abcdef12",
)
result = await job.enqueue_iwooos_wazuh_manager_posture_candidate_once(
recorder=recorder,
evidence_collector=AsyncMock(return_value=MagicMock(snapshot_id="wazuh-3")),
evidence_verifier=AsyncMock(return_value=True),
)
assert result["status"] == "queued_for_controlled_executor_context_retry"
assert result["queued"] == 1
assert result["retry_of_failed_check_mode"] is False
assert result["retry_of_incomplete_predecision_context"] is True
recorded = recorder.await_args.kwargs
assert recorded["incident"]["source_receipt_ref"] == (
"scheduled-wazuh-manager-posture:2026071100-ABCDEF123456-CONTEXT"
)
assert recorded["incident"]["incident_id"].startswith(
"IWZ-P-2026071100-"
)
assert len(recorded["incident"]["incident_id"]) == 29
existing_row["automation_run_id"] = result["automation_run_id"]
existing_row["source_receipt_ref"] = recorded["incident"]["source_receipt_ref"]
duplicate_collector = AsyncMock()
duplicate = await job.enqueue_iwooos_wazuh_manager_posture_candidate_once(
recorder=recorder,
evidence_collector=duplicate_collector,
)
assert duplicate["status"] == "context_retry_already_attempted_for_deploy"
assert duplicate["queued"] == 0
assert duplicate["retry_of_incomplete_predecision_context"] is True
assert duplicate["active_blockers"] == [
"bounded_context_retry_already_attempted_for_deploy"
]
recorder.assert_awaited_once()
duplicate_collector.assert_not_awaited()
@pytest.mark.asyncio
async def test_wazuh_posture_scheduler_does_not_duplicate_active_candidate(
monkeypatch: pytest.MonkeyPatch,
@@ -360,6 +451,7 @@ async def test_wazuh_posture_scheduler_does_not_duplicate_active_candidate(
"candidate_status": "dry_run",
"automation_run_id": "00000000-0000-0000-0000-000000000331",
"latest_check_status": "pending",
"predecision_evidence_ready": True,
}
class _Result:
@@ -396,6 +488,65 @@ async def test_wazuh_posture_scheduler_does_not_duplicate_active_candidate(
recorder.assert_not_awaited()
@pytest.mark.asyncio
async def test_wazuh_posture_scheduler_does_not_retry_pending_candidate_without_context(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.jobs import awooop_ansible_candidate_backfill_job as job
class _Mappings:
def first(self):
return {
"op_id": "00000000-0000-0000-0000-000000000332",
"candidate_status": "dry_run",
"automation_run_id": "00000000-0000-0000-0000-000000000332",
"latest_check_status": "pending",
"predecision_evidence_ready": False,
}
class _Result:
def mappings(self):
return _Mappings()
class _Db:
async def execute(self, _statement, _params):
return _Result()
@asynccontextmanager
async def fake_db_context(_project_id: str):
yield _Db()
recorder = AsyncMock(return_value=True)
collector = AsyncMock()
monkeypatch.setattr(job, "get_db_context", fake_db_context)
monkeypatch.setattr(
job.settings,
"ENABLE_IWOOOS_WAZUH_MANAGER_POSTURE_EXECUTOR",
True,
)
monkeypatch.setattr(
job.settings,
"IWOOOS_WAZUH_MANAGER_POSTURE_FRESHNESS_HOURS",
6,
)
result = await job.enqueue_iwooos_wazuh_manager_posture_candidate_once(
recorder=recorder,
evidence_collector=collector,
)
assert result["status"] == (
"fresh_candidate_predecision_evidence_not_verified"
)
assert result["queued"] == 0
assert result["evidence_ready"] == 0
assert result["active_blockers"] == [
"predecision_mcp_or_log_evidence_missing"
]
recorder.assert_not_awaited()
collector.assert_not_awaited()
@pytest.mark.asyncio
async def test_wazuh_posture_scheduler_limits_failed_retry_to_once_per_deploy(
monkeypatch: pytest.MonkeyPatch,
@@ -463,7 +614,7 @@ async def test_wazuh_posture_scheduler_limits_failed_retry_to_once_per_deploy(
@pytest.mark.asyncio
async def test_wazuh_posture_scheduler_queues_bounded_probe_with_degraded_context(
async def test_wazuh_posture_scheduler_fails_closed_with_degraded_context(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.jobs import awooop_ansible_candidate_backfill_job as job
@@ -503,11 +654,11 @@ async def test_wazuh_posture_scheduler_queues_bounded_probe_with_degraded_contex
evidence_verifier=AsyncMock(return_value=False),
)
assert result["status"] == ("queued_for_controlled_executor_with_degraded_context")
assert result["queued"] == 1
assert result["status"] == "blocked_predecision_evidence_not_verified"
assert result["queued"] == 0
assert result["evidence_ready"] == 0
assert result["active_blockers"] == ["predecision_mcp_or_log_evidence_missing"]
recorder.assert_awaited_once()
recorder.assert_not_awaited()
def test_wazuh_runtime_api_is_public_safe(monkeypatch: pytest.MonkeyPatch) -> None: