Files
awoooi/scripts/reboot-recovery/agent99-remote-atomic-deploy-receiver.ps1
ogt b233ac5c74
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m46s
CD Pipeline / build-and-deploy (push) Successful in 19m6s
CD Pipeline / post-deploy-checks (push) Successful in 2m4s
fix(agent99): complete same-run status reconcile
2026-07-14 23:28:42 +08:00

1455 lines
60 KiB
PowerShell

[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$EnvelopeJson,
[switch]$TransportPayloadDeletedBeforeReceiver
)
$ErrorActionPreference = "Stop"
$AgentRoot = "C:\Wooo\Agent99"
$BinDir = Join-Path $AgentRoot "bin"
$ConfigPath = Join-Path $AgentRoot "config\agent99.config.json"
$StateDir = Join-Path $AgentRoot "state"
$EvidenceDir = Join-Path $AgentRoot "evidence"
$DeployRoot = Join-Path $AgentRoot "deploy"
$ManifestPath = Join-Path $StateDir "runtime-manifest.json"
$LauncherPath = Join-Path $AgentRoot "agent99-run.ps1"
$WindowsPowerShell = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
$RelayTaskName = "Wooo-Agent99-SRE-Alert-Relay"
$RelayTaskPath = "\"
$RelayPort = 8787
$FixedRuntimeFiles = @(
"agent99-bootstrap.ps1",
"agent99-contract-check.ps1",
"agent99-control-plane.ps1",
"agent99-deploy.ps1",
"agent99-register-tasks.ps1",
"agent99-sre-alert-inbox.ps1",
"agent99-sre-alert-relay.ps1",
"agent99-submit-request.ps1",
"agent99-synthetic-tests.ps1",
"agent99-telegram-inbox.ps1",
"agent99-windows-update-policy.ps1",
"host-reboot-scorecard.ps1",
"agent99.config.example.json",
"agent99.config.99.example.json"
)
$script:DeployLock = $null
$script:ReceiverRemoteWritePerformed = $false
$script:ReceiverLiveRuntimeWritePerformed = $false
$script:ReceiverLivePromotionAttempted = $false
$script:ReceiverLivePromotionPerformed = $false
$script:ReceiverScheduledTaskRestartPerformed = $false
$script:ReceiverRollbackAttempted = $false
$script:ReceiverRollbackVerified = $false
$script:ReceiverOperationPhase = "envelope_validation"
$script:ReceiverAttemptReceiptPath = ""
$script:ReceiverCanonicalReceiptPath = ""
$script:ReceiverTraceId = ""
$script:ReceiverRunId = ""
$script:ReceiverWorkItemId = ""
$script:ReceiverSourceRevision = ""
$script:ReceiverManifestDigest = ""
$script:ReceiverTransportPayloadMode = "unknown"
$script:ReceiverTransientTransportPayloadStored = $false
$script:ReceiverTransportPayloadDeletedBeforeReceiver = $false
function Get-AgentSha256Bytes {
param([byte[]]$Bytes)
$sha = [Security.Cryptography.SHA256]::Create()
try {
return (($sha.ComputeHash($Bytes) | ForEach-Object { $_.ToString("x2") }) -join "")
} finally {
$sha.Dispose()
}
}
function Get-AgentSha256File {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return "missing" }
return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
}
function Get-AgentLauncherContract {
$requiredMarkers = @(
'[switch]$ReconcileOnly',
'[string]$ReconcileEvidenceId = ""',
'[switch]$ReconcileQueueOnly',
'[string]$ReconcileQueueId = ""',
'-ReconcileOnly:$ReconcileOnly',
'-ReconcileEvidenceId $ReconcileEvidenceId',
'-ReconcileQueueOnly:$ReconcileQueueOnly',
'-ReconcileQueueId $ReconcileQueueId'
)
if (-not (Test-Path -LiteralPath $LauncherPath -PathType Leaf)) {
return [pscustomobject]@{
schemaVersion = "agent99_launcher_contract_v1"
ok = $false
exists = $false
sha256 = "missing"
parseErrorCount = -1
requiredMarkerCount = $requiredMarkers.Count
missingMarkerCount = $requiredMarkers.Count
reconcileOnlyForwarded = $false
reconcileQueueOnlyForwarded = $false
}
}
$tokens = $null
$parseErrors = $null
[Management.Automation.Language.Parser]::ParseFile($LauncherPath, [ref]$tokens, [ref]$parseErrors) | Out-Null
$content = [IO.File]::ReadAllText($LauncherPath)
$missingMarkers = @($requiredMarkers | Where-Object { -not $content.Contains($_) })
return [pscustomobject]@{
schemaVersion = "agent99_launcher_contract_v1"
ok = [bool](@($parseErrors).Count -eq 0 -and $missingMarkers.Count -eq 0)
exists = $true
sha256 = (Get-AgentSha256File $LauncherPath)
parseErrorCount = @($parseErrors).Count
requiredMarkerCount = $requiredMarkers.Count
missingMarkerCount = $missingMarkers.Count
reconcileOnlyForwarded = [bool]($missingMarkers -notcontains '-ReconcileOnly:$ReconcileOnly' -and $missingMarkers -notcontains '-ReconcileEvidenceId $ReconcileEvidenceId')
reconcileQueueOnlyForwarded = [bool]($missingMarkers -notcontains '-ReconcileQueueOnly:$ReconcileQueueOnly' -and $missingMarkers -notcontains '-ReconcileQueueId $ReconcileQueueId')
}
}
function Get-AgentSafeRuntimeManifest {
if (-not (Test-Path -LiteralPath $ManifestPath -PathType Leaf)) {
return [pscustomobject]@{
exists = $false
sourceRevision = ""
runtimeMatched = $false
recordedRuntimeMatched = $false
fileCount = 0
mismatchCount = -1
recordedMismatchCount = -1
parseError = ""
}
}
try {
$manifest = Get-Content -LiteralPath $ManifestPath -Raw | ConvertFrom-Json
$manifestRows = @($manifest.files)
$rowsByName = @{}
$shapeValid = [bool]($manifestRows.Count -eq $FixedRuntimeFiles.Count)
foreach ($row in $manifestRows) {
$rowName = [string]$row.name
if (-not $rowName -or $rowsByName.ContainsKey($rowName)) {
$shapeValid = $false
continue
}
$rowsByName[$rowName] = $row
}
$actualMismatchCount = 0
foreach ($name in $FixedRuntimeFiles) {
if (-not $rowsByName.ContainsKey($name)) {
$shapeValid = $false
$actualMismatchCount += 1
continue
}
$recordedRuntimeSha = ([string]($rowsByName[$name].runtimeSha256)).ToLowerInvariant()
$actualRuntimeSha = Get-AgentSha256File (Join-Path $BinDir $name)
if (-not $recordedRuntimeSha -or $recordedRuntimeSha -ne $actualRuntimeSha) {
$actualMismatchCount += 1
}
}
$currentMatched = [bool](
$shapeValid -and
[bool]$manifest.runtimeMatched -and
[int]$manifest.fileCount -eq $FixedRuntimeFiles.Count -and
[int]$manifest.mismatchCount -eq 0 -and
$actualMismatchCount -eq 0
)
return [pscustomobject]@{
exists = $true
sourceRevision = [string]$manifest.sourceRevision
runtimeMatched = $currentMatched
recordedRuntimeMatched = [bool]$manifest.runtimeMatched
fileCount = [int]$manifest.fileCount
mismatchCount = [int]$actualMismatchCount
recordedMismatchCount = [int]$manifest.mismatchCount
parseError = ""
}
} catch {
return [pscustomobject]@{
exists = $true
sourceRevision = ""
runtimeMatched = $false
recordedRuntimeMatched = $false
fileCount = 0
mismatchCount = -1
recordedMismatchCount = -1
parseError = $_.Exception.GetType().Name
}
}
}
function Get-AgentLiveBundleDigest {
$rows = @()
foreach ($name in $FixedRuntimeFiles) {
$rows += "$name`t$(Get-AgentSha256File (Join-Path $BinDir $name))`n"
}
$rows += "agent99-run.ps1`t$(Get-AgentSha256File $LauncherPath)`n"
return Get-AgentSha256Bytes ([Text.Encoding]::UTF8.GetBytes(($rows -join "")))
}
function Test-AgentSafeIdentity {
param([string]$Value)
return [bool]($Value -and $Value -match "^[A-Za-z0-9][A-Za-z0-9._:@+-]{0,127}$")
}
function Test-AgentManifestEqual {
param([object]$Left, [object]$Right)
return [bool](
$Left.exists -eq $Right.exists -and
$Left.sourceRevision -eq $Right.sourceRevision -and
$Left.runtimeMatched -eq $Right.runtimeMatched -and
$Left.fileCount -eq $Right.fileCount -and
$Left.mismatchCount -eq $Right.mismatchCount
)
}
function Convert-AgentChildJson {
param([object[]]$Lines)
$text = (@($Lines | ForEach-Object { [string]$_ }) -join "`n").Trim()
$start = $text.IndexOf("{")
$end = $text.LastIndexOf("}")
if ($start -lt 0 -or $end -le $start) { return $null }
try {
return $text.Substring($start, $end - $start + 1) | ConvertFrom-Json
} catch {
return $null
}
}
function Invoke-AgentBoundedPowerShell {
param(
[string[]]$Arguments,
[int]$TimeoutSeconds
)
if ($TimeoutSeconds -lt 1 -or @($Arguments | Where-Object { $_ -match '[\s"]' }).Count -gt 0) {
throw "unsafe_or_invalid_child_process_contract"
}
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
$startInfo.FileName = $WindowsPowerShell
$startInfo.Arguments = $Arguments -join " "
$startInfo.UseShellExecute = $false
$startInfo.CreateNoWindow = $true
$startInfo.RedirectStandardOutput = $true
$startInfo.RedirectStandardError = $true
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $startInfo
try {
if (-not $process.Start()) { throw "child_process_start_failed" }
$stdoutTask = $process.StandardOutput.ReadToEndAsync()
$stderrTask = $process.StandardError.ReadToEndAsync()
$finished = $process.WaitForExit($TimeoutSeconds * 1000)
$terminated = $finished
if (-not $finished) {
try { $process.Kill() } catch {}
try { $terminated = $process.WaitForExit(10000) } catch { $terminated = $false }
}
$stdout = ""
if ($terminated) {
try { $stdout = [string]$stdoutTask.Result } catch { $stdout = "" }
try { $null = [string]$stderrTask.Result } catch {}
}
return [pscustomobject]@{
exitCode = if ($finished) { [int]$process.ExitCode } else { 124 }
timedOut = [bool](-not $finished)
stdout = $stdout
}
} finally {
$process.Dispose()
}
}
function Invoke-AgentDeployChild {
param(
[string]$SourceRoot,
[string]$SourceRevision,
[switch]$ValidateOnly
)
$arguments = @(
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy", "Bypass",
"-File", (Join-Path $SourceRoot "agent99-deploy.ps1"),
"-SourceRoot", $SourceRoot,
"-AgentRoot", $AgentRoot,
"-SourceRevision", $SourceRevision
)
if ($ValidateOnly) { $arguments += "-ValidateOnly" }
$timeoutSeconds = if ($ValidateOnly) { 300 } else { 900 }
$processResult = Invoke-AgentBoundedPowerShell -Arguments $arguments -TimeoutSeconds $timeoutSeconds
return [pscustomobject]@{
ok = [bool]($processResult.exitCode -eq 0 -and -not $processResult.timedOut)
exitCode = [int]$processResult.exitCode
timedOut = [bool]$processResult.timedOut
payload = Convert-AgentChildJson @($processResult.stdout)
validateOnly = [bool]$ValidateOnly
}
}
function Invoke-AgentLivePreflight {
param([string]$PreflightPath)
$arguments = @("-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", $PreflightPath, "-Mode", "Verify", "-AgentRoot", $AgentRoot)
$processResult = Invoke-AgentBoundedPowerShell -Arguments $arguments -TimeoutSeconds 120
$text = [string]$processResult.stdout
$match = [regex]::Match($text, "(?s)JSON_BEGIN\r?\n(?<json>\{.*?\})\r?\nJSON_END")
$payload = $null
if ($match.Success) {
try { $payload = $match.Groups["json"].Value | ConvertFrom-Json } catch { $payload = $null }
}
$summary = if ($payload -and $payload.PSObject.Properties["summary"]) { $payload.summary } else { $null }
[string[]]$blockingReasons = @()
if ($processResult.timedOut) {
$blockingReasons = @("preflight_process_timeout")
} elseif (-not $summary) {
$blockingReasons = @("preflight_output_unparseable")
} elseif ($null -ne $summary.blockingReasons) {
$blockingReasons = @($summary.blockingReasons | ForEach-Object { [string]$_ })
}
[string[]]$externalBlockingReasons = @($blockingReasons | Where-Object { $_ -eq "self_health_not_ok" })
[string[]]$runtimePlaneBlockingReasons = @($blockingReasons | Where-Object { $_ -ne "self_health_not_ok" })
$acceptedExitCode = [bool]($processResult.exitCode -in @(0, 2))
$runtimePlaneReady = [bool](
$acceptedExitCode -and
-not $processResult.timedOut -and
$payload -and
$payload.manifest -and
[bool]$payload.manifest.runtimeMatched -and
$summary -and
[int]$summary.requiredTaskCount -gt 0 -and
[int]$summary.healthyTaskCount -eq [int]$summary.requiredTaskCount -and
[int]$summary.taskFailureCount -eq 0 -and
[int]$summary.requiredEvidenceFailureCount -eq 0 -and
[int]$summary.requiredEvidenceParseFailureCount -eq 0 -and
[int]$summary.requiredEvidenceContentFailureCount -eq 0 -and
[int]$summary.relayListenerCount -gt 0 -and
[int]$summary.alertIncomingStaleCount -eq 0 -and
[int]$summary.alertRunningCount -eq 0 -and
[int]$summary.pendingCommandCount -eq 0 -and
$runtimePlaneBlockingReasons.Count -eq 0
)
return [pscustomobject]@{
ok = $runtimePlaneReady
exitCode = [int]$processResult.exitCode
acceptedExitCode = $acceptedExitCode
timedOut = [bool]$processResult.timedOut
parsed = [bool]($null -ne $payload)
sourceRevision = if ($payload -and $payload.manifest) { [string]$payload.manifest.sourceRevision } else { "" }
runtimeMatched = [bool]($payload -and $payload.manifest -and $payload.manifest.runtimeMatched)
healthyTaskCount = if ($summary) { [int]$summary.healthyTaskCount } else { 0 }
requiredTaskCount = if ($summary) { [int]$summary.requiredTaskCount } else { 0 }
taskFailureCount = if ($summary) { [int]$summary.taskFailureCount } else { -1 }
requiredEvidenceFailureCount = if ($summary) { [int]$summary.requiredEvidenceFailureCount } else { -1 }
requiredEvidenceParseFailureCount = if ($summary) { [int]$summary.requiredEvidenceParseFailureCount } else { -1 }
requiredEvidenceContentFailureCount = if ($summary) { [int]$summary.requiredEvidenceContentFailureCount } else { -1 }
relayListenerCount = if ($summary) { [int]$summary.relayListenerCount } else { 0 }
relayListenerGenerations = if ($payload) {
@($payload.relayListeners | ForEach-Object {
$processId = [int]$_.processId
$process = Get-Process -Id $processId -ErrorAction SilentlyContinue
if ($process) {
try {
$startedAt = $process.StartTime.ToUniversalTime().ToString("o")
Get-AgentSha256Bytes ([Text.Encoding]::UTF8.GetBytes("$processId|$startedAt"))
} catch {}
}
} | Where-Object { $_ } | Sort-Object -Unique)
} else { @() }
alertIncomingCount = if ($summary) { [int]$summary.alertIncomingCount } else { -1 }
alertIncomingStaleCount = if ($summary) { [int]$summary.alertIncomingStaleCount } else { -1 }
alertRunningCount = if ($summary) { [int]$summary.alertRunningCount } else { -1 }
pendingCommandCount = if ($summary) { [int]$summary.pendingCommandCount } else { -1 }
preflightGreen = [bool]($summary -and $summary.preflightGreen)
overallPreflightGreen = [bool]($summary -and $summary.preflightGreen)
runtimePlaneReady = $runtimePlaneReady
externalDegraded = [bool]($runtimePlaneReady -and $externalBlockingReasons.Count -gt 0)
blockingReasons = $blockingReasons
runtimePlaneBlockingReasons = $runtimePlaneBlockingReasons
externalBlockingReasons = $externalBlockingReasons
}
}
function Get-AgentRelayRuntime {
$task = $null
$taskError = ""
try {
$tasks = @(Get-ScheduledTask -TaskPath $RelayTaskPath -TaskName $RelayTaskName -ErrorAction Stop)
if ($tasks.Count -ne 1) {
$taskError = "exact_relay_task_count_$($tasks.Count)"
} else {
$task = $tasks[0]
}
} catch {
$taskError = $_.Exception.GetType().Name
}
$taskInfoError = ""
$taskLastRunTime = ""
$taskLastTaskResult = $null
$taskGeneration = ""
if ($task) {
try {
$taskInfo = Get-ScheduledTaskInfo -TaskPath $RelayTaskPath -TaskName $RelayTaskName -ErrorAction Stop
$lastRunTime = [datetime]$taskInfo.LastRunTime
if ($lastRunTime.Year -gt 1900) {
$taskLastRunTime = $lastRunTime.ToUniversalTime().ToString("o")
$taskGeneration = Get-AgentSha256Bytes ([Text.Encoding]::UTF8.GetBytes("$RelayTaskName|$taskLastRunTime"))
}
$taskLastTaskResult = [long]$taskInfo.LastTaskResult
} catch {
$taskInfoError = $_.Exception.GetType().Name
}
}
$listeners = @()
$listenerError = ""
try {
$connections = @(Get-NetTCPConnection -LocalPort $RelayPort -State Listen -ErrorAction Stop)
foreach ($connection in $connections) {
$processId = [int]$connection.OwningProcess
$process = Get-Process -Id $processId -ErrorAction SilentlyContinue
$startedAt = ""
$generation = ""
if ($process) {
try {
$startedAt = $process.StartTime.ToUniversalTime().ToString("o")
$generation = Get-AgentSha256Bytes ([Text.Encoding]::UTF8.GetBytes("$processId|$startedAt"))
} catch {}
}
$listeners += [pscustomobject]@{
address = [string]$connection.LocalAddress
port = [int]$connection.LocalPort
processId = $processId
processName = if ($process) { [string]$process.ProcessName } else { "" }
startedAt = $startedAt
generation = $generation
}
}
} catch {
$listenerError = $_.Exception.GetType().Name
}
$processIds = @($listeners | ForEach-Object { [int]$_.processId } | Sort-Object -Unique)
$listenerGenerations = @($listeners | ForEach-Object { [string]$_.generation } | Where-Object { $_ } | Sort-Object -Unique)
$taskEnabled = $false
if ($task) {
try { $taskEnabled = [bool]$task.Settings.Enabled } catch { $taskEnabled = $false }
}
return [pscustomobject]@{
taskName = $RelayTaskName
taskPath = $RelayTaskPath
taskExists = [bool]($null -ne $task)
taskState = if ($task) { [string]$task.State } else { "" }
taskRunning = [bool]($task -and [string]$task.State -eq "Running")
taskEnabled = $taskEnabled
taskError = $taskError
taskInfoError = $taskInfoError
taskLastRunTime = $taskLastRunTime
taskLastTaskResult = $taskLastTaskResult
taskGeneration = $taskGeneration
taskGenerationReady = [bool]$taskGeneration
listenerPort = $RelayPort
listenerCount = $listeners.Count
listenerProcessCount = $processIds.Count
listenerGenerations = $listenerGenerations
generationReady = [bool]($taskGeneration -and $listeners.Count -gt 0 -and $processIds.Count -gt 0 -and $listenerGenerations.Count -eq $processIds.Count)
listeners = $listeners
listenerError = $listenerError
}
}
function Test-AgentGenerationIntersection {
param([string[]]$Left, [string[]]$Right)
foreach ($value in @($Left)) {
if ($value -and $value -in @($Right)) { return $true }
}
return $false
}
function Invoke-AgentRelayTaskRestart {
param([string[]]$ForbiddenGenerations = @())
$before = Get-AgentRelayRuntime
$generationCandidates = @($ForbiddenGenerations) + @($before.taskGeneration)
$forbidden = @($generationCandidates | Where-Object { $_ } | Sort-Object -Unique)
if (-not $before.taskExists -or -not $before.taskEnabled) {
return [pscustomobject]@{
attempted = $false
ok = $false
stage = "precondition"
reason = if ($before.taskExists) { "exact_existing_relay_task_disabled" } else { "exact_existing_relay_task_missing" }
before = $before
after = $before
forbiddenGenerations = $forbidden
oldGenerationsGone = $false
newGenerationVerified = $false
listenerStopObserved = $false
taskGenerationChanged = $false
}
}
$stage = "stop"
$afterStop = $before
$after = $before
$listenerStopObserved = $false
$taskGenerationChanged = $false
try {
Stop-ScheduledTask -TaskPath $RelayTaskPath -TaskName $RelayTaskName -ErrorAction Stop
$stopDeadline = (Get-Date).AddSeconds(60)
do {
Start-Sleep -Seconds 1
$afterStop = Get-AgentRelayRuntime
$listenerStopObserved = [bool]($afterStop.listenerCount -eq 0)
$stopVerified = [bool]($listenerStopObserved -and -not $afterStop.taskRunning)
if ($stopVerified) { break }
} while ((Get-Date) -lt $stopDeadline)
if (-not $stopVerified) { throw "relay_old_listener_generation_stop_timeout" }
$stage = "start"
Start-ScheduledTask -TaskPath $RelayTaskPath -TaskName $RelayTaskName -ErrorAction Stop
$startDeadline = (Get-Date).AddSeconds(120)
do {
Start-Sleep -Seconds 1
$after = Get-AgentRelayRuntime
$newTaskGeneration = [string]$after.taskGeneration
$taskGenerationChanged = [bool]($newTaskGeneration -and $newTaskGeneration -notin $forbidden)
$newVerified = [bool](
$after.taskRunning -and
$after.taskEnabled -and
$after.generationReady -and
$after.listenerCount -gt 0 -and
$listenerStopObserved -and
$taskGenerationChanged
)
if ($newVerified) { break }
} while ((Get-Date) -lt $startDeadline)
if (-not $newVerified) { throw "relay_new_listener_generation_start_timeout" }
return [pscustomobject]@{
attempted = $true
ok = $true
stage = "verified"
reason = "exact_existing_relay_task_restarted"
before = $before
afterStop = $afterStop
after = $after
forbiddenGenerations = $forbidden
oldGenerationsGone = $listenerStopObserved
newGenerationVerified = $newVerified
listenerStopObserved = $listenerStopObserved
taskGenerationChanged = $taskGenerationChanged
}
} catch {
return [pscustomobject]@{
attempted = $true
ok = $false
stage = $stage
reason = "relay_task_restart_$($stage)_failed"
errorType = $_.Exception.GetType().Name
before = $before
afterStop = $afterStop
after = (Get-AgentRelayRuntime)
forbiddenGenerations = $forbidden
oldGenerationsGone = $listenerStopObserved
newGenerationVerified = $false
listenerStopObserved = $listenerStopObserved
taskGenerationChanged = $taskGenerationChanged
}
}
}
function Copy-AgentFileWithRetry {
param([string]$Source, [string]$Destination)
for ($attempt = 1; $attempt -le 20; $attempt += 1) {
try {
Copy-Item -LiteralPath $Source -Destination $Destination -Force
return
} catch [System.IO.IOException] {
if ($attempt -ge 20) { throw }
Start-Sleep -Milliseconds 500
}
}
}
function Test-AgentBackupPath {
param([string]$BackupPath)
if (-not $BackupPath) { return $false }
try {
$expectedPrefix = [IO.Path]::GetFullPath((Join-Path $DeployRoot "backup-"))
$candidate = [IO.Path]::GetFullPath($BackupPath)
return [bool]($candidate.StartsWith($expectedPrefix, [StringComparison]::OrdinalIgnoreCase))
} catch {
return $false
}
}
function Restore-AgentBundle {
param(
[string]$BackupPath,
[hashtable]$BeforeFilePresence,
[bool]$BeforeLauncherPresent,
[bool]$BeforeConfigPresent,
[bool]$BeforeManifestPresent
)
if (-not (Test-AgentBackupPath $BackupPath) -or -not (Test-Path -LiteralPath $BackupPath -PathType Container)) {
return [pscustomobject]@{ attempted = $false; restored = $false; reason = "bounded_backup_path_unavailable" }
}
try {
foreach ($name in $FixedRuntimeFiles) {
$livePath = Join-Path $BinDir $name
$backupFile = Join-Path $BackupPath $name
if ($BeforeFilePresence[$name]) {
if (-not (Test-Path -LiteralPath $backupFile -PathType Leaf)) { throw "required_runtime_backup_missing" }
Copy-AgentFileWithRetry $backupFile $livePath
} elseif (Test-Path -LiteralPath $livePath -PathType Leaf) {
Remove-Item -LiteralPath $livePath -Force
}
}
$backupLauncher = Join-Path $BackupPath "agent99-run.ps1"
if ($BeforeLauncherPresent) {
if (-not (Test-Path -LiteralPath $backupLauncher -PathType Leaf)) { throw "required_launcher_backup_missing" }
Copy-AgentFileWithRetry $backupLauncher $LauncherPath
} elseif (Test-Path -LiteralPath $LauncherPath -PathType Leaf) {
Remove-Item -LiteralPath $LauncherPath -Force
}
$backupConfig = Join-Path $BackupPath "agent99.config.json"
if ($BeforeConfigPresent) {
if (-not (Test-Path -LiteralPath $backupConfig -PathType Leaf)) { throw "required_config_backup_missing" }
Copy-AgentFileWithRetry $backupConfig $ConfigPath
} elseif (Test-Path -LiteralPath $ConfigPath -PathType Leaf) {
Remove-Item -LiteralPath $ConfigPath -Force
}
$backupManifest = Join-Path $BackupPath "runtime-manifest.json"
if ($BeforeManifestPresent) {
if (-not (Test-Path -LiteralPath $backupManifest -PathType Leaf)) { throw "required_manifest_backup_missing" }
Copy-AgentFileWithRetry $backupManifest $ManifestPath
} elseif (Test-Path -LiteralPath $ManifestPath -PathType Leaf) {
Remove-Item -LiteralPath $ManifestPath -Force
}
return [pscustomobject]@{ attempted = $true; restored = $true; reason = "bounded_bundle_restored" }
} catch {
return [pscustomobject]@{ attempted = $true; restored = $false; reason = $_.Exception.GetType().Name }
}
}
function Write-AgentRemoteDeployReceipt {
param([string]$Path, [object]$Receipt)
if (Test-Path -LiteralPath $Path) { throw "immutable_receipt_path_already_exists" }
$Receipt | Add-Member -NotePropertyName transportPayloadMode -NotePropertyValue $script:ReceiverTransportPayloadMode -Force
$Receipt | Add-Member -NotePropertyName transientTransportPayloadStored -NotePropertyValue $script:ReceiverTransientTransportPayloadStored -Force
$Receipt | Add-Member -NotePropertyName transientTransportPayloadDeletedBeforeReceiver -NotePropertyValue $script:ReceiverTransportPayloadDeletedBeforeReceiver -Force
$Receipt | Add-Member -NotePropertyName transportPayloadContainsSecrets -NotePropertyValue $false -Force
$Receipt | Add-Member -NotePropertyName rawEnvelopeStored -NotePropertyValue $false -Force
$temporary = "$Path.tmp.$PID.$([Guid]::NewGuid().ToString('N'))"
try {
$json = $Receipt | ConvertTo-Json -Depth 12
$null = $json | ConvertFrom-Json
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
$script:ReceiverRemoteWritePerformed = $true
[IO.File]::WriteAllText($temporary, $json, $utf8NoBom)
$storedJson = [IO.File]::ReadAllText($temporary, [Text.Encoding]::UTF8)
$null = $storedJson | ConvertFrom-Json
if (Test-Path -LiteralPath $Path) { throw "immutable_receipt_path_already_exists" }
# File.Move is the final operation: on Windows PowerShell/.NET it is atomic
# within this volume and refuses to replace an existing destination.
[IO.File]::Move($temporary, $Path)
} catch {
if (Test-Path -LiteralPath $temporary) {
Remove-Item -LiteralPath $temporary -Force -ErrorAction SilentlyContinue
}
throw
}
}
function Write-AgentResultAndExit {
param([object]$Result, [int]$ExitCode)
$Result | Add-Member -NotePropertyName transportPayloadMode -NotePropertyValue $script:ReceiverTransportPayloadMode -Force
$Result | Add-Member -NotePropertyName transientTransportPayloadStored -NotePropertyValue $script:ReceiverTransientTransportPayloadStored -Force
$Result | Add-Member -NotePropertyName transientTransportPayloadDeletedBeforeReceiver -NotePropertyValue $script:ReceiverTransportPayloadDeletedBeforeReceiver -Force
$Result | Add-Member -NotePropertyName transportPayloadContainsSecrets -NotePropertyValue $false -Force
$Result | Add-Member -NotePropertyName rawEnvelopeStored -NotePropertyValue $false -Force
$Result | ConvertTo-Json -Compress -Depth 12
exit $ExitCode
}
try {
$envelope = $EnvelopeJson | ConvertFrom-Json
if ([string]$envelope.schemaVersion -ne "agent99_remote_atomic_deploy_envelope_v1") { throw "invalid_envelope_schema" }
if ([string]$envelope.sourceHostAlias -ne "110") { throw "invalid_source_host_alias" }
if ([string]$envelope.targetHostAlias -ne "99") { throw "invalid_target_host_alias" }
if ([string]$envelope.transport -ne "110_to_99_ssh_publickey") { throw "invalid_transport" }
$mode = [string]$envelope.mode
if ($mode -notin @("check", "apply")) { throw "invalid_mode" }
$transportPayloadMode = [string]$envelope.transportPayloadMode
$transportPayloadContainsSecrets = [bool]$envelope.transportPayloadContainsSecrets
$expectedTransportPayloadMode = if ($mode -eq "apply") {
"ephemeral_file_delete_before_receiver"
} else {
"control_command_no_stdin"
}
if ($transportPayloadMode -ne $expectedTransportPayloadMode) { throw "invalid_transport_payload_mode" }
if ($transportPayloadContainsSecrets) { throw "transport_payload_secret_contract_failed" }
$script:ReceiverTransportPayloadMode = $transportPayloadMode
$script:ReceiverTransientTransportPayloadStored = [bool]($mode -eq "apply")
if ($mode -eq "apply") {
if (-not $TransportPayloadDeletedBeforeReceiver) {
throw "transport_payload_delete_before_receiver_unverified"
}
$script:ReceiverTransportPayloadDeletedBeforeReceiver = $true
} elseif ($TransportPayloadDeletedBeforeReceiver) {
throw "unexpected_transport_payload_delete_receipt"
}
$sourceRevision = ([string]$envelope.sourceRevision).ToLowerInvariant()
if ($sourceRevision -notmatch "^[0-9a-f]{40}$") { throw "invalid_source_revision" }
if ([int]$envelope.expectedRuntimeFileCount -ne 14) { throw "invalid_expected_runtime_file_count" }
$traceId = [string]$envelope.traceId
$runId = [string]$envelope.runId
$workItemId = [string]$envelope.workItemId
if ($mode -eq "apply") {
foreach ($identity in @($traceId, $runId, $workItemId)) {
if (-not (Test-AgentSafeIdentity $identity)) { throw "apply_identity_missing_or_invalid" }
}
}
$filesByName = @{}
foreach ($file in @($envelope.files)) {
$name = [string]$file.name
if ($filesByName.ContainsKey($name)) { throw "duplicate_runtime_file" }
$filesByName[$name] = $file
}
if ($filesByName.Count -ne $FixedRuntimeFiles.Count) { throw "runtime_file_count_mismatch" }
$manifestText = ""
$decodedFiles = @{}
foreach ($name in $FixedRuntimeFiles) {
if (-not $filesByName.ContainsKey($name)) { throw "required_runtime_file_missing" }
$row = $filesByName[$name]
$bytes = [Convert]::FromBase64String([string]$row.contentBase64)
$actualSha = Get-AgentSha256Bytes $bytes
if ($actualSha -ne ([string]$row.sha256).ToLowerInvariant()) { throw "runtime_file_sha256_mismatch" }
$decodedFiles[$name] = $bytes
$manifestText += "$name`t$actualSha`n"
}
$manifestDigest = Get-AgentSha256Bytes ([Text.Encoding]::UTF8.GetBytes($manifestText))
if ($manifestDigest -ne ([string]$envelope.manifestSha256).ToLowerInvariant()) { throw "manifest_sha256_mismatch" }
$sourceManifest = [pscustomobject]@{
schemaVersion = "agent99_remote_source_manifest_v1"
sourceRevision = $sourceRevision
fileCount = 14
manifestSha256 = $manifestDigest
files = @($FixedRuntimeFiles | ForEach-Object {
[pscustomobject]@{ name = $_; sha256 = ([string]($filesByName[$_].sha256)).ToLowerInvariant() }
})
}
$preflight = $envelope.livePreflight
if (-not $preflight -or [string]$preflight.name -ne "agent99-live-preflight.ps1") { throw "live_preflight_missing" }
$preflightBytes = [Convert]::FromBase64String([string]$preflight.contentBase64)
if ((Get-AgentSha256Bytes $preflightBytes) -ne ([string]$preflight.sha256).ToLowerInvariant()) { throw "live_preflight_sha256_mismatch" }
$stageIdentity = "$mode|$traceId|$runId|$workItemId|$sourceRevision"
$stageToken = (Get-AgentSha256Bytes ([Text.Encoding]::UTF8.GetBytes($stageIdentity))).Substring(0, 20)
$stagePath = Join-Path $DeployRoot "remote-source-$stageToken"
$receiptPath = Join-Path $EvidenceDir "agent99-remote-deploy-$stageToken.json"
$attemptStamp = [DateTime]::UtcNow.ToString(
"yyyyMMdd'T'HHmmssfffffff'Z'",
[Globalization.CultureInfo]::InvariantCulture
)
$attemptToken = "$attemptStamp-$PID-$([Guid]::NewGuid().ToString('N'))"
$attemptReceiptPath = Join-Path $EvidenceDir "agent99-remote-deploy-$stageToken-attempt-$attemptToken.json"
$script:ReceiverAttemptReceiptPath = $attemptReceiptPath
$script:ReceiverCanonicalReceiptPath = $receiptPath
$script:ReceiverTraceId = $traceId
$script:ReceiverRunId = $runId
$script:ReceiverWorkItemId = $workItemId
$script:ReceiverSourceRevision = $sourceRevision
$script:ReceiverManifestDigest = $manifestDigest
$currentManifest = Get-AgentSafeRuntimeManifest
if ($mode -eq "check") {
Write-AgentResultAndExit ([pscustomobject]@{
schemaVersion = "agent99_remote_atomic_deploy_receipt_v1"
status = "check_ready"
mode = "check"
sourceRevision = $sourceRevision
expectedRuntimeFileCount = 14
manifestSha256 = $manifestDigest
plannedStagingPath = $stagePath
runtimeManifest = $currentManifest
livePreflight = [pscustomobject]@{ executed = $false; reason = "apply_only_after_validate_only" }
operationBoundaries = [pscustomobject]@{
remoteWritePerformed = $false
liveRuntimeWritePerformed = $false
livePromotionAttempted = $false
livePromotionPerformed = $false
secretValueRead = $false
privateKeyValueRead = $false
tokenValueRead = $false
environmentSecretRead = $false
uiInteraction = $false
vmPowerChange = $false
hostReboot = $false
serviceRestart = $false
scheduledTaskRestart = $false
scheduledTaskModification = $false
}
nextSafeAction = "rerun_with_apply_and_trace_run_work_item_after_source_commit_and_transport_check"
}) 0
}
$script:ReceiverOperationPhase = "apply_state_initialization"
$script:ReceiverRemoteWritePerformed = $true
New-Item -ItemType Directory -Force -Path $StateDir, $EvidenceDir, $DeployRoot | Out-Null
$lockPath = Join-Path $StateDir "remote-atomic-deploy.lock"
try {
$script:DeployLock = [IO.File]::Open($lockPath, [IO.FileMode]::OpenOrCreate, [IO.FileAccess]::ReadWrite, [IO.FileShare]::None)
} catch {
Write-AgentResultAndExit ([pscustomobject]@{
schemaVersion = "agent99_remote_atomic_deploy_receipt_v1"
status = "blocked_single_flight_lock_unavailable"
mode = "apply"
traceId = $traceId
runId = $runId
workItemId = $workItemId
sourceRevision = $sourceRevision
operationPhase = $script:ReceiverOperationPhase
remoteWritePerformed = $script:ReceiverRemoteWritePerformed
liveRuntimeWritePerformed = $false
livePromotionAttempted = $false
livePromotionPerformed = $false
}) 75
}
$canonicalReceiptExists = $false
$prior = $null
$priorParseErrorType = ""
if (Test-Path -LiteralPath $receiptPath -PathType Leaf) {
$canonicalReceiptExists = $true
try {
$prior = Get-Content -LiteralPath $receiptPath -Raw | ConvertFrom-Json
if (-not $prior) { $priorParseErrorType = "NullCanonicalReceipt" }
} catch {
$prior = $null
$priorParseErrorType = $_.Exception.GetType().Name
}
}
$script:ReceiverOperationPhase = "staging"
if (-not (Test-Path -LiteralPath $stagePath -PathType Container)) {
$stageBuildPath = "$stagePath.partial.$PID.$([Guid]::NewGuid().ToString('N'))"
New-Item -ItemType Directory -Path $stageBuildPath | Out-Null
foreach ($name in $FixedRuntimeFiles) {
[IO.File]::WriteAllBytes((Join-Path $stageBuildPath $name), [byte[]]$decodedFiles[$name])
}
[IO.File]::WriteAllBytes((Join-Path $stageBuildPath "agent99-live-preflight.ps1"), $preflightBytes)
$sourceManifest | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Join-Path $stageBuildPath "source-manifest.json") -Encoding UTF8
Move-Item -LiteralPath $stageBuildPath -Destination $stagePath
$script:ReceiverRemoteWritePerformed = $true
} else {
foreach ($name in $FixedRuntimeFiles) {
if ((Get-AgentSha256File (Join-Path $stagePath $name)) -ne ([string]($filesByName[$name].sha256)).ToLowerInvariant()) {
throw "existing_staging_file_mismatch"
}
}
if ((Get-AgentSha256File (Join-Path $stagePath "agent99-live-preflight.ps1")) -ne ([string]$preflight.sha256).ToLowerInvariant()) {
throw "existing_staging_preflight_mismatch"
}
try {
$existingSourceManifest = Get-Content -LiteralPath (Join-Path $stagePath "source-manifest.json") -Raw | ConvertFrom-Json
if (
[string]$existingSourceManifest.schemaVersion -ne "agent99_remote_source_manifest_v1" -or
[string]$existingSourceManifest.sourceRevision -ne $sourceRevision -or
[int]$existingSourceManifest.fileCount -ne 14 -or
[string]$existingSourceManifest.manifestSha256 -ne $manifestDigest
) { throw "existing_staging_manifest_mismatch" }
} catch {
throw "existing_staging_manifest_invalid"
}
}
$live = Get-AgentSafeRuntimeManifest
$liveLauncherContract = Get-AgentLauncherContract
if (
$prior -and
[string]$prior.status -eq "deployed_verified" -and
[string]$prior.traceId -eq $traceId -and
[string]$prior.runId -eq $runId -and
[string]$prior.workItemId -eq $workItemId -and
[string]$prior.sourceRevision -eq $sourceRevision -and
$prior.PSObject.Properties["relayReload"] -and
[bool]$prior.relayReload.newGenerationVerified -and
$prior.PSObject.Properties["launcherContract"] -and
[bool]$prior.launcherContract.ok -and
[string]$prior.launcherContract.sha256 -match "^[0-9a-f]{64}$" -and
$live.runtimeMatched -and
$live.sourceRevision -eq $sourceRevision -and
$live.fileCount -eq 14 -and
$live.mismatchCount -eq 0 -and
$liveLauncherContract.ok -and
[string]$liveLauncherContract.sha256 -eq [string]$prior.launcherContract.sha256
) {
$script:ReceiverOperationPhase = "idempotent_replay_post_verifier"
$replayPreflight = Invoke-AgentLivePreflight (Join-Path $stagePath "agent99-live-preflight.ps1")
if ($replayPreflight.ok -and $replayPreflight.sourceRevision -eq $sourceRevision -and $replayPreflight.runtimeMatched) {
$originalOperationBoundaries = $prior.operationBoundaries
$prior | Add-Member -NotePropertyName idempotentReplay -NotePropertyValue $true -Force
$prior | Add-Member -NotePropertyName runtimeManifest -NotePropertyValue $live -Force
$prior | Add-Member -NotePropertyName launcherContract -NotePropertyValue $liveLauncherContract -Force
$prior | Add-Member -NotePropertyName livePreflight -NotePropertyValue $replayPreflight -Force
$prior | Add-Member -NotePropertyName originalOperationBoundaries -NotePropertyValue $originalOperationBoundaries -Force
$prior | Add-Member -NotePropertyName operationBoundaries -NotePropertyValue ([pscustomobject]@{
remoteWritePerformed = $script:ReceiverRemoteWritePerformed
liveRuntimeWritePerformed = $false
livePromotionAttempted = $false
livePromotionPerformed = $false
secretValueRead = $false
privateKeyValueRead = $false
tokenValueRead = $false
environmentSecretRead = $false
uiInteraction = $false
vmPowerChange = $false
hostReboot = $false
serviceRestart = $false
scheduledTaskRestart = $false
scheduledTaskModification = $false
}) -Force
Write-AgentResultAndExit $prior 0
}
$receipt = [pscustomobject]@{
schemaVersion = "agent99_remote_atomic_deploy_receipt_v1"
status = "idempotent_replay_post_verifier_failed_no_promotion"
mode = "apply"
traceId = $traceId
runId = $runId
workItemId = $workItemId
sourceRevision = $sourceRevision
manifestSha256 = $manifestDigest
stagingPath = $stagePath
canonicalReceiptPath = $receiptPath
attemptReceiptPath = $attemptReceiptPath
runtimeManifest = $live
livePreflight = $replayPreflight
durableReceiptWritten = $true
remoteWritePerformed = $script:ReceiverRemoteWritePerformed
liveRuntimeWritePerformed = $false
livePromotionAttempted = $false
livePromotionPerformed = $false
secretValueRead = $false
privateKeyValueRead = $false
tokenValueRead = $false
environmentSecretRead = $false
nextSafeAction = "repair_reported_live_preflight_blocker_then_retry_same_identity"
}
$script:ReceiverOperationPhase = "idempotent_replay_failure_receipt_write"
Write-AgentRemoteDeployReceipt $attemptReceiptPath $receipt
Write-AgentResultAndExit $receipt 2
}
if ($canonicalReceiptExists) {
$canonicalStatus = if (-not $prior) {
"blocked_canonical_receipt_invalid_no_promotion"
} elseif ([string]$prior.status -eq "deployed_verified") {
"blocked_canonical_success_receipt_runtime_drift_no_promotion"
} else {
"blocked_canonical_receipt_reserved_no_promotion"
}
$receipt = [pscustomobject]@{
schemaVersion = "agent99_remote_atomic_deploy_receipt_v1"
status = $canonicalStatus
mode = "apply"
traceId = $traceId
runId = $runId
workItemId = $workItemId
sourceRevision = $sourceRevision
manifestSha256 = $manifestDigest
stagingPath = $stagePath
canonicalReceiptPath = $receiptPath
attemptReceiptPath = $attemptReceiptPath
canonicalReceiptStatus = if ($prior) { [string]$prior.status } else { "unparseable" }
canonicalReceiptParseErrorType = $priorParseErrorType
runtimeManifest = $live
durableReceiptWritten = $true
remoteWritePerformed = $script:ReceiverRemoteWritePerformed
liveRuntimeWritePerformed = $false
livePromotionAttempted = $false
livePromotionPerformed = $false
secretValueRead = $false
privateKeyValueRead = $false
tokenValueRead = $false
environmentSecretRead = $false
nextSafeAction = "preserve_immutable_canonical_receipt_and_retry_with_new_trace_run_work_item_identity"
}
$script:ReceiverOperationPhase = "canonical_receipt_reserved_failure_receipt_write"
Write-AgentRemoteDeployReceipt $attemptReceiptPath $receipt
Write-AgentResultAndExit $receipt 2
}
$script:ReceiverOperationPhase = "validate_only"
$validate = Invoke-AgentDeployChild -SourceRoot $stagePath -SourceRevision $sourceRevision -ValidateOnly
if (-not $validate.ok -or -not $validate.payload -or [string]$validate.payload.status -ne "validated") {
$receipt = [pscustomobject]@{
schemaVersion = "agent99_remote_atomic_deploy_receipt_v1"
status = "validate_only_failed_no_promotion"
mode = "apply"
traceId = $traceId
runId = $runId
workItemId = $workItemId
sourceRevision = $sourceRevision
manifestSha256 = $manifestDigest
stagingPath = $stagePath
canonicalReceiptPath = $receiptPath
attemptReceiptPath = $attemptReceiptPath
validateOnly = [pscustomobject]@{ ok = $validate.ok; exitCode = $validate.exitCode; timedOut = $validate.timedOut }
durableReceiptWritten = $true
remoteWritePerformed = $script:ReceiverRemoteWritePerformed
liveRuntimeWritePerformed = $false
livePromotionAttempted = $false
livePromotionPerformed = $false
secretValueRead = $false
privateKeyValueRead = $false
tokenValueRead = $false
environmentSecretRead = $false
nextSafeAction = "repair_validate_only_blocker_then_retry_same_identity"
}
$script:ReceiverOperationPhase = "validate_only_failure_receipt_write"
Write-AgentRemoteDeployReceipt $attemptReceiptPath $receipt
Write-AgentResultAndExit $receipt 2
}
$beforeManifest = Get-AgentSafeRuntimeManifest
$beforeBundleDigest = Get-AgentLiveBundleDigest
$beforeConfigDigest = Get-AgentSha256File $ConfigPath
$beforeManifestDigest = Get-AgentSha256File $ManifestPath
$beforeFilePresence = @{}
foreach ($name in $FixedRuntimeFiles) { $beforeFilePresence[$name] = Test-Path -LiteralPath (Join-Path $BinDir $name) -PathType Leaf }
$beforeLauncherPresent = Test-Path -LiteralPath $LauncherPath -PathType Leaf
$beforeConfigPresent = Test-Path -LiteralPath $ConfigPath -PathType Leaf
$beforeManifestPresent = Test-Path -LiteralPath $ManifestPath -PathType Leaf
$beforeRelayRuntime = Get-AgentRelayRuntime
$backupDirsBefore = @{}
foreach ($directory in @(Get-ChildItem -LiteralPath $DeployRoot -Directory -Filter "backup-*" -ErrorAction SilentlyContinue)) {
$backupDirsBefore[$directory.FullName] = $true
}
if (
-not $beforeRelayRuntime.taskExists -or
-not $beforeRelayRuntime.taskEnabled -or
-not $beforeRelayRuntime.taskRunning -or
-not $beforeRelayRuntime.generationReady -or
$beforeRelayRuntime.listenerCount -lt 1
) {
$receipt = [pscustomobject]@{
schemaVersion = "agent99_remote_atomic_deploy_receipt_v1"
status = "blocked_relay_runtime_precondition_no_promotion"
mode = "apply"
traceId = $traceId
runId = $runId
workItemId = $workItemId
sourceRevision = $sourceRevision
manifestSha256 = $manifestDigest
stagingPath = $stagePath
canonicalReceiptPath = $receiptPath
attemptReceiptPath = $attemptReceiptPath
validateOnly = [pscustomobject]@{ ok = $validate.ok; exitCode = $validate.exitCode; timedOut = $validate.timedOut }
relayRuntime = $beforeRelayRuntime
durableReceiptWritten = $true
remoteWritePerformed = $script:ReceiverRemoteWritePerformed
liveRuntimeWritePerformed = $false
livePromotionAttempted = $false
livePromotionPerformed = $false
secretValueRead = $false
privateKeyValueRead = $false
tokenValueRead = $false
environmentSecretRead = $false
uiInteraction = $false
vmPowerChange = $false
hostReboot = $false
serviceRestart = $false
scheduledTaskRestart = $false
scheduledTaskModification = $false
nextSafeAction = "repair_exact_existing_relay_task_runtime_then_retry_same_identity"
}
$script:ReceiverOperationPhase = "relay_precondition_failure_receipt_write"
Write-AgentRemoteDeployReceipt $attemptReceiptPath $receipt
Write-AgentResultAndExit $receipt 2
}
$deploy = $null
$relayReload = $null
$runtimeManifest = $null
$livePreflight = $null
$launcherContract = $null
$postVerified = $false
$failurePhase = ""
$failureType = ""
$backupPath = ""
try {
$script:ReceiverOperationPhase = "live_deploy"
$script:ReceiverLiveRuntimeWritePerformed = $true
$script:ReceiverLivePromotionAttempted = $true
$deploy = Invoke-AgentDeployChild -SourceRoot $stagePath -SourceRevision $sourceRevision
if ($deploy.payload -and $deploy.payload.PSObject.Properties["backupDir"]) {
$backupPath = [string]$deploy.payload.backupDir
}
if (
-not $deploy.ok -or
-not $deploy.payload -or
[string]$deploy.payload.status -ne "deployed" -or
$deploy.payload.taskRegistration -or
-not $deploy.payload.PSObject.Properties["launcherContract"] -or
-not [bool]$deploy.payload.launcherContract.ok -or
[string]$deploy.payload.launcherContract.sha256 -notmatch "^[0-9a-f]{64}$"
) {
$failurePhase = "deploy"
$failureType = "deploy_child_failed_or_invalid"
} else {
$script:ReceiverLivePromotionPerformed = $true
$script:ReceiverOperationPhase = "relay_reload"
$script:ReceiverScheduledTaskRestartPerformed = $true
$relayReload = Invoke-AgentRelayTaskRestart -ForbiddenGenerations @($beforeRelayRuntime.taskGeneration)
if (-not $relayReload.ok) {
$failurePhase = "relay_reload"
$failureType = "relay_reload_failed"
} else {
$script:ReceiverOperationPhase = "independent_post_verifier"
$runtimeManifest = Get-AgentSafeRuntimeManifest
$launcherContract = Get-AgentLauncherContract
$livePreflight = Invoke-AgentLivePreflight (Join-Path $stagePath "agent99-live-preflight.ps1")
$postVerified = [bool](
$runtimeManifest.exists -and
$runtimeManifest.runtimeMatched -and
$runtimeManifest.sourceRevision -eq $sourceRevision -and
$runtimeManifest.fileCount -eq 14 -and
$runtimeManifest.mismatchCount -eq 0 -and
$launcherContract.ok -and
[string]$launcherContract.sha256 -eq [string]$deploy.payload.launcherContract.sha256 -and
$relayReload.oldGenerationsGone -and
$relayReload.newGenerationVerified -and
$relayReload.after.taskEnabled -and
$relayReload.after.taskRunning -and
$livePreflight.ok -and
$livePreflight.sourceRevision -eq $sourceRevision -and
$livePreflight.runtimeMatched -and
$livePreflight.relayListenerCount -gt 0 -and
(Test-AgentGenerationIntersection @($livePreflight.relayListenerGenerations) @($relayReload.after.listenerGenerations))
)
if (-not $postVerified) {
$failurePhase = "post_verifier"
$failureType = "independent_post_verifier_failed"
}
}
}
} catch {
$failurePhase = if ($deploy -and $deploy.ok) { "reload_or_post_verifier_exception" } else { "deploy_exception" }
$failureType = $_.Exception.GetType().Name
}
$successReceipt = $null
if (-not $failurePhase) {
try {
$script:ReceiverOperationPhase = "success_receipt_construction"
$successReceipt = [pscustomobject]@{
schemaVersion = "agent99_remote_atomic_deploy_receipt_v1"
status = "deployed_verified"
mode = "apply"
traceId = $traceId
runId = $runId
workItemId = $workItemId
sourceRevision = $sourceRevision
manifestSha256 = $manifestDigest
stagingPath = $stagePath
backupPath = $backupPath
canonicalReceiptPath = $receiptPath
validateOnly = [pscustomobject]@{ ok = $validate.ok; exitCode = $validate.exitCode; timedOut = $validate.timedOut; status = [string]$validate.payload.status }
deploy = [pscustomobject]@{ ok = $deploy.ok; exitCode = $deploy.exitCode; timedOut = $deploy.timedOut; status = [string]$deploy.payload.status }
relayReload = $relayReload
runtimeManifest = $runtimeManifest
launcherContract = $launcherContract
livePreflight = $livePreflight
durableReceiptWritten = $true
idempotentReplay = $false
operationBoundaries = [pscustomobject]@{
remoteWritePerformed = $true
liveRuntimeWritePerformed = $true
livePromotionAttempted = $true
livePromotionPerformed = $true
secretValueRead = $false
privateKeyValueRead = $false
tokenValueRead = $false
environmentSecretRead = $false
uiInteraction = $false
vmPowerChange = $false
hostReboot = $false
serviceRestart = $false
scheduledTaskRestart = $true
scheduledTaskModification = $false
}
nextSafeAction = "run_existing_production_principal_verifier_and_same_trace_completion_readback"
}
$script:ReceiverOperationPhase = "success_receipt_write"
Write-AgentRemoteDeployReceipt $receiptPath $successReceipt
} catch {
$failurePhase = $script:ReceiverOperationPhase
$failureType = $_.Exception.GetType().Name
}
}
if ($failurePhase) {
if (-not $backupPath) {
$newBackup = Get-ChildItem -LiteralPath $DeployRoot -Directory -Filter "backup-*" -ErrorAction SilentlyContinue |
Where-Object { -not $backupDirsBefore.ContainsKey($_.FullName) } |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
if ($newBackup) { $backupPath = $newBackup.FullName }
}
$script:ReceiverOperationPhase = "runtime_rollback"
$script:ReceiverRollbackAttempted = $true
$rollback = Restore-AgentBundle $backupPath $beforeFilePresence $beforeLauncherPresent $beforeConfigPresent $beforeManifestPresent
$rollbackForbiddenGenerations = @($beforeRelayRuntime.taskGeneration)
if ($relayReload) {
$rollbackForbiddenGenerations += @($relayReload.before.taskGeneration)
$rollbackForbiddenGenerations += @($relayReload.after.taskGeneration)
}
$relayBeforeRollback = Get-AgentRelayRuntime
$rollbackForbiddenGenerations += @($relayBeforeRollback.taskGeneration)
$rollbackForbiddenGenerations = @($rollbackForbiddenGenerations | Where-Object { $_ } | Sort-Object -Unique)
$rollbackRelayRestart = if ($rollback.restored) {
Invoke-AgentRelayTaskRestart -ForbiddenGenerations $rollbackForbiddenGenerations
} else {
[pscustomobject]@{
attempted = $false
ok = $false
stage = "restore"
reason = "bundle_restore_not_verified_relay_restart_withheld"
before = $relayBeforeRollback
after = $relayBeforeRollback
forbiddenGenerations = $rollbackForbiddenGenerations
oldGenerationsGone = $false
newGenerationVerified = $false
}
}
$afterManifest = [pscustomobject]@{
exists = $false
sourceRevision = ""
runtimeMatched = $false
fileCount = 0
mismatchCount = -1
parseError = "rollback_verifier_not_completed"
}
$rollbackVerified = $false
$rollbackVerifierErrorType = ""
try {
$afterManifest = Get-AgentSafeRuntimeManifest
$rollbackVerified = [bool](
$rollback.restored -and
(Test-AgentManifestEqual $beforeManifest $afterManifest) -and
(Get-AgentLiveBundleDigest) -eq $beforeBundleDigest -and
(Get-AgentSha256File $ConfigPath) -eq $beforeConfigDigest -and
(Get-AgentSha256File $ManifestPath) -eq $beforeManifestDigest -and
$rollbackRelayRestart.ok -and
$rollbackRelayRestart.oldGenerationsGone -and
$rollbackRelayRestart.newGenerationVerified -and
$rollbackRelayRestart.after.taskEnabled -and
$rollbackRelayRestart.after.taskRunning -and
$rollbackRelayRestart.after.listenerCount -gt 0
)
$script:ReceiverRollbackVerified = $rollbackVerified
} catch {
$rollbackVerified = $false
$rollbackVerifierErrorType = $_.Exception.GetType().Name
}
try {
$receipt = [pscustomobject]@{
schemaVersion = "agent99_remote_atomic_deploy_receipt_v1"
status = if ($failurePhase -eq "deploy") {
if ($rollbackVerified) { "deploy_failed_rollback_verified" } else { "deploy_failed_rollback_unverified" }
} elseif ($failurePhase -in @("success_receipt_construction", "success_receipt_write")) {
if ($rollbackVerified) { "rolled_back_success_receipt_write_failed" } else { "rollback_failed_after_success_receipt_write_failure" }
} else {
if ($rollbackVerified) { "rolled_back_post_verifier_failed" } else { "rollback_failed_after_post_verifier" }
}
mode = "apply"
traceId = $traceId
runId = $runId
workItemId = $workItemId
sourceRevision = $sourceRevision
manifestSha256 = $manifestDigest
stagingPath = $stagePath
backupPath = $backupPath
validateOnly = [pscustomobject]@{ ok = $validate.ok; exitCode = $validate.exitCode; timedOut = $validate.timedOut }
deploy = if ($deploy) { [pscustomobject]@{ ok = $deploy.ok; exitCode = $deploy.exitCode; timedOut = $deploy.timedOut } } else { [pscustomobject]@{ ok = $false; exitCode = -1; timedOut = $false } }
failurePhase = $failurePhase
failureType = $failureType
canonicalReceiptPath = $receiptPath
attemptReceiptPath = $attemptReceiptPath
relayReload = $relayReload
failedRuntimeManifest = $runtimeManifest
failedLauncherContract = $launcherContract
failedLivePreflight = $livePreflight
rollback = $rollback
rollbackRelayRestart = $rollbackRelayRestart
rollbackVerified = $rollbackVerified
rollbackVerifierErrorType = $rollbackVerifierErrorType
runtimeManifest = $afterManifest
durableReceiptWritten = $true
operationBoundaries = [pscustomobject]@{
remoteWritePerformed = $script:ReceiverRemoteWritePerformed
liveRuntimeWritePerformed = $script:ReceiverLiveRuntimeWritePerformed
livePromotionAttempted = $script:ReceiverLivePromotionAttempted
livePromotionPerformed = $script:ReceiverLivePromotionPerformed
rollbackAttempted = $script:ReceiverRollbackAttempted
rollbackVerified = $rollbackVerified
secretValueRead = $false
privateKeyValueRead = $false
tokenValueRead = $false
environmentSecretRead = $false
uiInteraction = $false
vmPowerChange = $false
hostReboot = $false
serviceRestart = $false
scheduledTaskRestart = [bool](($relayReload -and $relayReload.attempted) -or $rollbackRelayRestart.attempted)
scheduledTaskModification = $false
}
secretValueRead = $false
privateKeyValueRead = $false
tokenValueRead = $false
environmentSecretRead = $false
uiInteraction = $false
vmPowerChange = $false
hostReboot = $false
serviceRestart = $false
scheduledTaskRestart = [bool](($relayReload -and $relayReload.attempted) -or $rollbackRelayRestart.attempted)
scheduledTaskModification = $false
}
} catch {
$receipt = [pscustomobject]@{
schemaVersion = "agent99_remote_atomic_deploy_receipt_v1"
status = "rollback_receipt_construction_failed"
mode = "apply"
traceId = $traceId
runId = $runId
workItemId = $workItemId
sourceRevision = $sourceRevision
failurePhase = $failurePhase
failureType = $failureType
canonicalReceiptPath = $receiptPath
attemptReceiptPath = $attemptReceiptPath
rollbackVerified = $false
runtimeRollbackWasVerifiedBeforeReceiptFailure = $rollbackVerified
receiptConstructionErrorType = $_.Exception.GetType().Name
durableReceiptWritten = $true
operationBoundaries = [pscustomobject]@{
remoteWritePerformed = $script:ReceiverRemoteWritePerformed
liveRuntimeWritePerformed = $script:ReceiverLiveRuntimeWritePerformed
livePromotionAttempted = $script:ReceiverLivePromotionAttempted
livePromotionPerformed = $script:ReceiverLivePromotionPerformed
rollbackAttempted = $script:ReceiverRollbackAttempted
rollbackVerified = $false
scheduledTaskRestart = $script:ReceiverScheduledTaskRestartPerformed
scheduledTaskModification = $false
}
secretValueRead = $false
environmentSecretRead = $false
scheduledTaskModification = $false
}
$rollbackVerified = $false
}
try {
Write-AgentRemoteDeployReceipt $attemptReceiptPath $receipt
} catch {
$receipt.status = "rollback_receipt_write_failed"
$receipt.durableReceiptWritten = $false
$receipt | Add-Member -NotePropertyName receiptWriteErrorType -NotePropertyValue $_.Exception.GetType().Name -Force
Write-AgentResultAndExit $receipt 3
}
Write-AgentResultAndExit $receipt $(if ($rollbackVerified) { 2 } else { 3 })
}
$script:ReceiverOperationPhase = "success_result_output"
Write-AgentResultAndExit $successReceipt 0
} catch {
$outerErrorType = $_.Exception.GetType().Name
$outerFailure = [pscustomobject]@{
schemaVersion = "agent99_remote_atomic_deploy_receipt_v1"
status = "transport_receiver_failed"
errorType = $outerErrorType
operationPhase = $script:ReceiverOperationPhase
traceId = $script:ReceiverTraceId
runId = $script:ReceiverRunId
workItemId = $script:ReceiverWorkItemId
sourceRevision = $script:ReceiverSourceRevision
manifestSha256 = $script:ReceiverManifestDigest
canonicalReceiptPath = if ($script:ReceiverCanonicalReceiptPath) { $script:ReceiverCanonicalReceiptPath } else { "unavailable" }
attemptReceiptPath = if ($script:ReceiverAttemptReceiptPath) { $script:ReceiverAttemptReceiptPath } else { "unavailable" }
durableReceiptWritten = $false
remoteWritePerformed = $script:ReceiverRemoteWritePerformed
liveRuntimeWritePerformed = $script:ReceiverLiveRuntimeWritePerformed
livePromotionAttempted = $script:ReceiverLivePromotionAttempted
livePromotionPerformed = $script:ReceiverLivePromotionPerformed
rollbackAttempted = $script:ReceiverRollbackAttempted
rollbackVerified = $script:ReceiverRollbackVerified
secretValueRead = $false
privateKeyValueRead = $false
tokenValueRead = $false
rawEnvelopeStored = $false
environmentSecretRead = $false
uiInteraction = $false
vmPowerChange = $false
hostReboot = $false
serviceRestart = $false
scheduledTaskRestart = $script:ReceiverScheduledTaskRestartPerformed
scheduledTaskModification = $false
operationBoundaries = [pscustomobject]@{
remoteWritePerformed = $script:ReceiverRemoteWritePerformed
liveRuntimeWritePerformed = $script:ReceiverLiveRuntimeWritePerformed
livePromotionAttempted = $script:ReceiverLivePromotionAttempted
livePromotionPerformed = $script:ReceiverLivePromotionPerformed
rollbackAttempted = $script:ReceiverRollbackAttempted
rollbackVerified = $script:ReceiverRollbackVerified
secretValueRead = $false
privateKeyValueRead = $false
tokenValueRead = $false
environmentSecretRead = $false
uiInteraction = $false
vmPowerChange = $false
hostReboot = $false
serviceRestart = $false
scheduledTaskRestart = $script:ReceiverScheduledTaskRestartPerformed
scheduledTaskModification = $false
}
}
if ($script:ReceiverAttemptReceiptPath) {
try {
$script:ReceiverRemoteWritePerformed = $true
$outerFailure.remoteWritePerformed = $true
$outerFailure.operationBoundaries.remoteWritePerformed = $true
$outerFailure.durableReceiptWritten = $true
Write-AgentRemoteDeployReceipt $script:ReceiverAttemptReceiptPath $outerFailure
} catch {
$outerFailure.durableReceiptWritten = $false
$outerFailure | Add-Member -NotePropertyName receiptWriteErrorType -NotePropertyValue $_.Exception.GetType().Name -Force
}
}
Write-AgentResultAndExit $outerFailure 70
} finally {
if ($script:DeployLock) {
try { $script:DeployLock.Dispose() } catch {}
}
}