fix(ai-loop): validate durable Wazuh closure receipts
This commit is contained in:
@@ -3580,13 +3580,24 @@ def test_runtime_telegram_receipt_queries_include_alert_notifications() -> None:
|
||||
assert "controlled_apply_result" in _RUNTIME_TELEGRAM_LATEST_SQL
|
||||
assert "PARTITION BY CASE" in _RUNTIME_TELEGRAM_LATEST_SQL
|
||||
assert "receipt_kind_rank <= :limit" in _RUNTIME_TELEGRAM_LATEST_SQL
|
||||
for sql in (
|
||||
_RUNTIME_TELEGRAM_LATEST_SQL,
|
||||
runtime_control_module._RUNTIME_TELEGRAM_LATEST_DIRECT_SQL,
|
||||
runtime_control_module._RUNTIME_RETRY_TERMINAL_LATEST_SQL,
|
||||
):
|
||||
assert "notification_lifecycle_state" in sql
|
||||
assert "notification_suppression_reason" in sql
|
||||
retry_sql = runtime_control_module._RUNTIME_RETRY_TERMINAL_LATEST_SQL
|
||||
assert "notification_execution_kind" in retry_sql
|
||||
assert "{callback_reply,apply_op_id}" in retry_sql
|
||||
assert "{callback_reply,incident_id}" in retry_sql
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("catalog_id", "shadow_completes"),
|
||||
[
|
||||
("ansible:generic-posture-readback", True),
|
||||
("ansible:wazuh-manager-posture-readback", False),
|
||||
("ansible:wazuh-manager-posture-readback", True),
|
||||
],
|
||||
)
|
||||
def test_same_run_suppressed_shadow_respects_provider_receipt_policy(
|
||||
@@ -3658,6 +3669,8 @@ def test_same_run_suppressed_shadow_respects_provider_receipt_policy(
|
||||
),
|
||||
"notification_disposition": "suppressed",
|
||||
"notification_provider_delivery": "state_transition",
|
||||
"notification_lifecycle_state": "recovered",
|
||||
"notification_suppression_reason": "duplicate_lifecycle_state",
|
||||
"notification_provider_send_suppressed": True,
|
||||
}
|
||||
],
|
||||
@@ -3677,6 +3690,235 @@ def test_same_run_suppressed_shadow_respects_provider_receipt_policy(
|
||||
assert telegram_stage["run_id_matches_expected"] is shadow_completes
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"send_status",
|
||||
"policy_version",
|
||||
"provider_delivery",
|
||||
"lifecycle_state",
|
||||
"suppression_reason",
|
||||
"expected",
|
||||
),
|
||||
[
|
||||
(
|
||||
"shadow",
|
||||
"telegram_posture_lifecycle_transition_v1",
|
||||
"state_transition",
|
||||
"recovered",
|
||||
"duplicate_lifecycle_state",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"shadow",
|
||||
"telegram_posture_state_digest_v1",
|
||||
"shadow_only",
|
||||
"recovered",
|
||||
"historical_projection_backfill",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"shadow",
|
||||
"telegram_posture_state_digest_v1",
|
||||
"digest",
|
||||
"failed",
|
||||
"duplicate_failure_fingerprint_window",
|
||||
False,
|
||||
),
|
||||
(
|
||||
"shadow",
|
||||
"telegram_posture_lifecycle_transition_v1",
|
||||
"state_transition",
|
||||
"recovered",
|
||||
"cooldown_active",
|
||||
False,
|
||||
),
|
||||
(
|
||||
"sent",
|
||||
"telegram_posture_lifecycle_transition_v1",
|
||||
"state_transition",
|
||||
"recovered",
|
||||
"",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"sent",
|
||||
"telegram_posture_lifecycle_transition_v1",
|
||||
"state_transition",
|
||||
"failed",
|
||||
"",
|
||||
False,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_wazuh_apply_receipt_requires_exact_recovered_lifecycle_provenance(
|
||||
send_status: str,
|
||||
policy_version: str,
|
||||
provider_delivery: str,
|
||||
lifecycle_state: str,
|
||||
suppression_reason: str,
|
||||
expected: bool,
|
||||
) -> None:
|
||||
assert runtime_control_module._durable_controlled_apply_telegram_receipt(
|
||||
{
|
||||
"action": "controlled_apply_result",
|
||||
"send_status": send_status,
|
||||
"provider_message_id": "42" if send_status == "sent" else None,
|
||||
"notification_policy_version": policy_version,
|
||||
"notification_disposition": (
|
||||
"sent" if send_status == "sent" else "suppressed"
|
||||
),
|
||||
"notification_provider_delivery": provider_delivery,
|
||||
"notification_lifecycle_state": lifecycle_state,
|
||||
"notification_suppression_reason": suppression_reason,
|
||||
"notification_provider_send_suppressed": send_status == "shadow",
|
||||
},
|
||||
catalog_id="ansible:wazuh-manager-posture-readback",
|
||||
) is expected
|
||||
|
||||
|
||||
def test_wazuh_duplicate_lifecycle_shadow_closes_strict_same_run_eighteen_stage_loop() -> (
|
||||
None
|
||||
):
|
||||
automation_run_id = "wazuh-candidate-op"
|
||||
incident_id = "INC-WAZUH-SHADOW-STRICT-CLOSURE"
|
||||
apply_op_id = "wazuh-apply-op"
|
||||
catalog_id = "ansible:wazuh-manager-posture-readback"
|
||||
non_execution_stages = set(AI_AUTOMATION_REQUIRED_LOOP_STAGES) - set(
|
||||
AI_AUTOMATION_EXECUTION_CORRELATED_STAGES
|
||||
)
|
||||
|
||||
def stage_receipt(stage_id: str) -> dict[str, object]:
|
||||
receipt: dict[str, object] = {
|
||||
"schema_version": "ai_automation_stage_receipt_v1",
|
||||
"stage_id": stage_id,
|
||||
"automation_run_id": automation_run_id,
|
||||
"evidence_ref": f"receipt:{stage_id}",
|
||||
"durable_receipt": True,
|
||||
}
|
||||
if stage_id == "rag_writeback":
|
||||
receipt["detail"] = {
|
||||
"durable_write_acknowledged": True,
|
||||
"source_id": f"ansible-learning:{apply_op_id}",
|
||||
"rag_chunk_count": 1,
|
||||
"chunk_index_verified": True,
|
||||
}
|
||||
elif stage_id == "playbook_trust":
|
||||
receipt["detail"] = {
|
||||
"schema_version": "ansible_playbook_trust_writeback_v2",
|
||||
"identity_schema_version": "ansible_playbook_identity_v1",
|
||||
"identity_fingerprint": "a" * 64,
|
||||
"canonical_playbook_id": "PB-WAZUH-MANAGER-POSTURE",
|
||||
"playbook_row_version": 1,
|
||||
"playbook_row_updated_at": "2026-07-22T12:00:00+00:00",
|
||||
"playbook_row_fingerprint": "b" * 64,
|
||||
"trust_score": 1.0,
|
||||
"trust_observation_count": 1,
|
||||
"learning_recorded": True,
|
||||
"trust_updated": True,
|
||||
"repository_write_acknowledged": True,
|
||||
"repository_readback_verified": True,
|
||||
"operation_receipt_readback_verified": True,
|
||||
"durable_write_acknowledged": True,
|
||||
"writer_source_sha": "6b87cf6d7",
|
||||
"raw_log_payload_stored": False,
|
||||
"secret_value_stored": False,
|
||||
}
|
||||
return receipt
|
||||
|
||||
readback = build_runtime_receipt_readback_from_rows(
|
||||
operation_latest_rows=[
|
||||
{
|
||||
"op_id": automation_run_id,
|
||||
"operation_type": "ansible_candidate_matched",
|
||||
"status": "dry_run",
|
||||
"automation_run_id": automation_run_id,
|
||||
"incident_id": incident_id,
|
||||
"catalog_id": catalog_id,
|
||||
},
|
||||
{
|
||||
"op_id": "wazuh-check-op",
|
||||
"parent_op_id": automation_run_id,
|
||||
"operation_type": "ansible_check_mode_executed",
|
||||
"status": "success",
|
||||
"automation_run_id": automation_run_id,
|
||||
"incident_id": incident_id,
|
||||
"catalog_id": catalog_id,
|
||||
"returncode": "0",
|
||||
},
|
||||
{
|
||||
"op_id": apply_op_id,
|
||||
"parent_op_id": "wazuh-check-op",
|
||||
"source_candidate_op_id": automation_run_id,
|
||||
"check_mode_op_id": "wazuh-check-op",
|
||||
"operation_type": "ansible_apply_executed",
|
||||
"status": "success",
|
||||
"automation_run_id": automation_run_id,
|
||||
"incident_id": incident_id,
|
||||
"catalog_id": catalog_id,
|
||||
"returncode": "0",
|
||||
"runtime_stage_receipts": [
|
||||
stage_receipt(stage_id) for stage_id in non_execution_stages
|
||||
],
|
||||
},
|
||||
],
|
||||
auto_repair_latest_rows=[
|
||||
{
|
||||
"id": "wazuh-auto-repair",
|
||||
"incident_id": incident_id,
|
||||
"catalog_id": catalog_id,
|
||||
"result_status": "success",
|
||||
"automation_run_id": automation_run_id,
|
||||
"executed_steps_text": f'["apply:{apply_op_id}"]',
|
||||
}
|
||||
],
|
||||
verifier_latest_rows=[
|
||||
{
|
||||
"id": "wazuh-verifier",
|
||||
"incident_id": incident_id,
|
||||
"apply_op_id": apply_op_id,
|
||||
"automation_run_id": automation_run_id,
|
||||
"verification_result": "success",
|
||||
}
|
||||
],
|
||||
km_latest_rows=[
|
||||
{
|
||||
"id": "wazuh-km",
|
||||
"related_incident_id": incident_id,
|
||||
"path_type": f"ansible_apply_receipt:{apply_op_id[:8]}",
|
||||
"automation_run_id": automation_run_id,
|
||||
"status": "linked",
|
||||
}
|
||||
],
|
||||
telegram_latest_rows=[
|
||||
{
|
||||
"message_id": "wazuh-telegram-shadow",
|
||||
"automation_run_id": automation_run_id,
|
||||
"incident_id": incident_id,
|
||||
"action": "controlled_apply_result",
|
||||
"send_status": "shadow",
|
||||
"provider_message_id": None,
|
||||
"notification_policy_version": (
|
||||
"telegram_posture_lifecycle_transition_v1"
|
||||
),
|
||||
"notification_disposition": "suppressed",
|
||||
"notification_provider_delivery": "state_transition",
|
||||
"notification_lifecycle_state": "recovered",
|
||||
"notification_suppression_reason": "duplicate_lifecycle_state",
|
||||
"notification_provider_send_suppressed": True,
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
strict = build_ai_agent_strict_runtime_completion_from_readback(readback)
|
||||
|
||||
assert strict["required_stage_count"] == 18
|
||||
assert strict["present_stage_count"] == 18
|
||||
assert strict["missing_stage_ids"] == []
|
||||
assert strict["full_trace_same_run_correlation_proven"] is True
|
||||
assert strict["completion_percent"] == 100
|
||||
assert strict["closed"] is True
|
||||
|
||||
|
||||
def test_unclassified_shadow_cannot_satisfy_telegram_receipt() -> None:
|
||||
assert runtime_control_module._durable_controlled_apply_telegram_receipt(
|
||||
{
|
||||
@@ -4173,6 +4415,113 @@ def test_retry_terminal_accepts_policy_complete_suppressed_receipt() -> None:
|
||||
assert terminal["controls"]["telegram_receipt_same_run"] is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"execution_kind",
|
||||
"send_status",
|
||||
"policy_version",
|
||||
"provider_delivery",
|
||||
"lifecycle_state",
|
||||
"suppression_reason",
|
||||
"expected_closed",
|
||||
),
|
||||
[
|
||||
(
|
||||
"no_write_posture_readback",
|
||||
"sent",
|
||||
"telegram_posture_lifecycle_transition_v1",
|
||||
"state_transition",
|
||||
"failed",
|
||||
"",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"no_write_posture_readback",
|
||||
"shadow",
|
||||
"telegram_posture_lifecycle_transition_v1",
|
||||
"state_transition",
|
||||
"failed",
|
||||
"duplicate_lifecycle_state",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"no_write_replay",
|
||||
"sent",
|
||||
"telegram_failure_digest_v2",
|
||||
"digest",
|
||||
"failed",
|
||||
"",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"no_write_replay",
|
||||
"shadow",
|
||||
"telegram_failure_digest_v2",
|
||||
"digest",
|
||||
"failed",
|
||||
"duplicate_failure_fingerprint_window",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"no_write_replay",
|
||||
"shadow",
|
||||
"telegram_failure_digest_v2",
|
||||
"shadow_only",
|
||||
"failed",
|
||||
"historical_projection_backfill",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"no_write_posture_readback",
|
||||
"shadow",
|
||||
"telegram_posture_lifecycle_transition_v1",
|
||||
"state_transition",
|
||||
"recovered",
|
||||
"duplicate_lifecycle_state",
|
||||
False,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_wazuh_retry_terminal_requires_exact_failed_lifecycle_receipt(
|
||||
execution_kind: str,
|
||||
send_status: str,
|
||||
policy_version: str,
|
||||
provider_delivery: str,
|
||||
lifecycle_state: str,
|
||||
suppression_reason: str,
|
||||
expected_closed: bool,
|
||||
) -> None:
|
||||
row = _closed_retry_terminal_row()
|
||||
row.update(
|
||||
{
|
||||
"catalog_id": "ansible:wazuh-manager-posture-readback",
|
||||
"telegram_send_status": send_status,
|
||||
"telegram_provider_message_id": (
|
||||
"wazuh-provider-message" if send_status == "sent" else None
|
||||
),
|
||||
"telegram_notification_execution_kind": execution_kind,
|
||||
"telegram_notification_policy_version": policy_version,
|
||||
"telegram_notification_disposition": (
|
||||
"sent" if send_status == "sent" else "suppressed"
|
||||
),
|
||||
"telegram_notification_provider_delivery": provider_delivery,
|
||||
"telegram_notification_lifecycle_state": lifecycle_state,
|
||||
"telegram_notification_suppression_reason": suppression_reason,
|
||||
"telegram_provider_send_suppressed": send_status == "shadow",
|
||||
}
|
||||
)
|
||||
|
||||
terminal = build_runtime_receipt_readback_from_rows(
|
||||
retry_terminal_latest_rows=[row]
|
||||
)["autonomous_retry_rollback_terminal"]
|
||||
|
||||
assert terminal["closed"] is expected_closed
|
||||
assert terminal["controls"]["telegram_receipt_same_run"] is expected_closed
|
||||
assert (
|
||||
"telegram_receipt_same_run_not_verified" in terminal["active_blockers"]
|
||||
) is not expected_closed
|
||||
|
||||
|
||||
def test_retry_rollback_terminal_fails_closed_on_run_mismatch_or_missing_proof():
|
||||
row = _closed_retry_terminal_row()
|
||||
row["telegram_automation_run_id"] = "different-run"
|
||||
|
||||
Reference in New Issue
Block a user