rea-reverse

REA Evidence, Snapshots and Version Comparison Explained

Written for REA 6.4.0Updated 9 min read
On this page

Short version: every REA result is an Evidence record. It is tied to the exact artifact (by SHA-256), the provider, the operation, its parameters and its limitations. Records collect into Evidence bundles, which you can validate (rea evidence-import), canonicalize (rea evidence-export) and diff (rea compare). Snapshots (--snapshot) cache native analysis results and carry their own bundle. To answer “what changed between version A and version B”, use the domain comparisons for the actual diff, use bundle comparison to audit the evidence itself, and use rea import-reference-source to bring in the historical source tree as a reference.

This guide is for people who already run REA and now need results that last. That covers a reviewer who has to defend a conclusion a week later, a team that shares findings, and anyone who reviews releases of a dependency. Our scenario: two tagged releases of the same open-source Electron application (any MIT- or GPL-licensed app you can download from its GitHub releases), plus the source tree checked out at one of those tags.

You will learn:

  • what an Evidence record and an Evidence bundle are, and what they don’t claim
  • how --snapshot saves and reuses native analysis
  • what evidence-import, evidence-export and compare actually do
  • how to diff two versions of an application graph
  • how to map historical source files onto the shipped bundle

Prerequisites

  1. REA installed (npm install --global rea-agents, or npx -y rea-agents@latest <command>). See Getting started with REA and MCP.
  2. A native provider (Ghidra, Hopper or IDA) for the snapshot step. The Ghidra route is covered in REA + Ghidra for native binaries.
  3. jq and Node.js for building JSON inputs.
  4. Two releases of an open-source app, extracted. For an Electron app, that means each release’s app.asar (or its extracted directory), and any native .node addon from app.asar.unpacked. Reverse engineering an Electron app shows where these files live.
  5. A local git checkout of the project at the older tag.

Everything in this guide is static: REA reads files and does not run the application. If you extend the investigation to runtime capture, do it only on software and systems you are authorized to analyze, and read DISCLAIMER.md first.

We’ll use this layout (adapt the paths):

/work/app-v1/app.asar               # older release
/work/app-v2/app.asar               # newer release
/work/app-v1/native/addon.node      # native addon from v1
/work/app-v2/native/addon.node      # native addon from v2
/work/src-v1/                       # git checkout at the v1 tag
/work/evidence/                     # outputs

The model: records, bundles, snapshots

Before running anything, it helps to know what each file holds.

Evidence record. One analysis result. The schema in REA’s source includes evidence_id (a digest, prefixed ev_), subject (artifact name, SHA-256, format, architecture, local path), provider, operation, parameters, normalized_result, confidence (observed, derived or inferred), limitations, locations and evidence_links. The ID is derived from the record’s content, so the same input analyzed the same way gives the same ID. That is what makes comparison possible.

Evidence bundle. A JSON document with artifacts, providers, environments, scenarios, captures, unknowns and records. Records are sorted deterministically, and the order carries no meaning. Bundles also carry residual unknowns, the open questions an investigation recorded, with their revision history.

Analysis snapshot. A cache of successful analysis results for one target. It holds target, binding, entries, workflow_entries and an evidence_bundle. A result is reused only when the target bytes, operation, parameters, provider and settings all match. Snapshot files are written with owner-only permissions.

One rule runs through all of this: REA keeps observations, inferences and unknowns separate, and missing data never becomes “no change”. Keep that in mind when you read the comparison results below.

Step 1: Save native analysis with –snapshot

Start with the native addon, because it is the slowest part to analyze and benefits most from caching. The --snapshot option (“Load and update a local analysis snapshot”) is accepted by analyze, inspect, decompile, xrefs, trace, function, instructions and search, as well as inspect-native-api and inspect-native-dispatch-metadata.

mkdir -p /work/evidence
rea analyze /work/app-v1/native/addon.node --provider ghidra \
  --snapshot /work/evidence/addon-v1.snapshot.json --json > /work/evidence/addon-v1.overview.json
rea analyze /work/app-v2/native/addon.node --provider ghidra \
  --snapshot /work/evidence/addon-v2.snapshot.json --json > /work/evidence/addon-v2.overview.json

Run the same command again and it returns the cached result. The CLI guide notes that an exact cache hit is read before a provider process starts, so repeated queries are fast. Use one snapshot file per version. A snapshot belongs to one target identity, and mixing versions would defeat the point.

