.wasm, most of your reverse-engineering work is still valid. The
same functions exist, doing the same things, at shifted table indices. The diff engine
recovers that work automatically, classifies every function in the new binary, ports annotations
forward, and hands you a focused changelog of what actually changed in the application.
This is the diff and carry-over stage of the WARDEN loop: diff/engine.py, driven by warden diff.
The diff engine reuses the identical fingerprint and similarity engine as the Oracle: same
hash compositions, same
similarity() function, different corpus. Any improvement to the
fingerprinting algorithm benefits both. See core concepts for the full fingerprint
breakdown.The fuzzy similarity score is sharper than a plain MinHash compare. It blends the MinHash term
with the call-neighborhood, opcode-histogram, type-signature, and instruction-count terms, and
exposes every sub-score so you can see exactly why a pair scored the way it did. This reduces
false modified and new classifications. Stable identity and determinism are unchanged: the
same input still yields the same stable_id and the same fingerprint, and similarity() is
still a pure function of its two fingerprints.How it works
diff_versions() loads all defined functions for both versions from the KB, reconstructs their
fingerprints from the stored rows, and runs three passes in sequence. Each pass consumes
functions from a shared “unmatched” set so a function is never counted twice.
Pass 1: exact-body match
Functions with an identicalexact_hash (SHA-256 of the raw body bytes) are matched first. This
is O(n) via a dictionary lookup. No similarity math is needed.
- Same index in both versions → classified unchanged.
- Different index → classified moved.
stable_id row in the symbols table,
so their annotations are already there. There is nothing to port.
Pass 2: stable identity match
Functions that share astable_id but whose raw body differs slightly (for example, a literal
constant was patched in a way that structural_hash absorbs) are matched by identity key lookup.
Because the KB’s symbols table is keyed on stable_id (not on a version or function index),
these functions also already share an annotation row.
- Classified unchanged or moved by the same index-comparison rule.
- Score is 0.99 to distinguish from a literal exact-body match.
Pass 3: greedy fuzzy match
Remaining functions (those that changed meaningfully enough to get a newstable_id) are paired
by the highest similarity().overall score among all remaining-from candidates.
The overall score is a sharper blend than a plain MinHash compare. A single fuzzy term alone
pairs functions that share opcode shape but do different work, which inflates the modified count
and hides genuine deltas. So the blend now combines the MinHash term with four corroborating
signals: the call-neighborhood overlap, the opcode-histogram cosine, a type-signature match, and
an instruction-count closeness term. Two functions that only look alike at the opcode level no
longer pass the threshold unless their call targets, type signatures, and sizes line up too. The
structural-skeleton hash still contributes a small bonus when it matches exactly.
overall stays in [0, 1]. Each term is symmetric in its two
fingerprints, so similarity(a, b) equals similarity(b, a).
similarity() exposes each of these as a sub-score on the returned MatchScore (fuzzy,
histogram, call_overlap, type_match, count_ratio, and the boolean structural), plus a
sub_scores dict that carries all six as numbers, so you can read off exactly which signals
carried a pairing and which dragged it down. An exact-body match short-circuits to 1.0 as before.
A pair is accepted if the best score is at or above MODIFIED_THRESHOLD = 0.6 and the two
functions share a structural skeleton (structural is true). A cosmetic edit (a bounds check, an
extra arithmetic op) keeps the control-flow and call skeleton, so the structural floor rejects
unrelated functions that only scored highly on the coarse shape terms without losing genuine
modifications. Accepted pairs are classified modified. Functions left unmatched after all three
passes become new (in the newer version only) or deleted (in the older version only).
Because the extra terms make each accepted pair better corroborated, the sharper score cuts down on
false modified and new classifications: lookalikes no longer steal a match slot from the
function that genuinely evolved.
The 0.6 threshold is deliberately lenient. A modified function that retains its general call
pattern and opcode character but gained a bounds check or an extra branch will typically score
0.65–0.85. Setting the threshold higher risks losing carry-over for legitimately modified
functions; setting it lower creates false pairings. The value is defined as
MODIFIED_THRESHOLD in diff/engine.py and can be overridden if you are working with a
heavily optimized corpus.The sharper blend keeps the same threshold meaningful. A real modification still clears 0.6
because its call neighborhood, type signature, and size travel with it, while a coincidental
opcode lookalike that used to scrape past on the fuzzy term alone now falls short on the
corroborating terms. The score stays a deterministic, pure function of the two fingerprints, so
the same pair always produces the same number.Classification summary
Annotation carry-over: identical vs. fuzzy
Unchanged and moved: zero work
Functions classifiedunchanged or moved share the same stable_id between both versions.
Because the symbols table is keyed to stable_id, not to a version row or function index,
these functions already point at the same symbol. There is nothing to copy. The name, type
signature, summary, provenance, and confidence from your v1 work are immediately visible in v2
with zero intervention.
Modified: copied with a confidence penalty
When a fuzzy match is accepted,_carry_symbol() runs:
- Look up the older function’s symbol by its
stable_id. - Check whether the newer function’s
stable_idalready has a symbol. If it does, leave it alone. A pre-existing annotation from a higher-authority source takes precedence. - Write a new symbol for the newer
stable_idwith:- The same name, type signature, and summary as the source.
provenance = "diff-carry"(rank 40 in the provenance economy, beloworacle(90) but aboveagent(30)).confidence = old.confidence × CARRY_PENALTYwhereCARRY_PENALTY = 0.7.
parseToken with confidence 0.92 after the Oracle and human review will arrive
in v2 as parseToken with confidence 0.644 and provenance diff-carry. The penalty signals
“probably still right, worth a second look.” Agents will not overwrite this (their rank is lower);
Oracle re-identification can upgrade it if the function still hits a corpus signature.
The evidence field records the carry trail:
The semantic changelog
After classification,render_changelog() produces a human-readable report that does two things
ordinary binary diff tools cannot: it counts only app-code changes and explains the rest as
runtime/toolchain churn.
A function is tagged as runtime churn if its name (from the current or previous version) starts
with any of a list of known prefixes:
Sample changelog
The warden diff command
diffs table as a JSON
DiffReport and printed as the semantic changelog.
warden diff completes, you can inspect carry-over results directly:
Full pipeline: v2 in practice
1
Ingest the new version
2
Diff and carry
3
Review only the app deltas
The changelog’s “Needs review” section lists the functions that actually changed in
application code. Inspect each:If the carried name is still correct, lock it:
4
Re-run the Oracle and agents on new functions
New and heavily modified functions have no annotation yet. The Oracle may identify runtime
additions from a toolchain bump; agents cover the rest.
5
Export a deliverable
What the DiffReport contains
The full report is stored in the diffs table as JSON (the result of DiffReport.as_dict())
and contains every Change record:
review: true marks non-runtime modified functions. These are exactly the functions that appear in “Needs
review” in the changelog. carried_name is non-null when _carry_symbol() wrote a symbol for
this pairing.
Provenance economy position of diff-carry
diff-carry sits at rank 40 in the provenance hierarchy: below oracle (90), export (60),
and import (55), but above agent (30). In practice this means:
- An Oracle re-identification pass on v2 will upgrade a diff-carry annotation if the function still hits a corpus signature at score ≥ 0.82.
- An agent pass will not overwrite a diff-carry annotation, regardless of claimed confidence.
- A
warden set-namecall (provenancehuman) always wins.
Time-travel queries
Once you have ingested and diffed several versions, the KB holds the full history of every function across the timeline. TheKnowledgeBase class exposes read-only methods that answer the
common history questions directly, without re-running a diff:
when_first_seen(stable_id)answers “when did this function first appear?” It returns the earliest version label that contains the function’s stable identity.evolution_of(stable_id)answers “when did its body actually change?” It walks the versions the function appears in and reports each point where the body changed against where it stayed the same.symbol_history(stable_id)answers “who named it and when?” It returns the chain of symbol rows for the identity, with the name, provenance, and confidence recorded at each step.find_by_name(name)resolves a human-facing name back to the stable identities that have carried it, so you can start from a name and look up its history.resolve_stable_id(query)takes a full stable_id, a unique stable_id prefix, or a name and returns a single stable_id string (orNone), so you can feed a user query straight into the history methods.
warden history command
exposes the same answers on the command line (it is being wired separately), so you can ask for a
function’s first appearance, body-change timeline, and naming history without writing any Python.
Core concepts
Stable identity, the shared fingerprint engine, and the provenance economy: the three ideas
behind how carry-over works.
CLI reference
Full flag documentation for
warden diff and every other command.