fix(agent99): harden runtime deploy rollback

This commit is contained in:
ogt
2026-07-11 20:58:00 +08:00
parent 98ccf02231
commit aceedcc202
2 changed files with 54 additions and 9 deletions

View File

@@ -18,6 +18,7 @@ $BackupDir = Join-Path $DeployRoot "backup-$stamp"
$ManifestPath = Join-Path $StateDir "runtime-manifest.json"
$EvidencePath = Join-Path $EvidenceDir "agent99-deploy-$stamp.json"
$script:ConfigMigrations = @()
$script:RuntimeCopyRetries = @()
$runtimeFiles = @(
"agent99-bootstrap.ps1",
@@ -64,6 +65,29 @@ function Set-AgentProperty {
}
}
function Copy-AgentRuntimeFileWithRetry {
param(
[string]$Source,
[string]$Destination,
[int]$MaxAttempts = 20,
[int]$DelayMilliseconds = 500
)
for ($attempt = 1; $attempt -le $MaxAttempts; $attempt += 1) {
try {
Copy-Item -Force $Source $Destination
return
} catch [System.IO.IOException] {
if ($attempt -ge $MaxAttempts) { throw }
$script:RuntimeCopyRetries += [pscustomobject]@{
file = Split-Path -Leaf $Destination
attempt = $attempt
reason = "file_in_use"
}
Start-Sleep -Milliseconds $DelayMilliseconds
}
}
}
function Write-DeployEvidence {
param([bool]$Ok, [string]$Status, [string]$ErrorText, [object[]]$Files, [object]$TaskResult)
New-Item -ItemType Directory -Force -Path $EvidenceDir | Out-Null
@@ -82,6 +106,8 @@ function Write-DeployEvidence {
registerTasks = [bool]$RegisterTasks
error = $ErrorText
configMigrations = @($script:ConfigMigrations)
runtimeCopyRetryCount = @($script:RuntimeCopyRetries).Count
runtimeCopyRetries = @($script:RuntimeCopyRetries)
files = $Files
taskRegistration = $TaskResult
} | ConvertTo-Json -Depth 10 | Set-Content -Path $EvidencePath -Encoding UTF8
@@ -92,7 +118,7 @@ function Restore-Agent99Deployment {
foreach ($item in $Rows) {
$backupPath = Join-Path $BackupDir $item.name
if ($item.existed -and (Test-Path $backupPath)) {
Copy-Item -Force $backupPath $item.livePath
Copy-AgentRuntimeFileWithRetry $backupPath $item.livePath
} elseif (-not $item.existed -and (Test-Path $item.livePath)) {
Remove-Item -Force $item.livePath
}
@@ -151,14 +177,6 @@ $promotionRows = @()
$configBackedUp = $false
$manifestBackedUp = $false
try {
foreach ($name in $runtimeFiles) {
$livePath = Join-Path $BinDir $name
$existed = Test-Path $livePath
if ($existed) { Copy-Item -Force $livePath (Join-Path $BackupDir $name) }
$promotionRows += [pscustomobject]@{ name = $name; livePath = $livePath; existed = [bool]$existed }
Copy-Item -Force (Join-Path $StageDir $name) $livePath
}
if (-not (Test-Path $ConfigPath)) { throw "Live Agent99 config not found: $ConfigPath" }
Copy-Item -Force $ConfigPath (Join-Path $BackupDir "agent99.config.json")
$configBackedUp = $true
@@ -167,6 +185,14 @@ try {
$manifestBackedUp = $true
}
foreach ($name in $runtimeFiles) {
$livePath = Join-Path $BinDir $name
$existed = Test-Path $livePath
if ($existed) { Copy-Item -Force $livePath (Join-Path $BackupDir $name) }
$promotionRows += [pscustomobject]@{ name = $name; livePath = $livePath; existed = [bool]$existed }
Copy-AgentRuntimeFileWithRetry (Join-Path $StageDir $name) $livePath
}
$config = Get-Content $ConfigPath -Raw | ConvertFrom-Json
Add-DefaultProperty $config "sshIdentityFile" (Join-Path $AgentRoot "keys\agent99_ed25519")
Add-DefaultProperty $config "sshTransport" ([pscustomobject]@{})

View File

@@ -164,6 +164,25 @@ def test_agent99_deployer_verifies_promoted_runtime_and_writes_revision() -> Non
assert f'"{name}"' in bootstrap
def test_agent99_deployer_backs_up_manifest_before_runtime_promotion() -> None:
deploy = DEPLOY.read_text(encoding="utf-8")
promotion = deploy[deploy.index("$configBackedUp = $false") :]
promotion = promotion[: promotion.index("$config = Get-Content $ConfigPath")]
assert promotion.index('Copy-Item -Force $ConfigPath') < promotion.index(
"foreach ($name in $runtimeFiles)"
)
assert promotion.index('Copy-Item -Force $ManifestPath') < promotion.index(
"foreach ($name in $runtimeFiles)"
)
assert 'Restore-Agent99Deployment $promotionRows $configBackedUp $manifestBackedUp' in deploy
assert "function Copy-AgentRuntimeFileWithRetry" in deploy
assert "MaxAttempts = 20" in deploy
assert 'reason = "file_in_use"' in deploy
assert "runtimeCopyRetryCount" in deploy
assert "Copy-AgentRuntimeFileWithRetry (Join-Path $StageDir $name) $livePath" in deploy
def test_agent99_completion_callback_waits_for_durable_same_trace_readback() -> None:
source = CONTROL.read_text(encoding="utf-8")
deploy = DEPLOY.read_text(encoding="utf-8")