chore: harden send sandbox evidence package v2

This commit is contained in:
zhaoyilun
2026-07-10 20:51:50 +08:00
parent 713917d929
commit 281b9dda2a
8 changed files with 276 additions and 25 deletions

View File

@@ -58,7 +58,7 @@ N13/N14/N15 are pre-business validation results. They can help identify UI eleme
## Current loop ## Current loop
Current loop: `R6i B-route connector adapter shell complete; next R6j online sandbox-send recording package v2`. Current loop: `R6j online sandbox-send recording package v2 complete; next R6k returned sandbox evidence intake v2`.
Active continuous execution plan: `docs/superpowers/plans/2026-07-10-r6f-r14-continuous-execution-plan.md`. Active continuous execution plan: `docs/superpowers/plans/2026-07-10-r6f-r14-continuous-execution-plan.md`.
@@ -239,8 +239,8 @@ Continuous execution plan R6f-R14 is approved and execution has started:
- Plan file: `docs/superpowers/plans/2026-07-10-r6f-r14-continuous-execution-plan.md`. - Plan file: `docs/superpowers/plans/2026-07-10-r6f-r14-continuous-execution-plan.md`.
- Scope: 15 ordered rounds covering send-message connector/gate hardening, send-file preview/idempotency/package, receive-file download preview, receive-message reconciliation, end-to-end business smoke, and release-candidate report. - Scope: 15 ordered rounds covering send-message connector/gate hardening, send-file preview/idempotency/package, receive-file download preview, receive-message reconciliation, end-to-end business smoke, and release-candidate report.
- Execution rule after approval: one round at a time, status-card update, verification, commit, push, then continue automatically until an evidence-only blocker requires user action. - Execution rule after approval: one round at a time, status-card update, verification, commit, push, then continue automatically until an evidence-only blocker requires user action.
- Last completed round: R6i B-route connector adapter shell. - Last completed round: R6j online sandbox-send recording package v2.
- Next active round: R6j online sandbox-send recording package v2. - Next active round: R6k returned sandbox evidence intake v2.
R6g send audit and idempotency replay diagnostics is complete: R6g send audit and idempotency replay diagnostics is complete:
@@ -268,3 +268,13 @@ R6i B-route connector adapter shell is complete:
- The shell does not start sidecars, load DLLs, click/type UI, replay network traffic, or upload files. - The shell does not start sidecars, load DLLs, click/type UI, replay network traffic, or upload files.
- Standard verification still reports 9 tools and `production_send_enabled=false`. - Standard verification still reports 9 tools and `production_send_enabled=false`.
- Next node: R6j online sandbox-send recording package v2. - Next node: R6j online sandbox-send recording package v2.
R6j online sandbox-send recording package v2 is complete:
- `scripts\verify-send-sandbox-gate-package.ps1` now requires strict-v2 files and field markers.
- `scripts\package-send-sandbox-gate.ps1` now emits `RETURN-CHECKLIST-V2.md`, `SANDBOX-SEND-INPUTS.schema.json`, `CREATE-RETURN-ZIP.ps1`, and strict-v2 fields in the input template.
- `CREATE-RETURN-ZIP.ps1` refuses to create a return zip if after-recorder output or strict-v2 operator fields are missing.
- `scripts\validate-returned-send-sandbox-package.ps1` supports `-ZipPath` and `-StrictV2`.
- `scripts\test-validate-returned-send-sandbox-package.ps1` proves the existing partial package remains `partial_use_only` and fails strict-v2.
- Standard verification still reports 9 tools and `production_send_enabled=false`.
- Next node: R6k returned sandbox evidence intake v2.

View File

