fix(agent99): bind extended maintenance restore
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user