feat(awooop): surface ai route repair evidence
This commit is contained in:
@@ -445,6 +445,30 @@ interface AiRouteOperatorAction {
|
||||
reason?: string | null;
|
||||
}
|
||||
|
||||
interface AiRouteRepairEvidence {
|
||||
provider?: string | null;
|
||||
stage?: string | null;
|
||||
provider_event_id?: string | null;
|
||||
conversation_event_id?: string | null;
|
||||
run_id?: string | null;
|
||||
alertname?: string | null;
|
||||
severity?: string | null;
|
||||
fingerprint?: string | null;
|
||||
target_resource?: string | null;
|
||||
observed_state?: Record<string, unknown>;
|
||||
live_probe?: Record<string, unknown>;
|
||||
access_blockers?: string[];
|
||||
side_effects?: {
|
||||
incident_created?: boolean | null;
|
||||
telegram_sent?: boolean | null;
|
||||
approval_created?: boolean | null;
|
||||
runtime_route_changed?: boolean | null;
|
||||
};
|
||||
source_ref_count?: number | null;
|
||||
provider_ts?: string | null;
|
||||
received_at?: string | null;
|
||||
}
|
||||
|
||||
interface AiRouteStatusResponse {
|
||||
schema_version: string;
|
||||
workload_type: string;
|
||||
@@ -461,6 +485,7 @@ interface AiRouteStatusResponse {
|
||||
active_lane?: AiRouteLaneItem | null;
|
||||
skipped_lanes?: AiRouteLaneItem[];
|
||||
operator_action?: AiRouteOperatorAction | null;
|
||||
repair_evidence?: AiRouteRepairEvidence | null;
|
||||
checked_at: string;
|
||||
}
|
||||
|
||||
@@ -2050,6 +2075,49 @@ function aiRouteOperatorActionLabelKey(action?: string | null) {
|
||||
return "operatorActions.unknown";
|
||||
}
|
||||
|
||||
const AI_ROUTE_REPAIR_BLOCKER_KEYS = new Set([
|
||||
"gcloud_compute_instances_get_missing",
|
||||
"gcloud_compute_instances_list_missing",
|
||||
"gcloud_projects_get_iam_policy_missing",
|
||||
"gcp_a_ssh_refused",
|
||||
"gcp_a_ollama_11434_refused",
|
||||
"proxy_110_11435_http_502",
|
||||
]);
|
||||
|
||||
const AI_ROUTE_REPAIR_PROBE_KEYS = new Set([
|
||||
"gcp_a_ping",
|
||||
"gcp_a_ssh_22",
|
||||
"gcp_a_direct_11434",
|
||||
"gcp_b_direct_11434",
|
||||
"proxy_110_11435",
|
||||
"proxy_110_11436",
|
||||
"proxy_110_11437",
|
||||
]);
|
||||
|
||||
const AI_ROUTE_REPAIR_SIDE_EFFECT_KEYS = [
|
||||
"incident_created",
|
||||
"telegram_sent",
|
||||
"approval_created",
|
||||
"runtime_route_changed",
|
||||
] as const;
|
||||
|
||||
function aiRouteRepairBlockerLabelKey(blocker?: string | null) {
|
||||
return blocker && AI_ROUTE_REPAIR_BLOCKER_KEYS.has(blocker)
|
||||
? `repairEvidence.blockers.${blocker}`
|
||||
: "repairEvidence.blockers.unknown";
|
||||
}
|
||||
|
||||
function aiRouteRepairProbeLabelKey(probe?: string | null) {
|
||||
return probe && AI_ROUTE_REPAIR_PROBE_KEYS.has(probe)
|
||||
? `repairEvidence.probes.${probe}`
|
||||
: "repairEvidence.probes.unknown";
|
||||
}
|
||||
|
||||
function compactEvidenceId(value?: string | null) {
|
||||
if (!value) return "--";
|
||||
return value.length > 18 ? `${value.slice(0, 12)}...` : value;
|
||||
}
|
||||
|
||||
function AiRouteStatusPanel({
|
||||
status,
|
||||
error,
|
||||
@@ -2064,6 +2132,9 @@ function AiRouteStatusPanel({
|
||||
const laneMode = status?.lane_mode ?? null;
|
||||
const laneModeKey = aiRouteLaneModeLabelKey(laneMode);
|
||||
const operatorActionKey = aiRouteOperatorActionLabelKey(status?.operator_action?.action);
|
||||
const repairEvidence = status?.repair_evidence ?? null;
|
||||
const repairBlockers = repairEvidence?.access_blockers?.filter(Boolean).slice(0, 4) ?? [];
|
||||
const repairProbes = Object.entries(repairEvidence?.live_probe ?? {}).slice(0, 6);
|
||||
const skippedLanes = status?.skipped_lanes ?? [];
|
||||
const skippedProviderSet = new Set(
|
||||
skippedLanes
|
||||
@@ -2076,6 +2147,12 @@ function AiRouteStatusPanel({
|
||||
minute: "2-digit",
|
||||
})
|
||||
: "--";
|
||||
const repairEvidenceAt = repairEvidence?.received_at
|
||||
? new Date(repairEvidence.received_at).toLocaleTimeString("zh-TW", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})
|
||||
: "--";
|
||||
|
||||
return (
|
||||
<section className="border border-[#e0ddd4] bg-white">
|
||||
@@ -2132,6 +2209,117 @@ function AiRouteStatusPanel({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{repairEvidence && (
|
||||
<div className="border-b border-[#e0ddd4] bg-[#fbfcfb] px-4 py-4">
|
||||
<div className="flex flex-wrap items-start justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<SearchCheck className="h-4 w-4 shrink-0 text-[#17602a]" aria-hidden="true" />
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-semibold text-[#141413]">
|
||||
{t("repairEvidence.title")}
|
||||
</p>
|
||||
<p className="mt-1 truncate font-mono text-xs text-[#77736a]">
|
||||
{repairEvidence.provider_event_id ?? "--"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="border border-[#cbdccf] bg-[#f0faf2] px-2 py-0.5 text-xs font-semibold text-[#17602a]">
|
||||
{t("repairEvidence.sourceRefs", {
|
||||
count: repairEvidence.source_ref_count ?? 0,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 grid gap-3 lg:grid-cols-3">
|
||||
<div className="border border-[#e0ddd4] bg-white px-3 py-3">
|
||||
<p className="text-xs font-semibold text-[#77736a]">
|
||||
{t("repairEvidence.meta")}
|
||||
</p>
|
||||
<div className="mt-2 space-y-1 text-xs leading-5 text-[#5f5b52]">
|
||||
<p>
|
||||
{t("repairEvidence.target", {
|
||||
target: repairEvidence.target_resource ?? "--",
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{t("repairEvidence.run", {
|
||||
run: compactEvidenceId(repairEvidence.run_id),
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{t("repairEvidence.receivedAt", {
|
||||
time: repairEvidenceAt,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border border-[#e0ddd4] bg-white px-3 py-3">
|
||||
<p className="text-xs font-semibold text-[#77736a]">
|
||||
{t("repairEvidence.blockerTitle")}
|
||||
</p>
|
||||
<div className="mt-2 flex flex-wrap gap-1.5">
|
||||
{repairBlockers.length > 0 ? repairBlockers.map((blocker) => (
|
||||
<span
|
||||
key={blocker}
|
||||
className="border border-[#d9b36f] bg-[#fff7e8] px-2 py-1 text-xs font-medium text-[#6d4707]"
|
||||
title={blocker}
|
||||
>
|
||||
{AI_ROUTE_REPAIR_BLOCKER_KEYS.has(blocker)
|
||||
? t(aiRouteRepairBlockerLabelKey(blocker) as never)
|
||||
: t("repairEvidence.blockers.unknown", { blocker })}
|
||||
</span>
|
||||
)) : (
|
||||
<span className="text-xs text-[#77736a]">
|
||||
{t("repairEvidence.emptyValue")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border border-[#e0ddd4] bg-white px-3 py-3">
|
||||
<p className="text-xs font-semibold text-[#77736a]">
|
||||
{t("repairEvidence.sideEffectTitle")}
|
||||
</p>
|
||||
<div className="mt-2 space-y-1 text-xs leading-5 text-[#5f5b52]">
|
||||
{AI_ROUTE_REPAIR_SIDE_EFFECT_KEYS.map((key) => {
|
||||
const value = repairEvidence.side_effects?.[key];
|
||||
const valueKey = value === true
|
||||
? "repairEvidence.values.yes"
|
||||
: value === false
|
||||
? "repairEvidence.values.no"
|
||||
: "repairEvidence.values.unknown";
|
||||
return (
|
||||
<p key={key}>
|
||||
{t(`repairEvidence.sideEffects.${key}` as never)}
|
||||
{t("repairEvidence.sideEffectSeparator")}
|
||||
{t(valueKey as never)}
|
||||
</p>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{repairProbes.length > 0 && (
|
||||
<div className="mt-3 grid gap-px bg-[#e0ddd4] md:grid-cols-2 xl:grid-cols-3">
|
||||
{repairProbes.map(([probe, value]) => (
|
||||
<div key={probe} className="bg-white px-3 py-2 text-xs leading-5">
|
||||
<p className="font-semibold text-[#141413]">
|
||||
{AI_ROUTE_REPAIR_PROBE_KEYS.has(probe)
|
||||
? t(aiRouteRepairProbeLabelKey(probe) as never)
|
||||
: t("repairEvidence.probes.unknown", { probe })}
|
||||
</p>
|
||||
<p className="mt-1 truncate font-mono text-[#77736a]" title={String(value ?? "--")}>
|
||||
{String(value ?? "--")}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid gap-px bg-[#e0ddd4] md:grid-cols-4">
|
||||
<div className="bg-white px-4 py-3">
|
||||
<p className="text-xs font-semibold text-[#77736a]">{t("fields.workload")}</p>
|
||||
|
||||
@@ -126,6 +126,11 @@ interface AiRouteStatusResponse {
|
||||
action?: string | null
|
||||
human_required?: boolean
|
||||
} | null
|
||||
repair_evidence?: {
|
||||
target_resource?: string | null
|
||||
access_blockers?: string[]
|
||||
source_ref_count?: number | null
|
||||
} | null
|
||||
}
|
||||
|
||||
interface EvidenceSnapshot {
|
||||
@@ -394,8 +399,17 @@ export function AutomationEvidenceCard() {
|
||||
skipped: skippedLanes || '--',
|
||||
})
|
||||
: null
|
||||
const repairEvidence = route?.repair_evidence ?? null
|
||||
const repairBlockers = repairEvidence?.access_blockers?.slice(0, 2).join(', ')
|
||||
const repairDetail = repairEvidence
|
||||
? t('routeRepairDetail', {
|
||||
target: repairEvidence.target_resource ?? '--',
|
||||
blockers: repairBlockers || '--',
|
||||
sourceRefs: repairEvidence.source_ref_count ?? 0,
|
||||
})
|
||||
: null
|
||||
const routeDetail = route?.route_reason && !route.route_error
|
||||
? `${routeSummary}${laneDetail ? t('routeReasonSeparator') + laneDetail : ''}${t('routeReasonSeparator')}${t('routeReason', { reason: route.route_reason })}`
|
||||
? `${routeSummary}${laneDetail ? t('routeReasonSeparator') + laneDetail : ''}${repairDetail ? t('routeReasonSeparator') + repairDetail : ''}${t('routeReasonSeparator')}${t('routeReason', { reason: route.route_reason })}`
|
||||
: routeSummary
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user