From 93c1d59809606345d16a0d9a72952850313f2e98 Mon Sep 17 00:00:00 2001 From: zhaoyilun Date: Sun, 12 Jul 2026 12:55:53 +0800 Subject: [PATCH] tools: package login reachability probe --- docs/go-mcp-minimal-plan.md | 10 + ...-07-12-login-reachability-probe-package.md | 95 ++ .../capability-source-matrix.md | 5 +- scripts/package-login-reachability-probe.ps1 | 869 ++++++++++++++++++ ...erify-login-reachability-probe-package.ps1 | 224 +++++ 5 files changed, 1201 insertions(+), 2 deletions(-) create mode 100644 docs/source-discovery/2026-07-12-login-reachability-probe-package.md create mode 100644 scripts/package-login-reachability-probe.ps1 create mode 100644 scripts/verify-login-reachability-probe-package.ps1 diff --git a/docs/go-mcp-minimal-plan.md b/docs/go-mcp-minimal-plan.md index 2a5704c..6bdb8ba 100644 --- a/docs/go-mcp-minimal-plan.md +++ b/docs/go-mcp-minimal-plan.md @@ -21,6 +21,9 @@ - `docs/source-discovery/2026-07-12-login-reachability-validation-items.md` - `docs/source-discovery/2026-07-12-send-upload-deep-dive.md` - `docs/source-discovery/2026-07-12-inprocess-adapter-static-map.md` +- `docs/source-discovery/2026-07-12-login-reachability-probe-package.md` +- `scripts/package-login-reachability-probe.ps1` +- `scripts/verify-login-reachability-probe-package.ps1` - `docs/source-discovery/capability-source-matrix.md` 本节点通过条件: @@ -997,3 +1000,10 @@ Use `docs/source-discovery/2026-07-12-login-reachability-validation-items.md` as ``` Business status: contact search, group search, receive messages, and receive file-list remain the stable MCP foundation. `isphere_send_message` and `isphere_send_file` are implemented as preview/contract paths, but production send/file remains blocked until logged-in B-route reachability and sent-record/ack correlation pass. N12-pre/offline evidence remains pre-gate context. Existing UIA selector/report/RPA work is auxiliary evidence and fallback support only; the project core is MCP communication capability for contacts, groups, messages, and files. + +Prepared package: + +```text +runs/login-reachability-probe-package.zip +C:\Users\zhaoy\Downloads\login-reachability-probe-package-20260712-125451.zip +``` diff --git a/docs/source-discovery/2026-07-12-login-reachability-probe-package.md b/docs/source-discovery/2026-07-12-login-reachability-probe-package.md new file mode 100644 index 0000000..0d2c62e --- /dev/null +++ b/docs/source-discovery/2026-07-12-login-reachability-probe-package.md @@ -0,0 +1,95 @@ +# Login Reachability Probe Package + +Date: 2026-07-12 + +## Business purpose + +This package turns the login validation schema into an operator-runnable package for the 2026-07-13 logged-in environment. + +It is for B-route evidence collection. It does not enable production send. + +## Generated artifacts + +- Package script: `scripts/package-login-reachability-probe.ps1` +- Verification script: `scripts/verify-login-reachability-probe-package.ps1` +- Package directory: `runs/login-reachability-probe-package` +- Package zip: `runs/login-reachability-probe-package.zip` +- Downloads copy: `C:\Users\zhaoy\Downloads\login-reachability-probe-package-20260712-125451.zip` + +## What the package contains + +- `recorder/ISphereLiveProbeRecorder.exe` +- `tools/isphere-capability-smoke.exe` +- `LOGIN-REACHABILITY-INPUTS.template.json` +- `LOGIN-REACHABILITY-INPUTS.schema.json` +- `CREATE-RETURN-ZIP.ps1` +- `RUN-LOGIN-REACHABILITY-SUITE.bat` +- numbered operator steps: + - `00-prepare-inputs.bat` + - `01-run-before-recorder.bat` + - `02-run-after-text-recorder.bat` + - `03-run-after-file-recorder.bat` + - `04-create-return-zip.bat` +- copied reference docs: + - `docs/login-reachability-validation-items.md` + - `docs/send-upload-deep-dive.md` + +## Operator flow + +1. Run only on a machine where iSphere/IMPlatformClient is visibly logged in. +2. Fill `LOGIN-REACHABILITY-INPUTS.json` from the template. +3. Run before-recorder. +4. If text test is enabled, manually send exactly one text message in the official client, then run after-text-recorder. +5. If file test is enabled, manually send exactly one safe small file in the official client, then run after-file-recorder. +6. Run `CREATE-RETURN-ZIP.ps1` or `04-create-return-zip.bat`. +7. Return `isphere-login-reachability-return-*.zip`. + +## Required returned schema + +The return script writes `LOGIN-REACHABILITY-OUTPUT.json` with these top-level sections: + +- `run` +- `targets` +- `runtime` +- `reachability` +- `server_auth` +- `snapshots` +- `attempts` +- `failures` +- `correlation` + +## Safety and route status + +- `NO_AUTOMATIC_SEND=true`: the package does not send text or files automatically. +- `production_send_enabled=false` +- `production_file_send_enabled=false` +- A-route/RPA remains `backup_only`. +- B-route remains blocked until returned evidence proves logged-in reachability and sent-record/ack correlation. + +## Verification + +Fresh verification command: + +```powershell +powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-login-reachability-probe-package.ps1 +``` + +Verified result: + +```json +{ + "ok": true, + "required_file_count": 21, + "synthetic_return_verified": true, + "online_login_required": true, + "no_automatic_send": true, + "production_send_enabled": false, + "production_file_send_enabled": false +} +``` + +## Next decision after returned zip + +- If text evidence includes one-shot target, before/after sent-record source, content hash, and ack/ref correlation, move text B-route from contract/probe toward production connector design. +- If file evidence includes FileUploadPara/server/auth data, IFileTransfer reachability, FileUploadResult/FileID or structured failure, and file sent-record/ack correlation, move file B-route to upload connector design. +- If reachability fails with structured reasons, continue offline reverse against the failed step. diff --git a/docs/source-discovery/capability-source-matrix.md b/docs/source-discovery/capability-source-matrix.md index e79c822..f4bddd1 100644 --- a/docs/source-discovery/capability-source-matrix.md +++ b/docs/source-discovery/capability-source-matrix.md @@ -25,6 +25,7 @@ Offline B-route decision: `docs/source-discovery/2026-07-12-offline-broute-rever In-process adapter static map: `docs/source-discovery/2026-07-12-inprocess-adapter-static-map.md` Send/upload deep dive: `docs/source-discovery/2026-07-12-send-upload-deep-dive.md` Login reachability validation items: `docs/source-discovery/2026-07-12-login-reachability-validation-items.md` +Login reachability probe package: `docs/source-discovery/2026-07-12-login-reachability-probe-package.md` ## Source priority @@ -45,8 +46,8 @@ Route rule: UIA helper / A-route / RPA is a backup route only. It can preserve d | `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\` 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`; `docs/source-discovery/2026-07-10-file-download-mapping-v2.md`; `docs/reports/2026-07-10-business-goals-smoke.md` | C22 verifies safe file-list contract args; R10 proves resolver scoring with fixture accepted matches; R11 adds structured download preview with `blocked`/`planned` status and side-effect flags false; R13/R14 confirms list is ready but real download is still blocked. | blocked for real download pending accepted real cache mapping and copy gate; list mode is complete | -| `isphere_send_message` | B-route selected: `in_process_adapter_research` after offline reverse. Static call path is confirmed through `AppContextManager.SendTxtMessageByJid(...)`, `MessageScheduling.SendChatMessage(...)`, `Chat.SendMessage`, and `XMPPConnection.send`, but no existing IPC/plugin/local HTTP send bridge is confirmed; default MCP tool still exposes preview/dry-run unless an explicit connector mode is configured | A-route fallback is now implemented for UI action: `ISPHERE_SEND_CONNECTOR_MODE=uia_rpa` calls WinHelper `uia_send_message` against a configured HWND, writes `rtbSendMessage`, and clicks `btnSend`; this stays backup-only and 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`; `docs/source-discovery/2026-07-10-returned-send-sandbox-analysis-v2.md`; `docs/source-discovery/2026-07-10-returned-send-sent-record-diagnostic.md`; `docs/source-discovery/2026-07-11-a-route-uia-send.md`; `docs/source-discovery/2026-07-11-a-route-open-real-chat-window.md`; `docs/source-discovery/2026-07-12-broute-text-send-static-map.md`; `docs/source-discovery/2026-07-12-broute-bridge-static-map.md`; `docs/source-discovery/2026-07-12-offline-broute-reverse-decision.md`; `docs/source-discovery/2026-07-12-inprocess-adapter-static-map.md`; `docs/source-discovery/2026-07-12-send-upload-deep-dive.md`; `docs/source-discovery/2026-07-12-login-reachability-validation-items.md`; `internal/tools/isphere_send_message_test.go`; `cmd/isphere-capability-smoke/main.go`; `docs/reports/2026-07-10-business-goals-smoke.md` | R6f-R6l define connector contract, audit/idempotency replay, production gate, B-route shell, strict-v2 package, and explicit production closure. R10a validates two returned sent-record packages as manual-only evidence. A-route adds local UI action readiness: synthetic window proof plus real offline `frmP2PChat` open/write/click target proof. Offline B-route reverse confirms the send call path but rejects existing IPC/plugin-first for this evidence set; business delivery is still not verified because local runtime is offline and no success/ack or sent-record hash evidence has passed. | B-route now exposes only the `in_process_contract` connector mode for request-shape/audit validation; next work is the 2026-07-13 login reachability probe using `docs/source-discovery/2026-07-12-login-reachability-validation-items.md` as the active schema: one-shot direct/group target rule, AppContextManager/IAppContextManager and Chat/XMPPConnection reachability, in-process adapter reachability mode, before/after sent-record snapshots, sent-record source identification, structured failure reasons, and content-hash/ack correlation; keep external sidecar preview/probe-only and A-route UI action backup-only; production remains blocked until those P0 items pass | -| `isphere_send_file` | B-route selected after offline reverse: managed file send is upload-first through `OffLineFileSend.sendFile()`, `FileUploadPara`, `IMPP.Service*.dll` `IFileTransfer.FileUpload(...)`, then chat file-message finalization through `trans_UploadCompleted(fileId)`, `MessageScheduling.SendFileMessage`, and `Chat.sendFileMessage(...)`; current MCP tool exposes preview/dry-run only | A-route file fallback is not implemented yet; real offline UIA dump did not expose stable `btnSendFile`, so the fallback slice remains toolbar/menu locator discovery; native `TcpFileTransfer.dll` is not the first B-route bridge because managed upload/message flow is now mapped | `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-file-sandbox-gate.md`; `docs/source-discovery/2026-07-11-a-route-open-real-chat-window.md`; `docs/source-discovery/2026-07-12-broute-file-send-static-map.md`; `docs/source-discovery/2026-07-12-broute-bridge-static-map.md`; `docs/source-discovery/2026-07-12-offline-broute-reverse-decision.md`; `docs/source-discovery/2026-07-12-inprocess-adapter-static-map.md`; `docs/source-discovery/2026-07-12-send-upload-deep-dive.md`; `docs/source-discovery/2026-07-12-login-reachability-validation-items.md`; `internal/tools/isphere_files_test.go`; `docs/reports/2026-07-10-business-goals-smoke.md` | R7 registers send-file preview as the tenth MCP tool; R8 adds audit/idempotency duplicate/conflict hardening without raw file paths/content; R9 builds and verifies the online send-file evidence package; R13/R14 confirms preview is ready and production upload remains blocked. Offline B-route reverse confirms upload-first/file-message-second and shows file id/auth/runtime reachability are the blockers. A-route real-window work confirms file-button automation still needs a separate locator node. | blocked until the login reachability probe proves `FileUploadPara` host/port/domain/local path/token presence or hash-only token evidence, logged-in access to `IFileTransfer.FileUpload`, returned `FileUploadResult.FileID`, `trans_UploadCompleted(fileId)` / `Chat.sendFileMessage` finalization, structured failure reasons, before/after sent-record snapshots, and file hash/ack correlation; production file upload/send remains blocked until online upload plus sent-record/ack evidence exists; A-route locator remains backup-only | +| `isphere_send_message` | B-route selected: `in_process_adapter_research` after offline reverse. Static call path is confirmed through `AppContextManager.SendTxtMessageByJid(...)`, `MessageScheduling.SendChatMessage(...)`, `Chat.SendMessage`, and `XMPPConnection.send`, but no existing IPC/plugin/local HTTP send bridge is confirmed; default MCP tool still exposes preview/dry-run unless an explicit connector mode is configured | A-route fallback is now implemented for UI action: `ISPHERE_SEND_CONNECTOR_MODE=uia_rpa` calls WinHelper `uia_send_message` against a configured HWND, writes `rtbSendMessage`, and clicks `btnSend`; this stays backup-only and 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`; `docs/source-discovery/2026-07-10-returned-send-sandbox-analysis-v2.md`; `docs/source-discovery/2026-07-10-returned-send-sent-record-diagnostic.md`; `docs/source-discovery/2026-07-11-a-route-uia-send.md`; `docs/source-discovery/2026-07-11-a-route-open-real-chat-window.md`; `docs/source-discovery/2026-07-12-broute-text-send-static-map.md`; `docs/source-discovery/2026-07-12-broute-bridge-static-map.md`; `docs/source-discovery/2026-07-12-offline-broute-reverse-decision.md`; `docs/source-discovery/2026-07-12-inprocess-adapter-static-map.md`; `docs/source-discovery/2026-07-12-send-upload-deep-dive.md`; `docs/source-discovery/2026-07-12-login-reachability-validation-items.md`; `docs/source-discovery/2026-07-12-login-reachability-probe-package.md`; `internal/tools/isphere_send_message_test.go`; `cmd/isphere-capability-smoke/main.go`; `docs/reports/2026-07-10-business-goals-smoke.md` | R6f-R6l define connector contract, audit/idempotency replay, production gate, B-route shell, strict-v2 package, and explicit production closure. R10a validates two returned sent-record packages as manual-only evidence. A-route adds local UI action readiness: synthetic window proof plus real offline `frmP2PChat` open/write/click target proof. Offline B-route reverse confirms the send call path but rejects existing IPC/plugin-first for this evidence set; business delivery is still not verified because local runtime is offline and no success/ack or sent-record hash evidence has passed. The login reachability package is now built and verified; it produces `LOGIN-REACHABILITY-OUTPUT.json` with run/targets/runtime/reachability/server_auth/snapshots/attempts/failures/correlation. | Run the 2026-07-13 login reachability package in a logged-in environment and return `isphere-login-reachability-return-*.zip`; text production remains blocked until one-shot target, AppContextManager/IAppContextManager or MessageScheduling/Chat reachability, sent-record source, and content-hash/ack correlation pass | +| `isphere_send_file` | B-route selected after offline reverse: managed file send is upload-first through `OffLineFileSend.sendFile()`, `FileUploadPara`, `IMPP.Service*.dll` `IFileTransfer.FileUpload(...)`, then chat file-message finalization through `trans_UploadCompleted(fileId)`, `MessageScheduling.SendFileMessage`, and `Chat.sendFileMessage(...)`; current MCP tool exposes preview/dry-run only | A-route file fallback is not implemented yet; real offline UIA dump did not expose stable `btnSendFile`, so the fallback slice remains toolbar/menu locator discovery; native `TcpFileTransfer.dll` is not the first B-route bridge because managed upload/message flow is now mapped | `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-file-sandbox-gate.md`; `docs/source-discovery/2026-07-11-a-route-open-real-chat-window.md`; `docs/source-discovery/2026-07-12-broute-file-send-static-map.md`; `docs/source-discovery/2026-07-12-broute-bridge-static-map.md`; `docs/source-discovery/2026-07-12-offline-broute-reverse-decision.md`; `docs/source-discovery/2026-07-12-inprocess-adapter-static-map.md`; `docs/source-discovery/2026-07-12-send-upload-deep-dive.md`; `docs/source-discovery/2026-07-12-login-reachability-validation-items.md`; `docs/source-discovery/2026-07-12-login-reachability-probe-package.md`; `internal/tools/isphere_files_test.go`; `docs/reports/2026-07-10-business-goals-smoke.md` | R7 registers send-file preview as the tenth MCP tool; R8 adds audit/idempotency duplicate/conflict hardening without raw file paths/content; R9 builds and verifies the online send-file evidence package; R13/R14 confirms preview is ready and production upload remains blocked. Offline B-route reverse confirms upload-first/file-message-second and shows file id/auth/runtime reachability are the blockers. A-route real-window work confirms file-button automation still needs a separate locator node. The login reachability package is now built and verified with one-shot file fields, structured failure handling, and before/after recorder output validation. | Run the 2026-07-13 login reachability package in a logged-in environment and return `isphere-login-reachability-return-*.zip`; file production remains blocked until FileUploadPara/server/auth, `IFileTransfer.FileUpload`, FileUploadResult/FileID or structured upload failure, file-message finalization, and file sent-record/ack correlation pass | ## Stage C entry rule diff --git a/scripts/package-login-reachability-probe.ps1 b/scripts/package-login-reachability-probe.ps1 new file mode 100644 index 0000000..e92dfcb --- /dev/null +++ b/scripts/package-login-reachability-probe.ps1 @@ -0,0 +1,869 @@ +param( + [string]$OutputDir = "runs/login-reachability-probe-package", + [switch]$NoZip, + [switch]$CopyToDownloads +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +$repo = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "..")).Path +$packageDir = Join-Path $repo $OutputDir +$zipPath = "$packageDir.zip" +$recorderRelative = Join-Path $OutputDir "recorder" +$recorderDir = Join-Path $packageDir "recorder" +$toolsDir = Join-Path $packageDir "tools" +$docsDir = Join-Path $packageDir "docs" +$evidenceDir = Join-Path $packageDir "return-evidence" +$capabilityExe = Join-Path $toolsDir "isphere-capability-smoke.exe" + +if (Test-Path -LiteralPath $packageDir) { + Remove-Item -LiteralPath $packageDir -Recurse -Force +} +if (Test-Path -LiteralPath $zipPath) { + Remove-Item -LiteralPath $zipPath -Force +} + +New-Item -ItemType Directory -Force -Path $packageDir | Out-Null +New-Item -ItemType Directory -Force -Path $toolsDir | Out-Null +New-Item -ItemType Directory -Force -Path $docsDir | Out-Null +New-Item -ItemType Directory -Force -Path $evidenceDir | Out-Null + +& powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $repo "scripts\package-live-probe-recorder.ps1") -OutputDir $recorderRelative -NoZip +if ($LASTEXITCODE -ne 0) { + throw "package-live-probe-recorder.ps1 failed with exit code $LASTEXITCODE" +} +if (-not (Test-Path -LiteralPath (Join-Path $recorderDir "ISphereLiveProbeRecorder.exe"))) { + throw "recorder package was not created under $recorderDir" +} + +Push-Location $repo +try { + & go build -o $capabilityExe ./cmd/isphere-capability-smoke + if ($LASTEXITCODE -ne 0) { + throw "go build ./cmd/isphere-capability-smoke failed with exit code $LASTEXITCODE" + } +} +finally { + Pop-Location +} +if (-not (Test-Path -LiteralPath $capabilityExe)) { + throw "capability smoke executable was not created: $capabilityExe" +} + +Copy-Item -LiteralPath (Join-Path $repo "docs\source-discovery\2026-07-12-login-reachability-validation-items.md") -Destination (Join-Path $docsDir "login-reachability-validation-items.md") -Force +Copy-Item -LiteralPath (Join-Path $repo "docs\source-discovery\2026-07-12-send-upload-deep-dive.md") -Destination (Join-Path $docsDir "send-upload-deep-dive.md") -Force + +@' +iSphere Login Reachability Probe Package + +LOCAL_LOGIN_UNAVAILABLE: 本开发机无法登录 iSphere。 +ONLINE_LOGIN_REQUIRED: 只在可以正常登录 iSphere/IMPlatformClient 的电脑和网络里运行。 +NO_AUTOMATIC_SEND: 本包不会自动发消息、不会自动上传文件。 + +业务目标: +- 明天在可登录环境里一次性采到 B-route 需要的证据; +- 覆盖文字发送、文件发送、sent-record/ack、运行态/服务端参数、失败原因; +- 继续保持生产 send/file send 禁用,直到返回证据通过机器校验。 + +推荐一键流程: +1. 解压本 zip 到普通目录。 +2. 手工打开并登录 iSphere/IMPlatformClient。 +3. 准备一个 direct 测试目标,可选准备一个 group 测试目标。 +4. 准备一段一次性测试文字和一个安全小文件。 +5. 双击 00-prepare-inputs.bat,按说明填写 LOGIN-REACHABILITY-INPUTS.json。 +6. 双击 RUN-LOGIN-REACHABILITY-SUITE.bat,按暂停提示操作。 +7. 把生成的 isphere-login-reachability-return-*.zip 发回。 + +分步流程: +- 01-run-before-recorder.bat:发送/上传前采集。 +- 02-run-after-text-recorder.bat:手工发送一次文字后采集。 +- 03-run-after-file-recorder.bat:手工发送一次文件后采集。 +- 04-create-return-zip.bat:生成返回 zip。 + +它会记录: +- 目标进程、位数、模块、窗口、UIA 控件树; +- 发送入口静态可达性; +- TCP 连接、安装/缓存/配置/日志/数据库候选文件元数据; +- operator 填写的 one-shot 目标、hash、sent-record/ack、结构化失败原因; +- LOGIN-REACHABILITY-OUTPUT.json 顶层 schema:run/targets/runtime/reachability/server_auth/snapshots/attempts/failures/correlation。 + +它不会: +- 自动发送消息; +- 自动发送或上传文件; +- 点击或输入; +- 注入或 hook; +- 修改客户端文件、数据库或配置; +- 收集明文消息正文或 token。 +'@ | Set-Content -LiteralPath (Join-Path $packageDir "README.txt") -Encoding UTF8 + +@' +# 2026-07-13 登录可达性探针 - 小白使用说明 + +## 先记住三句话 + +1. 必须先手工登录 iSphere/IMPlatformClient。 +2. 本工具不会帮你发送,只负责前后采集;发送动作由你在官方客户端里手工做。 +3. 文字只发一次,文件只发一次;不要重复发。 + +## 第一步:准备输入 + +双击: + +```text +00-prepare-inputs.bat +``` + +它会复制模板并打开 `LOGIN-REACHABILITY-INPUTS.json`。 + +你至少要填: + +- `run.operator_confirmed_logged_in`: true +- `run.one_shot_text_enabled`: true 或 false +- `run.one_shot_file_enabled`: true 或 false +- `targets.direct_jid`: direct 测试对象 +- `targets.group_jid`: 可选群组测试对象 +- `targets.target_type`: direct 或 group + +如果要测文字: + +```powershell +powershell -NoProfile -ExecutionPolicy Bypass -File .\compute-text-hash.ps1 -Text "你准备发的那句测试文字" +``` + +把输出填到: + +- `text_attempt.content_sha256` +- `text_attempt.content_length` + +不要把明文测试文字写进 JSON。 + +如果要测文件: + +```powershell +powershell -NoProfile -ExecutionPolicy Bypass -File .\compute-file-hash.ps1 -Path "C:\path\to\test-file.txt" +``` + +把输出里的 hash/size 填进: + +- `file_attempt.sandbox_file_path` +- `file_attempt.file_sha256` +- `file_attempt.file_size_bytes` + +## 第二步:运行 before 采集 + +双击: + +```text +01-run-before-recorder.bat +``` + +看到完成后,确认 `return-evidence\before` 里有文件。 + +## 第三步:手工发送一次文字 + +如果 `run.one_shot_text_enabled=true`: + +1. 在官方 iSphere 客户端里打开目标会话。 +2. 手工发送刚才计算 hash 的那一句测试文字。 +3. 不要发第二次。 +4. 填写 `LOGIN-REACHABILITY-INPUTS.json` 里的: + - `run.one_text_send_only_confirmed`: true + - `text_attempt.operator_clicked_send_at_local` + - `text_attempt.success_ack_or_sent_record_present` + - `text_attempt.success_ack_or_sent_record` + - `text_attempt.duplicate_second_send_attempted`: false + +如果失败,也要填 `text_attempt.failure` 里的 step/code/classification/message/next_action。 + +然后双击: + +```text +02-run-after-text-recorder.bat +``` + +## 第四步:手工发送一次文件 + +如果 `run.one_shot_file_enabled=true`: + +1. 在官方 iSphere 客户端里打开目标会话。 +2. 手工发送刚才计算 hash 的那一个安全小文件。 +3. 不要发第二个文件,也不要重复发同一个文件。 +4. 填写 `LOGIN-REACHABILITY-INPUTS.json` 里的: + - `run.one_file_send_only_confirmed`: true + - `file_attempt.operator_clicked_send_file_at_local` + - `file_attempt.file_upload_or_sent_record_present` + - `file_attempt.file_upload_or_sent_record` + - `file_attempt.duplicate_second_file_send_attempted`: false + +如果失败,也要填 `file_attempt.failure` 里的 step/code/classification/message/next_action。 + +然后双击: + +```text +03-run-after-file-recorder.bat +``` + +## 第五步:生成返回包 + +双击: + +```text +04-create-return-zip.bat +``` + +它会生成: + +```text +isphere-login-reachability-return-时间戳.zip +``` + +把这个 zip 发回。 + +## 如果你不知道怎么填失败分类 + +常用 classification: + +- offline +- permission +- target_not_found +- token_missing +- file_size +- file_type +- network_timeout +- upload_failed +- ack_missing +- sent_record_missing +- unknown +'@ | Set-Content -LiteralPath (Join-Path $packageDir "OPERATOR-STEPS.md") -Encoding UTF8 + +@' +{ + "schema_version": "2026-07-13.login-reachability.v1", + "run": { + "operator_confirmed_logged_in": false, + "operator_started_at_local": "", + "operator_finished_at_local": "", + "before_recorder_present": false, + "one_shot_text_enabled": true, + "one_shot_file_enabled": true, + "one_text_send_only_confirmed": false, + "one_file_send_only_confirmed": false, + "production_send_enabled": false, + "production_file_send_enabled": false + }, + "targets": { + "target_type": "direct", + "direct_jid": "", + "group_jid": "", + "target_selection_source": "", + "self_send_or_sandbox_account_confirmed": false + }, + "adapter_reachability": { + "mode_observed": "unknown", + "same_process_adapter_possible": "unknown", + "managed_harness_required": "unknown", + "injection_required": "unknown", + "stop_condition_reason": "" + }, + "server_auth": { + "host": "", + "port": "", + "domain": "", + "token_present": null, + "token_hash_prefix": "", + "access_token_present": null, + "client_id_present": null + }, + "sent_record_source": { + "source_type": "", + "source_path_or_ref": "", + "before_count": "", + "after_count": "", + "ack_or_ref_id": "", + "correlation_notes": "" + }, + "text_attempt": { + "enabled": true, + "content_sha256": "", + "content_length": 0, + "operator_clicked_send_at_local": "", + "after_recorder_present": false, + "success_ack_or_sent_record_present": false, + "success_ack_or_sent_record": "", + "duplicate_second_send_attempted": false, + "failure": { + "step": "", + "code": "", + "classification": "", + "message": "", + "next_action": "" + } + }, + "file_attempt": { + "enabled": true, + "allowed_dir": "", + "sandbox_file_path": "", + "file_sha256": "", + "file_size_bytes": 0, + "operator_clicked_send_file_at_local": "", + "after_recorder_present": false, + "file_upload_or_sent_record_present": false, + "file_upload_or_sent_record": "", + "duplicate_second_file_send_attempted": false, + "failure": { + "step": "", + "code": "", + "classification": "", + "message": "", + "next_action": "" + } + }, + "operator_notes": "" +} +'@ | Set-Content -LiteralPath (Join-Path $packageDir "LOGIN-REACHABILITY-INPUTS.template.json") -Encoding UTF8 + +@' +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "iSphere login reachability probe input", + "type": "object", + "required": ["schema_version", "run", "targets", "text_attempt", "file_attempt"], + "properties": { + "schema_version": { "const": "2026-07-13.login-reachability.v1" }, + "run": { + "type": "object", + "required": ["operator_confirmed_logged_in", "before_recorder_present", "one_shot_text_enabled", "one_shot_file_enabled", "production_send_enabled", "production_file_send_enabled"], + "properties": { + "operator_confirmed_logged_in": { "type": "boolean" }, + "before_recorder_present": { "type": "boolean" }, + "one_shot_text_enabled": { "type": "boolean" }, + "one_shot_file_enabled": { "type": "boolean" }, + "production_send_enabled": { "const": false }, + "production_file_send_enabled": { "const": false } + } + }, + "targets": { + "type": "object", + "required": ["target_type", "direct_jid"], + "properties": { + "target_type": { "type": "string" }, + "direct_jid": { "type": "string" }, + "group_jid": { "type": "string" } + } + }, + "text_attempt": { "type": "object" }, + "file_attempt": { "type": "object" } + } +} +'@ | Set-Content -LiteralPath (Join-Path $packageDir "LOGIN-REACHABILITY-INPUTS.schema.json") -Encoding UTF8 + +@' +Expected return files: + +Required: +- LOGIN-REACHABILITY-INPUTS.json +- LOGIN-REACHABILITY-OUTPUT.json +- return-evidence/before/* + +Required when text_attempt.enabled=true: +- return-evidence/after-text/* +- text_attempt.content_sha256 +- text_attempt.operator_clicked_send_at_local +- text_attempt.duplicate_second_send_attempted=false +- either text_attempt.success_ack_or_sent_record_present=true plus success text, or structured text_attempt.failure + +Required when file_attempt.enabled=true: +- return-evidence/after-file/* +- file_attempt.sandbox_file_path +- file_attempt.file_sha256 +- file_attempt.file_size_bytes +- file_attempt.duplicate_second_file_send_attempted=false +- either file_attempt.file_upload_or_sent_record_present=true plus result text, or structured file_attempt.failure + +Decision: +- Production text send remains blocked unless one-shot target + before/after snapshot + sent-record source + content-hash/ack correlation pass. +- Production file send remains blocked unless FileUploadPara + IFileTransfer/FileUpload + FileUploadResult.FileID + file-message finalization + file sent-record/ack correlation pass. +- A-route/RPA remains backup-only. +'@ | Set-Content -LiteralPath (Join-Path $packageDir "EXPECTED-RETURN-FILES.txt") -Encoding UTF8 + +@' +@echo off +setlocal +cd /d "%~dp0" +if not exist LOGIN-REACHABILITY-INPUTS.json copy LOGIN-REACHABILITY-INPUTS.template.json LOGIN-REACHABILITY-INPUTS.json >nul +echo 已创建/确认 LOGIN-REACHABILITY-INPUTS.json +echo 请按 OPERATOR-STEPS.md 填写,然后保存。 +notepad LOGIN-REACHABILITY-INPUTS.json +pause +'@ | Set-Content -LiteralPath (Join-Path $packageDir "00-prepare-inputs.bat") -Encoding ASCII + +@' +@echo off +setlocal +cd /d "%~dp0" +mkdir return-evidence 2>nul +mkdir return-evidence\before 2>nul +echo Running BEFORE read-only recorder... +recorder\ISphereLiveProbeRecorder.exe --record-live-probe --out "%~dp0return-evidence\before" --no-pause +echo before recorder exit code: %ERRORLEVEL% > return-evidence\before\exit-code.txt +echo Done. Check return-evidence\before. +pause +exit /b 0 +'@ | Set-Content -LiteralPath (Join-Path $packageDir "01-run-before-recorder.bat") -Encoding ASCII + +@' +@echo off +setlocal +cd /d "%~dp0" +mkdir return-evidence 2>nul +mkdir return-evidence\after-text 2>nul +echo Run this AFTER the one manual text send. +recorder\ISphereLiveProbeRecorder.exe --record-live-probe --out "%~dp0return-evidence\after-text" --no-pause +echo after text recorder exit code: %ERRORLEVEL% > return-evidence\after-text\exit-code.txt +echo Done. Check return-evidence\after-text. +pause +exit /b 0 +'@ | Set-Content -LiteralPath (Join-Path $packageDir "02-run-after-text-recorder.bat") -Encoding ASCII + +@' +@echo off +setlocal +cd /d "%~dp0" +mkdir return-evidence 2>nul +mkdir return-evidence\after-file 2>nul +echo Run this AFTER the one manual file send. +recorder\ISphereLiveProbeRecorder.exe --record-live-probe --out "%~dp0return-evidence\after-file" --no-pause +echo after file recorder exit code: %ERRORLEVEL% > return-evidence\after-file\exit-code.txt +echo Done. Check return-evidence\after-file. +pause +exit /b 0 +'@ | Set-Content -LiteralPath (Join-Path $packageDir "03-run-after-file-recorder.bat") -Encoding ASCII + +@' +@echo off +setlocal +cd /d "%~dp0" +powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0CREATE-RETURN-ZIP.ps1" +echo exit code: %ERRORLEVEL% +pause +exit /b %ERRORLEVEL% +'@ | Set-Content -LiteralPath (Join-Path $packageDir "04-create-return-zip.bat") -Encoding ASCII + +@' +@echo off +setlocal +cd /d "%~dp0" +echo iSphere login reachability recording suite +echo. +call "%~dp000-prepare-inputs.bat" +echo. +echo Step 1: before recorder +call "%~dp001-run-before-recorder.bat" +echo. +echo Step 2: if text_attempt.enabled=true, manually send exactly one text message in official iSphere. +echo Fill text_attempt fields in LOGIN-REACHABILITY-INPUTS.json before continuing. +pause +call "%~dp002-run-after-text-recorder.bat" +echo. +echo Step 3: if file_attempt.enabled=true, manually send exactly one safe small file in official iSphere. +echo Fill file_attempt fields in LOGIN-REACHABILITY-INPUTS.json before continuing. +pause +call "%~dp003-run-after-file-recorder.bat" +echo. +echo Step 4: create return zip +call "%~dp004-create-return-zip.bat" +exit /b %ERRORLEVEL% +'@ | Set-Content -LiteralPath (Join-Path $packageDir "RUN-LOGIN-REACHABILITY-SUITE.bat") -Encoding ASCII + +@' +param( + [Parameter(Mandatory=$true)] + [string]$Text +) + +$bytes = [System.Text.Encoding]::UTF8.GetBytes($Text) +$sha = [System.Security.Cryptography.SHA256]::Create() +try { + $hash = $sha.ComputeHash($bytes) + [ordered]@{ + content_sha256 = (-join ($hash | ForEach-Object { $_.ToString("x2") })) + content_length = $Text.Length + note = "只把 hash 和长度填入 JSON,不要填写明文内容。" + } | ConvertTo-Json -Depth 4 +} +finally { + $sha.Dispose() +} +'@ | Set-Content -LiteralPath (Join-Path $packageDir "compute-text-hash.ps1") -Encoding ASCII + +@' +param( + [Parameter(Mandatory=$true)] + [string]$Path +) + +$resolved = Resolve-Path -LiteralPath $Path +$item = Get-Item -LiteralPath $resolved.Path +[ordered]@{ + sandbox_file_path = $resolved.Path + file_sha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $resolved.Path).Hash.ToLowerInvariant() + file_size_bytes = $item.Length + note = "确认这是安全小文件,并且只发送一次。" +} | ConvertTo-Json -Depth 4 +'@ | Set-Content -LiteralPath (Join-Path $packageDir "compute-file-hash.ps1") -Encoding ASCII + +@' +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +$root = Split-Path -Parent $MyInvocation.MyCommand.Path +$inputsPath = Join-Path $root "LOGIN-REACHABILITY-INPUTS.json" +$outputPath = Join-Path $root "LOGIN-REACHABILITY-OUTPUT.json" +$evidenceRoot = Join-Path $root "return-evidence" + +function Test-NonEmptyString($Value) { + if ($null -eq $Value) { return $false } + return -not [string]::IsNullOrWhiteSpace([string]$Value) +} + +function Test-Hash64($Value) { + return (Test-NonEmptyString $Value) -and ([string]$Value -match '^[0-9a-fA-F]{64}$') +} + +function Test-DirectoryHasFiles($Path) { + return (Test-Path -LiteralPath $Path) -and (@(Get-ChildItem -LiteralPath $Path -Recurse -File -ErrorAction SilentlyContinue).Count -gt 0) +} + +function Find-LatestProbeFile($Stage, $Name) { + $stageDir = Join-Path $evidenceRoot $Stage + if (-not (Test-Path -LiteralPath $stageDir)) { return $null } + $file = Get-ChildItem -LiteralPath $stageDir -Recurse -File -Filter $Name -ErrorAction SilentlyContinue | + Sort-Object LastWriteTime -Descending | + Select-Object -First 1 + if ($null -eq $file) { return $null } + return $file.FullName +} + +function Read-ProbeJson($Stage, $Name) { + $path = Find-LatestProbeFile -Stage $Stage -Name $Name + if ($null -eq $path) { return $null } + try { + return Get-Content -LiteralPath $path -Raw | ConvertFrom-Json + } + catch { + return $null + } +} + +function Get-EntrypointMap($Preflight) { + $map = [ordered]@{} + if ($null -eq $Preflight -or $null -eq $Preflight.entrypoints) { return $map } + foreach ($entry in @($Preflight.entrypoints)) { + $map[[string]$entry.id] = [ordered]@{ + capability = $entry.capability + assembly_found = $entry.assembly_found + reflection_type_found = $entry.reflection_type_found + reflection_method_found = $entry.reflection_method_found + string_type_hint = $entry.string_type_hint + string_method_hint = $entry.string_method_hint + available = $entry.available + reflection_error = $entry.reflection_error + } + } + return $map +} + +function Add-ValidationFailure([System.Collections.Generic.List[object]]$List, [string]$Code, [string]$Message) { + $List.Add([ordered]@{ + step = "return_zip_validation" + code = $Code + classification = "missing_or_invalid_input" + message = $Message + next_action = "补齐 LOGIN-REACHABILITY-INPUTS.json 或对应 return-evidence 目录后重新运行 04-create-return-zip.bat" + }) | Out-Null +} + +if (-not (Test-Path -LiteralPath $inputsPath)) { + throw "missing LOGIN-REACHABILITY-INPUTS.json; run 00-prepare-inputs.bat first" +} +if (-not (Test-Path -LiteralPath $evidenceRoot)) { + throw "missing return-evidence directory" +} + +$inputs = Get-Content -LiteralPath $inputsPath -Raw | ConvertFrom-Json +$validationFailures = New-Object System.Collections.Generic.List[object] + +if ($inputs.schema_version -ne "2026-07-13.login-reachability.v1") { + Add-ValidationFailure $validationFailures "BAD_SCHEMA_VERSION" "schema_version must be 2026-07-13.login-reachability.v1" +} +if ($inputs.run.operator_confirmed_logged_in -ne $true) { + Add-ValidationFailure $validationFailures "LOGIN_NOT_CONFIRMED" "run.operator_confirmed_logged_in must be true" +} +if ($inputs.run.production_send_enabled -ne $false) { + Add-ValidationFailure $validationFailures "PRODUCTION_SEND_ENABLED" "production_send_enabled must remain false" +} +if ($inputs.run.production_file_send_enabled -ne $false) { + Add-ValidationFailure $validationFailures "PRODUCTION_FILE_SEND_ENABLED" "production_file_send_enabled must remain false" +} +if ($inputs.run.before_recorder_present -ne $true) { + Add-ValidationFailure $validationFailures "BEFORE_RECORDER_FLAG_MISSING" "run.before_recorder_present must be true" +} +if (-not (Test-DirectoryHasFiles (Join-Path $evidenceRoot "before"))) { + Add-ValidationFailure $validationFailures "BEFORE_EVIDENCE_MISSING" "return-evidence/before must contain recorder output" +} +if (-not (Test-NonEmptyString $inputs.targets.direct_jid)) { + Add-ValidationFailure $validationFailures "DIRECT_TARGET_MISSING" "targets.direct_jid is required" +} + +$textEnabled = ($inputs.run.one_shot_text_enabled -eq $true) -or ($inputs.text_attempt.enabled -eq $true) +if ($textEnabled) { + if ($inputs.run.one_text_send_only_confirmed -ne $true) { + Add-ValidationFailure $validationFailures "TEXT_ONE_SHOT_NOT_CONFIRMED" "run.one_text_send_only_confirmed must be true" + } + if (-not (Test-Hash64 $inputs.text_attempt.content_sha256)) { + Add-ValidationFailure $validationFailures "TEXT_HASH_MISSING" "text_attempt.content_sha256 must be a 64-char SHA256" + } + if ($inputs.text_attempt.content_length -le 0) { + Add-ValidationFailure $validationFailures "TEXT_LENGTH_MISSING" "text_attempt.content_length must be greater than 0" + } + if (-not (Test-NonEmptyString $inputs.text_attempt.operator_clicked_send_at_local)) { + Add-ValidationFailure $validationFailures "TEXT_CLICK_TIME_MISSING" "text_attempt.operator_clicked_send_at_local is required" + } + if ($inputs.text_attempt.after_recorder_present -ne $true) { + Add-ValidationFailure $validationFailures "TEXT_AFTER_FLAG_MISSING" "text_attempt.after_recorder_present must be true" + } + if (-not (Test-DirectoryHasFiles (Join-Path $evidenceRoot "after-text"))) { + Add-ValidationFailure $validationFailures "TEXT_AFTER_EVIDENCE_MISSING" "return-evidence/after-text must contain recorder output" + } + if ($inputs.text_attempt.duplicate_second_send_attempted -ne $false) { + Add-ValidationFailure $validationFailures "TEXT_DUPLICATE_ATTEMPTED" "text_attempt.duplicate_second_send_attempted must be false" + } + $textSuccess = ($inputs.text_attempt.success_ack_or_sent_record_present -eq $true) -and (Test-NonEmptyString $inputs.text_attempt.success_ack_or_sent_record) + $textFailure = (Test-NonEmptyString $inputs.text_attempt.failure.step) -and (Test-NonEmptyString $inputs.text_attempt.failure.code) -and (Test-NonEmptyString $inputs.text_attempt.failure.classification) -and (Test-NonEmptyString $inputs.text_attempt.failure.next_action) + if (-not $textSuccess -and -not $textFailure) { + Add-ValidationFailure $validationFailures "TEXT_RESULT_OR_FAILURE_MISSING" "text attempt needs either success ack/sent-record or structured failure" + } +} + +$fileEnabled = ($inputs.run.one_shot_file_enabled -eq $true) -or ($inputs.file_attempt.enabled -eq $true) +if ($fileEnabled) { + if ($inputs.run.one_file_send_only_confirmed -ne $true) { + Add-ValidationFailure $validationFailures "FILE_ONE_SHOT_NOT_CONFIRMED" "run.one_file_send_only_confirmed must be true" + } + foreach ($field in @("allowed_dir", "sandbox_file_path", "operator_clicked_send_file_at_local")) { + if (-not (Test-NonEmptyString $inputs.file_attempt.$field)) { + Add-ValidationFailure $validationFailures ("FILE_" + $field.ToUpperInvariant() + "_MISSING") "file_attempt.$field is required" + } + } + if (-not (Test-Hash64 $inputs.file_attempt.file_sha256)) { + Add-ValidationFailure $validationFailures "FILE_HASH_MISSING" "file_attempt.file_sha256 must be a 64-char SHA256" + } + if ($inputs.file_attempt.file_size_bytes -le 0) { + Add-ValidationFailure $validationFailures "FILE_SIZE_MISSING" "file_attempt.file_size_bytes must be greater than 0" + } + if ($inputs.file_attempt.after_recorder_present -ne $true) { + Add-ValidationFailure $validationFailures "FILE_AFTER_FLAG_MISSING" "file_attempt.after_recorder_present must be true" + } + if (-not (Test-DirectoryHasFiles (Join-Path $evidenceRoot "after-file"))) { + Add-ValidationFailure $validationFailures "FILE_AFTER_EVIDENCE_MISSING" "return-evidence/after-file must contain recorder output" + } + if ($inputs.file_attempt.duplicate_second_file_send_attempted -ne $false) { + Add-ValidationFailure $validationFailures "FILE_DUPLICATE_ATTEMPTED" "file_attempt.duplicate_second_file_send_attempted must be false" + } + $fileSuccess = ($inputs.file_attempt.file_upload_or_sent_record_present -eq $true) -and (Test-NonEmptyString $inputs.file_attempt.file_upload_or_sent_record) + $fileFailure = (Test-NonEmptyString $inputs.file_attempt.failure.step) -and (Test-NonEmptyString $inputs.file_attempt.failure.code) -and (Test-NonEmptyString $inputs.file_attempt.failure.classification) -and (Test-NonEmptyString $inputs.file_attempt.failure.next_action) + if (-not $fileSuccess -and -not $fileFailure) { + Add-ValidationFailure $validationFailures "FILE_RESULT_OR_FAILURE_MISSING" "file attempt needs either upload/sent-record evidence or structured failure" + } +} + +$beforeRuntime = Read-ProbeJson -Stage "before" -Name "probe_client_runtime.json" +$beforePreflight = Read-ProbeJson -Stage "before" -Name "send_entrypoints_preflight.json" +$beforeNetwork = Read-ProbeJson -Stage "before" -Name "network_inventory.json" +$beforeFeasibility = Read-ProbeJson -Stage "before" -Name "feasibility_summary.json" +$entrypoints = Get-EntrypointMap $beforePreflight + +$runtimeSummary = [ordered]@{ + source = "return-evidence/before/probe_client_runtime.json" + target_count = if ($null -ne $beforeRuntime) { $beforeRuntime.target_count } else { $null } + host = if ($null -ne $beforeRuntime) { $beforeRuntime.host } else { $null } + targets = if ($null -ne $beforeRuntime) { $beforeRuntime.targets } else { @() } + feasibility = $beforeFeasibility +} + +$failures = New-Object System.Collections.Generic.List[object] +foreach ($failure in $validationFailures) { $failures.Add($failure) | Out-Null } +if ($textEnabled -and $inputs.text_attempt.success_ack_or_sent_record_present -ne $true -and (Test-NonEmptyString $inputs.text_attempt.failure.code)) { + $failures.Add($inputs.text_attempt.failure) | Out-Null +} +if ($fileEnabled -and $inputs.file_attempt.file_upload_or_sent_record_present -ne $true -and (Test-NonEmptyString $inputs.file_attempt.failure.code)) { + $failures.Add($inputs.file_attempt.failure) | Out-Null +} + +$output = [ordered]@{ + run = [ordered]@{ + schema_version = $inputs.schema_version + generated_at_local = (Get-Date).ToString("o") + machine = $env:COMPUTERNAME + operator_confirmed_logged_in = $inputs.run.operator_confirmed_logged_in + operator_started_at_local = $inputs.run.operator_started_at_local + operator_finished_at_local = $inputs.run.operator_finished_at_local + one_shot_text_enabled = $textEnabled + one_shot_file_enabled = $fileEnabled + production_send_enabled = $false + production_file_send_enabled = $false + validation_ok = ($validationFailures.Count -eq 0) + } + targets = $inputs.targets + runtime = $runtimeSummary + reachability = [ordered]@{ + in_process_adapter = $inputs.adapter_reachability + entrypoints = $entrypoints + app_context_manager = if ($entrypoints.Contains("text_high_level_by_jid")) { $entrypoints["text_high_level_by_jid"] } else { $null } + message_scheduling = if ($entrypoints.Contains("text_message_scheduler")) { $entrypoints["text_message_scheduler"] } else { $null } + chat_xmpp = if ($entrypoints.Contains("text_wire_chat_send")) { $entrypoints["text_wire_chat_send"] } else { $null } + file_upload = if ($entrypoints.Contains("file_service_upload")) { $entrypoints["file_service_upload"] } else { $null } + file_message = if ($entrypoints.Contains("file_wire_message")) { $entrypoints["file_wire_message"] } else { $null } + } + server_auth = [ordered]@{ + operator_supplied = $inputs.server_auth + network_inventory_source = "return-evidence/before/network_inventory.json" + network = $beforeNetwork + } + snapshots = [ordered]@{ + sent_record_source = $inputs.sent_record_source + before_dir = "return-evidence/before" + after_text_dir = if ($textEnabled) { "return-evidence/after-text" } else { $null } + after_file_dir = if ($fileEnabled) { "return-evidence/after-file" } else { $null } + } + attempts = [ordered]@{ + text = $inputs.text_attempt + file = $inputs.file_attempt + } + failures = $failures + correlation = [ordered]@{ + text_content_sha256 = $inputs.text_attempt.content_sha256 + file_sha256 = $inputs.file_attempt.file_sha256 + ack_or_ref_id = $inputs.sent_record_source.ack_or_ref_id + sent_record_correlation_notes = $inputs.sent_record_source.correlation_notes + text_machine_verifiable = $textEnabled -and ($inputs.text_attempt.success_ack_or_sent_record_present -eq $true) -and (Test-NonEmptyString $inputs.sent_record_source.ack_or_ref_id) + file_machine_verifiable = $fileEnabled -and ($inputs.file_attempt.file_upload_or_sent_record_present -eq $true) -and (Test-NonEmptyString $inputs.sent_record_source.ack_or_ref_id) + } +} + +$output | ConvertTo-Json -Depth 16 | Set-Content -LiteralPath $outputPath -Encoding UTF8 + +$stamp = Get-Date -Format "yyyyMMdd-HHmmss" +$zip = Join-Path $root ("isphere-login-reachability-return-" + $stamp + ".zip") +if (Test-Path -LiteralPath $zip) { + Remove-Item -LiteralPath $zip -Force +} + +$items = @( + $inputsPath, + $outputPath, + $evidenceRoot, + (Join-Path $root "EXPECTED-RETURN-FILES.txt"), + (Join-Path $root "OPERATOR-STEPS.md") +) +Compress-Archive -Path $items -DestinationPath $zip -Force + +[ordered]@{ + ok = ($validationFailures.Count -eq 0) + zip = $zip + output_json = $outputPath + validation_failure_count = $validationFailures.Count + production_send_enabled = $false + production_file_send_enabled = $false +} | ConvertTo-Json -Depth 8 -Compress + +if ($validationFailures.Count -gt 0) { + exit 2 +} +'@ | Set-Content -LiteralPath (Join-Path $packageDir "CREATE-RETURN-ZIP.ps1") -Encoding UTF8 + +@' +@echo off +setlocal +cd /d "%~dp0" +echo Optional local capability smoke. This does not log in and does not send. +tools\isphere-capability-smoke.exe > return-evidence\capability-smoke.txt 2>&1 +echo exit code: %ERRORLEVEL% > return-evidence\capability-smoke-exit-code.txt +type return-evidence\capability-smoke-exit-code.txt +pause +'@ | Set-Content -LiteralPath (Join-Path $packageDir "optional-run-capability-smoke.bat") -Encoding ASCII + +@' +{ + "package": "login-reachability-probe", + "schema_version": "2026-07-13.login-reachability.v1", + "LOCAL_LOGIN_UNAVAILABLE": true, + "ONLINE_LOGIN_REQUIRED": true, + "NO_AUTOMATIC_SEND": true, + "entrypoint": "RUN-LOGIN-REACHABILITY-SUITE.bat", + "input_template": "LOGIN-REACHABILITY-INPUTS.template.json", + "return_zip_script": "CREATE-RETURN-ZIP.ps1", + "recorder_exe": "recorder/ISphereLiveProbeRecorder.exe", + "capability_smoke_exe": "tools/isphere-capability-smoke.exe", + "required_output_schema_sections": [ + "run", + "targets", + "runtime", + "reachability", + "server_auth", + "snapshots", + "attempts", + "failures", + "correlation" + ], + "production_send_enabled": false, + "production_file_send_enabled": false, + "a_route_rpa": "backup_only" +} +'@ | Set-Content -LiteralPath (Join-Path $packageDir "LOGIN-REACHABILITY-PACKAGE-MANIFEST.json") -Encoding UTF8 + +$topReadme = Join-Path $packageDir "README.txt" +if (-not (Test-Path -LiteralPath $topReadme)) { + @( + "iSphere Login Reachability Probe Package", + "", + "LOCAL_LOGIN_UNAVAILABLE: development machine cannot log in to iSphere.", + "ONLINE_LOGIN_REQUIRED: run only where iSphere/IMPlatformClient is logged in.", + "NO_AUTOMATIC_SEND: this package does not auto-send text or files.", + "", + "Entrypoint: RUN-LOGIN-REACHABILITY-SUITE.bat", + "Input: LOGIN-REACHABILITY-INPUTS.json", + "Output: LOGIN-REACHABILITY-OUTPUT.json and isphere-login-reachability-return-*.zip", + "", + "Output schema sections: run, targets, runtime, reachability, server_auth, snapshots, attempts, failures, correlation.", + "", + "Rules: one-shot text/file only; duplicate_second_send_attempted=false; duplicate_second_file_send_attempted=false; production_send_enabled=false; production_file_send_enabled=false; A-route/RPA backup_only." + ) | Set-Content -LiteralPath $topReadme -Encoding UTF8 +} + +if (-not $NoZip) { + Compress-Archive -Path (Join-Path $packageDir "*") -DestinationPath $zipPath -Force +} + +$downloadZip = $null +if ($CopyToDownloads -and (Test-Path -LiteralPath $zipPath)) { + $downloads = Join-Path $env:USERPROFILE "Downloads" + if (Test-Path -LiteralPath $downloads) { + $downloadZip = Join-Path $downloads ("login-reachability-probe-package-" + (Get-Date -Format "yyyyMMdd-HHmmss") + ".zip") + Copy-Item -LiteralPath $zipPath -Destination $downloadZip -Force + } +} + +[ordered]@{ + ok = $true + package_dir = (Resolve-Path -LiteralPath $packageDir).Path + zip = if (Test-Path -LiteralPath $zipPath) { (Resolve-Path -LiteralPath $zipPath).Path } else { $null } + downloads_zip = if ($downloadZip) { (Resolve-Path -LiteralPath $downloadZip).Path } else { $null } + recorder_exe = (Resolve-Path -LiteralPath (Join-Path $recorderDir "ISphereLiveProbeRecorder.exe")).Path + capability_smoke_exe = (Resolve-Path -LiteralPath $capabilityExe).Path + online_login_required = $true + no_automatic_send = $true + production_send_enabled = $false + production_file_send_enabled = $false +} | ConvertTo-Json -Depth 6 -Compress diff --git a/scripts/verify-login-reachability-probe-package.ps1 b/scripts/verify-login-reachability-probe-package.ps1 new file mode 100644 index 0000000..10bbe8a --- /dev/null +++ b/scripts/verify-login-reachability-probe-package.ps1 @@ -0,0 +1,224 @@ +param( + [string]$OutputDir = "runs/login-reachability-probe-package" +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +$repo = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "..")).Path +$packageScript = Join-Path $repo "scripts\package-login-reachability-probe.ps1" + +if (-not (Test-Path -LiteralPath $packageScript)) { + throw "missing package script: $packageScript" +} + +$packageRoot = Join-Path $repo $OutputDir +$zipPath = "$packageRoot.zip" +if (Test-Path -LiteralPath $packageRoot) { + Remove-Item -LiteralPath $packageRoot -Recurse -Force +} +if (Test-Path -LiteralPath $zipPath) { + Remove-Item -LiteralPath $zipPath -Force +} + +& powershell -NoProfile -ExecutionPolicy Bypass -File $packageScript -OutputDir $OutputDir +if ($LASTEXITCODE -ne 0) { + throw "package-login-reachability-probe.ps1 failed with exit code $LASTEXITCODE" +} + +$requiredFiles = @( + "README.txt", + "OPERATOR-STEPS.md", + "EXPECTED-RETURN-FILES.txt", + "LOGIN-REACHABILITY-INPUTS.template.json", + "LOGIN-REACHABILITY-INPUTS.schema.json", + "LOGIN-REACHABILITY-PACKAGE-MANIFEST.json", + "RUN-LOGIN-REACHABILITY-SUITE.bat", + "00-prepare-inputs.bat", + "01-run-before-recorder.bat", + "02-run-after-text-recorder.bat", + "03-run-after-file-recorder.bat", + "04-create-return-zip.bat", + "CREATE-RETURN-ZIP.ps1", + "compute-text-hash.ps1", + "compute-file-hash.ps1", + "optional-run-capability-smoke.bat", + "docs\login-reachability-validation-items.md", + "docs\send-upload-deep-dive.md", + "tools\isphere-capability-smoke.exe", + "recorder\ISphereLiveProbeRecorder.exe", + "recorder\variants\ISphereLiveProbeRecorder-x86.exe" +) + +foreach ($relative in $requiredFiles) { + $path = Join-Path $packageRoot $relative + if (-not (Test-Path -LiteralPath $path)) { + throw "missing package file: $relative" + } +} + +$instructionText = @( + Get-Content -LiteralPath (Join-Path $packageRoot "README.txt") -Raw + Get-Content -LiteralPath (Join-Path $packageRoot "OPERATOR-STEPS.md") -Raw + Get-Content -LiteralPath (Join-Path $packageRoot "EXPECTED-RETURN-FILES.txt") -Raw + Get-Content -LiteralPath (Join-Path $packageRoot "LOGIN-REACHABILITY-INPUTS.template.json") -Raw + Get-Content -LiteralPath (Join-Path $packageRoot "LOGIN-REACHABILITY-PACKAGE-MANIFEST.json") -Raw +) -join "`n" + +foreach ($needle in @( + "LOCAL_LOGIN_UNAVAILABLE", + "ONLINE_LOGIN_REQUIRED", + "NO_AUTOMATIC_SEND", + "RUN-LOGIN-REACHABILITY-SUITE.bat", + "LOGIN-REACHABILITY-OUTPUT.json", + "run", + "targets", + "runtime", + "reachability", + "server_auth", + "snapshots", + "attempts", + "failures", + "correlation", + "content_sha256", + "file_sha256", + "duplicate_second_send_attempted", + "duplicate_second_file_send_attempted", + "production_send_enabled", + "production_file_send_enabled", + "backup_only" +)) { + if ($instructionText -notmatch [regex]::Escape($needle)) { + throw "package instructions missing required phrase: $needle" + } +} + +if (-not (Test-Path -LiteralPath $zipPath)) { + throw "missing package zip: $zipPath" +} + +$inputsTemplate = Join-Path $packageRoot "LOGIN-REACHABILITY-INPUTS.template.json" +$inputsPath = Join-Path $packageRoot "LOGIN-REACHABILITY-INPUTS.json" +$inputs = Get-Content -LiteralPath $inputsTemplate -Raw | ConvertFrom-Json +$inputs.run.operator_confirmed_logged_in = $true +$inputs.run.operator_started_at_local = "2026-07-13T09:00:00+08:00" +$inputs.run.operator_finished_at_local = "2026-07-13T09:05:00+08:00" +$inputs.run.before_recorder_present = $true +$inputs.run.one_shot_text_enabled = $true +$inputs.run.one_shot_file_enabled = $true +$inputs.run.one_text_send_only_confirmed = $true +$inputs.run.one_file_send_only_confirmed = $true +$inputs.targets.direct_jid = "test-direct@example.invalid" +$inputs.targets.target_selection_source = "verify-script" +$inputs.targets.self_send_or_sandbox_account_confirmed = $true +$inputs.text_attempt.content_sha256 = ("a" * 64) +$inputs.text_attempt.content_length = 16 +$inputs.text_attempt.operator_clicked_send_at_local = "2026-07-13T09:01:00+08:00" +$inputs.text_attempt.after_recorder_present = $true +$inputs.text_attempt.success_ack_or_sent_record_present = $true +$inputs.text_attempt.success_ack_or_sent_record = "synthetic sent-record ack for verify script" +$inputs.text_attempt.duplicate_second_send_attempted = $false +$inputs.file_attempt.allowed_dir = "C:\verify" +$inputs.file_attempt.sandbox_file_path = "C:\verify\small.txt" +$inputs.file_attempt.file_sha256 = ("b" * 64) +$inputs.file_attempt.file_size_bytes = 12 +$inputs.file_attempt.operator_clicked_send_file_at_local = "2026-07-13T09:03:00+08:00" +$inputs.file_attempt.after_recorder_present = $true +$inputs.file_attempt.file_upload_or_sent_record_present = $true +$inputs.file_attempt.file_upload_or_sent_record = "synthetic file sent-record ack for verify script" +$inputs.file_attempt.duplicate_second_file_send_attempted = $false +$inputs.sent_record_source.source_type = "synthetic" +$inputs.sent_record_source.source_path_or_ref = "verify-script" +$inputs.sent_record_source.before_count = "1" +$inputs.sent_record_source.after_count = "3" +$inputs.sent_record_source.ack_or_ref_id = "verify-ack" +$inputs.sent_record_source.correlation_notes = "synthetic verification only" +$inputs | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $inputsPath -Encoding UTF8 + +$beforeProbeDir = Join-Path $packageRoot "return-evidence\before\isphere-live-probe-verify" +$afterTextDir = Join-Path $packageRoot "return-evidence\after-text\isphere-live-probe-verify" +$afterFileDir = Join-Path $packageRoot "return-evidence\after-file\isphere-live-probe-verify" +New-Item -ItemType Directory -Force -Path $beforeProbeDir, $afterTextDir, $afterFileDir | Out-Null + +@' +{ + "probe_mode": "verify", + "target_count": 1, + "host": { "is_64_bit_os": true, "is_64_bit_process": false, "machine_name": "VERIFY" }, + "targets": [ + { + "pid": 1234, + "process_name": "IMPlatformClient", + "bitness": "x86", + "assembly_presence": { + "IMPlatformClient.exe": true, + "smack.dll": true, + "IMPP.Interface.dll": true, + "IMPP.Service.dll": true, + "IMPP.ServiceBase.dll": true + } + } + ] +} +'@ | Set-Content -LiteralPath (Join-Path $beforeProbeDir "probe_client_runtime.json") -Encoding UTF8 + +@' +{ + "entrypoints": [ + { "id": "text_high_level_by_jid", "capability": "send_message", "assembly_found": true, "reflection_type_found": true, "reflection_method_found": true, "string_type_hint": true, "string_method_hint": true, "available": true, "reflection_error": "" }, + { "id": "text_message_scheduler", "capability": "send_message", "assembly_found": true, "reflection_type_found": true, "reflection_method_found": true, "string_type_hint": true, "string_method_hint": true, "available": true, "reflection_error": "" }, + { "id": "text_wire_chat_send", "capability": "send_message", "assembly_found": true, "reflection_type_found": true, "reflection_method_found": true, "string_type_hint": true, "string_method_hint": true, "available": true, "reflection_error": "" }, + { "id": "file_service_upload", "capability": "send_file", "assembly_found": true, "reflection_type_found": true, "reflection_method_found": true, "string_type_hint": true, "string_method_hint": true, "available": true, "reflection_error": "" }, + { "id": "file_wire_message", "capability": "send_file", "assembly_found": true, "reflection_type_found": true, "reflection_method_found": true, "string_type_hint": true, "string_method_hint": true, "available": true, "reflection_error": "" } + ] +} +'@ | Set-Content -LiteralPath (Join-Path $beforeProbeDir "send_entrypoints_preflight.json") -Encoding UTF8 + +@' +{ "tcp": { "connection_count": 1, "connections": [ { "state": "ESTABLISHED", "remote_port": 5222 } ] } } +'@ | Set-Content -LiteralPath (Join-Path $beforeProbeDir "network_inventory.json") -Encoding UTF8 + +@' +{ "target_process_seen": true, "initial_route_hint": "B_ROUTE_PROMISING" } +'@ | Set-Content -LiteralPath (Join-Path $beforeProbeDir "feasibility_summary.json") -Encoding UTF8 + +"after text synthetic evidence" | Set-Content -LiteralPath (Join-Path $afterTextDir "dummy.txt") -Encoding UTF8 +"after file synthetic evidence" | Set-Content -LiteralPath (Join-Path $afterFileDir "dummy.txt") -Encoding UTF8 + +Push-Location $packageRoot +try { + $returnOutput = & powershell -NoProfile -ExecutionPolicy Bypass -File ".\CREATE-RETURN-ZIP.ps1" 2>&1 + $returnExit = $LASTEXITCODE +} +finally { + Pop-Location +} +if ($returnExit -ne 0) { + throw "CREATE-RETURN-ZIP.ps1 synthetic verification failed exit=$returnExit output=$($returnOutput -join "`n")" +} + +$outputJson = Join-Path $packageRoot "LOGIN-REACHABILITY-OUTPUT.json" +if (-not (Test-Path -LiteralPath $outputJson)) { + throw "missing generated LOGIN-REACHABILITY-OUTPUT.json" +} +$output = Get-Content -LiteralPath $outputJson -Raw | ConvertFrom-Json +foreach ($section in @("run", "targets", "runtime", "reachability", "server_auth", "snapshots", "attempts", "failures", "correlation")) { + if ($null -eq $output.PSObject.Properties[$section]) { + throw "LOGIN-REACHABILITY-OUTPUT.json missing section: $section" + } +} +if ($output.run.validation_ok -ne $true) { + throw "synthetic LOGIN-REACHABILITY-OUTPUT.json should have validation_ok=true" +} + +[ordered]@{ + ok = $true + package_dir = (Resolve-Path -LiteralPath $packageRoot).Path + zip = (Resolve-Path -LiteralPath $zipPath).Path + required_file_count = $requiredFiles.Count + synthetic_return_verified = $true + online_login_required = $true + no_automatic_send = $true + production_send_enabled = $false + production_file_send_enabled = $false +} | ConvertTo-Json -Depth 6 -Compress