docs: accept partial send sandbox evidence

This commit is contained in:
zhaoyilun
2026-07-10 19:11:52 +08:00
parent 89d7f65b45
commit 138615a29c
7 changed files with 446 additions and 6 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: `R6c local online-sandbox evidence package consolidated plus offline capability-test package prepared; next R6d returned sandbox evidence intake after package is run and returned`.
Current loop: `R6d partial returned sandbox evidence intake accepted for limited use; next R6e sandbox-only send connector shell and idempotency audit hardening`.
Plan file: `docs/superpowers/plans/2026-07-10-core-business-capabilities-roadmap.md`.
@@ -117,6 +117,7 @@ Current loop output so far:
44. R6b: added `isphere_send_message` preview/dry-run only. The tool validates `target_type`, `target_id`, `content_sha256`, and `idempotency_key`, returns connector metadata `implatform-sidecar`/`preview`, appends redacted JSONL audit events without raw message body or raw idempotency key, and returns `send_status="blocked"` for production with all side-effect flags false. `verify-go-mcp.ps1` now verifies the 9-tool surface and proves `send_preview_tool_present=true` and `production_send_enabled=false`.
45. R6c: because the local machine cannot log in, prepared an online/internal sandbox evidence package instead of attempting local send. Added `scripts/package-send-sandbox-gate.ps1`, `scripts/verify-send-sandbox-gate-package.ps1`, and `docs/source-discovery/2026-07-10-send-sandbox-gate.md`; local verification generates `runs/send-sandbox-gate-package.zip` with the read-only recorder, before/after scripts, sandbox input template, SHA256 helper, expected-return checklist, and return-zip helper.
46. R6c package consolidation: upgraded `runs/send-sandbox-gate-package.zip` into a one-pass operator recording suite with `RUN-RECORDING-SUITE.bat`, numbered `01/02/03` scripts, and `RECORDING-PACKAGE-MANIFEST.json`; added a second offline-safe `runs/send-capability-test-package.zip` with `isphere-capability-smoke.exe` plus packaged `ISphereWinHelper.exe` to verify the 9-tool MCP surface, receive/search/file-list fixture behavior, send preview, and `production_send_enabled=false`.
47. R6d partial intake: accepted `C:\Users\zhaoy\Downloads\1631.zip` as partial evidence only. Added `scripts\validate-returned-send-sandbox-package.ps1`, `scripts\test-validate-returned-send-sandbox-package.ps1`, and `docs/source-discovery/2026-07-10-returned-send-sandbox-analysis.md`. Validator reports `partial_evidence_usable=true` and `r6d_gate_pass=false`: capability test passed, content hash matched, idempotency key/success/no-second-send fields were present, but after-recorder output and manual send timestamps are missing. This can unblock sandbox-only connector-shell/idempotency work, not production send.
## Recompiled business roadmap
@@ -201,3 +202,14 @@ R6c local preparation for online sandbox-send evidence is complete:
- The capability-test package is offline-safe and confirms 9 MCP tools, fixture-backed receive/search/file-list behavior, send preview, no real send, and `production_send_enabled=false`.
- Production `isphere_send_message` and `isphere_send_file` remain blocked until the returned package proves success/ack or sent-record evidence and no second send.
- Next node: R6d returned sandbox evidence intake after the package is run and returned.
R6d partial returned sandbox evidence intake is complete:
- Local intake source: `C:\Users\zhaoy\Downloads\1631.zip`.
- Added repeatable validator: `scripts\validate-returned-send-sandbox-package.ps1`.
- Added fixture test: `scripts\test-validate-returned-send-sandbox-package.ps1`.
- Created `docs/source-discovery/2026-07-10-returned-send-sandbox-analysis.md`.
- Validator decision: `partial_use_only`.
- `partial_evidence_usable=true`: use it to continue sandbox-only send connector shell and idempotency/audit hardening.
- `r6d_gate_pass=false`: do not enable production `isphere_send_message`, because after-recorder output and manual send timestamps are missing.
- Next node: R6e sandbox-only send connector shell and idempotency audit hardening.

