159 lines
7.3 KiB
PowerShell
159 lines
7.3 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[string]$AgentRoot = "C:\Wooo\Agent99",
|
|
[string]$NotBefore = ""
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$notBeforeTime = [datetime]::MinValue
|
|
if ($NotBefore) {
|
|
$notBeforeTime = [datetime]::Parse($NotBefore)
|
|
}
|
|
|
|
$evidenceDir = Join-Path $AgentRoot "evidence"
|
|
$file = Get-ChildItem -Path $evidenceDir -Filter "agent99-Recover-*.json" -File -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.LastWriteTime -ge $notBeforeTime } |
|
|
Sort-Object LastWriteTime -Descending |
|
|
Select-Object -First 1
|
|
if (-not $file) {
|
|
throw "new_recover_evidence_missing"
|
|
}
|
|
|
|
$raw = Get-Content -Path $file.FullName -Raw | ConvertFrom-Json
|
|
$phases = @($raw.recoverySlo.phases | ForEach-Object {
|
|
[pscustomobject]@{
|
|
name = [string]$_.name
|
|
status = [string]$_.status
|
|
elapsedSeconds = [double]$_.elapsedSeconds
|
|
}
|
|
})
|
|
$telegram = @($raw.telegram | ForEach-Object {
|
|
$visualPath = if ($_.PSObject.Properties["visualPath"]) { [string]$_.visualPath } else { "" }
|
|
[pscustomobject]@{
|
|
eventType = [string]$_.eventType
|
|
severity = [string]$_.severity
|
|
sent = [bool]$_.sent
|
|
suppressed = if ($_.PSObject.Properties["suppressed"]) { [bool]$_.suppressed } else { $false }
|
|
reason = if ($_.PSObject.Properties["reason"]) { [string]$_.reason } else { "" }
|
|
messageFormat = if ($_.PSObject.Properties["messageFormat"]) { [string]$_.messageFormat } else { "" }
|
|
incidentId = if ($_.PSObject.Properties["incidentId"]) { [string]$_.incidentId } else { "" }
|
|
fingerprint = if ($_.PSObject.Properties["fingerprint"]) { [string]$_.fingerprint } else { "" }
|
|
lifecycle = if ($_.PSObject.Properties["lifecycle"]) { [string]$_.lifecycle } else { "" }
|
|
stateKey = if ($_.PSObject.Properties["stateKey"]) { [string]$_.stateKey } else { "" }
|
|
visualSent = if ($_.PSObject.Properties["visualSent"]) { [bool]$_.visualSent } else { $false }
|
|
messageIdPresent = [bool]($_.PSObject.Properties["messageId"] -and $_.messageId)
|
|
replyThreaded = [bool]($_.PSObject.Properties["replyToMessageId"] -and $_.replyToMessageId)
|
|
incidentStateWritten = [bool]($_.PSObject.Properties["incidentStateWritten"] -and $_.incidentStateWritten)
|
|
visualFile = if ($visualPath) { Split-Path -Leaf $visualPath } else { "" }
|
|
visualExists = [bool]($visualPath -and (Test-Path -LiteralPath $visualPath))
|
|
}
|
|
})
|
|
|
|
$phaseFailures = @($phases | Where-Object { $_.status -ne "ok" })
|
|
$sentCount = @($telegram | Where-Object { $_.sent }).Count
|
|
$suppressedCount = @($telegram | Where-Object { $_.suppressed }).Count
|
|
$lifecycleReceipts = @($telegram | Where-Object { $_.eventType -eq "recovery_slo_result" -and ($_.sent -or $_.suppressed) })
|
|
$sentLifecycleReceipts = @($lifecycleReceipts | Where-Object { $_.sent })
|
|
$notificationReceipted = [bool]($lifecycleReceipts.Count -gt 0)
|
|
$humanReadableCardReady = [bool](
|
|
$lifecycleReceipts.Count -gt 0 -and
|
|
@($lifecycleReceipts | Where-Object {
|
|
$_.messageFormat -ne "incident_card_zh_tw_v5" -or
|
|
$_.incidentId -notmatch "^INC-[A-F0-9]{8}$" -or
|
|
$_.lifecycle -notin @("remediating", "recovered", "blocked")
|
|
}).Count -eq 0
|
|
)
|
|
$visualLifecycleReady = [bool](@($sentLifecycleReceipts | Where-Object { -not $_.visualSent }).Count -eq 0)
|
|
$incidentStateReady = [bool](@($sentLifecycleReceipts | Where-Object { -not $_.messageIdPresent -or -not $_.incidentStateWritten }).Count -eq 0)
|
|
$recoveryVerified = [bool](
|
|
$raw.mode -eq "Recover" -and
|
|
$raw.recoverySlo.completed -and
|
|
$raw.recoverySlo.coordinatorVerified -and
|
|
$phases.Count -gt 0 -and
|
|
$phaseFailures.Count -eq 0
|
|
)
|
|
$recentReceipts = @(Get-ChildItem -Path $evidenceDir -Filter "agent99-Recover-*.json" -File -ErrorAction SilentlyContinue |
|
|
Sort-Object LastWriteTime -Descending |
|
|
Select-Object -First 5 |
|
|
ForEach-Object {
|
|
$historyFile = $_
|
|
try {
|
|
$history = Get-Content -Path $historyFile.FullName -Raw | ConvertFrom-Json
|
|
$historyTelegram = @($history.telegram)
|
|
[pscustomobject]@{
|
|
evidenceFile = $historyFile.Name
|
|
evidenceLastWriteTime = $historyFile.LastWriteTime.ToString("o")
|
|
runId = [string]$history.recoverySlo.runId
|
|
completed = [bool]$history.recoverySlo.completed
|
|
telegramAttemptCount = $historyTelegram.Count
|
|
telegramSentCount = @($historyTelegram | Where-Object { $_.sent }).Count
|
|
telegramSuppressedCount = @($historyTelegram | Where-Object { $_.PSObject.Properties["suppressed"] -and $_.suppressed }).Count
|
|
visualSentCount = @($historyTelegram | Where-Object { $_.PSObject.Properties["visualSent"] -and $_.visualSent }).Count
|
|
visualFiles = @($historyTelegram | Where-Object { $_.PSObject.Properties["visualPath"] -and $_.visualPath } | ForEach-Object { Split-Path -Leaf ([string]$_.visualPath) })
|
|
}
|
|
} catch {
|
|
[pscustomobject]@{
|
|
evidenceFile = $historyFile.Name
|
|
evidenceLastWriteTime = $historyFile.LastWriteTime.ToString("o")
|
|
parseError = $_.Exception.GetType().Name
|
|
}
|
|
}
|
|
})
|
|
|
|
$result = [pscustomobject]@{
|
|
schemaVersion = "agent99_recover_receipt_readback_v1"
|
|
observedAt = (Get-Date).ToString("o")
|
|
evidenceFile = $file.Name
|
|
evidenceLastWriteTime = $file.LastWriteTime.ToString("o")
|
|
secretValueRead = $false
|
|
remoteWritePerformed = $false
|
|
mode = [string]$raw.mode
|
|
controlledApply = [bool]$raw.controlledApply
|
|
vmCount = @($raw.vmResults).Count
|
|
vmOkCount = @($raw.vmResults | Where-Object { $_.ok -or $_.skipped }).Count
|
|
hostCount = @($raw.hosts).Count
|
|
hostReachableCount = @($raw.hosts | Where-Object { $_.ping }).Count
|
|
publicCount = @($raw.public).Count
|
|
publicOkCount = @($raw.public | Where-Object { $_.ok }).Count
|
|
recovery = [pscustomobject]@{
|
|
runId = [string]$raw.recoverySlo.runId
|
|
scope = [string]$raw.recoverySlo.scope
|
|
completed = [bool]$raw.recoverySlo.completed
|
|
withinSlo = [bool]$raw.recoverySlo.withinSlo
|
|
elapsedSeconds = [double]$raw.recoverySlo.elapsedSeconds
|
|
coordinatorVerified = [bool]$raw.recoverySlo.coordinatorVerified
|
|
rebootSloClaimed = [bool]$raw.recoverySlo.rebootSloClaimed
|
|
phases = $phases
|
|
}
|
|
telegramAttemptCount = $telegram.Count
|
|
telegramSentCount = $sentCount
|
|
telegramSuppressedCount = $suppressedCount
|
|
lifecycleReceiptCount = $lifecycleReceipts.Count
|
|
notificationReceipted = $notificationReceipted
|
|
humanReadableCardReady = $humanReadableCardReady
|
|
visualLifecycleReady = $visualLifecycleReady
|
|
incidentStateReady = $incidentStateReady
|
|
telegram = $telegram
|
|
recentReceipts = $recentReceipts
|
|
recoveryVerified = $recoveryVerified
|
|
ok = [bool]($recoveryVerified -and $notificationReceipted -and $humanReadableCardReady -and $visualLifecycleReady -and $incidentStateReady)
|
|
}
|
|
|
|
Write-Output "RECOVER_RECEIPT_OK=$([int]$result.ok)"
|
|
Write-Output "RECOVERY_VERIFIED=$([int]$result.recoveryVerified)"
|
|
Write-Output "NOTIFICATION_RECEIPTED=$([int]$result.notificationReceipted)"
|
|
Write-Output "HUMAN_READABLE_CARD_READY=$([int]$result.humanReadableCardReady)"
|
|
Write-Output "VISUAL_LIFECYCLE_READY=$([int]$result.visualLifecycleReady)"
|
|
Write-Output "INCIDENT_STATE_READY=$([int]$result.incidentStateReady)"
|
|
Write-Output "TELEGRAM_ATTEMPTS=$($result.telegramAttemptCount)"
|
|
Write-Output "TELEGRAM_SENT=$($result.telegramSentCount)"
|
|
Write-Output "TELEGRAM_SUPPRESSED=$($result.telegramSuppressedCount)"
|
|
Write-Output "SECRET_VALUE_READ=0"
|
|
Write-Output "REMOTE_WRITE_PERFORMED=0"
|
|
Write-Output "JSON_BEGIN"
|
|
$result | ConvertTo-Json -Depth 10 -Compress
|
|
Write-Output "JSON_END"
|
|
|
|
if ($result.ok) { exit 0 }
|
|
exit 2
|