docs: accept partial send sandbox evidence
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
61
scripts/test-validate-returned-send-sandbox-package.ps1
Normal file
61
scripts/test-validate-returned-send-sandbox-package.ps1
Normal 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
|
||||
165
scripts/validate-returned-send-sandbox-package.ps1
Normal file
165
scripts/validate-returned-send-sandbox-package.ps1
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user