From 4bc97290b9ee109c333e11bfdac2f75eb07df567 Mon Sep 17 00:00:00 2001 From: ogt Date: Sat, 11 Jul 2026 11:39:44 +0800 Subject: [PATCH] fix(iwooos): normalize wazuh receipt timestamps --- ...wooos_wazuh_controlled_executor_runtime.py | 27 ++++++++++++++++--- ...wooos_wazuh_controlled_executor_runtime.py | 22 ++++++++++++++- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/apps/api/src/services/iwooos_wazuh_controlled_executor_runtime.py b/apps/api/src/services/iwooos_wazuh_controlled_executor_runtime.py index d788f1436..61141b289 100644 --- a/apps/api/src/services/iwooos_wazuh_controlled_executor_runtime.py +++ b/apps/api/src/services/iwooos_wazuh_controlled_executor_runtime.py @@ -10,7 +10,7 @@ from __future__ import annotations import json from collections.abc import Mapping -from datetime import datetime +from datetime import UTC, datetime from typing import Any from sqlalchemy import text @@ -484,5 +484,26 @@ def _latest_receipt_at(data: Mapping[str, Any]) -> str | None: ] if not values: return None - latest = max(values) - return latest.isoformat() if isinstance(latest, datetime) else str(latest) + normalized = [ + parsed + for value in values + if (parsed := _utc_receipt_datetime(value)) is not None + ] + if normalized: + return max(normalized).isoformat() + return max(str(value) for value in values) + + +def _utc_receipt_datetime(value: Any) -> datetime | None: + if isinstance(value, datetime): + parsed = value + elif isinstance(value, str): + try: + parsed = datetime.fromisoformat(value.replace("Z", "+00:00")) + except ValueError: + return None + else: + return None + if parsed.tzinfo is None: + parsed = parsed.replace(tzinfo=UTC) + return parsed.astimezone(UTC) diff --git a/apps/api/tests/test_iwooos_wazuh_controlled_executor_runtime.py b/apps/api/tests/test_iwooos_wazuh_controlled_executor_runtime.py index 9094f0a11..a434e76b7 100644 --- a/apps/api/tests/test_iwooos_wazuh_controlled_executor_runtime.py +++ b/apps/api/tests/test_iwooos_wazuh_controlled_executor_runtime.py @@ -2,7 +2,7 @@ from __future__ import annotations import os from contextlib import asynccontextmanager -from datetime import datetime +from datetime import UTC, datetime from pathlib import Path from unittest.mock import AsyncMock, MagicMock @@ -208,6 +208,26 @@ def test_wazuh_posture_catalog_and_playbook_are_bounded_no_write() -> None: assert wazuh_manager["criticality"] == "P0" +def test_wazuh_runtime_readback_normalizes_mixed_receipt_timezones() -> None: + row = _complete_row() + row["candidate_created_at"] = datetime(2026, 7, 11, 3, 36) + row["check_created_at"] = datetime( + 2026, + 7, + 11, + 3, + 37, + tzinfo=UTC, + ) + + payload = build_iwooos_wazuh_controlled_executor_runtime_readback( + row, + executor_enabled=True, + ) + + assert payload["latest_receipt_at"] == "2026-07-11T03:37:00+00:00" + + @pytest.mark.asyncio async def test_wazuh_posture_scheduler_enqueues_after_mcp_and_log_evidence( monkeypatch: pytest.MonkeyPatch,