param( [ValidateSet("Check", "Apply", "Verify")] [string]$Mode = "Check", [Parameter(Mandatory = $true)] [string]$TraceId, [Parameter(Mandatory = $true)] [string]$RunId, [Parameter(Mandatory = $true)] [string]$WorkItemId, [Parameter(Mandatory = $true)] [string]$SourceRevision, [string]$AgentRoot = "C:\Wooo\Agent99" ) $ErrorActionPreference = "Stop" $CommandId = "telegram_receipt_index_apply_v1" $CanonicalAsset = "public.awooop_outbound_message" $MigrationSha256 = "50aa05dea1718475051bdf4d45b1361291ec2335512b4d5c7d0a7eb77f935f5e" $NormalizedSqlSha256 = "7e2f18b618c5c97bf7b428e1b224d4ef117c5551d5dc66b84838b14e546bf63a" $JumpHost = "192.168.0.110" $ControlPlaneHost = "192.168.0.120" $RemoteUser = "wooo" $Kubectl = "/usr/local/bin/kubectl" $Namespace = "awoooi-prod" $PodSelector = "app=awoooi-api,environment=prod,system=awoooi" $Container = "api" $IdentityFile = Join-Path $AgentRoot "keys\agent99_ed25519" $SafeIdPattern = "^[A-Za-z0-9][A-Za-z0-9._:@+-]{0,127}$" $ApiPodPattern = "^awoooi-api-[a-z0-9][a-z0-9-]{0,126}$" $ExecutorPod = $null $VerifierPod = $null $TransientTransportOutputUsed = $false $TransportCleanupSucceeded = $true function Write-Agent99DbTerminal { param( [string]$Terminal, [string]$ErrorCode, [object]$CheckReceipt, [object]$ApplyReceipt, [object]$VerifierReceipt, [object]$WritesPerformed ) [pscustomobject]@{ schemaVersion = "agent99_db_bounded_executor_dispatch_v1" commandId = $CommandId canonicalAsset = $CanonicalAsset domain = "database" executor = "db_bounded_executor" verifier = "db_independent_verifier" executionHub = "host:192.168.0.99" dispatchPath = "windows99_agent99_to_host110_to_host120_to_api_pod" dispatchOnly = $true windowsVmwareMutationPerformed = $false kubectlPath = $Kubectl namespace = $Namespace podSelector = $PodSelector executorPod = $ExecutorPod verifierPod = $VerifierPod traceId = $TraceId runId = $RunId workItemId = $WorkItemId sourceRevision = $SourceRevision migrationSha256 = $MigrationSha256 normalizedSqlSha256 = $NormalizedSqlSha256 terminal = $Terminal errorCode = if ($ErrorCode) { $ErrorCode } else { $null } checkReceipt = $CheckReceipt applyReceipt = $ApplyReceipt verifierReceipt = $VerifierReceipt independentVerifier = [bool]( $ApplyReceipt -and $VerifierReceipt -and [string]$ApplyReceipt.pod_name -and [string]$VerifierReceipt.pod_name -and [string]$ApplyReceipt.pod_name -ne [string]$VerifierReceipt.pod_name -and [string]$ApplyReceipt.process_identity_sha256 -ne [string]$VerifierReceipt.process_identity_sha256 ) writesPerformed = $WritesPerformed rawSqlAccepted = $false arbitraryCommandAllowed = $false crossDomainFallbackAllowed = $false secretValueRead = $false transientTransportOutputUsed = $TransientTransportOutputUsed transientTransportOutputDeletedBeforeReceipt = [bool]($TransientTransportOutputUsed -and $TransportCleanupSucceeded) rawTransportOutputPersisted = [bool]($TransientTransportOutputUsed -and -not $TransportCleanupSucceeded) } | ConvertTo-Json -Compress -Depth 12 } function Test-Agent99DbReceipt { param([object]$Receipt, [string]$ExpectedStage, [string]$ExpectedPod) if (-not $Receipt) { return $false } $terminal = [string]$Receipt.terminal $terminalAllowed = switch ($ExpectedStage) { "check" { $terminal -in @("check_ready", "blocked", "failed") } "apply" { $terminal -in @("apply_verified_local", "blocked", "verification_failed", "failed") } "verify" { $terminal -in @("verified_healthy", "verification_failed", "failed") } default { $false } } $writeContract = if ($ExpectedStage -eq "apply") { [bool]( $Receipt.write_attempted -is [bool] -and ( $Receipt.writes_performed -is [bool] -or ($terminal -eq "failed" -and $Receipt.write_attempted -eq $true -and $null -eq $Receipt.writes_performed) ) ) } else { [bool]($Receipt.write_attempted -eq $false -and $Receipt.writes_performed -eq $false) } $catalogContract = [bool]( ($terminal -ne "check_ready" -or $Receipt.catalog.apply_ready -eq $true) -and ($terminal -notin @("apply_verified_local", "verified_healthy") -or ( $Receipt.catalog.index_shape_exact -eq $true -and $Receipt.catalog.catalog_drift -eq $false )) ) return [bool]( $terminalAllowed -and $writeContract -and $catalogContract -and [string]$Receipt.schema_version -eq "awoooi_db_bounded_executor_receipt_v1" -and [string]$Receipt.command_id -eq $CommandId -and [string]$Receipt.canonical_asset -eq $CanonicalAsset -and [string]$Receipt.domain -eq "database" -and [string]$Receipt.executor -eq "db_bounded_executor" -and [string]$Receipt.verifier -eq "db_independent_verifier" -and [string]$Receipt.mode -eq $ExpectedStage -and [string]$Receipt.trace_id -eq $TraceId -and [string]$Receipt.run_id -eq $RunId -and [string]$Receipt.work_item_id -eq $WorkItemId -and [string]$Receipt.source_revision -eq $SourceRevision -and [string]$Receipt.migration_sha256 -eq $MigrationSha256 -and [string]$Receipt.normalized_sql_sha256 -eq $NormalizedSqlSha256 -and [string]$Receipt.pod_name -eq $ExpectedPod -and [string]$Receipt.pod_name -match $ApiPodPattern -and [string]$Receipt.process_identity_sha256 -match "^[0-9a-f]{64}$" -and $Receipt.raw_sql_emitted -eq $false -and $Receipt.secret_value_read -eq $false -and $Receipt.arbitrary_command_allowed -eq $false -and $Receipt.cross_domain_fallback_allowed -eq $false ) } function Invoke-Agent99ReadyApiPodReadback { $remoteCommand = ( "ssh -o BatchMode=yes -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes " + "-o NumberOfPasswordPrompts=0 -o ConnectTimeout=8 -l $RemoteUser " + "$ControlPlaneHost sudo -n $Kubectl get pods " + "--namespace $Namespace --selector '$PodSelector' " + "--field-selector 'status.phase=Running' " + "--output 'custom-columns=NAME:.metadata.name,PHASE:.status.phase,READY:.status.conditions[?(@.type==`"Ready`")].status,DELETING:.metadata.deletionTimestamp' " + "--no-headers" ) $arguments = @( "-i", $IdentityFile, "-o", "BatchMode=yes", "-o", "IdentitiesOnly=yes", "-o", "StrictHostKeyChecking=yes", "-o", "NumberOfPasswordPrompts=0", "-o", "ConnectTimeout=8", "-o", "ServerAliveInterval=20", "-o", "ServerAliveCountMax=3", "-l", $RemoteUser, $JumpHost, $remoteCommand ) $token = [Guid]::NewGuid().ToString("N") $stdoutPath = Join-Path $env:TEMP "agent99-db-pods-$token.out" $stderrPath = Join-Path $env:TEMP "agent99-db-pods-$token.err" $script:TransientTransportOutputUsed = $true try { $process = Start-Process -FilePath "ssh.exe" -ArgumentList $arguments ` -NoNewWindow -RedirectStandardOutput $stdoutPath ` -RedirectStandardError $stderrPath -PassThru if (-not $process.WaitForExit(120 * 1000)) { try { & taskkill.exe /PID $process.Id /T /F 2>&1 | Out-Null } catch {} return [pscustomobject]@{ ok = $false; errorCode = "pod_inventory_timeout"; pods = @() } } $process.WaitForExit() | Out-Null if ($process.ExitCode -ne 0) { return [pscustomobject]@{ ok = $false; errorCode = "pod_inventory_read_failed"; pods = @() } } $readyPods = @{} foreach ($line in @(Get-Content -LiteralPath $stdoutPath -ErrorAction Stop)) { $trimmed = [string]$line.Trim() if (-not $trimmed) { continue } $columns = @($trimmed -split "\s+") if ($columns.Count -ne 4 -or [string]$columns[0] -notmatch $ApiPodPattern -or [string]$columns[1] -ne "Running") { return [pscustomobject]@{ ok = $false; errorCode = "pod_inventory_contract_failed"; pods = @() } } if ([string]$columns[2] -eq "true" -and [string]$columns[3] -eq "") { $podName = [string]$columns[0] $readyPods[$podName] = $true } } $pods = @($readyPods.Keys | Sort-Object) if ($pods.Count -lt 2) { return [pscustomobject]@{ ok = $false; errorCode = "two_ready_api_pods_required"; pods = $pods } } return [pscustomobject]@{ ok = $true; errorCode = ""; pods = $pods } } finally { Remove-Item -LiteralPath $stdoutPath, $stderrPath -Force -ErrorAction SilentlyContinue if ((Test-Path -LiteralPath $stdoutPath) -or (Test-Path -LiteralPath $stderrPath)) { $script:TransportCleanupSucceeded = $false } } } function Invoke-Agent99FixedDbStage { param( [ValidateSet("check", "apply", "verify")][string]$Stage, [string]$PodName ) if ($PodName -notmatch $ApiPodPattern) { return [pscustomobject]@{ ok = $false; errorCode = "target_pod_contract_failed"; receipt = $null } } $remoteCommand = ( "ssh -o BatchMode=yes -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes " + "-o NumberOfPasswordPrompts=0 -o ConnectTimeout=8 -l $RemoteUser " + "$ControlPlaneHost sudo -n $Kubectl exec " + "--namespace $Namespace --container $Container $PodName -- " + "python -B -m src.services.db_bounded_executor " + "--mode $Stage --command-id $CommandId --canonical-asset $CanonicalAsset " + "--migration-sha256 $MigrationSha256 --normalized-sql-sha256 $NormalizedSqlSha256 " + "--trace-id $TraceId --run-id $RunId --work-item-id $WorkItemId --source-revision $SourceRevision" ) $arguments = @( "-i", $IdentityFile, "-o", "BatchMode=yes", "-o", "IdentitiesOnly=yes", "-o", "StrictHostKeyChecking=yes", "-o", "NumberOfPasswordPrompts=0", "-o", "ConnectTimeout=8", "-o", "ServerAliveInterval=20", "-o", "ServerAliveCountMax=3", "-l", $RemoteUser, $JumpHost, $remoteCommand ) $token = [Guid]::NewGuid().ToString("N") $stdoutPath = Join-Path $env:TEMP "agent99-db-$token.out" $stderrPath = Join-Path $env:TEMP "agent99-db-$token.err" $script:TransientTransportOutputUsed = $true try { $process = Start-Process -FilePath "ssh.exe" -ArgumentList $arguments ` -NoNewWindow -RedirectStandardOutput $stdoutPath ` -RedirectStandardError $stderrPath -PassThru $timeoutSeconds = if ($Stage -eq "apply") { 900 } else { 120 } if (-not $process.WaitForExit($timeoutSeconds * 1000)) { try { & taskkill.exe /PID $process.Id /T /F 2>&1 | Out-Null } catch {} return [pscustomobject]@{ ok = $false; errorCode = "dispatch_timeout"; receipt = $null } } $process.WaitForExit() | Out-Null try { $receipt = Get-Content -LiteralPath $stdoutPath -Raw | ConvertFrom-Json } catch { return [pscustomobject]@{ ok = $false; errorCode = "receipt_parse_failed"; receipt = $null } } if (-not (Test-Agent99DbReceipt $receipt $Stage $PodName)) { return [pscustomobject]@{ ok = $false; errorCode = "receipt_contract_failed"; receipt = $null } } if ($process.ExitCode -ne 0) { return [pscustomobject]@{ ok = $false; errorCode = "fixed_remote_stage_failed"; receipt = $receipt } } return [pscustomobject]@{ ok = $true; errorCode = ""; receipt = $receipt } } finally { Remove-Item -LiteralPath $stdoutPath, $stderrPath -Force -ErrorAction SilentlyContinue if ((Test-Path -LiteralPath $stdoutPath) -or (Test-Path -LiteralPath $stderrPath)) { $script:TransportCleanupSucceeded = $false } } } foreach ($identity in @($TraceId, $RunId, $WorkItemId)) { if ($identity -notmatch $SafeIdPattern) { Write-Agent99DbTerminal "blocked" "identity_missing_or_invalid" $null $null $null $false exit 64 } } $SourceRevision = $SourceRevision.ToLowerInvariant() if ($SourceRevision -notmatch "^[0-9a-f]{40}$") { Write-Agent99DbTerminal "blocked" "source_revision_missing_or_invalid" $null $null $null $false exit 64 } if (-not (Test-Path -LiteralPath $IdentityFile -PathType Leaf)) { Write-Agent99DbTerminal "blocked" "dedicated_identity_unavailable" $null $null $null $false exit 65 } $podReadback = Invoke-Agent99ReadyApiPodReadback if (-not $podReadback.ok) { Write-Agent99DbTerminal "blocked" $podReadback.errorCode $null $null $null $false exit 65 } $ExecutorPod = [string]$podReadback.pods[0] $VerifierPod = [string]$podReadback.pods[1] if ($ExecutorPod -notmatch $ApiPodPattern -or $VerifierPod -notmatch $ApiPodPattern -or $ExecutorPod -eq $VerifierPod) { Write-Agent99DbTerminal "blocked" "independent_verifier_pod_contract_failed" $null $null $null $false exit 65 } if ($Mode -eq "Check") { $check = Invoke-Agent99FixedDbStage "check" $ExecutorPod if (-not $check.ok -or [string]$check.receipt.terminal -ne "check_ready") { Write-Agent99DbTerminal "blocked" $(if ($check.errorCode) { $check.errorCode } else { "precheck_failed" }) $check.receipt $null $null $false exit 1 } Write-Agent99DbTerminal "check_ready" "" $check.receipt $null $null $false exit 0 } if ($Mode -eq "Verify") { $verify = Invoke-Agent99FixedDbStage "verify" $VerifierPod if (-not $verify.ok -or [string]$verify.receipt.terminal -ne "verified_healthy") { Write-Agent99DbTerminal "verification_failed" $(if ($verify.errorCode) { $verify.errorCode } else { "catalog_verification_failed" }) $null $null $verify.receipt $false exit 1 } Write-Agent99DbTerminal "verified_healthy" "" $null $null $verify.receipt $false exit 0 } # Apply is the only write path and always includes no-write precheck plus a # second Ready API pod verifier under the same trace/run/work-item identity. $check = Invoke-Agent99FixedDbStage "check" $ExecutorPod if (-not $check.ok -or [string]$check.receipt.terminal -ne "check_ready") { Write-Agent99DbTerminal "blocked" $(if ($check.errorCode) { $check.errorCode } else { "precheck_failed" }) $check.receipt $null $null $false exit 1 } $apply = Invoke-Agent99FixedDbStage "apply" $ExecutorPod $applyWriteState = if ($apply.receipt -and $apply.receipt.writes_performed -eq $true) { $true } elseif ($apply.receipt -and $apply.receipt.write_attempted -eq $true -and $null -eq $apply.receipt.writes_performed) { $null } elseif (-not $apply.ok -and -not $apply.receipt) { # A transport loss after dispatch cannot prove whether PostgreSQL accepted # the concurrent index build. Preserve unknown instead of false-green. $null } else { $false } if (-not $apply.ok -or [string]$apply.receipt.terminal -ne "apply_verified_local") { Write-Agent99DbTerminal "apply_failed" $(if ($apply.errorCode) { $apply.errorCode } else { "bounded_apply_failed" }) $check.receipt $apply.receipt $null $applyWriteState exit 1 } $verify = Invoke-Agent99FixedDbStage "verify" $VerifierPod $independent = [bool]( $verify.ok -and [string]$verify.receipt.terminal -eq "verified_healthy" -and [string]$apply.receipt.pod_name -ne [string]$verify.receipt.pod_name -and [string]$apply.receipt.process_identity_sha256 -ne [string]$verify.receipt.process_identity_sha256 ) if (-not $independent) { Write-Agent99DbTerminal "verification_failed" $(if ($verify.errorCode) { $verify.errorCode } else { "independent_verifier_failed" }) $check.receipt $apply.receipt $verify.receipt $applyWriteState exit 1 } Write-Agent99DbTerminal "verified_healthy" "" $check.receipt $apply.receipt $verify.receipt $applyWriteState exit 0