View File

@@ -401,7 +401,34 @@ The package also contains `RECORDING-PACKAGE-MANIFEST.json`, `run-before-recorde
Production `isphere_send_message` remains blocked until the returned package is analyzed.
## 14. Offline capability-test package
## 14. Returned sandbox package validation
When a returned package comes back, validate it before treating it as evidence:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\validate-returned-send-sandbox-package.ps1 -PackagePath C:\Users\zhaoy\Downloads\1631.zip
```
The current partial returned package `C:\Users\zhaoy\Downloads\1631.zip` validates as:
```json
{
"partial_evidence_usable": true,
"r6d_gate_pass": false,
"production_send_enabled": false,
"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.
The fixture test for the current partial package is:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\test-validate-returned-send-sandbox-package.ps1
```
## 15. Offline capability-test package
Use this second package when you want to test the current capability surface without logging in and without any real send.
@@ -440,7 +467,7 @@ Expected final JSON includes:
This package includes `isphere-capability-smoke.exe` and `runs\win-helper\ISphereWinHelper.exe`. It verifies the nine-tool MCP surface, fixture-backed receive/search/file-list behavior, send preview, and the production-send block. It does not log in, click, type, upload, hook, inject, or send.
## 15. Troubleshooting
## 16. Troubleshooting
- If C# helper build fails, run `scripts\build-win-helper.ps1` directly and check for missing .NET Framework reference assemblies.
- If `win_helper_version` fails, rerun `powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-win-helper.ps1` first.

View File

@@ -0,0 +1,133 @@
# Returned Send Sandbox Analysis
Date: 2026-07-10
Node: R6d returned sandbox evidence intake
Returned package: `C:\Users\zhaoy\Downloads\1631.zip`
Local intake copy: `runs/returned-send-sandbox/1631/`
## Decision
Use this return package as **partial R6d evidence** only.
Business interpretation:
- It is enough to continue the next engineering loop with a sandbox-only send connector shell, idempotency/audit hardening, and stricter returned-package validation.
- It is not enough to enable production `isphere_send_message`.
- It is not enough to start `isphere_send_file` production work.
Repository behavior remains:
```text
production_send_enabled=false
```
## Validator
Repeatable validation command:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\validate-returned-send-sandbox-package.ps1 -PackagePath C:\Users\zhaoy\Downloads\1631.zip
```
Fixture test:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\test-validate-returned-send-sandbox-package.ps1
```
Observed validator result:
```json
{
"partial_evidence_usable": true,
"r6d_gate_pass": false,
"capability_test_ok": true,
"capability_tool_count": 9,
"production_send_enabled": false,
"content_hash_matches": true,
"operator_observed_success": true,
"do_not_send_second_time_confirmed": true,
"has_before_probe": true,
"has_after_probe": false,
"missing_for_full_gate": [
"manual_send_started_at_local",
"manual_send_finished_at_local",
"after_recorder_output"
],
"decision": "partial_use_only"
}
```
## Evidence received
The returned zip contains:
```text
isphere-live-probe-20260710-162555.zip
RUN-RECORDING-SUITE.txt
SANDBOX-SEND-INPUTS..json
send-capability-test-package.txt
```
Notes:
- `SANDBOX-SEND-INPUTS..json` has a double dot in the filename, but the validator accepts this pattern.
- The target id is present but should stay redacted in committed docs.
- The raw message body is not needed in committed docs; the validator checks the supplied SHA256 against the local returned JSON.
- The capability-test transcript proves the offline-safe 9-tool package ran successfully and kept production send disabled.
## Evidence that is usable now
Usable for next engineering loop:
- explicit sandbox target fields are present;
- content SHA256 matches the supplied test content;
- idempotency key is present;
- operator recorded observed success/sent-record text;
- operator confirmed no second send;
- one read-only live probe package was returned;
- capability smoke passed with 9 tools and `production_send_enabled=false`.
This is enough to justify moving from "waiting for any returned send evidence" to:
1. returned-package validator hardening;
2. sandbox-only connector shell design;
3. idempotency/audit persistence design;
4. fake/sandbox connector tests.
## Missing for full R6d pass
Missing:
- `manual_send_started_at_local`;
- `manual_send_finished_at_local`;
- after-send recorder output;
- standard `return-evidence/before` and `return-evidence/after` shape from `make-return-zip.ps1`.
Because these are missing, the full gate does **not** pass.
## Current route decision
Proceed with "partial use" only:
```text
R6d partial_use_only -> R6e sandbox-only send connector shell and idempotency audit hardening
```
Do not change the current MCP production-send response:
```text
execution_mode="production" -> send_status="blocked"
```
## What would make it full pass later
Ask the operator to return a corrected package only when convenient:
1. run `02-run-after-recorder.bat`;
2. fill `manual_send_started_at_local`;
3. fill `manual_send_finished_at_local`;
4. run `03-make-return-zip.bat`;
5. return the generated `isphere-send-sandbox-return-*.zip`.
The current partial package can still be used for the next local development loop.

