411 lines
18 KiB
PowerShell
411 lines
18 KiB
PowerShell
param(
|
|
[string]$IlDir = "runs/offline-evidence-intake/zyl-qqfile-20260709/metadata/c28-il",
|
|
[string]$OutputDir = "runs/inprocess-adapter-research",
|
|
[string]$ReportPath = "docs/source-discovery/2026-07-12-inprocess-adapter-static-map.md",
|
|
[int]$WindowRadius = 4,
|
|
[int]$MaxEvidencePerAnchor = 16
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
$repo = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "..")).Path
|
|
$ilDirFull = Join-Path $repo $IlDir
|
|
$outDir = Join-Path $repo $OutputDir
|
|
$reportFullPath = Join-Path $repo $ReportPath
|
|
New-Item -ItemType Directory -Force -Path $outDir | Out-Null
|
|
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $reportFullPath) | Out-Null
|
|
|
|
$sources = @(
|
|
@{ path = "IMPlatformClient.exe.il"; role = "main managed client; UI, AppContextManager, MessageScheduling, frmMain, login runtime" },
|
|
@{ path = "smack.dll.il"; role = "XMPP/smack managed library; Chat and XMPPConnection send primitives" },
|
|
@{ path = "IMPP.Service.dll.il"; role = "file transfer service implementation" },
|
|
@{ path = "IMPP.ServiceBase.dll.il"; role = "file transfer DTOs and FileUploadPara/FileUploadResult contracts" },
|
|
@{ path = "IMPP.Interface.dll.il"; role = "service and network interface contracts" },
|
|
@{ path = "IMPP.Common.dll.il"; role = "common helper and runtime DTO evidence" }
|
|
)
|
|
|
|
$anchors = @(
|
|
@{
|
|
key = "text_high_level_entry"
|
|
display = "AppContextManager.SendTxtMessageByJid"
|
|
command = "send_text"
|
|
adapter_role = "preferred high-level managed entry if an in-process adapter can reach AppContextManager"
|
|
confidence = "high_static"
|
|
usable_offline = $false
|
|
required_runtime = @("logged-in IMPPManager/UserInfo", "target JID", "initialized AppContextManager", "live XMPP connection")
|
|
patterns = @("AppContextManager::SendTxtMessageByJid", "\bSendTxtMessageByJid\b")
|
|
},
|
|
@{
|
|
key = "text_scheduling_entry"
|
|
display = "MessageScheduling.SendChatMessage"
|
|
command = "send_text"
|
|
adapter_role = "fallback managed entry when scheduling layer has chat and contact args"
|
|
confidence = "high_static"
|
|
usable_offline = $false
|
|
required_runtime = @("MessageScheduling instance or static caller", "RosterContactsArgs/contact context", "Chat object", "live XMPP connection")
|
|
patterns = @("MessageScheduling::SendChatMessage", "SendChatMessage\(", "SendChatMessage\(class IMPP\.Client")
|
|
},
|
|
@{
|
|
key = "text_low_level_chat"
|
|
display = "com.vision.smack.Chat.SendMessage / XMPPConnection.send"
|
|
command = "send_text"
|
|
adapter_role = "lowest-level send target; requires already resolved Chat/XMPPConnection"
|
|
confidence = "high_static"
|
|
usable_offline = $false
|
|
required_runtime = @("Chat instance", "XMPPConnection authenticated session", "message payload format")
|
|
patterns = @("com\.vision\.smack\.Chat::SendMessage", "com\.vision\.smack\.Chat::sendMessage", "XMPPConnection::send\b")
|
|
},
|
|
@{
|
|
key = "file_upload_orchestrator"
|
|
display = "IMPP.Client.OffLineFileSend.sendFile"
|
|
command = "upload_file_then_send_file"
|
|
adapter_role = "UI-side upload orchestration and transition into chat file message"
|
|
confidence = "high_static"
|
|
usable_offline = $false
|
|
required_runtime = @("local file path", "HTTP file server/auth state", "target roster/chat args", "upload completion callback")
|
|
patterns = @("OffLineFileSend::sendFile", "sendFile\(\) cil managed", "OffLineFileSend::trans_UploadCompleted")
|
|
},
|
|
@{
|
|
key = "file_upload_service"
|
|
display = "IFileTransfer/FileTransfer.FileUpload + FileUploadPara"
|
|
command = "upload_file"
|
|
adapter_role = "service upload entry that must return FileUploadResult before chat file finalization"
|
|
confidence = "high_static"
|
|
usable_offline = $false
|
|
required_runtime = @("FileUploadPara host/port/domain", "localFilePath", "token/auth", "FileUploadResult.FileID")
|
|
patterns = @("FileTransfer::FileUpload", "FileUploadPara", "FileUploadResult::get_FileID", "FileUploadResult")
|
|
},
|
|
@{
|
|
key = "file_message_finalizer"
|
|
display = "MessageScheduling.SendFileMessage / Chat.sendFileMessage"
|
|
command = "send_file_message"
|
|
adapter_role = "chat file-message finalization after FileID/remote folder exists"
|
|
confidence = "high_static"
|
|
usable_offline = $false
|
|
required_runtime = @("uploaded file id", "remote folder metadata", "file name/size", "target Chat/JID")
|
|
patterns = @("MessageScheduling::SendFileMessage", "Chat::sendFileMessage", "sendFileMessage\(")
|
|
},
|
|
@{
|
|
key = "logged_in_identity_auth"
|
|
display = "IMPPManager.UserInfo / UnifiedAuthentication / Auth token"
|
|
command = "runtime_context"
|
|
adapter_role = "runtime state required before any production send/upload can be meaningful"
|
|
confidence = "medium_static"
|
|
usable_offline = $false
|
|
required_runtime = @("UserInfo", "UnifiedAuthentication", "Auth token", "HTTPFileServer", "server resource/domain")
|
|
patterns = @("IMPPManager::get_UserInfo", "IMPPManager.UserInfo", "UnifiedAuthentication", "HTTPFileServer", "Auth::get_token", "get_token")
|
|
},
|
|
@{
|
|
key = "main_window_runtime_owner"
|
|
display = "frmMain / MessageCenter / chat runtime ownership"
|
|
command = "runtime_context"
|
|
adapter_role = "likely object owner for initialized chat, reconnect, message center, and plugin runtime state"
|
|
confidence = "medium_static"
|
|
usable_offline = $false
|
|
required_runtime = @("running frmMain", "initialized message center", "contact/session state")
|
|
patterns = @("class IMPP\.Client\.frmMain", "frmMain::", "GetMessageCenter\(\)", "MessageCenter", "OnReConnectionSucceed", "GetChat\(")
|
|
},
|
|
@{
|
|
key = "existing_http_not_bridge"
|
|
display = "frmLogin.StartWinServer / HttpServerLib"
|
|
command = "not_selected_bridge"
|
|
adapter_role = "known local HTTP framework, but not a confirmed send bridge"
|
|
confidence = "high_static_negative"
|
|
usable_offline = $false
|
|
required_runtime = @("logged-in state", "route module registration not found", "send route not found")
|
|
patterns = @("StartWinServer", "HttpServerLib", "HttpService::\.ctor", "RegisterModule", "RouteAttribute")
|
|
}
|
|
)
|
|
|
|
function Get-RelativePath([string]$Path) {
|
|
if ($Path.StartsWith($repo, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
return $Path.Substring($repo.Length).TrimStart([char[]]@('\', '/'))
|
|
}
|
|
return $Path
|
|
}
|
|
|
|
function Write-Utf8NoBomLf([string]$Path, [string]$Text) {
|
|
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
|
|
$normalized = $Text -replace "`r`n", "`n"
|
|
$normalized = $normalized -replace "`r", "`n"
|
|
$normalized = $normalized.TrimEnd() + "`n"
|
|
[System.IO.File]::WriteAllText($Path, $normalized, $utf8NoBom)
|
|
}
|
|
|
|
function Get-EvidenceKind([string]$Line) {
|
|
if ($Line -match "^\s*\.class\b") { return "type_definition" }
|
|
if ($Line -match "^\s*\.method\b") { return "method_header" }
|
|
if ($Line -match "\bend of method\b") { return "method_end" }
|
|
if ($Line -match "\bnewobj\b") { return "constructor_call" }
|
|
if ($Line -match "\bcall(?:virt)?\b") { return "callsite" }
|
|
if ($Line -match "\bldstr\b") { return "string_literal" }
|
|
if ($Line -match "^\s*(instance|static)\b.*\(") { return "signature_continuation" }
|
|
if ($Line -match "^\s*\.field\b") { return "field" }
|
|
return "reference"
|
|
}
|
|
|
|
function Get-MethodContext($Lines, [int]$Index) {
|
|
$start = [Math]::Max(0, $Index - 100)
|
|
for ($i = $Index; $i -ge $start; $i--) {
|
|
if ($Lines[$i] -match "^\s*\.method\b") {
|
|
$header = $Lines[$i].Trim()
|
|
if (($i + 1) -lt $Lines.Count -and $Lines[$i + 1] -match "^\s+(instance|static|default)\b") {
|
|
$header = ($header + " " + $Lines[$i + 1].Trim())
|
|
}
|
|
return "L$($i + 1): $header"
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
function Get-WindowSnippet($Lines, [int]$Index, [int]$Radius) {
|
|
$start = [Math]::Max(0, $Index - $Radius)
|
|
$end = [Math]::Min($Lines.Count - 1, $Index + $Radius)
|
|
$parts = New-Object System.Collections.Generic.List[string]
|
|
for ($i = $start; $i -le $end; $i++) {
|
|
$parts.Add(("L{0}: {1}" -f ($i + 1), $Lines[$i].Trim()))
|
|
}
|
|
return ($parts -join "`n")
|
|
}
|
|
|
|
function Find-AnchorEvidence($Anchor) {
|
|
$items = New-Object System.Collections.Generic.List[object]
|
|
foreach ($source in $sources) {
|
|
$sourcePath = Join-Path $ilDirFull $source.path
|
|
if (-not (Test-Path -LiteralPath $sourcePath)) { continue }
|
|
$lines = [System.IO.File]::ReadAllLines($sourcePath)
|
|
for ($i = 0; $i -lt $lines.Count; $i++) {
|
|
$line = $lines[$i]
|
|
foreach ($pattern in $Anchor.patterns) {
|
|
if ($line -match $pattern) {
|
|
$items.Add([pscustomobject]@{
|
|
source = Get-RelativePath $sourcePath
|
|
source_role = $source.role
|
|
line = $i + 1
|
|
kind = Get-EvidenceKind $line
|
|
pattern = $pattern
|
|
snippet = $line.Trim()
|
|
context = Get-MethodContext $lines $i
|
|
window = Get-WindowSnippet $lines $i $WindowRadius
|
|
})
|
|
break
|
|
}
|
|
}
|
|
if ($items.Count -ge $MaxEvidencePerAnchor) { break }
|
|
}
|
|
if ($items.Count -ge $MaxEvidencePerAnchor) { break }
|
|
}
|
|
return $items.ToArray()
|
|
}
|
|
|
|
function Classify-Anchor($Anchor, $Evidence) {
|
|
if ($Evidence.Count -eq 0) { return "missing_static_evidence" }
|
|
if ($Anchor.command -eq "not_selected_bridge") { return "not_a_send_bridge" }
|
|
if ($Anchor.command -eq "runtime_context") { return "runtime_dependency" }
|
|
return "in_process_candidate_static_only"
|
|
}
|
|
|
|
$sourceSummary = foreach ($source in $sources) {
|
|
$path = Join-Path $ilDirFull $source.path
|
|
$exists = Test-Path -LiteralPath $path
|
|
$lineCount = 0
|
|
$size = 0
|
|
if ($exists) {
|
|
$item = Get-Item -LiteralPath $path
|
|
$size = $item.Length
|
|
$lineCount = ([System.IO.File]::ReadLines($path) | Measure-Object).Count
|
|
}
|
|
[pscustomobject]@{
|
|
path = Get-RelativePath $path
|
|
role = $source.role
|
|
exists = $exists
|
|
lines = $lineCount
|
|
size_bytes = $size
|
|
}
|
|
}
|
|
|
|
$anchorResults = foreach ($anchor in $anchors) {
|
|
$evidence = Find-AnchorEvidence $anchor
|
|
[pscustomobject]@{
|
|
key = $anchor.key
|
|
display = $anchor.display
|
|
command = $anchor.command
|
|
adapter_role = $anchor.adapter_role
|
|
classification = Classify-Anchor $anchor $evidence
|
|
confidence = $anchor.confidence
|
|
usable_offline = $anchor.usable_offline
|
|
required_runtime = $anchor.required_runtime
|
|
evidence_count = $evidence.Count
|
|
evidence = $evidence
|
|
}
|
|
}
|
|
|
|
$commands = @(
|
|
[pscustomobject]@{
|
|
op = "probe_runtime"
|
|
status = "contract_only"
|
|
purpose = "report whether adapter is inside logged-in IMPP runtime and can see required types/singletons"
|
|
required_evidence = @("process identity", "loaded assemblies", "IMPPManager.UserInfo presence", "XMPP connection state")
|
|
},
|
|
[pscustomobject]@{
|
|
op = "send_text"
|
|
status = "blocked_until_logged_in_adapter"
|
|
purpose = "invoke high-level text send path after target JID and content hash are validated"
|
|
required_evidence = @("AppContextManager or MessageScheduling reachability", "target JID", "sent record or ack containing content hash/correlation")
|
|
},
|
|
[pscustomobject]@{
|
|
op = "upload_file"
|
|
status = "blocked_until_logged_in_adapter"
|
|
purpose = "call file upload service and return server FileID/remote folder metadata"
|
|
required_evidence = @("FileUploadPara values", "HTTP file server/auth", "FileUploadResult.FileID")
|
|
},
|
|
[pscustomobject]@{
|
|
op = "send_file_message"
|
|
status = "blocked_until_upload_evidence"
|
|
purpose = "finalize file message after upload returns server id"
|
|
required_evidence = @("uploaded FileID", "file name/size", "target JID/Chat", "sent record/ack")
|
|
}
|
|
)
|
|
|
|
$requiredRuntimeState = @(
|
|
"logged-in IMPlatformClient.exe process",
|
|
"loaded IMPP.Client/AppContextManager/MessageScheduling types",
|
|
"initialized IMPPManager.UserInfo and UnifiedAuthentication/Auth token",
|
|
"live XMPPConnection/Chat session",
|
|
"target JID or group JID resolved by existing search tools",
|
|
"file upload service parameters and FileUploadResult.FileID for file sends"
|
|
)
|
|
|
|
$stopConditions = @(
|
|
"No logged-in process is available for runtime reachability validation.",
|
|
"Adapter cannot see AppContextManager, MessageScheduling, Chat, or IFileTransfer in process.",
|
|
"Every viable path requires unsupported binary patching instead of a replaceable managed adapter/harness.",
|
|
"Online evidence cannot produce sent-record/content-hash/ack correlation for text send.",
|
|
"File upload cannot return FileUploadResult.FileID or equivalent remote folder metadata."
|
|
)
|
|
|
|
$result = [pscustomobject]@{
|
|
ok = $true
|
|
generated_at = (Get-Date).ToUniversalTime().ToString("o")
|
|
decision = "managed_in_process_adapter_contract_first"
|
|
sources = $sourceSummary
|
|
anchors = $anchorResults
|
|
commands = $commands
|
|
required_runtime_state = $requiredRuntimeState
|
|
stop_conditions = $stopConditions
|
|
originals_copied = $false
|
|
originals_modified = $false
|
|
}
|
|
|
|
$jsonPath = Join-Path $outDir "inprocess-adapter-map.json"
|
|
$jsonText = $result | ConvertTo-Json -Depth 8
|
|
Write-Utf8NoBomLf $jsonPath $jsonText
|
|
|
|
$md = New-Object System.Collections.Generic.List[string]
|
|
$md.Add("# In-Process Adapter Static Map")
|
|
$md.Add("")
|
|
$md.Add("Date: 2026-07-12")
|
|
$md.Add("")
|
|
$md.Add("## Scope")
|
|
$md.Add("")
|
|
$md.Add("This report uses offline IL/static evidence only. It does not log in, attach hooks, inject into a process, modify client binaries, upload files, send messages, or open network connections.")
|
|
$md.Add("")
|
|
$md.Add("Machine-readable evidence: ``$(Get-RelativePath $jsonPath)``")
|
|
$md.Add("")
|
|
$md.Add("## Decision")
|
|
$md.Add("")
|
|
$md.Add("| Question | Answer |")
|
|
$md.Add("| --- | --- |")
|
|
$md.Add("| Selected next B-route branch | `managed_in_process_adapter_contract_first` |")
|
|
$md.Add("| Can offline branch enable production send? | **No** |")
|
|
$md.Add("| Go connector mode to expose now | `in_process_contract` |")
|
|
$md.Add("| Required online proof later | logged-in adapter reachability plus sent-record/content-hash/ack evidence |")
|
|
$md.Add("")
|
|
$md.Add("## Sources")
|
|
$md.Add("")
|
|
$md.Add("| Source | Role | Exists | Lines | Size bytes |")
|
|
$md.Add("| --- | --- | --- | ---: | ---: |")
|
|
foreach ($source in $sourceSummary) {
|
|
$md.Add("| ``$($source.path)`` | $($source.role) | $($source.exists) | $($source.lines) | $($source.size_bytes) |")
|
|
}
|
|
$md.Add("")
|
|
$md.Add("## Command contract for future in-process adapter")
|
|
$md.Add("")
|
|
$md.Add("| Op | Current status | Purpose | Required evidence before production |")
|
|
$md.Add("| --- | --- | --- | --- |")
|
|
foreach ($cmd in $commands) {
|
|
$required = ($cmd.required_evidence -join "; ")
|
|
$md.Add("| ``$($cmd.op)`` | ``$($cmd.status)`` | $($cmd.purpose) | $required |")
|
|
}
|
|
$md.Add("")
|
|
$md.Add("## Anchor summary")
|
|
$md.Add("")
|
|
$md.Add("| Anchor | Command | Classification | Confidence | Evidence | Required runtime |")
|
|
$md.Add("| --- | --- | --- | --- | ---: | --- |")
|
|
foreach ($anchor in $anchorResults) {
|
|
$required = ($anchor.required_runtime -join "; ")
|
|
$md.Add("| ``$($anchor.display)`` | ``$($anchor.command)`` | ``$($anchor.classification)`` | ``$($anchor.confidence)`` | $($anchor.evidence_count) | $required |")
|
|
}
|
|
$md.Add("")
|
|
$md.Add("## Verified facts")
|
|
$md.Add("")
|
|
$md.Add("- Text send has static in-process candidates, but all require logged-in runtime objects and a live XMPP/Chat path.")
|
|
$md.Add("- File send is split into upload and message-finalization; `FileUploadResult.FileID` is a required boundary value before `Chat.sendFileMessage` is meaningful.")
|
|
$md.Add("- The earlier IPC/plugin/local HTTP scan did not find a confirmed external send bridge, so this branch must stay at `in_process_contract` until online reachability exists.")
|
|
$md.Add("- `in_process_contract` is a Go-side contract and audit mode only; it must return blocked/no-side-effect metadata in this offline branch.")
|
|
$md.Add("")
|
|
$md.Add("## Required runtime state")
|
|
$md.Add("")
|
|
foreach ($state in $requiredRuntimeState) {
|
|
$md.Add("- $state")
|
|
}
|
|
$md.Add("")
|
|
$md.Add("## Anchor evidence")
|
|
foreach ($anchor in $anchorResults) {
|
|
$md.Add("")
|
|
$md.Add("### $($anchor.display)")
|
|
$md.Add("")
|
|
$md.Add("Classification: ``$($anchor.classification)``; command: ``$($anchor.command)``; evidence count: $($anchor.evidence_count).")
|
|
$md.Add("")
|
|
$md.Add("Adapter role: $($anchor.adapter_role)")
|
|
$md.Add("")
|
|
if ($anchor.evidence_count -eq 0) {
|
|
$md.Add("No static evidence found for this anchor in the configured IL set.")
|
|
continue
|
|
}
|
|
$md.Add("| Source | Kind | Snippet | Context |")
|
|
$md.Add("| --- | --- | --- | --- |")
|
|
foreach ($e in $anchor.evidence) {
|
|
$snippet = ($e.snippet -replace "\|", "\\|" )
|
|
if ($snippet.Length -gt 220) { $snippet = $snippet.Substring(0, 217) + "..." }
|
|
$context = ($e.context -replace "\|", "\\|" )
|
|
if ($context.Length -gt 180) { $context = $context.Substring(0, 177) + "..." }
|
|
$md.Add("| ``$($e.source):$($e.line)`` | ``$($e.kind)`` | ``$snippet`` | ``$context`` |")
|
|
}
|
|
}
|
|
$md.Add("")
|
|
$md.Add("## Stop conditions")
|
|
$md.Add("")
|
|
foreach ($condition in $stopConditions) {
|
|
$md.Add("- $condition")
|
|
}
|
|
$md.Add("")
|
|
$md.Add("## Next work")
|
|
$md.Add("")
|
|
$md.Add("1. Add Go connector mode `in_process_contract` that validates request shape and returns blocked/no-side-effect metadata.")
|
|
$md.Add("2. In a logged-in environment, build a recorder/probe that only checks reachability of the listed runtime objects before attempting any send.")
|
|
$md.Add("3. Promote text send only after sent-record/content-hash/ack evidence exists.")
|
|
$md.Add("4. Promote file send only after upload returns `FileUploadResult.FileID` and file-message finalization is proven online.")
|
|
|
|
Write-Utf8NoBomLf $reportFullPath ($md -join "`n")
|
|
|
|
[pscustomobject]@{
|
|
ok = $true
|
|
json_path = $jsonPath
|
|
report_path = $reportFullPath
|
|
anchors = $anchorResults.Count
|
|
decision = $result.decision
|
|
originals_copied = $false
|
|
originals_modified = $false
|
|
} | ConvertTo-Json -Depth 5
|