Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

src/mimir_lens/mod.rs

//! # Mimir lenses — the QUERY half, lifted into nornir (ARCHITECTURE L5a)
//!
//! `dwarves::mimir` states that the QUERY half of every lens belongs in nornir, not
//! dwarves: "A query that only dwarves can reach is a query korp's UI and nornir's
//! own viz cannot use." This module is that lift: the folds ([`bloat`], [`zombie`],
//! [`risk`], [`weak`], [`benchcov`] … — the scoring / report half of each lens) and
//! ONE dispatch point, [`answer`], that every front door calls.
//!
//! **Mímir v2: the folds read the code room.** The warehouse readers of v1 (the
//! per-lens hand joins over `symbol_facts` / `call_edges`, callees matched by
//! name) are gone. [`room_feed`] gathers each lens's inputs as typed facts — from
//! the workspace's code room (`mimir-room`: rustc-resolved node ids, edges, spans,
//! linked symbols) and from the warehouse for the feeds that are not a graph (git
//! heat, bench and test runs, sbom, advisories, the dep graph) — and the folds
//! score them without knowing where a fact came from.
//!
//! No lens waits for a producer any more ([`WAITING`] is empty). `data_reach`
//! reads the driver's TOUCHES_TABLE edges; `similar_code` and
//! `semantic_clones` read one vector per function node (`vector::fnvec`, written
//! by the `embeddings` feed) and say `no_embedder` / `vectors_missing` when there
//! is none. [`subclone`] folds source text, with the room's item spans as an input.
//!
//! **Every response says what it rests on**: `feeds` lists each input with its row
//! count, `evidence_note` names the missing ones ("a missing producer, not an
//! absence of findings") and the lower bounds, `lens_meta.state` tells a computed
//! answer (`complete` / `partial` / `empty_fact` / `empty_gap`) from a refusal. An
//! empty ranked list and an unfed input must NEVER draw the same — the pane's
//! signature failure class.
//!
//! What stays in dwarves permanently is the GATE (`--fail-on-findings`) and the
//! PROPOSAL. The direction law is unchanged: `dwarves → nornir` only.

pub mod anomaly;
pub mod benchcov;
#[cfg(feature = "vector")]
pub mod group_twins;
pub mod binary_drift;
pub mod bloat;
pub mod change_series;
pub mod churn_filter;
#[cfg(feature = "vector")]
pub mod clone_inc;
pub mod data_reach;
pub mod identity;
pub mod name_collision;
pub mod queued;
#[cfg(feature = "code-room")]
pub mod reach_inc;
pub mod reachable_vuln;
pub mod risk;
pub mod room_feed;
pub mod rows;
pub mod sources;
#[cfg(feature = "vector")]
pub mod subclone;
pub mod symbol_identity;
pub mod trust;
pub mod undoc_api;
pub mod verdict_svg;
pub mod villa;
pub mod weak;
pub mod world_impact;
pub mod zombie;

use serde_json::{Value, json};

/// The `evidence_note` every Mímir lens answers with until its v2 producer is
/// wired (PLAN-MIMIR-V2 §7: the old read/join halves are gone, the graph that
/// replaces them is not built yet). Named so a surface can test for it.
pub const NOT_WIRED_NOTE: &str =
    "Mímir v2: producer not wired yet — a missing producer, not an absence of findings";

/// The honest envelope of a lens whose v2 producer is not wired yet.
///
/// It keeps the wire shape every reader already parses (`kind`, `worst`,
/// `feeds`, `evidence_note`, `lens_meta`) but it is NOT an empty finding list:
/// `lens_meta.state` is `"not_wired"` and `evidence_note` says why, so no pane
/// can draw it as "computed, found nothing".
pub fn not_wired(lens: &str, repo: &str) -> Value {
    json!({
        "kind": lens,
        "worst": [],
        "feeds": [],
        "evidence_note": format!("{lens}: {NOT_WIRED_NOTE}."),
        "lens_meta": {
            "state": "not_wired",
            "lens": lens,
            "repo_scope": repo,
        },
    })
}

/// The table-fed lenses [`answer`] dispatches, by the name every front door
/// (gRPC `Mimir.*`, the MCP `mimir_*` tools, `nornir mimir analyze`) sends.
pub const LENSES: &[&str] = &[
    "generic_cost",
    "zombie_features",
    "change_risk",
    "similar_code",
    "semantic_clones",
    "weak_tests",
    "bench_gaps",
    "villa",
    "churn",
    "anomaly",
    "world_impact",
    "binary_drift",
    "data_reach",
    "trust",
    "name_collision",
    "symbol_identity",
    "reachable_vuln",
    "undoc_api",
    "system_boundary",
    // the clone signals (`clone_signals`, 2026-10-17): clones by what a
    // function DOES — `semantic_clones` above is the normalised model held by
    // them; `same_intent` is the raw model, NAME-DRIVEN and labelled so
    "exact_clones",
    "near_clones",
    "structural_clones",
    "role_neighbours",
    "same_intent",
    // v1's semantic twins on v2's signals: copies (Type-3) and twins (Type-4)
    "semantic_twins",
    // the twins × DEPENDS_ON: crate A imports crate B and holds a copy of a B
    // function — call the library's
    "reimplements_shared",
];

