From 273071b6542126cca79d27cc5847ab4e78aa357f Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 31 May 2026 14:26:19 +0800 Subject: [PATCH] fix(awooop): keep external incident ids out of aol bigint --- .../src/services/awooop_ansible_audit_service.py | 9 ++++++++- .../awooop_ansible_check_mode_service.py | 16 ++++++++++++++-- .../api/tests/test_awooop_truth_chain_service.py | 10 ++++++++-- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/apps/api/src/services/awooop_ansible_audit_service.py b/apps/api/src/services/awooop_ansible_audit_service.py index 2e2f2386..8e26f955 100644 --- a/apps/api/src/services/awooop_ansible_audit_service.py +++ b/apps/api/src/services/awooop_ansible_audit_service.py @@ -19,6 +19,12 @@ from src.db.base import get_db_context logger = structlog.get_logger(__name__) +def _automation_operation_log_incident_id(value: str) -> int | None: + # ADR-090 keeps external INC-* ids in input JSON; the DB column is BIGINT. + normalized = str(value or "").strip() + return int(normalized) if normalized.isdigit() else None + + ANSIBLE_OPERATION_TYPES = frozenset({ "ansible_candidate_matched", "ansible_check_mode_executed", @@ -422,7 +428,7 @@ async def record_ansible_decision_audit( :operation_type, 'decision_manager', :status, - NULLIF(:incident_id, ''), + :incident_db_id, CAST(:input AS jsonb), CAST(:output AS jsonb), CAST(:dry_run_result AS jsonb), @@ -433,6 +439,7 @@ async def record_ansible_decision_audit( "operation_type": payload["operation_type"], "status": payload["status"], "incident_id": incident_id, + "incident_db_id": _automation_operation_log_incident_id(incident_id), "input": json.dumps(payload["input"], ensure_ascii=False), "output": json.dumps(payload["output"], ensure_ascii=False), "dry_run_result": json.dumps(payload["dry_run_result"], ensure_ascii=False), diff --git a/apps/api/src/services/awooop_ansible_check_mode_service.py b/apps/api/src/services/awooop_ansible_check_mode_service.py index d39eab23..0577c213 100644 --- a/apps/api/src/services/awooop_ansible_check_mode_service.py +++ b/apps/api/src/services/awooop_ansible_check_mode_service.py @@ -87,6 +87,12 @@ def _incident_id_from_payload(payload: dict[str, Any]) -> str: return str(payload.get("incident_id") or "").strip() +def _automation_operation_log_incident_id(value: str) -> int | None: + # ADR-090 keeps external INC-* ids in input JSON; the DB column is BIGINT. + normalized = str(value or "").strip() + return int(normalized) if normalized.isdigit() else None + + def _check_mode_ssh_key_path() -> Path: return Path(settings.AWOOOP_ANSIBLE_CHECK_MODE_SSH_KEY_PATH) @@ -397,7 +403,7 @@ async def claim_pending_check_modes( 'ansible_check_mode_executed', 'ansible_check_mode_worker', 'pending', - NULLIF(:incident_id, ''), + :incident_db_id, CAST(:input AS jsonb), '{}'::jsonb, CAST(:dry_run_result AS jsonb), @@ -408,6 +414,9 @@ async def claim_pending_check_modes( """), { "incident_id": _incident_id_from_payload(claim_input), + "incident_db_id": _automation_operation_log_incident_id( + _incident_id_from_payload(claim_input) + ), "input": json.dumps(claim_input, ensure_ascii=False), "dry_run_result": json.dumps({ "check_mode_executed": False, @@ -508,7 +517,7 @@ async def _insert_skipped_candidate( 'ansible_execution_skipped', 'ansible_check_mode_worker', 'dry_run', - NULLIF(:incident_id, ''), + :incident_db_id, CAST(:input AS jsonb), CAST(:output AS jsonb), CAST(:dry_run_result AS jsonb), @@ -518,6 +527,9 @@ async def _insert_skipped_candidate( """), { "incident_id": _incident_id_from_payload(input_payload), + "incident_db_id": _automation_operation_log_incident_id( + _incident_id_from_payload(input_payload) + ), "input": json.dumps(input_payload, ensure_ascii=False), "output": json.dumps({ "not_used_reason": reason, diff --git a/apps/api/tests/test_awooop_truth_chain_service.py b/apps/api/tests/test_awooop_truth_chain_service.py index 36678007..2c4c05f1 100644 --- a/apps/api/tests/test_awooop_truth_chain_service.py +++ b/apps/api/tests/test_awooop_truth_chain_service.py @@ -12,6 +12,7 @@ from src.services.awooop_ansible_audit_service import ( record_ansible_decision_audit, ) from src.services.awooop_ansible_check_mode_service import ( + _automation_operation_log_incident_id, build_ansible_check_mode_claim_input, build_ansible_check_mode_command, claim_pending_check_modes, @@ -82,14 +83,19 @@ def test_quality_summary_includes_recent_ansible_operation_incidents() -> None: assert "source_ids.recent_evidence_at DESC" in source -def test_ansible_audit_writes_incident_id_column_for_truth_chain_join() -> None: +def test_ansible_audit_keeps_external_incident_id_in_json_not_bigint_column() -> None: decision_source = inspect.getsource(record_ansible_decision_audit) claim_source = inspect.getsource(claim_pending_check_modes) assert "operation_type, actor, status, incident_id" in decision_source assert "coalesce(incident_id::text, input ->> 'incident_id')" in decision_source assert "operation_type, actor, status, incident_id" in claim_source - assert "NULLIF(:incident_id, '')" in claim_source + assert "incident_db_id" in decision_source + assert "incident_db_id" in claim_source + assert "NULLIF(:incident_id, '')" not in decision_source + assert "NULLIF(:incident_id, '')" not in claim_source + assert _automation_operation_log_incident_id("INC-20260530-0E5C5C") is None + assert _automation_operation_log_incident_id("12345") == 12345 def test_ansible_transport_cooldown_uses_asyncpg_safe_interval_parameter() -> None: