222 lines
9.4 KiB
PowerShell
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
|
|
}
|
|
}
|