View File

@@ -7,6 +7,7 @@ Schema notes: `docs/source-discovery/2026-07-09-n12-pre-zyl-schema-notes.md`
Returned live probe: `docs/source-discovery/2026-07-10-returned-live-probe-analysis.md`
Send connector preflight: `docs/source-discovery/2026-07-10-send-connector-preflight.md`
Send sandbox gate: `docs/source-discovery/2026-07-10-send-sandbox-gate.md`
Returned send sandbox analysis: `docs/source-discovery/2026-07-10-returned-send-sandbox-analysis.md`
## Source priority
@@ -25,7 +26,7 @@ Send sandbox gate: `docs/source-discovery/2026-07-10-send-sandbox-gate.md`
| `isphere_search_contacts` | bare sender/receiver JIDs extracted from normalized log-backed messages loaded from configured PacketReader file or directory source | validated copied `MsgLib.db` tables: `TD_Roster`, `TD_CustomEffigy`, `tblRecent`, `tblChatLevel`, `tblPersonMsg`; decrypted roster/contact stanzas from `Smark.SendReceive`; UIA helper source if display names are still missing | `docs/source-discovery/2026-07-10-msglib-readonly-schema-extraction.md`; `internal/isphere/contacts_test.go`; `internal/tools/isphere_contacts_test.go` | C34 documents optional MsgLib contact display enrichment; R2 adds deterministic exact-match-first ranking, case-insensitive de-duplication across log/MsgLib candidates, and preserves `source`/`raw_ref` | core contact search complete; optional richer fields later |
| `isphere_search_groups` | decrypted `PacketReader.ProcessPacket` `type="groupchat"` stanzas and conference/MUC JIDs loaded from configured PacketReader file or directory source | validated copied `MsgLib.db` tables: `tblMsgGroupPersonMsg`, `TD_WorkGroupAuth`, `tblRecent`; UIA helper source if group display names are still missing | `docs/source-discovery/2026-07-10-msglib-readonly-schema-extraction.md`; `internal/isphere/groups_test.go`; `internal/tools/isphere_groups_test.go`; C9 ignored-log scan: PacketReader 86 groupchat/86 conference hits; Smark 24 groupchat/658 conference/631 muc hits | C34 documents optional MsgLib group display enrichment; R2 adds deterministic exact-match-first ranking, case-insensitive de-duplication across log/MsgLib candidates, and preserves `source`/`raw_ref` | core group search complete; optional member/owner hierarchy later |
| `isphere_receive_files` | decrypted `PacketReader.ProcessPacket` file-transfer message stanzas via `internal/isphere.ListFilesFromMessages` and `isphere_receive_files` list mode; configured PacketReader file or directory source; `zyl\importal\<hash>` cache entries remain unlinked | MsgLib `TD_ReceiveFileRecord` safe metadata through explicit copied-DB path; decrypted `Smark.SendReceive` and `SaveToDB` traces for file-reference reconciliation; future UIA/client connector for download | `docs/source-discovery/2026-07-09-n12-pre-zyl-schema-notes.md#c12-file-transfer-evidence-precheck`; `docs/source-discovery/2026-07-10-file-download-mapping-precheck.md`; `docs/source-discovery/2026-07-10-file-cache-mapping-diagnostic.md` | C22 verifies safe file-list contract args; R3 confirms list metadata is available; R3b diagnostic finds 0 current cache/archive matches, so R4 remains blocked | blocked pending better cache evidence or UI/client download connector |
| `isphere_send_message` | B-route selected: running-client sidecar / in-process connector, preferring `AppContextManager.SendTxtMessageByJid(...)` or `MessageScheduling.SendChatMessage(...)` over the unimplemented `SendP2PMessage(...)` interface path; current MCP tool exposes preview/dry-run only | A-route fallback: constrained UI/RPA wrapper only if B cannot pass runtime probe, entrypoint availability, dynamic observation, and idempotent dry-run gates; network/protocol replay remains deferred | `docs/source-discovery/2026-07-10-send-message-source-precheck.md`; `docs/source-discovery/2026-07-10-send-message-connector-selection.md`; `docs/source-discovery/2026-07-10-send-sidecar-b-first-plan.md`; `docs/source-discovery/2026-07-10-returned-live-probe-analysis.md`; `docs/source-discovery/2026-07-10-send-connector-preflight.md`; `docs/source-discovery/2026-07-10-send-sandbox-gate.md`; `internal/tools/isphere_send_message_test.go`; `cmd/isphere-capability-smoke/main.go` | R6c prepares the online sandbox evidence package because local login is unavailable. `runs/send-sandbox-gate-package.zip` now contains a consolidated recording suite for one manual sandbox send; `runs/send-capability-test-package.zip` separately verifies the offline-safe 9-tool surface, fixture-backed receive/search/file-list behavior, send preview, and `production_send_enabled=false`. No returned online send evidence has been analyzed yet. Production remains blocked. | next: R6d returned sandbox evidence intake after user returns the package |
| `isphere_send_message` | B-route selected: running-client sidecar / in-process connector, preferring `AppContextManager.SendTxtMessageByJid(...)` or `MessageScheduling.SendChatMessage(...)` over the unimplemented `SendP2PMessage(...)` interface path; current MCP tool exposes preview/dry-run only | A-route fallback: constrained UI/RPA wrapper only if B cannot pass runtime probe, entrypoint availability, dynamic observation, and idempotent dry-run gates; network/protocol replay remains deferred | `docs/source-discovery/2026-07-10-send-message-source-precheck.md`; `docs/source-discovery/2026-07-10-send-message-connector-selection.md`; `docs/source-discovery/2026-07-10-send-sidecar-b-first-plan.md`; `docs/source-discovery/2026-07-10-returned-live-probe-analysis.md`; `docs/source-discovery/2026-07-10-send-connector-preflight.md`; `docs/source-discovery/2026-07-10-send-sandbox-gate.md`; `docs/source-discovery/2026-07-10-returned-send-sandbox-analysis.md`; `internal/tools/isphere_send_message_test.go`; `cmd/isphere-capability-smoke/main.go` | R6d validates `C:\Users\zhaoy\Downloads\1631.zip` as partial evidence only: `partial_evidence_usable=true`, `r6d_gate_pass=false`, capability smoke passed, content hash matched, operator success/no-second-send fields were present, but after-recorder output and send timestamps were missing. This is enough for sandbox-only connector-shell/idempotency work, not enough for production send. | next: R6e sandbox-only connector shell and idempotency audit hardening |
| `isphere_send_file` | B-route selected after send-message probe: managed offline-file path through `OffLineFileSend.sendFile()`, `IMPP.Service*.dll` `IFileTransfer.FileUpload(...)`, and `Chat.sendFileMessage(...)` | A-route fallback: constrained UI/RPA upload/send-file flow; native `TcpFileTransfer.dll` is a later candidate only after managed path is rejected | `docs/source-discovery/2026-07-10-send-message-source-precheck.md`; `docs/source-discovery/2026-07-10-send-message-connector-selection.md`; `docs/source-discovery/2026-07-10-send-sidecar-b-first-plan.md`; `docs/source-discovery/2026-07-10-returned-live-probe-analysis.md`; `docs/source-discovery/2026-07-10-send-connector-preflight.md`; `docs/source-discovery/2026-07-10-send-sandbox-gate.md` | R6a/C31 metadata confirms `FileTransfer.FileUpload`, `FileTransfer.AsynFileUpload`, `OffLineFileSend.sendFile`, and `Chat.sendFileMessage` candidates. File-send remains blocked behind returned message-send sandbox evidence plus later upload/file-message dry-run evidence. | next: wait for R6d, then file-send upload preflight |
## Stage C entry rule
@@ -43,4 +44,4 @@ Start Stage C with a narrow log-backed `isphere_receive_messages` source abstrac
## Deferred source work
`MsgLib.db` has a validated copied read-only open path through the bundled 32-bit `System.Data.SQLite.dll` and password `123`. C27 adds `MsgLibReadSidecar` as the bounded x86 .NET reader boundary, C28 adds the Go `internal/msglib` process client, C29 adds bounded `display_entities` extraction, C30 wires it as optional contact/group MCP enrichment, C31 proves real copied-DB MCP enrichment with sanitized output, C32 reuses it for receive-message display fields, C33 proves that receive display path through a real copied-DB MCP smoke without printing entity values, C34 documents the operator setup/verification path, C35 maps MsgLib message tables to receive-message contract fields without reading row values, C36 adds metadata-only `message_sources` readiness, C37 defines the bounded DB-backed receive-source design, C38 implements sidecar/Go-wrapper `list_messages` with sanitized verification, C39 adds a Go adapter from MsgLib list results to the receive-message domain model, C40 adds explicit tool-level `msglib_readonly` source selection, C41 wires env-configured explicit MsgLib receive with optional sanitized smoke, R1 documents source reconciliation keys, and R2 hardens contact/group search ranking and de-duplication. R6c now has both the consolidated online recording package and the offline capability-test package. The active roadmap is `docs/superpowers/plans/2026-07-10-core-business-capabilities-roadmap.md`; next step is R6d returned sandbox evidence intake after the online package is returned.
`MsgLib.db` has a validated copied read-only open path through the bundled 32-bit `System.Data.SQLite.dll` and password `123`. C27 adds `MsgLibReadSidecar` as the bounded x86 .NET reader boundary, C28 adds the Go `internal/msglib` process client, C29 adds bounded `display_entities` extraction, C30 wires it as optional contact/group MCP enrichment, C31 proves real copied-DB MCP enrichment with sanitized output, C32 reuses it for receive-message display fields, C33 proves that receive display path through a real copied-DB MCP smoke without printing entity values, C34 documents the operator setup/verification path, C35 maps MsgLib message tables to receive-message contract fields without reading row values, C36 adds metadata-only `message_sources` readiness, C37 defines the bounded DB-backed receive-source design, C38 implements sidecar/Go-wrapper `list_messages` with sanitized verification, C39 adds a Go adapter from MsgLib list results to the receive-message domain model, C40 adds explicit tool-level `msglib_readonly` source selection, C41 wires env-configured explicit MsgLib receive with optional sanitized smoke, R1 documents source reconciliation keys, and R2 hardens contact/group search ranking and de-duplication. R6d partial evidence is now usable only for sandbox-only send connector shell and idempotency/audit hardening. The active roadmap is `docs/superpowers/plans/2026-07-10-core-business-capabilities-roadmap.md`; next step is R6e.

