1670 lines
83 KiB
PowerShell
1670 lines
83 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[ValidateSet('Debug', 'Release', 'ReleaseFast', 'ReleaseSize')]
|
|
[string]$Configuration = 'ReleaseFast',
|
|
[switch]$SkipBuild,
|
|
[switch]$SkipPromptGeneration
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
Set-StrictMode -Version Latest
|
|
|
|
. (Join-Path $PSScriptRoot 'toolbox-commands.ps1')
|
|
|
|
function Invoke-StrictNative {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$FilePath,
|
|
[Parameter(Mandatory = $true)]
|
|
[string[]]$ArgumentList
|
|
)
|
|
|
|
& $FilePath @ArgumentList
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Command failed with exit code ${LASTEXITCODE}: $FilePath $($ArgumentList -join ' ')"
|
|
}
|
|
}
|
|
|
|
function Get-RequiredCommandPath {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Name
|
|
)
|
|
|
|
$command = Get-Command -Name $Name -ErrorAction SilentlyContinue
|
|
if ($null -eq $command) {
|
|
throw "Required command not found on PATH: $Name"
|
|
}
|
|
|
|
return $command.Source
|
|
}
|
|
|
|
function Assert-Condition {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[bool]$Condition,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Message
|
|
)
|
|
|
|
if (-not $Condition) {
|
|
throw $Message
|
|
}
|
|
}
|
|
|
|
function Get-ToolboxBinaryPath {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Name
|
|
)
|
|
|
|
$path = Join-Path $script:BinaryRoot "$Name.exe"
|
|
if (-not (Test-Path -LiteralPath $path)) {
|
|
throw "Expected toolbox binary not found: $path"
|
|
}
|
|
|
|
return $path
|
|
}
|
|
|
|
function Get-FreeTcpPort {
|
|
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0)
|
|
$listener.Start()
|
|
try {
|
|
return ([System.Net.IPEndPoint]$listener.LocalEndpoint).Port
|
|
}
|
|
finally {
|
|
$listener.Stop()
|
|
}
|
|
}
|
|
|
|
function Start-LoopbackHttpServer {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[int]$Port
|
|
)
|
|
|
|
$readyFile = Join-Path ([System.IO.Path]::GetTempPath()) ("mercury-loopback-ready-" + [System.Guid]::NewGuid())
|
|
$job = Start-Job -ScriptBlock {
|
|
param($JobPort, $JobReadyFile)
|
|
|
|
$listener = [System.Net.HttpListener]::new()
|
|
$listener.Prefixes.Add("http://127.0.0.1:$JobPort/")
|
|
$listener.Start()
|
|
New-Item -ItemType File -Force -Path $JobReadyFile | Out-Null
|
|
|
|
try {
|
|
$context = $listener.GetContext()
|
|
$payload = [System.Text.Encoding]::UTF8.GetBytes('{"ok":true}')
|
|
$response = $context.Response
|
|
$response.StatusCode = 200
|
|
$response.ContentType = 'application/json'
|
|
$response.ContentLength64 = $payload.Length
|
|
$response.OutputStream.Write($payload, 0, $payload.Length)
|
|
$response.OutputStream.Close()
|
|
}
|
|
finally {
|
|
Remove-Item -LiteralPath $JobReadyFile -Force -ErrorAction SilentlyContinue
|
|
$listener.Stop()
|
|
$listener.Close()
|
|
}
|
|
} -ArgumentList $Port, $readyFile
|
|
|
|
Wait-Until -Description "loopback HTTP server on port $Port" -TimeoutMilliseconds 3000 -Probe {
|
|
if (Test-Path -LiteralPath $readyFile) {
|
|
return $true
|
|
}
|
|
if ($job.State -in @('Completed', 'Failed', 'Stopped')) {
|
|
return $true
|
|
}
|
|
return $false
|
|
}
|
|
if (-not (Test-Path -LiteralPath $readyFile)) {
|
|
Stop-JobSafe -Job $job
|
|
throw "loopback HTTP server on port $Port exited before becoming ready"
|
|
}
|
|
|
|
return [pscustomobject]@{
|
|
Job = $job
|
|
ReadyFile = $readyFile
|
|
}
|
|
}
|
|
|
|
function Stop-JobSafe {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[System.Management.Automation.Job]$Job
|
|
)
|
|
|
|
if ($Job.State -eq 'Running') {
|
|
Stop-Job -Job $Job | Out-Null
|
|
}
|
|
Receive-Job -Job $Job -ErrorAction SilentlyContinue | Out-Null
|
|
Remove-Job -Job $Job -Force -ErrorAction SilentlyContinue | Out-Null
|
|
}
|
|
|
|
function Stop-LoopbackHttpServer {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[psobject]$Server
|
|
)
|
|
|
|
if ($null -ne $Server.Job) {
|
|
Stop-JobSafe -Job $Server.Job
|
|
}
|
|
if ($Server.PSObject.Properties.Name -contains 'ReadyFile') {
|
|
Remove-Item -LiteralPath $Server.ReadyFile -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
function Wait-Until {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Description,
|
|
[Parameter(Mandatory = $true)]
|
|
[scriptblock]$Probe,
|
|
[int]$TimeoutMilliseconds = 3000,
|
|
[int]$IntervalMilliseconds = 50
|
|
)
|
|
|
|
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
|
|
while ($stopwatch.ElapsedMilliseconds -lt $TimeoutMilliseconds) {
|
|
if (& $Probe) {
|
|
return
|
|
}
|
|
Start-Sleep -Milliseconds $IntervalMilliseconds
|
|
}
|
|
|
|
throw "timed out waiting for $Description"
|
|
}
|
|
|
|
function Test-TcpPortListening {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[int]$Port
|
|
)
|
|
|
|
$client = [System.Net.Sockets.TcpClient]::new()
|
|
try {
|
|
$connect = $client.ConnectAsync([System.Net.IPAddress]::Loopback, $Port)
|
|
if (-not $connect.Wait(150)) {
|
|
return $false
|
|
}
|
|
return $client.Connected
|
|
}
|
|
catch {
|
|
return $false
|
|
}
|
|
finally {
|
|
$client.Dispose()
|
|
}
|
|
}
|
|
|
|
function Test-ExclusiveFileLock {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Path
|
|
)
|
|
|
|
$stream = $null
|
|
try {
|
|
$stream = [System.IO.File]::Open($Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
|
|
return $false
|
|
}
|
|
catch [System.IO.IOException] {
|
|
return $true
|
|
}
|
|
finally {
|
|
if ($null -ne $stream) {
|
|
$stream.Dispose()
|
|
}
|
|
}
|
|
}
|
|
|
|
function Invoke-Check {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Name,
|
|
[Parameter(Mandatory = $true)]
|
|
[scriptblock]$Script
|
|
)
|
|
|
|
try {
|
|
$detail = & $Script
|
|
$script:Results.Add([pscustomobject]@{
|
|
Check = $Name
|
|
Status = 'PASS'
|
|
Detail = [string]$detail
|
|
})
|
|
}
|
|
catch {
|
|
$script:Results.Add([pscustomobject]@{
|
|
Check = $Name
|
|
Status = 'FAIL'
|
|
Detail = $_.Exception.Message
|
|
})
|
|
$script:Failed = $true
|
|
}
|
|
}
|
|
|
|
function Invoke-PwshFile {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$FilePath,
|
|
[string[]]$ArgumentList = @()
|
|
)
|
|
|
|
$output = & pwsh -NoProfile -ExecutionPolicy Bypass -File $FilePath @ArgumentList 2>&1
|
|
return [pscustomobject]@{
|
|
ExitCode = $LASTEXITCODE
|
|
Output = (($output | ForEach-Object { [string]$_ }) -join "`n")
|
|
}
|
|
}
|
|
|
|
function Invoke-NativeCapture {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$FilePath,
|
|
[string[]]$ArgumentList = @(),
|
|
[int]$TimeoutMilliseconds = 5000
|
|
)
|
|
|
|
$psi = [System.Diagnostics.ProcessStartInfo]::new()
|
|
$psi.FileName = $FilePath
|
|
foreach ($argument in $ArgumentList) {
|
|
[void]$psi.ArgumentList.Add($argument)
|
|
}
|
|
$psi.UseShellExecute = $false
|
|
$psi.RedirectStandardOutput = $true
|
|
$psi.RedirectStandardError = $true
|
|
$psi.CreateNoWindow = $true
|
|
|
|
$process = [System.Diagnostics.Process]::new()
|
|
$process.StartInfo = $psi
|
|
[void]$process.Start()
|
|
if (-not $process.WaitForExit($TimeoutMilliseconds)) {
|
|
try {
|
|
$process.Kill($true)
|
|
}
|
|
catch {
|
|
$process.Kill()
|
|
}
|
|
throw "timed out after ${TimeoutMilliseconds}ms: $FilePath $($ArgumentList -join ' ')"
|
|
}
|
|
|
|
$stdout = $process.StandardOutput.ReadToEnd()
|
|
$stderr = $process.StandardError.ReadToEnd()
|
|
$exitCode = $process.ExitCode
|
|
$process.Dispose()
|
|
|
|
return [pscustomobject]@{
|
|
ExitCode = $exitCode
|
|
Stdout = $stdout
|
|
Stderr = $stderr
|
|
Output = ($stdout + $stderr)
|
|
}
|
|
}
|
|
|
|
function Test-ToolboxBinaryVersions {
|
|
foreach ($commandName in Get-ToolboxCommandNames) {
|
|
$binary = Get-ToolboxBinaryPath -Name $commandName
|
|
$result = Invoke-NativeCapture -FilePath $binary -ArgumentList @('--version')
|
|
Assert-Condition ($result.ExitCode -eq 0) "$commandName --version failed with exit code $($result.ExitCode): $($result.Output)"
|
|
Assert-Condition ($result.Output -match [regex]::Escape($commandName)) "$commandName --version did not include command name: $($result.Output)"
|
|
}
|
|
}
|
|
|
|
function Test-ToolboxNoArgsContracts {
|
|
$stdinWaitingCommands = @(
|
|
'ison',
|
|
'isonl',
|
|
'zon',
|
|
'tonl',
|
|
'jsonlgrep',
|
|
'ctxpack',
|
|
'diagpick',
|
|
'hitsnip',
|
|
'logshape',
|
|
'recent',
|
|
'codeshape',
|
|
'snip',
|
|
'sysshape',
|
|
'toon'
|
|
)
|
|
foreach ($commandName in Get-ToolboxCommandNames) {
|
|
if ($commandName -in $stdinWaitingCommands) {
|
|
continue
|
|
}
|
|
$binary = Get-ToolboxBinaryPath -Name $commandName
|
|
$result = Invoke-NativeCapture -FilePath $binary
|
|
Assert-Condition ($result.Output.Length -lt 20000) "$commandName no-args output was unexpectedly large"
|
|
Assert-Condition ($result.Output -notmatch '(?i)panic|backtrace') "$commandName no-args output contained panic/backtrace text"
|
|
}
|
|
}
|
|
|
|
function Test-ToolboxInvalidFlagContracts {
|
|
foreach ($commandName in Get-ToolboxCommandNames) {
|
|
$binary = Get-ToolboxBinaryPath -Name $commandName
|
|
$result = Invoke-NativeCapture -FilePath $binary -ArgumentList @('--mercury-invalid-flag')
|
|
Assert-Condition ($result.ExitCode -ne 0) "$commandName accepted an invalid flag"
|
|
Assert-Condition ($result.Output.Length -gt 0) "$commandName invalid-flag diagnostic was empty"
|
|
Assert-Condition ($result.Output -notmatch '(?i)panic|backtrace') "$commandName invalid-flag output contained panic/backtrace text"
|
|
}
|
|
}
|
|
|
|
function Test-ToolboxStructuredOutputHelpContracts {
|
|
foreach ($commandName in Get-ToolboxCommandNames) {
|
|
$binary = Get-ToolboxBinaryPath -Name $commandName
|
|
$result = Invoke-NativeCapture -FilePath $binary -ArgumentList @('--help')
|
|
Assert-Condition ($result.ExitCode -eq 0) "$commandName --help failed with exit code $($result.ExitCode)"
|
|
Assert-Condition ($result.Output -match '--json|--format|--toon|JSON|TOON') "$commandName --help does not mention structured output controls"
|
|
}
|
|
}
|
|
|
|
function Test-ToolboxMalformedJsonlStdinContracts {
|
|
foreach ($probe in @(
|
|
@{ Name = 'cjson'; Args = @('--input-format', 'jsonl', '--json') },
|
|
@{ Name = 'jsonshape'; Args = @('--input-format', 'jsonl', '--json') },
|
|
@{ Name = 'toon'; Args = @('--input-format', 'jsonl') }
|
|
)) {
|
|
$binary = Get-ToolboxBinaryPath -Name $probe.Name
|
|
$result = "'{not-json}'" | & $binary @($probe.Args) 2>&1
|
|
$exitCode = $LASTEXITCODE
|
|
$text = (($result | ForEach-Object { [string]$_ }) -join "`n")
|
|
Assert-Condition ($exitCode -ne 0) "$($probe.Name) accepted malformed JSONL stdin"
|
|
Assert-Condition ($text.Length -gt 0) "$($probe.Name) malformed JSONL diagnostic was empty"
|
|
Assert-Condition ($text -notmatch '(?i)panic|backtrace') "$($probe.Name) malformed JSONL diagnostic contained panic/backtrace text"
|
|
}
|
|
}
|
|
|
|
function Write-TestPackageSha256Sums {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$PackageRoot,
|
|
[switch]$UseWindowsSeparators
|
|
)
|
|
|
|
$hashPath = Join-Path $PackageRoot 'SHA256SUMS.txt'
|
|
$lines = [System.Collections.Generic.List[string]]::new()
|
|
foreach ($file in (Get-ChildItem -LiteralPath $PackageRoot -Recurse -File | Sort-Object FullName)) {
|
|
if ($file.FullName -eq $hashPath) {
|
|
continue
|
|
}
|
|
|
|
$relativePath = [System.IO.Path]::GetRelativePath($PackageRoot, $file.FullName)
|
|
if (-not $UseWindowsSeparators) {
|
|
$relativePath = $relativePath.Replace('\', '/')
|
|
}
|
|
$hash = (Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
$lines.Add("$hash $relativePath")
|
|
}
|
|
|
|
Set-Content -LiteralPath $hashPath -Value $lines -Encoding utf8NoBOM
|
|
}
|
|
|
|
function New-TestPortablePackage {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$PackageRoot,
|
|
[switch]$UseWindowsSeparators
|
|
)
|
|
|
|
$scriptsRoot = Join-Path $PackageRoot 'scripts'
|
|
$binRoot = Join-Path $PackageRoot 'bin'
|
|
$docsRoot = Join-Path $PackageRoot 'docs\ai'
|
|
$skillRoot = Join-Path $PackageRoot 'skills\mercury-toolbox'
|
|
$catalogRoot = Join-Path $skillRoot 'references'
|
|
New-Item -ItemType Directory -Force -Path $scriptsRoot, $binRoot, $docsRoot, $catalogRoot | Out-Null
|
|
Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'install-package-toolbox.ps1') -Destination (Join-Path $scriptsRoot 'install-package-toolbox.ps1') -Force
|
|
Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'toolbox-commands.ps1') -Destination (Join-Path $scriptsRoot 'toolbox-commands.ps1') -Force
|
|
|
|
foreach ($commandName in Get-ToolboxCommandNames) {
|
|
Set-Content -LiteralPath (Join-Path $binRoot "$commandName.exe") -Value "fake $commandName" -Encoding utf8NoBOM
|
|
}
|
|
Set-Content -LiteralPath (Join-Path $docsRoot 'mercury-toolbox-ai-prompt.md') -Value 'test prompt' -Encoding utf8NoBOM
|
|
Set-Content -LiteralPath (Join-Path $skillRoot 'SKILL.md') -Value 'test skill' -Encoding utf8NoBOM
|
|
Set-Content -LiteralPath (Join-Path $catalogRoot 'command-catalog.md') -Value 'test catalog' -Encoding utf8NoBOM
|
|
[ordered]@{
|
|
name = 'Mercury Toolbox'
|
|
commands = @(Get-ToolboxCommandNames)
|
|
} | ConvertTo-Json -Depth 3 | Set-Content -LiteralPath (Join-Path $PackageRoot 'mercury-toolbox-package.json') -Encoding utf8NoBOM
|
|
|
|
Write-TestPackageSha256Sums -PackageRoot $PackageRoot -UseWindowsSeparators:$UseWindowsSeparators
|
|
}
|
|
|
|
$workspaceRoot = Split-Path -Parent $PSScriptRoot
|
|
$targetProfile = Resolve-ToolboxProfileName -Configuration $Configuration
|
|
$script:BinaryRoot = Join-Path $workspaceRoot (Join-Path 'target' $targetProfile)
|
|
$dependencyRoot = Join-Path $script:BinaryRoot 'deps'
|
|
if (Test-Path -LiteralPath $dependencyRoot -PathType Container) {
|
|
$env:PATH = "$dependencyRoot$([System.IO.Path]::PathSeparator)$env:PATH"
|
|
}
|
|
$fixtureRoot = Join-Path $workspaceRoot 'fixtures'
|
|
$cargoPath = Get-RequiredCommandPath -Name 'cargo'
|
|
$batPath = Get-RequiredCommandPath -Name 'bat'
|
|
$fdPath = Get-RequiredCommandPath -Name 'fd'
|
|
$rgPath = Get-RequiredCommandPath -Name 'rg'
|
|
$jqPath = Get-RequiredCommandPath -Name 'jq'
|
|
$script:Results = [System.Collections.Generic.List[object]]::new()
|
|
$script:Failed = $false
|
|
|
|
Write-Host "Mercury Toolbox ecosystem check"
|
|
Write-Host "workspace: $workspaceRoot"
|
|
Write-Host "configuration: $Configuration"
|
|
Write-Host "binary root: $script:BinaryRoot"
|
|
|
|
if (-not $SkipBuild) {
|
|
Invoke-ToolboxBuild -CargoPath $cargoPath -Configuration $Configuration
|
|
}
|
|
|
|
$jsonlgrep = Get-ToolboxBinaryPath -Name 'jsonlgrep'
|
|
$jsonshape = Get-ToolboxBinaryPath -Name 'jsonshape'
|
|
$cjson = Get-ToolboxBinaryPath -Name 'cjson'
|
|
$recent = Get-ToolboxBinaryPath -Name 'recent'
|
|
$pathshadow = Get-ToolboxBinaryPath -Name 'pathshadow'
|
|
$portping = Get-ToolboxBinaryPath -Name 'portping'
|
|
$portunlock = Get-ToolboxBinaryPath -Name 'portunlock'
|
|
$msudo = Get-ToolboxBinaryPath -Name 'msudo'
|
|
$asmtype = Get-ToolboxBinaryPath -Name 'asmtype'
|
|
$asmmember = Get-ToolboxBinaryPath -Name 'asmmember'
|
|
$asmref = Get-ToolboxBinaryPath -Name 'asmref'
|
|
$binmeta = Get-ToolboxBinaryPath -Name 'binmeta'
|
|
$fileprobe = Get-ToolboxBinaryPath -Name 'fileprobe'
|
|
$outline = Get-ToolboxBinaryPath -Name 'outline'
|
|
$codeshape = Get-ToolboxBinaryPath -Name 'codeshape'
|
|
$snip = Get-ToolboxBinaryPath -Name 'snip'
|
|
$defsnip = Get-ToolboxBinaryPath -Name 'defsnip'
|
|
$chunkcat = Get-ToolboxBinaryPath -Name 'chunkcat'
|
|
$hitsnip = Get-ToolboxBinaryPath -Name 'hitsnip'
|
|
$diagpick = Get-ToolboxBinaryPath -Name 'diagpick'
|
|
$logshape = Get-ToolboxBinaryPath -Name 'logshape'
|
|
$stringscan = Get-ToolboxBinaryPath -Name 'stringscan'
|
|
$toon = Get-ToolboxBinaryPath -Name 'toon'
|
|
$csvshape = Get-ToolboxBinaryPath -Name 'csvshape'
|
|
$sqliteshape = Get-ToolboxBinaryPath -Name 'sqliteshape'
|
|
$sqlshape = Get-ToolboxBinaryPath -Name 'sqlshape'
|
|
$envdiff = Get-ToolboxBinaryPath -Name 'envdiff'
|
|
$proctree = Get-ToolboxBinaryPath -Name 'proctree'
|
|
$sysshape = Get-ToolboxBinaryPath -Name 'sysshape'
|
|
$unlock = Get-ToolboxBinaryPath -Name 'unlock'
|
|
|
|
Invoke-Check -Name 'toolbox:all-binaries-report-version' -Script {
|
|
Test-ToolboxBinaryVersions
|
|
"verified --version output for $((Get-ToolboxCommandNames).Count) toolbox binaries"
|
|
}
|
|
|
|
Invoke-Check -Name 'toolbox:all-binaries-no-args-contract' -Script {
|
|
Test-ToolboxNoArgsContracts
|
|
"verified bounded no-args behavior for non-stdin-waiting toolbox binaries"
|
|
}
|
|
|
|
Invoke-Check -Name 'toolbox:all-binaries-invalid-flag-contract' -Script {
|
|
Test-ToolboxInvalidFlagContracts
|
|
"verified invalid-flag diagnostics for $((Get-ToolboxCommandNames).Count) toolbox binaries"
|
|
}
|
|
|
|
Invoke-Check -Name 'toolbox:all-binaries-structured-output-help' -Script {
|
|
Test-ToolboxStructuredOutputHelpContracts
|
|
"verified structured-output help surface for $((Get-ToolboxCommandNames).Count) toolbox binaries"
|
|
}
|
|
|
|
Invoke-Check -Name 'toolbox:malformed-jsonl-stdin-contract' -Script {
|
|
Test-ToolboxMalformedJsonlStdinContracts
|
|
'verified malformed JSONL stdin fails closed for representative input-format commands'
|
|
}
|
|
|
|
Invoke-Check -Name 'toolbox:functional-toon-smokes' -Script {
|
|
$outputs = @(
|
|
(& $fileprobe (Join-Path $fixtureRoot 'reading\sample.rs') --toon),
|
|
(& $jsonshape (Join-Path $fixtureRoot 'jsonshape\events.jsonl') --input-format jsonl --toon),
|
|
(& $csvshape (Join-Path $fixtureRoot 'csvshape\sample.csv') --toon)
|
|
)
|
|
foreach ($output in $outputs) {
|
|
$text = (($output | ForEach-Object { [string]$_ }) -join "`n")
|
|
Assert-Condition ($text.Trim().Length -gt 0) 'expected non-empty representative TOON smoke output'
|
|
Assert-Condition ($text -notmatch '(?i)panic|backtrace') "TOON smoke output contained panic/backtrace text: $text"
|
|
}
|
|
|
|
'verified representative functional TOON smokes across file, JSONL, and CSV families'
|
|
}
|
|
|
|
Invoke-Check -Name 'cjson:stdin-jsonl-wrapper' -Script {
|
|
$wrapper = & $batPath '--style=plain' '--paging=never' (Join-Path $fixtureRoot 'cjson\records.jsonl') |
|
|
& $cjson --input-format jsonl --sort-keys --json |
|
|
ConvertFrom-Json
|
|
|
|
Assert-Condition ($wrapper.format -eq 'jsonl') "expected cjson wrapper format jsonl, got $($wrapper.format)"
|
|
Assert-Condition ($wrapper.documents -eq 2) "expected 2 compacted documents, got $($wrapper.documents)"
|
|
Assert-Condition ($wrapper.text -match '"event":"login","ok":true') 'expected compact wrapper text to contain a sorted login record'
|
|
"wrapped $($wrapper.documents) compacted JSONL document(s) for PowerShell consumers"
|
|
}
|
|
|
|
Invoke-Check -Name 'jsonlgrep:stdin-json' -Script {
|
|
$matchRows = & $batPath '--style=plain' '--paging=never' (Join-Path $fixtureRoot 'jsonl\events.jsonl') |
|
|
& $jsonlgrep 'level=error' --pick ts,msg --json |
|
|
ConvertFrom-Json
|
|
|
|
Assert-Condition (@($matchRows).Count -eq 2) "expected 2 error rows, got $(@($matchRows).Count)"
|
|
Assert-Condition ((@($matchRows)[0].msg) -eq 'failed login') 'expected first error message to be "failed login"'
|
|
"matched $(@($matchRows).Count) error rows through stdin + ConvertFrom-Json"
|
|
}
|
|
|
|
Invoke-Check -Name 'jsonshape:stdin-jq' -Script {
|
|
$summaryJson = & $batPath '--style=plain' '--paging=never' (Join-Path $fixtureRoot 'jsonshape\events.jsonl') |
|
|
& $jsonshape --input-format jsonl --json
|
|
|
|
$documents = [int]($summaryJson | & $jqPath '-r' '.documents')
|
|
$pathCount = [int]($summaryJson | & $jqPath '-r' '.paths | length')
|
|
|
|
Assert-Condition ($documents -eq 2) "expected 2 JSON documents, got $documents"
|
|
Assert-Condition ($pathCount -ge 3) "expected at least 3 summarized paths, got $pathCount"
|
|
"summarized $documents documents and $pathCount paths through jq"
|
|
}
|
|
|
|
Invoke-Check -Name 'recent:json-pipeline' -Script {
|
|
$recentJson = & $recent --root $fixtureRoot --ext rs --limit 3 --json
|
|
$firstPath = $recentJson | & $jqPath '-r' '.[0].path'
|
|
$count = [int]($recentJson | & $jqPath '-r' 'length')
|
|
|
|
Assert-Condition ($count -ge 1) "expected recent to return at least one Rust file, got $count"
|
|
Assert-Condition ($firstPath.EndsWith('.rs')) "expected first recent path to end with .rs, got $firstPath"
|
|
"returned $count recent Rust paths and projected the first path with jq"
|
|
}
|
|
|
|
Invoke-Check -Name 'pathshadow:piped-command' -Script {
|
|
$pathMatches = 'cargo' | & $pathshadow --json | ConvertFrom-Json
|
|
|
|
Assert-Condition (@($pathMatches).Count -ge 1) 'expected at least one cargo path match'
|
|
Assert-Condition ((@($pathMatches)[0].command) -eq 'cargo') "expected first pathshadow command to be cargo"
|
|
"resolved $(@($pathMatches).Count) PATH candidates for cargo from piped input"
|
|
}
|
|
|
|
Invoke-Check -Name 'pathshadow:summary-shell-json' -Script {
|
|
$summary = & $pathshadow cargo --shell powershell --summary --json | ConvertFrom-Json
|
|
$first = @($summary)[0]
|
|
|
|
Assert-Condition (@($summary).Count -ge 1) 'expected at least one pathshadow summary row'
|
|
Assert-Condition ($first.command -eq 'cargo') "expected cargo summary row, got $($first.command)"
|
|
Assert-Condition ($first.total_matches -ge 1) "expected at least one cargo match, got $($first.total_matches)"
|
|
Assert-Condition ($first.shell_mode -eq 'powershell') "expected powershell shell mode, got $($first.shell_mode)"
|
|
"summarized cargo resolution with shell-aware metadata"
|
|
}
|
|
|
|
Invoke-Check -Name 'portping:loopback-http' -Script {
|
|
$port = Get-FreeTcpPort
|
|
$server = Start-LoopbackHttpServer -Port $port
|
|
|
|
try {
|
|
$target = "http://127.0.0.1:$port/health"
|
|
$results = $target | & $portping --method GET --expect-status 200 --json | ConvertFrom-Json
|
|
$probe = @($results)[0]
|
|
|
|
Assert-Condition ($probe.ok) 'expected loopback HTTP probe to succeed'
|
|
Assert-Condition ($probe.status_code -eq 200) "expected HTTP 200, got $($probe.status_code)"
|
|
"probed $target successfully in $($probe.total_ms) ms from piped target input"
|
|
}
|
|
finally {
|
|
Stop-LoopbackHttpServer -Server $server
|
|
}
|
|
}
|
|
|
|
Invoke-Check -Name 'portunlock:who-and-free' -Script {
|
|
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("portunlock-check-" + [System.Guid]::NewGuid())
|
|
New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
|
|
$listener = $null
|
|
try {
|
|
$port = Get-FreeTcpPort
|
|
$listenerScript = Join-Path $tempDir 'listen.ps1'
|
|
Set-Content -LiteralPath $listenerScript -Encoding utf8NoBOM -Value @'
|
|
param([int]$Port)
|
|
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, $Port)
|
|
$listener.Start()
|
|
try {
|
|
Start-Sleep -Seconds 30
|
|
}
|
|
finally {
|
|
$listener.Stop()
|
|
}
|
|
'@
|
|
|
|
$listener = Start-Process -FilePath pwsh -ArgumentList @('-NoProfile', '-File', $listenerScript, $port) -PassThru -WindowStyle Hidden
|
|
Wait-Until -Description "TCP listener on port $port" -TimeoutMilliseconds 3000 -Probe {
|
|
Test-TcpPortListening -Port $port
|
|
}
|
|
|
|
$who = & $portunlock who --json --protocol tcp $port | ConvertFrom-Json
|
|
$free = & $portunlock free --json --force --protocol tcp $port | ConvertFrom-Json
|
|
$whoResult = @($who)[0]
|
|
$freeResult = @($free)[0]
|
|
|
|
Assert-Condition ($whoResult.port -eq $port) "expected who result for port $port, got $($whoResult.port)"
|
|
Assert-Condition (@($whoResult.initial_owners).Count -ge 1) 'expected portunlock who to surface at least one owner'
|
|
Assert-Condition ($freeResult.ok) 'expected portunlock free --force to succeed'
|
|
Assert-Condition (@($freeResult.final_owners).Count -eq 0) 'expected portunlock free to leave no remaining owners'
|
|
"identified and freed a loopback TCP owner on port $port"
|
|
}
|
|
finally {
|
|
if ($null -ne $listener -and -not $listener.HasExited) {
|
|
Stop-Process -Id $listener.Id -Force -ErrorAction SilentlyContinue
|
|
}
|
|
Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
Invoke-Check -Name 'msudo:status-json-stable-fields' -Script {
|
|
$status = & $msudo status --json | ConvertFrom-Json
|
|
$propertyNames = @($status.PSObject.Properties.Name)
|
|
$stableFields = @('ok', 'host', 'supports_runas', 'is_elevated', 'session_id', 'active_session_id', 'can_current_user')
|
|
$shellPresets = @($status.shells | ForEach-Object { [string]$_.preset })
|
|
|
|
Assert-Condition ($propertyNames.Count -ge 4) "expected msudo status to expose multiple stable fields, got $($propertyNames.Count)"
|
|
foreach ($stableField in $stableFields) {
|
|
Assert-Condition ($propertyNames -contains $stableField) "expected msudo status JSON to expose stable field $stableField"
|
|
}
|
|
Assert-Condition ($status.ok -is [bool]) "expected msudo status ok to be boolean, got $($status.ok.GetType().FullName)"
|
|
Assert-Condition ($status.is_elevated -is [bool]) "expected msudo status is_elevated to be boolean, got $($status.is_elevated.GetType().FullName)"
|
|
Assert-Condition ($status.supports_runas -is [bool]) "expected msudo status supports_runas to be boolean, got $($status.supports_runas.GetType().FullName)"
|
|
Assert-Condition ($status.can_current_user -is [bool]) "expected msudo status can_current_user to be boolean, got $($status.can_current_user.GetType().FullName)"
|
|
Assert-Condition (-not [string]::IsNullOrWhiteSpace([string]$status.host)) 'expected msudo status host to be a non-empty string'
|
|
Assert-Condition ($shellPresets -contains 'powershell') "expected msudo status to expose the CLI shell preset name 'powershell', got: $($shellPresets -join ', ')"
|
|
Assert-Condition ($shellPresets -contains 'git-bash') "expected msudo status to expose the CLI shell preset name 'git-bash', got: $($shellPresets -join ', ')"
|
|
"reported stable msudo discovery fields ok=$($status.ok) host=$($status.host) supports_runas=$($status.supports_runas) is_elevated=$($status.is_elevated) session=$($status.session_id) active_session=$($status.active_session_id) without launching a privileged child process"
|
|
}
|
|
|
|
Invoke-Check -Name 'msudo:help-surfaces-same-console-honestly' -Script {
|
|
$helpText = (& $msudo --help 2>&1 | Out-String)
|
|
Assert-Condition ($LASTEXITCODE -eq 0) 'expected msudo --help to succeed'
|
|
Assert-Condition (
|
|
$helpText -match '--same-console'
|
|
) "expected msudo help to expose the same-console flag, got: $helpText"
|
|
Assert-Condition (
|
|
$helpText -match 'Reuse the current console'
|
|
) "expected msudo help to describe same-console foreground behavior, got: $helpText"
|
|
'reported msudo help with the explicit same-console surface'
|
|
}
|
|
|
|
Invoke-Check -Name 'msudo:current-user-native-cli' -Script {
|
|
$tempDir = Join-Path $env:TEMP ("msudo-current-user-" + [guid]::NewGuid().ToString('N'))
|
|
New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
|
|
try {
|
|
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
|
|
$currentProcessPath = Join-Path $tempDir 'current-process.json'
|
|
$currentUserPath = Join-Path $tempDir 'current-user.json'
|
|
|
|
function New-EncodedPayload([string]$outputPath, [bool]$includeGroups) {
|
|
$escapedPath = $outputPath.Replace("'", "''")
|
|
$groupsExpr = if ($includeGroups) { "(whoami /groups | Out-String)" } else { '$null' }
|
|
$script = @'
|
|
$result = @{
|
|
user = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
|
|
session = (Get-Process -Id $PID).SessionId
|
|
groups = __GROUPS__
|
|
} | ConvertTo-Json -Compress
|
|
[System.IO.File]::WriteAllText('__OUTPUT_PATH__', $result)
|
|
'@
|
|
$script = $script.Replace('__GROUPS__', $groupsExpr).Replace('__OUTPUT_PATH__', $escapedPath)
|
|
[Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($script))
|
|
}
|
|
|
|
& $msudo --user current-process --wait -- powershell.exe -NoProfile -EncodedCommand (New-EncodedPayload -outputPath $currentProcessPath -includeGroups:$false)
|
|
Assert-Condition ($LASTEXITCODE -eq 0) "expected current-process launch to succeed, got exit code $LASTEXITCODE"
|
|
|
|
& $msudo --user current-user --wait -- powershell.exe -NoProfile -EncodedCommand (New-EncodedPayload -outputPath $currentUserPath -includeGroups:$false)
|
|
Assert-Condition ($LASTEXITCODE -eq 0) "expected current-user launch to succeed, got exit code $LASTEXITCODE"
|
|
|
|
$currentProcess = Get-Content $currentProcessPath -Raw | ConvertFrom-Json
|
|
$currentUserResult = Get-Content $currentUserPath -Raw | ConvertFrom-Json
|
|
|
|
Assert-Condition ($currentProcess.user -eq $currentUser) "expected current-process user $currentUser, got $($currentProcess.user)"
|
|
Assert-Condition ($currentUserResult.user -eq $currentUser) "expected current-user user $currentUser, got $($currentUserResult.user)"
|
|
|
|
"verified native current-process/current-user identities"
|
|
}
|
|
finally {
|
|
Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
Invoke-Check -Name 'msudo:admin-system-ti-current-session' -Script {
|
|
$tempDir = Join-Path $env:TEMP ("msudo-ecosystem-" + [guid]::NewGuid().ToString('N'))
|
|
New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
|
|
try {
|
|
$currentSession = (Get-Process -Id $PID).SessionId
|
|
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
|
|
|
|
$adminPath = Join-Path $tempDir 'admin.json'
|
|
$systemPath = Join-Path $tempDir 'system.json'
|
|
$tiPath = Join-Path $tempDir 'ti.json'
|
|
|
|
function New-EncodedPayload([string]$outputPath, [bool]$includeGroups) {
|
|
$escapedPath = $outputPath.Replace("'", "''")
|
|
$groupsExpr = if ($includeGroups) { "(whoami /groups | Out-String)" } else { '$null' }
|
|
$script = @'
|
|
$result = @{
|
|
user = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
|
|
session = (Get-Process -Id $PID).SessionId
|
|
groups = __GROUPS__
|
|
} | ConvertTo-Json -Compress
|
|
[System.IO.File]::WriteAllText('__OUTPUT_PATH__', $result)
|
|
'@
|
|
$script = $script.Replace('__GROUPS__', $groupsExpr).Replace('__OUTPUT_PATH__', $escapedPath)
|
|
[Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($script))
|
|
}
|
|
|
|
& $msudo --user admin --wait -- powershell -NoProfile -EncodedCommand (New-EncodedPayload -outputPath $adminPath -includeGroups:$false)
|
|
Assert-Condition ($LASTEXITCODE -eq 0) "expected admin msudo one-shot to succeed, got exit code $LASTEXITCODE"
|
|
|
|
& $msudo --user system --dangerous --wait -- powershell -NoProfile -EncodedCommand (New-EncodedPayload -outputPath $systemPath -includeGroups:$true)
|
|
Assert-Condition ($LASTEXITCODE -eq 0) "expected system msudo one-shot to succeed, got exit code $LASTEXITCODE"
|
|
|
|
& $msudo --user trustedinstaller --dangerous --wait -- powershell -NoProfile -EncodedCommand (New-EncodedPayload -outputPath $tiPath -includeGroups:$true)
|
|
Assert-Condition ($LASTEXITCODE -eq 0) "expected trustedinstaller msudo one-shot to succeed, got exit code $LASTEXITCODE"
|
|
|
|
$admin = Get-Content $adminPath -Raw | ConvertFrom-Json
|
|
$system = Get-Content $systemPath -Raw | ConvertFrom-Json
|
|
$ti = Get-Content $tiPath -Raw | ConvertFrom-Json
|
|
|
|
Assert-Condition ($admin.session -eq $currentSession) "expected admin session $currentSession, got $($admin.session)"
|
|
Assert-Condition ($system.session -eq $currentSession) "expected system session $currentSession, got $($system.session)"
|
|
Assert-Condition ($ti.session -eq $currentSession) "expected trustedinstaller session $currentSession, got $($ti.session)"
|
|
Assert-Condition ($admin.user -eq $currentUser) "expected admin user $currentUser, got $($admin.user)"
|
|
Assert-Condition ($system.user -eq 'NT AUTHORITY\SYSTEM') "expected system user NT AUTHORITY\\SYSTEM, got $($system.user)"
|
|
Assert-Condition ($ti.groups -match 'NT SERVICE\\TrustedInstaller') "expected trustedinstaller token groups to include TrustedInstaller SID, got: $($ti.groups)"
|
|
|
|
"verified admin/system/trustedinstaller one-shot launches in session $currentSession with SYSTEM user and TrustedInstaller service SID evidence"
|
|
}
|
|
finally {
|
|
Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
Invoke-Check -Name 'msudo:admin-integrity-and-privileges' -Script {
|
|
$tempDir = Join-Path $env:TEMP ("msudo-shape-" + [guid]::NewGuid().ToString('N'))
|
|
try {
|
|
New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
|
|
$disableAllPath = Join-Path $tempDir 'disable-all.json'
|
|
$enableAllPath = Join-Path $tempDir 'enable-all.json'
|
|
$escapedDisableAllPath = $disableAllPath.Replace("'", "''")
|
|
$disableAllPayload = @'
|
|
$result = @{
|
|
groups = (whoami /groups | Out-String)
|
|
privileges = (whoami /priv | Out-String)
|
|
} | ConvertTo-Json -Compress
|
|
[System.IO.File]::WriteAllText('__OUTPUT_PATH__', $result)
|
|
'@
|
|
$disableAllPayload = $disableAllPayload.Replace('__OUTPUT_PATH__', $escapedDisableAllPath)
|
|
$disableAllEncoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($disableAllPayload))
|
|
|
|
& $msudo --user admin --wait --integrity medium --privileges disable-all -- powershell.exe -NoProfile -EncodedCommand $disableAllEncoded
|
|
Assert-Condition ($LASTEXITCODE -eq 0) "expected admin integrity/privilege shaping launch to succeed, got exit code $LASTEXITCODE"
|
|
Assert-Condition (Test-Path -LiteralPath $disableAllPath) "expected admin integrity/privilege shaping launch to write $disableAllPath"
|
|
|
|
$disableAll = Get-Content $disableAllPath -Raw | ConvertFrom-Json
|
|
Assert-Condition ($disableAll.groups -match 'S-1-16-8192') "expected shaped admin token to include medium integrity SID S-1-16-8192, got: $($disableAll.groups)"
|
|
Assert-Condition ($disableAll.privileges -notmatch '(?m)(Enabled|已启用)\s*$') "expected shaped admin token privileges to be fully disabled, got: $($disableAll.privileges)"
|
|
|
|
$escapedEnableAllPath = $enableAllPath.Replace("'", "''")
|
|
$enableAllPayload = @'
|
|
$result = @{
|
|
privileges = (whoami /priv | Out-String)
|
|
} | ConvertTo-Json -Compress
|
|
[System.IO.File]::WriteAllText('__OUTPUT_PATH__', $result)
|
|
'@
|
|
$enableAllPayload = $enableAllPayload.Replace('__OUTPUT_PATH__', $escapedEnableAllPath)
|
|
$enableAllEncoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($enableAllPayload))
|
|
|
|
& $msudo --user admin --wait --privileges enable-all -- powershell.exe -NoProfile -EncodedCommand $enableAllEncoded
|
|
Assert-Condition ($LASTEXITCODE -eq 0) "expected admin enable-all privilege launch to succeed, got exit code $LASTEXITCODE"
|
|
Assert-Condition (Test-Path -LiteralPath $enableAllPath) "expected admin enable-all privilege launch to write $enableAllPath"
|
|
|
|
$enableAll = Get-Content $enableAllPath -Raw | ConvertFrom-Json
|
|
Assert-Condition ($enableAll.privileges -match '(?m)(Enabled|已启用)\s*$') "expected enable-all privilege shaping to leave at least one enabled privilege, got: $($enableAll.privileges)"
|
|
|
|
'verified admin token shaping for medium integrity plus real enable-all and disable-all privilege modes'
|
|
}
|
|
finally {
|
|
Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
Invoke-Check -Name 'msudo:shell-wrapped-system-and-ti' -Script {
|
|
$status = & $msudo status --json | ConvertFrom-Json
|
|
$availableShells = @{}
|
|
foreach ($shell in @($status.shells)) {
|
|
$availableShells[[string]$shell.preset] = [bool]$shell.available
|
|
}
|
|
|
|
$tempDir = Join-Path $env:TEMP ("msudo-shells-" + [guid]::NewGuid().ToString('N'))
|
|
New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
|
|
|
|
function New-EncodedPayload([string]$outputPath, [bool]$includeGroups) {
|
|
$escapedPath = $outputPath.Replace("'", "''")
|
|
$groupsExpr = if ($includeGroups) { "(whoami /groups | Out-String)" } else { '$null' }
|
|
$script = @'
|
|
$result = @{
|
|
user = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
|
|
session = (Get-Process -Id $PID).SessionId
|
|
groups = __GROUPS__
|
|
} | ConvertTo-Json -Compress
|
|
[System.IO.File]::WriteAllText('__OUTPUT_PATH__', $result)
|
|
'@
|
|
$script = $script.Replace('__GROUPS__', $groupsExpr).Replace('__OUTPUT_PATH__', $escapedPath)
|
|
[Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($script))
|
|
}
|
|
|
|
$systemCases = @(
|
|
@{ preset = 'cmd' }
|
|
@{ preset = 'powershell' }
|
|
@{ preset = 'pwsh' }
|
|
@{ preset = 'git-bash' }
|
|
)
|
|
|
|
$verified = [System.Collections.Generic.List[string]]::new()
|
|
try {
|
|
foreach ($case in $systemCases) {
|
|
if (-not $availableShells.ContainsKey($case.preset) -or -not $availableShells[$case.preset]) {
|
|
continue
|
|
}
|
|
|
|
$outputPath = Join-Path $tempDir ("system-" + $case.preset + '.json')
|
|
$argv = @(
|
|
'--user', 'system',
|
|
'--dangerous',
|
|
'--wait',
|
|
'--shell', $case.preset,
|
|
'--',
|
|
'powershell.exe',
|
|
'-NoProfile',
|
|
'-EncodedCommand',
|
|
(New-EncodedPayload -outputPath $outputPath -includeGroups:$true)
|
|
)
|
|
$output = (& $msudo @argv 2>&1 | Out-String)
|
|
$exitCode = $LASTEXITCODE
|
|
|
|
Assert-Condition ($exitCode -eq 0) "expected system shell-wrapped launch via $($case.preset) to succeed, got exit code $exitCode with output: $output"
|
|
Assert-Condition (Test-Path -LiteralPath $outputPath) "expected system shell-wrapped launch via $($case.preset) to write $outputPath"
|
|
|
|
$result = Get-Content $outputPath -Raw | ConvertFrom-Json
|
|
Assert-Condition ($result.user -eq 'NT AUTHORITY\SYSTEM') "expected system shell-wrapped launch via $($case.preset) to run as SYSTEM, got $($result.user)"
|
|
[void]$verified.Add($case.preset)
|
|
}
|
|
|
|
Assert-Condition ($verified.Count -ge 3) "expected to verify at least 3 available system shell presets, got $($verified.Count): $($verified -join ', ')"
|
|
|
|
$tiPreset = @('powershell', 'pwsh', 'cmd', 'git-bash') | Where-Object {
|
|
$availableShells.ContainsKey($_) -and $availableShells[$_]
|
|
} | Select-Object -First 1
|
|
Assert-Condition ($null -ne $tiPreset) 'expected at least one available shell preset to verify trustedinstaller shell wrapping'
|
|
|
|
$tiPath = Join-Path $tempDir ("trustedinstaller-" + $tiPreset + '.json')
|
|
$tiArgv = @(
|
|
'--user', 'trustedinstaller',
|
|
'--dangerous',
|
|
'--wait',
|
|
'--shell', $tiPreset,
|
|
'--',
|
|
'powershell.exe',
|
|
'-NoProfile',
|
|
'-EncodedCommand',
|
|
(New-EncodedPayload -outputPath $tiPath -includeGroups:$true)
|
|
)
|
|
$tiOutput = (& $msudo @tiArgv 2>&1 | Out-String)
|
|
$tiExitCode = $LASTEXITCODE
|
|
|
|
Assert-Condition ($tiExitCode -eq 0) "expected trustedinstaller shell-wrapped launch via $tiPreset to succeed, got exit code $tiExitCode with output: $tiOutput"
|
|
Assert-Condition (Test-Path -LiteralPath $tiPath) "expected trustedinstaller shell-wrapped launch via $tiPreset to write $tiPath"
|
|
|
|
$tiResult = Get-Content $tiPath -Raw | ConvertFrom-Json
|
|
Assert-Condition ($tiResult.user -eq 'NT AUTHORITY\SYSTEM') "expected trustedinstaller shell-wrapped launch via $tiPreset to report SYSTEM user, got $($tiResult.user)"
|
|
Assert-Condition ($tiResult.groups -match 'NT SERVICE\\TrustedInstaller') "expected trustedinstaller shell-wrapped launch via $tiPreset to include TrustedInstaller group evidence, got: $($tiResult.groups)"
|
|
|
|
"verified shell-wrapped system launches through $($verified -join ', ') and trustedinstaller group evidence through $tiPreset"
|
|
}
|
|
finally {
|
|
Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
Invoke-Check -Name 'msudo:same-console-system-and-ti' -Script {
|
|
$status = & $msudo status --json | ConvertFrom-Json
|
|
$availableShells = @{ }
|
|
foreach ($shell in @($status.shells)) {
|
|
$availableShells[[string]$shell.preset] = [bool]$shell.available
|
|
}
|
|
|
|
$tempDir = Join-Path $env:TEMP ("msudo-same-console-" + [guid]::NewGuid().ToString('N'))
|
|
New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
|
|
|
|
function New-EncodedPayload([string]$outputPath, [bool]$includeGroups) {
|
|
$escapedPath = $outputPath.Replace("'", "''")
|
|
$groupsExpr = if ($includeGroups) { "(whoami /groups | Out-String)" } else { '$null' }
|
|
$script = @'
|
|
$result = @{
|
|
user = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
|
|
session = (Get-Process -Id $PID).SessionId
|
|
groups = __GROUPS__
|
|
} | ConvertTo-Json -Compress
|
|
[System.IO.File]::WriteAllText('__OUTPUT_PATH__', $result)
|
|
'@
|
|
$script = $script.Replace('__GROUPS__', $groupsExpr).Replace('__OUTPUT_PATH__', $escapedPath)
|
|
[Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($script))
|
|
}
|
|
|
|
try {
|
|
$systemPath = Join-Path $tempDir 'system-direct.json'
|
|
& $msudo --same-console --user system --dangerous -- powershell.exe -NoProfile -EncodedCommand (New-EncodedPayload -outputPath $systemPath -includeGroups:$true)
|
|
Assert-Condition ($LASTEXITCODE -eq 0) "expected same-console system launch to succeed, got exit code $LASTEXITCODE"
|
|
Assert-Condition (Test-Path -LiteralPath $systemPath) "expected same-console system launch to write $systemPath"
|
|
$system = Get-Content $systemPath -Raw | ConvertFrom-Json
|
|
Assert-Condition ($system.user -eq 'NT AUTHORITY\SYSTEM') "expected same-console system launch to run as SYSTEM, got $($system.user)"
|
|
|
|
$tiPath = Join-Path $tempDir 'trustedinstaller-direct.json'
|
|
& $msudo --same-console --user trustedinstaller --dangerous -- powershell.exe -NoProfile -EncodedCommand (New-EncodedPayload -outputPath $tiPath -includeGroups:$true)
|
|
Assert-Condition ($LASTEXITCODE -eq 0) "expected same-console trustedinstaller launch to succeed, got exit code $LASTEXITCODE"
|
|
Assert-Condition (Test-Path -LiteralPath $tiPath) "expected same-console trustedinstaller launch to write $tiPath"
|
|
$ti = Get-Content $tiPath -Raw | ConvertFrom-Json
|
|
Assert-Condition ($ti.groups -match 'NT SERVICE\\TrustedInstaller') "expected same-console trustedinstaller launch to include TrustedInstaller group evidence, got: $($ti.groups)"
|
|
|
|
$sameConsolePreset = @('powershell', 'pwsh', 'cmd', 'git-bash') | Where-Object {
|
|
$availableShells.ContainsKey($_) -and $availableShells[$_]
|
|
} | Select-Object -First 1
|
|
Assert-Condition ($null -ne $sameConsolePreset) 'expected at least one available preset for same-console shell wrapping'
|
|
|
|
$wrappedPath = Join-Path $tempDir ("system-wrapped-" + $sameConsolePreset + '.json')
|
|
& $msudo --same-console --user system --dangerous --shell $sameConsolePreset -- powershell.exe -NoProfile -EncodedCommand (New-EncodedPayload -outputPath $wrappedPath -includeGroups:$true)
|
|
Assert-Condition ($LASTEXITCODE -eq 0) "expected same-console system shell wrapping via $sameConsolePreset to succeed, got exit code $LASTEXITCODE"
|
|
Assert-Condition (Test-Path -LiteralPath $wrappedPath) "expected same-console system shell wrapping via $sameConsolePreset to write $wrappedPath"
|
|
$wrapped = Get-Content $wrappedPath -Raw | ConvertFrom-Json
|
|
Assert-Condition ($wrapped.user -eq 'NT AUTHORITY\SYSTEM') "expected same-console system shell wrapping via $sameConsolePreset to run as SYSTEM, got $($wrapped.user)"
|
|
|
|
"verified same-console direct SYSTEM/TI launches and shell wrapping through $sameConsolePreset"
|
|
}
|
|
finally {
|
|
Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
Invoke-Check -Name 'msudo:privileged-pipeline-passthrough' -Script {
|
|
$systemOutput = (& $msudo --same-console --user system --dangerous --wait -- cmd /d /c whoami | Out-String)
|
|
$systemExit = $LASTEXITCODE
|
|
Assert-Condition ($systemExit -eq 0) "expected piped same-console SYSTEM launch to exit 0, got $systemExit"
|
|
Assert-Condition ($systemOutput -match 'nt authority\\system') "expected piped same-console SYSTEM stdout to include identity, got: $systemOutput"
|
|
|
|
$tiOutput = (& $msudo --same-console --user trustedinstaller --dangerous --wait -- cmd /d /c whoami /groups | Out-String)
|
|
$tiExit = $LASTEXITCODE
|
|
Assert-Condition ($tiExit -eq 0) "expected piped same-console TrustedInstaller launch to exit 0, got $tiExit"
|
|
Assert-Condition ($tiOutput -match 'NT SERVICE\\TrustedInstaller') "expected piped same-console TrustedInstaller stdout to include service SID, got: $tiOutput"
|
|
|
|
$stderrOutput = (& $msudo --same-console --user system --dangerous --wait -- cmd /d /c 'echo msudo-stderr 1>&2' 2>&1 | Out-String)
|
|
$stderrExit = $LASTEXITCODE
|
|
Assert-Condition ($stderrExit -eq 0) "expected piped same-console SYSTEM stderr launch to exit 0, got $stderrExit"
|
|
Assert-Condition ($stderrOutput -match 'msudo-stderr') "expected piped same-console SYSTEM stderr to be replayed, got: $stderrOutput"
|
|
|
|
'verified privileged stdout/stderr passthrough through non-elevated relay pipelines'
|
|
}
|
|
|
|
Invoke-Check -Name 'asmtype:managed-json' -Script {
|
|
$types = & $asmtype (Join-Path $fixtureRoot 'managed\bin\GameAssembly.dll') --with-member-match 'StartProject|QueueVehicle' --json |
|
|
ConvertFrom-Json
|
|
|
|
Assert-Condition (@($types).Count -ge 2) "expected at least 2 managed types, got $(@($types).Count)"
|
|
Assert-Condition ((@($types) | Where-Object full_name -eq 'Game.UI.Windows.Windows.SpaceCraftConstructionWindow').Count -ge 1) 'expected SpaceCraftConstructionWindow in asmtype output'
|
|
Assert-Condition ((@($types) | Where-Object full_name -eq 'Game.UI.Windows.Windows.SpaceCraftProjectWindow').Count -ge 1) 'expected SpaceCraftProjectWindow in asmtype output'
|
|
"listed $(@($types).Count) matching managed types"
|
|
}
|
|
|
|
Invoke-Check -Name 'asmmember:jsonl-types' -Script {
|
|
$members = & $asmtype (Join-Path $fixtureRoot 'managed\bin\GameAssembly.dll') --intent unity-spacecraft-workflow --json |
|
|
& $asmmember --assembly (Join-Path $fixtureRoot 'managed\bin\GameAssembly.dll') --input-format jsonl --intent unity-spacecraft-workflow --json |
|
|
ConvertFrom-Json
|
|
|
|
Assert-Condition ((@($members) | Where-Object kind -eq 'method' | Where-Object name -eq 'StartProject').Count -ge 1) 'expected StartProject method in asmmember output'
|
|
Assert-Condition ((@($members) | Where-Object kind -eq 'method' | Where-Object name -eq 'FinishProject').Count -ge 1) 'expected FinishProject method in asmmember output'
|
|
Assert-Condition ((@($members) | Where-Object kind -eq 'property' | Where-Object name -eq 'ProjectName').Count -ge 1) 'expected ProjectName property in asmmember output'
|
|
"listed $(@($members).Count) managed members from JSONL type handoff"
|
|
}
|
|
|
|
Invoke-Check -Name 'asmref:resolved-reference' -Script {
|
|
$report = & $asmref (Join-Path $fixtureRoot 'managed\bin\GameAssembly.dll') --resolve-dir (Join-Path $fixtureRoot 'managed\bin') --json |
|
|
ConvertFrom-Json
|
|
|
|
$fixtureSupport = @($report.references) | Where-Object name -eq 'FixtureSupport' | Select-Object -First 1
|
|
Assert-Condition ($null -ne $fixtureSupport) 'expected FixtureSupport reference in asmref output'
|
|
Assert-Condition ($fixtureSupport.resolved) 'expected FixtureSupport reference to resolve'
|
|
"resolved managed references for $($report.assembly.assembly_name)"
|
|
}
|
|
|
|
Invoke-Check -Name 'binmeta:fd-path-stream' -Script {
|
|
$reports = & $fdPath '-a' '--max-depth' '1' '^jsonlgrep\.exe$' $script:BinaryRoot |
|
|
& $binmeta --input-format lines --json |
|
|
ConvertFrom-Json
|
|
|
|
$report = @($reports)[0]
|
|
Assert-Condition (@($reports).Count -eq 1) "expected one binmeta report, got $(@($reports).Count)"
|
|
Assert-Condition ($report.path.EndsWith('jsonlgrep.exe')) "expected binmeta path to end with jsonlgrep.exe, got $($report.path)"
|
|
"inspected $($report.path) from fd path output"
|
|
}
|
|
|
|
Invoke-Check -Name 'fileprobe:fd-path-stream' -Script {
|
|
$reports = & $fdPath '-a' '^sample\.rs$' (Join-Path $fixtureRoot 'reading') |
|
|
& $fileprobe --input-format lines --json |
|
|
ConvertFrom-Json
|
|
|
|
$report = @($reports)[0]
|
|
Assert-Condition (@($reports).Count -eq 1) "expected one fileprobe report, got $(@($reports).Count)"
|
|
Assert-Condition ($report.language_hint -eq 'rust') "expected fileprobe language hint rust, got $($report.language_hint)"
|
|
"classified $($report.path) as $($report.family) with language hint $($report.language_hint)"
|
|
}
|
|
|
|
Invoke-Check -Name 'outline:recent-path-chain' -Script {
|
|
$reports = & $recent --root $fixtureRoot --ext rs --limit 1 --json |
|
|
ConvertFrom-Json |
|
|
Select-Object -ExpandProperty path |
|
|
& $outline --input-format lines --json |
|
|
ConvertFrom-Json
|
|
|
|
$report = @($reports)[0]
|
|
Assert-Condition (@($reports).Count -eq 1) "expected one outline report, got $(@($reports).Count)"
|
|
Assert-Condition (@($report.items).Count -ge 3) "expected outline to report at least 3 items, got $(@($report.items).Count)"
|
|
"outlined $($report.path) with $(@($report.items).Count) structural items"
|
|
}
|
|
|
|
Invoke-Check -Name 'codeshape:polyglot-json' -Script {
|
|
$report = & $codeshape (Join-Path $fixtureRoot 'polyglot\repo') --json | ConvertFrom-Json
|
|
$appTs = @($report.files) | Where-Object path -like '*web\app.ts' | Select-Object -First 1
|
|
|
|
Assert-Condition (@($report.files).Count -ge 6) "expected at least 6 indexed files, got $(@($report.files).Count)"
|
|
Assert-Condition ($null -ne $appTs) 'expected codeshape to include web\app.ts'
|
|
Assert-Condition ((@($appTs.items) | Where-Object name -eq 'helper').Count -ge 1) 'expected codeshape to capture helper in app.ts'
|
|
"indexed $(@($report.files).Count) files and surfaced AST-backed declarations"
|
|
}
|
|
|
|
Invoke-Check -Name 'snip:stdin-text' -Script {
|
|
$snippet = & $batPath '--style=plain' '--paging=never' (Join-Path $fixtureRoot 'reading\sample.rs') |
|
|
& $snip --around helper --context 0
|
|
|
|
Assert-Condition (($snippet -join "`n") -match 'helper') 'expected snip output to include helper'
|
|
"extracted helper-focused context from piped text"
|
|
}
|
|
|
|
Invoke-Check -Name 'defsnip:polyglot-json' -Script {
|
|
$symbolMatches = & $defsnip helper (Join-Path $fixtureRoot 'polyglot\repo') --json | ConvertFrom-Json
|
|
|
|
Assert-Condition (@($symbolMatches).Count -ge 4) "expected at least 4 helper definitions, got $(@($symbolMatches).Count)"
|
|
Assert-Condition ((@($symbolMatches) | Where-Object path -like '*web\app.ts').Count -ge 1) 'expected defsnip to include the TypeScript helper'
|
|
Assert-Condition ((@($symbolMatches) | Where-Object text -match 'helper').Count -ge 4) 'expected emitted definition text to contain helper bodies'
|
|
"extracted $(@($symbolMatches).Count) full definition block(s) by exact symbol name"
|
|
}
|
|
|
|
Invoke-Check -Name 'chunkcat:recent-path-chain' -Script {
|
|
$report = & $recent --root $fixtureRoot --ext rs --limit 1 --json |
|
|
ConvertFrom-Json |
|
|
Select-Object -ExpandProperty path |
|
|
& $chunkcat --input-format lines --max-lines 8 --chunk 0 --json |
|
|
ConvertFrom-Json
|
|
|
|
Assert-Condition ($report.chunk_count -ge 1) "expected at least one chunk, got $($report.chunk_count)"
|
|
Assert-Condition ($null -ne $report.selected_chunk) 'expected chunkcat to materialize the selected chunk'
|
|
"selected chunk 0 from $($report.path) with $($report.selected_chunk.line_count) lines"
|
|
}
|
|
|
|
Invoke-Check -Name 'hitsnip:rg-context' -Script {
|
|
$snippets = & $rgPath '-n' '-H' 'Mode::' (Join-Path $fixtureRoot 'reading\sample.rs') |
|
|
& $hitsnip --context 1 --json |
|
|
ConvertFrom-Json
|
|
|
|
Assert-Condition (@($snippets).Count -ge 1) 'expected at least one hitsnip snippet'
|
|
Assert-Condition ((@($snippets)[0].path) -like '*sample.rs') "expected hitsnip path to end with sample.rs, got $((@($snippets)[0].path))"
|
|
"expanded rg hits into $(@($snippets).Count) merged snippet(s)"
|
|
}
|
|
|
|
Invoke-Check -Name 'diagpick:stdin-log' -Script {
|
|
$diagnostics = & $batPath '--style=plain' '--paging=never' (Join-Path $fixtureRoot 'diag\rust-errors.txt') |
|
|
& $diagpick --with-source --json |
|
|
ConvertFrom-Json
|
|
|
|
$diagnostic = @($diagnostics)[0]
|
|
Assert-Condition (@($diagnostics).Count -ge 1) 'expected at least one diagnostic'
|
|
Assert-Condition ($diagnostic.severity -eq 'error') "expected first diagnostic severity error, got $($diagnostic.severity)"
|
|
"picked $(@($diagnostics).Count) diagnostic(s) with source context"
|
|
}
|
|
|
|
Invoke-Check -Name 'logshape:stdin-log' -Script {
|
|
$report = & $batPath '--style=plain' '--paging=never' (Join-Path $fixtureRoot 'logs\repetitive.log') |
|
|
& $logshape --json |
|
|
ConvertFrom-Json
|
|
|
|
Assert-Condition ($report.summary.group_count -ge 2) "expected at least 2 grouped patterns, got $($report.summary.group_count)"
|
|
Assert-Condition ((@($report.groups)[0].count) -ge 2) "expected top log group count >= 2, got $((@($report.groups)[0].count))"
|
|
"grouped repetitive logs into $($report.summary.group_count) templates"
|
|
}
|
|
|
|
Invoke-Check -Name 'stringscan:fd-path-stream' -Script {
|
|
$reports = & $fdPath '-a' '^stringscan-sample\.bin$' (Join-Path $fixtureRoot 'binaries') |
|
|
& $stringscan --input-format lines --json |
|
|
ConvertFrom-Json
|
|
|
|
$report = @($reports)[0]
|
|
Assert-Condition (@($reports).Count -eq 1) "expected one stringscan report, got $(@($reports).Count)"
|
|
Assert-Condition ((@($report.categories) -contains 'url')) 'expected stringscan categories to include url'
|
|
Assert-Condition ((@($report.categories) -contains 'bepinex')) 'expected stringscan categories to include bepinex'
|
|
"extracted $($report.match_count) classified strings from $($report.path)"
|
|
}
|
|
|
|
Invoke-Check -Name 'toon:json-wrapper-and-jsonl' -Script {
|
|
$wrapper = & $toon (Join-Path $fixtureRoot 'toon\config.json') --json | ConvertFrom-Json
|
|
$text = & $batPath '--style=plain' '--paging=never' (Join-Path $fixtureRoot 'toon\records.jsonl') |
|
|
& $toon
|
|
|
|
Assert-Condition ($wrapper.format -eq 'toon') "expected toon JSON wrapper format to be toon, got $($wrapper.format)"
|
|
Assert-Condition (($text -join "`n") -match 'event') 'expected TOON text converted from JSONL to include the event field'
|
|
"rendered a TOON JSON wrapper and converted JSONL stdin to TOON text"
|
|
}
|
|
|
|
Invoke-Check -Name 'csvshape:stdin-tsv' -Script {
|
|
$summary = & $batPath '--style=plain' '--paging=never' (Join-Path $fixtureRoot 'csvshape\sample.tsv') |
|
|
& $csvshape --delimiter tab --json |
|
|
ConvertFrom-Json
|
|
|
|
Assert-Condition ($summary.delimiter -eq 'tab') "expected detected delimiter tab, got $($summary.delimiter)"
|
|
Assert-Condition ($summary.column_count -eq 4) "expected 4 columns, got $($summary.column_count)"
|
|
Assert-Condition ((@($summary.columns) | Where-Object name -eq 'score').Count -eq 1) 'expected score column in csvshape output'
|
|
"summarized TSV schema with $($summary.column_count) columns and delimiter $($summary.delimiter)"
|
|
}
|
|
|
|
Invoke-Check -Name 'sqliteshape:fixture-db' -Script {
|
|
$summary = & $sqliteshape (Join-Path $fixtureRoot 'sqliteshape\sample.db') --include-indexes --json |
|
|
ConvertFrom-Json
|
|
|
|
Assert-Condition ((@($summary.tables) | Where-Object name -eq 'users').Count -eq 1) 'expected users table in sqliteshape output'
|
|
Assert-Condition ((@($summary.tables) | Where-Object name -eq 'events').Count -eq 1) 'expected events table in sqliteshape output'
|
|
Assert-Condition ((@($summary.tables)[0].columns).Count -ge 2) 'expected at least two columns in first table summary'
|
|
"inspected SQLite fixture with $(@($summary.tables).Count) tables"
|
|
}
|
|
|
|
Invoke-Check -Name 'sqlshape:sqlite-fixture-db' -Script {
|
|
$summary = & $sqlshape --engine sqlite --url (Join-Path $fixtureRoot 'sqliteshape\sample.db') --json |
|
|
ConvertFrom-Json
|
|
|
|
Assert-Condition ($summary.engine -eq 'sqlite') "expected sqlite engine, got $($summary.engine)"
|
|
Assert-Condition (@($summary.schemas | Where-Object { $_ -eq 'main' }).Count -eq 1) 'expected main schema in sqlshape output'
|
|
Assert-Condition (@($summary.tables | Where-Object name -eq 'users').Count -eq 1) 'expected users table in sqlshape output'
|
|
Assert-Condition (@($summary.tables | Where-Object name -eq 'events').Count -eq 1) 'expected events table in sqlshape output'
|
|
"inspected normalized SQLite fixture with $(@($summary.tables).Count) tables"
|
|
}
|
|
|
|
Invoke-Check -Name 'envdiff:cmd-wrapper-json' -Script {
|
|
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("envdiff-check-" + [System.Guid]::NewGuid())
|
|
New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
|
|
try {
|
|
$scriptPath = Join-Path $tempDir 'mutate-env.cmd'
|
|
Set-Content -LiteralPath $scriptPath -Encoding ascii -Value @'
|
|
@echo off
|
|
set TOOLBOX_STAGE=after
|
|
set TOOLBOX_REMOVE=
|
|
set PATH=%PATH%;C:\MercuryToolbox\TestBin
|
|
'@
|
|
$report = & $envdiff run --json --shell cmd -- $scriptPath | ConvertFrom-Json
|
|
|
|
Assert-Condition ((@($report.added) | Where-Object name -eq 'TOOLBOX_STAGE').Count -eq 1) 'expected TOOLBOX_STAGE in added variables'
|
|
Assert-Condition ((@($report.path_like_changes) | Where-Object name -eq 'Path' | Where-Object { @($_.added_segments) -contains 'C:\MercuryToolbox\TestBin' }).Count -eq 1) 'expected PATH segment delta in envdiff output'
|
|
"captured environment mutations and PATH segment deltas from cmd wrapper"
|
|
}
|
|
finally {
|
|
Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
Invoke-Check -Name 'proctree:run-json' -Script {
|
|
$report = & $proctree run --json -- pwsh -NoProfile -Command "Start-Process -FilePath pwsh -ArgumentList '-NoProfile','-Command','Start-Sleep -Seconds 2' -WindowStyle Hidden; Start-Sleep -Milliseconds 400" |
|
|
ConvertFrom-Json
|
|
|
|
Assert-Condition ($report.root_pid -gt 0) "expected rooted report pid, got $($report.root_pid)"
|
|
Assert-Condition (@($report.nodes).Count -ge 1) 'expected at least one process node from proctree run'
|
|
Assert-Condition ((@($report.nodes) | Where-Object pid -eq $report.root_pid).Count -eq 1) 'expected proctree report to include the root node'
|
|
"captured process tree with $(@($report.nodes).Count) node(s)"
|
|
}
|
|
|
|
Invoke-Check -Name 'sysshape:shell-json' -Script {
|
|
$report = & $sysshape --json --env none --group shell | ConvertFrom-Json
|
|
|
|
Assert-Condition (-not [string]::IsNullOrWhiteSpace($report.system.architecture)) 'expected sysshape to report a machine architecture'
|
|
Assert-Condition (-not [string]::IsNullOrWhiteSpace($report.system.default_shell)) 'expected sysshape to report a default shell hint'
|
|
Assert-Condition (@($report.tools).Count -ge 1) 'expected sysshape to detect at least one shell tool'
|
|
Assert-Condition (
|
|
((@($report.tools) | Where-Object name -eq 'pwsh').Count -ge 1) -or
|
|
((@($report.tools) | Where-Object name -eq 'powershell').Count -ge 1)
|
|
) 'expected sysshape shell inventory to include pwsh or powershell'
|
|
Assert-Condition ((@($report.tools) | Where-Object probe_status -eq 'ok').Count -ge 1) 'expected sysshape shell inventory to report probe_status'
|
|
"captured shell inventory with $(@($report.tools).Count) detected tool(s)"
|
|
}
|
|
|
|
Invoke-Check -Name 'unlock:who-and-delete' -Script {
|
|
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("unlock-check-" + [System.Guid]::NewGuid())
|
|
New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
|
|
$locker = $null
|
|
try {
|
|
$lockedPath = Join-Path $tempDir 'locked.txt'
|
|
$lockerScript = Join-Path $tempDir 'locker.ps1'
|
|
Set-Content -LiteralPath $lockedPath -Encoding utf8NoBOM -Value 'busy'
|
|
Set-Content -LiteralPath $lockerScript -Encoding utf8NoBOM -Value @'
|
|
$path = '__LOCKED_PATH__'
|
|
$stream = [System.IO.File]::Open($path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
|
|
try {
|
|
Start-Sleep -Seconds 30
|
|
}
|
|
finally {
|
|
$stream.Dispose()
|
|
}
|
|
'@.Replace('__LOCKED_PATH__', $lockedPath.Replace("'", "''"))
|
|
|
|
$locker = Start-Process -FilePath pwsh -ArgumentList @('-NoProfile', '-File', $lockerScript) -PassThru -WindowStyle Hidden
|
|
Wait-Until -Description "exclusive lock on $lockedPath" -TimeoutMilliseconds 3000 -Probe {
|
|
Test-ExclusiveFileLock -Path $lockedPath
|
|
}
|
|
|
|
$who = & $unlock who --json $lockedPath | ConvertFrom-Json
|
|
$delete = & $unlock delete --json --force $lockedPath | ConvertFrom-Json
|
|
$whoResult = @($who.results)[0]
|
|
$deleteResult = @($delete.results)[0]
|
|
|
|
Assert-Condition ($who.summary.results -eq 1) "expected single unlock who result, got $($who.summary.results)"
|
|
Assert-Condition (@($whoResult.initial_blockers).Count -ge 1 -or @($whoResult.final_blockers).Count -ge 1) 'expected unlock who to surface at least one blocker'
|
|
Assert-Condition ($delete.summary.results -eq 1) "expected single unlock delete result, got $($delete.summary.results)"
|
|
Assert-Condition ($deleteResult.ok) 'expected unlock delete --force to succeed'
|
|
Assert-Condition (-not (Test-Path -LiteralPath $lockedPath)) 'expected locked file to be deleted'
|
|
"identified blockers and deleted a locked file with staged escalation"
|
|
}
|
|
finally {
|
|
if ($null -ne $locker -and -not $locker.HasExited) {
|
|
Stop-Process -Id $locker.Id -Force -ErrorAction SilentlyContinue
|
|
}
|
|
Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
Invoke-Check -Name 'toolbox:valid-jsonl-path-stream-smokes' -Script {
|
|
'covered by per-command path-stream:* JSONL path-record checks below'
|
|
}
|
|
|
|
function Invoke-JsonlPathRecordSmoke {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Name,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$InputPath,
|
|
[string[]]$ArgumentList = @(),
|
|
[int[]]$AcceptExitCodes = @(0)
|
|
)
|
|
|
|
Assert-Condition (Test-Path -LiteralPath $InputPath) "expected JSONL path-record smoke input to exist for ${Name}: $InputPath"
|
|
$commandPath = Get-ToolboxBinaryPath -Name $Name
|
|
$jsonl = @{ path = $InputPath } | ConvertTo-Json -Compress
|
|
$output = $jsonl | & $commandPath @ArgumentList 2>&1
|
|
$exitCode = $LASTEXITCODE
|
|
$text = (($output | ForEach-Object { [string]$_ }) -join "`n")
|
|
|
|
Assert-Condition (@($AcceptExitCodes) -contains $exitCode) "expected ${Name} JSONL path-record exit code in $($AcceptExitCodes -join ', '), got $exitCode with output: $text"
|
|
Assert-Condition (-not [string]::IsNullOrWhiteSpace($text)) "expected ${Name} JSONL path-record smoke to emit output"
|
|
Assert-Condition ($text -notmatch "(?i)thread '.*' panicked|stack backtrace|RUST_BACKTRACE|panic at") "expected ${Name} JSONL path-record smoke to avoid panic/backtrace text, got: $text"
|
|
|
|
"verified ${Name} JSONL path-record stdin using $InputPath"
|
|
}
|
|
|
|
$sampleRust = Join-Path $fixtureRoot 'reading\sample.rs'
|
|
$readingRoot = Join-Path $fixtureRoot 'reading'
|
|
$polyglotRoot = Join-Path $fixtureRoot 'polyglot\repo'
|
|
$managedBinRoot = Join-Path $fixtureRoot 'managed\bin'
|
|
$managedAssembly = Join-Path $managedBinRoot 'GameAssembly.dll'
|
|
$csvSample = Join-Path $fixtureRoot 'csvshape\sample.csv'
|
|
$sqliteSample = Join-Path $fixtureRoot 'sqliteshape\sample.db'
|
|
$stringSample = Join-Path $fixtureRoot 'binaries\stringscan-sample.bin'
|
|
$peSample = Join-Path $script:BinaryRoot 'portping.exe'
|
|
$kernel32 = Join-Path $env:WINDIR 'System32\kernel32.dll'
|
|
$driverRoot = Join-Path $env:WINDIR 'System32\drivers'
|
|
$driverSmokeSample = @(
|
|
'ndis.sys',
|
|
'disk.sys',
|
|
'afd.sys',
|
|
'tcpip.sys',
|
|
'afunix.sys'
|
|
) |
|
|
ForEach-Object { Join-Path $driverRoot $_ } |
|
|
Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } |
|
|
Select-Object -First 1
|
|
if ([string]::IsNullOrWhiteSpace($driverSmokeSample) -and (Test-Path -LiteralPath $driverRoot -PathType Container)) {
|
|
$driverSmokeSample = Get-ChildItem -LiteralPath $driverRoot -Filter '*.sys' -File |
|
|
Sort-Object Name |
|
|
Select-Object -First 1 -ExpandProperty FullName
|
|
}
|
|
Assert-Condition (-not [string]::IsNullOrWhiteSpace($driverSmokeSample)) "expected at least one system driver sample under $driverRoot for path-stream smoke coverage"
|
|
|
|
$pathStreamSmokes = @(
|
|
@{ Name = 'asmflow'; Path = $managedAssembly; Args = @('--input-format', 'jsonl', '--json', 'find', '--match', 'StartProject') },
|
|
@{ Name = 'asmref'; Path = $managedAssembly; Args = @('--input-format', 'jsonl', '--resolve-dir', $managedBinRoot, '--json') },
|
|
@{ Name = 'asmtype'; Path = $managedAssembly; Args = @('--input-format', 'jsonl', '--match', 'SpaceCraft', '--json') },
|
|
@{ Name = 'binmeta'; Path = $peSample; Args = @('--input-format', 'jsonl', '--json') },
|
|
@{ Name = 'chunkcat'; Path = $sampleRust; Args = @('--input-format', 'jsonl', '--max-lines', '8', '--chunk', '0', '--json') },
|
|
@{ Name = 'codeshape'; Path = $polyglotRoot; Args = @('--input-format', 'jsonl', '--json') },
|
|
@{ Name = 'csvshape'; Path = $csvSample; Args = @('--input-format', 'jsonl', '--json') },
|
|
@{ Name = 'defsnip'; Path = $polyglotRoot; Args = @('helper', '--input-format', 'jsonl', '--json') },
|
|
@{ Name = 'mhash'; Path = $sampleRust; Args = @('--input-format', 'jsonl', '--json') },
|
|
@{ Name = 'drvshape'; Path = $driverSmokeSample; Args = @('--input-format', 'jsonl', '--json') },
|
|
@{ Name = 'fileprobe'; Path = $sampleRust; Args = @('--input-format', 'jsonl', '--json') },
|
|
@{ Name = 'ioctlscan'; Path = $driverSmokeSample; Args = @('--input-format', 'jsonl', '--json') },
|
|
@{ Name = 'llvmnm'; Path = $peSample; Args = @('--input-format', 'jsonl', '--json') },
|
|
@{ Name = 'llvmobjdump'; Path = $peSample; Args = @('--input-format', 'jsonl', '--json', '--raw-output-limit', '65536') },
|
|
@{ Name = 'llvmreadobj'; Path = $peSample; Args = @('--input-format', 'jsonl', '--json') },
|
|
@{ Name = 'outline'; Path = $sampleRust; Args = @('--input-format', 'jsonl', '--json') },
|
|
@{ Name = 'pecalls'; Path = $peSample; Args = @('--input-format', 'jsonl', '--json', '--api', 'GetProcAddress'); AcceptExitCodes = @(0, 1) },
|
|
@{ Name = 'peexports'; Path = $kernel32; Args = @('--input-format', 'jsonl', '--json') },
|
|
@{ Name = 'peimports'; Path = $peSample; Args = @('--input-format', 'jsonl', '--json') },
|
|
@{ Name = 'pesig'; Path = $peSample; Args = @('--input-format', 'jsonl', '--json', '--min-confidence', 'low') },
|
|
@{ Name = 'pestrrefs'; Path = $peSample; Args = @('--input-format', 'jsonl', '--json', '--contains', 'json') },
|
|
@{ Name = 'recent'; Path = $readingRoot; Args = @('--input-format', 'jsonl', '--json', '--limit', '1') },
|
|
@{ Name = 'refs'; Path = $polyglotRoot; Args = @('helper', '--input-format', 'jsonl', '--json') },
|
|
@{ Name = 'sqliteshape'; Path = $sqliteSample; Args = @('--input-format', 'jsonl', '--include-indexes', '--json') },
|
|
@{ Name = 'stringscan'; Path = $stringSample; Args = @('--input-format', 'jsonl', '--json') },
|
|
@{ Name = 'unityasset'; Path = $readingRoot; Args = @('--input-format', 'jsonl', '--json', 'index', '--allow-empty') },
|
|
@{ Name = 'unlock'; Path = $sampleRust; Args = @('--input-format', 'jsonl', '--json', 'who') }
|
|
)
|
|
|
|
foreach ($smoke in $pathStreamSmokes) {
|
|
Invoke-Check -Name "path-stream:$($smoke.Name):jsonl-path-record" -Script {
|
|
$acceptExitCodes = if ($smoke.ContainsKey('AcceptExitCodes')) { $smoke.AcceptExitCodes } else { @(0) }
|
|
Invoke-JsonlPathRecordSmoke -Name $smoke.Name -InputPath $smoke.Path -ArgumentList $smoke.Args -AcceptExitCodes $acceptExitCodes
|
|
}.GetNewClosure()
|
|
}
|
|
Invoke-Check -Name 'installer:temp-root-roundtrip' -Script {
|
|
$installRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("MercuryToolbox-" + [System.Guid]::NewGuid())
|
|
$codexHome = Join-Path ([System.IO.Path]::GetTempPath()) ("MercuryToolboxCodex-" + [System.Guid]::NewGuid())
|
|
$activeBinDir = Join-Path $installRoot 'current\bin'
|
|
try {
|
|
& (Join-Path $PSScriptRoot 'install-toolbox.ps1') `
|
|
-Configuration $Configuration `
|
|
-InstallRoot $installRoot `
|
|
-CodexHome $codexHome `
|
|
-NoPathUpdate `
|
|
-SkipBuild
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "install-toolbox.ps1 exited with $LASTEXITCODE"
|
|
}
|
|
|
|
foreach ($commandName in Get-ToolboxCommandNames) {
|
|
$commandPath = Join-Path $activeBinDir "$commandName.exe"
|
|
Assert-Condition (Test-Path -LiteralPath $commandPath) "expected installed binary not found: $commandPath"
|
|
}
|
|
Assert-Condition (Test-Path -LiteralPath (Join-Path $activeBinDir 'duckdb.dll')) 'expected installed DuckDB runtime'
|
|
Assert-Condition (
|
|
Test-Path -LiteralPath (Join-Path $installRoot 'share\mercury-toolbox\docs\ai\mercury-toolbox-ai-prompt.md')
|
|
) 'expected installed AI prompt in share directory'
|
|
Assert-Condition (
|
|
Test-Path -LiteralPath (Join-Path $installRoot 'share\mercury-toolbox\skills\mercury-toolbox\SKILL.md')
|
|
) 'expected installed shared skill copy'
|
|
Assert-Condition (
|
|
Test-Path -LiteralPath (Join-Path $codexHome 'skills\mercury-toolbox\SKILL.md')
|
|
) 'expected installed Codex skill copy'
|
|
Assert-Condition (
|
|
Test-Path -LiteralPath (Join-Path $codexHome 'skills\mercury-toolbox\references\command-catalog.md')
|
|
) 'expected installed Codex command catalog'
|
|
|
|
& (Join-Path $PSScriptRoot 'uninstall-toolbox.ps1') -InstallRoot $installRoot -CodexHome $codexHome
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "uninstall-toolbox.ps1 exited with $LASTEXITCODE"
|
|
}
|
|
|
|
Assert-Condition (-not (Test-Path -LiteralPath (Join-Path $installRoot 'current'))) 'expected uninstall to remove the current install junction'
|
|
Assert-Condition (-not (Test-Path -LiteralPath (Join-Path $installRoot 'versions'))) 'expected uninstall to remove staged versions'
|
|
Assert-Condition (-not (Test-Path -LiteralPath (Join-Path $installRoot 'share\mercury-toolbox'))) 'expected uninstall to remove the share directory'
|
|
Assert-Condition (-not (Test-Path -LiteralPath (Join-Path $codexHome 'skills\mercury-toolbox'))) 'expected uninstall to remove the Codex skill'
|
|
"installed and removed $((Get-ToolboxCommandNames).Count) commands in a temp root"
|
|
}
|
|
finally {
|
|
foreach ($path in @($installRoot, $codexHome)) {
|
|
if (Test-Path -LiteralPath $path) {
|
|
Remove-Item -LiteralPath $path -Recurse -Force
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Invoke-Check -Name 'installer:refuses-unmanaged-codex-skill' -Script {
|
|
$installRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("MercuryToolboxGuard-" + [System.Guid]::NewGuid())
|
|
$codexHome = Join-Path ([System.IO.Path]::GetTempPath()) ("MercuryToolboxGuardCodex-" + [System.Guid]::NewGuid())
|
|
$skillRoot = Join-Path $codexHome 'skills\mercury-toolbox'
|
|
$sentinel = Join-Path $skillRoot 'USER-SKILL.txt'
|
|
try {
|
|
New-Item -ItemType Directory -Force -Path $skillRoot | Out-Null
|
|
Set-Content -LiteralPath $sentinel -Value 'user managed' -Encoding utf8NoBOM
|
|
|
|
$result = Invoke-PwshFile -FilePath (Join-Path $PSScriptRoot 'install-toolbox.ps1') -ArgumentList @(
|
|
'-Configuration', $Configuration,
|
|
'-InstallRoot', $installRoot,
|
|
'-CodexHome', $codexHome,
|
|
'-NoPathUpdate',
|
|
'-SkipBuild'
|
|
)
|
|
|
|
Assert-Condition ($result.ExitCode -ne 0) 'expected workspace installer to refuse unmanaged Codex skill directory'
|
|
Assert-Condition (Test-Path -LiteralPath $sentinel) 'expected unmanaged Codex skill sentinel to be preserved'
|
|
Assert-Condition (-not (Test-Path -LiteralPath (Join-Path $skillRoot '.mercury-toolbox-owner.json'))) 'expected unmanaged skill to remain unmarked'
|
|
Assert-Condition (-not (Test-Path -LiteralPath (Join-Path $installRoot 'current'))) 'expected direct installer to fail before activating an install'
|
|
'refused unmanaged direct-install Codex skill directory without overwriting it'
|
|
}
|
|
finally {
|
|
foreach ($path in @($installRoot, $codexHome)) {
|
|
if (Test-Path -LiteralPath $path) {
|
|
Remove-Item -LiteralPath $path -Recurse -Force
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Invoke-Check -Name 'package:portable-roundtrip' -Script {
|
|
$packageOutputRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("MercuryToolboxPkg-" + [System.Guid]::NewGuid())
|
|
$extractRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("MercuryToolboxExtract-" + [System.Guid]::NewGuid())
|
|
$installRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("MercuryToolboxPortable-" + [System.Guid]::NewGuid())
|
|
$codexHome = Join-Path ([System.IO.Path]::GetTempPath()) ("MercuryToolboxPortableCodex-" + [System.Guid]::NewGuid())
|
|
$activeBinDir = Join-Path $installRoot 'current\bin'
|
|
try {
|
|
if ($SkipPromptGeneration) {
|
|
& (Join-Path $PSScriptRoot 'package-toolbox.ps1') `
|
|
-Configuration $Configuration `
|
|
-OutputRoot $packageOutputRoot `
|
|
-SkipBuild `
|
|
-SkipPromptGeneration
|
|
} else {
|
|
& (Join-Path $PSScriptRoot 'package-toolbox.ps1') `
|
|
-Configuration $Configuration `
|
|
-OutputRoot $packageOutputRoot `
|
|
-SkipBuild
|
|
}
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "package-toolbox.ps1 exited with $LASTEXITCODE"
|
|
}
|
|
|
|
$zip = @(Get-ChildItem -LiteralPath $packageOutputRoot -Filter '*.zip' -File)
|
|
Assert-Condition ($zip.Count -eq 1) 'expected exactly one portable package archive'
|
|
|
|
Expand-Archive -LiteralPath $zip[0].FullName -DestinationPath $extractRoot -Force
|
|
$packageDirs = @(Get-ChildItem -LiteralPath $extractRoot -Directory)
|
|
Assert-Condition ($packageDirs.Count -eq 1) 'expected extracted archive to contain one top-level package directory'
|
|
|
|
$packageRoot = $packageDirs[0].FullName
|
|
$installScript = Join-Path $packageRoot 'scripts\install-package-toolbox.ps1'
|
|
$uninstallScript = Join-Path $packageRoot 'scripts\uninstall-package-toolbox.ps1'
|
|
Assert-Condition (Test-Path -LiteralPath $installScript) "expected package install script at $installScript"
|
|
Assert-Condition (Test-Path -LiteralPath $uninstallScript) "expected package uninstall script at $uninstallScript"
|
|
Assert-Condition (Test-Path -LiteralPath (Join-Path $packageRoot 'docs\ai\mercury-toolbox-ai-prompt.md')) 'expected packaged AI prompt'
|
|
Assert-Condition (Test-Path -LiteralPath (Join-Path $packageRoot 'skills\mercury-toolbox\SKILL.md')) 'expected packaged Codex skill'
|
|
Assert-Condition (Test-Path -LiteralPath (Join-Path $packageRoot 'skills\mercury-toolbox\references\command-catalog.md')) 'expected packaged command catalog'
|
|
Assert-Condition (Test-Path -LiteralPath (Join-Path $packageRoot 'mercury-toolbox-package.json')) 'expected package manifest'
|
|
Assert-Condition (Test-Path -LiteralPath (Join-Path $packageRoot 'SHA256SUMS.txt')) 'expected package checksums'
|
|
Assert-Condition (Test-Path -LiteralPath (Join-Path $packageRoot 'START-HERE.txt')) 'expected portable package quickstart'
|
|
Assert-Condition (Test-Path -LiteralPath (Join-Path $packageRoot 'LICENSE')) 'expected packaged license'
|
|
Assert-Condition (Test-Path -LiteralPath (Join-Path $packageRoot 'bin\duckdb.dll')) 'expected packaged DuckDB runtime'
|
|
Assert-Condition (-not (Test-Path -LiteralPath (Join-Path $packageRoot 'README.md'))) 'expected portable package to omit README.md'
|
|
|
|
& $installScript -InstallRoot $installRoot -CodexHome $codexHome -NoPathUpdate
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "install-package-toolbox.ps1 exited with $LASTEXITCODE"
|
|
}
|
|
|
|
foreach ($commandName in Get-ToolboxCommandNames) {
|
|
$commandPath = Join-Path $activeBinDir "$commandName.exe"
|
|
Assert-Condition (Test-Path -LiteralPath $commandPath) "expected packaged install binary not found: $commandPath"
|
|
}
|
|
Assert-Condition (Test-Path -LiteralPath (Join-Path $activeBinDir 'duckdb.dll')) 'expected packaged install DuckDB runtime'
|
|
Assert-Condition (
|
|
Test-Path -LiteralPath (Join-Path $installRoot 'share\mercury-toolbox\docs\ai\mercury-toolbox-ai-prompt.md')
|
|
) 'expected installed AI prompt in portable share directory'
|
|
Assert-Condition (
|
|
Test-Path -LiteralPath (Join-Path $installRoot 'share\mercury-toolbox\skills\mercury-toolbox\SKILL.md')
|
|
) 'expected installed shared skill in portable share directory'
|
|
Assert-Condition (
|
|
-not (Test-Path -LiteralPath (Join-Path $installRoot 'share\mercury-toolbox\README.md'))
|
|
) 'expected portable share directory to omit README.md'
|
|
Assert-Condition (
|
|
Test-Path -LiteralPath (Join-Path $codexHome 'skills\mercury-toolbox\SKILL.md')
|
|
) 'expected installed portable Codex skill'
|
|
Assert-Condition (
|
|
Test-Path -LiteralPath (Join-Path $codexHome 'skills\mercury-toolbox\references\command-catalog.md')
|
|
) 'expected installed portable Codex command catalog'
|
|
|
|
& $uninstallScript -InstallRoot $installRoot -CodexHome $codexHome
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "uninstall-package-toolbox.ps1 exited with $LASTEXITCODE"
|
|
}
|
|
|
|
Assert-Condition (-not (Test-Path -LiteralPath (Join-Path $installRoot 'current'))) 'expected portable uninstall to remove the current install junction'
|
|
Assert-Condition (-not (Test-Path -LiteralPath (Join-Path $installRoot 'versions'))) 'expected portable uninstall to remove staged versions'
|
|
Assert-Condition (-not (Test-Path -LiteralPath (Join-Path $installRoot 'share\mercury-toolbox'))) 'expected portable uninstall to remove the share directory'
|
|
Assert-Condition (-not (Test-Path -LiteralPath (Join-Path $codexHome 'skills\mercury-toolbox'))) 'expected portable uninstall to remove the Codex skill'
|
|
'packaged, extracted, installed, and removed the portable toolbox bundle'
|
|
}
|
|
finally {
|
|
foreach ($path in @($packageOutputRoot, $extractRoot, $installRoot, $codexHome)) {
|
|
if (Test-Path -LiteralPath $path) {
|
|
Remove-Item -LiteralPath $path -Recurse -Force
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Invoke-Check -Name 'package:refuses-traversal-package-name' -Script {
|
|
$tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("MercuryToolboxTraversal-" + [System.Guid]::NewGuid())
|
|
$outputRoot = Join-Path $tempRoot 'out'
|
|
$escapeRoot = Join-Path $tempRoot 'escape'
|
|
$sentinel = Join-Path $escapeRoot 'sentinel.txt'
|
|
try {
|
|
New-Item -ItemType Directory -Force -Path $outputRoot, $escapeRoot | Out-Null
|
|
Set-Content -LiteralPath $sentinel -Value 'do not delete' -Encoding utf8NoBOM
|
|
|
|
$result = Invoke-PwshFile -FilePath (Join-Path $PSScriptRoot 'package-toolbox.ps1') -ArgumentList @(
|
|
'-Configuration', $Configuration,
|
|
'-OutputRoot', $outputRoot,
|
|
'-PackageName', '..\escape',
|
|
'-SkipBuild',
|
|
'-SkipPromptGeneration'
|
|
)
|
|
|
|
Assert-Condition ($result.ExitCode -ne 0) 'expected packager to reject a package name containing a path separator'
|
|
Assert-Condition (Test-Path -LiteralPath $sentinel) 'expected path outside OutputRoot to be preserved'
|
|
'rejected traversal package name without deleting outside OutputRoot'
|
|
}
|
|
finally {
|
|
if (Test-Path -LiteralPath $tempRoot) {
|
|
Remove-Item -LiteralPath $tempRoot -Recurse -Force
|
|
}
|
|
}
|
|
}
|
|
|
|
Invoke-Check -Name 'package-installer:verifies-sha256-before-install' -Script {
|
|
$tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("MercuryToolboxHash-" + [System.Guid]::NewGuid())
|
|
$packageRoot = Join-Path $tempRoot 'package'
|
|
$installRoot = Join-Path $tempRoot 'install'
|
|
$codexHome = Join-Path $tempRoot 'codex'
|
|
try {
|
|
New-TestPortablePackage -PackageRoot $packageRoot -UseWindowsSeparators
|
|
Set-Content -LiteralPath (Join-Path $packageRoot 'docs\ai\mercury-toolbox-ai-prompt.md') -Value 'tampered prompt' -Encoding utf8NoBOM
|
|
|
|
$result = Invoke-PwshFile -FilePath (Join-Path $packageRoot 'scripts\install-package-toolbox.ps1') -ArgumentList @(
|
|
'-InstallRoot', $installRoot,
|
|
'-CodexHome', $codexHome,
|
|
'-NoPathUpdate'
|
|
)
|
|
|
|
Assert-Condition ($result.ExitCode -ne 0) 'expected portable installer to reject a mismatched SHA256SUMS entry'
|
|
Assert-Condition (-not (Test-Path -LiteralPath $installRoot)) 'expected hash failure before writing install root'
|
|
Assert-Condition (-not (Test-Path -LiteralPath $codexHome)) 'expected hash failure before writing Codex skill root'
|
|
'rejected tampered package content before installing'
|
|
}
|
|
finally {
|
|
if (Test-Path -LiteralPath $tempRoot) {
|
|
Remove-Item -LiteralPath $tempRoot -Recurse -Force
|
|
}
|
|
}
|
|
}
|
|
|
|
Invoke-Check -Name 'package-installer:requires-listed-sha256-file' -Script {
|
|
$tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("MercuryToolboxHashMissing-" + [System.Guid]::NewGuid())
|
|
$packageRoot = Join-Path $tempRoot 'package'
|
|
$installRoot = Join-Path $tempRoot 'install'
|
|
try {
|
|
New-TestPortablePackage -PackageRoot $packageRoot -UseWindowsSeparators
|
|
Add-Content -LiteralPath (Join-Path $packageRoot 'SHA256SUMS.txt') -Value ('0' * 64 + ' docs\ai\missing.txt')
|
|
|
|
$result = Invoke-PwshFile -FilePath (Join-Path $packageRoot 'scripts\install-package-toolbox.ps1') -ArgumentList @(
|
|
'-InstallRoot', $installRoot,
|
|
'-NoCodexSkillInstall',
|
|
'-NoPathUpdate'
|
|
)
|
|
|
|
Assert-Condition ($result.ExitCode -ne 0) 'expected portable installer to reject a missing SHA256SUMS entry'
|
|
Assert-Condition (-not (Test-Path -LiteralPath $installRoot)) 'expected missing-file failure before writing install root'
|
|
'rejected package with a missing listed checksum file before installing'
|
|
}
|
|
finally {
|
|
if (Test-Path -LiteralPath $tempRoot) {
|
|
Remove-Item -LiteralPath $tempRoot -Recurse -Force
|
|
}
|
|
}
|
|
}
|
|
|
|
Invoke-Check -Name 'package-installer:refuses-unmanaged-codex-skill' -Script {
|
|
$tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("MercuryToolboxPackageGuard-" + [System.Guid]::NewGuid())
|
|
$packageRoot = Join-Path $tempRoot 'package'
|
|
$installRoot = Join-Path $tempRoot 'install'
|
|
$codexHome = Join-Path $tempRoot 'codex'
|
|
$skillRoot = Join-Path $codexHome 'skills\mercury-toolbox'
|
|
$sentinel = Join-Path $skillRoot 'USER-SKILL.txt'
|
|
try {
|
|
New-TestPortablePackage -PackageRoot $packageRoot -UseWindowsSeparators
|
|
New-Item -ItemType Directory -Force -Path $skillRoot | Out-Null
|
|
Set-Content -LiteralPath $sentinel -Value 'user managed' -Encoding utf8NoBOM
|
|
|
|
$result = Invoke-PwshFile -FilePath (Join-Path $packageRoot 'scripts\install-package-toolbox.ps1') -ArgumentList @(
|
|
'-InstallRoot', $installRoot,
|
|
'-CodexHome', $codexHome,
|
|
'-NoPathUpdate'
|
|
)
|
|
|
|
Assert-Condition ($result.ExitCode -ne 0) 'expected portable installer to refuse unmanaged Codex skill directory'
|
|
Assert-Condition (Test-Path -LiteralPath $sentinel) 'expected unmanaged portable Codex skill sentinel to be preserved'
|
|
Assert-Condition (-not (Test-Path -LiteralPath (Join-Path $skillRoot '.mercury-toolbox-owner.json'))) 'expected unmanaged portable skill to remain unmarked'
|
|
Assert-Condition (-not (Test-Path -LiteralPath $installRoot)) 'expected portable installer to fail before writing install root'
|
|
'refused unmanaged portable Codex skill directory without overwriting it'
|
|
}
|
|
finally {
|
|
if (Test-Path -LiteralPath $tempRoot) {
|
|
Remove-Item -LiteralPath $tempRoot -Recurse -Force
|
|
}
|
|
}
|
|
}
|
|
|
|
Write-Host ''
|
|
$script:Results | Format-Table -AutoSize
|
|
|
|
if ($script:Failed) {
|
|
throw 'One or more ecosystem checks failed.'
|
|
}
|