feat(awooop): apply source correlation links
This commit is contained in:
@@ -15,6 +15,7 @@ from src.services.channel_event_dossier_service import (
|
||||
build_recurrence_work_item_dry_run,
|
||||
build_recurrence_work_item_handoff,
|
||||
build_recurrence_work_item_preview,
|
||||
build_source_correlation_apply,
|
||||
build_source_correlation_review_decision,
|
||||
fetch_channel_event_dossier,
|
||||
fetch_channel_event_dossier_coverage,
|
||||
@@ -509,6 +510,178 @@ def test_build_dossier_recurrence_closes_source_review_after_decision() -> None:
|
||||
assert item["work_item"]["next_step"] == "verify_source_match_in_status_chain"
|
||||
|
||||
|
||||
def test_source_correlation_apply_requires_accepted_review_and_plans_source_link() -> None:
|
||||
accepted_decision = {
|
||||
"schema_version": "awooop_source_correlation_review_decision_v1",
|
||||
"review_id": "review-1",
|
||||
"work_item_id": "source-evidence:sentry:received:issue-1",
|
||||
"decision": "accepted",
|
||||
"review_status": "accepted",
|
||||
"target_incident_id": "INC-20260520-ABC123",
|
||||
"reviewer_id": "operator_console",
|
||||
"latest_provider_event_id": "sentry:received:issue-1",
|
||||
"recorded_at": "2026-05-20T13:11:00",
|
||||
}
|
||||
recurrence = build_dossier_recurrence(
|
||||
[
|
||||
{
|
||||
"event_id": "event-1",
|
||||
"project_id": "awoooi",
|
||||
"channel_type": "internal",
|
||||
"provider_event_id": "sentry:received:issue-1",
|
||||
"content_hash": "a" * 64,
|
||||
"content_preview": "Sentry issue",
|
||||
"content_redacted": "Sentry issue",
|
||||
"redaction_version": "audit_sink_v1",
|
||||
"source_envelope": {
|
||||
"provider": "sentry",
|
||||
"stage": "received",
|
||||
"source_refs": {
|
||||
"sentry_issue_ids": ["issue-1"],
|
||||
"alert_ids": ["sentry:received:issue-1"],
|
||||
},
|
||||
"log_correlation": {
|
||||
"alertname": "Sentry Issue",
|
||||
"severity": "error",
|
||||
"namespace": "awoooi-prod",
|
||||
"target_resource": "web",
|
||||
"fingerprint": "fp-sentry-issue-1",
|
||||
},
|
||||
},
|
||||
"is_duplicate": False,
|
||||
"provider_ts": None,
|
||||
"received_at": "2026-05-20T13:10:00",
|
||||
"run_id": None,
|
||||
"run_state": None,
|
||||
"run_agent_id": None,
|
||||
}
|
||||
],
|
||||
project_id="awoooi",
|
||||
limit=20,
|
||||
source_review_decisions_by_work_item={
|
||||
"source-evidence:sentry:received:issue-1": accepted_decision,
|
||||
},
|
||||
)
|
||||
|
||||
apply_payload = build_source_correlation_apply(
|
||||
recurrence,
|
||||
work_item_id="source-evidence:sentry:received:issue-1",
|
||||
)
|
||||
|
||||
assert apply_payload["schema_version"] == "awooop_source_correlation_apply_v1"
|
||||
assert apply_payload["allowed"] is True
|
||||
assert apply_payload["apply_status"] == "ready_to_apply"
|
||||
assert apply_payload["target_incident_id"] == "INC-20260520-ABC123"
|
||||
assert apply_payload["writes_source_event"] is True
|
||||
assert apply_payload["writes_incident_state"] is False
|
||||
assert apply_payload["writes_auto_repair_result"] is False
|
||||
assert apply_payload["writes_ticket"] is False
|
||||
assert apply_payload["source_event_stage"] == "source_correlation_linked"
|
||||
assert apply_payload["source_event_provider_event_id"] == (
|
||||
"sentry:source_correlation_linked:issue-1"
|
||||
)
|
||||
assert apply_payload["plan"]["writes"] == [
|
||||
"awooop_conversation_event",
|
||||
"timeline_events",
|
||||
"alert_operation_log",
|
||||
]
|
||||
assert apply_payload["next_step"] == "verify_source_link_in_status_chain"
|
||||
|
||||
|
||||
def test_source_correlation_apply_blocks_without_accepted_review() -> None:
|
||||
recurrence = build_dossier_recurrence(
|
||||
[
|
||||
{
|
||||
"event_id": "event-1",
|
||||
"project_id": "awoooi",
|
||||
"channel_type": "internal",
|
||||
"provider_event_id": "sentry:received:issue-1",
|
||||
"content_hash": "a" * 64,
|
||||
"content_preview": "Sentry issue",
|
||||
"content_redacted": "Sentry issue",
|
||||
"redaction_version": "audit_sink_v1",
|
||||
"source_envelope": {
|
||||
"provider": "sentry",
|
||||
"stage": "received",
|
||||
"source_refs": {"sentry_issue_ids": ["issue-1"]},
|
||||
},
|
||||
"is_duplicate": False,
|
||||
"provider_ts": None,
|
||||
"received_at": "2026-05-20T13:10:00",
|
||||
"run_id": None,
|
||||
"run_state": None,
|
||||
"run_agent_id": None,
|
||||
}
|
||||
],
|
||||
project_id="awoooi",
|
||||
limit=20,
|
||||
)
|
||||
|
||||
apply_payload = build_source_correlation_apply(
|
||||
recurrence,
|
||||
work_item_id="source-evidence:sentry:received:issue-1",
|
||||
)
|
||||
|
||||
assert apply_payload["allowed"] is False
|
||||
assert apply_payload["executed"] is False
|
||||
assert apply_payload["writes_source_event"] is False
|
||||
assert apply_payload["apply_status"] == "blocked"
|
||||
assert apply_payload["plan"]["writes"] == []
|
||||
assert apply_payload["next_step"] == "fix_preflight_checks"
|
||||
|
||||
|
||||
def test_build_dossier_recurrence_surfaces_source_apply_history() -> None:
|
||||
apply_record = {
|
||||
"schema_version": "awooop_source_correlation_apply_v1",
|
||||
"apply_id": "apply-1",
|
||||
"work_item_id": "source-evidence:sentry:received:issue-1",
|
||||
"apply_status": "applied",
|
||||
"target_incident_id": "INC-20260520-ABC123",
|
||||
"review_id": "review-1",
|
||||
"source_event_id": "source-event-1",
|
||||
"source_event_provider_event_id": (
|
||||
"sentry:source_correlation_linked:issue-1"
|
||||
),
|
||||
"recorded_at": "2026-05-20T13:12:00",
|
||||
}
|
||||
recurrence = build_dossier_recurrence(
|
||||
[
|
||||
{
|
||||
"event_id": "event-1",
|
||||
"project_id": "awoooi",
|
||||
"channel_type": "internal",
|
||||
"provider_event_id": "sentry:received:issue-1",
|
||||
"content_hash": "a" * 64,
|
||||
"content_preview": "Sentry issue",
|
||||
"content_redacted": "Sentry issue",
|
||||
"redaction_version": "audit_sink_v1",
|
||||
"source_envelope": {
|
||||
"provider": "sentry",
|
||||
"stage": "received",
|
||||
"source_refs": {"sentry_issue_ids": ["issue-1"]},
|
||||
},
|
||||
"is_duplicate": False,
|
||||
"provider_ts": None,
|
||||
"received_at": "2026-05-20T13:10:00",
|
||||
"run_id": None,
|
||||
"run_state": None,
|
||||
"run_agent_id": None,
|
||||
}
|
||||
],
|
||||
project_id="awoooi",
|
||||
limit=20,
|
||||
source_applies_by_work_item={
|
||||
"source-evidence:sentry:received:issue-1": apply_record,
|
||||
},
|
||||
)
|
||||
|
||||
item = recurrence["items"][0]
|
||||
assert recurrence["summary"]["source_correlation_applied_group_total"] == 1
|
||||
assert item["source_correlation_apply"] == apply_record
|
||||
assert item["work_item"]["matched_incident_id"] == "INC-20260520-ABC123"
|
||||
assert item["work_item"]["next_step"] == "verify_source_link_in_status_chain"
|
||||
|
||||
|
||||
def test_build_dossier_recurrence_opens_work_item_for_completed_run_without_repair() -> None:
|
||||
recurrence = build_dossier_recurrence(
|
||||
[
|
||||
@@ -760,6 +933,101 @@ async def test_source_correlation_dry_run_history_records_without_incident(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_source_correlation_apply_history_appends_source_event_and_audit(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
source_calls: list[dict[str, object]] = []
|
||||
timeline_calls: list[dict[str, object]] = []
|
||||
alert_calls: list[dict[str, object]] = []
|
||||
|
||||
async def fake_record_external_alert_event(**kwargs): # noqa: ANN003
|
||||
source_calls.append(kwargs)
|
||||
return UUID("99999999-9999-4999-8999-999999999999")
|
||||
|
||||
class FakeTimeline:
|
||||
async def add_event(self, **kwargs): # noqa: ANN001
|
||||
timeline_calls.append(kwargs)
|
||||
return {"id": "timeline-1"}
|
||||
|
||||
class FakeRecord:
|
||||
id = "alert-op-1"
|
||||
|
||||
class FakeRepo:
|
||||
async def append(self, event_type: str, **kwargs): # noqa: ANN001
|
||||
alert_calls.append({"event_type": event_type, **kwargs})
|
||||
return FakeRecord()
|
||||
|
||||
from src.repositories import alert_operation_log_repository
|
||||
from src.services import approval_db, channel_hub
|
||||
|
||||
monkeypatch.setattr(
|
||||
channel_hub,
|
||||
"record_external_alert_event",
|
||||
fake_record_external_alert_event,
|
||||
)
|
||||
monkeypatch.setattr(approval_db, "get_timeline_service", lambda: FakeTimeline())
|
||||
monkeypatch.setattr(
|
||||
alert_operation_log_repository,
|
||||
"get_alert_operation_log_repository",
|
||||
lambda: FakeRepo(),
|
||||
)
|
||||
|
||||
history = await channel_event_dossier_service._record_source_correlation_apply_history(
|
||||
{
|
||||
"schema_version": "awooop_source_correlation_apply_v1",
|
||||
"source": "channel_event_dossier.recurrence",
|
||||
"project_id": "awoooi",
|
||||
"work_item_id": "source-evidence:sentry:received:issue-1",
|
||||
"review_id": "review-1",
|
||||
"target_incident_id": "INC-20260520-ABC123",
|
||||
"reviewer_id": "operator_console",
|
||||
"operator_note": "matches incident",
|
||||
"allowed": True,
|
||||
"apply_status": "ready_to_apply",
|
||||
"safety_level": "source_review_append_only_apply",
|
||||
"writes_incident_state": False,
|
||||
"writes_source_event": True,
|
||||
"writes_auto_repair_result": False,
|
||||
"writes_ticket": False,
|
||||
"creates_external_ticket": False,
|
||||
"latest_provider_event_id": "sentry:received:issue-1",
|
||||
"source_event_provider_event_id": (
|
||||
"sentry:source_correlation_linked:issue-1"
|
||||
),
|
||||
"source_event_stage": "source_correlation_linked",
|
||||
"raw_event_id": "issue-1",
|
||||
"provider": "sentry",
|
||||
"alertname": "Sentry Issue",
|
||||
"severity": "error",
|
||||
"namespace": "awoooi-prod",
|
||||
"target_resource": "web",
|
||||
"fingerprint": "fp-sentry-issue-1",
|
||||
"checks": [],
|
||||
"current_state_summary": {},
|
||||
"plan": {"writes": ["awooop_conversation_event"]},
|
||||
"read_model_route": {},
|
||||
"next_step": "verify_source_link_in_status_chain",
|
||||
}
|
||||
)
|
||||
|
||||
assert history == {
|
||||
"recorded": True,
|
||||
"source_event_id": "99999999-9999-4999-8999-999999999999",
|
||||
"alert_operation_id": "alert-op-1",
|
||||
"timeline_event_id": "timeline-1",
|
||||
}
|
||||
assert source_calls[0]["stage"] == "source_correlation_linked"
|
||||
assert source_calls[0]["incident_id"] == "INC-20260520-ABC123"
|
||||
assert source_calls[0]["event_id"] == "issue-1"
|
||||
assert timeline_calls[0]["incident_id"] == "INC-20260520-ABC123"
|
||||
assert alert_calls[0]["actor"] == "awooop_source_correlation_apply_service"
|
||||
assert alert_calls[0]["context"]["schema_version"] == (
|
||||
"awooop_source_correlation_apply_v1"
|
||||
)
|
||||
assert alert_calls[0]["context"]["apply_status"] == "applied"
|
||||
|
||||
|
||||
def test_build_recurrence_work_item_handoff_records_ticket_proposal_contract() -> None:
|
||||
recurrence = build_dossier_recurrence(
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user