feat(governance): surface callback owner review work items
This commit is contained in:
@@ -16,6 +16,7 @@ from collections import defaultdict
|
||||
from collections.abc import Mapping
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from typing import Any, get_args
|
||||
from urllib.parse import urlencode
|
||||
from uuid import UUID
|
||||
|
||||
import httpx
|
||||
@@ -1087,6 +1088,12 @@ def _empty_km_stale_completion_summary(
|
||||
"items_truncated": False,
|
||||
"related_total": 0,
|
||||
"related_items": [],
|
||||
"work_item": _km_stale_callback_owner_review_work_item(
|
||||
project_id=project_id,
|
||||
incident_id=incident_id,
|
||||
status_value=status_value,
|
||||
reason=reason,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -1157,6 +1164,52 @@ def _build_km_stale_completion_summary(
|
||||
"items_truncated": total > returned,
|
||||
"related_total": len(related_items),
|
||||
"related_items": related_items[:3],
|
||||
"work_item": _km_stale_callback_owner_review_work_item(
|
||||
project_id=project_id,
|
||||
incident_id=incident_id,
|
||||
status_value=(
|
||||
"matched_owner_review"
|
||||
if related_items
|
||||
else "no_related_owner_review"
|
||||
),
|
||||
reason=None if related_items else "no_matching_completion_item",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _km_stale_callback_owner_review_work_item(
|
||||
*,
|
||||
project_id: str,
|
||||
incident_id: str | None,
|
||||
status_value: str,
|
||||
reason: str | None,
|
||||
) -> dict[str, Any] | None:
|
||||
"""Generate a read-only Work Items link for callback evidence gaps."""
|
||||
if not incident_id or status_value != "no_related_owner_review":
|
||||
return None
|
||||
work_item_id = f"km-callback-owner-review:{project_id}:{incident_id}"
|
||||
target_query = urlencode(
|
||||
{
|
||||
"project_id": project_id,
|
||||
"incident_id": incident_id,
|
||||
"callback_reply_status": "sent",
|
||||
}
|
||||
)
|
||||
return {
|
||||
"schema_version": "km_stale_callback_owner_review_work_item_v1",
|
||||
"work_item_id": work_item_id,
|
||||
"kind": "km_stale_callback_owner_review",
|
||||
"status": "open",
|
||||
"project_id": project_id,
|
||||
"incident_id": incident_id,
|
||||
"reason": reason or "no_matching_completion_item",
|
||||
"title": "Telegram callback incident has no matching KM owner-review item",
|
||||
"next_step": "review_or_queue_km_owner_review",
|
||||
"target_surface": "awooop_runs_callback_evidence",
|
||||
"target_href": f"/awooop/runs?{target_query}",
|
||||
"writes_on_read": False,
|
||||
"manual_review_required": True,
|
||||
"batch_writes_allowed": False,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -435,6 +435,7 @@ def test_list_callback_replies_response_preserves_callback_evidence() -> None:
|
||||
"batch_writes_allowed": False,
|
||||
"manual_review_required": True,
|
||||
"related_total": 1,
|
||||
"work_item": None,
|
||||
"related_items": [
|
||||
{
|
||||
"entry_id": "km-1",
|
||||
@@ -529,6 +530,53 @@ async def test_km_stale_completion_summary_matches_callback_incident(
|
||||
assert summary["batch_writes_allowed"] is False
|
||||
assert summary["related_items"][0]["entry_id"] == "km-1"
|
||||
assert summary["related_items"][0]["can_preview"] is True
|
||||
assert summary["work_item"] is None
|
||||
|
||||
|
||||
def test_km_stale_completion_summary_generates_owner_review_work_item() -> None:
|
||||
summary = platform_operator_service._build_km_stale_completion_summary(
|
||||
queue=SimpleNamespace(
|
||||
total=10,
|
||||
returned=10,
|
||||
pending_count=10,
|
||||
ready_count=10,
|
||||
blocked_count=0,
|
||||
completed_count=1,
|
||||
failed_count=0,
|
||||
writes_on_read=False,
|
||||
manual_review_required=True,
|
||||
batch_writes_allowed=False,
|
||||
items=[],
|
||||
),
|
||||
project_id="awoooi",
|
||||
incident_id="INC-20260524-16109D",
|
||||
)
|
||||
|
||||
assert summary["status"] == "no_related_owner_review"
|
||||
assert summary["work_item"] == {
|
||||
"schema_version": "km_stale_callback_owner_review_work_item_v1",
|
||||
"work_item_id": (
|
||||
"km-callback-owner-review:awoooi:INC-20260524-16109D"
|
||||
),
|
||||
"kind": "km_stale_callback_owner_review",
|
||||
"status": "open",
|
||||
"project_id": "awoooi",
|
||||
"incident_id": "INC-20260524-16109D",
|
||||
"reason": "no_matching_completion_item",
|
||||
"title": (
|
||||
"Telegram callback incident has no matching KM owner-review item"
|
||||
),
|
||||
"next_step": "review_or_queue_km_owner_review",
|
||||
"target_surface": "awooop_runs_callback_evidence",
|
||||
"target_href": (
|
||||
"/awooop/runs?project_id=awoooi"
|
||||
"&incident_id=INC-20260524-16109D"
|
||||
"&callback_reply_status=sent"
|
||||
),
|
||||
"writes_on_read": False,
|
||||
"manual_review_required": True,
|
||||
"batch_writes_allowed": False,
|
||||
}
|
||||
|
||||
|
||||
def test_list_approvals_response_preserves_status_chain() -> None:
|
||||
|
||||
@@ -1961,6 +1961,9 @@
|
||||
"telegramCallbacks": {
|
||||
"title": "Telegram detail / history as DB truth-first"
|
||||
},
|
||||
"callbackOwnerReview": {
|
||||
"title": "Callback missing KM owner-review work item"
|
||||
},
|
||||
"ciSecretHygiene": {
|
||||
"title": "CI/CD secret log exposure hardening"
|
||||
},
|
||||
@@ -1987,6 +1990,7 @@
|
||||
"configDriftFsm": "The same drift fingerprint must expose recurrence, PR, zero diff, handoff, and next step",
|
||||
"remediationQueue": "Every degraded / failed / timeout row must map to replay, reverify, ticket, or manual review",
|
||||
"telegramCallbacks": "Detail and history buttons cannot depend only on Redis TTL or stale snapshots",
|
||||
"callbackOwnerReview": "Telegram detail/history callbacks without a KM owner-review link must become trackable work items",
|
||||
"ciSecretHygiene": "Workflows must not mount secrets in step env / action inputs; historical logs still need rotation and retention governance",
|
||||
"governanceDispatch": "Governance alerts must enter dispatch and expose skipped / pending / repaired",
|
||||
"knowledgeHealthcheck": "knowledge_degradation must show Hermes / OpenClaw / ElephantAlpha ownership, current stage, and owner review point",
|
||||
@@ -2013,6 +2017,11 @@
|
||||
"remediationQueue": "Remediation work: {total}; AI-ready: {ready}; human: {human}",
|
||||
"telegramCallbacks": "Telegram callback lookup and history summary are being repaired",
|
||||
"telegramCallbacksLive": "Read-only callback toast 400 is nonfatal; detail / history replies now use DB truth-chain",
|
||||
"callbackOwnerReview": "Callback owner-review gaps: {open} open; callback evidence: {total}",
|
||||
"callbackOwnerReviewLatest": "Latest: {incident} / {action}",
|
||||
"callbackOwnerReviewQueue": "Completion queue: ready {ready}; blocked {blocked}; completed {completed}; failed {failed}",
|
||||
"callbackOwnerReviewNext": "Next: {next}",
|
||||
"callbackOwnerReviewEmpty": "Recent callback evidence is matched or no data is available yet",
|
||||
"ciSecretHygiene": "Repo-controlled step env / action input exposure is guarded; key rotation and log retention remain",
|
||||
"governance": "Unresolved governance alerts: {unresolved}; pending dispatch: {queued}",
|
||||
"governanceUnavailable": "Governance events API is not responding; pending dispatch: {queued}",
|
||||
@@ -2791,6 +2800,7 @@
|
||||
"related": "{entryId} · {readiness} · {nextAction}",
|
||||
"noRelated": "This incident has no matching owner-review completion item yet.",
|
||||
"fetchFailed": "KM owner-review summary failed to load: {reason}",
|
||||
"openWorkItem": "Open work item",
|
||||
"statuses": {
|
||||
"matched_owner_review": "Matched owner review",
|
||||
"no_related_owner_review": "No matched owner review",
|
||||
|
||||
@@ -1962,6 +1962,9 @@
|
||||
"telegramCallbacks": {
|
||||
"title": "Telegram 詳情 / 歷史改為 DB truth-first"
|
||||
},
|
||||
"callbackOwnerReview": {
|
||||
"title": "Callback 未匹配 KM Owner Review 工作項"
|
||||
},
|
||||
"ciSecretHygiene": {
|
||||
"title": "CI/CD secret log 泄漏面收斂"
|
||||
},
|
||||
@@ -1988,6 +1991,7 @@
|
||||
"configDriftFsm": "同一 drift fingerprint 必須顯示重複、PR、零 diff、交接與下一步",
|
||||
"remediationQueue": "每筆 degraded / failed / timeout 都必須映射到重跑、重驗、Ticket 或人工檢查",
|
||||
"telegramCallbacks": "按下詳情與歷史不能再只依賴 Redis TTL 或舊快照",
|
||||
"callbackOwnerReview": "Telegram 詳情 / 歷史若未連到 KM owner-review,必須變成可追蹤工作項",
|
||||
"ciSecretHygiene": "workflow 不可再把 secrets 掛在 step env / action input;歷史 log 需另做輪換與保留期治理",
|
||||
"governanceDispatch": "治理告警必須進 dispatch,並標示 skipped / pending / repaired",
|
||||
"knowledgeHealthcheck": "knowledge_degradation 必須顯示 Hermes / OpenClaw / ElephantAlpha 分工、目前階段與 owner 審核點",
|
||||
@@ -2014,6 +2018,11 @@
|
||||
"remediationQueue": "補救工作:{total};AI 可接手:{ready};人工:{human}",
|
||||
"telegramCallbacks": "目前修補 Telegram callback 查詢鏈與歷史摘要",
|
||||
"telegramCallbacksLive": "read-only callback toast 400 已非致命;詳情 / 歷史改由 DB truth-chain 回覆",
|
||||
"callbackOwnerReview": "Callback owner-review 缺口:{open} 個 open;callback evidence:{total}",
|
||||
"callbackOwnerReviewLatest": "最新:{incident} / {action}",
|
||||
"callbackOwnerReviewQueue": "Completion queue:可處理 {ready};卡住 {blocked};完成 {completed};失敗 {failed}",
|
||||
"callbackOwnerReviewNext": "下一步:{next}",
|
||||
"callbackOwnerReviewEmpty": "近期 callback evidence 均已匹配或尚無資料",
|
||||
"ciSecretHygiene": "repo 可控 step env / action input 泄漏面已加 guard;仍需 key rotation 與 log retention 收斂",
|
||||
"governance": "未解治理告警:{unresolved};pending dispatch:{queued}",
|
||||
"governanceUnavailable": "治理事件 API 目前無法回應;pending dispatch:{queued}",
|
||||
@@ -2792,6 +2801,7 @@
|
||||
"related": "{entryId} · {readiness} · {nextAction}",
|
||||
"noRelated": "本 Incident 尚未對到 owner-review completion item。",
|
||||
"fetchFailed": "KM owner-review 摘要讀取失敗:{reason}",
|
||||
"openWorkItem": "開啟工作項",
|
||||
"statuses": {
|
||||
"matched_owner_review": "已匹配 owner review",
|
||||
"no_related_owner_review": "未匹配 owner review",
|
||||
|
||||
@@ -294,6 +294,23 @@ interface KmStaleCompletionSummaryItem {
|
||||
can_preview?: boolean | null;
|
||||
}
|
||||
|
||||
interface KmStaleCallbackOwnerReviewWorkItem {
|
||||
schema_version?: string;
|
||||
work_item_id?: string | null;
|
||||
kind?: string | null;
|
||||
status?: string | null;
|
||||
project_id?: string | null;
|
||||
incident_id?: string | null;
|
||||
reason?: string | null;
|
||||
title?: string | null;
|
||||
next_step?: string | null;
|
||||
target_surface?: string | null;
|
||||
target_href?: string | null;
|
||||
writes_on_read?: boolean | null;
|
||||
manual_review_required?: boolean | null;
|
||||
batch_writes_allowed?: boolean | null;
|
||||
}
|
||||
|
||||
interface KmStaleCompletionSummary {
|
||||
schema_version?: string;
|
||||
project_id?: string | null;
|
||||
@@ -313,6 +330,7 @@ interface KmStaleCompletionSummary {
|
||||
items_truncated?: boolean | null;
|
||||
related_total?: number;
|
||||
related_items?: KmStaleCompletionSummaryItem[];
|
||||
work_item?: KmStaleCallbackOwnerReviewWorkItem | null;
|
||||
}
|
||||
|
||||
interface CallbackReplyEvent {
|
||||
@@ -1468,6 +1486,7 @@ function CallbackKmCompletionSummary({
|
||||
const related = Array.isArray(summary.related_items)
|
||||
? summary.related_items[0]
|
||||
: null;
|
||||
const workItem = summary.work_item?.status === "open" ? summary.work_item : null;
|
||||
|
||||
return (
|
||||
<div className="mt-3 border-t border-[#e0ddd4] pt-3">
|
||||
@@ -1511,6 +1530,15 @@ function CallbackKmCompletionSummary({
|
||||
) : (
|
||||
<p className="text-[#77736a]">{t("noRelated")}</p>
|
||||
)}
|
||||
{workItem?.target_href ? (
|
||||
<Link
|
||||
href={workItem.target_href as never}
|
||||
className="mt-1 inline-flex w-fit items-center gap-1.5 border border-[#d8d3c7] bg-[#faf9f3] px-2 py-1 text-xs font-semibold text-[#2e2b26] hover:border-[#d97757] hover:text-[#8a4a2f]"
|
||||
>
|
||||
<SearchCheck className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
{t("openWorkItem")}
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -771,6 +771,54 @@ type DriftFingerprintHandoffResult = DriftFingerprintState & {
|
||||
} | null;
|
||||
};
|
||||
|
||||
type KmStaleCallbackOwnerReviewWorkItem = {
|
||||
schema_version?: string;
|
||||
work_item_id?: string | null;
|
||||
kind?: string | null;
|
||||
status?: string | null;
|
||||
project_id?: string | null;
|
||||
incident_id?: string | null;
|
||||
reason?: string | null;
|
||||
title?: string | null;
|
||||
next_step?: string | null;
|
||||
target_surface?: string | null;
|
||||
target_href?: string | null;
|
||||
writes_on_read?: boolean | null;
|
||||
manual_review_required?: boolean | null;
|
||||
batch_writes_allowed?: boolean | null;
|
||||
};
|
||||
|
||||
type KmStaleCallbackCompletionSummary = {
|
||||
schema_version?: string;
|
||||
status?: string | null;
|
||||
project_id?: string | null;
|
||||
incident_id?: string | null;
|
||||
reason?: string | null;
|
||||
pending_count?: number;
|
||||
ready_count?: number;
|
||||
blocked_count?: number;
|
||||
completed_count?: number;
|
||||
failed_count?: number;
|
||||
work_item?: KmStaleCallbackOwnerReviewWorkItem | null;
|
||||
};
|
||||
|
||||
type CallbackReplyWorkItemEvent = {
|
||||
message_id: string;
|
||||
project_id: string;
|
||||
status: string;
|
||||
action?: string | null;
|
||||
incident_id?: string | null;
|
||||
event_at?: string | null;
|
||||
km_stale_completion_summary?: KmStaleCallbackCompletionSummary | null;
|
||||
};
|
||||
|
||||
type CallbackRepliesWorkItemResponse = {
|
||||
items?: CallbackReplyWorkItemEvent[];
|
||||
total: number;
|
||||
page: number;
|
||||
per_page: number;
|
||||
};
|
||||
|
||||
type Telemetry = {
|
||||
quality: AutomationQualitySummary | null;
|
||||
governanceEvents: GovernanceEventsResponse | null;
|
||||
@@ -787,6 +835,7 @@ type Telemetry = {
|
||||
slo: SloResponse | null;
|
||||
remediationHistory: RemediationHistoryResponse | null;
|
||||
driftFingerprintState: DriftFingerprintState | null;
|
||||
callbackReplies: CallbackRepliesWorkItemResponse | null;
|
||||
statusChain: AwoooPStatusChain | null;
|
||||
};
|
||||
|
||||
@@ -1490,6 +1539,18 @@ function governanceEventHistoryHref(eventId: string) {
|
||||
return `/governance?tab=events&event_id=${encodeURIComponent(eventId)}`;
|
||||
}
|
||||
|
||||
function callbackOwnerReviewOpenEvents(
|
||||
response: CallbackRepliesWorkItemResponse | null
|
||||
): CallbackReplyWorkItemEvent[] {
|
||||
return (response?.items ?? []).filter((item) => {
|
||||
const summary = item.km_stale_completion_summary;
|
||||
return (
|
||||
summary?.status === "no_related_owner_review" &&
|
||||
summary.work_item?.status === "open"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function buildWorkItems(
|
||||
telemetry: Telemetry,
|
||||
t: ReturnType<typeof useTranslations>
|
||||
@@ -1515,6 +1576,13 @@ function buildWorkItems(
|
||||
const topKnowledgeStaleCandidate = telemetry.knowledgeStaleCandidates?.items?.[0] ?? null;
|
||||
const knowledgeCompletionQueue = telemetry.knowledgeStaleOwnerReviewCompletionQueue;
|
||||
const latestKnowledgeCompletion = knowledgeCompletionQueue?.items?.[0] ?? null;
|
||||
const callbackOwnerReviewEvents = callbackOwnerReviewOpenEvents(
|
||||
telemetry.callbackReplies
|
||||
);
|
||||
const latestCallbackOwnerReview = callbackOwnerReviewEvents[0] ?? null;
|
||||
const latestCallbackSummary =
|
||||
latestCallbackOwnerReview?.km_stale_completion_summary ?? null;
|
||||
const latestCallbackWorkItem = latestCallbackSummary?.work_item ?? null;
|
||||
const remediationQueue = telemetry.slo?.adr100?.verification_coverage?.remediation_queue;
|
||||
const remediationTotal = remediationQueue?.total ?? 0;
|
||||
const remediationReadyForAi = remediationQueue?.ready_for_ai ?? 0;
|
||||
@@ -1704,6 +1772,40 @@ function buildWorkItems(
|
||||
evidence: t("evidence.telegramCallbacksLive"),
|
||||
href: "/awooop/runs",
|
||||
},
|
||||
{
|
||||
id: "callbackOwnerReview",
|
||||
phase: "T164",
|
||||
status: callbackOwnerReviewEvents.length > 0
|
||||
? "in_progress"
|
||||
: telemetry.callbackReplies
|
||||
? "watching"
|
||||
: "blocked",
|
||||
surfaceKey: "workItems",
|
||||
source: "/api/v1/platform/runs/callback-replies + km_stale_completion_summary",
|
||||
gateKey: "callbackOwnerReview",
|
||||
evidence: t("evidence.callbackOwnerReview", {
|
||||
open: callbackOwnerReviewEvents.length,
|
||||
total: telemetry.callbackReplies?.total ?? 0,
|
||||
}),
|
||||
evidenceDetails: latestCallbackOwnerReview
|
||||
? [
|
||||
t("evidence.callbackOwnerReviewLatest", {
|
||||
incident: latestCallbackOwnerReview.incident_id ?? "--",
|
||||
action: latestCallbackOwnerReview.action ?? "--",
|
||||
}),
|
||||
t("evidence.callbackOwnerReviewQueue", {
|
||||
ready: latestCallbackSummary?.ready_count ?? 0,
|
||||
blocked: latestCallbackSummary?.blocked_count ?? 0,
|
||||
completed: latestCallbackSummary?.completed_count ?? 0,
|
||||
failed: latestCallbackSummary?.failed_count ?? 0,
|
||||
}),
|
||||
t("evidence.callbackOwnerReviewNext", {
|
||||
next: latestCallbackWorkItem?.next_step ?? "--",
|
||||
}),
|
||||
]
|
||||
: [t("evidence.callbackOwnerReviewEmpty")],
|
||||
href: latestCallbackWorkItem?.target_href ?? "/awooop/runs",
|
||||
},
|
||||
{
|
||||
id: "ciSecretHygiene",
|
||||
phase: "T44",
|
||||
@@ -4459,6 +4561,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
slo: null,
|
||||
remediationHistory: null,
|
||||
driftFingerprintState: null,
|
||||
callbackReplies: null,
|
||||
statusChain: null,
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -4482,6 +4585,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
const sloUrl = `${API_BASE}/api/v1/ai/slo`;
|
||||
const remediationHistoryUrl = `${API_BASE}/api/v1/ai/slo/remediation/history?limit=80`;
|
||||
const driftFingerprintUrl = `${API_BASE}/api/v1/drift/fingerprints/state?namespace=awoooi-prod`;
|
||||
const callbackRepliesUrl = `${API_BASE}/api/v1/platform/runs/callback-replies?project_id=${encodedProjectId}&per_page=100`;
|
||||
|
||||
const [
|
||||
quality,
|
||||
@@ -4499,6 +4603,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
slo,
|
||||
remediationHistory,
|
||||
driftFingerprintState,
|
||||
callbackReplies,
|
||||
] = await Promise.all([
|
||||
fetchJson<AutomationQualitySummary>(qualityUrl, 15000),
|
||||
fetchJson<GovernanceEventsResponse>(governanceEventsUrl),
|
||||
@@ -4515,6 +4620,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
fetchJson<SloResponse>(sloUrl),
|
||||
fetchJson<RemediationHistoryResponse>(remediationHistoryUrl),
|
||||
fetchJson<DriftFingerprintState>(driftFingerprintUrl, 12000),
|
||||
fetchJson<CallbackRepliesWorkItemResponse>(callbackRepliesUrl, 12000),
|
||||
]);
|
||||
|
||||
const statusChainIncidentId = selectStatusChainIncidentId(
|
||||
@@ -4548,6 +4654,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
slo,
|
||||
remediationHistory,
|
||||
driftFingerprintState,
|
||||
callbackReplies,
|
||||
statusChain,
|
||||
});
|
||||
setLastUpdated(new Date());
|
||||
|
||||
Reference in New Issue
Block a user