Things to know:

  • Metadata writes invalidate the cache. If you rename functions or add comments in the session, close and reopen the target before you save or import another snapshot.
  • Profile changes matter. When a provider’s analysis profile changes, an older snapshot can produce a profile mismatch instead of replaying stale results. The old snapshot’s evidence_bundle stays readable. Run again without the old snapshot to build a fresh one.
  • In MCP, the same thing happens through open_binary with snapshot_path (import) and close_binary with snapshot_path and optional overwrite: true (save). A failed save leaves the session open so no cached work is lost.

Step 2: Extract and validate a bundle with evidence-import

A snapshot’s bundle sits at the top-level evidence_bundle key. REA’s native guide describes exactly this: copy that object to a separate JSON file and validate it with rea evidence-import.

jq '.evidence_bundle' /work/evidence/addon-v1.snapshot.json > /work/evidence/addon-v1.bundle.json
jq '.evidence_bundle' /work/evidence/addon-v2.snapshot.json > /work/evidence/addon-v2.bundle.json

rea evidence-import /work/evidence/addon-v1.bundle.json --json

On the CLI, evidence-import validates the bundle: every Evidence ID must match its record, and the manifests must be consistent. It then reports counts (imported, unknowns_added, total). A separate CLI process has no session to merge into, so validation is the useful part here. In MCP, import_evidence_bundle actually merges a bundle into the live session. The contract states that imported content is data only and is never executed.

The other common way to get a bundle is from an agent session. After the agent has run its analysis over MCP, have it call export_evidence_bundle with an absolute path. That writes the session’s whole ledger, including recorded unknowns, atomically. Do this before close_binary, because closing clears retained records.

Step 3: Canonicalize with evidence-export

rea evidence-export /work/evidence/addon-v1.bundle.json /work/evidence/addon-v1.canonical.json
rea evidence-export /work/evidence/addon-v2.bundle.json /work/evidence/addon-v2.canonical.json

evidence-export re-validates the source and writes canonical, byte-stable JSON atomically. It won’t replace an existing file unless you pass --overwrite. Why bother when you already have a valid bundle?

  • Stable diffs. Canonical bytes mean that a git diff or a checksum of two exports shows real content changes, not formatting noise.
  • Archiving. The canonical file is what you commit next to a review report or attach to an issue.
  • Clean inputs. rea compare is documented as comparing two canonical bundles.

If an export fails after the file has already been written, look at details.published, details.published_path and details.bytes. The MCP contract uses these to tell a completed write with a cleanup problem apart from a write that never happened.

Step 4: Compare bundles with rea compare

rea compare /work/evidence/addon-v1.canonical.json /work/evidence/addon-v2.canonical.json --json \
  > /work/evidence/addon.bundle-diff.json

jq '{status, summary, limitations}' /work/evidence/addon.bundle-diff.json

(compare-bundles is an alias.) It’s important to understand what this does. Bundle comparison compares evidence membership, not program behavior. In REA’s implementation:

  • Records present in both bundles (same evidence_id) count as unchanged.
  • A record present on only one side is classified unknown with conclusion unresolved_branch, not added or removed. The reason given: a bundle has no inventory-completeness marker, so a missing record may just mean it wasn’t captured.
  • Residual unknowns are compared by revision history: history_advanced when one history extends the other, and history_diverged (a contradiction) when they split.
  • The top-level status is unchanged, changed or unknown, and both bundle SHA-256 digests are included.

Two versions of a binary have different SHA-256s, so their records will have different IDs and almost everything lands in unknown. That’s the honest answer at this level. Bundle comparison earns its keep elsewhere: diffing two runs on the same version (did a teammate reproduce my findings? did a REA upgrade change a result?), and tracking how open questions moved between review rounds. In MCP, compare_bundles also accepts record_pairs to pair specific records explicitly one-to-one. The CLI command takes just the two paths.

For “what changed in the code”, use the domain comparison for the layer you’re looking at.

Step 5: Diff the JavaScript layer with compare-application-versions

Analyze each release’s JavaScript into an application graph:

rea analyze-javascript-application /work/app-v1/app.asar --json > /work/evidence/app-v1.json
rea analyze-javascript-application /work/app-v2/app.asar --json > /work/evidence/app-v2.json

