feat(governance): surface verification remediation queue
All checks were successful
Code Review / ai-code-review (push) Successful in 10s
CD Pipeline / tests (push) Successful in 1m0s
CD Pipeline / build-and-deploy (push) Successful in 3m25s
CD Pipeline / post-deploy-checks (push) Successful in 1m16s

This commit is contained in:
Your Name
2026-05-14 21:45:33 +08:00
parent f97127f704
commit aa63ae5eca
6 changed files with 326 additions and 3 deletions

View File

@@ -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);
}, []);

View File

@@ -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>