param([Parameter(Mandatory=$true)][string]$Executable,[Parameter(Mandatory=$true)][string]$OutputDirectory) $ErrorActionPreference='Stop' $exe=(Resolve-Path $Executable).Path if(Test-Path $OutputDirectory){throw 'Use a fresh evidence directory.'} $out=(New-Item -ItemType Directory $OutputDirectory).FullName $checks=New-Object System.Collections.Generic.List[object] function Check([string]$Name,[string[]]$Arguments){ $raw=(& $exe @Arguments 2> "$out\$Name.stderr.txt" | Out-String) $code=$LASTEXITCODE $raw | Set-Content "$out\$Name.json" -Encoding UTF8 $checks.Add(@{Stage=$Name;ExitCode=$code}) if($code -ne 0){throw "$Name failed; stopped."} return ($raw | ConvertFrom-Json) } try { $null=Check 'doctor' @('doctor','--timeout','30') $null=Check 'inspect-ui' @('inspect-ui','--output',"$out\ui-tree.json",'--timeout','30') $smoke=Check 'smoke' @('smoke','--read-only','--timeout','30') if(!$smoke.sendSkipped -or $smoke.confirmed){throw 'Expected a strictly read-only smoke.'} $null=Check 'sessions-down' @('session','scroll','--direction','down','--pages','2','--timeout','30') $null=Check 'sessions-up' @('session','scroll','--direction','up','--pages','2','--timeout','30') $latest=Check 'latest-before' @('chat','latest','--timeout','60') $history=Check 'history' @('chat','history','--limit','50','--scrolls','4','--timeout','60') $target=@($history.messages | Where-Object {$_.Type -eq 'Text'} | Select-Object -First 1) if($target.Count -ne 1){throw 'No text message available for read-only locate validation.'} $located=Check 'locate' @('chat','locate','--fingerprint',$target[0].Fingerprint,'--scrolls','10','--timeout','60') if($located.message.Fingerprint -cne $target[0].Fingerprint){throw 'Locate returned a different fingerprint.'} $latest=Check 'latest-after' @('chat','latest','--timeout','60') $merged=@($latest.messages | Where-Object {$_.Type -eq 'Merge'} | Select-Object -Last 1) if($merged.Count -ne 1){throw 'No merged-chat card was found in the latest File Transfer Assistant messages.'} @{Fingerprint=$merged[0].Fingerprint} | ConvertTo-Json | Set-Content "$out\merged-target.json" -Encoding UTF8 $opened=Check 'merged-open' @('chat','merged-open','--fingerprint',$merged[0].Fingerprint,'--timeout','30') if(!$opened.windowHandle){throw 'No merged-chat window handle returned.'} } catch { @{Message=$_.Exception.Message;Line=$_.InvocationInfo.ScriptLineNumber} | ConvertTo-Json | Set-Content "$out\error.json" -Encoding UTF8 } finally { $success=($checks.Count -eq 10 -and @($checks | Where-Object {$_.ExitCode -ne 0}).Count -eq 0) @{Success=$success;Checks=@($checks.ToArray());SendsAttempted=0;BinarySha256=(Get-FileHash $exe -Algorithm SHA256).Hash} | ConvertTo-Json -Depth 6 | Set-Content "$out\summary.json" -Encoding UTF8 } if(!$success){exit 1}