security: contain physical miner commands in Stage A

Disable physical control through legacy HTTP/M30 producers, typed admission,
queue dispatch and upgraded Windows executors. Restrict diagnostic transports
to exact read-only operations, with ordinal PowerShell validation before sockets.

Retire unsupported pending/claimed work with atomic, idempotent security audits.
Preserve prior delivery evidence and report unknown outcomes after a prior claim,
including explicitly repaired pending rows. Include containment, cancellation,
concurrency and malformed-payload regressions and the old-Agent cutover runbook.

Validation: independent final application-security revalidation PASS. Two unchanged
baseline source-assertion failures are documented as exceptions. Production release
still requires native Windows validation and verified fencing/stop/drain/upgrade;
server cancellation alone cannot revoke a cached command held by an old Agent.
This commit is contained in:
lussierj committed 2026-10-15 20:39:45 +00:00
1 parent 2d89e80410
commit e898f283ab
28 files changed
+2377 -2379

No files matched your search

+103 -72
View File
@@ -1,84 +1,115 @@
$ErrorActionPreference = "Stop"
$script:config = [pscustomobject]@{ api3_account = "operator"; api3_password = "secret"; api3_port = 4433 }
$script:calls = @()
$script:outcomes = @()
$script:outcomeIndex = 0
function Read-AgentConfig { return $script:config }
. (Join-Path (Split-Path $PSScriptRoot -Parent) "MinerOpsControl.ps1")
function Invoke-MinerOpsControlApi3Command {
param([string]$IpAddress, [string]$Command, [object]$Param = $null)
$script:calls += [pscustomobject]@{ command = $Command; param = $Param }
$outcome = $script:outcomes[$script:outcomeIndex]
$script:outcomeIndex += 1
if ($outcome -is [System.Exception]) { throw $outcome }
return $outcome
}
$script:assertions = 0
$script:physicalCalls = 0
$script:readCalls = 0
$script:apiCalls = @()
$script:commands = @()
function Assert-Equal($Actual, $Expected, [string]$Message) {
$script:assertions++
if ($Actual -ne $Expected) { throw "$Message. Expected '$Expected', got '$Actual'." }
}
function Reset-Harness([object[]]$Outcomes) {
$script:calls = @()
$script:outcomes = @($Outcomes)
$script:outcomeIndex = 0
}
function Response([string]$Mode = "") {
return @{ response = @{ code = 0; msg = @{ "power-mode" = $Mode } } }
}
$payload = @{ action = "performance_mode"; requested_value = "normal"; ip_address = "192.0.2.1" }
$script:config = [pscustomobject]@{ api3_account = ""; api3_password = ""; api3_port = 4433 }
Reset-Harness @()
$result = Run-MinerControlCommand -Payload $payload
Assert-Equal $result.failure_code "credentials_missing" "Missing credentials code"
Assert-Equal $script:calls.Count 0 "Missing credentials must not contact API3"
$script:config = [pscustomobject]@{ api3_account = "operator"; api3_password = "secret"; api3_port = 4433 }
Reset-Harness @((Response), (Response "normal"))
$result = Run-MinerControlCommand -Payload $payload
Assert-Equal $result.verification_status "confirmed" "Performance confirmation"
Assert-Equal @($script:calls | Where-Object command -Like "set.*").Count 1 "Exactly one write"
Reset-Harness @((Response), (Response "low"), (Response "low"), (Response "low"))
$result = Run-MinerControlCommand -Payload $payload
Assert-Equal $result.verification_status "verification_failed" "Contradictory mode"
Assert-Equal $script:calls.Count 4 "One write and three bounded reads"
Reset-Harness @((Response), (Response), (Response), (Response))
$result = Run-MinerControlCommand -Payload $payload
Assert-Equal $result.verification_status "unconfirmed" "Ambiguous reads"
Reset-Harness @((Response), [System.Exception]::new("unknown_command"))
$result = Run-MinerControlCommand -Payload $payload
Assert-Equal $result.verification_status "unavailable" "Unavailable read path"
foreach ($action in @("suspend", "resume")) {
Reset-Harness @((Response))
$result = Run-MinerControlCommand -Payload @{
action = $action; requested_value = $null; ip_address = "192.0.2.1"
function Assert-Throws($Block, [string]$Message) {
$caught = $false
try { & $Block | Out-Null } catch {
$caught = $true
Assert-Equal $_.Exception.Message "physical_control_disabled_stage_a" $Message
}
Assert-Equal $result.command_accepted $true "$action accepted"
Assert-Equal $result.verification_status "unconfirmed" "$action remains unconfirmed"
Assert-Equal $result.observed_service_state $null "$action has no invented state"
Assert-Equal @($script:calls | Where-Object command -Like "set.*").Count 1 "$action one write"
Assert-Equal $caught $true "$Message must reject"
}
Reset-Harness @([System.Exception]::new("vendor response included secret"))
$result = Run-MinerControlCommand -Payload @{
action = "suspend"; requested_value = $null; ip_address = "192.0.2.1"
# Define functions from the real service AST, without executing its service loop,
# config loading, startup, or any scheduled tasks.
$root = Split-Path $PSScriptRoot -Parent
$tokens = $null; $parseErrors = $null
$ast = [System.Management.Automation.Language.Parser]::ParseFile(
(Join-Path $root "MinerOpsAgentService.ps1"), [ref]$tokens, [ref]$parseErrors)
Assert-Equal $parseErrors.Count 0 "Service parser"
foreach ($function in $ast.FindAll({
param($node)
$node -is [System.Management.Automation.Language.FunctionDefinitionAst] -and
$node.Parent -is [System.Management.Automation.Language.NamedBlockAst]
}, $true)) {
Invoke-Expression $function.Extent.Text
}
Assert-Equal $result.failure_code "write_rejected" "Write rejection is sanitized"
$json = $result | ConvertTo-Json -Compress
if ($json -match "vendor|secret|192\\.0\\.2\\.1") { throw "Typed result leaked raw data" }
Reset-Harness @()
$result = Run-MinerControlCommand -Payload @{
action = "performance_mode"; requested_value = "turbo"; ip_address = "192.0.2.1"
$script:productionApi3Payload = (Get-Command Invoke-MinerOpsApi3Payload).ScriptBlock
$script:productionPlainCommand = (Get-Command Invoke-MinerPlainCommand).ScriptBlock
function Invoke-MinerOpsApi3Payload { $script:physicalCalls++; throw "Unexpected API3 transport" }
function Get-MinerOpsApi3Salt { $script:physicalCalls++; throw "Unexpected salt probe" }
function Invoke-MinerPlainCommand { $script:physicalCalls++; throw "Unexpected legacy transport" }
function Read-AgentConfig { return [pscustomobject]@{
agent_id = "simulated-agent"; site_id = ""; site_name = "Simulation"
api3_account = "operator"; api3_password = "mock-only"; api3_port = 4433
} }
function Write-AgentLog { param([string]$Message) }
function Invoke-AgentApi {
param([string]$Method, [string]$Path, [object]$Body)
$script:apiCalls += @{ method = $Method; path = $Path; body = $Body }
return @{ ok = $true }
}
Assert-Equal $result.failure_code "invalid_mode" "Invalid mode code"
Assert-Equal $script:calls.Count 0 "Invalid mode must not contact API3"
function Get-PendingAgentCommands { return $script:commands }
function Run-KnownMinerPoll { $script:readCalls++; return @{ simulated = $true } }
function Run-NewMinerDiscovery { $script:readCalls++; return @{ simulated = $true } }
Write-Host "MinerOps typed Agent control tests passed."
# The pre-module legacy implementation must also be inert, including helpers
# which used to support direct generic API3 or M30/4028 writes.
Assert-Throws { Run-MinerControlCommand -Payload @{ action = "reboot"; dry_run = $false } } "Legacy executor"
Assert-Throws { Invoke-MinerOpsApi3Service -IpAddress "192.0.2.1" -ServiceAction "stop" } "Legacy service helper"
Assert-Throws { & $script:productionApi3Payload -IpAddress "192.0.2.1" -Payload @{ cmd = "set.miner.service"; param = "stop" } } "Generic API3 payload helper"
Assert-Throws { & $script:productionPlainCommand -IpAddress "192.0.2.1" -Command "reboot" } "Generic legacy command helper"
Assert-Throws { Invoke-MinerOpsApi3AuthenticatedCommand -IpAddress "192.0.2.1" -Command "set.miner.service" -Param "stop" } "Legacy API3"
Assert-Throws { Invoke-MinerOpsLegacy4028Command -IpAddress "192.0.2.1" -CommandJson '{"command":"reboot"}' } "Legacy 4028"
Assert-Throws { Invoke-MinerOpsM30Legacy4028Test -Payload @{ legacy_4028_command = "reboot" } } "M30 helper"
. (Join-Path $root "MinerOpsControl.ps1")
Assert-Throws { Invoke-MinerOpsControlApi3Command -IpAddress "192.0.2.1" -Command "set.miner.service" -Param "stop" } "Typed transport"
Assert-Throws { Invoke-MinerOpsControlApi3Command -IpAddress "192.0.2.1" -Command "get.read_then_write" } "Disguised diagnostic"
$payloads = @(
@{ action = "legacy_4028"; legacy_4028_command = "reboot"; dry_run = $false },
@{ action = "reboot"; ip_address = "192.0.2.1" },
@{ schema_version = 1; action = "suspend"; ip_address = "192.0.2.1"; miner_id = "spoofed" },
@{ schema_version = 1; action = "resume"; ip_address = "192.0.2.1" },
@{ schema_version = 1; action = "performance_mode"; requested_value = "high"; ip_address = "192.0.2.1" },
@{ schema_version = 999; action = "suspend" },
@{ schema_version = "1"; action = "performance_mode"; performance_mode = "high" },
@{ command = "summary"; params = @{ cmd = "set.miner.service"; param = "stop" } },
@{ action = "read_reboot"; dry_run = $true },
@{},
'{"schema_version":0,"action":"reboot"}',
"{invalid-json"
)
foreach ($payload in $payloads) {
$result = Run-MinerControlCommand -Payload $payload
Assert-Equal $result.command_accepted $false "Rejected payload acceptance"
Assert-Equal $result.failure_code "physical_control_disabled_stage_a" "Rejected payload reason"
Assert-Equal $result._queue_status "failed" "Rejected payload terminal result"
Assert-Equal $result.verification_attempted $false "No physical verification/probe"
Assert-Equal $result.observed_service_state $null "No invented device state"
Assert-Equal $script:physicalCalls 0 "No physical calls"
$script:commands = @(@{ command_id = "legacy-delivery"; command_type = "miner_control"; request_payload = $payload })
$script:apiCalls = @()
Assert-Equal (Process-PendingCommands) 1 "Delivered legacy command handled"
Assert-Equal $script:apiCalls.Count 1 "One failed result submitted"
Assert-Equal $script:apiCalls[0].body.status "failed" "Legacy delivery failed"
Assert-Equal $script:apiCalls[0].body.result_payload.command_accepted $false "Legacy delivery not accepted"
Assert-Equal $script:physicalCalls 0 "No writes after delivery"
}
foreach ($kind in @("raw_api3", "miner_control_v2", "read_reboot", "")) {
$script:commands = @(@{ command_id = "unsupported"; command_type = $kind; request_payload = @{ action = "reboot" } })
$script:apiCalls = @()
Assert-Equal (Process-PendingCommands) 1 "Unknown command handled"
Assert-Equal $script:apiCalls[0].body.status "failed" "Unknown command fails closed"
}
Assert-Equal $script:readCalls 0 "No physical request reinterpreted as telemetry"
$script:commands = @(@{ command_id = "read-only"; command_type = "poll_known_miners_now"; request_payload = @{} })
$script:apiCalls = @()
Assert-Equal (Process-PendingCommands) 1 "Fixed read-only command available"
Assert-Equal $script:readCalls 1 "Only simulated read performed"
Assert-Equal $script:physicalCalls 0 "No physical calls in entire suite"
$script:apiCalls = @()
Send-AgentCheckin | Out-Null
Assert-Equal $script:apiCalls[0].body.control_available $false "Cannot advertise physical capability"
Assert-Equal $script:apiCalls[0].body.control_unavailable_reason "physical_control_disabled_stage_a" "Containment advertised"
Write-Host "Stage A Windows containment passed: $($script:assertions) assertions; zero physical transport calls."
@@ -0,0 +1,199 @@
$ErrorActionPreference = "Stop"
$script:assertions = 0
$script:connections = 0
$TimeoutMs = 1000
function Assert-True([bool]$Condition, [string]$Message) {
$script:assertions++
if (-not $Condition) { throw $Message }
}
function Assert-Rejected([scriptblock]$Call) {
$before = $script:connections
$caught = $false
try { & $Call | Out-Null } catch {
$caught = $true
Assert-True ($_.Exception.Message -eq "unsupported_diagnostic_operation_stage_a") "Must reject before transport"
}
Assert-True $caught "Unsupported diagnostics must throw"
Assert-True ($script:connections -eq $before) "Rejected request must create zero sockets and write zero bytes"
}
function Import-Definitions([string]$Path) {
$tokens = $null; $errors = $null
$ast = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$tokens, [ref]$errors)
Assert-True ($errors.Count -eq 0) "Production script must parse"
foreach ($definition in $ast.FindAll({
param($node)
$node -is [System.Management.Automation.Language.FunctionDefinitionAst] -and
$node.Parent -is [System.Management.Automation.Language.NamedBlockAst]
}, $true)) {
. ([scriptblock]::Create("function global:" + $definition.Name + " " + $definition.Body.Extent.Text))
}
}
# Load only real function definitions: no config, Hub request, or scheduled loop.
$root = Split-Path $PSScriptRoot -Parent
Import-Definitions (Join-Path $root "EnrichOneMiner.ps1")
Import-Definitions (Join-Path $root "MinerOpsApi3EnrichmentPoller.ps1")
Import-Definitions (Join-Path $root "MinerOpsPoolPoller.ps1")
function New-Object {
param([Parameter(Position=0)][string]$TypeName,
[Parameter(Position=1)][object[]]$ArgumentList)
if ($TypeName -eq "System.Net.Sockets.TcpClient") { $script:connections++ }
if ($null -ne $ArgumentList) {
return Microsoft.PowerShell.Utility\New-Object -TypeName $TypeName -ArgumentList $ArgumentList
}
return Microsoft.PowerShell.Utility\New-Object -TypeName $TypeName
}
# Auditor reproductions plus write families, alias/parameter overrides, injection,
# malformed structures and operations that merely contain a read-looking substring.
foreach ($command in @("reboot", "set.miner.service", "power_off", "switchpool", "ascset",
"summary|reboot", "summary`nreboot", 'summary","cmd":"reboot', "read_then_reboot",
"SUMMARY", "", "get.device.info", "get_miner_info|all")) {
Assert-Rejected { Invoke-MinerCommandRaw -IpAddress "127.0.0.1" -Command $command }
}
Assert-Rejected { Invoke-MinerCommandRaw -IpAddress "127.0.0.1" -Command "summary" -Port 4433 }
$badRequests = @(
@{ cmd = "set.miner.service"; param = "stop" },
@{ cmd = "reboot" }, @{ cmd = "set.miner.power_mode"; param = "high" },
@{ cmd = "set.device.password"; param = "changed" }, @{ cmd = "get.read_then_write" },
@{ cmd = "get.device.info"; command = "reboot" },
@{ cmd = "get.device.info"; param = "stop" },
@{ cmd = "get.device.info"; account = "operator"; token = "anything" },
@{ cmd = "get.miner.status" }, @{ cmd = "get.miner.status"; param = "all" },
@{ cmd = "get.miner.status"; param = @("summary+pools+edevs") },
@{ cmd = "get.miner.status"; param = "summary+pools+edevs"; command = "reboot" },
@{ Cmd = "get.device.info" }, @{ cmd = "GET.DEVICE.INFO" },
@{ cmd = "get.device.info`nreboot" }, @{ cmd = @("get.device.info", "reboot") },
@{ cmd = $null }, @{}
)
foreach ($request in $badRequests) {
Assert-Rejected { Invoke-WhatsMinerApi3Read -Ip "127.0.0.1" -Request $request }
Assert-Rejected { Invoke-Api3OkCommand -Ip "127.0.0.1" -Request $request }
}
# Exact re-audit suffixes: U+0000, U+00AD and U+200B; also prefix/embedded
# invisible characters, controls and whitespace. Never trim or normalize inputs.
$originalCulture = [Threading.Thread]::CurrentThread.CurrentCulture
try {
foreach ($cultureName in @("en-US", "tr-TR", "")) {
[Threading.Thread]::CurrentThread.CurrentCulture = [Globalization.CultureInfo]::GetCultureInfo($cultureName)
$variants = @()
foreach ($code in @(0, 0x00ad, 0x200b, 0x200c, 0x200d, 0x2060, 0xfeff,
0x001f, 13, 10, 9, 32, 0x00a0)) {
$character = [string][char]$code
foreach ($position in @("prefix", "embedded", "suffix")) {
$variants += @{ Character = $character; Position = $position }
}
}
foreach ($variant in $variants) {
$legacy = "summary"; $api = "get.device.info"; $key = "cmd"; $param = "summary+pools+edevs"
if ($variant.Position -eq "prefix") {
$legacy = $variant.Character + $legacy; $api = $variant.Character + $api
$key = $variant.Character + $key; $param = $variant.Character + $param
} elseif ($variant.Position -eq "embedded") {
$legacy = "sum" + $variant.Character + "mary"; $api = "get.device." + $variant.Character + "info"
$key = "c" + $variant.Character + "md"; $param = "summary+" + $variant.Character + "pools+edevs"
} else {
$legacy += $variant.Character; $api += $variant.Character
$key += $variant.Character; $param += $variant.Character
}
Assert-Rejected { Invoke-MinerCommandRaw -IpAddress "127.0.0.1" -Command $legacy }
Assert-Rejected { Invoke-WhatsMinerApi3Read -Ip "127.0.0.1" -Request @{ cmd = $api } }
Assert-Rejected { Invoke-Api3OkCommand -Ip "127.0.0.1" -Request @{ cmd = $api } }
$request = @{}; $request[$key] = "get.device.info"
Assert-Rejected { Invoke-WhatsMinerApi3Read -Ip "127.0.0.1" -Request $request }
Assert-Rejected { Invoke-WhatsMinerApi3Read -Ip "127.0.0.1" -Request @{ cmd = "get.miner.status"; param = $param } }
$request = @{ cmd = "get.miner.status" }; $request[$key.Replace("cmd", "param")] = "summary+pools+edevs"
Assert-Rejected { Invoke-WhatsMinerApi3Read -Ip "127.0.0.1" -Request $request }
}
foreach ($legacy in @(("summar" + [char]0x0443), ([char]0xff53 + "ummary"),
("summ" + [char]0x0061 + [char]0x0301 + "ry"),
("summ" + [char]0x00e1 + "ry"), "Summary", "summary/", "summary+")) {
Assert-Rejected { Invoke-MinerCommandRaw -IpAddress "127.0.0.1" -Command $legacy }
}
foreach ($api in @(("get.dev" + [char]0x0456 + "ce.info"), ("get" + [char]0xff0e + "device.info"),
([char]0xff47 + "et.device.info"), "Get.Device.Info", "get.device.info/", "get.device.info+")) {
Assert-Rejected { Invoke-WhatsMinerApi3Read -Ip "127.0.0.1" -Request @{cmd=$api} }
}
Assert-Rejected { Invoke-WhatsMinerApi3Read -Ip "127.0.0.1" -Request @{cmd="get.miner.status"; Param="summary+pools+edevs"} }
Assert-Rejected { Invoke-WhatsMinerApi3Read -Ip "127.0.0.1" -Request @{cmd="get.miner.status"; param="Summary+pools+edevs"} }
}
} finally { [Threading.Thread]::CurrentThread.CurrentCulture = $originalCulture }
Assert-True ($script:connections -eq 0) "All negative cases produced zero transport connections/writes"
# Real TCP compatibility tests: bind loopback only, one request per simulator.
# This suite must be run with physical networks disconnected (Docker --network none).
Add-Type -TypeDefinition @'
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
public class MinerOpsDiagnosticSimulator : IDisposable {
TcpListener listener;
Thread thread;
public string Payload;
public string Error;
public MinerOpsDiagnosticSimulator(int port, bool framed) {
listener = new TcpListener(IPAddress.Loopback, port); listener.Start();
thread = new Thread(() => {
try {
using (var client = listener.AcceptTcpClient()) {
client.ReceiveTimeout = 2000;
var stream = client.GetStream(); var buffer = new byte[65536]; int count;
if (framed) {
var length = new byte[4]; ReadExact(stream, length, 4);
count = BitConverter.ToInt32(length, 0);
if(count < 1 || count > buffer.Length) throw new Exception("Bad request length");
ReadExact(stream, buffer, count);
} else { count = stream.Read(buffer, 0, buffer.Length); }
Payload = Encoding.ASCII.GetString(buffer, 0, count);
var response = Encoding.ASCII.GetBytes("{\"code\":0,\"POOLS\":[],\"msg\":{}}");
if(framed) { var length = BitConverter.GetBytes(response.Length); stream.Write(length,0,4); }
stream.Write(response, 0, response.Length);
}
} catch(Exception e) { Error = e.Message; }
finally { listener.Stop(); }
}); thread.IsBackground = true; thread.Start();
}
static void ReadExact(NetworkStream stream, byte[] bytes, int count) {
int offset=0;
while(offset<count) { int n=stream.Read(bytes,offset,count-offset); if(n==0) throw new Exception("EOF"); offset+=n; }
}
public void Dispose() { listener.Stop(); thread.Join(3000); }
}
'@
foreach ($command in @("summary", "devs", "edevs", "stats", "pools", "devdetails", "version", "get_version", "get_miner_info")) {
$miner = [MinerOpsDiagnosticSimulator]::new(4028, $false)
try {
$null = Invoke-MinerCommandRaw -IpAddress "127.0.0.1" -Command $command -TimeoutMs 100
$observed = $miner.Payload | ConvertFrom-Json
Assert-True ([string]::Equals($observed.cmd, $command, [StringComparison]::Ordinal)) "Legacy query must remain compatible"
Assert-True ($observed.PSObject.Properties.Count -eq 1) "Legacy request contains only selected read"
Assert-True (-not $miner.Error) "Simulator received a valid legacy request"
} finally { $miner.Dispose() }
}
foreach ($command in @("get.device.info", "get.miner.setting", "get.miner.status", "get.fan.setting", "get.device.custom_data")) {
$request = @{ cmd = $command }
if ($command -ceq "get.miner.status") { $request.param = "summary+pools+edevs" }
$miner = [MinerOpsDiagnosticSimulator]::new(4433, $true)
try {
$result = Invoke-Api3OkCommand -Ip "127.0.0.1" -Request $request
$observed = $miner.Payload | ConvertFrom-Json
Assert-True ($null -ne $result) "Existing API3 reader wrapper remains compatible"
Assert-True ([string]::Equals($observed.cmd, $command, [StringComparison]::Ordinal)) "Exact API3 query transmitted"
Assert-True (@($observed.PSObject.Properties).Count -eq $request.Count) "No additional API3 request keys"
if ($command -ceq "get.miner.status") {
Assert-True ([string]::Equals($observed.param, "summary+pools+edevs", [StringComparison]::Ordinal)) "Only exact read parameter transmitted"
}
Assert-True (-not $miner.Error) "Simulator received a valid framed read"
} finally { $miner.Dispose() }
}
$miner = [MinerOpsDiagnosticSimulator]::new(4028, $false)
try {
$null = Invoke-MinerOps4028PoolsSimple -Ip "127.0.0.1"
Assert-True (($miner.Payload | ConvertFrom-Json).command -ceq "pools") "Pool poller sends a constant read"
} finally { $miner.Dispose() }
Write-Host "Diagnostic containment passed: $($script:assertions) assertions; all rejected operations made zero connections/writes."
@@ -0,0 +1,46 @@
param(
[Parameter(Mandatory=$true)][string]$BaselineRoot,
[Parameter(Mandatory=$true)][string]$CachedPayloadPath,
[Parameter(Mandatory=$true)][string]$MinerIp,
[switch]$ExpectFenced
)
$ErrorActionPreference = "Stop"
if ($env:MINEROPS_STAGE_A_SIMULATION -ne "1") {
throw "Requires an explicitly isolated Stage A simulation; never use physical targets."
}
# Supply a git-archived 2d89e80 Windows source directory, NOT a running installation.
# Only AST function definitions execute; no startup, config loading, or task loops.
$tokens = $null; $errors = $null
$ast = [System.Management.Automation.Language.Parser]::ParseFile(
(Join-Path $BaselineRoot "MinerOpsAgentService.ps1"), [ref]$tokens, [ref]$errors)
if ($errors.Count) { throw "Baseline parser error" }
foreach ($definition in $ast.FindAll({
param($node)
$node -is [System.Management.Automation.Language.FunctionDefinitionAst] -and
$node.Parent -is [System.Management.Automation.Language.NamedBlockAst]
}, $true)) {
Invoke-Expression $definition.Extent.Text
}
. (Join-Path $BaselineRoot "MinerOpsControl.ps1")
function Read-AgentConfig {
return [pscustomobject]@{
api3_account = "simulation"; api3_password = "simulation-only"; api3_port = 4433
}
}
function Get-MinerOpsApi3Salt { return @{ salt = "simulation-only"; salt_response = @{} } }
$payload = Get-Content -LiteralPath $CachedPayloadPath -Raw | ConvertFrom-Json
if ($payload.action -ne "suspend") { throw "This simulation expects the audited cached suspend command" }
# Remap ONLY the target to the address of the dedicated disposable miner container.
$payload.ip_address = $MinerIp
$result = Run-MinerControlCommand -Payload $payload
if ($ExpectFenced) {
if ($result.command_accepted -ne $false -or $result.failure_code -ne "write_rejected") {
throw "Old executor unexpectedly reached/accepted a simulated miner through the fence"
}
Write-Host "Fenced old cached command failed; independently verify simulator write count stayed zero."
} else {
if ($result.command_accepted -ne $true) { throw "Unfenced control simulation did not reproduce old execution" }
Write-Host "Unfenced old cached command reproduced; simulated miner received service stop."
}