Files
reverse-skill/skills/scripts/test-p0-friction.ps1
T
Edisonandjhuang-tw 41bd788c34
CI / routing tests (ubuntu-latest) (push) Canceled after 0s
CI / routing tests (windows-latest) (push) Canceled after 0s
CI / shell script syntax check (push) Canceled after 0s
CI / field-journal leak scan (push) Canceled after 0s
CI / case contract test (ctf-demo) (push) Canceled after 0s
CI / version consistency (push) Canceled after 0s
macOS Bash compatibility / system Bash compatibility (push) Canceled after 0s
fix(workflow): carry transition decisions by delta (#113)
Co-authored-by: jhuang-tw <alr51307@gmail.com>
2026-08-22 14:10:08 +08:00

419 lines
22 KiB
PowerShell

#Requires -Version 5.1
# In-repo test: drives real smoke / case-init / append-evidence entrypoints.
# Usage:
# powershell -NoProfile -ExecutionPolicy Bypass -File skills/scripts/test-p0-friction.ps1
# powershell -File skills/scripts/test-p0-friction.ps1 -ScratchDir $env:TEMP\my-scratch
param(
[string] $ScratchDir = '',
[string] $PackageRoot = ''
)
$ErrorActionPreference = 'Stop'
$scriptDir = $PSScriptRoot
$skillsRoot = Split-Path -Parent $scriptDir
if (-not $PackageRoot) { $PackageRoot = Split-Path -Parent $skillsRoot }
. (Join-Path (Join-Path $scriptDir 'lib') 'HostRuntime.ps1')
$HostExe = Resolve-ReverseHostExe
if ([string]::IsNullOrWhiteSpace($ScratchDir)) {
$tmpBase = if ($env:TEMP) { $env:TEMP } else { [System.IO.Path]::GetTempPath() }
$ScratchDir = Join-Path $tmpBase ('rs-p0-test-' + (Get-Date -Format 'yyyyMMdd-HHmmss'))
}
New-Item -ItemType Directory -Path $ScratchDir -Force | Out-Null
$fail = New-Object System.Collections.Generic.List[string]
function Ok($m) { Write-Host "[OK] $m" -ForegroundColor Green }
function Bad($m) { Write-Host "[FAIL] $m" -ForegroundColor Red; [void]$fail.Add($m) }
Write-Host "ScratchDir=$ScratchDir"
Write-Host "PackageRoot=$PackageRoot"
# 1) smoke entrypoint
$smokeLog = Join-Path $ScratchDir 'smoke.log'
$smoke = Join-Path $scriptDir 'smoke.ps1'
& $HostExe -NoProfile -ExecutionPolicy Bypass -File $smoke -LogDir (Join-Path $ScratchDir 'smoke-logs') -PackageRoot $PackageRoot 2>&1 |
Tee-Object -FilePath $smokeLog | Out-Null
$smokeExit = $LASTEXITCODE
if ($smokeExit -eq 0) { Ok 'smoke exit 0' } else { Bad "smoke exit $smokeExit" }
$smokeText = Get-Content $smokeLog -Raw -Encoding UTF8
if ($smokeText -match 'verify-routing-coherence|VERIFY_EXIT=0|ALL PASS') { Ok 'smoke log has verify/pass signal' } else { Bad 'smoke log missing verify/pass signal' }
if ($smokeText -match 'route apk|parse master-route|parse case-init') { Ok 'smoke log has parse/route activity' } else { Bad 'smoke log missing parse/route activity' }
# 2) case-init ready-to-act
$caseName = 'p0-ready-' + (Get-Date -Format 'HHmmss')
$ciLog = Join-Path $ScratchDir 'case-init.log'
$ci = Join-Path $scriptDir 'case-init.ps1'
& $HostExe -NoProfile -ExecutionPolicy Bypass -File $ci `
-Hint 'web pentest nmap nuclei' `
-CaseName $caseName `
-PackageRoot $PackageRoot `
-AuthGranted `
-TargetUrl 'https://app.example.invalid/' `
-NetworkProfile 'authorized_target_only' 2>&1 |
Tee-Object -FilePath $ciLog | Out-Null
$caseRoot = Join-Path $PackageRoot ("work\{0}" -f $caseName)
$scopePath = Join-Path $caseRoot 'scope.md'
if (-not (Test-Path $scopePath)) { Bad "scope.md missing at $scopePath" }
else {
$scope = Get-Content $scopePath -Raw -Encoding UTF8
$scope | Set-Content (Join-Path $ScratchDir 'scope.md') -Encoding UTF8
if ($scope -match 'status:\s*granted') { Ok 'scope auth granted' } else { Bad 'scope auth not granted' }
if ($scope -match 'app\.example\.invalid') { Ok 'scope in_scope has target' } else { Bad 'scope missing target asset' }
if ($scope -match 'mode:\s*authorized_target_only') { Ok 'scope network authorized_target_only' } else { Bad 'scope network not authorized_target_only' }
if ($scope -match 'ready_for_act:\s*true') { Ok 'scope ready_for_act true' } else { Bad 'scope ready_for_act not true' }
}
# 2b) transition handoff is delta-by-reference: scope holds full state; timeline does not duplicate unchanged target context
$timelinePath = Join-Path $caseRoot 'timeline.md'
if (-not (Test-Path $timelinePath)) { Bad "timeline.md missing at $timelinePath" }
else {
$timelineText = Get-Content $timelinePath -Raw -Encoding UTF8
if ($timelineText -match '(?m)^- decision_delta:\s*\[case_initialized\]\s*$') { Ok 'timeline decision_delta initialized' } else { Bad 'timeline decision_delta missing' }
if ($timelineText -match '(?m)^- carry_forward_refs:\s*\[scope\.md\]\s*$') { Ok 'timeline carries authoritative scope by reference' } else { Bad 'timeline carry_forward_refs missing' }
if ($timelineText -notmatch [regex]::Escape('https://app.example.invalid/')) { Ok 'timeline does not duplicate unchanged target context' } else { Bad 'timeline duplicated target context from scope' }
}
# 3) bare case-init still pending defaults
$bareName = 'p0-bare-' + (Get-Date -Format 'HHmmss')
& $HostExe -NoProfile -ExecutionPolicy Bypass -File $ci -CaseName $bareName -PackageRoot $PackageRoot 2>&1 | Out-Null
$bareScope = Get-Content (Join-Path $PackageRoot ("work\{0}\scope.md" -f $bareName)) -Raw -Encoding UTF8
if ($bareScope -match 'status:\s*pending' -and $bareScope -match 'ready_for_act:\s*false') {
Ok 'bare case-init still pending/offline defaults'
} else {
Bad 'bare case-init defaults changed unexpectedly'
}
# 4) append-evidence
$evCase = Join-Path $ScratchDir 'case-ev'
New-Item -ItemType Directory -Path (Join-Path $evCase 'evidence') -Force | Out-Null
# minimal case tree
'placeholder' | Set-Content (Join-Path $evCase 'scope.md') -Encoding UTF8
$ae = Join-Path $scriptDir 'append-evidence.ps1'
$aeLog = Join-Path $ScratchDir 'append-evidence.log'
& $HostExe -NoProfile -ExecutionPolicy Bypass -File $ae `
-CaseRoot $evCase `
-Id 'E-001' `
-Title 'Smoke evidence item' `
-ReproCommand 'curl -sI https://app.example.invalid/' `
-Severity info `
-Status observed `
-RawExcerpt 'HTTP/1.1 200 OK' 2>&1 |
Tee-Object -FilePath $aeLog | Out-Null
$evFile = Join-Path $evCase 'evidence\E-001.md'
if (-not (Test-Path $evFile)) { Bad 'E-001.md not written' }
else {
$ev = Get-Content $evFile -Raw -Encoding UTF8
$ev | Set-Content (Join-Path $ScratchDir 'E-001.md') -Encoding UTF8
if ($ev -match 'title:\s*Smoke evidence item') { Ok 'evidence title' } else { Bad 'evidence title missing' }
if ($ev -match 'repro_command:') { Ok 'evidence repro_command' } else { Bad 'evidence repro_command missing' }
if ($ev -match 'curl -sI') { Ok 'evidence repro body' } else { Bad 'evidence repro body missing' }
}
$artifact = Join-Path $evCase 'evidence\fixture.bin'
[System.IO.File]::WriteAllBytes($artifact, [System.Text.Encoding]::UTF8.GetBytes('case artifact'))
& $HostExe -NoProfile -ExecutionPolicy Bypass -File $ae `
-CaseRoot $evCase `
-Id 'E-002' `
-Title 'Hashed evidence item' `
-ReproCommand 'Get-FileHash evidence\fixture.bin -Algorithm SHA256' `
-ArtifactPath 'evidence\fixture.bin' `
-Severity info `
-Status observed 2>&1 | Out-Null
$hashedEvidence = Join-Path $evCase 'evidence\E-002.md'
if (-not (Test-Path $hashedEvidence)) { Bad 'E-002.md not written' }
else {
$hashedText = Get-Content $hashedEvidence -Raw -Encoding UTF8
$expectedHash = (Get-FileHash -LiteralPath $artifact -Algorithm SHA256).Hash.ToLowerInvariant()
if ($hashedText -match [regex]::Escape('sha256:' + $expectedHash)) { Ok 'evidence SHA-256 recorded' } else { Bad 'evidence SHA-256 missing or incorrect' }
if ($hashedText -match '(?m)^- artifact_path:\s*evidence/fixture\.bin\s*$') { Ok 'evidence artifact path recorded' } else { Bad 'evidence artifact path missing' }
}
# 5) recon-pipeline topics present
$recon = Join-Path $skillsRoot 'pentest-tools\references\recon-pipeline.md'
$rt = Get-Content $recon -Raw -Encoding UTF8
foreach ($topic in @('Origin:', 'Referer:', 'eth0', 'append-evidence', 'Access-Control', 'nmap -sT')) {
# looser checks
}
if ($rt -match 'Origin:' -and $rt -match 'Referer:') { Ok 'recon-pipeline browser Origin/Referer' } else { Bad 'recon-pipeline missing Origin/Referer' }
if ($rt -match 'eth0' -or $rt -match 'nmap -sT') { Ok 'recon-pipeline Windows nmap note' } else { Bad 'recon-pipeline missing nmap note' }
if ($rt -match 'append-evidence') { Ok 'recon-pipeline Evidence append' } else { Bad 'recon-pipeline missing append-evidence' }
# 7) verify alone
$vLog = Join-Path $ScratchDir 'verify.log'
& $HostExe -NoProfile -ExecutionPolicy Bypass -File (Join-Path $scriptDir 'verify-routing-coherence.ps1') 2>&1 |
Tee-Object -FilePath $vLog | Out-Null
if ($LASTEXITCODE -eq 0) { Ok 'verify-routing-coherence exit 0' } else { Bad "verify exit $LASTEXITCODE" }
# 8) Chinese route samples (P1)
$mr = Join-Path $scriptDir 'master-route.ps1'
$zhCases = @(
@{ Hint = '安卓 APK 加固 反编译'; Expect = 'apk-reverse' },
@{ Hint = '渗透测试 端口扫描 SQL注入'; Expect = 'pentest-tools' },
@{ Hint = '前端签名 JS逆向'; Expect = 'js-reverse' }
)
foreach ($zc in $zhCases) {
$raw = & $HostExe -NoProfile -ExecutionPolicy Bypass -File $mr -Hint $zc.Hint 2>&1 | Out-String
if ($raw -match [regex]::Escape($zc.Expect)) { Ok ("zh route -> {0}" -f $zc.Expect) }
else { Bad ("zh route miss {0}: {1}" -f $zc.Expect, ($raw.Substring(0, [Math]::Min(120, $raw.Length)))) }
}
# 9) case-guard: ready case exits 0; bare pending exits 2 even with -Force
$cg = Join-Path $scriptDir 'case-guard.ps1'
& $HostExe -NoProfile -ExecutionPolicy Bypass -File $cg -CaseRoot $caseRoot 2>&1 | Out-Null
if ($LASTEXITCODE -eq 0) { Ok 'case-guard ready exit 0' } else { Bad "case-guard ready exit $LASTEXITCODE" }
$bareRoot = Join-Path $PackageRoot ("work\{0}" -f $bareName)
& $HostExe -NoProfile -ExecutionPolicy Bypass -File $cg -CaseRoot $bareRoot 2>&1 | Out-Null
if ($LASTEXITCODE -eq 2) { Ok 'case-guard pending exit 2' } else { Bad "case-guard pending expected 2 got $LASTEXITCODE" }
& $HostExe -NoProfile -ExecutionPolicy Bypass -File $cg -CaseRoot $bareRoot -Force 2>&1 | Out-Null
if ($LASTEXITCODE -eq 2) { Ok 'case-guard -Force cannot bypass hard gate' } else { Bad "case-guard -Force expected 2 got $LASTEXITCODE" }
# 10) AuthGranted must not be clobbered by junk AuthStatus / multi-asset lab init
# Note: -ProjectRoot is passed explicitly to prevent the -InScopeAssets array
# from being flattened by powershell -File and binding its second element to the
# -ProjectRoot positional parameter slot.
$labName = 'p0-lab-' + (Get-Date -Format 'HHmmss')
& $HostExe -NoProfile -ExecutionPolicy Bypass -File $ci `
-Hint 'gin juice lab pentest' `
-CaseName $labName `
-PackageRoot $PackageRoot `
-ProjectRoot $PackageRoot `
-AuthGranted `
-AuthBasis 'public_training_lab' `
-EvidenceOfAuth 'PortSwigger intentionally vulnerable public site example' `
-TargetUrl 'https://ginandjuice.shop/' `
-NetworkProfile 'lab_only' `
-InScopeAssets @('https://ginandjuice.shop/', 'ginandjuice.shop') 2>&1 | Out-Null
$labScopePath = Join-Path $PackageRoot ("work\{0}\scope.md" -f $labName)
$labScope = Get-Content $labScopePath -Raw -Encoding UTF8
$labScope | Set-Content (Join-Path $ScratchDir 'scope-lab.md') -Encoding UTF8
if ($labScope -match 'status:\s*granted') { Ok 'lab AuthGranted stays granted' } else { Bad 'lab auth not granted (AuthStatus clobber?)' }
if ($labScope -match 'ready_for_act:\s*true') { Ok 'lab ready_for_act true with lab_only' } else { Bad 'lab ready_for_act false' }
if ($labScope -match 'mode:\s*lab_only') { Ok 'lab network lab_only' } else { Bad 'lab network wrong' }
if ($labScope -match 'ginandjuice\.shop') { Ok 'lab in_scope has target' } else { Bad 'lab missing target asset' }
# 10b) garbage AuthStatus must not override AuthGranted
$junkName = 'p0-junk-' + (Get-Date -Format 'HHmmss')
& $HostExe -NoProfile -ExecutionPolicy Bypass -File $ci `
-Hint 'web pentest lab' `
-CaseName $junkName `
-PackageRoot $PackageRoot `
-AuthGranted `
-AuthStatus 'demo.owasp-juice.shop' `
-TargetUrl 'https://example.invalid/' `
-NetworkProfile 'lab_only' 2>&1 | Tee-Object -FilePath (Join-Path $ScratchDir 'case-init-junk-auth.log') | Out-Null
$junkScope = Get-Content (Join-Path $PackageRoot ("work\{0}\scope.md" -f $junkName)) -Raw -Encoding UTF8
$junkScope | Set-Content (Join-Path $ScratchDir 'scope-junk-auth.md') -Encoding UTF8
if ($junkScope -match 'status:\s*granted' -and $junkScope -notmatch 'status:\s*demo\.owasp') {
Ok 'junk AuthStatus ignored; remains granted'
} else {
Bad 'junk AuthStatus clobbered granted'
}
# 11) append-evidence: special-char excerpt via -File + -RawExcerptFile (real nested CLI path)
$ae2 = Join-Path $ScratchDir 'case-ev'
New-Item -ItemType Directory -Path (Join-Path $ae2 'evidence') -Force | Out-Null
'x' | Set-Content (Join-Path $ae2 'scope.md') -Encoding UTF8
$excerptPayload = '"XML parsing error" / Entities are not allowed'
$excerptFile = Join-Path $ScratchDir 'excerpt-payload.txt'
# UTF-8 no BOM for portability
[System.IO.File]::WriteAllText($excerptFile, $excerptPayload, (New-Object System.Text.UTF8Encoding $false))
& $HostExe -NoProfile -ExecutionPolicy Bypass -File $ae `
-CaseRoot $ae2 `
-Id 'E-XML' `
-Title 'Stock API error sample' `
-ReproCommand 'curl -s -X POST https://example/stock --data-binary @x.xml' `
-RawExcerptFile $excerptFile `
-Severity medium `
-Status observed 2>&1 |
Tee-Object -FilePath (Join-Path $ScratchDir 'append-evidence.log') | Out-Null
if ($LASTEXITCODE -ne 0) { Bad "append-evidence -File exit $LASTEXITCODE" }
$evXml = Join-Path $ae2 'evidence\E-XML.md'
if (Test-Path $evXml) {
$ex = Get-Content $evXml -Raw -Encoding UTF8
$ex | Set-Content (Join-Path $ScratchDir 'E-XML.md') -Encoding UTF8
if ($ex -match '(?m)^- title:\s*Stock API error sample\s*$' -and $ex -match 'repro_command:') {
Ok 'evidence has title+repro'
} else { Bad 'evidence missing title/repro' }
# Assert specifically inside raw_excerpt block (not title)
$excerptBlockOk = $false
if ($ex -match '(?ms)^- raw_excerpt:\s*\|\r?\n(?<block>.*?)(?=\r?\n- |\z)') {
$block = $Matches['block']
if ($block -match 'XML parsing error' -and $block -match 'Entities are not allowed' -and $block -match '"') {
$excerptBlockOk = $true
}
}
if ($excerptBlockOk) { Ok 'raw_excerpt block preserves special payload' } else { Bad 'raw_excerpt block lost special payload' }
if ($ex -match '(?m)^- location:\s*n/a\s*$') { Ok 'location remains n/a' } else { Bad 'location polluted by arg split' }
if ($ex -match '(?m)^- source_type:\s*command\s*$') { Ok 'source_type remains command' } else { Bad 'source_type polluted by arg split' }
} else { Bad 'E-XML.md not written' }
# 11b) broken multi-word -RawExcerpt under -File must fail loud (not silently corrupt fields)
$ae3 = Join-Path $ScratchDir 'case-ev-broken'
New-Item -ItemType Directory -Path (Join-Path $ae3 'evidence') -Force | Out-Null
'x' | Set-Content (Join-Path $ae3 'scope.md') -Encoding UTF8
$brokenLog = Join-Path $ScratchDir 'append-evidence-broken.log'
# Intentionally unquoted multi-word after -RawExcerpt to simulate nested -File quote loss
cmd /c "`"$HostExe`" -NoProfile -ExecutionPolicy Bypass -File `"$ae`" -CaseRoot `"$ae3`" -Id E-BAD -Title `"T`" -ReproCommand `"curl -sI https://example/`" -RawExcerpt XML parsing error / Entities -Severity info -Status observed >`"$brokenLog`" 2>&1"
if ($LASTEXITCODE -ne 0) { Ok 'broken multi-word RawExcerpt fails loud' } else { Bad 'broken multi-word RawExcerpt should not exit 0' }
# 12) client-side + recon playbook topics
$play = Join-Path $skillsRoot 'pentest-tools\references\client-side-lab-playbook.md'
$recon = Join-Path $skillsRoot 'pentest-tools\references\recon-pipeline.md'
if (Test-Path $play) {
$pt = Get-Content $play -Raw -Encoding UTF8
if ($pt -match 'globoff' -and $pt -match 'innerHTML' -and $pt -match 'agent-browser' -and $pt -match 'observed') {
Ok 'client-side-lab-playbook topics'
} else { Bad 'client-side-lab-playbook missing topics' }
} else { Bad 'client-side-lab-playbook missing' }
if (Test-Path $recon) {
$rt = Get-Content $recon -Raw -Encoding UTF8
if ($rt -match 'Origin:' -and $rt -match 'Referer:' -and $rt -match 'globoff' -and ($rt -match 'nmap -sT' -or $rt -match 'eth0')) {
Ok 'recon-pipeline topic families'
} else { Bad 'recon-pipeline missing topic family' }
} else { Bad 'recon-pipeline missing' }
# 13) ReadyForAct alone must NOT mark ready without auth/assets
$forceName = 'p0-forceonly-' + (Get-Date -Format 'HHmmss')
& $HostExe -NoProfile -ExecutionPolicy Bypass -File $ci `
-CaseName $forceName -PackageRoot $PackageRoot -ReadyForAct 2>&1 | Out-Null
$forceScope = Get-Content (Join-Path $PackageRoot ("work\{0}\scope.md" -f $forceName)) -Raw -Encoding UTF8
if ($forceScope -match 'ready_for_act:\s*false' -and $forceScope -match 'status:\s*pending') {
Ok 'ReadyForAct alone does not bypass auth'
} else {
Bad 'ReadyForAct alone incorrectly set ready/granted'
}
# 14) case-guard must not treat ops_refs paths as in_scope assets
$ghostCase = Join-Path $ScratchDir 'case-guard-ghost-asset'
New-Item -ItemType Directory -Force -Path $ghostCase | Out-Null
$ghostScope = @'
# Case Scope
## auth
- status: granted
## in_scope
- assets:
[]
## network_profile
- mode: lab_only
## signoff
- ready_for_act: true
## ops_refs
- skills/ops/scope-contract.md
- https://example.com/docs-only-not-asset
'@
Set-Content (Join-Path $ghostCase 'scope.md') $ghostScope -Encoding UTF8
& $HostExe -NoProfile -ExecutionPolicy Bypass -File $cg -CaseRoot $ghostCase 2>&1 | Out-Null
if ($LASTEXITCODE -eq 2) { Ok 'case-guard rejects empty assets despite ops_refs URLs' }
else { Bad "case-guard should fail empty assets; exit=$LASTEXITCODE" }
# 14a) Auth/signoff fields outside their contract sections must not satisfy gate.
$sectionCase = Join-Path $ScratchDir 'case-guard-section-scope'
New-Item -ItemType Directory -Force -Path $sectionCase | Out-Null
$sectionScope = @'
# Case Scope
## auth
- status: pending
## in_scope
- assets:
- https://example.test/
## network_profile
- mode: authorized_target_only
## signoff
- ready_for_act: false
## notes
- status: granted
- ready_for_act: true
'@
Set-Content (Join-Path $sectionCase 'scope.md') $sectionScope -Encoding UTF8
& $HostExe -NoProfile -ExecutionPolicy Bypass -File $cg -CaseRoot $sectionCase 2>&1 | Out-Null
if ($LASTEXITCODE -eq 2) { Ok 'case-guard scopes auth/signoff fields to contract sections' }
else { Bad "case-guard accepted forged fields from notes; exit=$LASTEXITCODE" }
$networkCase = Join-Path $ScratchDir 'case-guard-invalid-network'
New-Item -ItemType Directory -Force -Path $networkCase | Out-Null
$networkScope = @'
# Case Scope
## auth
- status: granted
## in_scope
- assets:
- https://example.test/
## network_profile
- mode: internet
## signoff
- ready_for_act: true
'@
Set-Content (Join-Path $networkCase 'scope.md') $networkScope -Encoding UTF8
& $HostExe -NoProfile -ExecutionPolicy Bypass -File $cg -CaseRoot $networkCase 2>&1 | Out-Null
if ($LASTEXITCODE -eq 2) { Ok 'case-guard rejects unsupported network mode' }
else { Bad "case-guard accepted unsupported network mode; exit=$LASTEXITCODE" }
$invalidNetworkName = 'p0-invalid-network-' + (Get-Date -Format 'HHmmss')
$invalidNetworkProcess = Start-Process -FilePath $HostExe -ArgumentList @(
'-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $ci,
'-Hint', 'authorized web review',
'-CaseName', $invalidNetworkName,
'-PackageRoot', $PackageRoot,
'-AuthGranted',
'-TargetUrl', 'https://example.test/',
'-NetworkProfile', 'internet'
) -Wait -PassThru -WindowStyle Hidden
$invalidNetworkRoot = Join-Path $PackageRoot ("work\{0}" -f $invalidNetworkName)
if ($invalidNetworkProcess.ExitCode -ne 0 -and -not (Test-Path -LiteralPath $invalidNetworkRoot)) {
Ok 'case-init rejects unsupported network profile before writing case'
} else {
Bad 'case-init accepted unsupported network profile or wrote a partial case'
}
# 14b) CaseName must remain a single safe directory name under work/.
$invalidCaseNames = @('..\case-escape', '../case-escape', 'case/name', 'case:name', '.. ', 'case.')
$workRoot = Join-Path $PackageRoot 'work'
foreach ($invalidCaseName in $invalidCaseNames) {
$beforeCases = @(Get-ChildItem -LiteralPath $workRoot -Force -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name)
$process = Start-Process -FilePath $HostExe -ArgumentList @(
'-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $ci,
'-CaseName', $invalidCaseName, '-PackageRoot', $PackageRoot
) -Wait -PassThru -NoNewWindow
$exitCode = $process.ExitCode
$afterCases = @(Get-ChildItem -LiteralPath $workRoot -Force -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name)
$newCases = @($afterCases | Where-Object { $_ -notin $beforeCases })
if ($exitCode -ne 0 -and $newCases.Count -eq 0) {
Ok "case-init rejects unsafe CaseName '$invalidCaseName'"
} else {
Bad "case-init accepted or wrote unsafe CaseName '$invalidCaseName'"
foreach ($newCase in $newCases) {
Remove-Item -LiteralPath (Join-Path $workRoot $newCase) -Recurse -Force -ErrorAction SilentlyContinue
}
}
}
# 13) copy smoke primary log alias for harness
$smokeLogs = Join-Path $ScratchDir 'smoke-logs'
if (Test-Path (Join-Path $smokeLogs 'SUMMARY.txt')) {
Copy-Item (Join-Path $smokeLogs 'SUMMARY.txt') (Join-Path $ScratchDir 'smoke-summary.txt') -Force
}
if (Test-Path $smokeLog) {
# already smoke.log
}
# OPT summary
$opt = @(
"entrypoints: smoke.ps1, case-init.ps1, append-evidence.ps1, case-guard.ps1, case-review/review_case.py, master-route.ps1, verify-routing-coherence.ps1, test-p0-friction.ps1",
"docs: recon-pipeline.md (Origin/Referer, nmap -sT/eth0, globoff, append-evidence); client-side-lab-playbook.md (innerHTML sink, agent-browser, observed vs validated, PP)",
"FAIL_COUNT=$($fail.Count)",
"ScratchDir=$ScratchDir"
)
$opt -join [Environment]::NewLine | Set-Content (Join-Path $ScratchDir 'OPT-SUMMARY.txt') -Encoding UTF8
$summary = "FAIL_COUNT=$($fail.Count)`nScratchDir=$ScratchDir"
$summary | Set-Content (Join-Path $ScratchDir 'TEST-SUMMARY.txt') -Encoding UTF8
Write-Host '=== TEST-P0 SUMMARY ==='
Write-Host $summary
if ($fail.Count -gt 0) {
$fail | Set-Content (Join-Path $ScratchDir 'failures.txt') -Encoding UTF8
exit 1
}
Write-Host 'OVERALL: ALL PASS' -ForegroundColor Green
exit 0