feat(governance): surface km completion callback evidence
Some checks failed
CD Pipeline / tests (push) Successful in 1m10s
Code Review / ai-code-review (push) Successful in 12s
CD Pipeline / build-and-deploy (push) Successful in 4m19s
E2E Health Check / e2e-health (push) Failing after 34s
CD Pipeline / post-deploy-checks (push) Successful in 1m46s

This commit is contained in:
Your Name
2026-05-24 23:55:16 +08:00
parent 318ca645d0
commit 760d6745a5
6 changed files with 423 additions and 15 deletions

View File

@@ -281,6 +281,40 @@ interface EventRecurrenceResponse {
items: EventRecurrenceItem[];
}
interface KmStaleCompletionSummaryItem {
entry_id?: string | null;
title?: string | null;
dispatch_id?: string | null;
governance_event_id?: string | null;
readiness?: string | null;
workflow_stage?: string | null;
next_action?: string | null;
priority_tier?: string | null;
recommended_completion_outcome?: string | null;
can_preview?: boolean | null;
}
interface KmStaleCompletionSummary {
schema_version?: string;
project_id?: string | null;
incident_id?: string | null;
status?: string | null;
missing_reason?: string | null;
total?: number;
returned?: number;
pending_count?: number;
ready_count?: number;
blocked_count?: number;
completed_count?: number;
failed_count?: number;
writes_on_read?: boolean | null;
manual_review_required?: boolean | null;
batch_writes_allowed?: boolean | null;
items_truncated?: boolean | null;
related_total?: number;
related_items?: KmStaleCompletionSummaryItem[];
}
interface CallbackReplyEvent {
message_id: string;
run_id: string;
@@ -301,6 +335,7 @@ interface CallbackReplyEvent {
agent_id?: string | null;
run_detail_href?: string | null;
awooop_status_chain?: AwoooPStatusChain | null;
km_stale_completion_summary?: KmStaleCompletionSummary | null;
}
interface CallbackRepliesResponse {
@@ -660,6 +695,18 @@ function normalizeCallbackReplyEventStatus(statusValue?: string | null): Callbac
return "observed";
}
function normalizeKmCompletionStatus(statusValue?: string | null) {
if (
statusValue === "matched_owner_review" ||
statusValue === "no_related_owner_review" ||
statusValue === "fetch_failed" ||
statusValue === "no_incident"
) {
return statusValue;
}
return "observed";
}
function RemediationEvidenceCell({ summary }: { summary?: RemediationSummary | null }) {
const t = useTranslations("awooop.listEvidence");
const status = normalizeRemediationStatus(summary);
@@ -1409,6 +1456,66 @@ function GroupedAlertEventsPanel({ events }: { events: PlatformEvent[] }) {
);
}
function CallbackKmCompletionSummary({
summary,
}: {
summary?: KmStaleCompletionSummary | null;
}) {
const t = useTranslations("awooop.callbackReply.events.kmCompletion");
if (!summary) return null;
const statusKey = normalizeKmCompletionStatus(summary.status);
const related = Array.isArray(summary.related_items)
? summary.related_items[0]
: null;
return (
<div className="mt-3 border-t border-[#e0ddd4] pt-3">
<div className="flex items-center gap-2">
<ShieldCheck className="h-3.5 w-3.5 text-[#17602a]" aria-hidden="true" />
<p className="text-xs font-semibold text-[#141413]">{t("title")}</p>
</div>
<div className="mt-2 grid gap-1 text-xs leading-5 text-[#5f5b52]">
<p>
{t("status", {
status: t(`statuses.${statusKey}` as never),
})}
</p>
<p>
{t("counts", {
ready: summary.ready_count ?? 0,
blocked: summary.blocked_count ?? 0,
completed: summary.completed_count ?? 0,
failed: summary.failed_count ?? 0,
})}
</p>
<p>
{t("guardrail", {
writesOnRead: String(summary.writes_on_read ?? false),
batchWrite: String(summary.batch_writes_allowed ?? false),
manualReview: String(summary.manual_review_required ?? true),
})}
</p>
{related ? (
<p className="truncate font-mono text-[#77736a]">
{t("related", {
entryId: related.entry_id ?? "--",
readiness: related.readiness ?? "--",
nextAction: related.next_action ?? "--",
})}
</p>
) : statusKey === "fetch_failed" ? (
<p className="text-[#9f2f25]">
{t("fetchFailed", { reason: summary.missing_reason ?? "--" })}
</p>
) : (
<p className="text-[#77736a]">{t("noRelated")}</p>
)}
</div>
</div>
);
}
function CallbackReplyEvidencePanel({
events,
total,
@@ -1499,6 +1606,9 @@ function CallbackReplyEvidencePanel({
compact
className="mt-3"
/>
<CallbackKmCompletionSummary
summary={event.km_stale_completion_summary}
/>
<Link
href={runHref as never}
className="mt-3 inline-flex items-center gap-1.5 border border-[#d8d3c7] bg-[#faf9f3] px-2 py-1 text-xs font-semibold text-[#2e2b26] hover:border-[#1f6feb] hover:bg-[#edf4ff] hover:text-[#0f4fa8]"