Files
isphere-ai-bridge/scripts/validate-returned-send-file-sandbox-package.ps1
2026-07-11 00:08:56 +08:00

224 lines
11 KiB
PowerShell

param(
[string]$PackagePath = "",
[string]$ZipPath = "",
[string]$WorkDir = "",
[switch]$Strict
)
$ErrorActionPreference = "Stop"
Set-StrictMode -Version Latest
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
}
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
}
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-file-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 "FILE-SEND-INPUTS*.json" } | Sort-Object FullName | Select-Object -First 1
if (-not $inputsFile) {
throw "missing FILE-SEND-INPUTS json"
}
$inputs = Get-Content -LiteralPath $inputsFile.FullName -Raw | ConvertFrom-Json
$capabilityLog = $allFiles | Where-Object { $_.Name -like "*capability-smoke*.txt" -or $_.Name -like "*capability*.log" } | Sort-Object FullName | Select-Object -First 1
$capability = $null
if ($capabilityLog) {
$capability = Read-FirstJsonLine $capabilityLog.FullName
}
$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*" })
$targetTypePresent = Test-NonEmptyString (Get-JsonPropertyValue $inputs "sandbox_target_type")
$targetIDPresent = Test-NonEmptyString (Get-JsonPropertyValue $inputs "sandbox_target_id")
$allowedDirPresent = Test-NonEmptyString (Get-JsonPropertyValue $inputs "ISPHERE_SEND_FILE_ALLOWED_DIR")
$filePathPresent = Test-NonEmptyString (Get-JsonPropertyValue $inputs "sandbox_file_path")
$fileHash = [string](Get-JsonPropertyValue $inputs "file_sha256")
$fileHashPresent = ($fileHash -match "^[0-9a-f]{64}$")
$fileSize = Get-JsonPropertyValue $inputs "file_size_bytes"
$fileSizePresent = ($null -ne $fileSize -and [int64]$fileSize -gt 0)
$idempotencyKeyPresent = Test-NonEmptyString (Get-JsonPropertyValue $inputs "idempotency_key")
$operatorStartedAtLocalPresent = Test-NonEmptyString (Get-JsonPropertyValue $inputs "operator_started_at_local")
$operatorClickedSendFileAtLocalPresent = Test-NonEmptyString (Get-JsonPropertyValue $inputs "operator_clicked_send_file_at_local")
$operatorObservedFileSuccessAtLocalPresent = Test-NonEmptyString (Get-JsonPropertyValue $inputs "operator_observed_file_success_at_local")
$manualStartPresent = Test-NonEmptyString (Get-JsonPropertyValue $inputs "manual_file_send_started_at_local")
$manualFinishPresent = Test-NonEmptyString (Get-JsonPropertyValue $inputs "manual_file_send_finished_at_local")
$operatorObservedFileSuccess = ((Get-JsonPropertyValue $inputs "operator_observed_file_success") -eq $true)
$fileUploadOrSentRecordPresent = Test-NonEmptyString (Get-JsonPropertyValue $inputs "file_upload_or_sent_record")
$fileUploadOrSentRecordPresentFlag = ((Get-JsonPropertyValue $inputs "file_upload_or_sent_record_present") -eq $true)
$beforeRecorderPresentFlag = ((Get-JsonPropertyValue $inputs "before_recorder_present") -eq $true)
$afterRecorderPresentFlag = ((Get-JsonPropertyValue $inputs "after_recorder_present") -eq $true)
$duplicateSecondFileSendAttemptedFalse = ((Get-JsonPropertyValue $inputs "duplicate_second_file_send_attempted") -eq $false)
$doNotSendSecondFileConfirmed = ((Get-JsonPropertyValue $inputs "do_not_send_second_file_confirmed") -eq $true)
$productionFileSendEnabledFalse = ((Get-JsonPropertyValue $inputs "production_file_send_enabled") -eq $false)
$capabilityOK = $false
$capabilityToolCount = 0
$capabilitySendFilePreview = $false
$capabilityProductionFileEnabled = $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.send_file_preview_tool_present) {
$capabilitySendFilePreview = [bool]$capability.send_file_preview_tool_present
}
if ($null -ne $capability.send_file_production_enabled) {
$capabilityProductionFileEnabled = [bool]$capability.send_file_production_enabled
}
}
$hasBeforeProbe = ($beforeMarkers.Count -gt 0)
$hasAfterProbe = ($afterMarkers.Count -gt 0)
$missing = New-Object System.Collections.Generic.List[string]
if (-not $targetTypePresent) { $missing.Add("sandbox_target_type") }
if (-not $targetIDPresent) { $missing.Add("sandbox_target_id") }
if (-not $allowedDirPresent) { $missing.Add("ISPHERE_SEND_FILE_ALLOWED_DIR") }
if (-not $filePathPresent) { $missing.Add("sandbox_file_path") }
if (-not $fileHashPresent) { $missing.Add("file_sha256") }
if (-not $fileSizePresent) { $missing.Add("file_size_bytes") }
if (-not $idempotencyKeyPresent) { $missing.Add("idempotency_key") }
if (-not $operatorStartedAtLocalPresent) { $missing.Add("operator_started_at_local") }
if (-not $operatorClickedSendFileAtLocalPresent) { $missing.Add("operator_clicked_send_file_at_local") }
if (-not $operatorObservedFileSuccessAtLocalPresent) { $missing.Add("operator_observed_file_success_at_local") }
if (-not $manualStartPresent) { $missing.Add("manual_file_send_started_at_local") }
if (-not $manualFinishPresent) { $missing.Add("manual_file_send_finished_at_local") }
if (-not $operatorObservedFileSuccess) { $missing.Add("operator_observed_file_success") }
if (-not $fileUploadOrSentRecordPresent) { $missing.Add("file_upload_or_sent_record") }
if (-not $fileUploadOrSentRecordPresentFlag) { $missing.Add("file_upload_or_sent_record_present") }
if (-not $beforeRecorderPresentFlag) { $missing.Add("before_recorder_present") }
if (-not $afterRecorderPresentFlag) { $missing.Add("after_recorder_present") }
if (-not $duplicateSecondFileSendAttemptedFalse) { $missing.Add("duplicate_second_file_send_attempted_false") }
if (-not $doNotSendSecondFileConfirmed) { $missing.Add("do_not_send_second_file_confirmed") }
if (-not $productionFileSendEnabledFalse) { $missing.Add("production_file_send_enabled_false") }
if (-not $hasBeforeProbe) { $missing.Add("before_recorder_output") }
if (-not $hasAfterProbe) { $missing.Add("after_recorder_output") }
$strictPass = (
$targetTypePresent -and
$targetIDPresent -and
$allowedDirPresent -and
$filePathPresent -and
$fileHashPresent -and
$fileSizePresent -and
$idempotencyKeyPresent -and
$operatorStartedAtLocalPresent -and
$operatorClickedSendFileAtLocalPresent -and
$operatorObservedFileSuccessAtLocalPresent -and
$manualStartPresent -and
$manualFinishPresent -and
$operatorObservedFileSuccess -and
$fileUploadOrSentRecordPresent -and
$fileUploadOrSentRecordPresentFlag -and
$beforeRecorderPresentFlag -and
$afterRecorderPresentFlag -and
$duplicateSecondFileSendAttemptedFalse -and
$doNotSendSecondFileConfirmed -and
$productionFileSendEnabledFalse -and
$hasBeforeProbe -and
$hasAfterProbe
)
$summary = [ordered]@{
ok = $true
package = $resolvedPackage
input_file_name = $inputsFile.Name
strict_gate_pass = $strictPass
target_type_present = $targetTypePresent
target_id_present = $targetIDPresent
allowed_dir_present = $allowedDirPresent
sandbox_file_path_present = $filePathPresent
file_sha256_present = $fileHashPresent
file_size_bytes_present = $fileSizePresent
idempotency_key_present = $idempotencyKeyPresent
operator_started_at_local_present = $operatorStartedAtLocalPresent
operator_clicked_send_file_at_local_present = $operatorClickedSendFileAtLocalPresent
operator_observed_file_success_at_local_present = $operatorObservedFileSuccessAtLocalPresent
manual_file_send_started_at_local_present = $manualStartPresent
manual_file_send_finished_at_local_present = $manualFinishPresent
operator_observed_file_success = $operatorObservedFileSuccess
file_upload_or_sent_record_present = $fileUploadOrSentRecordPresent
file_upload_or_sent_record_present_flag = $fileUploadOrSentRecordPresentFlag
before_recorder_present_flag = $beforeRecorderPresentFlag
after_recorder_present_flag = $afterRecorderPresentFlag
duplicate_second_file_send_attempted_false = $duplicateSecondFileSendAttemptedFalse
do_not_send_second_file_confirmed = $doNotSendSecondFileConfirmed
production_file_send_enabled = -not $productionFileSendEnabledFalse
has_before_probe = $hasBeforeProbe
has_after_probe = $hasAfterProbe
capability_test_ok = $capabilityOK
capability_tool_count = $capabilityToolCount
capability_send_file_preview_tool_present = $capabilitySendFilePreview
capability_send_file_production_enabled = $capabilityProductionFileEnabled
missing_for_full_gate = @($missing)
decision = if ($strictPass) { "strict_file_gate_pass" } else { "not_usable_for_production_file_send" }
}
$summary | ConvertTo-Json -Depth 6 -Compress
if ($Strict -and -not $strictPass) {
exit 1
}
}
finally {
if ($cleanup -and (Test-Path -LiteralPath $WorkDir)) {
Remove-Item -LiteralPath $WorkDir -Recurse -Force
}
}