1024 lines
40 KiB
PowerShell
1024 lines
40 KiB
PowerShell
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": ""
|
||
},
|
||
"runtime_observations": {
|
||
"app_context_manager_reachable": null,
|
||
"message_scheduling_reachable": null,
|
||
"chat_xmpp_authenticated": null,
|
||
"target_jid_mapping_observed": null,
|
||
"file_upload_para_observed": null,
|
||
"ifile_transfer_reachable": null,
|
||
"file_upload_result_file_id_observed": null,
|
||
"send_file_message_finalization_observed": null,
|
||
"notes": "",
|
||
"failure": {
|
||
"step": "",
|
||
"code": "",
|
||
"classification": "",
|
||
"message": "",
|
||
"next_action": ""
|
||
}
|
||
},
|
||
"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": ""
|
||
}
|
||
},
|
||
"group_probe": {
|
||
"enabled": false,
|
||
"group_jid": "",
|
||
"chatroom_branch_observed": null,
|
||
"speech_permission_state": "",
|
||
"upload_permission_state": "",
|
||
"mute_or_permission_failure_reason": "",
|
||
"failure": {
|
||
"step": "",
|
||
"code": "",
|
||
"classification": "",
|
||
"message": "",
|
||
"next_action": ""
|
||
}
|
||
},
|
||
"fallback_a_route": {
|
||
"enabled_if_b_route_fails": true,
|
||
"hwnd": "",
|
||
"rtbSendMessage_present": null,
|
||
"btnSend_present": null,
|
||
"btnSendFile_or_file_menu_present": null,
|
||
"uia_or_screenshot_ref": "",
|
||
"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", "runtime_observations", "text_attempt", "file_attempt", "group_probe", "fallback_a_route"],
|
||
"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" }
|
||
}
|
||
},
|
||
"runtime_observations": { "type": "object" },
|
||
"text_attempt": { "type": "object" },
|
||
"file_attempt": { "type": "object" },
|
||
"group_probe": { "type": "object" },
|
||
"fallback_a_route": { "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
|
||
|
||
Required for remaining validation TODO coverage:
|
||
- runtime_observations records AppContextManager/MessageScheduling/Chat-XMPP/FileUploadPara/IFileTransfer/FileID/sendFileMessage observations or a structured failure.
|
||
- group_probe records ChatRoom branch and permissions when group_jid is used.
|
||
- fallback_a_route records hwnd/rtbSendMessage/btnSend/file-menu evidence when B-route fails and A-route evidence is needed.
|
||
|
||
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
|
||
|
||
@'
|
||
# TODO-COVERAGE
|
||
|
||
This package covers the remaining validation TODO items from the 2026-07-12 login reachability schema.
|
||
|
||
P0 coverage:
|
||
- one-shot test target: LOGIN-REACHABILITY-INPUTS.json / targets
|
||
- before/after sent-record snapshots: return-evidence/before, after-text, after-file plus sent_record_source
|
||
- sent-record and ack source: sent_record_source
|
||
- in-process adapter reachability mode: adapter_reachability
|
||
- logged-in runtime and loaded assemblies: recorder/probe_client_runtime.json
|
||
- AppContextManager/IAppContextManager: runtime_observations plus send_entrypoints_preflight.json
|
||
- Chat/XMPPConnection authenticated state: runtime_observations plus send_entrypoints_preflight.json
|
||
- FileUploadPara values: server_auth plus runtime_observations
|
||
- IFileTransfer/FileUpload reachability: runtime_observations plus send_entrypoints_preflight.json
|
||
- FileUploadResult.FileID: runtime_observations and file_attempt evidence/failure
|
||
- trans_UploadCompleted/SendFileMessage finalization: runtime_observations and file_attempt evidence/failure
|
||
- structured failure reasons: text_attempt.failure, file_attempt.failure, runtime_observations.failure, group_probe.failure, fallback_a_route.failure
|
||
- sent record/ack correlation: correlation output section
|
||
|
||
P1 coverage:
|
||
- group ChatRoom branch and permissions: group_probe
|
||
- A-route fallback UI evidence: fallback_a_route
|
||
|
||
The package still does not auto-send, auto-upload, click, type, inject, hook, or enable production send.
|
||
'@ | Set-Content -LiteralPath (Join-Path $packageDir "TODO-COVERAGE.md") -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
|
||
}
|
||
|
||
function Test-StructuredFailure($Failure) {
|
||
if ($null -eq $Failure) { return $false }
|
||
return (Test-NonEmptyString $Failure.step) -and
|
||
(Test-NonEmptyString $Failure.code) -and
|
||
(Test-NonEmptyString $Failure.classification) -and
|
||
(Test-NonEmptyString $Failure.next_action)
|
||
}
|
||
|
||
function Test-ObservedOrStructuredFailure($Observed, $Failure) {
|
||
if ($Observed -eq $true) { return $true }
|
||
return Test-StructuredFailure $Failure
|
||
}
|
||
|
||
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"
|
||
}
|
||
|
||
foreach ($field in @(
|
||
"app_context_manager_reachable",
|
||
"message_scheduling_reachable",
|
||
"chat_xmpp_authenticated",
|
||
"target_jid_mapping_observed",
|
||
"file_upload_para_observed",
|
||
"ifile_transfer_reachable",
|
||
"file_upload_result_file_id_observed",
|
||
"send_file_message_finalization_observed"
|
||
)) {
|
||
if (-not (Test-ObservedOrStructuredFailure $inputs.runtime_observations.$field $inputs.runtime_observations.failure)) {
|
||
Add-ValidationFailure $validationFailures ("RUNTIME_" + $field.ToUpperInvariant() + "_MISSING") ("runtime_observations." + $field + " must be true or runtime_observations.failure must be structured")
|
||
}
|
||
}
|
||
|
||
$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"
|
||
}
|
||
}
|
||
|
||
$groupEnabled = ($inputs.group_probe.enabled -eq $true) -or (Test-NonEmptyString $inputs.targets.group_jid)
|
||
if ($groupEnabled) {
|
||
if (-not (Test-NonEmptyString $inputs.group_probe.group_jid) -and -not (Test-NonEmptyString $inputs.targets.group_jid)) {
|
||
Add-ValidationFailure $validationFailures "GROUP_JID_MISSING" "group_probe.group_jid or targets.group_jid is required when group probe is enabled"
|
||
}
|
||
$groupObserved = ($inputs.group_probe.chatroom_branch_observed -eq $true) -and
|
||
((Test-NonEmptyString $inputs.group_probe.speech_permission_state) -or (Test-NonEmptyString $inputs.group_probe.upload_permission_state))
|
||
if (-not $groupObserved -and -not (Test-StructuredFailure $inputs.group_probe.failure)) {
|
||
Add-ValidationFailure $validationFailures "GROUP_OBSERVATION_OR_FAILURE_MISSING" "group_probe needs ChatRoom/permission evidence or structured failure"
|
||
}
|
||
}
|
||
|
||
$fallbackNeeded = ($inputs.fallback_a_route.enabled_if_b_route_fails -eq $true) -and (Test-StructuredFailure $inputs.runtime_observations.failure)
|
||
if ($fallbackNeeded) {
|
||
$fallbackObserved = (Test-NonEmptyString $inputs.fallback_a_route.hwnd) -and
|
||
($inputs.fallback_a_route.rtbSendMessage_present -eq $true) -and
|
||
($inputs.fallback_a_route.btnSend_present -eq $true)
|
||
if (-not $fallbackObserved -and -not (Test-StructuredFailure $inputs.fallback_a_route.failure)) {
|
||
Add-ValidationFailure $validationFailures "FALLBACK_A_ROUTE_EVIDENCE_MISSING" "fallback_a_route needs hwnd/rtbSendMessage/btnSend evidence or structured failure when B-route fails"
|
||
}
|
||
}
|
||
|
||
$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
|
||
}
|
||
if (Test-StructuredFailure $inputs.runtime_observations.failure) {
|
||
$failures.Add($inputs.runtime_observations.failure) | Out-Null
|
||
}
|
||
if ($groupEnabled -and (Test-StructuredFailure $inputs.group_probe.failure)) {
|
||
$failures.Add($inputs.group_probe.failure) | Out-Null
|
||
}
|
||
if ($fallbackNeeded -and (Test-StructuredFailure $inputs.fallback_a_route.failure)) {
|
||
$failures.Add($inputs.fallback_a_route.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
|
||
operator_runtime_observations = $inputs.runtime_observations
|
||
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 }
|
||
group_probe = $inputs.group_probe
|
||
fallback_a_route = $inputs.fallback_a_route
|
||
}
|
||
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
|
||
group = $inputs.group_probe
|
||
fallback_a_route = $inputs.fallback_a_route
|
||
}
|
||
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"),
|
||
(Join-Path $root "TODO-COVERAGE.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",
|
||
"todo_coverage": "TODO-COVERAGE.md",
|
||
"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,
|
||
"remaining_todo_completed": [
|
||
"runtime_observations",
|
||
"group_probe",
|
||
"fallback_a_route"
|
||
],
|
||
"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
|