639b0b7f59
Add native IK-style capture processing, Artifact Inventory, explicit promotion and single-result review. Confirm the three live OCR corrections in the eval corpus and preserve extraction/value separation.
266 lines
9.5 KiB
PowerShell
266 lines
9.5 KiB
PowerShell
param(
|
|
[string]$BaseUrl = "http://127.0.0.1:17317",
|
|
[int]$Limit = 2,
|
|
[ValidateSet("artifacts", "weapons", "characters", "materials")]
|
|
[string]$Category = "artifacts",
|
|
[int]$PollIntervalSeconds = 1,
|
|
[int]$TimeoutSeconds = 180,
|
|
[string]$OutputRoot = (Join-Path (Resolve-Path -LiteralPath ".").Path "outputs\native-live-smoke"),
|
|
[switch]$SkipProbe,
|
|
[switch]$Persist
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
function ConvertTo-SafeFilePart([string]$Value) {
|
|
$safe = $Value -replace "[^A-Za-z0-9._-]+", "-"
|
|
$safe = $safe.Trim("-")
|
|
if ($safe.Length -eq 0) { return "item" }
|
|
if ($safe.Length -gt 80) { return $safe.Substring(0, 80) }
|
|
return $safe
|
|
}
|
|
|
|
function Invoke-DevJson([string]$Path) {
|
|
$uri = if ($Path.StartsWith("http")) { $Path } else { "$BaseUrl$Path" }
|
|
try {
|
|
Invoke-RestMethod -Method Get -Uri $uri -TimeoutSec 90
|
|
} catch {
|
|
$response = $_.Exception.Response
|
|
if ($response) {
|
|
$stream = $response.GetResponseStream()
|
|
if ($stream) {
|
|
$reader = New-Object System.IO.StreamReader($stream)
|
|
$body = $reader.ReadToEnd()
|
|
if (-not [string]::IsNullOrWhiteSpace($body)) {
|
|
try {
|
|
return $body | ConvertFrom-Json
|
|
} catch {
|
|
throw "Dev endpoint $uri returned HTTP error with non-JSON body: $body"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
throw
|
|
}
|
|
}
|
|
|
|
function Save-Json([string]$Name, [object]$Payload) {
|
|
$path = Join-Path $RunDir "$(ConvertTo-SafeFilePart $Name).json"
|
|
$Payload | ConvertTo-Json -Depth 40 | Set-Content -LiteralPath $path -Encoding UTF8
|
|
return $path
|
|
}
|
|
|
|
function Test-ProbeSucceeded([object]$ProbePayload) {
|
|
if ($ProbePayload.ok) { return $true }
|
|
if ($ProbePayload.changed) { return $true }
|
|
if ($ProbePayload.click -and $ProbePayload.click.clicked -and $ProbePayload.click.moved -and -not $ProbePayload.click.inputBlocked) { return $true }
|
|
return $false
|
|
}
|
|
|
|
function Get-NativeScanner([object]$StatusPayload) {
|
|
if ($StatusPayload.nativeScanner) { return $StatusPayload.nativeScanner }
|
|
if ($StatusPayload.scanner) { return $StatusPayload.scanner }
|
|
return $null
|
|
}
|
|
|
|
function Get-ExpectedAppSignature() {
|
|
$mainPath = Join-Path (Resolve-Path -LiteralPath ".").Path "electron\main.ts"
|
|
$mainSource = Get-Content -LiteralPath $mainPath -Raw
|
|
$match = [regex]::Match($mainSource, 'APP_RUNTIME_SIGNATURE\s*=\s*"([^"]+)"')
|
|
if (-not $match.Success) {
|
|
throw "APP_RUNTIME_SIGNATURE not found in electron/main.ts."
|
|
}
|
|
return $match.Groups[1].Value
|
|
}
|
|
|
|
function UriEscape([string]$Value) {
|
|
return [System.Uri]::EscapeDataString($Value)
|
|
}
|
|
|
|
$safeLimit = [Math]::Max(1, [Math]::Min(100, $Limit))
|
|
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
|
$RunDir = Join-Path $OutputRoot $timestamp
|
|
New-Item -ItemType Directory -Force -Path $RunDir | Out-Null
|
|
|
|
$summary = [ordered]@{
|
|
ok = $false
|
|
createdAt = (Get-Date).ToString("o")
|
|
limit = $safeLimit
|
|
category = $Category
|
|
expectedSignature = ""
|
|
outputDir = $RunDir
|
|
persistRequested = [bool]$Persist
|
|
health = $null
|
|
data = $null
|
|
preflight = $null
|
|
probe = $null
|
|
finalStatus = $null
|
|
process = $null
|
|
results = $null
|
|
errors = @()
|
|
}
|
|
|
|
try {
|
|
$expectedSignature = Get-ExpectedAppSignature
|
|
$summary.expectedSignature = $expectedSignature
|
|
$health = Invoke-DevJson "/health"
|
|
$summary.health = @{
|
|
ok = [bool]$health.ok
|
|
signature = if ($health.appBuild) { [string]$health.appBuild.signature } else { "" }
|
|
}
|
|
Save-Json "01-health" $health | Out-Null
|
|
if (-not $health.appBuild -or [string]$health.appBuild.signature -ne $expectedSignature) {
|
|
throw "Dev endpoint is stale: /health signature '$($summary.health.signature)' does not match source '$expectedSignature'. Restart the elevated app."
|
|
}
|
|
|
|
$data = Invoke-DevJson "/scanner/native/data"
|
|
$summary.data = @{
|
|
ok = [bool]$data.ok
|
|
version = [string]$data.status.version
|
|
totalEntries = [int]$data.status.totalEntries
|
|
valid = [bool]$data.status.valid
|
|
}
|
|
Save-Json "02-native-data" $data | Out-Null
|
|
if (-not $data.ok) {
|
|
throw "Native IK data check failed."
|
|
}
|
|
|
|
$preflight = Invoke-DevJson "/scanner/native/preflight?category=$Category"
|
|
$summary.preflight = @{
|
|
ok = [bool]$preflight.ok
|
|
ready = [bool]$preflight.status.ready
|
|
category = [string]$preflight.status.category
|
|
categoryReady = [bool]$preflight.status.categoryReady
|
|
genshinFound = [bool]$preflight.status.genshinFound
|
|
isSixteenNine = [bool]$preflight.status.isSixteenNine
|
|
gridCount = [int]$preflight.status.grid.count
|
|
visualReady = if ($preflight.status.visual) { [bool]$preflight.status.visual.ready } else { $null }
|
|
visualWhitePct = if ($preflight.status.visual) { [double]$preflight.status.visual.whitePct } else { $null }
|
|
visualDarkPct = if ($preflight.status.visual) { [double]$preflight.status.visual.darkPct } else { $null }
|
|
visualColorPct = if ($preflight.status.visual) { [double]$preflight.status.visual.colorPct } else { $null }
|
|
visualLumaStdDev = if ($preflight.status.visual) { [double]$preflight.status.visual.lumaStdDev } else { $null }
|
|
blockReason = [string]$preflight.status.blockReason
|
|
}
|
|
Save-Json "03-native-preflight" $preflight | Out-Null
|
|
if (-not $preflight.ok) {
|
|
$preflightReason = if ($preflight.status.blockReason) { [string]$preflight.status.blockReason } else { "unknown preflight block reason" }
|
|
throw "Native scanner preflight is not ready for category '$Category': $preflightReason"
|
|
}
|
|
|
|
if (-not $SkipProbe) {
|
|
$probe = Invoke-DevJson "/automation/probe-click?index=1"
|
|
$summary.probe = @{
|
|
ok = [bool](Test-ProbeSucceeded $probe)
|
|
endpointOk = [bool]$probe.ok
|
|
changed = [bool]$probe.changed
|
|
}
|
|
Save-Json "04-probe-click" $probe | Out-Null
|
|
if (-not (Test-ProbeSucceeded $probe)) {
|
|
throw "Probe click did not prove safe input delivery."
|
|
}
|
|
}
|
|
|
|
$start = Invoke-DevJson "/scanner/start?limit=$safeLimit&category=$Category"
|
|
Save-Json "05-native-start" $start | Out-Null
|
|
$startedScanner = Get-NativeScanner $start
|
|
if ($null -eq $startedScanner) {
|
|
throw "Native scanner start returned no scanner payload."
|
|
}
|
|
|
|
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
|
$polls = @()
|
|
do {
|
|
Start-Sleep -Seconds $PollIntervalSeconds
|
|
$status = Invoke-DevJson "/scanner/status"
|
|
$scanner = Get-NativeScanner $status
|
|
$polls += [pscustomobject]@{
|
|
at = (Get-Date).ToString("o")
|
|
status = if ($scanner) { [string]$scanner.status } else { "missing" }
|
|
running = if ($scanner) { [bool]$scanner.running } else { $false }
|
|
captured = if ($scanner) { [int]$scanner.captured } else { 0 }
|
|
activeMs = if ($scanner) { [int]$scanner.activeMs } else { 0 }
|
|
message = if ($scanner) { [string]$scanner.message } else { "missing native scanner payload" }
|
|
}
|
|
if ($null -ne $scanner -and -not $scanner.running) {
|
|
break
|
|
}
|
|
} while ((Get-Date) -lt $deadline)
|
|
|
|
Save-Json "06-native-polls" $polls | Out-Null
|
|
$final = Invoke-DevJson "/scanner/status"
|
|
Save-Json "07-native-final-status" $final | Out-Null
|
|
$finalScanner = Get-NativeScanner $final
|
|
if ($null -eq $finalScanner) {
|
|
throw "Native scanner final status returned no scanner payload."
|
|
}
|
|
|
|
$summary.finalStatus = @{
|
|
status = [string]$finalScanner.status
|
|
running = [bool]$finalScanner.running
|
|
runDir = [string]$finalScanner.runDir
|
|
captured = [int]$finalScanner.captured
|
|
clicked = [int]$finalScanner.clicked
|
|
pages = [int]$finalScanner.pages
|
|
activeMs = [int]$finalScanner.activeMs
|
|
message = [string]$finalScanner.message
|
|
}
|
|
|
|
if ($finalScanner.running) {
|
|
throw "Native scanner did not finish before timeout."
|
|
}
|
|
if ([string]$finalScanner.status -ne "done") {
|
|
throw "Native scanner finished with status '$($finalScanner.status)': $($finalScanner.message)"
|
|
}
|
|
if ([int]$finalScanner.captured -lt $safeLimit) {
|
|
throw "Native scanner captured $($finalScanner.captured), expected at least $safeLimit."
|
|
}
|
|
|
|
$runDirParam = UriEscape([string]$finalScanner.runDir)
|
|
$persistParam = if ($Persist) { "1" } else { "0" }
|
|
$process = Invoke-DevJson "/scanner/native/process?runDir=$runDirParam&limit=$safeLimit&persist=$persistParam"
|
|
Save-Json "08-native-process" $process | Out-Null
|
|
$summary.process = @{
|
|
ok = [bool]$process.ok
|
|
processed = [int]$process.status.processed
|
|
parsed = [int]$process.status.parsed
|
|
review = [int]$process.status.review
|
|
errors = [int]$process.status.errors
|
|
stored = [int]$process.status.stored
|
|
persisted = [bool]$process.status.persisted
|
|
queueConcurrency = [int]$process.status.queueConcurrency
|
|
elapsedMs = [int]$process.status.elapsedMs
|
|
}
|
|
if (-not $process.ok) {
|
|
throw "Native post-capture processing failed."
|
|
}
|
|
|
|
$results = Invoke-DevJson "/scanner/native/results?runDir=$runDirParam&limit=$safeLimit"
|
|
Save-Json "09-native-results" $results | Out-Null
|
|
$summary.results = @{
|
|
ok = [bool]$results.ok
|
|
total = [int]$results.status.total
|
|
loaded = [int]$results.status.results.Count
|
|
}
|
|
if (-not $results.ok) {
|
|
throw "Native scan results could not be loaded."
|
|
}
|
|
|
|
$summary.ok = $true
|
|
} catch {
|
|
$summary.errors += [string]$_.Exception.Message
|
|
throw
|
|
} finally {
|
|
$summaryPath = Save-Json "native-live-smoke-summary" ([pscustomobject]$summary)
|
|
Write-Host "Native live smoke summary: $summaryPath"
|
|
if ($summary.ok) {
|
|
Write-Host ("ok limit={0} captured={1} parsed={2} review={3} errors={4} stored={5} persisted={6}" -f `
|
|
$summary.limit,
|
|
$summary.finalStatus.captured,
|
|
$summary.process.parsed,
|
|
$summary.process.review,
|
|
$summary.process.errors,
|
|
$summary.process.stored,
|
|
$summary.process.persisted)
|
|
}
|
|
}
|