feat(governance): surface verification remediation queue
This commit is contained in:
@@ -46,11 +46,24 @@ type RecentEventsResponse = {
|
||||
events?: Array<{ provider_event_id: string; is_duplicate: boolean }>;
|
||||
};
|
||||
|
||||
type SloResponse = {
|
||||
adr100?: {
|
||||
verification_coverage?: {
|
||||
remediation_queue?: {
|
||||
total: number;
|
||||
ready_for_ai: number;
|
||||
needs_human: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
type Telemetry = {
|
||||
quality: AutomationQualitySummary | null;
|
||||
governanceEvents: GovernanceEventsResponse | null;
|
||||
governanceQueue: GovernanceQueueResponse | null;
|
||||
channelEvents: RecentEventsResponse | null;
|
||||
slo: SloResponse | null;
|
||||
};
|
||||
|
||||
type WorkItem = {
|
||||
@@ -108,6 +121,10 @@ function buildWorkItems(
|
||||
const recentChannelEvents = telemetry.channelEvents?.total ?? 0;
|
||||
const governanceUnresolved = telemetry.governanceEvents?.total ?? 0;
|
||||
const governanceQueuePending = telemetry.governanceQueue?.total ?? 0;
|
||||
const remediationQueue = telemetry.slo?.adr100?.verification_coverage?.remediation_queue;
|
||||
const remediationTotal = remediationQueue?.total ?? 0;
|
||||
const remediationReadyForAi = remediationQueue?.ready_for_ai ?? 0;
|
||||
const remediationNeedsHuman = remediationQueue?.needs_human ?? 0;
|
||||
const governanceEventsUnavailable = telemetry.governanceEvents === null;
|
||||
const governanceQueueMissing = telemetry.governanceQueue?.table_pending === true;
|
||||
const governanceDispatchBlocked =
|
||||
@@ -141,6 +158,24 @@ function buildWorkItems(
|
||||
}),
|
||||
href: "/awooop/runs",
|
||||
},
|
||||
{
|
||||
id: "remediationQueue",
|
||||
phase: "T24",
|
||||
status: remediationTotal === 0
|
||||
? "watching"
|
||||
: remediationReadyForAi > 0
|
||||
? "in_progress"
|
||||
: "blocked",
|
||||
surfaceKey: "governance",
|
||||
source: "/api/v1/ai/slo remediation_queue",
|
||||
gateKey: "remediationQueue",
|
||||
evidence: t("evidence.remediationQueue", {
|
||||
total: remediationTotal,
|
||||
ready: remediationReadyForAi,
|
||||
human: remediationNeedsHuman,
|
||||
}),
|
||||
href: "/governance",
|
||||
},
|
||||
{
|
||||
id: "telegramCallbacks",
|
||||
phase: "T17",
|
||||
@@ -252,6 +287,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
governanceEvents: null,
|
||||
governanceQueue: null,
|
||||
channelEvents: null,
|
||||
slo: null,
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
|
||||
@@ -262,15 +298,17 @@ export default function AwoooPWorkItemsPage() {
|
||||
const governanceEventsUrl = `${API_BASE}/api/v1/ai/governance/events?event_type=knowledge_degradation&event_type=governance_slo_data_gap&status=unresolved&size=10`;
|
||||
const governanceQueueUrl = `${API_BASE}/api/v1/ai/governance/queue?dispatch_status=pending&size=10`;
|
||||
const channelEventsUrl = `${API_BASE}/api/v1/platform/events/recent?project_id=awoooi&provider_prefix=alertmanager&limit=20`;
|
||||
const sloUrl = `${API_BASE}/api/v1/ai/slo`;
|
||||
|
||||
const [quality, governanceEvents, governanceQueue, channelEvents] = await Promise.all([
|
||||
const [quality, governanceEvents, governanceQueue, channelEvents, slo] = await Promise.all([
|
||||
fetchJson<AutomationQualitySummary>(qualityUrl),
|
||||
fetchJson<GovernanceEventsResponse>(governanceEventsUrl),
|
||||
fetchJson<GovernanceQueueResponse>(governanceQueueUrl),
|
||||
fetchJson<RecentEventsResponse>(channelEventsUrl),
|
||||
fetchJson<SloResponse>(sloUrl),
|
||||
]);
|
||||
|
||||
setTelemetry({ quality, governanceEvents, governanceQueue, channelEvents });
|
||||
setTelemetry({ quality, governanceEvents, governanceQueue, channelEvents, slo });
|
||||
setLastUpdated(new Date());
|
||||
setLoading(false);
|
||||
}, []);
|
||||
|
||||
@@ -99,6 +99,10 @@ interface Adr100VerificationCoverage {
|
||||
verification_result: string
|
||||
failure_class: string
|
||||
next_step: string
|
||||
remediation_status?: string | null
|
||||
remediation_action?: string | null
|
||||
remediation_owner?: string | null
|
||||
remediation_reason?: string | null
|
||||
auto_error_excerpt?: string | null
|
||||
evidence_excerpt?: string | null
|
||||
auto_created_at?: string | null
|
||||
@@ -107,6 +111,30 @@ interface Adr100VerificationCoverage {
|
||||
non_success_breakdown?: {
|
||||
by_verification_result?: Array<{ name: string; count: number }>
|
||||
by_failure_class?: Array<{ name: string; count: number }>
|
||||
by_remediation_status?: Array<{ name: string; count: number }>
|
||||
}
|
||||
remediation_queue?: {
|
||||
total: number
|
||||
ready_for_ai: number
|
||||
needs_human: number
|
||||
items?: Array<{
|
||||
work_item_id: string
|
||||
incident_id?: string | null
|
||||
auto_repair_id?: string | null
|
||||
alertname?: string | null
|
||||
playbook_id?: string | null
|
||||
failure_class?: string | null
|
||||
verification_result?: string | null
|
||||
remediation_status?: string | null
|
||||
remediation_action?: string | null
|
||||
remediation_owner?: string | null
|
||||
remediation_reason?: string | null
|
||||
source?: string | null
|
||||
auto_created_at?: string | null
|
||||
verification_collected_at?: string | null
|
||||
}>
|
||||
by_status?: Array<{ name: string; count: number }>
|
||||
by_action?: Array<{ name: string; count: number }>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +196,25 @@ function nextStepKey(value?: string | null): string {
|
||||
return 'review_degraded_verification'
|
||||
}
|
||||
|
||||
function remediationStatusKey(value?: string | null): string {
|
||||
if (value === 'ready_for_replay') return value
|
||||
if (value === 'ready_for_reverify') return value
|
||||
if (value === 'needs_target_mapping') return value
|
||||
if (value === 'needs_playbook_ticket') return value
|
||||
if (value === 'manual_review') return value
|
||||
return 'unknown'
|
||||
}
|
||||
|
||||
function remediationActionKey(value?: string | null): string {
|
||||
if (value === 'replay_with_supported_executor') return value
|
||||
if (value === 'reverify_with_promql_template') return value
|
||||
if (value === 'map_target_and_reverify') return value
|
||||
if (value === 'create_playbook_ticket') return value
|
||||
if (value === 'escalate_verification_failure') return value
|
||||
if (value === 'inspect_degraded_evidence') return value
|
||||
return 'inspect_degraded_evidence'
|
||||
}
|
||||
|
||||
function compactLabel(value?: string | null, fallback = '--'): string {
|
||||
if (!value) return fallback
|
||||
return value.length > 54 ? `${value.slice(0, 54)}...` : value
|
||||
@@ -238,6 +285,7 @@ function VerificationCoveragePanel({ coverage }: { coverage?: Adr100Verification
|
||||
]
|
||||
const failureBreakdown = coverage?.non_success_breakdown?.by_failure_class ?? []
|
||||
const recentFindings = coverage?.recent_non_success ?? []
|
||||
const remediationQueue = coverage?.remediation_queue
|
||||
|
||||
return (
|
||||
<GlassCard variant="subtle" padding="md">
|
||||
@@ -313,6 +361,61 @@ function VerificationCoveragePanel({ coverage }: { coverage?: Adr100Verification
|
||||
</div>
|
||||
)}
|
||||
|
||||
{remediationQueue && remediationQueue.total > 0 && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
|
||||
<div style={{ fontFamily: 'Syne, sans-serif', fontSize: 11, fontWeight: 700, color: '#141413' }}>
|
||||
{t('remediationQueue')}
|
||||
</div>
|
||||
<div style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f' }}>
|
||||
{t('queueSummary', {
|
||||
total: remediationQueue.total,
|
||||
ready: remediationQueue.ready_for_ai,
|
||||
human: remediationQueue.needs_human,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
|
||||
{(remediationQueue.items ?? []).slice(0, 4).map(item => (
|
||||
<div key={item.work_item_id} style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'minmax(130px, 0.8fr) minmax(180px, 1fr) minmax(160px, 1fr)',
|
||||
gap: 10,
|
||||
alignItems: 'center',
|
||||
minWidth: 0,
|
||||
padding: '7px 0',
|
||||
borderTop: '0.5px solid rgba(20,20,19,0.08)',
|
||||
}} className="slo-remediation-row">
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#141413' }}>
|
||||
{item.incident_id ?? '--'}
|
||||
</div>
|
||||
<div style={{ fontFamily: "'DM Mono', monospace", fontSize: 9, color: '#87867f', marginTop: 2 }}>
|
||||
{t(`remediationStatus.${remediationStatusKey(item.remediation_status)}`)}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#7c5a10' }}>
|
||||
{t(`remediationAction.${remediationActionKey(item.remediation_action)}`)}
|
||||
</div>
|
||||
<div style={{ fontFamily: "'DM Mono', monospace", fontSize: 9, color: '#87867f', marginTop: 2, overflowWrap: 'anywhere' }}>
|
||||
{compactLabel(item.alertname)} · {compactLabel(item.playbook_id)}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#141413' }}>
|
||||
{item.remediation_owner ?? '--'}
|
||||
</div>
|
||||
<div style={{ fontFamily: "'DM Mono', monospace", fontSize: 9, color: '#87867f', marginTop: 2, overflowWrap: 'anywhere' }}>
|
||||
{compactLabel(item.remediation_reason)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{recentFindings.length > 0 && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
<div style={{ fontFamily: 'Syne, sans-serif', fontSize: 11, fontWeight: 700, color: '#141413' }}>
|
||||
@@ -478,11 +581,12 @@ export function SloTab() {
|
||||
|
||||
{/* Responsive helpers */}
|
||||
<style>{`
|
||||
.slo-kpi-grid > * { flex: 1; min-width: 200px; }
|
||||
.slo-kpi-grid > * { flex: 1; min-width: 200px; }
|
||||
@media (max-width: 640px) {
|
||||
.slo-kpi-grid > * { flex: 0 0 100%; min-width: 0; }
|
||||
.slo-coverage-grid { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
|
||||
.slo-finding-row { grid-template-columns: 1fr !important; }
|
||||
.slo-remediation-row { grid-template-columns: 1fr !important; }
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user