diff --git a/agent99-bootstrap.ps1 b/agent99-bootstrap.ps1 index 44adacc54..44d7482d8 100644 --- a/agent99-bootstrap.ps1 +++ b/agent99-bootstrap.ps1 @@ -223,6 +223,7 @@ $agentFiles = @( "agent99-contract-check.ps1", "agent99-control-plane.ps1", "agent99-db-bounded-executor.ps1", + "agent99-signoz-metadata-executor.ps1", "agent99-deploy.ps1", "agent99-register-tasks.ps1", "agent99-alertmanager-alertchain-poll.ps1", diff --git a/agent99-contract-check.ps1 b/agent99-contract-check.ps1 index ecc8b9e09..d9404fad3 100644 --- a/agent99-contract-check.ps1 +++ b/agent99-contract-check.ps1 @@ -19,6 +19,7 @@ $requiredFiles = @( "agent99-contract-check.ps1", "agent99-control-plane.ps1", "agent99-db-bounded-executor.ps1", + "agent99-signoz-metadata-executor.ps1", "agent99-deploy.ps1", "agent99-register-tasks.ps1", "agent99-alertmanager-alertchain-poll.ps1", @@ -129,6 +130,7 @@ foreach ($name in @("agent99.config.example.json", "agent99.config.99.example.js $control = [IO.File]::ReadAllText((Join-Path $SourceRoot "agent99-control-plane.ps1"), [Text.Encoding]::UTF8) $dbBoundedExecutor = [IO.File]::ReadAllText((Join-Path $SourceRoot "agent99-db-bounded-executor.ps1"), [Text.Encoding]::UTF8) +$signozMetadataExecutor = [IO.File]::ReadAllText((Join-Path $SourceRoot "agent99-signoz-metadata-executor.ps1"), [Text.Encoding]::UTF8) $inbox = [IO.File]::ReadAllText((Join-Path $SourceRoot "agent99-sre-alert-inbox.ps1"), [Text.Encoding]::UTF8) $telegram = [IO.File]::ReadAllText((Join-Path $SourceRoot "agent99-telegram-inbox.ps1"), [Text.Encoding]::UTF8) $bootstrap = [IO.File]::ReadAllText((Join-Path $SourceRoot "agent99-bootstrap.ps1"), [Text.Encoding]::UTF8) @@ -169,6 +171,24 @@ $dbExecutorTransportContract = [bool]( -not $dbBoundedExecutor.Contains("/usr/local/sbin/awoooi-db-bounded-executor") ) Add-Check "database:fixed_bounded_executor" ($dbExecutorIdentityContract -and $dbExecutorRecoveryContract -and $dbExecutorTransportContract) "Agent99 dispatches one digest-bound create/reindex command through the allowlisted host120 kubectl path, validates safe receipts, and requires a second Ready API pod verifier" +$signozMetadataExecutorContract = [bool]( + $signozMetadataExecutor.Contains('$CanonicalAsset = "signoz-110-control-plane-metadata"') -and + $signozMetadataExecutor.Contains('$CredentialFile = "/etc/awoooi/secrets/signoz-metadata-api-key"') -and + $signozMetadataExecutor.Contains('windows99_agent99_to_host110_fixed_metadata_toolchain') -and + $signozMetadataExecutor.Contains('credential_reference_absent') -and + $signozMetadataExecutor.Contains('Get-Agent99RuntimeSourceBinding') -and + $signozMetadataExecutor.Contains('$result.fileCount -eq 18') -and + $signozMetadataExecutor.Contains('pass_export_verified_restore_pending') -and + $signozMetadataExecutor.Contains('productionRuntimeMutationPerformed = $false') -and + $signozMetadataExecutor.Contains('rawSqliteRead = $false') -and + $signozMetadataExecutor.Contains('rawVolumeRead = $false') -and + $signozMetadataExecutor.Contains('secretValueTransmitted = $false') -and + $signozMetadataExecutor.Contains('arbitraryCommandAllowed = $false') -and + $signozMetadataExecutor.Contains('controlledApplyPhases = [pscustomobject]$controlledApplyPhases') -and + -not $signozMetadataExecutor.Contains('Invoke-Expression') -and + -not $signozMetadataExecutor.Contains('Get-Content -LiteralPath $CredentialFile') +) +Add-Check "backup:signoz_metadata_fixed_executor" $signozMetadataExecutorContract "Windows99 dispatches one fixed host110 metadata export contract, consumes only a root-owned credential reference on target, and requires the independent bundle verifier without raw SQLite or secret output" Add-Check "recovery:auto_trigger" ($control.Contains("Get-AgentBootState") -and $control.Contains("Start-AgentRecoveryFromObservation") -and $control.Contains("recovery_already_queued")) "boot and host-down recovery use a single-flight queue" Add-Check "recovery:durable_boot_event" ($control.Contains("pendingRecovery") -and $control.Contains("host_reboot_recovery_pending") -and $control.Contains("Update-AgentBootRecoveryState") -and $control.Contains("recovered_late")) "boot event remains pending until verified recovery terminal" Add-Check "recovery:terminal_evidence_budget" ($taskRegistration.Contains('$recoverySettings') -and $taskRegistration.Contains('New-TimeSpan -Minutes 20')) "startup recovery outlives the ten-minute SLO long enough to write terminal evidence" diff --git a/agent99-deploy.ps1 b/agent99-deploy.ps1 index a59890425..d2ebb0b2c 100644 --- a/agent99-deploy.ps1 +++ b/agent99-deploy.ps1 @@ -34,6 +34,7 @@ $runtimeFiles = @( "agent99-contract-check.ps1", "agent99-control-plane.ps1", "agent99-db-bounded-executor.ps1", + "agent99-signoz-metadata-executor.ps1", "agent99-deploy.ps1", "agent99-register-tasks.ps1", "agent99-alertmanager-alertchain-poll.ps1", diff --git a/agent99-signoz-metadata-executor.ps1 b/agent99-signoz-metadata-executor.ps1 new file mode 100644 index 000000000..b193592d5 --- /dev/null +++ b/agent99-signoz-metadata-executor.ps1 @@ -0,0 +1,657 @@ +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" + +$CanonicalAsset = "signoz-110-control-plane-metadata" +$TargetHost = "192.168.0.110" +$RemoteUser = "wooo" +$IdentityFile = Join-Path $AgentRoot "keys\agent99_ed25519" +$EvidenceDir = Join-Path $AgentRoot "evidence" +$RuntimeManifestPath = Join-Path $AgentRoot "state\runtime-manifest.json" +$EntrypointPath = [string]$MyInvocation.MyCommand.Path +$SafeIdPattern = "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$" +$BundleId = "bb6d78b67f506072b2e45e92bc1d415364e25852282a9b068900665c578df011" +$ToolchainRoot = "/backup/toolchains/signoz-metadata/$BundleId" +$PolicyPath = "$ToolchainRoot/metadata-export-policy.json" +$ExporterPath = "$ToolchainRoot/signoz-metadata-export.py" +$VerifierPath = "$ToolchainRoot/verify-signoz-metadata-export.py" +$RestoreDriverPath = "$ToolchainRoot/signoz-metadata-restore-drill.py" +$SharedContractPath = "$ToolchainRoot/signoz_metadata_contract.py" +$CredentialFile = "/etc/awoooi/secrets/signoz-metadata-api-key" +$BaseUrl = "http://127.0.0.1:8080" +$OutputDir = "/backup/signoz-metadata-export-$RunId" +$RemoteReceipt = "/backup/deploy-receipts/signoz-metadata-toolchain/export-$RunId.json" +$SignozImage = "signoz/signoz:v0.113.0" +$SignozImageId = "sha256:c17991d10966aedcb0d4d83805a0baf5310f1553d90611629036fcfaee8d969b" +$SignozContainer = "signoz" +$OperationLock = "/tmp/awoooi-signoz-backup-operation.lock" +$TargetKillAfterSeconds = 15 +$TargetGuardWorstCaseSeconds = 95 +$CheckVerifyTargetTimeoutSeconds = 90 +$ApplyTargetTimeoutSeconds = 720 +$CheckVerifyControllerWaitSeconds = 300 +$ApplyControllerWaitSeconds = 900 +$ControllerTransportReserveSeconds = 30 +$TargetProgramTimeoutSeconds = if ($Mode -eq "Apply") { $ApplyTargetTimeoutSeconds } else { $CheckVerifyTargetTimeoutSeconds } +$ControllerWaitSeconds = if ($Mode -eq "Apply") { $ApplyControllerWaitSeconds } else { $CheckVerifyControllerWaitSeconds } +$TargetWorstCaseSeconds = $TargetGuardWorstCaseSeconds + $TargetProgramTimeoutSeconds + $TargetKillAfterSeconds +$EvidenceRunId = if ($RunId -match $SafeIdPattern) { $RunId } else { "invalid-$PID" } +$EvidencePath = Join-Path $EvidenceDir "agent99-signoz-metadata-$($Mode.ToLowerInvariant())-$EvidenceRunId.json" +$EvidenceReservationPath = "$EvidencePath.reserve" +$script:TransientTransportOutputUsed = $false +$script:TransportCleanupSucceeded = $true +$script:EvidenceReserved = $false +$script:ApplyDispatchAttempted = $false + +$ToolchainFiles = [ordered]@{ + $PolicyPath = "df09dbf91d30bcf4d1a2119b1c301240a252fddd446c56ecba253d5399526533" + $ExporterPath = "b184d55bd5601377d25e78e422de172d01c3f8b8ea2de948b65c915201094956" + $SharedContractPath = "f719d3f2ec86acfafe12be1883e57e6675b8b64caab0ee16d6054c8b383fa213" + $VerifierPath = "cbab56dd3919866a96550c1dce46ec67783cd6f00473e4491861e17d1430048c" + $RestoreDriverPath = "38ce70b3f891a3ce8edfaeb880ccf69d0bb6817c3f801a0aefa0f06341901997" +} + +function Invoke-Agent99SignozFixedRemote { + param( + [Parameter(Mandatory = $true)][string]$RemoteCommand, + [int]$TimeoutSeconds = 120 + ) + + $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, + $TargetHost, + $RemoteCommand + ) + $token = [Guid]::NewGuid().ToString("N") + $stdoutPath = Join-Path $env:TEMP "agent99-signoz-metadata-$token.out" + $stderrPath = Join-Path $env:TEMP "agent99-signoz-metadata-$token.err" + $script:TransientTransportOutputUsed = $true + try { + $process = Start-Process -FilePath "ssh.exe" -ArgumentList $arguments ` + -NoNewWindow -RedirectStandardOutput $stdoutPath ` + -RedirectStandardError $stderrPath -PassThru + if (-not $process.WaitForExit($TimeoutSeconds * 1000)) { + try { & taskkill.exe /PID $process.Id /T /F 2>&1 | Out-Null } catch {} + return [pscustomobject]@{ ok = $false; exitCode = $null; errorCode = "remote_timeout"; stdout = "" } + } + $process.WaitForExit() | Out-Null + $stdout = if (Test-Path -LiteralPath $stdoutPath) { + ([IO.File]::ReadAllText($stdoutPath, [Text.Encoding]::UTF8)).Trim() + } else { "" } + return [pscustomobject]@{ + ok = [bool]($process.ExitCode -eq 0) + exitCode = [int]$process.ExitCode + errorCode = if ($process.ExitCode -eq 0) { "" } else { "fixed_remote_command_failed" } + stdout = $stdout + } + } finally { + Remove-Item -LiteralPath $stdoutPath, $stderrPath -Force -ErrorAction SilentlyContinue + if ((Test-Path -LiteralPath $stdoutPath) -or (Test-Path -LiteralPath $stderrPath)) { + $script:TransportCleanupSucceeded = $false + } + } +} + +function Convert-Agent99JsonReceipt { + param([object]$RemoteResult) + if (-not $RemoteResult -or -not [string]$RemoteResult.stdout) { return $null } + try { + return ([string]$RemoteResult.stdout | ConvertFrom-Json) + } catch { + return $null + } +} + +function New-Agent99SignozLockedRemoteCommand { + param( + [Parameter(Mandatory = $true)][string]$FixedCommand, + [Parameter(Mandatory = $true)][int]$TargetTimeoutSeconds, + [ValidateSet("absent", "present")][string]$ExpectedArtifactState + ) + $artifactGuard = if ($ExpectedArtifactState -eq "present") { + "[ -d `"$OutputDir`" ] && [ ! -L `"$OutputDir`" ] && " + + "[ -f `"$RemoteReceipt`" ] && [ ! -L `"$RemoteReceipt`" ]; " + } else { + "[ ! -e `"$OutputDir`" ] && [ ! -L `"$OutputDir`" ] && " + + "[ ! -e `"$RemoteReceipt`" ] && [ ! -L `"$RemoteReceipt`" ]; " + } + return ( + "sudo -n /bin/bash -c 'set -euo pipefail; " + + "[ -f `"$OperationLock`" ] && [ ! -L `"$OperationLock`" ]; " + + "exec 8<`"$OperationLock`"; /usr/bin/flock -n 8; " + + $artifactGuard + + "[ `"`$(/usr/bin/timeout --kill-after=5s 15s /usr/bin/docker inspect --format={{.Image}} $SignozContainer)`" = `"$SignozImageId`" ]; " + + "[ `"`$(/usr/bin/timeout --kill-after=5s 15s /usr/bin/docker inspect --format={{.Config.Image}} $SignozContainer)`" = `"$SignozImage`" ]; " + + "[ `"`$(/usr/bin/timeout --kill-after=5s 15s /usr/bin/docker inspect --format={{.State.Running}} $SignozContainer)`" = `"true`" ]; " + + "[ `"`$(/usr/bin/timeout --kill-after=5s 15s /usr/bin/curl -sS -o /dev/null -w `"%{http_code}`" $BaseUrl/api/v1/health)`" = `"200`" ]; " + + "set +e; active_output=`$(/usr/bin/timeout --kill-after=5s 10s /usr/bin/pgrep -af `"([s]ignoz-metadata-export[.]py|[s]ignoz-metadata-restore-drill[.]py|(^|/|[[:space:]])([b]ackup-signoz|[c]lickhouse-native-backup|[c]lickhouse-native-restore-drill|[r]un-signoz-backup-canary)[.]sh([[:space:]]|$))`"); active_rc=`$?; set -e; " + + "[ `"`$active_rc`" -eq 1 ] && [ -z `"`$active_output`" ]; " + + "exec /usr/bin/timeout --signal=TERM --kill-after=$($TargetKillAfterSeconds)s " + + "$($TargetTimeoutSeconds)s $FixedCommand'" + ) +} + +function Get-Agent99RuntimeSourceBinding { + $result = [ordered]@{ + ok = $false + manifestPresent = $false + runtimeMatched = $false + fileCount = $null + mismatchCount = $null + sourceRevision = "" + entrypointTracked = $false + entrypointHashMatched = $false + } + if (-not (Test-Path -LiteralPath $RuntimeManifestPath -PathType Leaf)) { + return [pscustomobject]$result + } + $result.manifestPresent = $true + try { + $manifest = Get-Content -LiteralPath $RuntimeManifestPath -Raw | ConvertFrom-Json + $result.runtimeMatched = [bool]$manifest.runtimeMatched + $result.fileCount = [int]$manifest.fileCount + $result.mismatchCount = [int]$manifest.mismatchCount + $result.sourceRevision = [string]$manifest.sourceRevision + $rows = @($manifest.files | Where-Object { + [string]$_.name -eq "agent99-signoz-metadata-executor.ps1" + }) + $result.entrypointTracked = [bool]($rows.Count -eq 1) + if ($rows.Count -eq 1 -and $EntrypointPath -and (Test-Path -LiteralPath $EntrypointPath -PathType Leaf)) { + $actualHash = (Get-FileHash -LiteralPath $EntrypointPath -Algorithm SHA256).Hash.ToLowerInvariant() + $result.entrypointHashMatched = [bool]( + [string]$rows[0].runtimeSha256 -eq $actualHash + ) + } + $result.ok = [bool]( + $result.runtimeMatched -and + $result.fileCount -eq 18 -and + $result.mismatchCount -eq 0 -and + $result.sourceRevision -eq $SourceRevision -and + $result.entrypointTracked -and + $result.entrypointHashMatched + ) + } catch { + $result.ok = $false + } + return [pscustomobject]$result +} + +function Test-Agent99SignozReceiptIdentity { + param( + [object]$Receipt, + [string]$ExpectedPhase, + [string[]]$AllowedTerminals + ) + return [bool]( + $Receipt -and + [string]$Receipt.schema -eq "awoooi_signoz_metadata_receipt_v1" -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.phase -eq $ExpectedPhase -and + [string]$Receipt.terminal -in $AllowedTerminals + ) +} + +function Get-Agent99SignozPreflight { + param( + [ValidateSet("absent", "present")][string]$ExpectedArtifactState, + [bool]$RequireCredential + ) + + $credential = Invoke-Agent99SignozFixedRemote ( + "sudo -n /usr/bin/stat -c %u:%a:%s $CredentialFile" + ) + $credentialOwner = $null + $credentialMode = $null + $credentialSize = $null + $credentialMetadataValid = $false + if ($credential.ok -and [string]$credential.stdout -match "^([0-9]+):([0-9]+):([0-9]+)$") { + $credentialOwner = [int]$Matches[1] + $credentialMode = [string]$Matches[2] + $credentialSize = [int64]$Matches[3] + $credentialMetadataValid = [bool]( + $credentialOwner -eq 0 -and + $credentialMode -in @("400", "600") -and + $credentialSize -gt 0 -and + $credentialSize -le 4096 + ) + } + + $hashCommand = "sudo -n /usr/bin/sha256sum " + (($ToolchainFiles.Keys) -join " ") + $hashReadback = Invoke-Agent99SignozFixedRemote $hashCommand + $actualHashes = @{} + if ($hashReadback.ok) { + foreach ($line in @(([string]$hashReadback.stdout) -split "`r?`n")) { + if ($line -match "^([0-9a-f]{64})\s+(.+)$") { + $actualHashes[[string]$Matches[2]] = [string]$Matches[1] + } + } + } + $toolchainExact = [bool]($actualHashes.Count -eq $ToolchainFiles.Count) + foreach ($entry in $ToolchainFiles.GetEnumerator()) { + if (-not $actualHashes.ContainsKey([string]$entry.Key) -or $actualHashes[[string]$entry.Key] -ne [string]$entry.Value) { + $toolchainExact = $false + } + } + + $image = Invoke-Agent99SignozFixedRemote ( + "docker image inspect --format={{.Id}} $SignozImage" + ) + $imageExact = [bool]($image.ok -and [string]$image.stdout -eq $SignozImageId) + $runtime = Invoke-Agent99SignozFixedRemote ( + "docker inspect --format='{{.Id}}|{{.Image}}|{{.Config.Image}}|{{.State.Running}}' $SignozContainer" + ) + $runtimeContainerId = $null + $runtimeImageId = $null + $runtimeImageRef = $null + $runtimeRunning = $false + if ($runtime.ok -and [string]$runtime.stdout -match "^([^|]+)[|]([^|]+)[|]([^|]+)[|](true|false)$") { + $runtimeContainerId = [string]$Matches[1] + $runtimeImageId = [string]$Matches[2] + $runtimeImageRef = [string]$Matches[3] + $runtimeRunning = [bool]([string]$Matches[4] -eq "true") + } + $runtimeImageExact = [bool]( + $runtimeRunning -and + $runtimeImageId -eq $SignozImageId -and + $runtimeImageRef -eq $SignozImage + ) + $health = Invoke-Agent99SignozFixedRemote ( + "curl -fsS --max-time 15 $BaseUrl/api/v1/health" + ) + $healthOk = $false + if ($health.ok) { + try { + $healthPayload = [string]$health.stdout | ConvertFrom-Json + $healthOk = [bool]([string]$healthPayload.status -eq "ok") + } catch { + $healthOk = $false + } + } + + $active = Invoke-Agent99SignozFixedRemote ( + "sudo -n /usr/bin/pgrep -fc '[s]ignoz-metadata-export.py.*--apply'" + ) + $activeOperationCount = $null + if ([string]$active.stdout -match "^[0-9]+$") { + $activeOperationCount = [int]$active.stdout + } + $activeOperationsClear = [bool]( + $active.exitCode -in @(0, 1) -and + $null -ne $activeOperationCount -and + $activeOperationCount -eq 0 + ) + + $outputState = Invoke-Agent99SignozFixedRemote ( + "sudo -n /usr/bin/test -e $OutputDir" + ) + $receiptState = Invoke-Agent99SignozFixedRemote ( + "sudo -n /usr/bin/test -e $RemoteReceipt" + ) + $outputPresent = [bool]($outputState.exitCode -eq 0) + $receiptPresent = [bool]($receiptState.exitCode -eq 0) + $stateReadable = [bool]( + $outputState.exitCode -in @(0, 1) -and + $receiptState.exitCode -in @(0, 1) + ) + $artifactStateReady = if ($ExpectedArtifactState -eq "present") { + [bool]($outputPresent -and $receiptPresent) + } else { + [bool](-not $outputPresent -and -not $receiptPresent) + } + $credentialReady = [bool](-not $RequireCredential -or $credentialMetadataValid) + $ready = [bool]( + $credentialReady -and + $toolchainExact -and + $imageExact -and + $runtimeImageExact -and + $healthOk -and + $activeOperationsClear -and + $stateReadable -and + $artifactStateReady + ) + + return [pscustomobject]@{ + ready = $ready + credentialReferencePresent = [bool]$credential.ok + credentialMetadataValid = $credentialMetadataValid + credentialOwnerUid = $credentialOwner + credentialMode = $credentialMode + credentialSizeBytes = $credentialSize + credentialValueRead = $false + credentialValueOutput = $false + toolchainBundleId = $BundleId + toolchainFileCount = $ToolchainFiles.Count + toolchainExact = $toolchainExact + signozImage = $SignozImage + signozImageId = if ($image.ok) { [string]$image.stdout } else { $null } + signozImageExact = $imageExact + signozContainer = $SignozContainer + signozRuntimeContainerId = $runtimeContainerId + signozRuntimeImageId = $runtimeImageId + signozRuntimeImageRef = $runtimeImageRef + signozRuntimeRunning = $runtimeRunning + signozRuntimeImageExact = $runtimeImageExact + apiHealthOk = $healthOk + activeOperationCount = $activeOperationCount + activeOperationsClear = $activeOperationsClear + expectedArtifactState = $ExpectedArtifactState + outputPresent = $outputPresent + receiptPresent = $receiptPresent + artifactStateReady = $artifactStateReady + } +} + +function Write-Agent99SignozEvidenceCollision { + $collision = [pscustomobject]@{ + schemaVersion = "agent99_signoz_metadata_executor_dispatch_v1" + mode = $Mode + traceId = $TraceId + runId = $RunId + workItemId = $WorkItemId + terminal = "blocked_evidence_identity_collision" + errorCode = "evidence_path_or_reservation_already_exists" + evidencePath = $EvidencePath + completionClaim = $false + } + [Console]::WriteLine(($collision | ConvertTo-Json -Compress)) +} + +function New-Agent99SignozEvidenceReservation { + New-Item -ItemType Directory -Force -Path $EvidenceDir | Out-Null + if ( + (Test-Path -LiteralPath $EvidencePath) -or + (Test-Path -LiteralPath $EvidenceReservationPath) + ) { + return $false + } + $reservationStream = $null + try { + $reservationStream = [IO.File]::Open( + $EvidenceReservationPath, + [IO.FileMode]::CreateNew, + [IO.FileAccess]::Write, + [IO.FileShare]::None + ) + $reservationPayload = [Text.Encoding]::UTF8.GetBytes( + "$TraceId|$RunId|$WorkItemId|$Mode" + [Environment]::NewLine + ) + $reservationStream.Write($reservationPayload, 0, $reservationPayload.Length) + $reservationStream.Flush($true) + $script:EvidenceReserved = $true + return $true + } catch { + return $false + } finally { + if ($reservationStream) { $reservationStream.Dispose() } + } +} + +function Write-Agent99SignozTerminal { + param( + [string]$Terminal, + [string]$ErrorCode, + [object]$Preflight, + [object]$CheckReceipt, + [object]$ApplyReceipt, + [object]$VerifierReceipt, + [object]$ArtifactWritePerformed + ) + + $controlledApplyPhases = [ordered]@{ + sensorSource = if ($Preflight) { "observed" } else { "blocked" } + normalizedAsset = if ($Preflight) { $CanonicalAsset } else { "blocked" } + sourceTruthDiff = if ($Preflight -and $Preflight.toolchainExact) { "exact" } else { "blocked_or_drift" } + aiDecision = "fixed_allowlisted_metadata_export" + riskPolicy = "high_bounded_read_only_source_private_artifact_no_raw_sqlite" + checkMode = if ($CheckReceipt) { [string]$CheckReceipt.terminal } elseif ($Preflight -and $Preflight.ready) { "preflight_pass" } else { "blocked" } + boundedExecution = if ($ApplyReceipt) { [string]$ApplyReceipt.terminal } elseif ($Mode -eq "Apply") { "not_verified" } else { "not_run" } + independentVerifier = if ($VerifierReceipt) { [string]$VerifierReceipt.terminal } else { "not_run" } + rollbackOrNoWrite = if ($Mode -in @("Check", "Verify")) { "no_write" } elseif ($Terminal -eq "export_verified_restore_pending") { "not_required_additive_immutable_artifact" } else { "artifact_preserved_no_delete" } + learningWriteback = "pending_telegram_km_rag_mcp_playbook_ack" + } + $payload = [pscustomobject]@{ + schemaVersion = "agent99_signoz_metadata_executor_dispatch_v1" + canonicalAsset = $CanonicalAsset + domain = "backup_observability" + executor = "agent99_signoz_metadata_executor" + verifier = "signoz_metadata_export_independent_verifier" + executionHub = "host:192.168.0.99" + dispatchPath = "windows99_agent99_to_host110_fixed_metadata_toolchain" + dispatchOnly = $true + mode = $Mode + traceId = $TraceId + runId = $RunId + workItemId = $WorkItemId + sourceRevision = $SourceRevision + targetHost = $TargetHost + toolchainBundleId = $BundleId + credentialReference = $CredentialFile + targetOperationLock = $OperationLock + targetOperationLockRequiredExistingNonSymlink = $true + targetTimeoutKillAfterSeconds = $TargetKillAfterSeconds + targetGuardWorstCaseSeconds = $TargetGuardWorstCaseSeconds + targetProgramTimeoutSeconds = $TargetProgramTimeoutSeconds + targetWorstCaseSeconds = $TargetWorstCaseSeconds + controllerWaitSeconds = $ControllerWaitSeconds + controllerTransportReserveSeconds = $ControllerTransportReserveSeconds + targetTimeoutFinishesBeforeControllerWait = [bool]( + $TargetWorstCaseSeconds + $ControllerTransportReserveSeconds -lt $ControllerWaitSeconds + ) + terminal = $Terminal + errorCode = if ($ErrorCode) { $ErrorCode } else { $null } + preflight = $Preflight + checkReceipt = $CheckReceipt + applyReceipt = $ApplyReceipt + verifierReceipt = $VerifierReceipt + controlledApplyPhases = [pscustomobject]$controlledApplyPhases + artifactWritePerformed = $ArtifactWritePerformed + productionRuntimeMutationPerformed = $false + rawSqliteRead = $false + rawVolumeRead = $false + secretValueReadByController = $false + secretValueTransmitted = $false + secretValuePersistedInEvidence = $false + credentialReferenceDispatchAttempted = $script:ApplyDispatchAttempted + credentialReferenceConsumedOnTarget = if (-not $script:ApplyDispatchAttempted) { + $false + } elseif ($ApplyReceipt) { + $true + } else { + $null + } + arbitraryCommandAllowed = $false + crossDomainFallbackAllowed = $false + restoreTerminal = "pending_isolated_same_version_target" + zeroResidueTerminal = "pending" + completionClaim = $false + telegramAcknowledged = $false + kmRagMcpPlaybookAcknowledged = $false + transientTransportOutputUsed = $script:TransientTransportOutputUsed + transientTransportOutputDeletedBeforeReceipt = [bool]( + $script:TransientTransportOutputUsed -and $script:TransportCleanupSucceeded + ) + rawTransportOutputPersisted = [bool]( + $script:TransientTransportOutputUsed -and -not $script:TransportCleanupSucceeded + ) + evidenceReservedBeforeDispatch = $script:EvidenceReserved + evidencePath = $EvidencePath + } + $json = $payload | ConvertTo-Json -Compress -Depth 14 + New-Item -ItemType Directory -Force -Path $EvidenceDir | Out-Null + if ( + (Test-Path -LiteralPath $EvidencePath) -or + (-not $script:EvidenceReserved -and (Test-Path -LiteralPath $EvidenceReservationPath)) + ) { + Write-Agent99SignozEvidenceCollision + return $false + } + $tempEvidencePath = "$EvidencePath.$PID.$([Guid]::NewGuid().ToString('N')).tmp" + try { + [IO.File]::WriteAllText( + $tempEvidencePath, + $json + [Environment]::NewLine, + (New-Object Text.UTF8Encoding($false)) + ) + Move-Item -LiteralPath $tempEvidencePath -Destination $EvidencePath + if ($script:EvidenceReserved) { + Remove-Item -LiteralPath $EvidenceReservationPath -Force -ErrorAction SilentlyContinue + $script:EvidenceReserved = $false + } + } finally { + Remove-Item -LiteralPath $tempEvidencePath -Force -ErrorAction SilentlyContinue + } + [Console]::WriteLine($json) + return $true +} + +function Stop-Agent99SignozRun { + param( + [string]$Terminal, + [string]$ErrorCode, + [object]$Preflight, + [object]$CheckReceipt, + [object]$ApplyReceipt, + [object]$VerifierReceipt, + [object]$ArtifactWritePerformed, + [int]$ExitCode + ) + if (-not (Write-Agent99SignozTerminal $Terminal $ErrorCode $Preflight $CheckReceipt $ApplyReceipt $VerifierReceipt $ArtifactWritePerformed)) { + exit 74 + } + exit $ExitCode +} + +foreach ($identity in @($TraceId, $RunId, $WorkItemId)) { + if ($identity -notmatch $SafeIdPattern) { + Stop-Agent99SignozRun "blocked_with_safe_next_action" "identity_missing_or_invalid" $null $null $null $null $false 64 + } +} +if ($WorkItemId -ne "P0-OBS-002") { + Stop-Agent99SignozRun "blocked_with_safe_next_action" "work_item_not_allowlisted" $null $null $null $null $false 64 +} +$SourceRevision = $SourceRevision.ToLowerInvariant() +if ($SourceRevision -notmatch "^[0-9a-f]{40}$") { + Stop-Agent99SignozRun "blocked_with_safe_next_action" "source_revision_missing_or_invalid" $null $null $null $null $false 64 +} +if ($TargetWorstCaseSeconds + $ControllerTransportReserveSeconds -ge $ControllerWaitSeconds) { + Stop-Agent99SignozRun "blocked_with_safe_next_action" "target_timeout_not_bounded_before_controller_wait" $null $null $null $null $false 64 +} +if (-not (New-Agent99SignozEvidenceReservation)) { + Write-Agent99SignozEvidenceCollision + exit 66 +} +if (-not (Test-Path -LiteralPath $IdentityFile -PathType Leaf)) { + Stop-Agent99SignozRun "blocked_with_safe_next_action" "dedicated_identity_unavailable" $null $null $null $null $false 65 +} +$runtimeSourceBinding = Get-Agent99RuntimeSourceBinding +if (-not $runtimeSourceBinding.ok) { + Stop-Agent99SignozRun "blocked_with_safe_next_action" "runtime_source_binding_failed" $runtimeSourceBinding $null $null $null $false 65 +} + +if ($Mode -eq "Verify") { + $preflight = Get-Agent99SignozPreflight -ExpectedArtifactState "present" -RequireCredential $false + if (-not $preflight.ready) { + Stop-Agent99SignozRun "verification_failed" "verified_bundle_preflight_failed" $preflight $null $null $null $false 1 + } + $verifyProgram = ( + "/usr/bin/python3 $VerifierPath " + + "--bundle-dir $OutputDir --policy $PolicyPath " + + "--expected-trace-id $TraceId --expected-run-id $RunId " + + "--expected-work-item-id $WorkItemId" + ) + $verifyCommand = New-Agent99SignozLockedRemoteCommand $verifyProgram $CheckVerifyTargetTimeoutSeconds "present" + $verify = Invoke-Agent99SignozFixedRemote $verifyCommand $CheckVerifyControllerWaitSeconds + $verifyReceipt = Convert-Agent99JsonReceipt $verify + if (-not $verify.ok -or -not (Test-Agent99SignozReceiptIdentity $verifyReceipt "independent_export_verifier" @("pass_export_verified_restore_pending"))) { + Stop-Agent99SignozRun "verification_failed" "independent_export_verifier_failed" $preflight $null $null $verifyReceipt $false 1 + } + $postflight = Get-Agent99SignozPreflight -ExpectedArtifactState "present" -RequireCredential $false + if (-not $postflight.ready) { + Stop-Agent99SignozRun "verification_failed" "post_verifier_runtime_or_artifact_drift" $postflight $null $null $verifyReceipt $false 1 + } + Stop-Agent99SignozRun "export_verified_restore_pending" "" $postflight $null $null $verifyReceipt $false 0 +} + +$preflight = Get-Agent99SignozPreflight -ExpectedArtifactState "absent" -RequireCredential $true +if (-not $preflight.credentialReferencePresent) { + Stop-Agent99SignozRun "blocked_with_safe_next_action" "credential_reference_absent" $preflight $null $null $null $false 2 +} +if (-not $preflight.credentialMetadataValid) { + Stop-Agent99SignozRun "blocked_with_safe_next_action" "credential_reference_metadata_invalid" $preflight $null $null $null $false 2 +} +if (-not $preflight.ready) { + Stop-Agent99SignozRun "blocked_with_safe_next_action" "metadata_export_preflight_failed" $preflight $null $null $null $false 1 +} + +$checkProgram = ( + "/usr/bin/python3 $ExporterPath --check " + + "--base-url $BaseUrl --output-dir $OutputDir --credential-file $CredentialFile " + + "--policy $PolicyPath --trace-id $TraceId --run-id $RunId --work-item-id $WorkItemId" +) +$checkCommand = New-Agent99SignozLockedRemoteCommand $checkProgram $CheckVerifyTargetTimeoutSeconds "absent" +$check = Invoke-Agent99SignozFixedRemote $checkCommand $CheckVerifyControllerWaitSeconds +$checkReceipt = Convert-Agent99JsonReceipt $check +if (-not $check.ok -or -not (Test-Agent99SignozReceiptIdentity $checkReceipt "terminal" @("check_pass_no_write"))) { + Stop-Agent99SignozRun "blocked_with_safe_next_action" "metadata_export_check_failed" $preflight $checkReceipt $null $null $false 1 +} +if ($Mode -eq "Check") { + Stop-Agent99SignozRun "check_ready" "" $preflight $checkReceipt $null $null $false 0 +} + +$applyProgram = ( + "/usr/bin/python3 $ExporterPath --apply " + + "--base-url $BaseUrl --output-dir $OutputDir --credential-file $CredentialFile " + + "--policy $PolicyPath --trace-id $TraceId --run-id $RunId --work-item-id $WorkItemId " + + "--receipt-file $RemoteReceipt" +) +$applyCommand = New-Agent99SignozLockedRemoteCommand $applyProgram $ApplyTargetTimeoutSeconds "absent" +$script:ApplyDispatchAttempted = $true +$apply = Invoke-Agent99SignozFixedRemote $applyCommand $ApplyControllerWaitSeconds +$applyReceipt = Convert-Agent99JsonReceipt $apply +if (-not $apply.ok -or -not (Test-Agent99SignozReceiptIdentity $applyReceipt "terminal" @("partial_degraded"))) { + $artifactReadback = Get-Agent99SignozPreflight -ExpectedArtifactState "present" -RequireCredential $false + $writeState = if ($artifactReadback.outputPresent -or $artifactReadback.receiptPresent) { $true } else { $null } + Stop-Agent99SignozRun "apply_failed_artifact_preserved_for_readback" "bounded_export_failed" $artifactReadback $checkReceipt $applyReceipt $null $writeState 1 +} + +$verifyProgram = ( + "/usr/bin/python3 $VerifierPath " + + "--bundle-dir $OutputDir --policy $PolicyPath " + + "--expected-trace-id $TraceId --expected-run-id $RunId " + + "--expected-work-item-id $WorkItemId" +) +$verifyCommand = New-Agent99SignozLockedRemoteCommand $verifyProgram $CheckVerifyTargetTimeoutSeconds "present" +$verify = Invoke-Agent99SignozFixedRemote $verifyCommand $CheckVerifyControllerWaitSeconds +$verifyReceipt = Convert-Agent99JsonReceipt $verify +if (-not $verify.ok -or -not (Test-Agent99SignozReceiptIdentity $verifyReceipt "independent_export_verifier" @("pass_export_verified_restore_pending"))) { + $artifactReadback = Get-Agent99SignozPreflight -ExpectedArtifactState "present" -RequireCredential $false + Stop-Agent99SignozRun "verification_failed_artifact_preserved" "independent_export_verifier_failed" $artifactReadback $checkReceipt $applyReceipt $verifyReceipt $true 1 +} + +$postflight = Get-Agent99SignozPreflight -ExpectedArtifactState "present" -RequireCredential $false +if (-not $postflight.ready) { + Stop-Agent99SignozRun "verification_failed_artifact_preserved" "post_verifier_runtime_or_artifact_drift" $postflight $checkReceipt $applyReceipt $verifyReceipt $true 1 +} +Stop-Agent99SignozRun "export_verified_restore_pending" "" $postflight $checkReceipt $applyReceipt $verifyReceipt $true 0 diff --git a/ops/signoz/p0-obs-002-post-closure-regression.yaml b/ops/signoz/p0-obs-002-post-closure-regression.yaml index 0fed82ef1..865d4c5f8 100644 --- a/ops/signoz/p0-obs-002-post-closure-regression.yaml +++ b/ops/signoz/p0-obs-002-post-closure-regression.yaml @@ -57,9 +57,9 @@ asset_reconciliation: drift_work_item_id: P0-OBS-002-ASSET-DRIFT-001 source: ops/config/service-registry.yaml runtime_mirror: k8s/awoooi-prod/15-service-registry-configmap.yaml - source_service_count: 28 - runtime_mirror_service_count: 28 - exact_shared_service_count: 28 + source_service_count: 29 + runtime_mirror_service_count: 29 + exact_shared_service_count: 29 shared_service_drift_count: 0 source_only_services: [] source_runtime_manifest_exact: true @@ -821,6 +821,33 @@ pending_verifiers: terminal: pass_source_deployed_inactive active_pointer_present: false runtime_export_executed: false + windows99_execution_contract: + status: source_ready_runtime_not_deployed + execution_hub: host:192.168.0.99 + executor: agent99-signoz-metadata-executor.ps1 + executor_sha256: bb6116ab2ffca90001faa567f25f56a90dd5b5506b06b5197bafa773aafafeaa + dispatch_path: windows99_agent99_to_host110_fixed_metadata_toolchain + agent99_runtime_bundle_file_count: 18 + source_contract_test_terminal: 55_passed_4_skipped + windows_powershell_parse_error_count: 0 + credential_reference: /etc/awoooi/secrets/signoz-metadata-api-key + credential_reference_discovery: + sensor_run_id: P0-OBS-002-win99-transport-20260717 + checked_via: windows99_dedicated_agent99_identity_to_host110 + fixed_candidate_count: 5 + present_count: 0 + status: credential_reference_absent + secret_value_read: false + secret_value_output: false + durable_agent99_receipt: pending_executor_deploy + exact_local_images: + signoz: sha256:c17991d10966aedcb0d4d83805a0baf5310f1553d90611629036fcfaee8d969b + clickhouse: sha256:8248e5926d7304400e44ecdf0c7181fbde28e6a9b1d647780eca839f34c00cc6 + zookeeper: sha256:3ab0e8f032ab58f14ac1e929ac40305a4a464cf320bdfe4151d62d6922729ba0 + source_toolchain_exact: true + signoz_api_health: pass + production_export_executed: false + completion_claim: false learning_writeback: partial_degraded remaining_blocker: authenticated_export_restore_zero_residue_pending portable_assets: diff --git a/scripts/ops/tests/test_agent99_signoz_metadata_executor_runtime_entrypoint.py b/scripts/ops/tests/test_agent99_signoz_metadata_executor_runtime_entrypoint.py new file mode 100644 index 000000000..cdbc693bb --- /dev/null +++ b/scripts/ops/tests/test_agent99_signoz_metadata_executor_runtime_entrypoint.py @@ -0,0 +1,206 @@ +from __future__ import annotations + +import re +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[3] +ENTRYPOINT = ROOT / "agent99-signoz-metadata-executor.ps1" + + +def _quoted_array(source: str, start: str, end: str) -> tuple[str, ...]: + body = source[source.index(start) + len(start) :] + body = body[: body.index(end)] + return tuple(re.findall(r'"([^"\n]+)"', body)) + + +def test_entrypoint_is_fixed_to_windows99_host110_and_p0_obs_002() -> None: + source = ENTRYPOINT.read_text(encoding="utf-8") + + assert '[ValidateSet("Check", "Apply", "Verify")]' in source + assert '$CanonicalAsset = "signoz-110-control-plane-metadata"' in source + assert '$TargetHost = "192.168.0.110"' in source + assert '$RemoteUser = "wooo"' in source + assert '$WorkItemId -ne "P0-OBS-002"' in source + assert '$SafeIdPattern = "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"' in source + assert "._+" not in source + assert '$EvidenceRunId = if ($RunId -match $SafeIdPattern)' in source + assert "Get-Agent99RuntimeSourceBinding" in source + assert "$result.fileCount -eq 18" in source + assert "$result.sourceRevision -eq $SourceRevision" in source + assert "$result.entrypointHashMatched" in source + assert '$CredentialFile = "/etc/awoooi/secrets/signoz-metadata-api-key"' in source + assert ( + '$BundleId = "bb6d78b67f506072b2e45e92bc1d415364e25852282a9b068900665c578df011"' + in source + ) + assert '$BaseUrl = "http://127.0.0.1:8080"' in source + assert '$SignozImage = "signoz/signoz:v0.113.0"' in source + assert '$SignozContainer = "signoz"' in source + assert ( + '$SignozImageId = "sha256:c17991d10966aedcb0d4d83805a0baf5310f1553d90611629036fcfaee8d969b"' + in source + ) + + +def test_entrypoint_uses_fixed_public_key_transport_and_no_arbitrary_shell() -> None: + source = ENTRYPOINT.read_text(encoding="utf-8") + + assert 'Start-Process -FilePath "ssh.exe"' in source + assert '"-o", "BatchMode=yes"' in source + assert '"-o", "IdentitiesOnly=yes"' in source + assert '"-o", "StrictHostKeyChecking=yes"' in source + assert '"-o", "NumberOfPasswordPrompts=0"' in source + assert 'windows99_agent99_to_host110_fixed_metadata_toolchain' in source + assert 'arbitraryCommandAllowed = $false' in source + assert 'crossDomainFallbackAllowed = $false' in source + assert "Invoke-Expression" not in source + assert "-EncodedCommand" not in source + assert "cmd.exe" not in source + assert '$OperationLock = "/tmp/awoooi-signoz-backup-operation.lock"' in source + assert '[ -f `"$OperationLock`" ] && [ ! -L `"$OperationLock`" ]' in source + assert 'exec 8<`"$OperationLock`"; /usr/bin/flock -n 8' in source + assert "$TargetGuardWorstCaseSeconds = 95" in source + assert "$CheckVerifyTargetTimeoutSeconds = 90" in source + assert "$ApplyTargetTimeoutSeconds = 720" in source + assert "$CheckVerifyControllerWaitSeconds = 300" in source + assert "$ApplyControllerWaitSeconds = 900" in source + assert "$ControllerTransportReserveSeconds = 30" in source + assert ( + "New-Agent99SignozLockedRemoteCommand $applyProgram " + '$ApplyTargetTimeoutSeconds "absent"' in source + ) + assert ( + "Invoke-Agent99SignozFixedRemote $applyCommand " + "$ApplyControllerWaitSeconds" in source + ) + assert ( + "New-Agent99SignozLockedRemoteCommand $verifyProgram " + '$CheckVerifyTargetTimeoutSeconds "present"' in source + ) + assert ( + "Invoke-Agent99SignozFixedRemote $verifyCommand " + "$CheckVerifyControllerWaitSeconds" in source + ) + assert "targetTimeoutFinishesBeforeControllerWait" in source + assert "target_timeout_not_bounded_before_controller_wait" in source + assert '--kill-after=5s 15s /usr/bin/docker inspect' in source + assert '--kill-after=5s 15s /usr/bin/curl' in source + assert '--kill-after=5s 10s /usr/bin/pgrep' in source + assert "active_rc=`$?; set -e" in source + assert '[ `"`$active_rc`" -eq 1 ]' in source + assert '[ -z `"`$active_output`" ]' in source + lock_helper = source[ + source.index("function New-Agent99SignozLockedRemoteCommand") : + source.index("function Get-Agent99RuntimeSourceBinding") + ] + assert "pgrep -af" in lock_helper + assert "|| true" not in lock_helper + assert '--signal=TERM --kill-after=$($TargetKillAfterSeconds)s' in source + + guard = 95 + kill_after = 15 + transport_reserve = 30 + assert guard + 90 + kill_after + transport_reserve < 300 + assert guard + 720 + kill_after + transport_reserve < 900 + + +def test_entrypoint_never_reads_or_persists_credential_value_in_controller() -> None: + source = ENTRYPOINT.read_text(encoding="utf-8") + + assert 'sudo -n /usr/bin/stat -c %u:%a:%s $CredentialFile' in source + assert 'credentialValueRead = $false' in source + assert 'credentialValueOutput = $false' in source + assert 'secretValueReadByController = $false' in source + assert 'secretValueTransmitted = $false' in source + assert 'secretValuePersistedInEvidence = $false' in source + assert "credentialReferenceDispatchAttempted" in source + assert "$script:ApplyDispatchAttempted = $true" in source + assert "credentialReferenceConsumedOnTarget = if" in source + assert "elseif ($ApplyReceipt)" in source + assert 'rawSqliteRead = $false' in source + assert 'rawVolumeRead = $false' in source + assert "Get-Content -LiteralPath $CredentialFile" not in source + assert "ReadAllText($CredentialFile" not in source + assert "ReadAllBytes($CredentialFile" not in source + + +def test_entrypoint_requires_check_apply_independent_verify_and_partial_terminal() -> None: + source = ENTRYPOINT.read_text(encoding="utf-8") + + assert 'Receipt.schema -eq "awoooi_signoz_metadata_receipt_v1"' in source + assert "awoooi_controlled_apply_receipt_v1" not in source + assert '[string]$Receipt.phase -eq $ExpectedPhase' in source + assert ( + 'Test-Agent99SignozReceiptIdentity $verifyReceipt ' + '"independent_export_verifier"' in source + ) + assert ( + 'Test-Agent99SignozReceiptIdentity $checkReceipt "terminal"' in source + ) + assert ( + 'Test-Agent99SignozReceiptIdentity $applyReceipt "terminal"' in source + ) + assert "$ExporterPath --check" in source + assert "$ExporterPath --apply" in source + assert '--receipt-file $RemoteReceipt' in source + assert 'pass_export_verified_restore_pending' in source + assert 'export_verified_restore_pending' in source + assert 'restoreTerminal = "pending_isolated_same_version_target"' in source + assert 'zeroResidueTerminal = "pending"' in source + assert 'completionClaim = $false' in source + assert "controlledApplyPhases = [pscustomobject]$controlledApplyPhases" in source + assert 'credential_reference_absent' in source + assert 'blocked_with_safe_next_action' in source + assert 'apply_failed_artifact_preserved_for_readback' in source + assert 'post_verifier_runtime_or_artifact_drift' in source + + +def test_entrypoint_reserves_durable_evidence_before_any_remote_dispatch() -> None: + source = ENTRYPOINT.read_text(encoding="utf-8") + main = source[source.index("foreach ($identity") :] + + reservation = main.index("New-Agent99SignozEvidenceReservation") + first_remote = main.index("Get-Agent99RuntimeSourceBinding") + assert reservation < first_remote + assert '[IO.FileMode]::CreateNew' in source + assert '$EvidenceReservationPath = "$EvidencePath.reserve"' in source + assert "blocked_evidence_identity_collision" in source + assert "evidence_path_or_reservation_already_exists" in source + assert "if (-not (Write-Agent99SignozTerminal" in source + assert "exit 74" in source + assert "Write-Agent99SignozTerminal" not in main.replace( + "Stop-Agent99SignozRun", "" + ) + + +def test_entrypoint_binds_export_to_the_running_canonical_signoz_image() -> None: + source = ENTRYPOINT.read_text(encoding="utf-8") + + assert "{{.Id}}|{{.Image}}|{{.Config.Image}}|{{.State.Running}}" in source + assert "$runtimeImageId -eq $SignozImageId" in source + assert "$runtimeImageRef -eq $SignozImage" in source + assert "signozRuntimeImageExact = $runtimeImageExact" in source + assert "$runtimeImageExact -and" in source + + +def test_entrypoint_is_in_exact_agent99_runtime_bundle() -> None: + expected = "agent99-signoz-metadata-executor.ps1" + bootstrap = (ROOT / "agent99-bootstrap.ps1").read_text(encoding="utf-8") + deployer = (ROOT / "agent99-deploy.ps1").read_text(encoding="utf-8") + contract = (ROOT / "agent99-contract-check.ps1").read_text(encoding="utf-8") + sender = ( + ROOT / "scripts/reboot-recovery/deploy-agent99-via-windows99-ssh.sh" + ).read_text(encoding="utf-8") + receiver = ( + ROOT + / "scripts/reboot-recovery/agent99-remote-atomic-deploy-receiver.ps1" + ).read_text(encoding="utf-8") + + assert expected in _quoted_array(bootstrap, "$agentFiles = @(", ")") + assert expected in _quoted_array(deployer, "$runtimeFiles = @(", ")") + assert expected in _quoted_array(contract, "$requiredFiles = @(", ")") + assert expected in _quoted_array(sender, "RUNTIME_FILES=(", ")") + assert expected in _quoted_array(receiver, "$FixedRuntimeFiles = @(", ")") + assert 'expectedRuntimeFileCount": 18' in sender + assert '$runtimeManifest.fileCount -eq 18' in receiver diff --git a/scripts/ops/tests/test_db_bounded_executor_runtime_entrypoint.py b/scripts/ops/tests/test_db_bounded_executor_runtime_entrypoint.py index 238118ee5..f453ca371 100644 --- a/scripts/ops/tests/test_db_bounded_executor_runtime_entrypoint.py +++ b/scripts/ops/tests/test_db_bounded_executor_runtime_entrypoint.py @@ -122,7 +122,7 @@ def test_agent99_atomic_bundle_owns_the_dispatch_entrypoint() -> None: assert "$dbExecutorRecoveryContract" in contract assert "awoooi_db_bounded_executor_receipt_v2" in contract assert "Receipt.database_sqlstate" in contract - assert 'expectedRuntimeFileCount": 17' in transport - assert "expectedRuntimeFileCount=17" in transport - assert "expectedRuntimeFileCount -ne 17" in receiver - assert "fileCount = 17" in receiver + assert 'expectedRuntimeFileCount": 18' in transport + assert "expectedRuntimeFileCount=18" in transport + assert "expectedRuntimeFileCount -ne 18" in receiver + assert "fileCount = 18" in receiver diff --git a/scripts/reboot-recovery/agent99-live-preflight.ps1 b/scripts/reboot-recovery/agent99-live-preflight.ps1 index d1816ab5b..92189091a 100644 --- a/scripts/reboot-recovery/agent99-live-preflight.ps1 +++ b/scripts/reboot-recovery/agent99-live-preflight.ps1 @@ -486,7 +486,7 @@ $decision = Get-AgentLivePreflightDecision ` -AgentRootPresent ([bool](Test-Path $AgentRoot)) ` -Manifest $manifest ` -ManifestCheckRequired ([bool]($ReadinessScope -eq "FullRuntime")) ` - -ExpectedRuntimeFileCount 17 ` + -ExpectedRuntimeFileCount 18 ` -TaskFailureCount $taskFailures.Count ` -RelayListenerCount $relayListeners.Count ` -RequiredEvidenceStaleCount $requiredEvidenceStale.Count ` diff --git a/scripts/reboot-recovery/agent99-remote-atomic-deploy-receiver.ps1 b/scripts/reboot-recovery/agent99-remote-atomic-deploy-receiver.ps1 index 1f368e794..61bbe3058 100644 --- a/scripts/reboot-recovery/agent99-remote-atomic-deploy-receiver.ps1 +++ b/scripts/reboot-recovery/agent99-remote-atomic-deploy-receiver.ps1 @@ -33,6 +33,7 @@ $FixedRuntimeFiles = @( "agent99-contract-check.ps1", "agent99-control-plane.ps1", "agent99-db-bounded-executor.ps1", + "agent99-signoz-metadata-executor.ps1", "agent99-deploy.ps1", "agent99-register-tasks.ps1", "agent99-alertmanager-alertchain-poll.ps1", @@ -940,7 +941,7 @@ try { } $sourceRevision = ([string]$envelope.sourceRevision).ToLowerInvariant() if ($sourceRevision -notmatch "^[0-9a-f]{40}$") { throw "invalid_source_revision" } - if ([int]$envelope.expectedRuntimeFileCount -ne 17) { throw "invalid_expected_runtime_file_count" } + if ([int]$envelope.expectedRuntimeFileCount -ne 18) { throw "invalid_expected_runtime_file_count" } $traceId = [string]$envelope.traceId $runId = [string]$envelope.runId @@ -975,7 +976,7 @@ try { $sourceManifest = [pscustomobject]@{ schemaVersion = "agent99_remote_source_manifest_v1" sourceRevision = $sourceRevision - fileCount = 17 + fileCount = 18 manifestSha256 = $manifestDigest files = @($FixedRuntimeFiles | ForEach-Object { [pscustomobject]@{ name = $_; sha256 = ([string]($filesByName[$_].sha256)).ToLowerInvariant() } @@ -1012,7 +1013,7 @@ try { status = "check_ready" mode = "check" sourceRevision = $sourceRevision - expectedRuntimeFileCount = 17 + expectedRuntimeFileCount = 18 manifestSha256 = $manifestDigest plannedStagingPath = $stagePath runtimeManifest = $currentManifest @@ -1099,7 +1100,7 @@ try { if ( [string]$existingSourceManifest.schemaVersion -ne "agent99_remote_source_manifest_v1" -or [string]$existingSourceManifest.sourceRevision -ne $sourceRevision -or - [int]$existingSourceManifest.fileCount -ne 17 -or + [int]$existingSourceManifest.fileCount -ne 18 -or [string]$existingSourceManifest.manifestSha256 -ne $manifestDigest ) { throw "existing_staging_manifest_mismatch" } } catch { @@ -1123,7 +1124,7 @@ try { [string]$prior.launcherContract.sha256 -match "^[0-9a-f]{64}$" -and $live.runtimeMatched -and $live.sourceRevision -eq $sourceRevision -and - $live.fileCount -eq 17 -and + $live.fileCount -eq 18 -and $live.mismatchCount -eq 0 -and $liveLauncherContract.ok -and [string]$liveLauncherContract.sha256 -eq [string]$prior.launcherContract.sha256 @@ -1436,7 +1437,7 @@ try { $runtimeManifest.exists -and $runtimeManifest.runtimeMatched -and $runtimeManifest.sourceRevision -eq $sourceRevision -and - $runtimeManifest.fileCount -eq 17 -and + $runtimeManifest.fileCount -eq 18 -and $runtimeManifest.mismatchCount -eq 0 -and $launcherContract.ok -and [string]$launcherContract.sha256 -eq [string]$deploy.payload.launcherContract.sha256 -and diff --git a/scripts/reboot-recovery/deploy-agent99-via-windows99-ssh.sh b/scripts/reboot-recovery/deploy-agent99-via-windows99-ssh.sh index 95292c831..658e5f375 100755 --- a/scripts/reboot-recovery/deploy-agent99-via-windows99-ssh.sh +++ b/scripts/reboot-recovery/deploy-agent99-via-windows99-ssh.sh @@ -17,6 +17,7 @@ RUNTIME_FILES=( "agent99-contract-check.ps1" "agent99-control-plane.ps1" "agent99-db-bounded-executor.ps1" + "agent99-signoz-metadata-executor.ps1" "agent99-deploy.ps1" "agent99-register-tasks.ps1" "agent99-alertmanager-alertchain-poll.ps1" @@ -226,7 +227,7 @@ trace_id, run_id, work_item_id = sys.argv[4:7] output_path = Path(sys.argv[7]) runtime_names = sys.argv[8:] -if len(runtime_names) != 17 or len(set(runtime_names)) != 17: +if len(runtime_names) != 18 or len(set(runtime_names)) != 18: raise SystemExit("fixed_runtime_file_contract_failed") files: list[dict[str, str]] = [] @@ -266,7 +267,7 @@ envelope = { "runId": run_id, "workItemId": work_item_id, "sourceRevision": source_revision, - "expectedRuntimeFileCount": 17, + "expectedRuntimeFileCount": 18, "manifestSha256": manifest_sha256, "files": files, "livePreflight": { @@ -418,14 +419,14 @@ stage_token = hashlib.sha256(stage_identity.encode("utf-8")).hexdigest()[:20] script = r'''$ErrorActionPreference='Stop';$ProgressPreference='SilentlyContinue' $a='C:\Wooo\Agent99';$b=Join-Path $a 'bin';$p=Join-Path $a 'state\runtime-manifest.json' $r=[ordered]@{exists=$false;sourceRevision='';runtimeMatched=$false;recordedRuntimeMatched=$false;fileCount=0;mismatchCount=-1;recordedMismatchCount=-1;parseError=''} -if(Test-Path -LiteralPath $p -PathType Leaf){try{$m=Get-Content -LiteralPath $p -Raw|ConvertFrom-Json;$rows=@($m.files);$seen=@{};$bad=0;foreach($x in $rows){$n=[string]$x.name;if(!$n-or[IO.Path]::GetFileName($n)-ne$n-or$seen.ContainsKey($n)){$bad++;continue};$seen[$n]=1;$q=Join-Path $b $n;$h=if(Test-Path -LiteralPath $q -PathType Leaf){(Get-FileHash -LiteralPath $q -Algorithm SHA256).Hash.ToLowerInvariant()}else{'missing'};if(([string]$x.runtimeSha256).ToLowerInvariant()-ne$h){$bad++}};$r.exists=$true;$r.sourceRevision=[string]$m.sourceRevision;$r.recordedRuntimeMatched=[bool]$m.runtimeMatched;$r.fileCount=[int]$m.fileCount;$r.mismatchCount=$bad;$r.recordedMismatchCount=[int]$m.mismatchCount;$r.runtimeMatched=[bool]($m.runtimeMatched-and$m.fileCount-eq 17-and$m.mismatchCount-eq 0-and$rows.Count-eq 17-and$seen.Count-eq 17-and$bad-eq 0)}catch{$r.exists=$true;$r.parseError=$_.Exception.GetType().Name}} +if(Test-Path -LiteralPath $p -PathType Leaf){try{$m=Get-Content -LiteralPath $p -Raw|ConvertFrom-Json;$rows=@($m.files);$seen=@{};$bad=0;foreach($x in $rows){$n=[string]$x.name;if(!$n-or[IO.Path]::GetFileName($n)-ne$n-or$seen.ContainsKey($n)){$bad++;continue};$seen[$n]=1;$q=Join-Path $b $n;$h=if(Test-Path -LiteralPath $q -PathType Leaf){(Get-FileHash -LiteralPath $q -Algorithm SHA256).Hash.ToLowerInvariant()}else{'missing'};if(([string]$x.runtimeSha256).ToLowerInvariant()-ne$h){$bad++}};$r.exists=$true;$r.sourceRevision=[string]$m.sourceRevision;$r.recordedRuntimeMatched=[bool]$m.runtimeMatched;$r.fileCount=[int]$m.fileCount;$r.mismatchCount=$bad;$r.recordedMismatchCount=[int]$m.mismatchCount;$r.runtimeMatched=[bool]($m.runtimeMatched-and$m.fileCount-eq 18-and$m.mismatchCount-eq 0-and$rows.Count-eq 18-and$seen.Count-eq 18-and$bad-eq 0)}catch{$r.exists=$true;$r.parseError=$_.Exception.GetType().Name}} $c=[ordered]@{executed=$false;ok=$false;exitCode=-1;errorType=''} try{& powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File (Join-Path $b 'agent99-contract-check.ps1') -SourceRoot $b 2>$null|Out-Null;$c.executed=$true;$c.exitCode=$LASTEXITCODE;$c.ok=[bool]($c.exitCode-eq 0)}catch{$c.executed=$true;$c.errorType=$_.Exception.GetType().Name} $z=[ordered]@{};foreach($n in 'remoteWritePerformed,liveRuntimeWritePerformed,livePromotionAttempted,livePromotionPerformed,secretValueRead,privateKeyValueRead,tokenValueRead,environmentSecretRead,uiInteraction,vmPowerChange,hostReboot,serviceRestart,scheduledTaskRestart,scheduledTaskModification'.Split(',')){$z[$n]=$false} $ready=[bool]($c.executed-and$c.ok-and$c.exitCode-eq 0) $status=if($ready){'check_ready'}else{'check_failed_no_apply'} $next=if($ready){'rerun_with_apply_and_trace_run_work_item_after_source_commit_and_transport_check'}else{'repair_runtime_contract_before_apply'} -[ordered]@{schemaVersion='agent99_remote_atomic_deploy_receipt_v1';status=$status;mode='check';sourceRevision='__SOURCE_REVISION__';expectedRuntimeFileCount=17;manifestSha256='__MANIFEST_SHA256__';plannedStagingPath='C:\Wooo\Agent99\deploy\remote-source-__STAGE_TOKEN__';runtimeManifest=[pscustomobject]$r;runtimeContract=[pscustomobject]$c;livePreflight=[ordered]@{executed=$false;reason='apply_only_after_validate_only'};transportPayloadMode='control_command_no_stdin';transientTransportPayloadStored=$false;transientTransportPayloadDeletedBeforeReceiver=$false;transportPayloadContainsSecrets=$false;operationBoundaries=$z;nextSafeAction=$next}|ConvertTo-Json -Compress -Depth 8 +[ordered]@{schemaVersion='agent99_remote_atomic_deploy_receipt_v1';status=$status;mode='check';sourceRevision='__SOURCE_REVISION__';expectedRuntimeFileCount=18;manifestSha256='__MANIFEST_SHA256__';plannedStagingPath='C:\Wooo\Agent99\deploy\remote-source-__STAGE_TOKEN__';runtimeManifest=[pscustomobject]$r;runtimeContract=[pscustomobject]$c;livePreflight=[ordered]@{executed=$false;reason='apply_only_after_validate_only'};transportPayloadMode='control_command_no_stdin';transientTransportPayloadStored=$false;transientTransportPayloadDeletedBeforeReceiver=$false;transportPayloadContainsSecrets=$false;operationBoundaries=$z;nextSafeAction=$next}|ConvertTo-Json -Compress -Depth 8 if(-not$ready){exit 65}''' script = ( script.replace("__SOURCE_REVISION__", source_revision) diff --git a/scripts/reboot-recovery/signoz-canonical-route-preflight.py b/scripts/reboot-recovery/signoz-canonical-route-preflight.py index ca5308d3e..4ef9cf80f 100644 --- a/scripts/reboot-recovery/signoz-canonical-route-preflight.py +++ b/scripts/reboot-recovery/signoz-canonical-route-preflight.py @@ -346,13 +346,13 @@ def evaluate(root: Path, contract_path: Path) -> dict[str, Any]: asset_reconciliation.get("drift_work_item_id") == "P0-OBS-002-ASSET-DRIFT-001" and asset_reconciliation.get("source_service_count") == len(source_services) - == 28 + == 29 and asset_reconciliation.get("runtime_mirror_service_count") == len(runtime_services) - == 28 + == 29 and asset_reconciliation.get("exact_shared_service_count") == len(exact_shared_services) - == 28 + == 29 and asset_reconciliation.get("shared_service_drift_count") == 0 and asset_reconciliation.get("source_only_services") == source_only_services diff --git a/scripts/reboot-recovery/tests/test_agent99_live_preflight_decision.py b/scripts/reboot-recovery/tests/test_agent99_live_preflight_decision.py index 4502d6183..cdf725aef 100644 --- a/scripts/reboot-recovery/tests/test_agent99_live_preflight_decision.py +++ b/scripts/reboot-recovery/tests/test_agent99_live_preflight_decision.py @@ -36,7 +36,7 @@ def _base_case() -> dict[str, Any]: "exists": True, "sourceRevision": "a" * 40, "runtimeMatched": True, - "fileCount": 17, + "fileCount": 18, "mismatchCount": 0, "parseError": "", }, @@ -84,7 +84,7 @@ $parameters = @{{ AgentRootPresent = [bool]$case.agentRootPresent Manifest = $case.manifest ManifestCheckRequired = [bool]$case.manifestCheckRequired - ExpectedRuntimeFileCount = 17 + ExpectedRuntimeFileCount = 18 TaskFailureCount = [int]$case.taskFailureCount RelayListenerCount = [int]$case.relayListenerCount RequiredEvidenceStaleCount = [int]$case.requiredEvidenceStaleCount @@ -133,7 +133,7 @@ def test_warning_receipt_is_visible_without_weakening_integrity_blockers() -> No ) assert "deploymentEligible = [bool]($blockingReasons.Count -eq 0)" in decision assert "preflightGreen = [bool]$decision.deploymentEligible" in source - assert "-ExpectedRuntimeFileCount 17" in source + assert "-ExpectedRuntimeFileCount 18" in source assert "warningReasons = $warningReasons" in source assert "selfHealthPerformanceIssues = @(" in source assert 'if ($safeHost -notmatch "^[A-Za-z0-9.:-]{1,128}$")' in source diff --git a/scripts/reboot-recovery/tests/test_agent99_remote_atomic_deploy_transport_contract.py b/scripts/reboot-recovery/tests/test_agent99_remote_atomic_deploy_transport_contract.py index 0dd7c27bd..f4544e578 100644 --- a/scripts/reboot-recovery/tests/test_agent99_remote_atomic_deploy_transport_contract.py +++ b/scripts/reboot-recovery/tests/test_agent99_remote_atomic_deploy_transport_contract.py @@ -25,6 +25,7 @@ EXPECTED_RUNTIME_FILES = ( "agent99-contract-check.ps1", "agent99-control-plane.ps1", "agent99-db-bounded-executor.ps1", + "agent99-signoz-metadata-executor.ps1", "agent99-deploy.ps1", "agent99-register-tasks.ps1", "agent99-alertmanager-alertchain-poll.ps1", @@ -87,7 +88,7 @@ def test_sender_and_receiver_allow_exactly_the_deployer_runtime_bundle() -> None ) assert preflight_count is not None assert int(preflight_count.group(1)) == len(EXPECTED_RUNTIME_FILES) - assert "expectedRuntimeFileCount\": 17" in sender + assert "expectedRuntimeFileCount\": 18" in sender assert "$filesByName.Count -ne $FixedRuntimeFiles.Count" in receiver assert "required_runtime_file_missing" in receiver assert "duplicate_runtime_file" in receiver @@ -262,7 +263,7 @@ def test_receiver_rolls_back_failed_deploy_or_failed_independent_post_verifier() ) assert "$livePreflight.sourceRevision -eq $sourceRevision" in source assert "$runtimeManifest.sourceRevision -eq $sourceRevision" in source - assert '$runtimeManifest.fileCount -eq 17' in source + assert '$runtimeManifest.fileCount -eq 18' in source assert "function Invoke-AgentWindowsControlBaseline" in source assert '"agent99-windows-control-baseline.ps1"' in source assert '"-Mode", "Apply"' in source diff --git a/scripts/reboot-recovery/tests/test_signoz_canonical_route_migration.py b/scripts/reboot-recovery/tests/test_signoz_canonical_route_migration.py index added60fe..3ee5d5ae0 100644 --- a/scripts/reboot-recovery/tests/test_signoz_canonical_route_migration.py +++ b/scripts/reboot-recovery/tests/test_signoz_canonical_route_migration.py @@ -964,9 +964,9 @@ def test_post_release_verifier_and_native_backup_close_with_remaining_gaps() -> ) assets = receipt["asset_reconciliation"] assert assets["drift_work_item_id"] == "P0-OBS-002-ASSET-DRIFT-001" - assert assets["source_service_count"] == 28 - assert assets["runtime_mirror_service_count"] == 28 - assert assets["exact_shared_service_count"] == 28 + assert assets["source_service_count"] == 29 + assert assets["runtime_mirror_service_count"] == 29 + assert assets["exact_shared_service_count"] == 29 assert assets["shared_service_drift_count"] == 0 assert assets["source_only_services"] == [] assert assets["source_runtime_manifest_exact"] is True