@@ -439,18 +439,25 @@ When a returned package comes back, validate it before treating it as evidence:
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\validate-returned-send-sandbox-package.ps1 -PackagePath C:\Users\zhaoy\Downloads\1631.zip powershell -NoProfile -ExecutionPolicy Bypass -File scripts\validate-returned-send-sandbox-package.ps1 -PackagePath C:\Users\zhaoy\Downloads\1631.zip
``` ```
For R6j strict-v2 packages, use:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\validate-returned-send-sandbox-package.ps1 -ZipPath C:\Users\zhaoy\Downloads\isphere-send-sandbox-return-20260710-180000.zip -StrictV2
```
The current partial returned package `C:\Users\zhaoy\Downloads\1631.zip` validates as: The current partial returned package `C:\Users\zhaoy\Downloads\1631.zip` validates as:
```json ```json
{ {
"partial_evidence_usable": true, "partial_evidence_usable": true,
"r6d_gate_pass": false, "r6d_gate_pass": false,
"strict_v2_pass": false,
"production_send_enabled": false, "production_send_enabled": false,
"decision": "partial_use_only" "decision": "partial_use_only"
} }
``` ```
Use `partial_use_only` to continue local sandbox-only connector-shell and idempotency/audit work. Do not enable production send from a partial package. Full pass still requires after-recorder output and manual send timestamps. Use `partial_use_only` to continue local sandbox-only connector-shell and idempotency/audit work. Do not enable production send from a partial package. Full pass still requires after-recorder output, manual send timestamps, and the R6j strict-v2 fields: `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`, and `duplicate_second_send_attempted=false`.
The fixture test for the current partial package is: The fixture test for the current partial package is:

View File

@@ -57,6 +57,8 @@ recorder/variants/ISphereLiveProbeRecorder-x86.exe
| --- | --- | | --- | --- |
| `README.txt` | Explains local login limitation, online sandbox requirement, and one-send rule. | | `README.txt` | Explains local login limitation, online sandbox requirement, and one-send rule. |
| `OPERATOR-STEPS.md` | Step-by-step operator procedure. | | `OPERATOR-STEPS.md` | Step-by-step operator procedure. |
| `RETURN-CHECKLIST-V2.md` | Strict-v2 return checklist with required manual timestamps and before/after evidence flags. |
| `SANDBOX-SEND-INPUTS.schema.json` | Machine-readable schema for the strict-v2 operator input fields. |
| `SANDBOX-SEND-INPUTS.template.json` | Template for sandbox target, content hash, idempotency key, and observed result. | | `SANDBOX-SEND-INPUTS.template.json` | Template for sandbox target, content hash, idempotency key, and observed result. |
| `EXPECTED-RETURN-FILES.txt` | Return package acceptance checklist. | | `EXPECTED-RETURN-FILES.txt` | Return package acceptance checklist. |
| `RUN-RECORDING-SUITE.bat` | Top-level guided before/manual-send/after/return-zip flow. | | `RUN-RECORDING-SUITE.bat` | Top-level guided before/manual-send/after/return-zip flow. |
@@ -67,7 +69,8 @@ recorder/variants/ISphereLiveProbeRecorder-x86.exe
| `run-before-recorder.bat` | Runs read-only recorder before the manual sandbox send. | | `run-before-recorder.bat` | Runs read-only recorder before the manual sandbox send. |
| `run-after-recorder.bat` | Runs read-only recorder after the manual sandbox send. | | `run-after-recorder.bat` | Runs read-only recorder after the manual sandbox send. |
| `compute-content-hash.ps1` | Computes SHA256 for the exact test message body. | | `compute-content-hash.ps1` | Computes SHA256 for the exact test message body. |
| `make-return-zip.ps1` | Creates the return zip after before/after recordings and input JSON are present. | | `CREATE-RETURN-ZIP.ps1` | Strict-v2 return zip creator; refuses missing after-recorder output or required v2 fields. |
| `make-return-zip.ps1` | Compatibility wrapper that calls `CREATE-RETURN-ZIP.ps1`. |
## Operator rules ## Operator rules
@@ -81,6 +84,13 @@ DO_NOT_SEND_SECOND_TIME
content_sha256 content_sha256
idempotency_key idempotency_key
success_ack_or_sent_record success_ack_or_sent_record
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
``` ```
Required online procedure: Required online procedure:
@@ -92,8 +102,9 @@ Required online procedure:
5. Record `success_ack_or_sent_record`. 5. Record `success_ack_or_sent_record`.
6. Do not send a second copy. 6. Do not send a second copy.
7. Continue the guided flow, or run `02-run-after-recorder.bat`. 7. Continue the guided flow, or run `02-run-after-recorder.bat`.
8. Continue the guided flow, or run `03-make-return-zip.bat`. 8. Set the strict-v2 booleans in `SANDBOX-SEND-INPUTS.json`: `before_recorder_present=true`, `after_recorder_present=true`, `success_ack_or_sent_record_present=true`, and `duplicate_second_send_attempted=false`.
9. Return the generated zip. 9. Continue the guided flow, or run `03-make-return-zip.bat`.
10. Return the generated zip.
## Safety boundary ## Safety boundary
@@ -118,6 +129,7 @@ R6c can pass only after a returned package proves all of the following:
- observed success/ack or local sent-record evidence; - observed success/ack or local sent-record evidence;
- confirmation that no second send was made; - confirmation that no second send was made;
- before/after read-only recorder outputs are present. - before/after read-only recorder outputs are present.
- strict-v2 fields are complete: `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`, and `duplicate_second_send_attempted=false`.
## Current decision ## Current decision
@@ -126,3 +138,14 @@ R6c local-preparation slice is complete, including the consolidated recording su
Production `isphere_send_message` remains blocked. Production `isphere_send_message` remains blocked.
Next node: **R6d returned sandbox evidence intake** after the user returns the generated package. Next node: **R6d returned sandbox evidence intake** after the user returns the generated package.
## R6j strict-v2 package hardening
R6j upgrades the recording package so the previously missing R6d evidence cannot be omitted silently:
- `scripts\verify-send-sandbox-gate-package.ps1` now requires `RETURN-CHECKLIST-V2.md`, `SANDBOX-SEND-INPUTS.schema.json`, `CREATE-RETURN-ZIP.ps1`, and all strict-v2 field names.
- `CREATE-RETURN-ZIP.ps1` refuses to create the return zip unless before/after recorder directories contain files and strict-v2 fields are present.
- `scripts\validate-returned-send-sandbox-package.ps1` accepts `-ZipPath` and `-StrictV2`; strict mode exits non-zero when the returned package does not prove the full gate.
- The older partial package `C:\Users\zhaoy\Downloads\1631.zip` remains `partial_use_only` and `strict_v2_pass=false`.
Current decision: production `isphere_send_message` remains blocked. The next returned package must be generated from the R6j strict-v2 package to pass the full gate.

View File

@@ -362,7 +362,7 @@ git push gitea main
- `success_ack_or_sent_record_present=true` - `success_ack_or_sent_record_present=true`
- `duplicate_second_send_attempted=false` - `duplicate_second_send_attempted=false`
- [ ] **Step 1: Add v2 manifest assertions to package verifier** - [x] **Step 1: Add v2 manifest assertions to package verifier**
```powershell ```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-send-sandbox-gate-package.ps1 powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-send-sandbox-gate-package.ps1
@@ -370,11 +370,11 @@ powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-send-sandbox-
Expected before implementation: FAIL because v2 required returned fields are not enforced. Expected before implementation: FAIL because v2 required returned fields are not enforced.
- [ ] **Step 2: Add v2 checklist to package builder** - [x] **Step 2: Add v2 checklist to package builder**
Update package contents to include `RETURN-CHECKLIST-V2.md`, `SANDBOX-SEND-INPUTS.schema.json`, and a stricter `CREATE-RETURN-ZIP.ps1` that refuses to package if after-recorder output is missing. Update package contents to include `RETURN-CHECKLIST-V2.md`, `SANDBOX-SEND-INPUTS.schema.json`, and a stricter `CREATE-RETURN-ZIP.ps1` that refuses to package if after-recorder output is missing.
- [ ] **Step 3: Add validator strict mode** - [x] **Step 3: Add validator strict mode**
Add parameter: Add parameter:
@@ -384,7 +384,7 @@ param([string]$ZipPath, [switch]$StrictV2)
`-StrictV2` exits non-zero if any required returned field is absent. `-StrictV2` exits non-zero if any required returned field is absent.
- [ ] **Step 4: Verify and commit** - [x] **Step 4: Verify and commit**
```powershell ```powershell
git diff --check git diff --check
@@ -400,6 +400,14 @@ git push gitea main
**Acceptance gate:** A generated package can no longer omit the R6d missing after-recorder/timestamp fields. **Acceptance gate:** A generated package can no longer omit the R6d missing after-recorder/timestamp fields.
**R6j Result:**
- `scripts\verify-send-sandbox-gate-package.ps1` first failed against the old package because `RETURN-CHECKLIST-V2.md` was missing.
- `scripts\package-send-sandbox-gate.ps1` now emits `RETURN-CHECKLIST-V2.md`, `SANDBOX-SEND-INPUTS.schema.json`, `CREATE-RETURN-ZIP.ps1`, and strict-v2 fields in `SANDBOX-SEND-INPUTS.template.json`.
- `CREATE-RETURN-ZIP.ps1` refuses to build a return zip when after-recorder output, strict-v2 timestamps, success evidence, or no-duplicate confirmation are missing.
- `scripts\validate-returned-send-sandbox-package.ps1` now supports `-ZipPath` and `-StrictV2`.
- Existing partial package validation remains `partial_use_only`; strict-v2 validation exits non-zero for that partial package.
--- ---
### R6k: Returned sandbox evidence intake v2 ### R6k: Returned sandbox evidence intake v2

View File

@@ -60,7 +60,7 @@ Required rule:
- Fill success_ack_or_sent_record after the operator sees the sent message, ack, or local sent record. - Fill success_ack_or_sent_record after the operator sees the sent message, ack, or local sent record.
Return: Return:
- run make-return-zip.ps1 and send back the generated zip. - run CREATE-RETURN-ZIP.ps1 and send back the generated zip.
- preferred one-file flow: run RUN-RECORDING-SUITE.bat and follow the pauses. - preferred one-file flow: run RUN-RECORDING-SUITE.bat and follow the pauses.
'@ | Set-Content -LiteralPath (Join-Path $packageDir "README.txt") -Encoding UTF8 '@ | Set-Content -LiteralPath (Join-Path $packageDir "README.txt") -Encoding UTF8
@@ -108,6 +108,13 @@ Run `RUN-RECORDING-SUITE.bat`. It will run the before recorder, pause for the ma
2. Send it only to the declared sandbox target. 2. Send it only to the declared sandbox target.
3. Do not send a second copy. 3. Do not send a second copy.
4. Fill these fields in `SANDBOX-SEND-INPUTS.json`: 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_started_at_local`
- `manual_send_finished_at_local` - `manual_send_finished_at_local`
- `operator_observed_success` - `operator_observed_success`
@@ -123,7 +130,7 @@ Run `run-after-recorder.bat`.
Run: Run:
```powershell ```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\make-return-zip.ps1 powershell -NoProfile -ExecutionPolicy Bypass -File .\CREATE-RETURN-ZIP.ps1
``` ```
Send back the generated zip file. Send back the generated zip file.
@@ -139,6 +146,7 @@ The returned evidence should contain:
- completed `SANDBOX-SEND-INPUTS.json`; - completed `SANDBOX-SEND-INPUTS.json`;
- success_ack_or_sent_record text; - success_ack_or_sent_record text;
- confirmation that there was no second send. - 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. The repository must keep production send disabled until this evidence is reviewed.
'@ | Set-Content -LiteralPath (Join-Path $packageDir "OPERATOR-STEPS.md") -Encoding UTF8 '@ | Set-Content -LiteralPath (Join-Path $packageDir "OPERATOR-STEPS.md") -Encoding UTF8
@@ -155,6 +163,13 @@ The repository must keep production send disabled until this evidence is reviewe
"content_text_to_send": "<exact one-time sandbox message text>", "content_text_to_send": "<exact one-time sandbox message text>",
"content_sha256": "<sha256 hex of content_text_to_send>", "content_sha256": "<sha256 hex of content_text_to_send>",
"idempotency_key": "<one unique idempotency key>", "idempotency_key": "<one unique 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_started_at_local": "",
"manual_send_finished_at_local": "", "manual_send_finished_at_local": "",
"operator_observed_success": false, "operator_observed_success": false,
@@ -164,6 +179,66 @@ The repository must keep production send disabled until this evidence is reviewe
} }
'@ | Set-Content -LiteralPath (Join-Path $packageDir "SANDBOX-SEND-INPUTS.template.json") -Encoding UTF8 '@ | 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: Expected return files for R6c:
@@ -174,6 +249,13 @@ Expected return files for R6c:
- MANUAL_ONE_SEND_ONLY is true. - MANUAL_ONE_SEND_ONLY is true.
- DO_NOT_SEND_SECOND_TIME is true. - DO_NOT_SEND_SECOND_TIME is true.
- do_not_send_second_time_confirmed 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/* 2. return-evidence/before/*
- Read-only recorder output before manual send. - Read-only recorder output before manual send.
@@ -181,7 +263,7 @@ Expected return files for R6c:
3. return-evidence/after/* 3. return-evidence/after/*
- Read-only recorder output after manual send. - Read-only recorder output after manual send.
4. The generated zip from make-return-zip.ps1. 4. The generated zip from CREATE-RETURN-ZIP.ps1.
Decision rule: Decision rule:
- If success_ack_or_sent_record is missing, production send remains blocked. - If success_ack_or_sent_record is missing, production send remains blocked.
@@ -254,6 +336,11 @@ $inputs = Join-Path $root "SANDBOX-SEND-INPUTS.json"
if (-not (Test-Path -LiteralPath $inputs)) { if (-not (Test-Path -LiteralPath $inputs)) {
throw "missing SANDBOX-SEND-INPUTS.json; copy the template and fill it first" 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" $before = Join-Path $root "return-evidence\before"
$after = Join-Path $root "return-evidence\after" $after = Join-Path $root "return-evidence\after"
if (-not (Test-Path -LiteralPath $before)) { if (-not (Test-Path -LiteralPath $before)) {
@@ -262,13 +349,40 @@ if (-not (Test-Path -LiteralPath $before)) {
if (-not (Test-Path -LiteralPath $after)) { if (-not (Test-Path -LiteralPath $after)) {
throw "missing after recorder output: $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" $stamp = Get-Date -Format "yyyyMMdd-HHmmss"
$zip = Join-Path $root ("isphere-send-sandbox-return-" + $stamp + ".zip") $zip = Join-Path $root ("isphere-send-sandbox-return-" + $stamp + ".zip")
$items = @( $items = @(
$inputs, $inputs,
(Join-Path $root "return-evidence"), (Join-Path $root "return-evidence"),
(Join-Path $root "EXPECTED-RETURN-FILES.txt") (Join-Path $root "EXPECTED-RETURN-FILES.txt"),
(Join-Path $root "RETURN-CHECKLIST-V2.md")
) )
Compress-Archive -Path $items -DestinationPath $zip -Force Compress-Archive -Path $items -DestinationPath $zip -Force
[ordered]@{ [ordered]@{
@@ -276,14 +390,24 @@ Compress-Archive -Path $items -DestinationPath $zip -Force
zip = $zip zip = $zip
production_send_enabled = $false production_send_enabled = $false
online_sandbox_required = $true online_sandbox_required = $true
strict_v2_return = $true
} | ConvertTo-Json -Depth 4 -Compress } | 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 '@ | Set-Content -LiteralPath (Join-Path $packageDir "make-return-zip.ps1") -Encoding ASCII
@' @'
@echo off @echo off
setlocal setlocal
cd /d "%~dp0" cd /d "%~dp0"
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0make-return-zip.ps1" powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0CREATE-RETURN-ZIP.ps1"
echo exit code: %ERRORLEVEL% echo exit code: %ERRORLEVEL%
pause pause
exit /b %ERRORLEVEL% exit /b %ERRORLEVEL%
@@ -307,7 +431,7 @@ echo Step 3: AFTER read-only recorder.
call "%~dp0run-after-recorder.bat" call "%~dp0run-after-recorder.bat"
echo. echo.
echo Step 4: make return zip. echo Step 4: make return zip.
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0make-return-zip.ps1" powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0CREATE-RETURN-ZIP.ps1"
echo exit code: %ERRORLEVEL% echo exit code: %ERRORLEVEL%
pause pause
exit /b %ERRORLEVEL% exit /b %ERRORLEVEL%
@@ -328,6 +452,9 @@ exit /b %ERRORLEVEL%
], ],
"recorder": "recorder/ISphereLiveProbeRecorder.exe", "recorder": "recorder/ISphereLiveProbeRecorder.exe",
"return_inputs": "SANDBOX-SEND-INPUTS.json", "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 "production_send_enabled": false
} }
'@ | Set-Content -LiteralPath (Join-Path $packageDir "RECORDING-PACKAGE-MANIFEST.json") -Encoding ASCII '@ | Set-Content -LiteralPath (Join-Path $packageDir "RECORDING-PACKAGE-MANIFEST.json") -Encoding ASCII

View File

@@ -50,12 +50,21 @@ if ($result.has_after_probe -ne $false) {
if ($result.production_send_enabled -ne $false) { if ($result.production_send_enabled -ne $false) {
throw "production_send_enabled mismatch: $($result.production_send_enabled)" throw "production_send_enabled mismatch: $($result.production_send_enabled)"
} }
if ($result.strict_v2_pass -ne $false) {
throw "strict_v2_pass mismatch: $($result.strict_v2_pass)"
}
$strictOutput = & powershell -NoProfile -ExecutionPolicy Bypass -File $validator -ZipPath $FixtureZip -StrictV2
if ($LASTEXITCODE -eq 0) {
throw "StrictV2 unexpectedly passed for partial fixture: $strictOutput"
}
[ordered]@{ [ordered]@{
ok = $true ok = $true
fixture = $FixtureZip fixture = $FixtureZip
partial_evidence_usable = $result.partial_evidence_usable partial_evidence_usable = $result.partial_evidence_usable
r6d_gate_pass = $result.r6d_gate_pass r6d_gate_pass = $result.r6d_gate_pass
strict_v2_pass = $result.strict_v2_pass
capability_tool_count = $result.capability_tool_count capability_tool_count = $result.capability_tool_count
production_send_enabled = $result.production_send_enabled production_send_enabled = $result.production_send_enabled
} | ConvertTo-Json -Depth 4 -Compress } | ConvertTo-Json -Depth 4 -Compress

View File

@@ -1,7 +1,8 @@
param( param(
[Parameter(Mandatory=$true)] [string]$PackagePath = "",
[string]$PackagePath, [string]$ZipPath = "",
[string]$WorkDir = "" [string]$WorkDir = "",
[switch]$StrictV2
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
@@ -38,6 +39,23 @@ function Test-NonEmptyString($Value) {
return -not [string]::IsNullOrWhiteSpace([string]$Value) 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)) { if (-not (Test-Path -LiteralPath $PackagePath)) {
throw "package not found: $PackagePath" throw "package not found: $PackagePath"
} }
@@ -107,6 +125,14 @@ try {
$noSecondSend = ($inputs.do_not_send_second_time_confirmed -eq $true) $noSecondSend = ($inputs.do_not_send_second_time_confirmed -eq $true)
$hasBeforeProbe = ($liveProbeZips.Count -gt 0 -or $beforeMarkers.Count -gt 0) $hasBeforeProbe = ($liveProbeZips.Count -gt 0 -or $beforeMarkers.Count -gt 0)
$hasAfterProbe = ($afterMarkers.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 = ( $partialUsable = (
$capabilityOK -and $capabilityOK -and
@@ -132,18 +158,44 @@ try {
if (-not $manualStartPresent) { $missing.Add("manual_send_started_at_local") } if (-not $manualStartPresent) { $missing.Add("manual_send_started_at_local") }
if (-not $manualFinishPresent) { $missing.Add("manual_send_finished_at_local") } if (-not $manualFinishPresent) { $missing.Add("manual_send_finished_at_local") }
if (-not $hasAfterProbe) { $missing.Add("after_recorder_output") } 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") }
[ordered]@{ $strictV2Pass = (
$r6dGatePass -and
$operatorStartedAtLocalPresent -and
$operatorClickedSendAtLocalPresent -and
$operatorObservedSuccessAtLocalPresent -and
$beforeRecorderPresentFlag -and
$afterRecorderPresentFlag -and
$successAckOrSentRecordPresentFlag -and
$duplicateSecondSendAttemptedFalse
)
$summary = [ordered]@{
ok = $true ok = $true
package = $resolvedPackage package = $resolvedPackage
input_file_name = $inputsFile.Name input_file_name = $inputsFile.Name
partial_evidence_usable = $partialUsable partial_evidence_usable = $partialUsable
r6d_gate_pass = $r6dGatePass r6d_gate_pass = $r6dGatePass
strict_v2_pass = $strictV2Pass
target_type_present = Test-NonEmptyString $inputs.sandbox_target_type target_type_present = Test-NonEmptyString $inputs.sandbox_target_type
target_id_present = Test-NonEmptyString $inputs.sandbox_target_id target_id_present = Test-NonEmptyString $inputs.sandbox_target_id
content_hash_matches = $contentHashMatches content_hash_matches = $contentHashMatches
idempotency_key_present = $hasIdempotencyKey idempotency_key_present = $hasIdempotencyKey
operator_observed_success = $operatorSuccess 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 success_ack_or_sent_record_present = $hasSuccessRecord
do_not_send_second_time_confirmed = $noSecondSend do_not_send_second_time_confirmed = $noSecondSend
manual_send_started_at_local_present = $manualStartPresent manual_send_started_at_local_present = $manualStartPresent
@@ -155,8 +207,12 @@ try {
has_after_probe = $hasAfterProbe has_after_probe = $hasAfterProbe
live_probe_zip_count = $liveProbeZips.Count live_probe_zip_count = $liveProbeZips.Count
missing_for_full_gate = @($missing) missing_for_full_gate = @($missing)
decision = if ($r6dGatePass) { "full_gate_pass" } elseif ($partialUsable) { "partial_use_only" } else { "not_usable" } decision = if ($strictV2Pass) { "strict_v2_gate_pass" } elseif ($r6dGatePass) { "full_gate_pass" } elseif ($partialUsable) { "partial_use_only" } else { "not_usable" }
} | ConvertTo-Json -Depth 6 -Compress }
$summary | ConvertTo-Json -Depth 6 -Compress
if ($StrictV2 -and -not $strictV2Pass) {
exit 1
}
} }
finally { finally {
if ($cleanup -and (Test-Path -LiteralPath $WorkDir)) { if ($cleanup -and (Test-Path -LiteralPath $WorkDir)) {

View File

@@ -29,6 +29,8 @@ if ($LASTEXITCODE -ne 0) {
$requiredFiles = @( $requiredFiles = @(
"README.txt", "README.txt",
"OPERATOR-STEPS.md", "OPERATOR-STEPS.md",
"RETURN-CHECKLIST-V2.md",
"SANDBOX-SEND-INPUTS.schema.json",
"SANDBOX-SEND-INPUTS.template.json", "SANDBOX-SEND-INPUTS.template.json",
"EXPECTED-RETURN-FILES.txt", "EXPECTED-RETURN-FILES.txt",
"RUN-RECORDING-SUITE.bat", "RUN-RECORDING-SUITE.bat",
@@ -38,6 +40,7 @@ $requiredFiles = @(
"RECORDING-PACKAGE-MANIFEST.json", "RECORDING-PACKAGE-MANIFEST.json",
"run-before-recorder.bat", "run-before-recorder.bat",
"run-after-recorder.bat", "run-after-recorder.bat",
"CREATE-RETURN-ZIP.ps1",
"make-return-zip.ps1", "make-return-zip.ps1",
"recorder\ISphereLiveProbeRecorder.exe", "recorder\ISphereLiveProbeRecorder.exe",
"recorder\variants\ISphereLiveProbeRecorder-x86.exe" "recorder\variants\ISphereLiveProbeRecorder-x86.exe"
@@ -53,6 +56,7 @@ foreach ($relative in $requiredFiles) {
$readme = Get-Content -LiteralPath (Join-Path $packageRoot "README.txt") -Raw $readme = Get-Content -LiteralPath (Join-Path $packageRoot "README.txt") -Raw
$steps = Get-Content -LiteralPath (Join-Path $packageRoot "OPERATOR-STEPS.md") -Raw $steps = Get-Content -LiteralPath (Join-Path $packageRoot "OPERATOR-STEPS.md") -Raw
$expected = Get-Content -LiteralPath (Join-Path $packageRoot "EXPECTED-RETURN-FILES.txt") -Raw $expected = Get-Content -LiteralPath (Join-Path $packageRoot "EXPECTED-RETURN-FILES.txt") -Raw
$checklistV2 = Get-Content -LiteralPath (Join-Path $packageRoot "RETURN-CHECKLIST-V2.md") -Raw
foreach ($needle in @( foreach ($needle in @(
"LOCAL_LOGIN_UNAVAILABLE", "LOCAL_LOGIN_UNAVAILABLE",
@@ -62,9 +66,16 @@ foreach ($needle in @(
"RUN-RECORDING-SUITE.bat", "RUN-RECORDING-SUITE.bat",
"content_sha256", "content_sha256",
"idempotency_key", "idempotency_key",
"success_ack_or_sent_record" "success_ack_or_sent_record",
"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"
)) { )) {
if ($readme -notmatch [regex]::Escape($needle) -and $steps -notmatch [regex]::Escape($needle) -and $expected -notmatch [regex]::Escape($needle)) { if ($readme -notmatch [regex]::Escape($needle) -and $steps -notmatch [regex]::Escape($needle) -and $expected -notmatch [regex]::Escape($needle) -and $checklistV2 -notmatch [regex]::Escape($needle)) {
throw "package instructions missing required phrase: $needle" throw "package instructions missing required phrase: $needle"
} }
} }