feat: add in-process adapter research contract

This commit is contained in:
zhaoyilun
2026-07-12 11:15:07 +08:00
parent a4242c12be
commit a9e692a574
8 changed files with 1054 additions and 3 deletions

View File

@@ -0,0 +1,410 @@
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