The input to compare-application-versions is a JSON object with the two full Evidence records as left and right. CLI workflow commands take inline JSON or a file path, and they reject ID-only inputs, because a separate CLI process can’t see an MCP session’s retained records:

jq -n --slurpfile l /work/evidence/app-v1.json --slurpfile r /work/evidence/app-v2.json \
  '{left: $l[0], right: $r[0]}' > /work/evidence/versions-input.json
rea compare-application-versions /work/evidence/versions-input.json --json > /work/evidence/versions.json

jq '.normalized_result | {summary, coverage}' /work/evidence/versions.json

Real application Evidence can be hundreds of megabytes, so jq --slurpfile may need memory. The JavaScript workflows guide shows the same step with a short Node script if you prefer that.

How pairing works, from the JavaScript application workflows guide: an entity is paired only when a tier yields one unique candidate on each side. The tiers, in order: exact content digest, exact module-source digest, exact node identity, source-map identity, structural fingerprint, then a semantic key for non-module entities. Minified names and module ordinals are never treated as identities. Each item is unchanged, added, removed, changed or unknown. added/removed requires complete coverage on the opposite graph. Otherwise the item is unknown.

If you want to check the result end to end, the repository has a verifier that takes two operator-provided directories or ASARs (npm run verify:application-workflows -- --left ... --right ...). It is a development script in the REA checkout, so run it only if you are working from source.

Step 6: Bring in historical source with import-reference-source

Release artifacts are bundled and minified. If the project is open source, the source at a tag is the best reference you have. REA treats it as reference data, never as an observation of the shipped app:

cd /work/src-v1 && git checkout v1.0.0   # the tag matching app-v1
rea import-reference-source /work/src-v1 --json > /work/evidence/src-v1.graph.json

What the import records, per the CLI guide:

  • hashes and metadata for each included file
  • the HEAD commit of the nearest enclosing Git repository. Whether the working tree actually matches that commit is recorded as unknown, so commit or stash local edits first.
  • exclusions: the project’s .gitignore, plus defaults such as node_modules/, dist/ and *.log, each recorded with the pattern and its origin
  • optional sensitive-path patterns, set as a JSON array in REA_REFERENCE_SECRET_PATTERNS_JSON and reported separately

Host note: historical-source import needs safe no-follow file opens on Linux or macOS. Native Windows returns unsupported_host, so use WSL there.

Now map that source onto a shipped bundle:

jq -n --slurpfile ref /work/evidence/src-v1.graph.json --slurpfile app /work/evidence/app-v1.json \
  '{reference: $ref[0], application: $app[0]}' > /work/evidence/src-to-bundle-input.json
rea compare-source-to-bundle /work/evidence/src-to-bundle-input.json --json > /work/evidence/src-to-bundle.json

jq '.normalized_result.summary' /work/evidence/src-to-bundle.json

Each historical file is classified unchanged, modified, removed, split, merged, duplicated or unknown. The scoring signals are reported with their weights: exact source digest, source-map original path, exact current path, path suffix, basename and language extension. Exact digests win. Weak basename or extension matches never force a mapping.

Two useful comparisons:

  • v1 source vs the v1 bundle: a sanity check. It shows how well the published artifact corresponds to the tagged source and where the build pipeline changed things.
  • v1 source vs the v2 bundle: shows which old source files still have exact or high-confidence matches in the new release, and which became modified or unknown. Combined with Step 5, this tells you where to read the real Git diff first.

If you saved a graph with an older REA version whose pattern exclusions lack pattern, re-import it before running this comparison, as the docs instruct.

Troubleshooting

evidence-import rejects my bundle. Usually an ID mismatch: the record was edited by hand or by a tool after export. Evidence IDs are content-derived, so any edit breaks them. Export again from the source session or snapshot.

evidence-export refuses to write. The destination exists. Add --overwrite deliberately, or pick a new filename.

rea compare says almost everything is unknown. Expected across two versions; see Step 4. Use compare-application-versions, compare-managed-members for .NET, or compare_functions/compare_artifacts over MCP for native and artifact layers.

The snapshot doesn’t seem to be reused. A hit requires the same bytes, operation, parameters, provider and settings. A different --provider, a changed analysis profile or a metadata write all cause a miss.

Workflow command says invalid_request with an Evidence ID. The CLI needs the full record. Retained IDs only work inside one MCP connection.

import-reference-source fails with unsupported_host. You’re on native Windows. Run REA inside WSL.

Next steps