View File

@@ -28,7 +28,7 @@
| Search contacts | Registered MCP tool; deterministic exact-match ranking and case-insensitive de-duplication across log/JID candidates and optional copied-DB MsgLib display enrichment. | Optional richer fields such as department/title remain future enrichment, not a core search blocker. | complete for core search; optional enrichment later |
| Search groups | Registered MCP tool; deterministic exact-match ranking and case-insensitive de-duplication across groupchat/conference candidates and optional copied-DB MsgLib display enrichment. | Optional member count, owner refs, and nested/group hierarchy remain future enrichment, not a core search blocker. | complete for core search; optional enrichment later |
| Receive messages | PacketReader source works; copied `MsgLib.db` explicit receive works with `source_preference="msglib_readonly"`; R1 precheck says default should remain PacketReader until a future reconciliation helper is implemented. | Future reconciliation helper, source confidence scoring, pagination policy. | future receive helper after R2/R3 |
| Send messages | `isphere_send_message` preview/dry-run is registered; R6a confirms B-route entrypoint metadata; R6c has a consolidated online sandbox recording package plus a separate offline capability-test package. | Need returned online sandbox evidence proving success/ack and no duplicate send before production. | next R6d returned sandbox evidence intake |
| Send messages | `isphere_send_message` preview/dry-run is registered; R6a confirms B-route entrypoint metadata; R6d partial returned package is usable for sandbox-only connector-shell/idempotency work. | Full production still needs after-recorder output and manual send timestamps, or a later complete returned package. | next R6e sandbox-only connector shell |
| Receive files | List mode exists from message-derived file metadata; R3/R3b confirm DB file metadata exists but current cache/archive evidence does not map to local files. | R4 download is blocked until better cache evidence or a UI/client download connector is available. | blocked; continue R5 |
| Send files | Contract exists; R6a/C31 metadata confirms file-upload/offline-file/file-message candidates and A-route file menu classifier support. | Blocked behind returned send-message sandbox evidence plus upload/file-transfer dry-run. | wait for R6d, then file-send upload preflight |
@@ -510,6 +510,47 @@ powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-go-mcp.ps1
**Stop condition:**
- Do not infer success from screenshots or vague operator notes alone. Require the structured returned package fields and before/after recorder output.
**R6d Result:**
- Added `scripts/validate-returned-send-sandbox-package.ps1`.
- Added `scripts/test-validate-returned-send-sandbox-package.ps1`.
- Created `docs/source-discovery/2026-07-10-returned-send-sandbox-analysis.md`.
- Validated `C:\Users\zhaoy\Downloads\1631.zip` as partial evidence:
- `partial_evidence_usable=true`;
- `r6d_gate_pass=false`;
- capability test passed with 9 tools;
- production send remained disabled;
- content hash matched;
- idempotency key, operator observed success, success record, and no-second-send confirmation were present;
- after-recorder output and manual send timestamps were missing.
- Decision: use the returned package to proceed with sandbox-only connector shell and idempotency/audit hardening, but keep production `isphere_send_message` blocked.
---
### R6e: Sandbox-only send connector shell and idempotency audit hardening
**Purpose:** Use R6d partial evidence to build the non-production connector control plane before any real send connector is enabled.
**Files:**
- Modify: `internal/tools/isphere_send_message.go`
- Modify tests: `internal/tools/isphere_send_message_test.go`
- Modify docs: this roadmap, `docs/current-status-card.md`, and `docs/go-mcp-runbook.md`
**Execution policy:**
- `execution_mode="preview"` remains no-send.
- `execution_mode="production"` remains blocked.
- Add or harden idempotency/audit state so duplicate idempotency keys can be detected before any future production connector is attached.
- If a connector interface is introduced, tests must use a fake connector only.
- No helper click/type/send op, no upload, no hook, no network replay.
**Acceptance gate:**
- Duplicate idempotency-key behavior is tested.
- Audit records remain redacted and do not store raw message body.
- Standard MCP smoke still reports `production_send_enabled=false`.
**Stop condition:**
- Do not wire a real send entrypoint until a full returned package or controlled online sandbox connector test is available.
---
### R6: Send-message preview and production path

