param( [string]$OutputDir = "runs/send-sandbox-gate-package", [switch]$NoZip ) $ErrorActionPreference = "Stop" Set-StrictMode -Version Latest $repo = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "..")).Path $packageDir = Join-Path $repo $OutputDir $recorderRelative = Join-Path $OutputDir "recorder" $recorderDir = Join-Path $packageDir "recorder" $zipPath = "$packageDir.zip" if (Test-Path -LiteralPath $packageDir) { Remove-Item -LiteralPath $packageDir -Recurse -Force } if (Test-Path -LiteralPath $zipPath) { Remove-Item -LiteralPath $zipPath -Force } New-Item -ItemType Directory -Force -Path $packageDir | Out-Null & powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $repo "scripts\package-live-probe-recorder.ps1") -OutputDir $recorderRelative -NoZip if ($LASTEXITCODE -ne 0) { throw "package-live-probe-recorder.ps1 failed with exit code $LASTEXITCODE" } if (-not (Test-Path -LiteralPath (Join-Path $recorderDir "ISphereLiveProbeRecorder.exe"))) { throw "recorder package was not created under $recorderDir" } @' R6c iSphere Send Sandbox Gate Package LOCAL_LOGIN_UNAVAILABLE: the development machine cannot log in to iSphere. ONLINE_SANDBOX_REQUIRED: run this package only on a machine/network where iSphere/IMPlatformClient can log in. Goal: - collect one operator-approved sandbox-send evidence package; - keep production send disabled in the repository; - return enough evidence to decide whether production send work can start later. What this package does: - runs the read-only live probe recorder before the manual sandbox send; - asks the operator to manually send exactly one sandbox message in the official client; - runs the same read-only recorder after the manual send; - zips the returned evidence folder. - provides RUN-RECORDING-SUITE.bat plus numbered 01/02/03 batch files so the operator can run the whole recording flow from this package. What this package does not do: - it does not send messages automatically; - it does not click or type; - it does not upload files; - it does not hook or inject into the client; - it does not enable production send. Required rule: - MANUAL_ONE_SEND_ONLY: send exactly one sandbox message manually. - DO_NOT_SEND_SECOND_TIME: do not send a second message with the same content or idempotency key. - Record content_sha256 and idempotency_key in SANDBOX-SEND-INPUTS.json. - Fill success_ack_or_sent_record after the operator sees the sent message, ack, or local sent record. Return: - run CREATE-RETURN-ZIP.ps1 and send back the generated zip. - preferred one-file flow: run RUN-RECORDING-SUITE.bat and follow the pauses. '@ | Set-Content -LiteralPath (Join-Path $packageDir "README.txt") -Encoding UTF8 @' # R6c Online Sandbox Send Gate - Operator Steps This package is for an online/internal sandbox only. Markers: - LOCAL_LOGIN_UNAVAILABLE: do not run this on the local development machine for proof. - ONLINE_SANDBOX_REQUIRED: run on a machine where iSphere/IMPlatformClient can log in. - MANUAL_ONE_SEND_ONLY: send exactly one test message manually. - DO_NOT_SEND_SECOND_TIME: do not repeat the send with the same idempotency_key. ## Step 1 - Prepare inputs 1. Copy `SANDBOX-SEND-INPUTS.template.json` to `SANDBOX-SEND-INPUTS.json`. 2. Fill: - `sandbox_target_type` - `sandbox_target_id` - `content_text_to_send` - `idempotency_key` 3. Run: ```powershell powershell -NoProfile -ExecutionPolicy Bypass -File .\compute-content-hash.ps1 -Text "" ``` 4. Copy the printed SHA256 into `content_sha256`. ## Step 2 - Before-send recording 1. Open and log in to iSphere/IMPlatformClient normally. 2. Open the sandbox target conversation. 3. Do not send anything yet. 4. Run `run-before-recorder.bat`. Alternative one-file flow: Run `RUN-RECORDING-SUITE.bat`. It will run the before recorder, pause for the manual one-send action, run the after recorder, and then call the return-zip script. ## Step 3 - Manual one-send action 1. In the official iSphere client, manually send exactly the `content_text_to_send`. 2. Send it only to the declared sandbox target. 3. Do not send a second copy. 4. Fill these fields in `SANDBOX-SEND-INPUTS.json`: - `operator_started_at_local` - `operator_clicked_send_at_local` - `operator_observed_success_at_local` - `before_recorder_present` - `after_recorder_present` - `success_ack_or_sent_record_present` - `duplicate_second_send_attempted` - `manual_send_started_at_local` - `manual_send_finished_at_local` - `operator_observed_success` - `success_ack_or_sent_record` - `do_not_send_second_time_confirmed` ## Step 4 - After-send recording Run `run-after-recorder.bat`. ## Step 5 - Return package Run: ```powershell powershell -NoProfile -ExecutionPolicy Bypass -File .\CREATE-RETURN-ZIP.ps1 ``` Send back the generated zip file. You can also run `03-make-return-zip.bat`. ## Required returned evidence The returned evidence should contain: - before recorder output; - after recorder output; - completed `SANDBOX-SEND-INPUTS.json`; - success_ack_or_sent_record text; - confirmation that there was no second send. - strict-v2 fields proving before/after recorder presence and manual send timestamps. The repository must keep production send disabled until this evidence is reviewed. '@ | Set-Content -LiteralPath (Join-Path $packageDir "OPERATOR-STEPS.md") -Encoding UTF8 @' { "gate": "R6c online sandbox-send evidence gate", "LOCAL_LOGIN_UNAVAILABLE": true, "ONLINE_SANDBOX_REQUIRED": true, "MANUAL_ONE_SEND_ONLY": true, "DO_NOT_SEND_SECOND_TIME": true, "sandbox_target_type": "direct-or-group", "sandbox_target_id": "", "content_text_to_send": "", "content_sha256": "", "idempotency_key": "", "operator_started_at_local": "", "operator_clicked_send_at_local": "", "operator_observed_success_at_local": "", "before_recorder_present": false, "after_recorder_present": false, "success_ack_or_sent_record_present": false, "duplicate_second_send_attempted": false, "manual_send_started_at_local": "", "manual_send_finished_at_local": "", "operator_observed_success": false, "success_ack_or_sent_record": "", "do_not_send_second_time_confirmed": false, "notes": "" } '@ | Set-Content -LiteralPath (Join-Path $packageDir "SANDBOX-SEND-INPUTS.template.json") -Encoding UTF8 @' { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "iSphere R6j sandbox send inputs", "type": "object", "required": [ "sandbox_target_type", "sandbox_target_id", "content_text_to_send", "content_sha256", "idempotency_key", "operator_started_at_local", "operator_clicked_send_at_local", "operator_observed_success_at_local", "before_recorder_present", "after_recorder_present", "success_ack_or_sent_record_present", "duplicate_second_send_attempted" ], "properties": { "sandbox_target_type": { "type": "string" }, "sandbox_target_id": { "type": "string" }, "content_text_to_send": { "type": "string" }, "content_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, "idempotency_key": { "type": "string" }, "operator_started_at_local": { "type": "string" }, "operator_clicked_send_at_local": { "type": "string" }, "operator_observed_success_at_local": { "type": "string" }, "before_recorder_present": { "type": "boolean" }, "after_recorder_present": { "type": "boolean" }, "success_ack_or_sent_record_present": { "type": "boolean" }, "duplicate_second_send_attempted": { "type": "boolean" } } } '@ | Set-Content -LiteralPath (Join-Path $packageDir "SANDBOX-SEND-INPUTS.schema.json") -Encoding UTF8 @' # RETURN-CHECKLIST-V2 Fill `SANDBOX-SEND-INPUTS.json` and run `CREATE-RETURN-ZIP.ps1`. Strict-v2 required fields: - `operator_started_at_local`: local time when the full operator flow started. - `operator_clicked_send_at_local`: local time when the operator clicked or confirmed the one manual send. - `operator_observed_success_at_local`: local time when the operator observed success, ack, or a local sent record. - `before_recorder_present`: set true after `return-evidence\before` is generated. - `after_recorder_present`: set true after `return-evidence\after` is generated. - `success_ack_or_sent_record_present`: set true when `success_ack_or_sent_record` is filled. - `duplicate_second_send_attempted`: must be false. Return rule: - One sandbox target only. - One content hash only. - One idempotency key only. - Do not send a second copy. - `CREATE-RETURN-ZIP.ps1` refuses to build a return zip when after-recorder output or strict-v2 fields are missing. '@ | Set-Content -LiteralPath (Join-Path $packageDir "RETURN-CHECKLIST-V2.md") -Encoding UTF8 @' Expected return files for R6c: 1. SANDBOX-SEND-INPUTS.json - content_sha256 is filled. - idempotency_key is filled. - success_ack_or_sent_record is filled. - MANUAL_ONE_SEND_ONLY is true. - DO_NOT_SEND_SECOND_TIME is true. - do_not_send_second_time_confirmed is true. - operator_started_at_local is filled. - operator_clicked_send_at_local is filled. - operator_observed_success_at_local is filled. - before_recorder_present is true. - after_recorder_present is true. - success_ack_or_sent_record_present is true. - duplicate_second_send_attempted is false. 2. return-evidence/before/* - Read-only recorder output before manual send. 3. return-evidence/after/* - Read-only recorder output after manual send. 4. The generated zip from CREATE-RETURN-ZIP.ps1. Decision rule: - If success_ack_or_sent_record is missing, production send remains blocked. - If duplicate/idempotency confirmation is missing, production send remains blocked. - If the target was not an explicit sandbox target, production send remains blocked. '@ | Set-Content -LiteralPath (Join-Path $packageDir "EXPECTED-RETURN-FILES.txt") -Encoding UTF8 @' @echo off setlocal cd /d "%~dp0" mkdir return-evidence 2>nul mkdir return-evidence\before 2>nul echo Running BEFORE read-only recorder... recorder\ISphereLiveProbeRecorder.exe --record-live-probe --out "%~dp0return-evidence\before" --no-pause echo Done. Check return-evidence\before. pause '@ | Set-Content -LiteralPath (Join-Path $packageDir "run-before-recorder.bat") -Encoding ASCII @' @echo off setlocal cd /d "%~dp0" mkdir return-evidence 2>nul mkdir return-evidence\after 2>nul echo Running AFTER read-only recorder... recorder\ISphereLiveProbeRecorder.exe --record-live-probe --out "%~dp0return-evidence\after" --no-pause echo Done. Check return-evidence\after. pause '@ | Set-Content -LiteralPath (Join-Path $packageDir "run-after-recorder.bat") -Encoding ASCII @' @echo off setlocal cd /d "%~dp0" call run-before-recorder.bat '@ | Set-Content -LiteralPath (Join-Path $packageDir "01-run-before-recorder.bat") -Encoding ASCII @' @echo off setlocal cd /d "%~dp0" echo Manual one-send action must already be complete before this step. call run-after-recorder.bat '@ | Set-Content -LiteralPath (Join-Path $packageDir "02-run-after-recorder.bat") -Encoding ASCII @' param( [Parameter(Mandatory=$true)] [string]$Text ) $bytes = [System.Text.Encoding]::UTF8.GetBytes($Text) $sha = [System.Security.Cryptography.SHA256]::Create() try { $hash = $sha.ComputeHash($bytes) -join ($hash | ForEach-Object { $_.ToString("x2") }) } finally { $sha.Dispose() } '@ | Set-Content -LiteralPath (Join-Path $packageDir "compute-content-hash.ps1") -Encoding ASCII @' $ErrorActionPreference = "Stop" Set-StrictMode -Version Latest $root = Split-Path -Parent $MyInvocation.MyCommand.Path $inputs = Join-Path $root "SANDBOX-SEND-INPUTS.json" if (-not (Test-Path -LiteralPath $inputs)) { throw "missing SANDBOX-SEND-INPUTS.json; copy the template and fill it first" } $json = Get-Content -LiteralPath $inputs -Raw | ConvertFrom-Json function Test-NonEmptyString($Value) { if ($null -eq $Value) { return $false } return -not [string]::IsNullOrWhiteSpace([string]$Value) } $before = Join-Path $root "return-evidence\before" $after = Join-Path $root "return-evidence\after" if (-not (Test-Path -LiteralPath $before)) { throw "missing before recorder output: $before" } if (-not (Test-Path -LiteralPath $after)) { throw "missing after recorder output: $after" } if (@(Get-ChildItem -LiteralPath $before -Recurse -File -ErrorAction SilentlyContinue).Count -eq 0) { throw "before recorder output is empty: $before" } if (@(Get-ChildItem -LiteralPath $after -Recurse -File -ErrorAction SilentlyContinue).Count -eq 0) { throw "after recorder output is empty: $after" } foreach ($field in @("sandbox_target_type","sandbox_target_id","content_sha256","idempotency_key","operator_started_at_local","operator_clicked_send_at_local","operator_observed_success_at_local","success_ack_or_sent_record")) { if (-not (Test-NonEmptyString $json.$field)) { throw "missing strict-v2 field: $field" } } if ($json.before_recorder_present -ne $true) { throw "before_recorder_present must be true" } if ($json.after_recorder_present -ne $true) { throw "after_recorder_present must be true" } if ($json.success_ack_or_sent_record_present -ne $true) { throw "success_ack_or_sent_record_present must be true" } if ($json.duplicate_second_send_attempted -ne $false) { throw "duplicate_second_send_attempted must be false" } if ($json.do_not_send_second_time_confirmed -ne $true) { throw "do_not_send_second_time_confirmed must be true" } $stamp = Get-Date -Format "yyyyMMdd-HHmmss" $zip = Join-Path $root ("isphere-send-sandbox-return-" + $stamp + ".zip") $items = @( $inputs, (Join-Path $root "return-evidence"), (Join-Path $root "EXPECTED-RETURN-FILES.txt"), (Join-Path $root "RETURN-CHECKLIST-V2.md") ) Compress-Archive -Path $items -DestinationPath $zip -Force [ordered]@{ ok = $true zip = $zip production_send_enabled = $false online_sandbox_required = $true strict_v2_return = $true } | ConvertTo-Json -Depth 4 -Compress '@ | Set-Content -LiteralPath (Join-Path $packageDir "CREATE-RETURN-ZIP.ps1") -Encoding ASCII @' $ErrorActionPreference = "Stop" Set-StrictMode -Version Latest $root = Split-Path -Parent $MyInvocation.MyCommand.Path & (Join-Path $root "CREATE-RETURN-ZIP.ps1") exit $LASTEXITCODE '@ | Set-Content -LiteralPath (Join-Path $packageDir "make-return-zip.ps1") -Encoding ASCII @' @echo off setlocal cd /d "%~dp0" powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0CREATE-RETURN-ZIP.ps1" echo exit code: %ERRORLEVEL% pause exit /b %ERRORLEVEL% '@ | Set-Content -LiteralPath (Join-Path $packageDir "03-make-return-zip.bat") -Encoding ASCII @' @echo off setlocal cd /d "%~dp0" echo R6c online sandbox-send recording suite echo. echo Step 1: BEFORE read-only recorder. call "%~dp0run-before-recorder.bat" echo. echo Step 2: manual one-send action. echo In the official iSphere client, manually send exactly one sandbox message. echo Fill SANDBOX-SEND-INPUTS.json before continuing. pause echo. echo Step 3: AFTER read-only recorder. call "%~dp0run-after-recorder.bat" echo. echo Step 4: make return zip. powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0CREATE-RETURN-ZIP.ps1" echo exit code: %ERRORLEVEL% pause exit /b %ERRORLEVEL% '@ | Set-Content -LiteralPath (Join-Path $packageDir "RUN-RECORDING-SUITE.bat") -Encoding ASCII @' { "package": "send-sandbox-gate", "LOCAL_LOGIN_UNAVAILABLE": true, "ONLINE_SANDBOX_REQUIRED": true, "MANUAL_ONE_SEND_ONLY": true, "DO_NOT_SEND_SECOND_TIME": true, "entrypoint": "RUN-RECORDING-SUITE.bat", "numbered_steps": [ "01-run-before-recorder.bat", "02-run-after-recorder.bat", "03-make-return-zip.bat" ], "recorder": "recorder/ISphereLiveProbeRecorder.exe", "return_inputs": "SANDBOX-SEND-INPUTS.json", "return_schema": "SANDBOX-SEND-INPUTS.schema.json", "return_checklist_v2": "RETURN-CHECKLIST-V2.md", "return_zip_script": "CREATE-RETURN-ZIP.ps1", "production_send_enabled": false } '@ | Set-Content -LiteralPath (Join-Path $packageDir "RECORDING-PACKAGE-MANIFEST.json") -Encoding ASCII if (-not $NoZip) { Compress-Archive -Path (Join-Path $packageDir "*") -DestinationPath $zipPath -Force } [ordered]@{ ok = $true package_dir = (Resolve-Path -LiteralPath $packageDir).Path recorder_dir = (Resolve-Path -LiteralPath $recorderDir).Path zip = if (Test-Path -LiteralPath $zipPath) { (Resolve-Path -LiteralPath $zipPath).Path } else { $null } local_login_required = $false online_sandbox_required = $true production_send_enabled = $false } | ConvertTo-Json -Depth 4 -Compress