Gunnar

mikronova/modgunn

.nornir/worlddelta-reasoning.md

# worlddelta (Engine B) — reasoning

> **Class:** `reasoning`. Why Engine B is shaped this way, and the forks not taken. The
> descriptive shape is in [`worlddelta-design.md`](worlddelta-design.md); the full analysis
> catalog + prior-art survey in [`engine-b-analyses.md`](engine-b-analyses.md) and
> [`modgunn-master-plan.md`](modgunn-master-plan.md) §3/§7.

## Why a behaviour-delta at all (history almost nobody has)

The field is fragmented: SCA tools own the dep-graph, SAST tools own the call-graph, GUAC owns
metadata aggregation, Socket owns behavioral malware — and *almost no one* owns **history**.
Point-in-time is the industry norm. modgunn historizes the whole world and diffs any two
snapshots, so it can emit a signal the standard stack structurally cannot: "a 5th-level jar,
unchanged for years, silently changed behaviour" (A22). That signal only exists if you keep a
per-symbol behaviour history — which is exactly what the skade/Iceberg warehouse is for, and
why Engine B exists as a distinct engine rather than a bigger scanner.

## Why one engine fuses call-graph + dep-graph

Reachability-gated vuln (A8) — the commercial differentiator (Snyk/Endor) — needs *both* the
call-graph (is the vulnerable function reached?) and the dep-graph (which dep, which version?).
Tools that own only one can't do it well; Snyk's reachability is a 3-language add-on. modgunn
carries both in one `WorldSnapshot` vocabulary, so A8 is first-class, multi-ecosystem and
offline. That fusion is the reason the vocabulary is modgunn-owned (`Symbol`/`CallEdge`/
`DepNode`/`DepEdge`) rather than two borrowed graphs.

## Why modgunn owns its SCIP vocabulary (lifted, not imported)

The SCIP/knowledge machinery originally lived in nornir. Importing it would make
`modgunn → nornir` — the forbidden cycle. So the graph vocabulary is **lifted into** modgunn
as plain data types with no nornir dependency; nornir remains the consumer (viz pane +
tripwire feed). Leaf-cleanliness wins over code reuse here because the alternative reintroduces
the exact cycle the crate was built to break.

## Why the tripwire battery is a trait, not a fixed function

`Tripwire` is a trait and `diff` runs a `Vec<Box<dyn Tripwire>>` so the cheap default
battery (`default_tripwires`) and the heavy world-delta site's extended set share one fold via
`diff_with`. This let the analyses land incrementally — all **25 of 25** now carry real logic
(the seven pairwise tripwires fire by default; the declarative `PolicyTripwire` /
`ThresholdTripwire` are plugged in with rules at the heavy site) — without ever changing
`diff`'s signature or the `BehaviorDelta` type. A fixed function would force every analysis into
one build; the trait lets the lean and heavy planes differ by data.

## Why `FingerprintDriftTripwire` fires only on the silent case

A version bump or a public-API change is already visible to every consumer — flagging it would
be noise. The dangerous case is a dependency whose *effective behaviour* (transitive call
closure) changed while its version and public signature stayed identical: nothing in the
manifest moved, so nothing else would notice. Restricting the finding to `same package version
&& same display && changed fingerprint` isolates exactly that silent signal and keeps the
tripwire quiet on the visible changes other tools already catch.

## Why heavy graph algorithms are materialized off the CDC delta (not recomputed)

Betweenness is O(V·E), PageRank O(E·iters), min-cut O(V·E²) — recomputing them per query over
the whole transitive world is unaffordable at S3/S4. The design materializes them into Iceberg
tables and refreshes only the neighborhood a `read_delta` touched (see
[`warehouse-reasoning.md`](warehouse-reasoning.md)). That is why the warehouse's CDC seam and
Engine B are co-designed: the delta is what makes whole-graph analytics incremental instead of
batch.

## Why heavy and off by default

Full SCIP closure + whole-graph algorithms + (eventually) embeddings need 128 GB–1 TB RAM.
holger's gate and static-holger must stay pure-leaf. So Engine B is `--features scip`, never
compiled into the lean gate, and its verdict still folds into the same `core::Verdict` so the
split is invisible to consumers. The general two-engine rationale is in [`reasoning.md`](reasoning.md).