Files
isphere-ai-bridge/scripts/validate-returned-send-sandbox-package.ps1
2026-07-10 20:51:50 +08:00

222 lines
9.4 KiB
PowerShell

param(
[string]$PackagePath = "",
[string]$ZipPath = "",
[string]$WorkDir = "",
[switch]$StrictV2
)
$ErrorActionPreference = "Stop"
Set-StrictMode -Version Latest
function ConvertTo-Sha256Hex([string]$Text) {
$bytes = [System.Text.Encoding]::UTF8.GetBytes($Text)
$sha = [System.Security.Cryptography.SHA256]::Create()
try {
return -join ($sha.ComputeHash($bytes) | ForEach-Object { $_.ToString("x2") })
}
finally {
$sha.Dispose()
}
}
function Read-FirstJsonLine([string]$Path) {
if (-not (Test-Path -LiteralPath $Path)) {
return $null
}
foreach ($line in Get-Content -LiteralPath $Path) {
$trimmed = $line.Trim()
if ($trimmed.StartsWith("{") -and $trimmed.EndsWith("}")) {
return ($trimmed | ConvertFrom-Json)
}
}
return $null
}
function Test-NonEmptyString($Value) {
if ($null -eq $Value) {
return $false
}
return -not [string]::IsNullOrWhiteSpace([string]$Value)
}
function Get-JsonPropertyValue($Object, [string]$Name) {
if ($null -eq $Object) {
return $null
}
$property = $Object.PSObject.Properties[$Name]
if ($null -eq $property) {
return $null
}
return $property.Value
}
if (-not [string]::IsNullOrWhiteSpace($ZipPath)) {
$PackagePath = $ZipPath
}
if ([string]::IsNullOrWhiteSpace($PackagePath)) {
throw "PackagePath or ZipPath is required"
}
if (-not (Test-Path -LiteralPath $PackagePath)) {
throw "package not found: $PackagePath"
}
$resolvedPackage = (Resolve-Path -LiteralPath $PackagePath).Path
$cleanup = $false
if ([string]::IsNullOrWhiteSpace($WorkDir)) {
$WorkDir = Join-Path ([System.IO.Path]::GetTempPath()) ("isphere-returned-send-sandbox-" + [guid]::NewGuid().ToString("N"))
$cleanup = $true
}
if (Test-Path -LiteralPath $WorkDir) {
Remove-Item -LiteralPath $WorkDir -Recurse -Force
}
New-Item -ItemType Directory -Force -Path $WorkDir | Out-Null
try {
if ($resolvedPackage.EndsWith(".zip", [System.StringComparison]::OrdinalIgnoreCase)) {
Expand-Archive -LiteralPath $resolvedPackage -DestinationPath $WorkDir -Force
$root = $WorkDir
}
else {
$root = $resolvedPackage
}
$allFiles = Get-ChildItem -LiteralPath $root -Recurse -File
$inputsFile = $allFiles | Where-Object { $_.Name -like "SANDBOX-SEND-INPUTS*.json" } | Sort-Object FullName | Select-Object -First 1
if (-not $inputsFile) {
throw "missing SANDBOX-SEND-INPUTS json"
}
$inputs = Get-Content -LiteralPath $inputsFile.FullName -Raw | ConvertFrom-Json
$capabilityLog = $allFiles | Where-Object { $_.Name -like "*capability-test*.txt" -or $_.Name -like "*capability*.log" } | Sort-Object FullName | Select-Object -First 1
$capability = $null
if ($capabilityLog) {
$capability = Read-FirstJsonLine $capabilityLog.FullName
}
$liveProbeZips = @($allFiles | Where-Object { $_.Name -like "isphere-live-probe-*.zip" })
$beforeMarkers = @($allFiles | Where-Object { $_.FullName -match "\\before\\" -or $_.FullName -match "/before/" -or $_.Name -like "*before*" })
$afterMarkers = @($allFiles | Where-Object { $_.FullName -match "\\after\\" -or $_.FullName -match "/after/" -or $_.Name -like "*after*" })
$contentText = [string]$inputs.content_text_to_send
$expectedHash = [string]$inputs.content_sha256
$actualHash = ConvertTo-Sha256Hex $contentText
$contentHashMatches = ($actualHash -eq $expectedHash)
$capabilityOK = $false
$capabilityToolCount = 0
$productionSendEnabled = $false
if ($null -ne $capability) {
$capabilityOK = ($capability.ok -eq $true)
if ($null -ne $capability.tool_count) {
$capabilityToolCount = [int]$capability.tool_count
}
if ($null -ne $capability.production_send_enabled) {
$productionSendEnabled = [bool]$capability.production_send_enabled
}
}
$hasExplicitTarget = (Test-NonEmptyString $inputs.sandbox_target_type) -and (Test-NonEmptyString $inputs.sandbox_target_id)
$hasIdempotencyKey = Test-NonEmptyString $inputs.idempotency_key
$hasSuccessRecord = Test-NonEmptyString $inputs.success_ack_or_sent_record
$manualStartPresent = Test-NonEmptyString $inputs.manual_send_started_at_local
$manualFinishPresent = Test-NonEmptyString $inputs.manual_send_finished_at_local
$operatorSuccess = ($inputs.operator_observed_success -eq $true)
$noSecondSend = ($inputs.do_not_send_second_time_confirmed -eq $true)
$hasBeforeProbe = ($liveProbeZips.Count -gt 0 -or $beforeMarkers.Count -gt 0)
$hasAfterProbe = ($afterMarkers.Count -gt 0)
$operatorStartedAtLocalPresent = Test-NonEmptyString (Get-JsonPropertyValue $inputs "operator_started_at_local")
$operatorClickedSendAtLocalPresent = Test-NonEmptyString (Get-JsonPropertyValue $inputs "operator_clicked_send_at_local")
$operatorObservedSuccessAtLocalPresent = Test-NonEmptyString (Get-JsonPropertyValue $inputs "operator_observed_success_at_local")
$beforeRecorderPresentFlag = ((Get-JsonPropertyValue $inputs "before_recorder_present") -eq $true)
$afterRecorderPresentFlag = ((Get-JsonPropertyValue $inputs "after_recorder_present") -eq $true)
$successAckOrSentRecordPresentFlag = ((Get-JsonPropertyValue $inputs "success_ack_or_sent_record_present") -eq $true)
$duplicateSecondSendAttemptedValue = Get-JsonPropertyValue $inputs "duplicate_second_send_attempted"
$duplicateSecondSendAttemptedFalse = ($null -ne $duplicateSecondSendAttemptedValue -and $duplicateSecondSendAttemptedValue -eq $false)
$partialUsable = (
$capabilityOK -and
$capabilityToolCount -eq 9 -and
-not $productionSendEnabled -and
$hasExplicitTarget -and
$contentHashMatches -and
$hasIdempotencyKey -and
$operatorSuccess -and
$hasSuccessRecord -and
$noSecondSend -and
$hasBeforeProbe
)
$r6dGatePass = (
$partialUsable -and
$manualStartPresent -and
$manualFinishPresent -and
$hasAfterProbe
)
$missing = New-Object System.Collections.Generic.List[string]
if (-not $manualStartPresent) { $missing.Add("manual_send_started_at_local") }
if (-not $manualFinishPresent) { $missing.Add("manual_send_finished_at_local") }
if (-not $hasAfterProbe) { $missing.Add("after_recorder_output") }
if (-not $operatorStartedAtLocalPresent) { $missing.Add("operator_started_at_local") }
if (-not $operatorClickedSendAtLocalPresent) { $missing.Add("operator_clicked_send_at_local") }
if (-not $operatorObservedSuccessAtLocalPresent) { $missing.Add("operator_observed_success_at_local") }
if (-not $beforeRecorderPresentFlag) { $missing.Add("before_recorder_present") }
if (-not $afterRecorderPresentFlag) { $missing.Add("after_recorder_present") }
if (-not $successAckOrSentRecordPresentFlag) { $missing.Add("success_ack_or_sent_record_present") }
if (-not $duplicateSecondSendAttemptedFalse) { $missing.Add("duplicate_second_send_attempted_false") }
$strictV2Pass = (
$r6dGatePass -and
$operatorStartedAtLocalPresent -and
$operatorClickedSendAtLocalPresent -and
$operatorObservedSuccessAtLocalPresent -and
$beforeRecorderPresentFlag -and
$afterRecorderPresentFlag -and
$successAckOrSentRecordPresentFlag -and
$duplicateSecondSendAttemptedFalse
)
$summary = [ordered]@{
ok = $true
package = $resolvedPackage
input_file_name = $inputsFile.Name
partial_evidence_usable = $partialUsable
r6d_gate_pass = $r6dGatePass
strict_v2_pass = $strictV2Pass
target_type_present = Test-NonEmptyString $inputs.sandbox_target_type
target_id_present = Test-NonEmptyString $inputs.sandbox_target_id
content_hash_matches = $contentHashMatches
idempotency_key_present = $hasIdempotencyKey
operator_observed_success = $operatorSuccess
operator_started_at_local_present = $operatorStartedAtLocalPresent
operator_clicked_send_at_local_present = $operatorClickedSendAtLocalPresent
operator_observed_success_at_local_present = $operatorObservedSuccessAtLocalPresent
before_recorder_present_flag = $beforeRecorderPresentFlag
after_recorder_present_flag = $afterRecorderPresentFlag
success_ack_or_sent_record_present_flag = $successAckOrSentRecordPresentFlag
duplicate_second_send_attempted_false = $duplicateSecondSendAttemptedFalse
success_ack_or_sent_record_present = $hasSuccessRecord
do_not_send_second_time_confirmed = $noSecondSend
manual_send_started_at_local_present = $manualStartPresent
manual_send_finished_at_local_present = $manualFinishPresent
capability_test_ok = $capabilityOK
capability_tool_count = $capabilityToolCount
production_send_enabled = $productionSendEnabled
has_before_probe = $hasBeforeProbe
has_after_probe = $hasAfterProbe
live_probe_zip_count = $liveProbeZips.Count
missing_for_full_gate = @($missing)
decision = if ($strictV2Pass) { "strict_v2_gate_pass" } elseif ($r6dGatePass) { "full_gate_pass" } elseif ($partialUsable) { "partial_use_only" } else { "not_usable" }
}
$summary | ConvertTo-Json -Depth 6 -Compress
if ($StrictV2 -and -not $strictV2Pass) {
exit 1
}
}
finally {
if ($cleanup -and (Test-Path -LiteralPath $WorkDir)) {
Remove-Item -LiteralPath $WorkDir -Recurse -Force
}
}