Files
gensokyo-dev 5508b2affd fix(analysis): retain source evidence through directory exports (#7250)
Retain authenticated provider source records independently of snapshot replay eligibility and return them explicitly to plain application callers, including late cancellation. CLI/MCP commit source records before delivering links; function dossiers share the canonical Evidence constructor. Keep directory replies compact and preserve successful retry verification.

Real Ghidra CLI/MCP investigations verify 652-function ls, historical report provenance, cross-session resume, unchanged source/project content and complete offline Evidence. Final full 14,338-function libcrypto comparison matches all metadata/pseudocode facts and independent fresh-import observations: 14,194 written, 143 external, one recorded deadline; 494.03 s, 445.63 CPU s, 1.584 GiB sampled family peak under 2-CPU/8-GiB guards, with no OOM or cleanup failure.

144 focused tests and static/docs/site checks pass; latest-main integration passes 289 cases plus typecheck/lint and actual CLI snapshot source retrieval. Final CI source/static/build/docs/quality/package checks pass. Four ADB shard failures also occur in main CI 40349559391; cancelled coverage remains unverified and author declarations stay unchecked under the user's explicit bypass authorization. No human-review attestation is made.
2026-10-26 23:56:03 +08:00

9.2 KiB
Raw Permalink Blame History

Directory decompilation: capacity, resume and source Evidence

The directory-export feature requested in #6090 already shipped in #6170. This follow-up verifies the current public workflow and fixes a source-provenance gap found while combining it with existing Ghidra projects (#1048).

Completed task and correctness

The analyst asks for all functions as local pseudocode files, without putting their bodies in agent context. The committed decompile-directory.mjs driver uses the production stdio MCP entrypoint and installed SDK. It discovers the catalog, exports an initial chunk, closes/reopens the provider, resumes all remaining work, and resumes again to verify settled files. It authenticates an explicitly exported Evidence bundle and checks the original target/project.

Every written file is checked against its index SHA-256 and address/profile header. Existing file hashes and modification times must survive both resumes. Comparison facts include every address, name, status, body range and pseudocode digest. An independent fresh-import investigation from #1048 additionally checks the complete inventory and two nonempty pseudocode observations. This establishes export preservation of provider observations; it does not establish discovery of every actual function or equivalence to original source.

Real artifact Provider inventory Written External Failed
/usr/bin/ls 652 541 111 0
system libcrypto.so.3 14,338 14,194 143 1

Target SHA-256 values are respectively cb30d69b24245bf2ecdc9e7f53bbad19159999970b6d82c0c00c7d32d9e37aa4 and aa01e614af634bc7c8b678c9250b5c4f613c61989bacf0a5d6aed6570e7dcabd. These installed ELF x86-64 binaries were analyzed statically, never executed. The large run explicitly selected a five-second per-function deadline; function 0x387f00 exceeded it, remained a recorded failure, and was retried on resume. Later functions were still exported. No disappearance of that failure is claimed. The resulting large tree occupies about 107 MiB on this filesystem.

Source-provenance correction

Before this change, BinarySession retained provider sourceEvidence only inside its snapshot-eligible branch. Ghidra procedure reads without a document selector are not snapshot eligible. A composed directory export therefore lost saved project reports even though the provider supplied them. The workflow also ignored source-record IDs, and the function-dossier adapter copied observation fields manually without those links.

The session now authenticates/retains source records independently of replay eligibility. Plain application helpers also return authenticated immutable source records with their outcome, including late cancellation. Both adapters explicitly commit them before delivering links; an adapter without a retention owner refuses the response. Source collection borrows ledger records without constructing or hashing an intermediate bundle. The workflow links distinct source IDs without copying their payloads. The function adapter uses the common observation-to-Evidence constructor, preserving sources through normal and late-cancelled completion. Queries with no source records avoid the extra recording path. Complete provider bodies remain in the exported function files; this does not add per-function payload retention.

A real test project contains historical seed and disabled-analyzer reports saved by REA's actual Ghidra pre-scripts. The unmodified runtime completed the export but failed the provenance oracle: its bundle had two workflow records and no historical source. The fixed runtime retains exactly one historical source and links the workflow to it. All 652 inventory entries and the sampled pseudocode still match fresh import; source project content stays unchanged. The report is historical, with remaining original settings/completeness explicitly unknown.

Measured costs

Baseline runtime: 19447516a4bdbc9437d112ebd3f12a5fd062db83. Initial fixed runtime: f50763fd21527bdf3bab944633617b9df61dded4, rebased on main f02df45a4. Both use Node 24.22.1, locked dependencies, actual Ghidra 12.1.4 and JDK 21.0.12.1+1. The fixed driver later delegates transcript publication to the existing cleanup/receipt owner; the protocol and task are unchanged.

Completed task Wall s Family CPU s Peak family GiB Full-wire token estimate
ls, before 51.411 78.984 0.943 57,082
ls, initial fix 50.937 79.891 1.201 57,072
ls with historical reports, fixed 52.827 80.236 1.097 57,207
libcrypto, baseline capacity run 506.387 459.818 1.502 57,322

The plain ls pair has identical complete comparison facts. Runtime is similar; the higher sampled memory in the fixed run is reported rather than treated as an improvement. These sequential shared-host samples establish no generic speed or memory ratio. PR #7250 records the final large-run comparison and CI/review results. The failed historical baseline is excluded from completed-task comparisons.

The complete MCP replies stayed around 11–12 KiB, including repeated textual and structured representations, profile and caveats. The normalized result is only the manifest. For example, the large continuation costs about 3,159 estimated wire tokens; its complete task is 57,322 because discovery alone costs 47,049. Both numbers include actual protocol content, rather than only pseudocode bytes.

Estimates use cl100k_base on the actual JSON-RPC messages, including discovery, requests, responses and notifications, excluding only transport framing and the driver's local transcript wrapper. They are not actual model billing. Host projection, prompts/reasoning and later agent file reads remain unmeasured. The historical source increases response token cost slightly while preserving provenance. No token-reduction claim is made for this correctness fix.

CPU and memory cover the driver, REA and provider process family. Each run uses CPU affinity 0,1 and an 8-GiB sampled family-RSS guard; Java heaps are 1536M for ls and 3072M for crypto. RSS is sampled every 100 ms and summed: shared pages may be counted repeatedly and short peaks may be missed. CPU is waited child usage. Reported completed runs had no resource stop, OOM or cleanup failure. Project preparation is excluded and documented separately in project reuse. Provider starts are process-cold, with host filesystem caches unflushed; verified resume occurs in the second session.

The index and inventory still scale with function count. This 14,338-item run does not establish a larger capacity guarantee. Real Windows/macOS filesystems, other decompilers and native DWARF directory layout remain unverified. The current workflow reports unavailable DWARF layout explicitly instead of inventing it.

Actual CLI decompile-all calls additionally selected only 0x10a420, reported 651 excluded entries and debug_layout_unavailable for requested dwarf layout, then resumed in a new process. The existing file's hash and modification time stayed unchanged; the returned historical source ID stayed stable. This CLI check verifies the returned manifest/link and local function file, not offline retrieval of the separately retained source record after the CLI process exits.

Reproduce and inspect

Prepare an owned project using the documented existing-project route. Use fresh receipt/output paths; the driver refuses existing output directories.

node experiments/analysis-efficiency/decompile-directory.mjs \
  --runtime /absolute/rea --output /absolute/new-receipt \
  --target /usr/bin/ls --project /absolute/ls-analysis.gpr --program ls \
  --ghidra "$GHIDRA_INSTALL_DIR" --java "$JAVA_HOME"

Use --chunk 512 --heap 3072M --decompile-seconds 5 for the crypto case. --control /absolute/previous-receipt compares all exported facts; --golden /absolute/fresh-import-receipt checks the #1048 driver's inventory and pseudocode oracle. --history required additionally requires a linked historical source. The driver allows failed functions to succeed on a later retry, verifies only previously written files remain unchanged, and reloads the final index before comparison. Complete local receipts include the transcript, resource report, index, function files, comparison facts and authenticated Evidence export. The resource report is supplied by the external process-family sampler, not the driver.

The current nine-file suite passed 144 tests. Against unmodified main source, the three source-retention/constructor suites reproduce seven failures and 70 passes; the fixed full suite passes. This includes invalid source commitments, live and cursor-dependent reads, shared source deduplication, selected/null/provider subjects, and source links on late-cancelled completion.