feat(sre): enforce typed host ansible handoffs
This commit is contained in:
@@ -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 && (
|
||||
|
||||
41
apps/web/src/lib/__tests__/auto-repair-result.test.ts
Normal file
41
apps/web/src/lib/__tests__/auto-repair-result.test.ts
Normal 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')
|
||||
})
|
||||
})
|
||||
27
apps/web/src/lib/auto-repair-result.ts
Normal file
27
apps/web/src/lib/auto-repair-result.ts
Normal 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'
|
||||
}
|
||||
Reference in New Issue
Block a user