Some checks failed
CD Pipeline / select-latest-carrier (push) Successful in 2m2s
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 6m29s
CD Pipeline / revalidate-deploy-carrier (push) Successful in 29s
CD Pipeline / build-and-deploy (push) Failing after 42s
CD Pipeline / revalidate-post-deploy-carrier (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
511 lines
18 KiB
PowerShell
511 lines
18 KiB
PowerShell
param(
|
|
[string]$Instruction = "",
|
|
[string]$InstructionBase64 = "",
|
|
[string]$Mode = "",
|
|
[switch]$ControlledApply,
|
|
[switch]$RunNow,
|
|
[string]$Source = "agent99-submit-request",
|
|
[string]$RequestedBy = "",
|
|
[string]$RequestedByBase64 = "",
|
|
[string]$ExternalId = "",
|
|
[string]$ReplyChatId = "",
|
|
[string]$AgentRoot = "C:\Wooo\Agent99"
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
function ConvertFrom-AgentBase64Argument {
|
|
param([string]$Value, [string]$FieldName, [int]$MaximumBytes)
|
|
try {
|
|
$bytes = [Convert]::FromBase64String($Value)
|
|
if ($bytes.Length -gt $MaximumBytes) { throw "${FieldName}_too_large" }
|
|
$encoding = New-Object Text.UTF8Encoding($false, $true)
|
|
$text = $encoding.GetString($bytes)
|
|
if ($text.IndexOf([char]0) -ge 0) { throw "${FieldName}_nul_invalid" }
|
|
$text
|
|
} catch {
|
|
throw "${FieldName}_base64_invalid"
|
|
}
|
|
}
|
|
|
|
if ($Instruction -and $InstructionBase64) { throw "instruction_transport_ambiguous" }
|
|
if ($RequestedBy -and $RequestedByBase64) { throw "requested_by_transport_ambiguous" }
|
|
if ($InstructionBase64) { $Instruction = ConvertFrom-AgentBase64Argument $InstructionBase64 "instruction" 8192 }
|
|
if ($RequestedByBase64) { $RequestedBy = ConvertFrom-AgentBase64Argument $RequestedByBase64 "requested_by" 1024 }
|
|
|
|
if (-not $Instruction) {
|
|
$Instruction = Read-Host "Agent99 request"
|
|
}
|
|
|
|
if (-not $Instruction) {
|
|
throw "Instruction is required."
|
|
}
|
|
|
|
$allowedModes = @("Status", "Recover", "StartVMs", "PublicSmoke", "HarborRepair", "AwoooRepair", "Perf", "LoadShed", "SelfCheck", "BackupCheck")
|
|
|
|
function Test-AgentContainsChar {
|
|
param(
|
|
[string]$Text,
|
|
[int[]]$Codes
|
|
)
|
|
foreach ($code in $Codes) {
|
|
if ($Text.IndexOf([string][char]$code) -ge 0) {
|
|
return $true
|
|
}
|
|
}
|
|
return $false
|
|
}
|
|
|
|
function ConvertTo-AgentStableJson {
|
|
param([System.Collections.IDictionary]$Value)
|
|
return (ConvertTo-Json -InputObject $Value -Depth 8 -Compress)
|
|
}
|
|
|
|
function Get-AgentSha256 {
|
|
param([string]$Text)
|
|
$sha = [System.Security.Cryptography.SHA256]::Create()
|
|
try {
|
|
$bytes = [Text.Encoding]::UTF8.GetBytes($Text)
|
|
return ([BitConverter]::ToString($sha.ComputeHash($bytes))).Replace("-", "").ToLowerInvariant()
|
|
} finally {
|
|
$sha.Dispose()
|
|
}
|
|
}
|
|
|
|
function Write-AgentDurableJson {
|
|
param(
|
|
[object]$Value,
|
|
[string]$Path,
|
|
[string]$ExpectedId,
|
|
[string]$ExpectedExternalId
|
|
)
|
|
$temporaryPath = "$Path.tmp-$PID-$([guid]::NewGuid().ToString('N'))"
|
|
$stream = $null
|
|
$writer = $null
|
|
try {
|
|
$json = $Value | ConvertTo-Json -Depth 10
|
|
$encoding = New-Object Text.UTF8Encoding($false)
|
|
$stream = [IO.File]::Open($temporaryPath, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None)
|
|
$writer = New-Object IO.StreamWriter($stream, $encoding)
|
|
$writer.Write($json)
|
|
$writer.Flush()
|
|
$stream.Flush($true)
|
|
$writer.Dispose(); $writer = $null
|
|
$stream.Dispose(); $stream = $null
|
|
$temporaryHash = (Get-FileHash -LiteralPath $temporaryPath -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
Move-Item -LiteralPath $temporaryPath -Destination $Path -Force
|
|
$readbackHash = (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
if ($readbackHash -ne $temporaryHash) { throw "durable_json_hash_mismatch" }
|
|
$readback = Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json
|
|
if ([string]$readback.id -ne $ExpectedId) { throw "durable_json_id_mismatch" }
|
|
if ([string]$readback.externalId -ne $ExpectedExternalId) { throw "durable_json_external_id_mismatch" }
|
|
[pscustomobject]@{
|
|
ok = $true
|
|
path = $Path
|
|
sha256 = $readbackHash
|
|
id = [string]$readback.id
|
|
externalId = [string]$readback.externalId
|
|
}
|
|
} finally {
|
|
if ($writer) { $writer.Dispose() }
|
|
if ($stream) { $stream.Dispose() }
|
|
Remove-Item -LiteralPath $temporaryPath -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
function Read-AgentDurableIngressArtifact {
|
|
param(
|
|
[string]$Path,
|
|
[string]$ExpectedId,
|
|
[string]$ExpectedExternalId,
|
|
[string]$ExpectedSource,
|
|
[string]$ExpectedInstruction,
|
|
[string]$ExpectedMode,
|
|
[bool]$ExpectedControlledApply
|
|
)
|
|
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { throw "ingress_artifact_missing" }
|
|
if ((Get-Item -LiteralPath $Path).Length -gt 262144) { throw "ingress_artifact_oversized" }
|
|
$hash = (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
$readback = Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json
|
|
$modeValue = if ($readback.PSObject.Properties["mode"]) {
|
|
[string]$readback.mode
|
|
} elseif ($readback.PSObject.Properties["resolvedMode"]) {
|
|
[string]$readback.resolvedMode
|
|
} else {
|
|
""
|
|
}
|
|
if (
|
|
[string]$readback.id -ne $ExpectedId -or
|
|
[string]$readback.externalId -ne $ExpectedExternalId -or
|
|
[string]$readback.source -ne $ExpectedSource -or
|
|
[string]$readback.instruction -ne $ExpectedInstruction -or
|
|
$modeValue -ne $ExpectedMode -or
|
|
-not $readback.PSObject.Properties["controlledApply"] -or
|
|
$readback.controlledApply -isnot [bool] -or
|
|
[bool]$readback.controlledApply -ne $ExpectedControlledApply
|
|
) {
|
|
throw "ingress_artifact_identity_conflict"
|
|
}
|
|
[pscustomobject]@{
|
|
ok = $true
|
|
existing = $true
|
|
path = $Path
|
|
sha256 = $hash
|
|
id = [string]$readback.id
|
|
externalId = [string]$readback.externalId
|
|
}
|
|
}
|
|
|
|
function Get-AgentIngressLifecycleReceipt {
|
|
param(
|
|
[string]$QueueDirectory,
|
|
[string]$RequestId,
|
|
[string]$ExpectedExternalId,
|
|
[string]$ExpectedSource,
|
|
[string]$ExpectedInstruction,
|
|
[string]$ExpectedMode,
|
|
[bool]$ExpectedControlledApply
|
|
)
|
|
$processedDirectory = Join-Path $QueueDirectory "processed"
|
|
$failedDirectory = Join-Path $QueueDirectory "failed"
|
|
$known = @(
|
|
[pscustomobject]@{ state = "pending"; path = (Join-Path $QueueDirectory "$RequestId.json") },
|
|
[pscustomobject]@{ state = "running"; path = (Join-Path $QueueDirectory "running-$RequestId.json") },
|
|
[pscustomobject]@{ state = "processed"; path = (Join-Path $processedDirectory "processed-$RequestId.json") },
|
|
[pscustomobject]@{ state = "failed"; path = (Join-Path $failedDirectory "failed-$RequestId.json") }
|
|
)
|
|
if (Test-Path -LiteralPath $failedDirectory -PathType Container) {
|
|
foreach ($stale in @(Get-ChildItem -LiteralPath $failedDirectory -File -Filter "stale-*-*$RequestId.json" -ErrorAction SilentlyContinue)) {
|
|
$known += [pscustomobject]@{ state = "stale_quarantined"; path = $stale.FullName }
|
|
}
|
|
}
|
|
$present = @($known | Where-Object { Test-Path -LiteralPath $_.path -PathType Leaf } | Sort-Object path -Unique)
|
|
if ($present.Count -gt 1) { throw "ingress_lifecycle_ambiguous" }
|
|
if ($present.Count -eq 0) { return $null }
|
|
$receipt = Read-AgentDurableIngressArtifact `
|
|
-Path $present[0].path `
|
|
-ExpectedId $RequestId `
|
|
-ExpectedExternalId $ExpectedExternalId `
|
|
-ExpectedSource $ExpectedSource `
|
|
-ExpectedInstruction $ExpectedInstruction `
|
|
-ExpectedMode $ExpectedMode `
|
|
-ExpectedControlledApply $ExpectedControlledApply
|
|
$receipt | Add-Member -NotePropertyName lifecycleState -NotePropertyValue ([string]$present[0].state) -Force
|
|
$receipt
|
|
}
|
|
|
|
function Convert-AgentGuidToNetworkBytes {
|
|
param([guid]$Guid)
|
|
[byte[]]$bytes = $Guid.ToByteArray()
|
|
return [byte[]]@(
|
|
$bytes[3], $bytes[2], $bytes[1], $bytes[0],
|
|
$bytes[5], $bytes[4], $bytes[7], $bytes[6],
|
|
$bytes[8], $bytes[9], $bytes[10], $bytes[11],
|
|
$bytes[12], $bytes[13], $bytes[14], $bytes[15]
|
|
)
|
|
}
|
|
|
|
function New-AgentUuidV5 {
|
|
param(
|
|
[guid]$Namespace,
|
|
[string]$Name
|
|
)
|
|
[byte[]]$namespaceBytes = Convert-AgentGuidToNetworkBytes $Namespace
|
|
[byte[]]$nameBytes = [Text.Encoding]::UTF8.GetBytes($Name)
|
|
[byte[]]$inputBytes = $namespaceBytes + $nameBytes
|
|
$sha1 = [System.Security.Cryptography.SHA1]::Create()
|
|
try {
|
|
[byte[]]$hash = $sha1.ComputeHash($inputBytes)
|
|
} finally {
|
|
$sha1.Dispose()
|
|
}
|
|
$hash[6] = [byte](($hash[6] -band 0x0f) -bor 0x50)
|
|
$hash[8] = [byte](($hash[8] -band 0x3f) -bor 0x80)
|
|
$hex = -join @($hash[0..15] | ForEach-Object { $_.ToString("x2") })
|
|
$uuidText = "{0}-{1}-{2}-{3}-{4}" -f $hex.Substring(0, 8), $hex.Substring(8, 4), $hex.Substring(12, 4), $hex.Substring(16, 4), $hex.Substring(20, 12)
|
|
return [guid]::Parse($uuidText)
|
|
}
|
|
|
|
function New-AgentControlledDispatchIdentity {
|
|
param(
|
|
[string]$RequestId,
|
|
[string]$RequestSource,
|
|
[string]$RequestExternalId
|
|
)
|
|
|
|
$projectId = "awoooi"
|
|
$incidentId = "agent99-local-$RequestId"
|
|
$sourceFingerprint = Get-AgentSha256 "$RequestSource|$RequestExternalId|$RequestId"
|
|
$routeId = "agent99_local_submit"
|
|
$executionGeneration = "1"
|
|
$approvalId = ""
|
|
$workItemId = "agent99-local:$RequestId"
|
|
$normalized = [ordered]@{
|
|
approval_id = $approvalId
|
|
execution_generation = $executionGeneration
|
|
incident_id = $incidentId
|
|
project_id = $projectId
|
|
route_id = $routeId
|
|
source_fingerprint = $sourceFingerprint
|
|
work_item_id = $workItemId
|
|
}
|
|
$canonical = ConvertTo-AgentStableJson $normalized
|
|
$digest = Get-AgentSha256 $canonical
|
|
$singleFlightCanonical = ConvertTo-AgentStableJson ([ordered]@{
|
|
incident_id = $incidentId
|
|
project_id = $projectId
|
|
route_id = $routeId
|
|
source_fingerprint = $sourceFingerprint
|
|
})
|
|
$singleFlightDigest = Get-AgentSha256 $singleFlightCanonical
|
|
$runId = (New-AgentUuidV5 -Namespace ([guid]::Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8")) -Name "agent99-controlled-dispatch:$canonical").ToString()
|
|
$traceId = "00-$($digest.Substring(0, 32))-$($digest.Substring(32, 16))-01"
|
|
$idempotencyKey = "agent99-controlled:$digest"
|
|
$singleFlightKey = "agent99-controlled-flight:$singleFlightDigest"
|
|
$public = [ordered]@{
|
|
approval_id = $approvalId
|
|
execution_generation = $executionGeneration
|
|
idempotency_key = $idempotencyKey
|
|
incident_id = $incidentId
|
|
lock_owner = $runId
|
|
project_id = $projectId
|
|
route_id = $routeId
|
|
run_id = $runId
|
|
schema_version = "agent99_controlled_dispatch_identity_v1"
|
|
single_flight_key = $singleFlightKey
|
|
source_fingerprint = $sourceFingerprint
|
|
trace_id = $traceId
|
|
work_item_id = $workItemId
|
|
}
|
|
|
|
return [pscustomobject]@{
|
|
schemaVersion = [string]$public.schema_version
|
|
projectId = $projectId
|
|
incidentId = $incidentId
|
|
sourceFingerprint = $sourceFingerprint
|
|
dispatchRouteId = $routeId
|
|
executionGeneration = $executionGeneration
|
|
approvalId = $approvalId
|
|
automationRunId = $runId
|
|
traceId = $traceId
|
|
workItemId = $workItemId
|
|
idempotencyKey = $idempotencyKey
|
|
singleFlightKey = $singleFlightKey
|
|
lockOwner = $runId
|
|
canonicalDigest = Get-AgentSha256 (ConvertTo-AgentStableJson $public)
|
|
}
|
|
}
|
|
|
|
function Resolve-AgentRequestMode {
|
|
param(
|
|
[string]$Text,
|
|
[string]$ExplicitMode
|
|
)
|
|
|
|
if ($ExplicitMode) {
|
|
if ($ExplicitMode -notin $allowedModes) {
|
|
throw "Mode is not allowlisted: $ExplicitMode"
|
|
}
|
|
return $ExplicitMode
|
|
}
|
|
|
|
$lower = $Text.ToLowerInvariant()
|
|
$mentionsPublic = ($lower -match "public|smoke|website|site|route|url|http|502" -or (Test-AgentContainsChar $Text @(0x7DB2, 0x7AD9)))
|
|
$wantsRecovery = ($lower -match "recover|reboot|restore|restart|startup|repair|fix|502" -or (Test-AgentContainsChar $Text @(0x6062, 0x5FA9, 0x91CD, 0x555F, 0x4FEE)))
|
|
|
|
if ($mentionsPublic -and $wantsRecovery) {
|
|
return "Recover"
|
|
}
|
|
|
|
if ($mentionsPublic) {
|
|
return "PublicSmoke"
|
|
}
|
|
|
|
if ($wantsRecovery) {
|
|
return "Recover"
|
|
}
|
|
|
|
if ($lower -match "vm|vmware|startvm|start vm|start vms" -or (Test-AgentContainsChar $Text @(0x865B, 0x64EC, 0x4E3B, 0x6A5F))) {
|
|
return "StartVMs"
|
|
}
|
|
|
|
if ($lower -match "cpu|load|memory|ram|disk|perf|performance|pressure" -or (Test-AgentContainsChar $Text @(0x6548, 0x80FD, 0x78C1, 0x789F, 0x8CA0, 0x8F09))) {
|
|
return "Perf"
|
|
}
|
|
|
|
if ($lower -match "backup|snapshot|restore drill|cron" -or (Test-AgentContainsChar $Text @(0x5099, 0x4EFD))) {
|
|
return "BackupCheck"
|
|
}
|
|
|
|
if ($lower -match "self|agent|health|selfcheck" -or (Test-AgentContainsChar $Text @(0x81EA, 0x6AA2, 0x5065, 0x5EB7))) {
|
|
return "SelfCheck"
|
|
}
|
|
|
|
if ($lower -match "harbor|registry") {
|
|
return "HarborRepair"
|
|
}
|
|
|
|
if ($lower -match "awoooi|k3s|pod|pods") {
|
|
return "AwoooRepair"
|
|
}
|
|
|
|
return "Status"
|
|
}
|
|
|
|
$modeName = Resolve-AgentRequestMode $Instruction $Mode
|
|
$queueDir = Join-Path $AgentRoot "queue"
|
|
$requestDir = Join-Path $AgentRoot "requests"
|
|
$evidenceDir = Join-Path $AgentRoot "evidence"
|
|
New-Item -ItemType Directory -Force -Path $queueDir, $requestDir, $evidenceDir | Out-Null
|
|
|
|
$deterministicIngress = [bool]$ExternalId
|
|
$ingressDigest = Get-AgentSha256 (([string]$Source) + "`n" + ([string]$ExternalId))
|
|
$id = if ($deterministicIngress) {
|
|
"request-" + $ingressDigest.Substring(0, 32)
|
|
} else {
|
|
"request-" + (Get-Date -Format "yyyyMMdd-HHmmss") + "-" + ([guid]::NewGuid().ToString("N").Substring(0, 8))
|
|
}
|
|
$ingressMutex = $null
|
|
$ingressMutexAcquired = $false
|
|
try {
|
|
$ingressMutex = New-Object Threading.Mutex($false, ("Global\Wooo-Agent99-Submit-" + $ingressDigest.Substring(0, 32)))
|
|
try {
|
|
$ingressMutexAcquired = [bool]$ingressMutex.WaitOne([TimeSpan]::FromSeconds(30))
|
|
} catch [Threading.AbandonedMutexException] {
|
|
$ingressMutexAcquired = $true
|
|
}
|
|
if (-not $ingressMutexAcquired) { throw "ingress_single_writer_timeout" }
|
|
|
|
$dispatchIdentity = [pscustomobject]@{
|
|
schemaVersion = ""
|
|
projectId = ""
|
|
incidentId = ""
|
|
sourceFingerprint = ""
|
|
dispatchRouteId = ""
|
|
executionGeneration = ""
|
|
approvalId = ""
|
|
automationRunId = ""
|
|
traceId = ""
|
|
workItemId = ""
|
|
idempotencyKey = ""
|
|
singleFlightKey = ""
|
|
lockOwner = ""
|
|
canonicalDigest = ""
|
|
}
|
|
if ($ControlledApply) {
|
|
$dispatchIdentity = New-AgentControlledDispatchIdentity -RequestId $id -RequestSource $Source -RequestExternalId $ExternalId
|
|
}
|
|
$request = [pscustomobject]@{
|
|
id = $id
|
|
instruction = $Instruction
|
|
resolvedMode = $modeName
|
|
controlledApply = [bool]$ControlledApply
|
|
requestedBy = if ($RequestedBy) { $RequestedBy } else { $env:USERNAME }
|
|
source = $Source
|
|
externalId = $ExternalId
|
|
dispatchIdentitySchemaVersion = $dispatchIdentity.schemaVersion
|
|
automationRunId = $dispatchIdentity.automationRunId
|
|
traceId = $dispatchIdentity.traceId
|
|
workItemId = $dispatchIdentity.workItemId
|
|
idempotencyKey = $dispatchIdentity.idempotencyKey
|
|
executionGeneration = $dispatchIdentity.executionGeneration
|
|
incidentId = $dispatchIdentity.incidentId
|
|
approvalId = $dispatchIdentity.approvalId
|
|
projectId = $dispatchIdentity.projectId
|
|
sourceFingerprint = $dispatchIdentity.sourceFingerprint
|
|
dispatchRouteId = $dispatchIdentity.dispatchRouteId
|
|
singleFlightKey = $dispatchIdentity.singleFlightKey
|
|
lockOwner = $dispatchIdentity.lockOwner
|
|
canonicalDigest = $dispatchIdentity.canonicalDigest
|
|
createdAt = (Get-Date -Format o)
|
|
}
|
|
|
|
$requestPath = Join-Path $requestDir "$id.json"
|
|
$queuePath = Join-Path $queueDir "$id.json"
|
|
$requestReceipt = if (Test-Path -LiteralPath $requestPath -PathType Leaf) {
|
|
Read-AgentDurableIngressArtifact $requestPath $id $ExternalId $Source $Instruction $modeName ([bool]$ControlledApply)
|
|
} else {
|
|
Write-AgentDurableJson $request $requestPath $id $ExternalId
|
|
}
|
|
$queueItem = [pscustomobject]@{
|
|
id = $id
|
|
mode = $modeName
|
|
controlledApply = [bool]$ControlledApply
|
|
requestedBy = if ($RequestedBy) { $RequestedBy } else { $env:USERNAME }
|
|
source = $Source
|
|
externalId = $ExternalId
|
|
correlationKey = $dispatchIdentity.idempotencyKey
|
|
reason = "operator_instruction"
|
|
instruction = $Instruction
|
|
requestPath = $requestPath
|
|
replyChatId = $ReplyChatId
|
|
dispatchIdentitySchemaVersion = $dispatchIdentity.schemaVersion
|
|
automationRunId = $dispatchIdentity.automationRunId
|
|
traceId = $dispatchIdentity.traceId
|
|
workItemId = $dispatchIdentity.workItemId
|
|
idempotencyKey = $dispatchIdentity.idempotencyKey
|
|
executionGeneration = $dispatchIdentity.executionGeneration
|
|
incidentId = $dispatchIdentity.incidentId
|
|
approvalId = $dispatchIdentity.approvalId
|
|
projectId = $dispatchIdentity.projectId
|
|
sourceFingerprint = $dispatchIdentity.sourceFingerprint
|
|
dispatchRouteId = $dispatchIdentity.dispatchRouteId
|
|
singleFlightKey = $dispatchIdentity.singleFlightKey
|
|
lockOwner = $dispatchIdentity.lockOwner
|
|
canonicalDigest = $dispatchIdentity.canonicalDigest
|
|
createdAt = (Get-Date -Format o)
|
|
}
|
|
$queueReceipt = Get-AgentIngressLifecycleReceipt $queueDir $id $ExternalId $Source $Instruction $modeName ([bool]$ControlledApply)
|
|
if (-not $queueReceipt) {
|
|
$queueReceipt = Write-AgentDurableJson $queueItem $queuePath $id $ExternalId
|
|
$queueReceipt | Add-Member -NotePropertyName existing -NotePropertyValue $false -Force
|
|
$queueReceipt | Add-Member -NotePropertyName lifecycleState -NotePropertyValue "pending" -Force
|
|
}
|
|
$submitEvidence = Join-Path $evidenceDir "agent99-submit-$id.json"
|
|
|
|
$result = [pscustomobject]@{
|
|
ok = $true
|
|
id = $id
|
|
instruction = $Instruction
|
|
resolvedMode = $modeName
|
|
controlledApply = [bool]$ControlledApply
|
|
externalId = $ExternalId
|
|
requestPath = $requestPath
|
|
queuePath = $queuePath
|
|
queueLifecyclePath = [string]$queueReceipt.path
|
|
queueLifecycleState = [string]$queueReceipt.lifecycleState
|
|
idempotentReplay = [bool]$queueReceipt.existing
|
|
durableRequest = [bool]$requestReceipt.ok
|
|
requestSha256 = [string]$requestReceipt.sha256
|
|
durableQueue = [bool]$queueReceipt.ok
|
|
queueSha256 = [string]$queueReceipt.sha256
|
|
submitEvidencePath = $submitEvidence
|
|
runNow = [bool]$RunNow
|
|
automationRunId = $dispatchIdentity.automationRunId
|
|
traceId = $dispatchIdentity.traceId
|
|
workItemId = $dispatchIdentity.workItemId
|
|
idempotencyKey = $dispatchIdentity.idempotencyKey
|
|
canonicalDigest = $dispatchIdentity.canonicalDigest
|
|
controlDispatchStatus = "not_requested"
|
|
controlExitCode = $null
|
|
}
|
|
|
|
if ($RunNow) {
|
|
try {
|
|
Start-ScheduledTask -TaskPath "\" -TaskName "Wooo-Agent99-Control-Loop" -ErrorAction Stop
|
|
$result.controlDispatchStatus = "scheduled_task_signaled"
|
|
} catch {
|
|
$result.ok = $false
|
|
$result.controlDispatchStatus = "scheduled_task_signal_failed"
|
|
$result.controlExitCode = -1
|
|
}
|
|
}
|
|
|
|
$submitReceipt = Write-AgentDurableJson $result $submitEvidence $id $ExternalId
|
|
$result | Add-Member -NotePropertyName submitEvidenceSha256 -NotePropertyValue ([string]$submitReceipt.sha256) -Force
|
|
$result | ConvertTo-Json -Depth 8
|
|
} finally {
|
|
if ($ingressMutexAcquired -and $ingressMutex) { try { $ingressMutex.ReleaseMutex() } catch {} }
|
|
if ($ingressMutex) { try { $ingressMutex.Dispose() } catch {} }
|
|
}
|