fix(agent99): bind extended maintenance restore

This commit is contained in:
Your Name
2026-07-22 17:30:21 +08:00
parent ab0259fe8a
commit 5cd865c805
2 changed files with 44 additions and 3 deletions

View File

@@ -5,8 +5,10 @@ param(
[Parameter(Mandatory = $true)]
[ValidatePattern("^agent99-control-loop-maintenance-2[0-9]{7}-[0-9]{6}-[0-9]{3}-[0-9a-f]{8}\.json$")]
[string]$MaintenanceReceiptRef,
[ValidateRange(1, 60)]
[ValidateRange(1, 180)]
[int]$MaxReceiptAgeMinutes = 30,
[ValidatePattern("^(?:|[0-9a-f]{64})$")]
[string]$ExpectedMaintenanceReceiptSha256 = "",
[string]$AgentRoot = "C:\Wooo\Agent99"
)
@@ -155,13 +157,33 @@ try {
$blockers.Add("maintenance_receipt_missing")
} else {
try {
$maintenance = Get-Content -LiteralPath $maintenancePath -Raw | ConvertFrom-Json
$maintenanceSha256 = (Get-FileHash -LiteralPath $maintenancePath -Algorithm SHA256).Hash.ToLowerInvariant()
$maintenanceBytes = [IO.File]::ReadAllBytes($maintenancePath)
$sha256 = [Security.Cryptography.SHA256]::Create()
try {
$maintenanceSha256 = ([BitConverter]::ToString($sha256.ComputeHash($maintenanceBytes))).Replace("-", "").ToLowerInvariant()
} finally {
$sha256.Dispose()
}
$strictUtf8 = New-Object Text.UTF8Encoding($false, $true)
$maintenanceJson = $strictUtf8.GetString($maintenanceBytes)
$maintenance = $maintenanceJson | ConvertFrom-Json
if ($null -eq $maintenance -or $maintenance -isnot [pscustomobject]) {
throw "maintenance_receipt_shape_invalid"
}
$maintenanceAgeMinutes = ((Get-Date) - (Get-Item -LiteralPath $maintenancePath).LastWriteTime).TotalMinutes
} catch {
$blockers.Add("maintenance_receipt_invalid")
}
}
if ($MaxReceiptAgeMinutes -gt 60 -and [string]::IsNullOrWhiteSpace($ExpectedMaintenanceReceiptSha256)) {
$blockers.Add("extended_receipt_window_requires_exact_sha256")
}
if (
-not [string]::IsNullOrWhiteSpace($ExpectedMaintenanceReceiptSha256) -and
-not [string]::Equals($maintenanceSha256, $ExpectedMaintenanceReceiptSha256, [StringComparison]::Ordinal)
) {
$blockers.Add("maintenance_receipt_sha256_mismatch")
}
if ($maintenance) {
if (
[string]$maintenance.schemaVersion -ne "agent99_control_loop_maintenance_v2" -or
@@ -191,6 +213,9 @@ try {
$intentPath = Join-Path $evidenceDir $intentRef
try {
$maintenanceIntent = Get-Content -LiteralPath $intentPath -Raw -ErrorAction Stop | ConvertFrom-Json
if ($null -eq $maintenanceIntent -or $maintenanceIntent -isnot [pscustomobject]) {
throw "maintenance_intent_shape_invalid"
}
} catch {
$blockers.Add("maintenance_intent_invalid")
}
@@ -257,6 +282,8 @@ try {
taskName = $taskName
maintenanceReceiptRef = $MaintenanceReceiptRef
maintenanceReceiptSha256 = $maintenanceSha256
maxReceiptAgeMinutes = $MaxReceiptAgeMinutes
extendedReceiptWindow = [bool]($MaxReceiptAgeMinutes -gt 60)
plannedActions = @("enable_exact_control_task", "start_exact_control_task", "verify_control_task_started")
rollback = "stop and disable the exact control task if postcheck fails"
vmPowerChangePerformed = $false
@@ -310,6 +337,8 @@ try {
maintenanceReceiptRef = $MaintenanceReceiptRef
maintenanceReceiptSha256 = $maintenanceSha256
maintenanceAgeMinutes = if ($null -ne $maintenanceAgeMinutes) { [math]::Round($maintenanceAgeMinutes, 2) } else { $null }
maxReceiptAgeMinutes = $MaxReceiptAgeMinutes
extendedReceiptWindow = [bool]($MaxReceiptAgeMinutes -gt 60)
precheckPassed = $precheckPassed
blockers = @($blockers)
operationIntentRef = $operationIntentRef

View File

@@ -191,6 +191,18 @@ def test_control_loop_maintenance_restore_is_bounded_and_receipted() -> None:
assert 'if ($env:COMPUTERNAME -ne "WOOO-SUPER")' in source
assert "Global\\WoooAgent99ControlLoopMaintenanceV1" in source
assert 'ValidateSet("Check", "Apply")' in source
assert "ValidateRange(1, 180)" in source
assert "ExpectedMaintenanceReceiptSha256" in source
assert "extended_receipt_window_requires_exact_sha256" in source
assert "maintenance_receipt_sha256_mismatch" in source
assert "[IO.File]::ReadAllBytes($maintenancePath)" in source
assert "ComputeHash($maintenanceBytes)" in source
assert "maintenance_receipt_shape_invalid" in source
assert "maintenance_intent_shape_invalid" in source
assert "Get-Content -LiteralPath $maintenancePath" not in source
extended_guard = source.index("extended_receipt_window_requires_exact_sha256")
maintenance_contract = source.index('schemaVersion -ne "agent99_control_loop_maintenance_v2"')
assert extended_guard < maintenance_contract
assert "maintenanceReceiptSha256" in source
assert 'schemaVersion -ne "agent99_control_loop_maintenance_v2"' in source
assert 'terminal -ne "control_loop_maintenance_frozen"' in source