feat(sre): enforce typed host ansible handoffs

This commit is contained in:
Your Name
2026-07-18 23:11:04 +08:00
parent 83fe799f50
commit 5b2547e4ec
24 changed files with 2255 additions and 58 deletions

View File

@@ -13,6 +13,7 @@
import { useState, useEffect, useCallback, useRef } from 'react'
import { useTranslations } from 'next-intl'
import { useIncidents } from '@/hooks/useIncidents'
import { classifyAutoRepairResult } from '@/lib/auto-repair-result'
import { cn } from '@/lib/utils'
import {
Wrench, RefreshCw, AlertCircle,
@@ -56,6 +57,9 @@ interface EvaluateResponse {
interface ExecuteResponse {
success: boolean
status: string
repair_executed: boolean
repair_verified: boolean
incident_id: string
playbook_id: string
executed_steps: string[]
@@ -94,6 +98,7 @@ function IncidentEvalRow({
const [executing, setExecuting] = useState(false)
const [result, setResult] = useState<ExecuteResponse | null>(null)
const [expanded, setExpanded] = useState(false)
const resultState = result ? classifyAutoRepairResult(result) : null
const fetchEval = useCallback(async () => {
const base = getApiBaseUrl()
@@ -193,14 +198,33 @@ function IncidentEvalRow({
{result && (
<div className={cn(
'rounded-lg border p-4',
result.success ? 'bg-[#f0faf2] border-[#8fc29a]' : 'bg-[#fff1f0] border-[#e2a29b]'
resultState === 'verified'
? 'bg-[#f0faf2] border-[#8fc29a]'
: resultState === 'failed'
? 'bg-[#fff1f0] border-[#e2a29b]'
: 'bg-[#fff7e8] border-[#d9b36f]'
)}>
<div className="mb-3 flex items-center gap-2">
{result.success
{resultState === 'verified'
? <CheckCircle2 className="h-5 w-5 text-[#17602a]" />
: <XCircle className="h-5 w-5 text-[#9f2f25]" />}
<span className={cn('font-mono text-sm font-bold', result.success ? 'text-[#17602a]' : 'text-[#9f2f25]')}>
{result.success ? t('execSuccess', { ms: result.execution_time_ms }) : t('execFailed', { error: result.error ?? '' })}
: resultState === 'failed'
? <XCircle className="h-5 w-5 text-[#9f2f25]" />
: <Activity className="h-5 w-5 text-[#8a5a08]" />}
<span className={cn(
'font-mono text-sm font-bold',
resultState === 'verified'
? 'text-[#17602a]'
: resultState === 'failed'
? 'text-[#9f2f25]'
: 'text-[#8a5a08]'
)}>
{resultState === 'queued'
? t('execQueued')
: resultState === 'verification_pending'
? t('execVerificationPending')
: resultState === 'verified'
? t('execVerified', { ms: result.execution_time_ms })
: t('execFailed', { error: result.error ?? '' })}
</span>
</div>
{result.executed_steps.length > 0 && (

View File

@@ -0,0 +1,41 @@
import { describe, expect, it } from 'vitest'
import { classifyAutoRepairResult } from '../auto-repair-result'
describe('classifyAutoRepairResult', () => {
it('keeps an accepted queue neutral instead of reporting failure', () => {
expect(classifyAutoRepairResult({
success: false,
status: 'queued',
repair_executed: false,
repair_verified: false,
})).toBe('queued')
})
it('does not mark an unverified runtime write green', () => {
expect(classifyAutoRepairResult({
success: true,
status: 'completed',
repair_executed: true,
repair_verified: false,
})).toBe('verification_pending')
})
it('requires independent verification for verified state', () => {
expect(classifyAutoRepairResult({
success: true,
status: 'completed',
repair_executed: true,
repair_verified: true,
})).toBe('verified')
})
it('keeps failed and malformed receipts red', () => {
expect(classifyAutoRepairResult({
success: false,
status: 'failed',
repair_executed: false,
repair_verified: false,
})).toBe('failed')
})
})

View File

@@ -0,0 +1,27 @@
export type AutoRepairResultState =
| 'queued'
| 'verification_pending'
| 'verified'
| 'failed'
export interface AutoRepairResultTruth {
success: boolean
status: string
repair_executed: boolean
repair_verified: boolean
}
export function classifyAutoRepairResult(
result: AutoRepairResultTruth,
): AutoRepairResultState {
if (result.status === 'queued') return 'queued'
if (
result.status === 'completed'
&& result.repair_executed
&& result.repair_verified
) return 'verified'
if (result.status === 'completed' && result.repair_executed) {
return 'verification_pending'
}
return 'failed'
}