$ErrorActionPreference = "Stop" $root = Split-Path $PSScriptRoot -Parent $scriptPath = Join-Path $root "Upgrade-MinerOpsAgentCredential.ps1" $tokens = $null $parseErrors = $null [System.Management.Automation.Language.Parser]::ParseFile( $scriptPath, [ref]$tokens, [ref]$parseErrors ) | Out-Null if ($parseErrors.Count -gt 0) { throw "Credential transition parser errors: $($parseErrors.Message -join '; ')" } $source = Get-Content -LiteralPath $scriptPath -Raw function Assert-True { param( [Parameter(Mandatory = $true)][bool]$Condition, [Parameter(Mandatory = $true)][string]$Message ) if (-not $Condition) { throw $Message } } function Assert-SourceContains { param( [Parameter(Mandatory = $true)][string]$Pattern, [Parameter(Mandatory = $true)][string]$Message ) if ($source -notmatch $Pattern) { throw $Message } } function Assert-Throws { param( [Parameter(Mandatory = $true)][scriptblock]$Action, [string]$Pattern = "" ) try { & $Action | Out-Null } catch { if ($Pattern -and $_.Exception.Message -notmatch $Pattern) { throw "Expected error '$Pattern', got '$($_.Exception.Message)'." } return } throw "Expected action to fail." } $script:Passed = 0 function Invoke-BehaviorTest { param( [Parameter(Mandatory = $true)][string]$Name, [Parameter(Mandatory = $true)][scriptblock]$Action ) & $Action $script:Passed++ Write-Host "PASS: $Name" } # Dot-sourcing loads functions without executing the real transition. . $scriptPath function Get-SimPathKey { param([Parameter(Mandatory = $true)][string]$Path) return [IO.Path]::GetFullPath($Path) } function New-TransitionSimulation { param([Parameter(Mandatory = $true)][bool]$OriginallyRunning) $testRoot = Join-Path ([IO.Path]::GetTempPath()) ( "minerops-transition-test-" + [guid]::NewGuid().ToString("N") ) $targetRoot = Join-Path $testRoot "agent" $packageRoot = Join-Path $testRoot "package" [void](New-Item -ItemType Directory -Path $targetRoot, $packageRoot) foreach ($name in $RequiredFiles) { [IO.File]::WriteAllText((Join-Path $packageRoot $name), "test`n") } $config = [ordered]@{ hub_url = "https://hub.example.test" agent_id = "00000000-0000-4000-8000-000000000111" site_id = "00000000-0000-4000-8000-000000000222" agent_zone_id = "00000000-0000-4000-8000-000000000333" site_name = "Test Site" agent_secret = "legacy-shared-secret" api3_account = "test-account" api3_password = "test-password" poll_seconds = 30 } $configPath = Join-Path $targetRoot "agent-config.json" [IO.File]::WriteAllText( $configPath, ($config | ConvertTo-Json -Depth 20) + [Environment]::NewLine ) $token = "transition-token-123456789012345678901234567890" $enrollment = [ordered]@{ workflow = "existing_agent_credential_transition" hub_url = "https://hub.example.test" token = $token } [IO.File]::WriteAllText( (Join-Path $packageRoot "enrollment.json"), ($enrollment | ConvertTo-Json) + [Environment]::NewLine ) $script:Sim = @{ Root = $testRoot Target = $targetRoot Package = $packageRoot Config = $configPath Recovery = Join-Path $targetRoot "agent-credential-transition.recovery.json" Pending = Join-Path $targetRoot "agent-config.json.pending" Prior = Join-Path $targetRoot "agent-config.pre-credential-transition.json" Enrollment = Join-Path $packageRoot "enrollment.json" Token = $token Secure = @{} Events = [System.Collections.Generic.List[string]]::new() Redemptions = [System.Collections.Generic.List[object]]::new() TaskRunning = $OriginallyRunning TaskVerificationFailureAfterCommit = $false ResponseMode = "success" SwapMode = "success" FailAclLeaf = "" FailTaskRestore = $false InstallerFailure = $false TaskDefinitionInvalid = $false TaskStateOverride = $null CleanupSafe = $false SwapCalls = 0 DirectoryTrusted = $true DirectoryTrustedAtDiscovery = $true TrustEpochBlocked = $false TrustResetAuthorized = $false TrustResetRuntimePublished = $false TrustResetOriginalTaskRunning = $null BootChanged = $false TrustBlocker = Join-Path $testRoot ` "agent.minerops-filesystem-trust.blocked-simulated.state" DirectoryReparse = $false AttemptSwap = $false } $script:Sim.Secure[(Get-SimPathKey -Path $configPath)] = $false Set-TransitionSimulationMocks return $script:Sim } function Remove-TransitionSimulation { if ($null -ne $script:Sim -and (Test-Path -LiteralPath $script:Sim.Root)) { Remove-Item -LiteralPath $script:Sim.Root -Recurse -Force } $script:Sim = $null } function Set-TransitionSimulationMocks { function script:Initialize-MinerOpsCredentialDirectory { param( [Parameter(Mandatory = $true)][string]$Path, [scriptblock]$CheckpointAction, [switch]$ApproveFilesystemTrustResetAfterReboot ) if ($script:Sim.DirectoryReparse) { throw "Agent credential directory path contains a reparse point." } $wasTrusted = $script:Sim.DirectoryTrusted $script:Sim.DirectoryTrustedAtDiscovery = $wasTrusted if (-not $wasTrusted) { if (-not $script:Sim.TrustEpochBlocked) { [IO.File]::WriteAllText($script:Sim.TrustBlocker, "blocked") $script:Sim.TrustEpochBlocked = $true Invoke-MinerOpsTransitionCheckpoint ` -Name "directory-repair-blocker-written" ` -Action $CheckpointAction } $script:Sim.Events.Add("repair-directory") $script:Sim.DirectoryTrusted = $true Invoke-MinerOpsTransitionCheckpoint ` -Name "directory-trust-established" ` -Action $CheckpointAction } $newlyBlocked = -not $wasTrusted $approved = $false if ($script:Sim.TrustEpochBlocked -and -not $newlyBlocked) { if (-not $ApproveFilesystemTrustResetAfterReboot) { throw "filesystem trust epoch is blocked" } if (-not $script:Sim.BootChanged) { throw "without a verified reboot" } $approved = $true } return [pscustomobject]@{ Path = [IO.Path]::GetFullPath($Path) WasTrustedAtDiscovery = $wasTrusted WasRepaired = -not $wasTrusted TrustEpochBlocked = $script:Sim.TrustEpochBlocked TrustEpochNewlyBlocked = $newlyBlocked TrustResetApproved = $approved TrustResetAuthorized = $script:Sim.TrustResetAuthorized TrustResetRuntimePublished = ` $script:Sim.TrustResetRuntimePublished TrustResetOriginalTaskRunning = ` $script:Sim.TrustResetOriginalTaskRunning TrustBlockerPaths = @($script:Sim.TrustBlocker) } } function script:Assert-MinerOpsCredentialDirectory { param([Parameter(Mandatory = $true)][string]$Path) if ($script:Sim.DirectoryReparse) { throw "Agent credential directory path contains a reparse point." } if (-not $script:Sim.DirectoryTrusted) { throw "Agent credential directory owner or DACL is not trusted." } $script:Sim.Events.Add("assert-directory") if ($script:Sim.AttemptSwap) { $script:Sim.Events.Add("swap-blocked-by-directory") } return [IO.Path]::GetFullPath($Path) } function script:Initialize-MinerOpsQuarantineDirectory { param([Parameter(Mandatory = $true)][string]$TargetRoot) $path = Join-Path $TargetRoot ` "minerops-credential-transition-quarantine" if (-not (Test-Path -LiteralPath $path)) { [void](New-Item -ItemType Directory -Path $path) } return [IO.Path]::GetFullPath($path) } function script:Protect-MinerOpsQuarantineEvidence { param( [Parameter(Mandatory = $true)][string]$Path, [Parameter(Mandatory = $true)][string]$QuarantineRoot ) $script:Sim.Secure[(Get-SimPathKey -Path $Path)] = $true } function script:Get-MinerOpsCredentialArtifactProvenance { param( [Parameter(Mandatory = $true)][string]$Path, [Parameter(Mandatory = $true)][string]$ExpectedParent, [Parameter(Mandatory = $true)][string]$Role, [Parameter(Mandatory = $true)][bool]$DirectoryWasTrustedAtDiscovery, [switch]$CreatedByCurrentTransition ) if ($Role -eq "Active") { return "UnknownUntrusted" } if ($Role -eq "Quarantine") { return "ProtectedButUntrustedQuarantined" } $key = Get-SimPathKey -Path $Path if ( $CreatedByCurrentTransition -or ($DirectoryWasTrustedAtDiscovery -and $script:Sim.Secure.ContainsKey($key) -and $script:Sim.Secure[$key]) ) { return "ProtectedTransitionCandidate" } return "UnknownUntrusted" } function script:Move-MinerOpsArtifactToQuarantine { param( [Parameter(Mandatory = $true)][string]$Path, [Parameter(Mandatory = $true)][string]$ExpectedParent, [Parameter(Mandatory = $true)][string]$Role, [scriptblock]$CheckpointAction ) if (-not (Test-Path -LiteralPath $Path)) { return $null } $sourceKey = Get-SimPathKey -Path $Path $script:Sim.Events.Add("contain-without-read:$([IO.Path]::GetFileName($Path))") $quarantineRoot = Initialize-MinerOpsQuarantineDirectory ` -TargetRoot $ExpectedParent $destination = Join-Path $quarantineRoot ( "minerops-quarantine-credential-transition-$Role-" + [guid]::NewGuid().ToString("N") + ".evidence" ) [IO.File]::Move($Path, $destination) Invoke-MinerOpsTransitionCheckpoint ` -Name "artifact-moved-to-quarantine" ` -Action $CheckpointAction $script:Sim.Secure.Remove($sourceKey) $script:Sim.Secure[(Get-SimPathKey -Path $destination)] = $true $script:Sim.Events.Add("quarantine:$([IO.Path]::GetFileName($Path))") return $destination } function script:Read-MinerOpsProtectedCredentialText { param( [Parameter(Mandatory = $true)][string]$Path, [Parameter(Mandatory = $true)][string]$ExpectedParent, [switch]$AllowAclRepair ) $key = Get-SimPathKey -Path $Path if (-not $script:Sim.Secure.ContainsKey($key) -or -not $script:Sim.Secure[$key]) { $script:Sim.Events.Add("repair:$([IO.Path]::GetFileName($Path))") $script:Sim.Secure[$key] = $true if (-not $AllowAclRepair) { throw "Credential transition artifact had an insecure owner or DACL." } } $script:Sim.Events.Add("read:$([IO.Path]::GetFileName($Path))") return [IO.File]::ReadAllText($Path) } function script:Write-MinerOpsProtectedCredentialJson { param( [Parameter(Mandatory = $true)][string]$Path, [Parameter(Mandatory = $true)][string]$ExpectedParent, [Parameter(Mandatory = $true)]$Value ) $leaf = [IO.Path]::GetFileName($Path) $key = Get-SimPathKey -Path $Path if (Test-Path -LiteralPath $Path) { throw "CreateNew refused existing artifact." } [IO.File]::Create($Path).Dispose() $script:Sim.Events.Add("create-empty:$leaf") $script:Sim.Events.Add("protect:$leaf") if ($script:Sim.FailAclLeaf -eq $leaf) { Remove-Item -LiteralPath $Path -Force -ErrorAction SilentlyContinue throw "simulated ACL establishment failure" } $script:Sim.Secure[$key] = $true $script:Sim.Events.Add("verify-acl:$leaf") [IO.File]::WriteAllText( $Path, ($Value | ConvertTo-Json -Depth 50) + [Environment]::NewLine ) $script:Sim.Events.Add("write-secret:$leaf") } function script:Open-MinerOpsProtectedCredentialFile { param( [Parameter(Mandatory = $true)][string]$Path, [Parameter(Mandatory = $true)][string]$ExpectedParent, [switch]$AllowAclRepair ) $key = Get-SimPathKey -Path $Path if (-not $script:Sim.Secure.ContainsKey($key) -or -not $script:Sim.Secure[$key]) { $script:Sim.Secure[$key] = $true if (-not $AllowAclRepair) { throw "Credential transition artifact had an insecure owner or DACL." } } return [IO.File]::Open( $Path, [IO.FileMode]::Open, [IO.FileAccess]::ReadWrite, [IO.FileShare]::None ) } function script:Remove-MinerOpsProtectedCredentialArtifact { param( [Parameter(Mandatory = $true)][string]$Path, [Parameter(Mandatory = $true)][string]$ExpectedParent ) if (-not (Test-Path -LiteralPath $Path)) { return } $leaf = [IO.Path]::GetFileName($Path) if ($leaf -eq "agent-credential-transition.recovery.json") { $active = [IO.File]::ReadAllText($script:Sim.Config) | ConvertFrom-Json $script:Sim.CleanupSafe = ( [string]$active.agent_secret -ne "legacy-shared-secret" -and $script:Sim.TaskRunning -eq [bool](ConvertFrom-Json ([IO.File]::ReadAllText($Path))).original_task_running ) } Remove-Item -LiteralPath $Path -Force $script:Sim.Secure.Remove((Get-SimPathKey -Path $Path)) $script:Sim.Events.Add("remove:$leaf") } function script:Invoke-MinerOpsRuntimeInstaller { param( [Parameter(Mandatory = $true)][string]$Path, [switch]$ApproveFilesystemTrustResetAfterReboot ) if ($ApproveFilesystemTrustResetAfterReboot) { $script:Sim.Events.Add("trust-reset-runtime-publication") if ($script:Sim.InstallerFailure) { throw "simulated runtime publication failure" } $script:Sim.TaskRunning = $false } $script:Sim.Events.Add("install") } function script:Set-MinerOpsFilesystemTrustBlockerRuntimePublished { param( [Parameter(Mandatory = $true)][string]$Path, [Parameter(Mandatory = $true)][string]$TargetRoot, [Parameter(Mandatory = $true)][bool]$OriginalTaskRunning ) if (-not (Test-Path -LiteralPath $Path) -or -not $script:Sim.TrustResetAuthorized) { throw "simulated blocker disappeared before publication state" } $script:Sim.TrustResetRuntimePublished = $true $script:Sim.TrustResetOriginalTaskRunning = $OriginalTaskRunning $script:Sim.Events.Add("trust-blocker:runtime-published") } function script:Set-MinerOpsFilesystemTrustBlockerResetAuthorized { param( [Parameter(Mandatory = $true)][string]$Path, [Parameter(Mandatory = $true)][string]$TargetRoot, [Parameter(Mandatory = $true)][bool]$OriginalTaskRunning ) if (-not (Test-Path -LiteralPath $Path)) { throw "simulated blocker disappeared before reset authorization" } $script:Sim.TrustResetAuthorized = $true $script:Sim.TrustResetOriginalTaskRunning = $OriginalTaskRunning $script:Sim.Events.Add("trust-blocker:reset-authorized") } function script:Remove-MinerOpsFilesystemTrustBlockerAfterConvergence { param( [Parameter(Mandatory = $true)][string]$Path, [Parameter(Mandatory = $true)][string]$TargetRoot ) if (-not $script:Sim.TrustResetRuntimePublished) { throw "simulated blocker removed before runtime publication" } Remove-Item -LiteralPath $Path -Force -ErrorAction Stop $script:Sim.TrustEpochBlocked = $false $script:Sim.Events.Add("trust-blocker:removed-after-convergence") } function script:Get-MinerOpsVerifiedScheduledTask { param( [Parameter(Mandatory = $true)][string]$Name, [Parameter(Mandatory = $true)][string]$TargetRoot ) if ($script:Sim.TaskVerificationFailureAfterCommit -and $script:Sim.Redemptions.Count -gt 0) { throw "simulated strict task verification failure" } if ($script:Sim.TaskDefinitionInvalid) { throw "simulated strict task definition failure" } $state = if ($null -ne $script:Sim.TaskStateOverride) { [string]$script:Sim.TaskStateOverride } elseif ($script:Sim.TaskRunning) { "Running" } else { "Ready" } return [pscustomobject]@{ Task = [pscustomobject]@{ TaskName = $Name } State = $state } } function script:Set-MinerOpsScheduledTaskState { param( [Parameter(Mandatory = $true)][string]$Name, [Parameter(Mandatory = $true)][string]$TargetRoot, [Parameter(Mandatory = $true)][bool]$ShouldRun, [TimeSpan]$Timeout = [TimeSpan]::FromSeconds(30) ) if ($script:Sim.FailTaskRestore -and $ShouldRun) { throw "simulated task restart failure" } $script:Sim.TaskRunning = $ShouldRun $script:Sim.Events.Add("task:$ShouldRun") } function script:Stop-MinerOpsAgentTask { param( [Parameter(Mandatory = $true)][string]$Name, [Parameter(Mandatory = $true)][string]$TargetRoot ) $script:Sim.TaskRunning = $false $script:Sim.Events.Add("task:stopped-for-transition") } function script:Invoke-MinerOpsCredentialRedemption { param( [Parameter(Mandatory = $true)][string]$HubUrl, [Parameter(Mandatory = $true)][string]$Token, [Parameter(Mandatory = $true)][string]$SecretHash ) $script:Sim.Redemptions.Add([pscustomobject]@{ Token = $Token SecretHash = $SecretHash }) if ($script:Sim.ResponseMode -eq "lost-once" -and $script:Sim.Redemptions.Count -eq 1) { throw "simulated lost HTTP response after commit" } return [pscustomobject]@{ data = [pscustomobject]@{ agent_id = "00000000-0000-4000-8000-000000000111" site_id = "00000000-0000-4000-8000-000000000222" site_name = "Test Site" agent_zone_id = "00000000-0000-4000-8000-000000000333" agent_zone_name = "Test Zone" } } } function script:Invoke-MinerOpsRecoverableConfigInstall { param( [Parameter(Mandatory = $true)][string]$PendingPath, [Parameter(Mandatory = $true)][string]$ConfigPath, [Parameter(Mandatory = $true)][string]$PriorPath, [Parameter(Mandatory = $true)][string]$TargetRoot, [scriptblock]$CheckpointAction ) $script:Sim.SwapCalls++ if ($script:Sim.SwapMode -eq "fail-before-once" -and $script:Sim.SwapCalls -eq 1) { throw "simulated replacement failure" } if ($script:Sim.SwapMode -eq "active-missing-pending-once" -and $script:Sim.SwapCalls -eq 1) { Remove-Item -LiteralPath $ConfigPath -Force $script:Sim.Secure.Remove((Get-SimPathKey -Path $ConfigPath)) throw "simulated ERROR_UNABLE_TO_MOVE_REPLACEMENT" } if (Test-Path -LiteralPath $ConfigPath) { [void](Assert-MinerOpsCredentialDirectory -Path $TargetRoot) $script:Sim.Events.Add("directory-verified-before-active-move") [IO.File]::Move($ConfigPath, $PriorPath) $script:Sim.Secure[(Get-SimPathKey -Path $PriorPath)] = ` $script:Sim.Secure[(Get-SimPathKey -Path $ConfigPath)] $script:Sim.Secure.Remove((Get-SimPathKey -Path $ConfigPath)) Invoke-MinerOpsTransitionCheckpoint ` -Name "active-moved-to-prior" -Action $CheckpointAction } if ($script:Sim.SwapMode -eq "active-to-prior-fail-once" -and $script:Sim.SwapCalls -eq 1) { throw "simulated ERROR_UNABLE_TO_MOVE_REPLACEMENT_2" } [void](Assert-MinerOpsCredentialDirectory -Path $TargetRoot) $script:Sim.Events.Add("directory-verified-before-pending-move") [IO.File]::Move($PendingPath, $ConfigPath) $script:Sim.Secure[(Get-SimPathKey -Path $ConfigPath)] = $true $script:Sim.Secure.Remove((Get-SimPathKey -Path $PendingPath)) $script:Sim.Events.Add("recoverable-install") Invoke-MinerOpsTransitionCheckpoint ` -Name "pending-moved-to-active" -Action $CheckpointAction if ($script:Sim.SwapMode -eq "move-then-fail-once" -and $script:Sim.SwapCalls -eq 1) { throw "simulated ambiguous post-replacement failure" } } } function Invoke-SimulatedTransition { param( [scriptblock]$Checkpoint, [switch]$ApproveFilesystemTrustResetAfterReboot ) Invoke-MinerOpsAgentCredentialTransition ` -TargetRoot $script:Sim.Target ` -PackageRoot $script:Sim.Package ` -ScheduledTaskName "MinerOpsAgent-Test" ` -CheckpointAction $Checkpoint ` -ApproveFilesystemTrustResetAfterReboot:$ApproveFilesystemTrustResetAfterReboot } function Get-SimRecovery { return [IO.File]::ReadAllText($script:Sim.Recovery) | ConvertFrom-Json } function Set-SimPendingObject { param([Parameter(Mandatory = $true)]$Value) [IO.File]::WriteAllText( $script:Sim.Pending, ($Value | ConvertTo-Json -Depth 50) + [Environment]::NewLine ) } # Source invariants complement the behavioral harness. In particular, JSON is # not even serialized until after the empty file's DACL has been read back. Assert-SourceContains 'existing_agent_credential_transition' ` "Transition package workflow is not validated." Assert-SourceContains '/api/agent/credential-transition' ` "Dedicated credential transition endpoint is missing." Assert-SourceContains 'FileMode\]::CreateNew' ` "Secret artifacts are not created without overwrite." Assert-SourceContains 'Test-MinerOpsCredentialAcl' ` "Credential artifact DACL verification is missing." Assert-SourceContains 'original_task_running' ` "Original task state is not durable." Assert-SourceContains 'transition_token_hash' ` "Recovery does not bind the transition token." Assert-SourceContains 'agent_secret_hash' ` "Recovery does not bind the generated credential." Assert-SourceContains 'Assert-MinerOpsNoReparseDirectoryPath' ` "Full directory-path reparse validation is missing." Assert-SourceContains 'Test-MinerOpsAgentDirectoryAcl' ` "Credential directory DACL verification is missing." Assert-SourceContains 'TrustedCurrentTransitionState' ` "Trusted transition provenance is not explicit." Assert-SourceContains 'TrustedActiveConfig' ` "Trusted active-config provenance is not explicit." Assert-SourceContains 'ProtectedButUntrustedQuarantined' ` "Quarantined provenance is not explicit." Assert-SourceContains 'UnknownUntrusted' ` "Unknown artifact provenance is not explicit." Assert-SourceContains 'minerops-quarantine-credential-transition' ` "Non-authoritative quarantine naming is missing." Assert-SourceContains 'minerops-credential-transition\.blocked' ` "Durable quarantine blocked-state naming is missing." Assert-SourceContains 'minerops-credential-transition-quarantine' ` "Protected quarantine directory naming is missing." Assert-SourceContains 'marker exists before ACL repair' ` "ACL-repair laundering protection is not documented in source." Assert-SourceContains 'Invoke-MinerOpsRecoverableConfigInstall' ` "Recoverable config installation is missing." Assert-SourceContains 'active-moved-to-prior' ` "Known active-to-prior intermediate state is missing." Assert-SourceContains 'pending-moved-to-active' ` "Known pending-to-active intermediate state is missing." Assert-SourceContains 'before-recovery-cleanup' ` "Success cleanup checkpoint is missing." Assert-SourceContains 'Remove-MinerOpsFilesystemTrustBlockerAfterConvergence' ` "Parent-owned post-convergence trust-blocker removal is missing." Assert-SourceContains 'windows-full-boot-event-v1:' ` "Canonical Windows full-kernel-boot representation is missing." Assert-True ($source -notmatch 'LastBootUpTime') ` "Wall-clock boot timestamps must not authorize a trust reset." foreach ($functionName in @( "Assert-MinerOpsNoReparseDirectoryPath", "Assert-MinerOpsTrustedAncestorNamespace" )) { $functionSource = [regex]::Match( $source, "(?s)function $functionName \{.*?\n\}" ).Value Assert-True (-not [string]::IsNullOrWhiteSpace($functionSource)) ` "$functionName source was not found." Assert-True ($functionSource -match '\$current\s*=\s*\$root') ` "$functionName does not preserve the filesystem root." Assert-True ($functionSource -match ` '\$fullPath\.Equals\(\s*\$root') ` "$functionName does not protect a root-valued full path from trimming." Assert-True ($functionSource -notmatch '\$root\.TrimEnd\(') ` "$functionName converts a root-qualified path to drive-relative form." Assert-True ($functionSource -notmatch ` 'GetFullPath\(\$Path\)\.TrimEnd\(') ` "$functionName trims the filesystem root before identifying it." if ($functionName -eq "Assert-MinerOpsTrustedAncestorNamespace") { Assert-True ($functionSource -match ` '\$parent\.Equals\(\s*\$root') ` "$functionName does not protect a root-valued parent from trimming." } } $taskRestoreIndex = $source.LastIndexOf('-Name "task-restored"') $nonRecoveryCleanupIndex = $source.LastIndexOf('-Path $pendingPath') $blockerRemovalIndex = $source.LastIndexOf( 'Remove-MinerOpsFilesystemTrustBlockerAfterConvergence' ) $recoveryCleanupIndex = $source.LastIndexOf('-Path $recoveryPath') Assert-True ( $taskRestoreIndex -ge 0 -and $nonRecoveryCleanupIndex -gt $taskRestoreIndex -and $blockerRemovalIndex -gt $nonRecoveryCleanupIndex -and $recoveryCleanupIndex -gt $blockerRemovalIndex ) "Blocker deletion must follow convergence and non-recovery cleanup while recovery remains last." function New-TestTrustBlockerRecord { return [pscustomobject][ordered]@{ version = 2 state = "blocked" reason = "existing-agent-directory-was-untrusted" target_path = [IO.Path]::GetFullPath($script:VerifierTarget) detected_boot_id = "windows-full-boot-event-v1:000000000000002a" reset_boot_id = $null original_task_running = $null created_at = "2026-09-19T00:00:00.0000000Z" } } $script:VerifierTarget = Join-Path ([IO.Path]::GetTempPath()) ` "minerops-verifier-target" Invoke-BehaviorTest "production blocker parser accepts canonical boot epoch" { $json = (New-TestTrustBlockerRecord) | ConvertTo-Json -Depth 10 $record = ConvertFrom-MinerOpsJson ` -Json $json -Description "test filesystem trust blocker" [void](Assert-MinerOpsFilesystemTrustBlockerRecord ` -Record $record -TargetRoot $script:VerifierTarget) } Invoke-BehaviorTest "malformed stored BootId fails closed" { $record = New-TestTrustBlockerRecord $record.detected_boot_id = "not-a-boot-id" Assert-Throws { Assert-MinerOpsFilesystemTrustBlockerRecord ` -Record $record -TargetRoot $script:VerifierTarget } "canonical" } Invoke-BehaviorTest "missing stored BootId fails closed" { $record = New-TestTrustBlockerRecord $record.PSObject.Properties.Remove("detected_boot_id") Assert-Throws { Assert-MinerOpsFilesystemTrustBlockerRecord ` -Record $record -TargetRoot $script:VerifierTarget } "invalid" } Invoke-BehaviorTest "empty stored BootId fails closed" { $record = New-TestTrustBlockerRecord $record.detected_boot_id = "" Assert-Throws { Assert-MinerOpsFilesystemTrustBlockerRecord ` -Record $record -TargetRoot $script:VerifierTarget } "canonical" } Invoke-BehaviorTest "wrong-type stored BootId fails closed" { $record = New-TestTrustBlockerRecord $record.detected_boot_id = 42 Assert-Throws { Assert-MinerOpsFilesystemTrustBlockerRecord ` -Record $record -TargetRoot $script:VerifierTarget } "canonical" } Invoke-BehaviorTest "additional blocker representation fails closed" { $record = New-TestTrustBlockerRecord $record | Add-Member -NotePropertyName boot_timestamp ` -NotePropertyValue "2026-09-19T00:00:00Z" Assert-Throws { Assert-MinerOpsFilesystemTrustBlockerRecord ` -Record $record -TargetRoot $script:VerifierTarget } "invalid" } Invoke-BehaviorTest "current boot-epoch query failure fails closed" { Assert-Throws { Get-MinerOpsBootIdentifier -QueryAction { throw "simulated registry failure" } } "could not be verified" } Invoke-BehaviorTest "malformed current boot-epoch query fails closed" { Assert-Throws { Get-MinerOpsBootIdentifier -QueryAction { [pscustomobject]@{ RecordId = "42" EventId = [int]27 ProviderName = "Microsoft-Windows-Kernel-Boot" Version = [byte]1 BootType = [int]0 } } } "could not be verified" } Invoke-BehaviorTest "duplicated current full-boot identity fails closed" { Assert-Throws { Get-MinerOpsBootIdentifier -QueryAction { 1..2 | ForEach-Object { [pscustomobject]@{ RecordId = [long]42 EventId = [int]27 ProviderName = "Microsoft-Windows-Kernel-Boot" Version = [byte]1 BootType = [int]0 } } } } "could not be verified" } Invoke-BehaviorTest "wrong-type current BootType fails closed" { Assert-Throws { Get-MinerOpsBootIdentifier -QueryAction { [pscustomobject]@{ RecordId = [long]42 EventId = [int]27 ProviderName = "Microsoft-Windows-Kernel-Boot" Version = [byte]1 BootType = "0" } } } "could not be verified" } Invoke-BehaviorTest "canonical full-kernel-boot event query succeeds" { $bootId = Get-MinerOpsBootIdentifier -QueryAction { [pscustomobject]@{ RecordId = [long]42 EventId = [int]27 ProviderName = "Microsoft-Windows-Kernel-Boot" Version = [byte]1 BootType = [int]0 } } Assert-True ( $bootId -ceq "windows-full-boot-event-v1:000000000000002a" ) "Full-kernel-boot event was not canonicalized." } Invoke-BehaviorTest "Fast Startup event does not advance full-boot epoch" { $bootId = Get-MinerOpsBootIdentifier -QueryAction { [pscustomobject]@{ RecordId = [long]43 EventId = [int]27 ProviderName = "Microsoft-Windows-Kernel-Boot" Version = [byte]1 BootType = [int]1 } [pscustomobject]@{ RecordId = [long]42 EventId = [int]27 ProviderName = "Microsoft-Windows-Kernel-Boot" Version = [byte]1 BootType = [int]0 } } Assert-True ( $bootId -ceq "windows-full-boot-event-v1:000000000000002a" ) "Fast Startup advanced the full-kernel-boot epoch." } Invoke-BehaviorTest "hibernate event does not advance full-boot epoch" { $bootId = Get-MinerOpsBootIdentifier -QueryAction { [pscustomobject]@{ RecordId = [long]44 EventId = [int]27 ProviderName = "Microsoft-Windows-Kernel-Boot" Version = [byte]1 BootType = [int]2 } [pscustomobject]@{ RecordId = [long]42 EventId = [int]27 ProviderName = "Microsoft-Windows-Kernel-Boot" Version = [byte]1 BootType = [int]0 } } Assert-True ( $bootId -ceq "windows-full-boot-event-v1:000000000000002a" ) "Hibernate advanced the full-kernel-boot epoch." } Invoke-BehaviorTest "same canonical boot epoch denies reset" { Assert-Throws { Assert-MinerOpsBootEpochChanged ` -StoredBootIdentifier "windows-full-boot-event-v1:000000000000002a" ` -CurrentBootIdentifier "windows-full-boot-event-v1:000000000000002a" } ` "without a verified full Restart" } Invoke-BehaviorTest "different canonical boot epoch passes epoch check only" { Assert-MinerOpsBootEpochChanged ` -StoredBootIdentifier "windows-full-boot-event-v1:000000000000002a" ` -CurrentBootIdentifier "windows-full-boot-event-v1:000000000000002b" } Invoke-BehaviorTest "older canonical boot epoch denies reset" { Assert-Throws { Assert-MinerOpsBootEpochChanged ` -StoredBootIdentifier "windows-full-boot-event-v1:000000000000002a" ` -CurrentBootIdentifier "windows-full-boot-event-v1:0000000000000029" } ` "older than the" } $bootQueryFunction = [regex]::Match( $source, '(?s)function Get-MinerOpsBootIdentifier \{.*?\n\}' ).Value Assert-True ( $bootQueryFunction -notmatch 'Get-Date|DateTime|LastBootUpTime' -and $bootQueryFunction -match 'Get-WinEvent' -and $bootQueryFunction -match 'BootType' ) "Boot epoch comparison must not derive from the wall clock." $writeFunction = [regex]::Match( $source, '(?s)function Write-MinerOpsProtectedCredentialJson \{.*?\n\}' ).Value $createIndex = $writeFunction.IndexOf( "New-MinerOpsProtectedFileCreateNewStream" ) $verifyIndex = $writeFunction.IndexOf("Test-MinerOpsCredentialAcl") $jsonIndex = $writeFunction.IndexOf('$json =') Assert-True ( $createIndex -ge 0 -and $verifyIndex -gt $createIndex -and $jsonIndex -gt $verifyIndex -and $writeFunction -notmatch 'Set-MinerOpsStreamAcl' ) "Secret serialization must follow protected creation and ACL verification." $createStreamFunction = [regex]::Match( $source, '(?s)function New-MinerOpsProtectedFileCreateNewStream \{.*?\n\}' ).Value foreach ($requiredCreateContract in @( '\[IO\.FileStream\]::new', 'FileMode\]::CreateNew', 'FileSystemRights\]::WriteData', 'FileSystemRights\]::ReadPermissions', 'FileSystemRights\]::Synchronize', 'FileShare\]::None', '\$Acl' )) { Assert-True ($createStreamFunction -match $requiredCreateContract) ` "Protected CreateNew stream contract is incomplete." } Assert-True ( $createStreamFunction -notmatch 'FullControl|SetAccessControl|Set-Acl' ) "Protected creation must use minimum rights and creation-time FileSecurity." $openStreamFunction = [regex]::Match( $source, '(?s)function Open-MinerOpsFileStreamWithRights \{.*?\n\}' ).Value Assert-True ( $openStreamFunction -match 'FileMode\]::Open' -and $openStreamFunction -match 'FileShare\]::None' -and $openStreamFunction -match '\$Rights' ) "Existing protected artifacts must use an exclusive explicit-rights handle." $repairOpenFunction = [regex]::Match( $source, '(?s)function Open-MinerOpsProtectedCredentialFile \{.*?\n\}' ).Value foreach ($requiredRepairRight in @( 'ReadData', 'ReadPermissions', 'ChangePermissions', 'TakeOwnership', 'Synchronize' )) { Assert-True ($repairOpenFunction -match $requiredRepairRight) ` "Existing-artifact repair is missing $requiredRepairRight." } Assert-True ( $repairOpenFunction -match 'Set-MinerOpsStreamAcl' -and $repairOpenFunction -match 'will not be trusted' -and $repairOpenFunction -notmatch 'Set-Acl' ) "Existing-artifact repair must remain handle-bound and fail closed." $trustBlockerCreateFunction = [regex]::Match( $source, '(?s)function New-MinerOpsFilesystemTrustBlocker \{.*?\n\}' ).Value $blockerCreateIndex = $trustBlockerCreateFunction.IndexOf( 'New-MinerOpsProtectedFileCreateNewStream' ) $blockerVerifyIndex = $trustBlockerCreateFunction.IndexOf( 'Test-MinerOpsCredentialAcl' ) $blockerContentIndex = $trustBlockerCreateFunction.IndexOf('$record =') Assert-True ( $blockerCreateIndex -ge 0 -and $blockerVerifyIndex -gt $blockerCreateIndex -and $blockerContentIndex -gt $blockerVerifyIndex ) "Filesystem trust blockers must be protected and verified before content." Assert-True ($source -notmatch '\[IO\.File\]::Open') ` "Credential transition stream security must use explicit FileSystemRights." $quarantineFunction = [regex]::Match( $source, '(?s)function Move-MinerOpsArtifactToQuarantine \{.*?\n\}' ).Value $quarantineMoveIndex = $quarantineFunction.IndexOf('[IO.File]::Move') $quarantineAclIndex = $quarantineFunction.IndexOf('Set-Acl') Assert-True ( $quarantineMoveIndex -ge 0 -and $quarantineAclIndex -gt $quarantineMoveIndex -and $quarantineFunction -notmatch 'Read(AllText|-MinerOpsProtectedCredentialText)' ) "Untrusted evidence must leave authority before ACL mutation and must never be read." foreach ($sensitiveVariable in @( 'enrollmentToken', 'candidateSecret', 'transition_token', 'agent_secret' )) { Assert-True ( $source -notmatch ( '(?im)Write-(Host|Output|Verbose|Information).*?\$' + $sensitiveVariable ) ) "$sensitiveVariable must not be written to output." } foreach ($productionValue in @('plexus-windows-agent', 'Plexus Mining Site')) { Assert-True ( $source -notmatch [regex]::Escape($productionValue) ) "Production-specific value found in generic transition source." } Assert-True ($source -notmatch 'Get-ScheduledTask[^\r\n]*SilentlyContinue') ` "Scheduled Task queries must use terminating error behavior." Assert-SourceContains 'function Get-MinerOpsVerifiedScheduledTask' ` "The strict Scheduled Task verifier is missing." Assert-SourceContains 'Settings\.Enabled' ` "The strict verifier does not require an enabled task." Assert-SourceContains 'Principal\.UserId' ` "The strict verifier does not validate the task principal." Assert-SourceContains 'Actions' ` "The strict verifier does not validate the task action." # Exercise the real strict verifier with deterministic Scheduled Task provider # results before the transition simulation replaces it with a filesystem-free # mock. These cases prove that only a valid expected task in exactly Running or # Ready can satisfy durable intent. $script:VerifierTasks = @() $script:VerifierQueryFailure = "" function script:Get-ScheduledTask { param($ErrorAction) if ($script:VerifierQueryFailure -eq "provider") { throw "provider failure" } if ($script:VerifierQueryFailure -eq "access") { throw (New-Object UnauthorizedAccessException("access denied")) } return $script:VerifierTasks } function script:New-VerifierTask { param( [string]$State = "Ready", [string]$Execute = "", [string]$Arguments = "", [string]$UserId = "SYSTEM", [bool]$Enabled = $true, [switch]$Malformed, [string]$Name = "MinerOpsAgent-Test" ) if (-not $Execute) { $Execute = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" } if (-not $Arguments) { $Arguments = '-NoProfile -ExecutionPolicy Bypass -File "{0}"' -f (Join-Path $script:VerifierTarget "MinerOpsAgentService.ps1") } $task = [pscustomobject]@{ TaskName = $Name TaskPath = "\" State = $State Settings = [pscustomobject]@{ Enabled = $Enabled } Actions = @([pscustomobject]@{ Execute = $Execute Arguments = $Arguments }) Principal = [pscustomobject]@{ UserId = $UserId LogonType = "ServiceAccount" RunLevel = "Highest" } } if ($Malformed) { $task.Actions = @() } return $task } function script:Set-VerifierTask { param([Parameter(Mandatory)]$Task) $script:VerifierQueryFailure = "" $script:VerifierTasks = @($Task) } function script:Start-ScheduledTask { param([string]$TaskName, $ErrorAction) $script:VerifierTasks[0].State = "Running" } function script:Stop-ScheduledTask { param([string]$TaskName, $ErrorAction) $script:VerifierTasks[0].State = "Ready" } function script:Start-Sleep { param([int]$Milliseconds) } $strictName = "MinerOpsAgent-Test" Invoke-BehaviorTest "running intent accepts exactly Running" { Set-VerifierTask (New-VerifierTask -State "Running") [void](Assert-MinerOpsScheduledTaskIntent ` -Name $strictName -TargetRoot $script:VerifierTarget -ShouldRun $true) } Invoke-BehaviorTest "running intent rejects Ready" { Set-VerifierTask (New-VerifierTask -State "Ready") Assert-Throws { Assert-MinerOpsScheduledTaskIntent ` -Name $strictName -TargetRoot $script:VerifierTarget -ShouldRun $true } } Invoke-BehaviorTest "running intent rejects missing task" { $script:VerifierTasks = @() Assert-Throws { Assert-MinerOpsScheduledTaskIntent ` -Name $strictName -TargetRoot $script:VerifierTarget -ShouldRun $true } "missing" } Invoke-BehaviorTest "running intent rejects query exception" { $script:VerifierQueryFailure = "provider" Assert-Throws { Assert-MinerOpsScheduledTaskIntent ` -Name $strictName -TargetRoot $script:VerifierTarget -ShouldRun $true } "query failed" $script:VerifierQueryFailure = "" } Invoke-BehaviorTest "stopped intent accepts exactly Ready" { Set-VerifierTask (New-VerifierTask -State "Ready") [void](Assert-MinerOpsScheduledTaskIntent ` -Name $strictName -TargetRoot $script:VerifierTarget -ShouldRun $false) } Invoke-BehaviorTest "stopped intent stops Running then verifies Ready" { Set-VerifierTask (New-VerifierTask -State "Running") [void](Set-MinerOpsScheduledTaskState ` -Name $strictName -TargetRoot $script:VerifierTarget -ShouldRun $false) [void](Assert-MinerOpsScheduledTaskIntent ` -Name $strictName -TargetRoot $script:VerifierTarget -ShouldRun $false) } Invoke-BehaviorTest "stopped intent rejects missing task" { $script:VerifierTasks = @() Assert-Throws { Assert-MinerOpsScheduledTaskIntent ` -Name $strictName -TargetRoot $script:VerifierTarget -ShouldRun $false } "missing" } Invoke-BehaviorTest "stopped intent rejects provider exception" { $script:VerifierQueryFailure = "provider" Assert-Throws { Assert-MinerOpsScheduledTaskIntent ` -Name $strictName -TargetRoot $script:VerifierTarget -ShouldRun $false } "query failed" $script:VerifierQueryFailure = "" } Invoke-BehaviorTest "stopped intent rejects access denied" { $script:VerifierQueryFailure = "access" Assert-Throws { Assert-MinerOpsScheduledTaskIntent ` -Name $strictName -TargetRoot $script:VerifierTarget -ShouldRun $false } "query failed" $script:VerifierQueryFailure = "" } Invoke-BehaviorTest "stopped intent rejects empty state" { Set-VerifierTask (New-VerifierTask -State "") Assert-Throws { Assert-MinerOpsScheduledTaskIntent ` -Name $strictName -TargetRoot $script:VerifierTarget -ShouldRun $false } "malformed" } Invoke-BehaviorTest "stopped intent rejects Disabled" { Set-VerifierTask (New-VerifierTask -State "Disabled" -Enabled:$false) Assert-Throws { Assert-MinerOpsScheduledTaskIntent ` -Name $strictName -TargetRoot $script:VerifierTarget -ShouldRun $false } "disabled" } foreach ($invalidState in @("Unknown", "Queued")) { Invoke-BehaviorTest "stopped intent rejects $invalidState" { Set-VerifierTask (New-VerifierTask -State $invalidState) Assert-Throws { Assert-MinerOpsScheduledTaskIntent ` -Name $strictName -TargetRoot $script:VerifierTarget -ShouldRun $false } } } Invoke-BehaviorTest "strict verifier rejects malformed task object" { Set-VerifierTask (New-VerifierTask -Malformed) Assert-Throws { Get-MinerOpsVerifiedScheduledTask ` -Name $strictName -TargetRoot $script:VerifierTarget } "exactly one" } Invoke-BehaviorTest "strict verifier rejects multiple results" { $script:VerifierTasks = @( (New-VerifierTask), (New-VerifierTask) ) Assert-Throws { Get-MinerOpsVerifiedScheduledTask ` -Name $strictName -TargetRoot $script:VerifierTarget } "multiple" } Invoke-BehaviorTest "strict verifier rejects wrong executable" { Set-VerifierTask (New-VerifierTask -Execute "C:\Windows\System32\cmd.exe") Assert-Throws { Get-MinerOpsVerifiedScheduledTask ` -Name $strictName -TargetRoot $script:VerifierTarget } "executable" } Invoke-BehaviorTest "strict verifier rejects wrong script arguments" { Set-VerifierTask (New-VerifierTask -Arguments '-NoProfile -File "C:\wrong.ps1"') Assert-Throws { Get-MinerOpsVerifiedScheduledTask ` -Name $strictName -TargetRoot $script:VerifierTarget } "arguments" } Invoke-BehaviorTest "strict verifier rejects wrong principal" { Set-VerifierTask (New-VerifierTask -UserId "BUILTIN\Users") Assert-Throws { Get-MinerOpsVerifiedScheduledTask ` -Name $strictName -TargetRoot $script:VerifierTarget } "principal" } Invoke-BehaviorTest "strict verifier rejects missing TaskPath" { $task = New-VerifierTask $task.PSObject.Properties.Remove("TaskPath") Set-VerifierTask $task Assert-Throws { Get-MinerOpsVerifiedScheduledTask ` -Name $strictName -TargetRoot $script:VerifierTarget } "task path" } Invoke-BehaviorTest "strict verifier rejects null TaskPath" { $task = New-VerifierTask $task.TaskPath = $null Set-VerifierTask $task Assert-Throws { Get-MinerOpsVerifiedScheduledTask ` -Name $strictName -TargetRoot $script:VerifierTarget } "task path" } Invoke-BehaviorTest "strict verifier rejects empty TaskPath" { $task = New-VerifierTask $task.TaskPath = "" Set-VerifierTask $task Assert-Throws { Get-MinerOpsVerifiedScheduledTask ` -Name $strictName -TargetRoot $script:VerifierTarget } "task path" } Invoke-BehaviorTest "strict verifier rejects wrong-type TaskPath" { $task = New-VerifierTask $task.TaskPath = 1 Set-VerifierTask $task Assert-Throws { Get-MinerOpsVerifiedScheduledTask ` -Name $strictName -TargetRoot $script:VerifierTarget } "task path" } Invoke-BehaviorTest "strict verifier rejects non-root TaskPath" { $task = New-VerifierTask $task.TaskPath = "\MinerOps\" Set-VerifierTask $task Assert-Throws { Get-MinerOpsVerifiedScheduledTask ` -Name $strictName -TargetRoot $script:VerifierTarget } "task path" } Invoke-BehaviorTest "strict verifier rejects missing Settings" { $task = New-VerifierTask $task.PSObject.Properties.Remove("Settings") Set-VerifierTask $task Assert-Throws { Get-MinerOpsVerifiedScheduledTask ` -Name $strictName -TargetRoot $script:VerifierTarget } "enabled" } Invoke-BehaviorTest "strict verifier rejects missing Enabled" { $task = New-VerifierTask $task.Settings.PSObject.Properties.Remove("Enabled") Set-VerifierTask $task Assert-Throws { Get-MinerOpsVerifiedScheduledTask ` -Name $strictName -TargetRoot $script:VerifierTarget } "enabled" } Invoke-BehaviorTest "strict verifier rejects Boolean false Enabled" { Set-VerifierTask (New-VerifierTask -Enabled:$false) Assert-Throws { Get-MinerOpsVerifiedScheduledTask ` -Name $strictName -TargetRoot $script:VerifierTarget } "enabled" } Invoke-BehaviorTest "strict verifier rejects string False Enabled" { $task = New-VerifierTask $task.Settings.Enabled = "False" Set-VerifierTask $task Assert-Throws { Get-MinerOpsVerifiedScheduledTask ` -Name $strictName -TargetRoot $script:VerifierTarget } "enabled" } Invoke-BehaviorTest "strict verifier rejects numeric Enabled" { $task = New-VerifierTask $task.Settings.Enabled = 1 Set-VerifierTask $task Assert-Throws { Get-MinerOpsVerifiedScheduledTask ` -Name $strictName -TargetRoot $script:VerifierTarget } "enabled" } Invoke-BehaviorTest "strict verifier accepts actual Boolean true Enabled" { Set-VerifierTask (New-VerifierTask -Enabled:$true) [void](Get-MinerOpsVerifiedScheduledTask ` -Name $strictName -TargetRoot $script:VerifierTarget) } foreach ($malformation in @( "numeric-state", "string-actions", "numeric-execute", "array-arguments", "string-principal", "numeric-user", "numeric-logon", "numeric-runlevel" )) { Invoke-BehaviorTest "strict verifier rejects $malformation property" { $task = New-VerifierTask switch ($malformation) { "numeric-state" { $task.State = 3 } "string-actions" { $task.Actions = "powershell.exe" } "numeric-execute" { $task.Actions[0].Execute = 1 } "array-arguments" { $task.Actions[0].Arguments = @("arg1", "arg2") } "string-principal" { $task.Principal = "SYSTEM" } "numeric-user" { $task.Principal.UserId = 18 } "numeric-logon" { $task.Principal.LogonType = 5 } "numeric-runlevel" { $task.Principal.RunLevel = 1 } } Set-VerifierTask $task Assert-Throws { Get-MinerOpsVerifiedScheduledTask ` -Name $strictName -TargetRoot $script:VerifierTarget } } } try { Invoke-BehaviorTest "normal successful transition" { $sim = New-TransitionSimulation -OriginallyRunning $true Invoke-SimulatedTransition | Out-Null $active = [IO.File]::ReadAllText($sim.Config) | ConvertFrom-Json Assert-True ($active.agent_secret -ne "legacy-shared-secret") ` "Permanent secret was not installed." Assert-True $sim.TaskRunning "Originally running task did not end running." Assert-True (-not (Test-Path $sim.Recovery)) "Recovery was not cleaned." Assert-True $sim.CleanupSafe "Cleanup occurred before success criteria." Remove-TransitionSimulation } Invoke-BehaviorTest "protected-before-secret writes" { $sim = New-TransitionSimulation -OriginallyRunning $false Invoke-SimulatedTransition | Out-Null foreach ($leaf in @( "agent-credential-transition.recovery.json", "agent-config.json.pending" )) { $create = $sim.Events.IndexOf("create-empty:$leaf") $protect = $sim.Events.IndexOf("protect:$leaf") $verify = $sim.Events.IndexOf("verify-acl:$leaf") $write = $sim.Events.IndexOf("write-secret:$leaf") Assert-True ( $create -ge 0 -and $protect -gt $create -and $verify -gt $protect -and $write -gt $verify ) "ACL ordering failed for $leaf." } Remove-TransitionSimulation } Invoke-BehaviorTest "ACL failure blocks redemption without leaked content" { $sim = New-TransitionSimulation -OriginallyRunning $true $sim.FailAclLeaf = "agent-credential-transition.recovery.json" Assert-Throws { Invoke-SimulatedTransition } "ACL establishment failure" Assert-True ($sim.Redemptions.Count -eq 0) ` "Redemption proceeded after ACL failure." Assert-True (-not (Test-Path $sim.Recovery)) ` "Insecure recovery artifact remained after handled ACL failure." Remove-TransitionSimulation } Invoke-BehaviorTest "insecure recovery is quarantined and blocked on two runs" { $sim = New-TransitionSimulation -OriginallyRunning $true Assert-Throws { Invoke-SimulatedTransition -Checkpoint { param($name) if ($name -eq "recovery-written") { throw "stop" } } } "stop" $malicious = Get-SimRecovery $malicious.agent_secret = "attacker-provided-secret" $malicious.agent_secret_hash = Get-MinerOpsSha256 ` -Value $malicious.agent_secret $malicious.original_task_running = $false [IO.File]::WriteAllText( $sim.Recovery, ($malicious | ConvertTo-Json -Depth 50) + [Environment]::NewLine ) $sim.Events.Clear() $sim.Secure[(Get-SimPathKey $sim.Recovery)] = $false Assert-Throws { Invoke-SimulatedTransition } "evidence is quarantined" Assert-True ( $sim.Events -contains "quarantine:agent-credential-transition.recovery.json" ) "Insecure recovery was not moved out of authority." Assert-True ( $sim.Events -notcontains "read:agent-credential-transition.recovery.json" ) "Insecure recovery contents were read." Assert-True ($sim.Redemptions.Count -eq 0) ` "Recovery credential was used after an insecure read." Assert-Throws { Invoke-SimulatedTransition } "evidence is quarantined" Assert-True ($sim.Redemptions.Count -eq 0) ` "Second invocation used quarantined recovery authority." Assert-True $sim.TaskRunning ` "Attacker-provided task intent changed the Scheduled Task." Remove-TransitionSimulation } Invoke-BehaviorTest "blocked recovery cannot be laundered by later ACL repair" { $sim = New-TransitionSimulation -OriginallyRunning $true Assert-Throws { Invoke-SimulatedTransition -Checkpoint { param($name) if ($name -eq "recovery-written") { throw "stop" } } } "stop" $malicious = Get-SimRecovery $malicious.agent_secret = "attacker-laundering-secret" $malicious.agent_secret_hash = Get-MinerOpsSha256 ` -Value $malicious.agent_secret $malicious.original_task_running = $false [IO.File]::WriteAllText( $sim.Recovery, ($malicious | ConvertTo-Json -Depth 50) + [Environment]::NewLine ) $sim.Secure[(Get-SimPathKey $sim.Recovery)] = $false Assert-Throws { Invoke-SimulatedTransition -Checkpoint { param($name) if ($name -eq "quarantine-blocker-written") { throw "termination after quarantine blocker" } } } "termination after quarantine blocker" Assert-True (Test-Path -LiteralPath $sim.Recovery) ` "Injected interruption did not leave the authority pathname." Assert-True ( @(Get-ChildItem -LiteralPath $sim.Target ` -Filter "minerops-credential-transition.blocked*.state").Count -gt 0 ) "Durable blocker was not present before containment." # Model an external/legacy ACL repair after interruption. ACL # correctness must not override the already-durable blocked state. $sim.Secure[(Get-SimPathKey $sim.Recovery)] = $true Assert-Throws { Invoke-SimulatedTransition } "evidence is quarantined" Assert-True (-not (Test-Path -LiteralPath $sim.Recovery)) ` "Second invocation retained repaired recovery authority." Assert-True ($sim.Redemptions.Count -eq 0) ` "Repaired malicious recovery reached redemption on a later run." Assert-True $sim.TaskRunning ` "Repaired malicious recovery changed Scheduled Task intent." Remove-TransitionSimulation } foreach ($originallyRunning in @($true, $false)) { $label = if ($originallyRunning) { "running" } else { "stopped" } Invoke-BehaviorTest "lost response retry restores originally $label task" { $sim = New-TransitionSimulation -OriginallyRunning $originallyRunning $sim.ResponseMode = "lost-once" Assert-Throws { Invoke-SimulatedTransition } "lost HTTP response" Assert-True (-not $sim.TaskRunning) ` "Task should remain stopped after ambiguous redemption." $recoverySecretHash = (Get-SimRecovery).agent_secret_hash Invoke-SimulatedTransition | Out-Null Assert-True ($sim.TaskRunning -eq $originallyRunning) ` "Original task intent was not restored." Assert-True ($sim.Redemptions.Count -eq 2) ` "Retry did not redeem twice." Assert-True ( $sim.Redemptions[0].SecretHash -eq $recoverySecretHash -and $sim.Redemptions[1].SecretHash -eq $recoverySecretHash ) "Retry changed the committed credential." Remove-TransitionSimulation } } Invoke-BehaviorTest "crash after server commit before pending converges" { $sim = New-TransitionSimulation -OriginallyRunning $true Assert-Throws { Invoke-SimulatedTransition -Checkpoint { param($name) if ($name -eq "server-committed") { throw "crash-after-commit" } } } "crash-after-commit" Assert-True (-not (Test-Path $sim.Pending)) ` "Pending unexpectedly existed at injected checkpoint." Invoke-SimulatedTransition | Out-Null Assert-True $sim.TaskRunning "Retry did not restore task." Remove-TransitionSimulation } Invoke-BehaviorTest "valid protected pending resumes on rerun" { $sim = New-TransitionSimulation -OriginallyRunning $true Assert-Throws { Invoke-SimulatedTransition -Checkpoint { param($name) if ($name -eq "pending-written") { throw "crash-after-pending" } } } "crash-after-pending" $writesBefore = @($sim.Events | Where-Object { $_ -eq "write-secret:agent-config.json.pending" }).Count Invoke-SimulatedTransition | Out-Null $writesAfter = @($sim.Events | Where-Object { $_ -eq "write-secret:agent-config.json.pending" }).Count Assert-True ($writesAfter -eq $writesBefore) ` "Valid pending config was overwritten instead of resumed." Remove-TransitionSimulation } Invoke-BehaviorTest "failure immediately before replacement converges" { $sim = New-TransitionSimulation -OriginallyRunning $true Assert-Throws { Invoke-SimulatedTransition -Checkpoint { param($name) if ($name -eq "before-replace") { throw "before-replace" } } } "before-replace" Assert-True (Test-Path $sim.Pending) "Pending config was lost." Invoke-SimulatedTransition | Out-Null Assert-True $sim.TaskRunning "Retry did not finish replacement." Remove-TransitionSimulation } Invoke-BehaviorTest "pre-rename replacement failure converges" { $sim = New-TransitionSimulation -OriginallyRunning $true $sim.SwapMode = "fail-before-once" Assert-Throws { Invoke-SimulatedTransition } "replacement failure" Assert-True (Test-Path $sim.Pending) ` "Pending config was lost on replacement failure." Invoke-SimulatedTransition | Out-Null Assert-True $sim.TaskRunning "Replacement retry did not restore task." Remove-TransitionSimulation } Invoke-BehaviorTest "post-replacement ambiguity converges" { $sim = New-TransitionSimulation -OriginallyRunning $true $sim.SwapMode = "move-then-fail-once" Assert-Throws { Invoke-SimulatedTransition } "post-replacement" Assert-True (-not (Test-Path $sim.Pending)) ` "Recoverable installation should consume pending config." Assert-True (Test-Path $sim.Prior) ` "Known prior remnant was not retained." Invoke-SimulatedTransition | Out-Null Assert-True $sim.TaskRunning ` "Already-replaced config was not recovered." Remove-TransitionSimulation } foreach ($originallyRunning in @($true, $false)) { $label = if ($originallyRunning) { "running" } else { "stopped" } Invoke-BehaviorTest "active-missing pending recovery restores originally $label task" { $sim = New-TransitionSimulation -OriginallyRunning $originallyRunning $sim.SwapMode = "active-missing-pending-once" Assert-Throws { Invoke-SimulatedTransition } ` "ERROR_UNABLE_TO_MOVE_REPLACEMENT" Assert-True (-not (Test-Path $sim.Config)) ` "Active config unexpectedly survived the injected partial state." Assert-True (Test-Path $sim.Pending) ` "Authenticated pending config was lost." $recovery = Get-SimRecovery $first = $sim.Redemptions[0] Invoke-SimulatedTransition | Out-Null Assert-True ($sim.Redemptions.Count -eq 2) ` "Active-missing recovery did not retry idempotent redemption." Assert-True ( $sim.Redemptions[1].Token -eq $first.Token -and $sim.Redemptions[1].SecretHash -eq $first.SecretHash -and $sim.Redemptions[1].Token -eq $recovery.transition_token -and $sim.Redemptions[1].SecretHash -eq $recovery.agent_secret_hash ) "Active-missing recovery changed token or permanent credential." $active = [IO.File]::ReadAllText($sim.Config) | ConvertFrom-Json Assert-True ( [string]$active.agent_id -eq "00000000-0000-4000-8000-000000000111" -and [string]$active.site_id -eq "00000000-0000-4000-8000-000000000222" -and [string]$active.agent_zone_id -eq "00000000-0000-4000-8000-000000000333" ) "Active-missing recovery changed Agent routing." Assert-True ($sim.TaskRunning -eq $originallyRunning) ` "Active-missing recovery lost original task intent." Remove-TransitionSimulation } } Invoke-BehaviorTest "known active-missing pending prior intermediate converges" { $sim = New-TransitionSimulation -OriginallyRunning $true $sim.SwapMode = "active-to-prior-fail-once" Assert-Throws { Invoke-SimulatedTransition } ` "ERROR_UNABLE_TO_MOVE_REPLACEMENT_2" Assert-True (-not (Test-Path $sim.Config)) ` "Active config should be absent at the known rename midpoint." Assert-True (Test-Path $sim.Pending) ` "Pending config should remain at the known rename midpoint." Assert-True (Test-Path $sim.Prior) ` "Authenticated prior remnant should represent the moved active file." Invoke-SimulatedTransition | Out-Null Assert-True $sim.TaskRunning ` "Known replacement midpoint did not converge." Remove-TransitionSimulation } Invoke-BehaviorTest "active-missing unknown pending fails closed" { $sim = New-TransitionSimulation -OriginallyRunning $true Remove-Item -LiteralPath $sim.Config -Force $sim.Secure.Remove((Get-SimPathKey -Path $sim.Config)) [IO.File]::WriteAllText( $sim.Pending, '{"agent_secret":"unknown-pending-secret"}' ) $sim.Secure[(Get-SimPathKey -Path $sim.Pending)] = $false $captured = & { try { Invoke-SimulatedTransition } catch { $_.Exception.Message } } *>&1 | Out-String Assert-True ($sim.Redemptions.Count -eq 0) ` "Unknown active-missing pending state reached redemption." Assert-True (-not $captured.Contains("unknown-pending-secret")) ` "Unknown active-missing pending secret reached output." Assert-Throws { Invoke-SimulatedTransition } "evidence is quarantined" Remove-TransitionSimulation } foreach ($activeMissingMutation in @("malformed", "agent", "secret")) { Invoke-BehaviorTest "active-missing $activeMissingMutation pending fails closed" { $sim = New-TransitionSimulation -OriginallyRunning $true Assert-Throws { Invoke-SimulatedTransition -Checkpoint { param($name) if ($name -eq "pending-written") { throw "stop" } } } "stop" Remove-Item -LiteralPath $sim.Config -Force $sim.Secure.Remove((Get-SimPathKey -Path $sim.Config)) if ($activeMissingMutation -eq "malformed") { [IO.File]::WriteAllText($sim.Pending, '{"agent_secret":') } else { $pending = [IO.File]::ReadAllText($sim.Pending) | ConvertFrom-Json if ($activeMissingMutation -eq "agent") { $pending.agent_id = "00000000-0000-4000-8000-000000009999" } else { $pending.agent_secret = "mismatched-active-missing-secret" } Set-SimPendingObject -Value $pending } $calls = $sim.Redemptions.Count Assert-Throws { Invoke-SimulatedTransition } Assert-True ($sim.Redemptions.Count -eq $calls) ` "Invalid active-missing pending state reached redemption." Remove-TransitionSimulation } } Invoke-BehaviorTest "prior remnant alone cannot restore active authority" { $sim = New-TransitionSimulation -OriginallyRunning $true Assert-Throws { Invoke-SimulatedTransition -Checkpoint { param($name) if ($name -eq "active-moved-to-prior") { throw "stop-at-rename-midpoint" } } } "stop-at-rename-midpoint" Remove-Item -LiteralPath $sim.Pending -Force $sim.Secure.Remove((Get-SimPathKey -Path $sim.Pending)) $calls = $sim.Redemptions.Count Assert-Throws { Invoke-SimulatedTransition } ` "no authenticated pending config" Assert-True (-not (Test-Path -LiteralPath $sim.Config)) ` "Prior remnant was promoted into active authority." Assert-True ($sim.Redemptions.Count -eq $calls) ` "Prior-only state reached redemption." Remove-TransitionSimulation } Invoke-BehaviorTest "directory junction fails before credential operations" { $sim = New-TransitionSimulation -OriginallyRunning $true $sim.DirectoryReparse = $true Assert-Throws { Invoke-SimulatedTransition } "reparse point" Assert-True ($sim.Redemptions.Count -eq 0) ` "Reparse directory reached redemption." Assert-True (-not (Test-Path $sim.Recovery)) ` "Reparse directory received new secret state." Remove-TransitionSimulation } Invoke-BehaviorTest "directory repair quarantines authority and blocks three invocations" { $sim = New-TransitionSimulation -OriginallyRunning $true Assert-Throws { Invoke-SimulatedTransition -Checkpoint { param($name) if ($name -eq "recovery-written") { throw "stop" } } } "stop" $sim.DirectoryTrusted = $false Assert-Throws { Invoke-SimulatedTransition } "existing Agent directory was untrusted" Assert-True ($sim.Events -contains "repair-directory") ` "Permissive target directory was not repaired." Assert-True ( $sim.Events -contains "quarantine:agent-credential-transition.recovery.json" ) "Pre-repair recovery was promoted instead of quarantined." Assert-True ($sim.Redemptions.Count -eq 0) ` "Directory repair promoted pre-existing recovery authority." Assert-Throws { Invoke-SimulatedTransition } "filesystem trust epoch is blocked" Assert-True ($sim.Redemptions.Count -eq 0) ` "Later rerun promoted a pre-repair artifact." Assert-Throws { Invoke-SimulatedTransition } "filesystem trust epoch is blocked" Assert-True ($sim.Redemptions.Count -eq 0) ` "Third invocation escaped the durable trust-epoch blocker." Remove-TransitionSimulation } Invoke-BehaviorTest "clean directory repair contains but never continues automatically" { $sim = New-TransitionSimulation -OriginallyRunning $true $sim.DirectoryTrusted = $false Assert-Throws { Invoke-SimulatedTransition } ` "existing Agent directory was untrusted" $repairIndex = $sim.Events.IndexOf("repair-directory") $recoveryWriteIndex = $sim.Events.IndexOf( "write-secret:agent-credential-transition.recovery.json" ) Assert-True ($repairIndex -ge 0 -and $recoveryWriteIndex -lt 0) ` "Same-epoch repair created transition credential state." Assert-True ($sim.Redemptions.Count -eq 0) ` "Same-epoch repair redeemed a transition token." Assert-Throws { Invoke-SimulatedTransition } "filesystem trust epoch is blocked" Assert-Throws { Invoke-SimulatedTransition } "filesystem trust epoch is blocked" Assert-True (Test-Path -LiteralPath $sim.TrustBlocker) ` "Ordinary reruns removed the external trust blocker." Remove-TransitionSimulation } foreach ($lateLeaf in @( "agent-credential-transition.recovery.json", "agent-config.json.pending" )) { Invoke-BehaviorTest "post-scan $lateLeaf stays non-authoritative" { $sim = New-TransitionSimulation -OriginallyRunning $true $sim.DirectoryTrusted = $false Assert-Throws { Invoke-SimulatedTransition -Checkpoint { param($name) if ($name -eq "directory-containment-scan-complete") { $latePath = Join-Path $sim.Target $lateLeaf [IO.File]::WriteAllText( $latePath, '{"original_task_running":false,"agent_secret":"late"}' ) $sim.Secure[(Get-SimPathKey $latePath)] = $true } } } "existing Agent directory was untrusted" Assert-True ($sim.Redemptions.Count -eq 0) ` "Post-scan artifact reached redemption in the repair epoch." Assert-Throws { Invoke-SimulatedTransition } ` "filesystem trust epoch is blocked" Assert-Throws { Invoke-SimulatedTransition } ` "filesystem trust epoch is blocked" Assert-True ($sim.Redemptions.Count -eq 0) ` "Correct-ACL late artifact became authority on an ordinary rerun." Assert-True $sim.TaskRunning ` "Late artifact changed durable Scheduled Task intent." $active = [IO.File]::ReadAllText($sim.Config) | ConvertFrom-Json Assert-True ( [string]$active.agent_secret -eq "legacy-shared-secret" ) "Late pending state was activated while the blocker existed." Remove-TransitionSimulation } } Invoke-BehaviorTest "explicit post-reboot trust reset republishes runtime then resumes" { $sim = New-TransitionSimulation -OriginallyRunning $true $sim.DirectoryTrusted = $false Assert-Throws { Invoke-SimulatedTransition } ` "existing Agent directory was untrusted" $sim.BootChanged = $true $script:BlockerPresentBeforeRemoval = $false Invoke-SimulatedTransition ` -ApproveFilesystemTrustResetAfterReboot ` -Checkpoint { param($name) if ($name -eq "before-trust-blocker-removal") { $script:BlockerPresentBeforeRemoval = Test-Path ` -LiteralPath $sim.TrustBlocker } } | Out-Null Assert-True $script:BlockerPresentBeforeRemoval ` "Parent removed the blocker before final convergence." Assert-True (-not (Test-Path -LiteralPath $sim.TrustBlocker)) ` "Approved post-reboot reset did not remove the external blocker." Assert-True ( $sim.Events -contains "trust-reset-runtime-publication" ) "Trust reset did not publish fresh runtime before continuing." Assert-True ($sim.Redemptions.Count -eq 1) ` "Approved clean trust reset did not resume normal transition." $publishIndex = $sim.Events.IndexOf("trust-reset-runtime-publication") $restoreIndex = $sim.Events.IndexOf("task:True") $removeIndex = $sim.Events.IndexOf( "trust-blocker:removed-after-convergence" ) Assert-True ( $publishIndex -ge 0 -and $restoreIndex -gt $publishIndex -and $removeIndex -gt $restoreIndex ) "Trust blocker removal did not follow runtime and task convergence." Remove-TransitionSimulation } Invoke-BehaviorTest "installer failure leaves trust blocker present" { $sim = New-TransitionSimulation -OriginallyRunning $true $sim.DirectoryTrusted = $false Assert-Throws { Invoke-SimulatedTransition } ` "existing Agent directory was untrusted" $sim.BootChanged = $true $sim.InstallerFailure = $true Assert-Throws { Invoke-SimulatedTransition ` -ApproveFilesystemTrustResetAfterReboot } ` "runtime publication failure" Assert-True (Test-Path -LiteralPath $sim.TrustBlocker) ` "Installer failure removed the trust blocker." Assert-True ($sim.Redemptions.Count -eq 0) ` "Installer failure reached credential redemption." Assert-True ( $sim.TrustResetAuthorized -and $sim.TrustResetOriginalTaskRunning ) "Installer failure lost the strictly validated task intent." $sim.InstallerFailure = $false Invoke-SimulatedTransition ` -ApproveFilesystemTrustResetAfterReboot | Out-Null Assert-True $sim.TaskRunning ` "Installer-failure retry did not restore original running intent." Assert-True (-not (Test-Path -LiteralPath $sim.TrustBlocker)) ` "Installer-failure retry did not converge blocker removal." Remove-TransitionSimulation } Invoke-BehaviorTest "published reset marker resumes without early deletion" { $sim = New-TransitionSimulation -OriginallyRunning $true $sim.DirectoryTrusted = $false Assert-Throws { Invoke-SimulatedTransition } ` "existing Agent directory was untrusted" $sim.BootChanged = $true Assert-Throws { Invoke-SimulatedTransition ` -ApproveFilesystemTrustResetAfterReboot ` -Checkpoint { param($name) if ($name -eq "recovery-written") { throw "stop-after-reset-publication" } } } "stop-after-reset-publication" Assert-True (Test-Path -LiteralPath $sim.TrustBlocker) ` "Interrupted parent removed the trust blocker." Assert-True $sim.TrustResetRuntimePublished ` "Interrupted parent did not persist runtime publication state." Invoke-SimulatedTransition ` -ApproveFilesystemTrustResetAfterReboot | Out-Null Assert-True (-not (Test-Path -LiteralPath $sim.TrustBlocker)) ` "Resumed converged parent did not remove the blocker." Assert-True ($sim.Redemptions.Count -eq 1) ` "Resumed trust reset did not complete exactly one redemption." Remove-TransitionSimulation } Invoke-BehaviorTest "wrong task definition cannot remove trust blocker" { $sim = New-TransitionSimulation -OriginallyRunning $true $sim.DirectoryTrusted = $false Assert-Throws { Invoke-SimulatedTransition } ` "existing Agent directory was untrusted" $sim.BootChanged = $true $sim.TaskDefinitionInvalid = $true Assert-Throws { Invoke-SimulatedTransition ` -ApproveFilesystemTrustResetAfterReboot } ` "strict task definition" Assert-True (Test-Path -LiteralPath $sim.TrustBlocker) ` "Wrong task definition indirectly removed the trust blocker." Assert-True ( $sim.Events -notcontains "trust-reset-runtime-publication" ) "Wrong task definition reached runtime publication." Remove-TransitionSimulation } foreach ($blockedTaskState in @("Disabled", "Unknown", "Queued")) { Invoke-BehaviorTest "$blockedTaskState task cannot remove trust blocker" { $sim = New-TransitionSimulation -OriginallyRunning $true $sim.DirectoryTrusted = $false Assert-Throws { Invoke-SimulatedTransition } ` "existing Agent directory was untrusted" $sim.BootChanged = $true $sim.TaskStateOverride = $blockedTaskState Assert-Throws { Invoke-SimulatedTransition ` -ApproveFilesystemTrustResetAfterReboot } ` "no valid pre-reset intent" Assert-True (Test-Path -LiteralPath $sim.TrustBlocker) ` "$blockedTaskState task indirectly removed the trust blocker." Remove-TransitionSimulation } } Invoke-BehaviorTest "directory is revalidated and simulated swap is blocked" { $sim = New-TransitionSimulation -OriginallyRunning $true $sim.AttemptSwap = $true Invoke-SimulatedTransition | Out-Null Assert-True ( $sim.Events -contains "directory-verified-before-active-move" -and $sim.Events -contains "directory-verified-before-pending-move" ) "Directory invariant was not revalidated before both renames." Assert-True ( $sim.Events -contains "swap-blocked-by-directory" ) "Simulated untrusted pathname swap was not blocked." $active = [IO.File]::ReadAllText($sim.Config) | ConvertFrom-Json Assert-True ([string]$active.agent_secret -ne "legacy-shared-secret") ` "Simulated swap prevented installation of the protected config." Remove-TransitionSimulation } Invoke-BehaviorTest "unknown replacement remnant is quarantined and blocks rerun" { $sim = New-TransitionSimulation -OriginallyRunning $true $unknown = Join-Path $sim.Target "agent-config.json.unknown-replacement" [IO.File]::WriteAllText($unknown, '{"agent_secret":"do-not-log-this"}') $captured = & { try { Invoke-SimulatedTransition } catch { $_.Exception.Message } } *>&1 | Out-String Assert-True (-not $captured.Contains("do-not-log-this")) ` "Unknown remnant contents reached output." Assert-True ($sim.Redemptions.Count -eq 0) ` "Unknown replacement remnant reached redemption." Assert-Throws { Invoke-SimulatedTransition } "evidence is quarantined" Assert-True ($sim.Redemptions.Count -eq 0) ` "Quarantined unknown remnant was later trusted." Remove-TransitionSimulation } Invoke-BehaviorTest "wrong committed active config never converges" { $sim = New-TransitionSimulation -OriginallyRunning $true Assert-Throws { Invoke-SimulatedTransition -Checkpoint { param($name) if ($name -eq "pending-moved-to-active") { throw "stop-after-active-install" } } } "stop-after-active-install" $active = [IO.File]::ReadAllText($sim.Config) | ConvertFrom-Json $active.agent_id = "00000000-0000-4000-8000-000000009999" [IO.File]::WriteAllText( $sim.Config, ($active | ConvertTo-Json -Depth 50) + [Environment]::NewLine ) $calls = $sim.Redemptions.Count Assert-Throws { Invoke-SimulatedTransition } "does not match this Agent" Assert-True ($sim.Redemptions.Count -eq $calls) ` "Wrong active Agent identity reached redemption." Remove-TransitionSimulation } Invoke-BehaviorTest "malformed pending fails closed before redemption" { $sim = New-TransitionSimulation -OriginallyRunning $true Assert-Throws { Invoke-SimulatedTransition -Checkpoint { param($name) if ($name -eq "pending-written") { throw "stop" } } } "stop" [IO.File]::WriteAllText($sim.Pending, '{"agent_secret":') $calls = $sim.Redemptions.Count Assert-Throws { Invoke-SimulatedTransition } "malformed JSON" Assert-True ($sim.Redemptions.Count -eq $calls) ` "Malformed pending state reached redemption." Remove-TransitionSimulation } foreach ($mutation in @("agent", "secret")) { Invoke-BehaviorTest "mismatched pending $mutation fails closed" { $sim = New-TransitionSimulation -OriginallyRunning $true Assert-Throws { Invoke-SimulatedTransition -Checkpoint { param($name) if ($name -eq "pending-written") { throw "stop" } } } "stop" $pending = [IO.File]::ReadAllText($sim.Pending) | ConvertFrom-Json if ($mutation -eq "agent") { $pending.agent_id = "00000000-0000-4000-8000-000000009999" } else { $pending.agent_secret = "attacker-secret" } Set-SimPendingObject -Value $pending $calls = $sim.Redemptions.Count Assert-Throws { Invoke-SimulatedTransition } ` "does not match protected recovery" Assert-True ($sim.Redemptions.Count -eq $calls) ` "Mismatched pending state reached redemption." Remove-TransitionSimulation } } Invoke-BehaviorTest "insecure pending is quarantined and blocked on two runs" { $sim = New-TransitionSimulation -OriginallyRunning $true Assert-Throws { Invoke-SimulatedTransition -Checkpoint { param($name) if ($name -eq "pending-written") { throw "stop" } } } "stop" $sim.Events.Clear() $sim.Secure[(Get-SimPathKey $sim.Pending)] = $false $calls = $sim.Redemptions.Count Assert-Throws { Invoke-SimulatedTransition } "evidence is quarantined" Assert-True ( $sim.Events -notcontains "read:agent-config.json.pending" ) "Insecure pending contents were read." Assert-True ($sim.Redemptions.Count -eq $calls) ` "Insecure pending state reached redemption." Assert-Throws { Invoke-SimulatedTransition } "evidence is quarantined" Assert-True ($sim.Redemptions.Count -eq $calls) ` "Second invocation used quarantined pending authority." Remove-TransitionSimulation } Invoke-BehaviorTest "task restart failure retains recovery and no success cleanup" { $sim = New-TransitionSimulation -OriginallyRunning $true $sim.FailTaskRestore = $true Assert-Throws { Invoke-SimulatedTransition } "task restart failure" Assert-True (Test-Path $sim.Recovery) ` "Recovery was deleted before task verification." Assert-True (-not $sim.CleanupSafe) ` "Cleanup was marked safe after restart failure." $sim.FailTaskRestore = $false Invoke-SimulatedTransition | Out-Null Assert-True $sim.TaskRunning "Task recovery retry failed." Remove-TransitionSimulation } Invoke-BehaviorTest "strict task verification failure preserves committed recovery for retry" { $sim = New-TransitionSimulation -OriginallyRunning $false $sim.TaskVerificationFailureAfterCommit = $true Assert-Throws { Invoke-SimulatedTransition } ` "strict task verification failure" Assert-True (Test-Path -LiteralPath $sim.Recovery) ` "Recovery was deleted after strict final task verification failed." Assert-True (-not $sim.CleanupSafe) ` "Task verification failure reached success cleanup." $recovery = Get-SimRecovery $first = $sim.Redemptions[0] $sim.TaskVerificationFailureAfterCommit = $false Invoke-SimulatedTransition | Out-Null Assert-True ($sim.Redemptions.Count -eq 2) ` "Task-repair retry did not repeat idempotent redemption." Assert-True ( $sim.Redemptions[1].Token -eq $first.Token -and $sim.Redemptions[1].SecretHash -eq $first.SecretHash -and $sim.Redemptions[1].Token -eq $recovery.transition_token -and $sim.Redemptions[1].SecretHash -eq $recovery.agent_secret_hash ) "Task-repair retry changed the committed credential." Assert-True (-not (Test-Path -LiteralPath $sim.Recovery)) ` "Successful task-repair retry did not complete cleanup." Remove-TransitionSimulation } Invoke-BehaviorTest "cleanup interruption resumes from recovery token" { $sim = New-TransitionSimulation -OriginallyRunning $true Assert-Throws { Invoke-SimulatedTransition -Checkpoint { param($name) if ($name -eq "before-recovery-cleanup") { throw "cleanup-interrupted" } } } "cleanup-interrupted" Assert-True (-not (Test-Path $sim.Enrollment)) ` "Enrollment token file should already be removed at this checkpoint." Assert-True (Test-Path $sim.Recovery) ` "Recovery must remain last during cleanup." Invoke-SimulatedTransition | Out-Null Assert-True (-not (Test-Path $sim.Recovery)) ` "Recovery-token retry did not finish cleanup." Assert-True $sim.TaskRunning ` "Cleanup retry changed the intended task state." Remove-TransitionSimulation } Invoke-BehaviorTest "ordinary output and errors omit token and secret" { $sim = New-TransitionSimulation -OriginallyRunning $true $sim.ResponseMode = "lost-once" $captured = & { try { Invoke-SimulatedTransition } catch { $_.Exception.Message } } *>&1 | Out-String $recovery = Get-SimRecovery Assert-True (-not $captured.Contains([string]$recovery.agent_secret)) ` "Generated Agent secret appeared in output." Assert-True (-not $captured.Contains([string]$recovery.transition_token)) ` "Transition token appeared in output." Remove-TransitionSimulation } } finally { Remove-TransitionSimulation } Write-Host "$script:Passed credential-transition behavioral tests passed."