/// The lenses whose room has the dependency universe folded in: their answer
/// walks from our code down through std and the dependencies to the system
/// boundary (`syscall` nodes, SYSCALLS edges).
pub const RING_ON: &[&str] = &["system_boundary"];

/// The lenses whose answer for ONE repo reads only that repo's own nodes and
/// the edges leaving them: asked with a `repo`, they open the room of that repo
/// alone (`Scope::repo`), never the whole workspace's.
///
/// Every other room-fed lens keeps the workspace room even for one repo, because
/// its answer counts something that ARRIVES from the other repos: call sites
/// into the repo (`change_risk`, `bench_gaps`, `zombie_features`), reachers
/// (`data_reach`), merged ids (`symbol_identity`, `name_collision`), pairs
/// (`similar_code`, `semantic_clones`), dependents (`world_impact`), the
/// dependency stubs other repos call (`reachable_vuln`), the test reach
/// (`weak_tests`).
pub const REPO_SCOPED: &[&str] = &["villa", "churn", "undoc_api", "trust", "system_boundary"];

/// The lenses that read no room at all: answered on the request, never
/// queued — there is no room build to wait for. `anomaly` reads
/// `git_heat_facts` only; `generic_cost` and `binary_drift` read what Build
/// Thing runs filed (builder 2: the `@build-thing` products and their binary
/// facts by sha256, [`room_feed`]'s `built`), never a linked artifact of the
/// room.
pub const ROOMLESS: &[&str] = &["anomaly", "generic_cost", "binary_drift"];

/// The lenses that read Builder 2's filed artifacts (the `@build-thing`
/// products and `binary_facts` / `section_sizes` / `symbol_sizes` by sha256):
/// a landed build forgets their kept answers, since a build does not move the
/// room's version. **The only binary producer** (owner, 2026-10-17): a Build
/// Thing run files; the reader never links, and nothing reads
/// `<builds>/mimir_v2/artifacts`.
pub const BUILD_FED: &[&str] = &["generic_cost", "binary_drift", "zombie_features"];

/// The room a lens opens for a request naming `repo` (`""` = every repo).
#[cfg(feature = "code-room")]
pub fn scope_of(lens: &str, repo: &str) -> mimir_room::Scope {
    let s = if !repo.is_empty() && REPO_SCOPED.contains(&lens) { mimir_room::Scope::repo(repo, None) } else { mimir_room::Scope::current() };
    s.with_universe(RING_ON.contains(&lens))
}

/// The lenses that fold SOURCE TEXT (the repo's checkout) with the code room's
/// item spans as an input. They have no `Mimir.*` gRPC verb — the MCP tool is
/// their door — so they are not in [`LENSES`]; [`answer`] dispatches them all
/// the same. `group_twins` answers from the compiled graph (the room's
/// per-line facts); `embeddings_groups` is an optional extra index whose
/// pairs are added beside the graph's.
pub const SOURCE_LENSES: &[&str] = &["subclone", "group_twins"];

/// Rows a lens returns when the request names no `limit` (the wire default 0).
pub const DEFAULT_LIMIT: usize = 50;

/// The lenses that stay `not_wired`, each with the producer it waits for
/// (wiring map §6.3: a surface stays `not_wired` until its producer row closes).
pub const WAITING: &[(&str, &str)] = &[
];

/// THE dispatch point of the Mímir lenses (wiring map §6.6): every front door
/// calls this with the lens name, what the door reaches (the workspace's code
/// room + warehouse) and the request as JSON — the wire query's fields by
/// their proto names (`repo`, `limit`, `min_instantiations`, `band`,
/// `cutoff_low`, `cutoff_high`, `z_threshold`, `act_at`, `hold_at`,
/// `min_commits_90d`; the MCP / CLI doors add their own, e.g. `threshold`).
///
/// The answer is always the lens envelope (`kind`, `worst`, `feeds`,
/// `evidence_note`, `lens_meta`); a lens that cannot answer says so in
/// `lens_meta.state`, never as an empty `worst` (see [`room_feed`]).
pub fn answer(lens: &str, reach: &crate::code_room::Reach<'_>, params: &Value) -> Value {
    use crate::code_room::{Refusal, State};
    let q = room_feed::Query::new(lens, params, DEFAULT_LIMIT);
    if lens == "subclone" {
        return room_feed::subclone(reach, &q);
    }
    if lens == "group_twins" {
        return room_feed::group_twins(reach, &q);
    }
    if !LENSES.contains(&lens) {
        let r = Refusal::new(State::InvalidArgument, format!("`{lens}` is not a Mímir lens (known: {})", LENSES.join(", ")));
        return room_feed::refused(lens, q.repo, &r);
    }
    if let Some((_, producer)) = WAITING.iter().find(|(l, _)| *l == lens) {
        return room_feed::not_wired(lens, q.repo, producer);
    }
    let wh = room_feed::Warehouse::of(reach);
    if lens == "anomaly" {
        return room_feed::anomaly(&wh, &q);
    }
    room_feed::from_room(reach, &wh, &q).unwrap_or_else(|| room_feed::not_wired(lens, q.repo, "its room feed"))
}