View File

@@ -0,0 +1,61 @@
param(
[string]$FixtureZip = "C:\Users\zhaoy\Downloads\1631.zip"
)
$ErrorActionPreference = "Stop"
Set-StrictMode -Version Latest
$repo = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "..")).Path
$validator = Join-Path $repo "scripts\validate-returned-send-sandbox-package.ps1"
if (-not (Test-Path -LiteralPath $validator)) {
throw "missing validator script: $validator"
}
if (-not (Test-Path -LiteralPath $FixtureZip)) {
throw "missing fixture zip: $FixtureZip"
}
$output = & powershell -NoProfile -ExecutionPolicy Bypass -File $validator -PackagePath $FixtureZip
if ($LASTEXITCODE -ne 0) {
throw "validator failed with exit code $LASTEXITCODE"
}
$output | ForEach-Object { Write-Host $_ }
$lastLine = @($output | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) | Select-Object -Last 1
$result = $lastLine | ConvertFrom-Json
if ($result.ok -ne $true) {
throw "validator ok mismatch: $($result.ok)"
}
if ($result.partial_evidence_usable -ne $true) {
throw "partial_evidence_usable mismatch: $($result.partial_evidence_usable)"
}
if ($result.r6d_gate_pass -ne $false) {
throw "r6d_gate_pass mismatch: $($result.r6d_gate_pass)"
}
if ($result.capability_test_ok -ne $true) {
throw "capability_test_ok mismatch: $($result.capability_test_ok)"
}
if ($result.capability_tool_count -ne 9) {
throw "capability_tool_count mismatch: $($result.capability_tool_count)"
}
if ($result.content_hash_matches -ne $true) {
throw "content_hash_matches mismatch: $($result.content_hash_matches)"
}
if ($result.operator_observed_success -ne $true) {
throw "operator_observed_success mismatch: $($result.operator_observed_success)"
}
if ($result.has_after_probe -ne $false) {
throw "has_after_probe mismatch: $($result.has_after_probe)"
}
if ($result.production_send_enabled -ne $false) {
throw "production_send_enabled mismatch: $($result.production_send_enabled)"
}
[ordered]@{
ok = $true
fixture = $FixtureZip
partial_evidence_usable = $result.partial_evidence_usable
r6d_gate_pass = $result.r6d_gate_pass
capability_tool_count = $result.capability_tool_count
production_send_enabled = $result.production_send_enabled
} | ConvertTo-Json -Depth 4 -Compress

View File

@@ -0,0 +1,165 @@
param(
[Parameter(Mandatory=$true)]
[string]$PackagePath,
[string]$WorkDir = ""
)
$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)
}
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)
$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") }
[ordered]@{
ok = $true
package = $resolvedPackage
input_file_name = $inputsFile.Name
partial_evidence_usable = $partialUsable
r6d_gate_pass = $r6dGatePass
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
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 ($r6dGatePass) { "full_gate_pass" } elseif ($partialUsable) { "partial_use_only" } else { "not_usable" }
} | ConvertTo-Json -Depth 6 -Compress
}
finally {
if ($cleanup -and (Test-Path -LiteralPath $WorkDir)) {
Remove-Item -LiteralPath $WorkDir -Recurse -Force
}
}