param( [string]$PackagePath = "", [string]$ZipPath = "", [string]$WorkDir = "", [switch]$Strict ) $ErrorActionPreference = "Stop" Set-StrictMode -Version Latest function New-StringFromCodes([int[]]$Codes) { return -join ($Codes | ForEach-Object { [char]$_ }) } $inputFileName = New-StringFromCodes @(0x586B,0x5199,0x2D,0x53D1,0x9001,0x4FE1,0x606F,0x2E,0x6A,0x73,0x6F,0x6E) $summaryFileName = New-StringFromCodes @(0x53D1,0x9001,0x8BB0,0x5F55,0x8BCA,0x65AD,0x6458,0x8981,0x2E,0x6A,0x73,0x6F,0x6E) function ConvertTo-Sha256Hex([string]$Text) { $bytes = [System.Text.Encoding]::UTF8.GetBytes($Text) $sha = [System.Security.Cryptography.SHA256]::Create() try { return -join ($sha.ComputeHash($bytes) | ForEach-Object { $_.ToString("x2") }) } finally { $sha.Dispose() } } function Get-JsonPropertyValue($Object, [string]$Name, $Default = $null) { if ($null -eq $Object) { return $Default } $property = $Object.PSObject.Properties[$Name] if ($null -eq $property) { return $Default } return $property.Value } function Test-NonEmptyString($Value) { if ($null -eq $Value) { return $false } return -not [string]::IsNullOrWhiteSpace([string]$Value) } function ConvertTo-Bool($Value, [bool]$Default = $false) { if ($null -eq $Value) { return $Default } if ($Value -is [bool]) { return [bool]$Value } $text = ([string]$Value).Trim().ToLowerInvariant() if ($text -eq "true" -or $text -eq "1" -or $text -eq "yes") { return $true } if ($text -eq "false" -or $text -eq "0" -or $text -eq "no") { return $false } return $Default } function Get-RunCountFromEvidence($AllFiles, [string]$Phase) { $pattern = "[\\/]return-evidence[\\/]$Phase[\\/]" $manifestDirs = @($AllFiles | Where-Object { $_.FullName -match $pattern -and $_.Name -eq "manifest.json" } | ForEach-Object { $_.DirectoryName } | Sort-Object -Unique) if ($manifestDirs.Count -gt 0) { return $manifestDirs.Count } $phaseFiles = @($AllFiles | Where-Object { $_.FullName -match $pattern }) if ($phaseFiles.Count -gt 0) { return 1 } return 0 } function Test-AfterOfflineBlocked($AllFiles, $Summary) { $summaryValue = Get-JsonPropertyValue $Summary "after_offline_blocked" $null if ($null -ne $summaryValue) { return ConvertTo-Bool $summaryValue $false } $afterFiles = @($AllFiles | Where-Object { $_.FullName -match "[\\/]return-evidence[\\/]after[\\/]" -and $_.Name -eq "send_uia_controls_preflight.json" }) foreach ($file in $afterFiles) { $text = Get-Content -LiteralPath $file.FullName -Raw if ($text.Contains("A_ROUTE_OFFLINE_BLOCKED") -or $text.Contains('"offline_blocker_visible":true')) { return $true } } return $false } function Get-ArrayValue($Object, [string]$Name) { $value = Get-JsonPropertyValue $Object $Name @() if ($null -eq $value) { return @() } return @($value) } if (-not [string]::IsNullOrWhiteSpace($ZipPath)) { $PackagePath = $ZipPath } if ([string]::IsNullOrWhiteSpace($PackagePath)) { throw "PackagePath or ZipPath is required" } if (-not (Test-Path -LiteralPath $PackagePath)) { throw "package not found" } $resolvedPackage = (Resolve-Path -LiteralPath $PackagePath).Path $cleanup = $false if ([string]::IsNullOrWhiteSpace($WorkDir)) { $WorkDir = Join-Path ([System.IO.Path]::GetTempPath()) ("isphere-send-sent-diagnostic-" + [guid]::NewGuid().ToString("N")) $cleanup = $true } if (Test-Path -LiteralPath $WorkDir) { Remove-Item -LiteralPath $WorkDir -Recurse -Force } New-Item -ItemType Directory -Force -Path $WorkDir | Out-Null try { if ($resolvedPackage.EndsWith(".zip", [System.StringComparison]::OrdinalIgnoreCase)) { Expand-Archive -LiteralPath $resolvedPackage -DestinationPath $WorkDir -Force $root = $WorkDir } else { $root = $resolvedPackage } $allFiles = @(Get-ChildItem -LiteralPath $root -Recurse -File) $summaryFile = $allFiles | Where-Object { $_.Name -eq $summaryFileName } | Sort-Object FullName | Select-Object -First 1 $inputFile = $allFiles | Where-Object { $_.Name -eq $inputFileName } | Sort-Object FullName | Select-Object -First 1 $summaryJson = $null if ($summaryFile) { $summaryJson = Get-Content -LiteralPath $summaryFile.FullName -Raw | ConvertFrom-Json } $inputJson = $null if ($inputFile) { $inputJson = Get-Content -LiteralPath $inputFile.FullName -Raw | ConvertFrom-Json } $contentHashMatches = ConvertTo-Bool (Get-JsonPropertyValue $summaryJson "content_sha256_matches_input" $null) $false if ($null -ne $inputJson -and (Test-NonEmptyString (Get-JsonPropertyValue $inputJson "content_text_to_send")) -and (Test-NonEmptyString (Get-JsonPropertyValue $inputJson "content_sha256"))) { $contentHashMatches = ((ConvertTo-Sha256Hex ([string](Get-JsonPropertyValue $inputJson "content_text_to_send"))) -eq ([string](Get-JsonPropertyValue $inputJson "content_sha256"))) } $beforeRunCount = [int](Get-JsonPropertyValue $summaryJson "before_run_count" (Get-RunCountFromEvidence $allFiles "before")) $afterRunCount = [int](Get-JsonPropertyValue $summaryJson "after_run_count" (Get-RunCountFromEvidence $allFiles "after")) $exactContentSeenAfter = ConvertTo-Bool (Get-JsonPropertyValue $summaryJson "exact_content_seen_after" $false) $false $contentShaSeenAfter = ConvertTo-Bool (Get-JsonPropertyValue $summaryJson "content_sha256_seen_after" $false) $false $afterOfflineBlocked = Test-AfterOfflineBlocked $allFiles $summaryJson $operatorObservedSuccess = ConvertTo-Bool (Get-JsonPropertyValue $summaryJson "operator_observed_success" (Get-JsonPropertyValue $inputJson "operator_observed_success" $false)) $false $noSecondSend = ConvertTo-Bool (Get-JsonPropertyValue $summaryJson "do_not_send_second_time_confirmed" (Get-JsonPropertyValue $inputJson "do_not_send_second_time_confirmed" $false)) $false $productionSendEnabled = ConvertTo-Bool (Get-JsonPropertyValue $summaryJson "production_send_enabled" (Get-JsonPropertyValue $inputJson "production_send_enabled" $false)) $false $automaticSendEnabled = ConvertTo-Bool (Get-JsonPropertyValue $summaryJson "automatic_send_enabled" (Get-JsonPropertyValue $inputJson "automatic_send_enabled" $false)) $false $rawBodyWritten = ConvertTo-Bool (Get-JsonPropertyValue $summaryJson "raw_message_body_written" $false) $false $rawTargetWritten = ConvertTo-Bool (Get-JsonPropertyValue $summaryJson "raw_target_id_written" $false) $false $rawIdemWritten = ConvertTo-Bool (Get-JsonPropertyValue $summaryJson "raw_idempotency_key_written" $false) $false $localPathsWritten = ConvertTo-Bool (Get-JsonPropertyValue $summaryJson "local_paths_written" $false) $false $strictPass = ( $contentHashMatches -and $beforeRunCount -ge 1 -and $afterRunCount -ge 1 -and ($exactContentSeenAfter -or $contentShaSeenAfter) -and -not $afterOfflineBlocked -and $operatorObservedSuccess -and $noSecondSend -and -not $productionSendEnabled -and -not $automaticSendEnabled -and -not $rawBodyWritten -and -not $rawTargetWritten -and -not $rawIdemWritten -and -not $localPathsWritten ) $decision = "not_usable" if ($strictPass) { $decision = "strict_send_record_pass" } elseif ($operatorObservedSuccess -and $beforeRunCount -ge 1 -and $afterRunCount -ge 1 -and $contentHashMatches) { $decision = "manual_success_without_machine_sent_record" } elseif ($beforeRunCount -ge 1 -or $afterRunCount -ge 1) { $decision = "partial_probe_only" } $summaryOut = [ordered]@{ ok = $true package_type = "send-sent-record-diagnostic" package_file_name = [System.IO.Path]::GetFileName($resolvedPackage) summary_json_present = ($null -ne $summaryFile) input_json_present = ($null -ne $inputFile) content_sha256_matches_input = $contentHashMatches before_run_count = $beforeRunCount after_run_count = $afterRunCount exact_content_seen_after = $exactContentSeenAfter content_sha256_seen_after = $contentShaSeenAfter after_offline_blocked = $afterOfflineBlocked latest_after_route_hints = Get-ArrayValue $summaryJson "latest_after_route_hints" operator_observed_success = $operatorObservedSuccess do_not_send_second_time_confirmed = $noSecondSend production_send_enabled = $productionSendEnabled automatic_send_enabled = $automaticSendEnabled raw_message_body_written = $rawBodyWritten raw_target_id_written = $rawTargetWritten raw_idempotency_key_written = $rawIdemWritten local_paths_written = $localPathsWritten strict_send_record_pass = $strictPass production_send_unlock_recommended = $strictPass decision = $decision } $summaryOut | ConvertTo-Json -Depth 6 -Compress if ($Strict -and -not $strictPass) { exit 1 } } finally { if ($cleanup -and (Test-Path -LiteralPath $WorkDir)) { Remove-Item -LiteralPath $WorkDir -Recurse -Force } }