# Jube vs mw-core: Gap Analysis — Overview

## Purpose

`mw-core` (this Elixir/Phoenix umbrella) was built as a "parity" reimplementation of **Jube**
(`aml-fraud-transaction-monitoring-master`, a C#/.NET AML/fraud transaction-monitoring system).
This document set records, in detail, where the two systems actually agree and where they
diverge — both intentional architectural decisions and unintentional gaps/bugs discovered while
hardening mw-core's risk engine.

Everything here is grounded in direct source inspection of both codebases (not assumption) —
file paths and line numbers are cited throughout the companion documents so each claim can be
re-verified against current code.

## Document set

| Doc | Covers |
|---|---|
| [01-APIS-AND-FLOWS.md](01-APIS-AND-FLOWS.md) | HTTP/AMQP endpoints, request/response shape, auth, model routing, dry-run semantics |
| [02-RULE-EXPRESSION-ENGINE.md](02-RULE-EXPRESSION-ENGINE.md) | How each system parses/evaluates a rule condition — compiled code vs. hand-rolled interpreter |
| [03-GATEWAY-RULES.md](03-GATEWAY-RULES.md) | First-stage filters: semantics, short-circuiting, sampling |
| [04-ACTIVATION-RULES.md](04-ACTIVATION-RULES.md) | Final-stage rules: decision combination, side effects |
| [05-ABSTRACTION-RULES-AND-VELOCITY.md](05-ABSTRACTION-RULES-AND-VELOCITY.md) | Derived/windowed statistics (count, sum, stddev, distinct, etc.) |
| [06-TTL-COUNTERS.md](06-TTL-COUNTERS.md) | Sliding-window counters: data model, grouping, storage schema |
| [07-MULTI-MODEL-SCOPING.md](07-MULTI-MODEL-SCOPING.md) | How each system isolates one model's rules/state from another's |
| [08-FIXED-GAPS-CHANGELOG.md](08-FIXED-GAPS-CHANGELOG.md) | Chronological list of every gap found and fixed in mw-core this engagement, plus what's still open |

## How to read these documents

Each topic document follows the same structure:

1. **Jube's actual behavior** — quoted/cited from source, not from documentation or assumption.
2. **mw-core's actual behavior** — same standard of evidence.
3. **Gap table** — side-by-side comparison, each row tagged as one of:
   - 🟢 **Parity** — behaves equivalently for practical purposes
   - 🟡 **Intentional divergence** — a deliberate design choice, with the trade-off stated
   - 🔴 **Unintentional gap (fixed)** — a real bug found and corrected during this engagement
   - ⚠️ **Unintentional gap (open)** — a real bug or missing capability, not yet addressed

## Headline findings (detail in the topic docs)

1. **Jube's rule engine is a real compiler** (Roslyn-compiled VB.NET) with full arithmetic,
   nested boolean logic, and native field-to-field comparison. mw-core's `RuleExpression` is a
   hand-rolled JSON-tree/legacy-text interpreter that had to have AND/OR support, arithmetic, and
   field-to-field comparison added incrementally — these were all silent gaps (rules parsed
   without error but evaluated incorrectly) until found and fixed. See
   [02-RULE-EXPRESSION-ENGINE.md](02-RULE-EXPRESSION-ENGINE.md).

2. **Jube treats sanction/list/dictionary lookups as injected parameters** to the compiled rule
   function, not part of the expression language. mw-core embeds them as special node types
   (`"op": "sanction" | "tag" | "dict"`) inside the same JSON expression tree — these node types
   existed in the parser/UI but had **zero evaluation logic** (`RuleExpression.evaluate/2`'s
   catch-all silently returned `false`) until fixed this engagement.

3. **Jube's gateway-rule semantics are stricter than mw-core's**: in Jube, if *no* gateway rule
   matches, the entire downstream scoring pipeline (TTL counters, abstractions, activations) is
   **skipped entirely**. In mw-core, gateway rules only ever *flag and continue* (unless
   `is_bypass`/a sanction hit short-circuits) — a transaction with zero matching gateway rules
   still runs the full pipeline. This is an **intentional divergence**, not a bug, but it means
   gateway rules carry different weight in each system. See
   [03-GATEWAY-RULES.md](03-GATEWAY-RULES.md).

4. **Jube's approval workflow (`ReviewStatusId`) is enforced at rule *load* time** — an unapproved
   rule is simply never added to the in-memory collection the engine evaluates against. mw-core
   had the *database column* and *UI* for this concept but the application layer never declared
   the field in any Ecto schema and never filtered on it in any query — unapproved
   (`pending_review`/`draft`/`rejected`) rules executed identically to approved ones, across all
   three rule types. Fixed this engagement. See
   [08-FIXED-GAPS-CHANGELOG.md](08-FIXED-GAPS-CHANGELOG.md).

5. **Jube's per-model isolation is structural** — rules live in a `Dictionary<int,
   EntityAnalysisModel>`, each with its own `Collections` object, so model A's rules cannot
   physically reach model B's evaluation. mw-core's caches were tenant-scoped only (not
   model-scoped) across three independent subsystems (rule caches, TTL counter cache, abstraction
   velocity journal) — found and fixed one at a time during this engagement, each requiring
   explicit `model_id` threading through call sites that previously had no model awareness at
   all. This is a **structural difference in risk profile**: Jube's design makes cross-model
   leakage close to impossible by construction; mw-core's makes it easy to reintroduce in any new
   feature that touches these caches unless model-scoping is deliberately re-applied each time.
   See [07-MULTI-MODEL-SCOPING.md](07-MULTI-MODEL-SCOPING.md).

6. **TTL Counter storage schema differs by deliberate choice, not oversight.** Jube gives each
   counter *definition* its own Redis key (keyed by the counter's GUID), with the resolved
   grouping value as a hash field. mw-core shares one Redis hash per `entity:value:horizon`,
   with multiple metrics as different hash fields, requiring a metric-name-from-row-name parsing
   convention Jube doesn't need. This was evaluated and **knowingly kept** — confirmed sufficient
   for business correctness, with the schema rewrite deferred as a separate, larger effort if
   Jube-level bulk-listing/operational parity is ever required. See
   [06-TTL-COUNTERS.md](06-TTL-COUNTERS.md).
