feat(awooop): show callback evidence capture status
All checks were successful
CD Pipeline / tests (push) Successful in 1m5s
Code Review / ai-code-review (push) Successful in 14s
CD Pipeline / build-and-deploy (push) Successful in 4m30s
CD Pipeline / post-deploy-checks (push) Successful in 1m55s

This commit is contained in:
Your Name
2026-05-25 10:54:39 +08:00
parent 1c8ebdf283
commit 04684eef5f
6 changed files with 299 additions and 6 deletions

View File

@@ -98,6 +98,7 @@ class CallbackReplyItem(BaseModel):
persisted_awooop_status_chain: dict[str, Any] | None = None
km_stale_completion_summary: dict[str, Any] | None = None
persisted_km_stale_completion_summary: dict[str, Any] | None = None
evidence_capture_status: dict[str, Any] | None = None
run_detail_href: str | None = None

View File

@@ -107,6 +107,7 @@ _SOURCE_CORRELATION_PRE_WINDOW_HOURS = 2
_KM_STALE_COMPLETION_CALLBACK_SCHEMA_VERSION = (
"km_stale_owner_review_completion_callback_summary_v1"
)
_CALLBACK_EVIDENCE_CAPTURE_STATUS_SCHEMA_VERSION = "callback_evidence_capture_status_v1"
# =============================================================================
# Tenants
@@ -1027,6 +1028,56 @@ def _callback_reply_public_status(callback_reply: dict[str, Any]) -> str:
}.get(raw_status, "observed")
def _callback_reply_evidence_capture_status(
*,
callback_reply: Mapping[str, Any],
persisted_awooop_status_chain: dict[str, Any] | None,
persisted_km_stale_completion_summary: dict[str, Any] | None,
event_at: Any,
) -> dict[str, Any]:
"""Explain whether callback-time evidence snapshots were persisted."""
captured: list[str] = []
missing: list[str] = []
if persisted_awooop_status_chain:
captured.append("awooop_status_chain")
else:
missing.append("awooop_status_chain")
if persisted_km_stale_completion_summary:
captured.append("km_stale_completion_summary")
else:
missing.append("km_stale_completion_summary")
if not missing:
status_value = "captured"
reason = "ok"
next_action = "none"
elif captured:
status_value = "partial"
reason = "partial_snapshot_rollout_transition"
next_action = "press_telegram_detail_or_history_after_rollout"
else:
status_value = "not_captured"
raw_status = str(callback_reply.get("status") or "")
reason = (
"callback_reply_delivery_failed_snapshot_missing"
if raw_status == "callback_reply_failed"
else "legacy_callback_before_snapshot_rollout"
)
next_action = "press_telegram_detail_or_history_after_rollout"
return {
"schema_version": _CALLBACK_EVIDENCE_CAPTURE_STATUS_SCHEMA_VERSION,
"status": status_value,
"reason": reason,
"action": str(callback_reply.get("action") or "").strip() or None,
"captured": captured,
"missing": missing,
"snapshot_rollout": "t167_t169",
"next_action": next_action,
"event_at": event_at,
}
def _callback_reply_event_item(row: Mapping[str, Any]) -> dict[str, Any]:
"""Convert one callback reply outbound row into a read-only evidence item."""
callback_reply = _as_dict(row.get("callback_reply"))
@@ -1036,6 +1087,12 @@ def _callback_reply_event_item(row: Mapping[str, Any]) -> dict[str, Any]:
run_id = row.get("run_id")
status_value = _callback_reply_public_status(callback_reply)
event_at = row.get("sent_at") or row.get("queued_at")
persisted_awooop_status_chain = _as_dict(
row.get("persisted_awooop_status_chain"),
) or None
persisted_km_stale_completion_summary = _as_dict(
row.get("persisted_km_stale_completion_summary"),
) or None
return {
"message_id": row.get("message_id"),
@@ -1057,12 +1114,18 @@ def _callback_reply_event_item(row: Mapping[str, Any]) -> dict[str, Any]:
"agent_id": row.get("agent_id"),
"run_created_at": row.get("run_created_at"),
"callback_reply": callback_reply,
"persisted_awooop_status_chain": _as_dict(
row.get("persisted_awooop_status_chain"),
) or None,
"persisted_km_stale_completion_summary": _as_dict(
row.get("persisted_km_stale_completion_summary"),
) or None,
"persisted_awooop_status_chain": persisted_awooop_status_chain,
"persisted_km_stale_completion_summary": (
persisted_km_stale_completion_summary
),
"evidence_capture_status": _callback_reply_evidence_capture_status(
callback_reply=callback_reply,
persisted_awooop_status_chain=persisted_awooop_status_chain,
persisted_km_stale_completion_summary=(
persisted_km_stale_completion_summary
),
event_at=event_at,
),
"run_detail_href": (
f"/awooop/runs/{run_id}?project_id={project_id}"
if run_id and project_id

View File

@@ -407,6 +407,56 @@ def test_callback_reply_event_item_surfaces_run_link_and_human_flag() -> None:
assert item["persisted_awooop_status_chain"]["repair_state"] == (
"blocked_manual_required"
)
assert item["evidence_capture_status"]["status"] == "captured"
assert item["evidence_capture_status"]["captured"] == [
"awooop_status_chain",
"km_stale_completion_summary",
]
assert item["evidence_capture_status"]["missing"] == []
assert item["evidence_capture_status"]["next_action"] == "none"
def test_callback_reply_event_item_marks_legacy_snapshot_missing() -> None:
run_id = UUID("5c0306e0-591a-5445-9a33-80f499426b38")
message_id = UUID("56cdb6ad-46a4-48f5-9d3b-b1ac9c0b2e92")
item = _callback_reply_event_item({
"message_id": message_id,
"run_id": run_id,
"project_id": "awoooi",
"channel_type": "telegram",
"message_type": "final",
"send_status": "sent",
"send_error": None,
"provider_message_id": "123",
"queued_at": datetime(2026, 5, 18, 7, 31, 37),
"sent_at": datetime(2026, 5, 18, 7, 31, 38),
"triggered_by_state": "callback_reply",
"content_preview": "事件詳情",
"run_state": "completed",
"agent_id": "legacy-telegram-gateway",
"run_created_at": datetime(2026, 5, 18, 7, 30, 0),
"callback_reply": {
"status": "callback_reply_sent",
"action": "detail",
"incident_id": "INC-20260513-79ED5E",
},
"persisted_awooop_status_chain": None,
"persisted_km_stale_completion_summary": None,
})
capture_status = item["evidence_capture_status"]
assert capture_status["schema_version"] == "callback_evidence_capture_status_v1"
assert capture_status["status"] == "not_captured"
assert capture_status["reason"] == "legacy_callback_before_snapshot_rollout"
assert capture_status["missing"] == [
"awooop_status_chain",
"km_stale_completion_summary",
]
assert capture_status["captured"] == []
assert capture_status["next_action"] == (
"press_telegram_detail_or_history_after_rollout"
)
def test_list_callback_replies_response_preserves_callback_evidence() -> None:
@@ -513,6 +563,20 @@ def test_list_callback_replies_response_preserves_callback_evidence() -> None:
"automation_state": "manual_owner_review_required",
},
},
"evidence_capture_status": {
"schema_version": "callback_evidence_capture_status_v1",
"status": "captured",
"reason": "ok",
"action": "detail",
"captured": [
"awooop_status_chain",
"km_stale_completion_summary",
],
"missing": [],
"snapshot_rollout": "t167_t169",
"next_action": "none",
"event_at": datetime(2026, 5, 18, 7, 31, 37),
},
"run_detail_href": (
"/awooop/runs/5c0306e0-591a-5445-9a33-80f499426b38"
"?project_id=awoooi"
@@ -538,6 +602,7 @@ def test_list_callback_replies_response_preserves_callback_evidence() -> None:
assert dumped["items"][0]["persisted_km_stale_completion_summary"]["triage"][
"ai_lead_agent"
] == "Hermes"
assert dumped["items"][0]["evidence_capture_status"]["status"] == "captured"
assert dumped["items"][0]["run_detail_href"].endswith("project_id=awoooi")