diff --git a/apps/api/src/services/ai_agent_autonomous_runtime_control.py b/apps/api/src/services/ai_agent_autonomous_runtime_control.py index 5c3e47311..389fa3087 100644 --- a/apps/api/src/services/ai_agent_autonomous_runtime_control.py +++ b/apps/api/src/services/ai_agent_autonomous_runtime_control.py @@ -5687,6 +5687,16 @@ _RUNTIME_OPERATION_COUNTS_DIRECT_SQL = """ _RUNTIME_OPERATION_LATEST_SQL = """ + WITH latest_apply_chain AS ( + SELECT + op_id::text AS apply_op_id, + parent_op_id::text AS check_mode_op_id, + input ->> 'source_candidate_op_id' AS candidate_op_id + FROM automation_operation_log + WHERE operation_type = 'ansible_apply_executed' + ORDER BY created_at DESC + LIMIT 1 + ) SELECT op_id::text AS op_id, parent_op_id::text AS parent_op_id, @@ -5714,7 +5724,7 @@ _RUNTIME_OPERATION_LATEST_SQL = """ coalesce(output ->> 'returncode', dry_run_result ->> 'returncode') AS returncode, duration_ms, created_at - FROM automation_operation_log + FROM automation_operation_log operation_row WHERE ( operation_type IN ( 'ansible_candidate_matched', @@ -5732,12 +5742,19 @@ _RUNTIME_OPERATION_LATEST_SQL = """ ) ORDER BY CASE + WHEN EXISTS ( + SELECT 1 + FROM latest_apply_chain + WHERE operation_row.op_id::text = latest_apply_chain.apply_op_id + OR operation_row.op_id::text = latest_apply_chain.check_mode_op_id + OR operation_row.op_id::text = latest_apply_chain.candidate_op_id + ) THEN 0 WHEN operation_type IN ( 'ansible_candidate_matched', 'ansible_check_mode_executed', 'ansible_apply_executed' - ) THEN 0 - ELSE 1 + ) THEN 1 + ELSE 2 END, created_at DESC LIMIT :limit @@ -5745,6 +5762,16 @@ _RUNTIME_OPERATION_LATEST_SQL = """ _RUNTIME_OPERATION_LATEST_DIRECT_SQL = """ + WITH latest_apply_chain AS ( + SELECT + op_id::text AS apply_op_id, + parent_op_id::text AS check_mode_op_id, + input ->> 'source_candidate_op_id' AS candidate_op_id + FROM automation_operation_log + WHERE operation_type = 'ansible_apply_executed' + ORDER BY created_at DESC + LIMIT 1 + ) SELECT op_id::text AS op_id, parent_op_id::text AS parent_op_id, @@ -5772,7 +5799,7 @@ _RUNTIME_OPERATION_LATEST_DIRECT_SQL = """ coalesce(output ->> 'returncode', dry_run_result ->> 'returncode') AS returncode, duration_ms, created_at - FROM automation_operation_log + FROM automation_operation_log operation_row WHERE ( operation_type IN ( 'ansible_candidate_matched', @@ -5790,12 +5817,19 @@ _RUNTIME_OPERATION_LATEST_DIRECT_SQL = """ ) ORDER BY CASE + WHEN EXISTS ( + SELECT 1 + FROM latest_apply_chain + WHERE operation_row.op_id::text = latest_apply_chain.apply_op_id + OR operation_row.op_id::text = latest_apply_chain.check_mode_op_id + OR operation_row.op_id::text = latest_apply_chain.candidate_op_id + ) THEN 0 WHEN operation_type IN ( 'ansible_candidate_matched', 'ansible_check_mode_executed', 'ansible_apply_executed' - ) THEN 0 - ELSE 1 + ) THEN 1 + ELSE 2 END, created_at DESC LIMIT $1 diff --git a/apps/api/tests/test_ai_agent_autonomous_runtime_control.py b/apps/api/tests/test_ai_agent_autonomous_runtime_control.py index db5e24d61..d73fa5776 100644 --- a/apps/api/tests/test_ai_agent_autonomous_runtime_control.py +++ b/apps/api/tests/test_ai_agent_autonomous_runtime_control.py @@ -7,6 +7,8 @@ from src.services.ai_agent_autonomous_runtime_control import ( _RUNTIME_ALERT_OPERATION_COUNTS_SQL, _RUNTIME_ALERTMANAGER_EVENT_COUNTS_SQL, _RUNTIME_GROUPED_ALERT_EVENT_COUNTS_SQL, + _RUNTIME_OPERATION_LATEST_DIRECT_SQL, + _RUNTIME_OPERATION_LATEST_SQL, _RUNTIME_PLAYBOOK_TRUST_COUNTS_FALLBACK_SQL, _RUNTIME_TELEGRAM_COUNTS_SQL, _RUNTIME_TELEGRAM_LATEST_SQL, @@ -2360,6 +2362,14 @@ def test_runtime_telegram_receipt_queries_include_alert_notifications() -> None: assert "controlled_apply_result" in _RUNTIME_TELEGRAM_LATEST_SQL +def test_runtime_operation_latest_queries_prioritize_latest_apply_chain() -> None: + for sql in (_RUNTIME_OPERATION_LATEST_SQL, _RUNTIME_OPERATION_LATEST_DIRECT_SQL): + assert "WITH latest_apply_chain AS" in sql + assert "operation_row.op_id::text = latest_apply_chain.apply_op_id" in sql + assert "operation_row.op_id::text = latest_apply_chain.check_mode_op_id" in sql + assert "operation_row.op_id::text = latest_apply_chain.candidate_op_id" in sql + + def test_runtime_execution_loop_ledger_does_not_mix_unrelated_check_mode_rows(): apply_op_id = "db3f12ce-08fc-4289-8c93-338305d5850c" readback = build_runtime_receipt_readback_from_rows(