[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(?\{.*?\})\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 {} } }