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

nordisk/nornir

src/code_room/reader/wired.rs

//! The linked half of the reader: every surface answered from a room.
//!
//! One rule for all of them: a real answer carries the room's evidence
//! (`state`, `evidence`), and "cannot answer" is a [`Refusal`] by state —
//! `not_consumed` when the repo has no consumer offset, `invalid_argument`
//! when a name resolves to nothing, `not_wired` (with the producer row that
//! is missing) when the graph does not hold the fact at all. Never an empty
//! list that reads as "there is none".

use std::collections::{BTreeMap, BTreeSet, HashMap};

use mimir_room::{AnswerState, EdgeSel, Evidence, GroupBy, Mask, MaskSpec, NameMatch, NameQuery, Room, Scope};
use mimir_schema::{EdgeKind, Origin, Provenance, kind as nk};
use serde_json::{Value, json};

use super::super::{CodeRoom, Reach, Refusal, State};
use crate::arch::{self, ArchGraph};
use crate::knowledge::query::KnowledgeView;
use crate::knowledge::symbols::{CallEdgeRow, SymbolRow};
use crate::warehouse::access_scan::{Access, AccessEdge};

/// Said with every reachability answer (producer gap P6).
pub const LOWER_BOUND: &str =
    "calls through operators, closures and fn pointers are not recorded by the producer yet: every reachability answer is a lower bound";
/// Said with every answer of the binary verbs (producer gap P4).
pub const LINKED_ONLY: &str = "source CALLS between LINKED nodes only (provenance `both`): a call the compiler inlined away or made indirect is absent — \
     binary call edges have no producer yet";
/// Said when a room holds `cfg` gates but no GATED_BY edge (a log whose
/// producer predates mimir-schema 2, or a repo consumed without the gate scan).
pub const ACTIVE_CFG_ONLY: &str = "this room holds no GATED_BY edge and no `cfg_gated_out` stub: only items the consumer's `cargo check` configuration \
     compiled are listed, by their own `cfg` — what a feature that was OFF there would add is absent (a lower bound)";

/// Node kinds that are SYMBOLS in the v1 row vocabulary (`symbol_facts`).
const SYMBOL_KINDS: [&str; 13] = [
    nk::FN,
    nk::METHOD,
    nk::STRUCT,
    nk::ENUM,
    nk::UNION,
    nk::TRAIT,
    nk::TRAIT_ALIAS,
    nk::IMPL,
    nk::CONST,
    nk::STATIC,
    nk::TYPE,
    nk::MACRO,
    nk::MOD,
];

/// `Viz.Architecture` on the wire — field for field the 🏛 tab's
/// `viz::arch_tab::ArchPayload` (the tab is an egui type the server does not
/// build; a test on the tab's side decodes this into it).
#[derive(Debug, Clone, serde::Serialize)]
pub struct ArchPayload {
    pub graph: ArchGraph,
    pub members_with_data: Vec<String>,
    pub errors: Vec<String>,
    pub coverage: BTreeMap<String, String>,
    pub symbols: Vec<SymbolRow>,
    pub calls: Vec<CallEdgeRow>,
    pub metro_fuel_source: String,
    /// `(gRPC handler fn, table)`: the warehouse table(s) nearest to each
    /// handler along CALLS + TOUCHES_TABLE — the 🚇 lines' termini. A handler
    /// that reaches no table has no pair (its line ends in a placeholder).
    pub access: Vec<(String, String)>,
}

/// `Viz.Knowledge` on the wire — the 🗺 tab's `KnowledgeSummary`.
#[derive(Debug, Clone, serde::Serialize)]
pub struct KnowledgeSummary {
    pub repos: Vec<RepoScanSummary>,
    pub crates: Vec<Bubble>,
    /// Local crate → local crate (CALLS ∪ USES_TYPE): the release tab's deep edges.
    pub crate_edges: Vec<CrateEdge>,
}

/// `from` calls or uses a type of `to`.
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct CrateEdge {
    pub from: String,
    pub to: String,
    /// Item-level edges collapsed into this one.
    pub edges: u64,
    /// A few of the names used (the first by ordinal).
    pub via: Vec<String>,
}

/// One consumed repo of the knowledge map.
#[derive(Debug, Clone, serde::Serialize)]
pub struct RepoScanSummary {
    pub repo: String,
    pub ok: bool,
    pub symbols: usize,
    pub calls: usize,
    pub features: usize,
    pub git_files: usize,
    pub error: Option<String>,
}

/// One local crate of the knowledge map.
#[derive(Debug, Clone, serde::Serialize)]
pub struct Bubble {
    pub repo: String,
    pub krate: String,
    pub symbols: usize,
    pub calls: usize,
    pub files: usize,
    pub gates: usize,
    pub heat_30d: i64,
}

/// Call edges shipped as metro fuel over one wire message, at most.
const METRO_FUEL_CAP: usize = 250_000;

pub(super) fn internal(e: impl std::fmt::Display) -> Refusal {
    Refusal::new(State::Internal, format!("code room: {e}"))
}

fn invalid(msg: impl Into<String>) -> Refusal {
    Refusal::new(State::InvalidArgument, msg)
}

fn not_wired(msg: impl Into<String>) -> Refusal {
    Refusal::new(State::NotWired, msg)
}

fn text<'a>(p: &'a Value, key: &str) -> &'a str {
    p.get(key).and_then(Value::as_str).unwrap_or("").trim()
}

fn limit_of(p: &Value, default: usize) -> usize {
    match p.get("limit").and_then(Value::as_u64) {
        Some(0) | None => default,
        Some(n) => n as usize,
    }
}

/// The room of `repo` — every consumed repo when `repo` is empty — or the
/// state that says why there is none.
pub fn open(reach: &Reach<'_>, repo: &str) -> Result<Room, Refusal> {
    let handle = reach.code_room()?;
    open_in(handle, repo)
}

fn open_in(handle: &CodeRoom, repo: &str) -> Result<Room, Refusal> {
    let scope = if repo.is_empty() { Scope::current() } else { Scope::repo(repo, None) };
    let room = handle.room(&scope)?;
    if room.scope().repos.is_empty() {
        return Err(Refusal::new(
            State::NotConsumed,
            format!("code room of `{}`: no repo has a consumer offset yet — nothing of this workspace was consumed", handle.workspace()),
        ));
    }
    Ok(room)
}

/// Fold another query's gaps and notes into the evidence that is returned.
fn absorb(into: &mut Evidence, other: &Evidence) {
    for m in &other.missing {
        if !into.missing.contains(m) {
            into.missing.push(m.clone());
        }
    }
    for n in &other.notes {
        // each query words its own "empty answer" note; the final state says it once
        if !n.starts_with("empty answer") && !n.starts_with("matched as") && !n.starts_with("capped:") && !into.notes.contains(n) {
            into.notes.push(n.clone());
        }
    }
    for k in &other.kinds {
        if !into.kinds.contains(k) {
            into.kinds.push(*k);
        }
    }
}

/// The final state of an answer that is `empty` or not, given its evidence.
fn settle(ev: &mut Evidence, empty: bool) {
    let gaps = !ev.missing.is_empty();
    ev.state = match (empty, gaps) {
        (false, false) => AnswerState::Complete,
        (false, true) => AnswerState::Partial,
        (true, false) => AnswerState::EmptyFact,
        (true, true) => AnswerState::EmptyGap,
    };
    ev.notes.retain(|n| !n.starts_with("empty answer"));
    if empty {
        ev.notes.push(if gaps {
            "empty answer, and inputs are MISSING (above): the empty set may be the gap, not a fact".into()
        } else {
            "empty answer with every input present: the room holds no match (a fact, not a gap)".into()
        });
    }
}

fn note(ev: &mut Evidence, n: &str) {
    if !ev.notes.iter().any(|x| x == n) {
        ev.notes.push(n.to_string());
    }
}

fn miss(ev: &mut Evidence, input: &str, repo: Option<&str>, why: impl Into<String>) {
    let m = mimir_room::Missing { input: input.to_string(), repo: repo.map(str::to_string), why: why.into() };
    if !ev.missing.contains(&m) {
        ev.missing.push(m);
    }
}

/// `body` (a JSON object) with `state` and `evidence` beside its own fields.
fn envelope(mut body: Value, ev: &Evidence) -> Value {
    if let Some(o) = body.as_object_mut() {
        o.insert("state".into(), json!(ev.state.as_str()));
        o.insert("evidence".into(), serde_json::to_value(ev).unwrap_or(Value::Null));
    }
    body
}

/// A JSON object from OWNED values, moved in. (`json!` serializes every
/// expression it is handed once more — `"fns": fns` deep-copied a function
/// tier's 120 000 rows, a quarter of its answer time.)
pub(crate) fn obj<const N: usize>(pairs: [(&str, Value); N]) -> Value {
    Value::Object(pairs.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
}

/// Readable paths, computed once per node.
struct Paths<'r> {
    room: &'r Room,
    memo: HashMap<u64, String>,
}

impl<'r> Paths<'r> {
    fn new(room: &'r Room) -> Self {
        Self { room, memo: HashMap::new() }
    }
    fn of(&mut self, o: u64) -> String {
        if let Some(p) = self.memo.get(&o) {
            return p.clone();
        }
        let p = self.room.display_path(o);
        self.memo.insert(o, p.clone());
        p
    }
}

/// The nodes `name` means, or `invalid_argument` naming what was tried: a
/// name that resolves to nothing is not "no callers".
fn resolve(room: &Room, name: &str, what: &str) -> Result<(Vec<u64>, Evidence), Refusal> {
    if name.is_empty() {
        return Err(invalid(format!("`{what}` is empty: give a moniker, a `a::b::c` path (or its suffix), or a linker symbol")));
    }
    let found = room.lookup(&NameQuery::new(name).mode(NameMatch::Auto)).map_err(internal)?;
    if found.value.is_empty() {
        return Err(invalid(format!(
            "`{name}` ({what}) names no node of the room [{}] — tried as a moniker, as a linker symbol and as a path suffix",
            room.scope()
        )));
    }
    Ok((found.value, found.evidence))
}

// ------------------------------------------------------------------ rows

fn kind_of<'a>(room: &'a Room, o: u64) -> &'a str {
    room.node(o).kind.unwrap_or("")
}

/// `fn` for both free fns and methods (the v1 `symbol_facts.item_kind`).
fn item_kind(kind: &str) -> &str {
    if kind == nk::METHOD { nk::FN } else { kind }
}

/// The locally defined symbol nodes of `repo` (all repos of the room when
/// `None`), in ordinal order.
fn symbol_nodes(room: &Room, repo: Option<&str>) -> Result<Mask, Refusal> {
    let spec = MaskSpec {
        repos: repo.map(|r| vec![r.to_string()]).unwrap_or_default(),
        origins: vec![Origin::Local],
        node_kinds: SYMBOL_KINDS.iter().map(|k| k.to_string()).collect(),
        defined_only: true,
        ..Default::default()
    };
    // (a symbol kind no node of this room has is no gap of the answer)
    Ok(room.mask(&spec).map_err(internal)?.value)
}

/// The trait an impl block implements, by its readable name.
fn impl_traits(room: &Room) -> Result<HashMap<u64, u64>, Refusal> {
    let e = room.edges(&EdgeSel::kinds(&[EdgeKind::Implements])).map_err(internal)?;
    Ok(e.value.edges.iter().map(|x| (x.from, x.to)).collect())
}

/// One node as a v1 symbol row. `visibility` is the producer's nominal
/// visibility; `doc_lines` is 1 / 0 for documented / not (the producer keeps
/// the first doc line, not a line count).
fn symbol_row(room: &Room, paths: &mut Paths<'_>, traits: &HashMap<u64, u64>, o: u64) -> SymbolRow {
    let v = room.node(o);
    let kind = v.kind.unwrap_or("");
    let name = v.name.unwrap_or("").to_string();
    let item_name = if kind == nk::IMPL {
        let ty = room.impl_self(o).and_then(|t| room.node(t).name).map(str::to_string).unwrap_or_else(|| paths.of(o).rsplit("::").next().unwrap_or("?").to_string());
        match traits.get(&o).and_then(|t| room.node(*t).name) {
            Some(tr) => format!("impl {tr} for {ty}"),
            None => format!("impl {ty}"),
        }
    } else {
        name
    };
    SymbolRow {
        crate_name: v.krate.unwrap_or("").to_string(),
        module_path: v.parent.map(|p| paths.of(p)).unwrap_or_default(),
        item_kind: item_kind(kind).to_string(),
        item_name,
        visibility: v.visibility.unwrap_or("").to_string(),
        file: v.file.unwrap_or("").to_string(),
        line: v.start_line.unwrap_or(0),
        doc_lines: v.has_doc.unwrap_or(false) as u32,
        signature: v.ty.map(str::to_string),
    }
}

/// CALLS edges as v1 call rows: one row per positioned site, or one row at
/// line 0 for an edge without one (a call inside a macro expansion).
fn call_rows(room: &Room, paths: &mut Paths<'_>, edges: &[mimir_room::EdgeOut], cap: usize) -> Vec<CallEdgeRow> {
    let mut out = Vec::new();
    for e in edges {
        let (caller, callee) = (paths.of(e.from), paths.of(e.to));
        let krate = room.node(e.from).krate.unwrap_or("").to_string();
        let sites = room.sites(e.from, e.to, e.kind);
        let row = |file: &str, line: u32| CallEdgeRow {
            crate_name: krate.clone(),
            caller_path: caller.clone(),
            callee_ident: callee.clone(),
            call_kind: "call".to_string(),
            file: file.to_string(),
            line,
        };
        if sites.is_empty() {
            out.push(row(room.node(e.from).file.unwrap_or(""), 0));
        } else {
            out.extend(sites.iter().map(|s| row(s.file, s.start_line)));
        }
        if out.len() >= cap {
            out.truncate(cap);
            break;
        }
    }
    out
}

/// The symbol / call view of one repo (every repo of the room when `None`).
fn view_rows(room: &Room, repo: Option<&str>) -> Result<(Vec<SymbolRow>, Vec<CallEdgeRow>, Evidence), Refusal> {
    let nodes = symbol_nodes(room, repo)?;
    let traits = impl_traits(room)?;
    let mut paths = Paths::new(room);
    let symbols: Vec<SymbolRow> = nodes.ordinals().iter().map(|o| symbol_row(room, &mut paths, &traits, *o)).collect();
    // every call a local definition of the repo makes — to anything
    let callers = room
        .mask(&MaskSpec { repos: repo.map(|r| vec![r.to_string()]).unwrap_or_default(), origins: vec![Origin::Local], defined_only: true, ..Default::default() })
        .map_err(internal)?
        .value;
    let edges = room.edges(&EdgeSel { from: Some(&callers), ..EdgeSel::kinds(&[EdgeKind::Calls]) }).map_err(internal)?;
    let calls = call_rows(room, &mut paths, &edges.value.edges, usize::MAX);
    let mut ev = edges.evidence;
    ev.query = format!("symbol / call view of {}", repo.unwrap_or("every consumed repo"));
    note(&mut ev, LOWER_BOUND);
    p1_gap(room, &mut ev);
    settle(&mut ev, symbols.is_empty());
    Ok((symbols, calls, ev))
}

/// [`super::knowledge_view`].
pub fn knowledge_view(reach: &Reach<'_>, repo: &str) -> Result<KnowledgeView, Refusal> {
    if repo.is_empty() {
        return Err(invalid("the knowledge view is per repo: `repo` is empty"));
    }
    let room = open(reach, repo)?;
    let (symbols, calls, _) = view_rows(&room, Some(repo))?;
    Ok(KnowledgeView::new(symbols, calls))
}

fn symbol_json(repo: Option<&str>, s: &SymbolRow) -> Value {
    json!({
        "repo": repo,
        "crate_name": s.crate_name,
        "module_path": s.module_path,
        "item_kind": s.item_kind,
        "item_name": s.item_name,
        "visibility": s.visibility,
        "file": s.file,
        "line": s.line,
        "doc_lines": s.doc_lines,
        "signature": s.signature.clone().unwrap_or_default(),
    })
}

/// A symbol row plus what only the room knows of the node: how far outside
/// its crate it is visible, the first doc line, its moniker.
fn symbol_json_of(room: &Room, o: u64, s: &SymbolRow) -> Value {
    let v = room.node(o);
    let mut row = symbol_json(v.repo, s);
    row["effective_visibility"] = json!(v.effective_visibility.map(|e| e.as_str()));
    row["doc"] = json!(v.doc);
    row["moniker"] = json!(v.moniker);
    row
}

// ------------------------------------------------------------------ call verbs

#[derive(Clone, Copy, PartialEq, Eq)]
enum Dir {
    Callers,
    Callees,
}

/// The linked part of the room (`both`), or the state that says the binary
/// stream is not there. With `binary`, narrowed to the nodes that artifact links.
fn linked_mask(reach: &Reach<'_>, room: &Room, binary: &str) -> Result<(Mask, Evidence), Refusal> {
    let all = room.symbols(None, None).map_err(internal)?;
    if all.value.is_empty() {
        return Err(no_linked_symbol(reach, room, "a verb over the linked call graph cannot answer, and that is not `no callers`"));
    }
    let both = room.mask(&MaskSpec { provenances: vec![Provenance::Both], ..Default::default() }).map_err(internal)?;
    let mut ev = both.evidence;
    if binary.is_empty() {
        return Ok((both.value, ev));
    }
    // `binary` names an artifact: its file name, its bin name, or `name@triple:profile`
    let base = binary.rsplit('/').next().unwrap_or(binary);
    let of: Vec<u64> = all
        .value
        .iter()
        .filter(|s| s.binary == Some(base) || s.artifact == binary || s.artifact.split('@').next() == Some(base))
        .map(|s| s.node)
        .collect();
    if of.is_empty() {
        let known: BTreeSet<&str> = all.value.iter().map(|s| s.artifact).collect();
        return Err(invalid(format!("no linked artifact `{binary}` in the room [{}] (linked: {known:?})", room.scope())));
    }
    note(&mut ev, &format!("restricted to what the artifact `{binary}` links"));
    Ok((both.value.intersect(&room.mask_of(&of)), ev))
}

/// The state of a room that holds no linked symbol: the binary hook looked
/// and found no artifact (`no_artifact`, with where it looked), or the binary
/// stream of its repos was never consumed (`not_consumed`). `what` says what
/// therefore cannot answer — never an empty answer.
fn no_linked_symbol(reach: &Reach<'_>, room: &Room, what: &str) -> Refusal {
    if let Some(why) = no_artifact_of(reach, room) {
        return Refusal::new(State::NoArtifact, format!("the room [{}] holds no linked symbol: {why} — {what}", room.scope()));
    }
    Refusal::new(
        State::NotConsumed,
        format!(
            "the room [{}] holds no linked symbol: the binary stream of its repos was not consumed (no linked artifact of them was read: the binary hook reads `<builds>/mimir_v2/artifacts/<member>/<sha>/`) — {what}",
            room.scope()
        ),
    )
}

/// The binary hook's own word for the repos of `room`, when it is
/// `no_artifact` for the commit a repo is at: `<repo>: <where it looked>`.
fn no_artifact_of(reach: &Reach<'_>, room: &Room) -> Option<String> {
    let log = reach.code_room().ok()?.log().ok()?;
    let said: Vec<String> = room
        .scope()
        .offset_shas
        .iter()
        .filter_map(|(repo, sha)| {
            let row = log.block_on(log.feed_last(repo)).ok()?.remove(crate::code_room::feeds::Binary::NAME)?;
            (row.status == mimir_ingest::schema::STATUS_NO_ARTIFACT && row.sha == *sha).then(|| format!("{repo}: no_artifact — {}", row.detail.unwrap_or_default()))
        })
        .collect();
    (!said.is_empty()).then(|| said.join("; "))
}

/// Direct callers / callees of `name`, optionally inside `within` (the
/// linked nodes): the edges themselves, with their sites.
fn one_hop(room: &Room, dir: Dir, name: &str, within: Option<&Mask>, limit: usize) -> Result<(Vec<mimir_room::EdgeOut>, Evidence), Refusal> {
    let (seeds, lookup_ev) = resolve(room, name, "name")?;
    let seed_mask = room.mask_of(&seeds);
    let seed_in = within.map(|w| seed_mask.intersect(w));
    let seed_ref = seed_in.as_ref().unwrap_or(&seed_mask);
    let sel = match dir {
        Dir::Callers => EdgeSel { to: Some(seed_ref), from: within, cap: Some(limit), ..EdgeSel::kinds(&[EdgeKind::Calls]) },
        Dir::Callees => EdgeSel { from: Some(seed_ref), to: within, cap: Some(limit), ..EdgeSel::kinds(&[EdgeKind::Calls]) },
    };
    let found = room.edges(&sel).map_err(internal)?;
    let mut ev = found.evidence;
    ev.query = format!("{} of `{name}` ({} node(s))", if dir == Dir::Callers { "callers" } else { "callees" }, seeds.len());
    absorb(&mut ev, &lookup_ev);
    if let Some(w) = within
        && seed_mask.intersect(w).is_empty()
    {
        note(&mut ev, &format!("`{name}` itself is not linked (no symbol, no linked instance): it has no place in the linked call graph"));
    }
    note(&mut ev, LOWER_BOUND);
    let empty = found.value.edges.is_empty();
    settle(&mut ev, empty);
    Ok((found.value.edges, ev))
}

fn edge_rows(room: &Room, paths: &mut Paths<'_>, edges: &[mimir_room::EdgeOut]) -> Vec<Value> {
    edges
        .iter()
        .map(|e| {
            let sites = room.sites(e.from, e.to, e.kind);
            json!({
                "caller": paths.of(e.from),
                "callee": paths.of(e.to),
                "caller_moniker": room.moniker(e.from),
                "callee_moniker": room.moniker(e.to),
                "sites": e.count,
                "file": sites.first().map(|s| s.file),
                "lines": sites.iter().map(|s| s.start_line).collect::<Vec<_>>(),
            })
        })
        .collect()
}

/// `{"names": […]}` for a one-hop verb: the OTHER end of every edge.
fn names_answer(room: &Room, dir: Dir, edges: &[mimir_room::EdgeOut], ev: &Evidence, linked: bool) -> Value {
    let mut paths = Paths::new(room);
    let names: BTreeSet<String> = edges.iter().map(|e| paths.of(if dir == Dir::Callers { e.from } else { e.to })).collect();
    envelope(json!({ "names": names, "rows": edge_rows(room, &mut paths, edges), "linked_only": linked }), ev)
}

/// A shortest CALLS path `from` → `to` as `{"names": […]}`.
fn path_answer(room: &Room, from: &str, to: &str, within: Option<&Mask>, extra: &[&str]) -> Result<Value, Refusal> {
    let (a, ev_a) = resolve(room, from, "from")?;
    let (b, ev_b) = resolve(room, to, "to")?;
    let found = room.path(&a, &b, &[EdgeKind::Calls], None, within).map_err(internal)?;
    let mut ev = found.evidence;
    ev.query = format!("call path `{from}` → `{to}`");
    absorb(&mut ev, &ev_a);
    absorb(&mut ev, &ev_b);
    note(&mut ev, LOWER_BOUND);
    for n in extra {
        note(&mut ev, n);
    }
    settle(&mut ev, found.value.is_none());
    let mut paths = Paths::new(room);
    let (names, steps): (Vec<String>, Vec<Value>) = match &found.value {
        Some(p) => (
            p.nodes.iter().map(|o| paths.of(*o)).collect(),
            p.nodes
                .iter()
                .map(|o| {
                    let v = room.node(*o);
                    json!({ "node": paths.of(*o), "moniker": v.moniker, "kind": v.kind, "file": v.file, "line": v.start_line })
                })
                .collect(),
        ),
        None => (Vec::new(), Vec::new()),
    };
    Ok(envelope(json!({ "names": names, "path": steps, "hops": names.len().saturating_sub(1), "linked_only": within.is_some() }), &ev))
}

/// What a path answered through the ring says about itself.
const THROUGH_THE_RING: &str = "walked with the universe ring folded in (the dependencies and the toolchain's std, down to their `extern` declarations): \
     the room of the repo's own code holds no such path";

/// `Knowledge.CallPath`: a shortest CALLS path `from` → `to`.
///
/// Asked of the room of the repo's own code first (what every other reader
/// opens). That room ends at the reference stubs of what the code calls in
/// other crates — so when it holds no answer (`to` names no node of it, or no
/// path ends inside it) the walk is done again with the UNIVERSE RING folded
/// in: the dependency crates and the toolchain's std, where a path from a
/// workspace function to an `extern "C"` item goes. The answer says which
/// room it is from (`ring`, and a note).
fn call_path(reach: &Reach<'_>, p: &Value) -> Result<Value, Refusal> {
    let (repo, from, to) = (text(p, "repo"), text(p, "from"), text(p, "to"));
    let room = open(reach, repo)?;
    let first = path_answer(&room, from, to, None, &[]);
    let found = |v: &Value| v["names"].as_array().is_some_and(|n| !n.is_empty());
    match &first {
        Ok(v) if found(v) => return first,
        // an empty `from` / `to` is the caller's, with or without the ring
        Err(r) if r.state != State::InvalidArgument || from.is_empty() || to.is_empty() => return first,
        _ => {}
    }
    let handle = reach.code_room()?;
    let scope = if repo.is_empty() { Scope::current() } else { Scope::repo(repo, None) };
    let through = handle.room(&scope.with_universe(true)).and_then(|ring| path_answer(&ring, from, to, None, &[THROUGH_THE_RING]));
    match through {
        Ok(mut v) if found(&v) => {
            v["ring"] = json!(true);
            Ok(v)
        }
        // the ring holds no such path either: the answer of the repo's own room
        // stands (its evidence says where its walk stopped)
        _ => first,
    }
}

/// `Introspect.*` and `Dwarf.*`: ONE reader — source CALLS between linked nodes.
fn binary_verb(reach: &Reach<'_>, surface: &str, p: &Value) -> Result<Value, Refusal> {
    let repo = text(p, "repo");
    let room = open(reach, repo)?;
    pinned_sha(&room, repo, text(p, "sha"))?;
    let (linked, linked_ev) = linked_mask(reach, &room, text(p, "binary"))?;
    let finish = |mut v: Value| {
        if let Some(e) = v.get_mut("evidence").and_then(|e| e.get_mut("notes")).and_then(Value::as_array_mut) {
            for n in linked_ev.notes.iter().map(String::as_str).chain([LINKED_ONLY]) {
                if !e.iter().any(|x| x == n) {
                    e.push(json!(n));
                }
            }
        }
        if let Some(m) = v.get_mut("evidence").and_then(|e| e.get_mut("missing")).and_then(Value::as_array_mut) {
            for gap in &linked_ev.missing {
                let g = serde_json::to_value(gap).unwrap_or(Value::Null);
                if !m.contains(&g) {
                    m.push(g);
                }
            }
        }
        v
    };
    let name = if text(p, "name").is_empty() { text(p, "arg") } else { text(p, "name") };
    match surface.rsplit('.').next().unwrap_or("") {
        "Callers" => {
            let (edges, ev) = one_hop(&room, Dir::Callers, name, Some(&linked), limit_of(p, 200))?;
            Ok(finish(names_answer(&room, Dir::Callers, &edges, &ev, true)))
        }
        "Callees" => {
            let (edges, ev) = one_hop(&room, Dir::Callees, name, Some(&linked), limit_of(p, 200))?;
            Ok(finish(names_answer(&room, Dir::Callees, &edges, &ev, true)))
        }
        _ => Ok(finish(path_answer(&room, text(p, "from"), text(p, "to"), Some(&linked), &[])?)),
    }
}

/// A `sha` pin must be the room's own commit: this reader does not time
/// travel by sha (the ledger maps sha → seq; the handle does not expose it).
fn pinned_sha(room: &Room, repo: &str, sha: &str) -> Result<(), Refusal> {
    if sha.is_empty() || repo.is_empty() {
        return Ok(());
    }
    let at = room.scope().offset_shas.get(repo).map(String::as_str).unwrap_or("");
    if at.starts_with(sha) || sha.starts_with(at) && !at.is_empty() {
        return Ok(());
    }
    Err(invalid(format!("`{repo}` is consumed at {at}; this reader answers at the consumer offset only, not at `{sha}`")))
}

fn knowledge_calls(reach: &Reach<'_>, dir: Dir, p: &Value) -> Result<Value, Refusal> {
    let repo = text(p, "repo");
    let room = open(reach, repo)?;
    let limit = limit_of(p, 100);
    let (edges, ev) = one_hop(&room, dir, text(p, "name"), None, limit)?;
    let mut paths = Paths::new(&room);
    let calls = call_rows(&room, &mut paths, &edges, limit);
    Ok(envelope(json!({ "calls": calls, "edges": edges.len() }), &ev))
}

// ------------------------------------------------------------------ knowledge

fn symbol_lookup(reach: &Reach<'_>, p: &Value) -> Result<Value, Refusal> {
    let repo = text(p, "repo");
    let room = open(reach, repo)?;
    let arg = text(p, "arg");
    if arg.is_empty() {
        return Err(invalid("symbol lookup: `arg` (a name, or part of one) is empty"));
    }
    let limit = limit_of(p, 100);
    let scope = room
        .mask(&MaskSpec {
            repos: if repo.is_empty() { vec![] } else { vec![repo.to_string()] },
            origins: vec![Origin::Local],
            node_kinds: SYMBOL_KINDS.iter().map(|k| k.to_string()).collect(),
            defined_only: true,
            ..Default::default()
        })
        .map_err(internal)?;
    // the v1 contract: the item's NAME contains the pattern, any case (an
    // impl block's name is `impl Trait for Type`)
    let low = arg.to_lowercase();
    let traits = impl_traits(&room)?;
    let mut paths = Paths::new(&room);
    let mut rows: Vec<Value> = Vec::new();
    let mut total = 0usize;
    for o in scope.value.ordinals() {
        let v = room.node(*o);
        let is_impl = v.kind == Some(nk::IMPL);
        if !is_impl && !v.name.is_some_and(|n| n.to_lowercase().contains(&low)) {
            continue;
        }
        let row = symbol_row(&room, &mut paths, &traits, *o);
        if is_impl && !row.item_name.to_lowercase().contains(&low) {
            continue;
        }
        total += 1;
        if rows.len() < limit {
            rows.push(symbol_json_of(&room, *o, &row));
        }
    }
    let mut ev = scope.evidence;
    // a symbol kind no node of this room has is no gap of a name lookup
    ev.missing.retain(|m| !m.input.starts_with("node kind"));
    ev.query = format!("symbols whose name contains `{arg}`");
    if total > rows.len() {
        ev.capped = Some(mimir_room::Capped { shown: rows.len() as u64, total: total as u64 });
    }
    p1_gap(&room, &mut ev);
    settle(&mut ev, rows.is_empty());
    Ok(envelope(json!({ "symbols": rows }), &ev))
}

/// A log written before the producer knew visibility and docs: the row
/// fields are empty, and the evidence says they are unknown, not `private`.
fn p1_gap(room: &Room, ev: &mut Evidence) {
    if !room.has_attr("visibility") {
        miss(ev, "visibility", None, "this log's node payload has no `visibility` / `has_doc` column (written before mimir-schema 2): those row fields are empty, not `private` / undocumented");
    }
}

fn defined_in(reach: &Reach<'_>, p: &Value) -> Result<Value, Refusal> {
    let repo = text(p, "repo");
    let room = open(reach, repo)?;
    let suffix = text(p, "arg");
    if suffix.is_empty() {
        return Err(invalid("defined-in: `arg` (a file path, or its tail) is empty"));
    }
    let files: Vec<(String, String)> = room
        .files()
        .iter()
        .filter(|f| f.path.ends_with(suffix) && (repo.is_empty() || f.repo == repo))
        .map(|f| (f.repo.to_string(), f.path.to_string()))
        .collect();
    let mut ev = room.mask(&MaskSpec::default()).map_err(internal)?.evidence;
    ev.query = format!("symbols defined in a file ending with `{suffix}`");
    if room.files().is_empty() {
        miss(&mut ev, "code_span_events", None, "the room holds no span row: no node can be placed in a file");
    } else if files.is_empty() {
        note(&mut ev, &format!("no file of the room ends with `{suffix}` ({} files hold spans)", room.files().len()));
    }
    let traits = impl_traits(&room)?;
    let mut paths = Paths::new(&room);
    let limit = limit_of(p, 100);
    let mut rows = Vec::new();
    for (r, path) in &files {
        for o in room.nodes_in_file(Some(r), path) {
            if SYMBOL_KINDS.contains(&kind_of(&room, o)) && room.node(o).defined {
                rows.push(symbol_json_of(&room, o, &symbol_row(&room, &mut paths, &traits, o)));
            }
        }
    }
    let total = rows.len();
    rows.truncate(limit);
    if total > rows.len() {
        ev.capped = Some(mimir_room::Capped { shown: rows.len() as u64, total: total as u64 });
    }
    p1_gap(&room, &mut ev);
    settle(&mut ev, rows.is_empty());
    Ok(envelope(json!({ "symbols": rows, "files": files.iter().map(|f| &f.1).collect::<Vec<_>>() }), &ev))
}

/// The cargo-feature view of a repo: every item with a `cfg` of its own
/// (`gates`), every item → feature gate with its `negated` / `partial` flags
/// (`gated_by`), the features and whether the build switched them on, and the
/// items the configuration compiled OUT (`gated_out`, token-level stubs).
fn feature_gates(reach: &Reach<'_>, p: &Value) -> Result<Value, Refusal> {
    let repo = text(p, "repo");
    let room = open(reach, repo)?;
    let repos = if repo.is_empty() { vec![] } else { vec![repo.to_string()] };
    let gated = room.mask(&MaskSpec { repos: repos.clone(), origins: vec![Origin::Local], with_cfg: true, defined_only: true, ..Default::default() }).map_err(internal)?;
    let own = room.mask(&MaskSpec { repos: repos.clone(), origins: vec![Origin::Local], ..Default::default() }).map_err(internal)?.value;
    let mut paths = Paths::new(&room);
    let place = |o: u64, paths: &mut Paths<'_>| {
        let v = room.node(o);
        json!({
            "crate_name": v.krate,
            "module_path": v.parent.map(|x| paths.of(x)).unwrap_or_default(),
            "item_name": v.name,
            "item_kind": if v.kind == Some(nk::CFG_GATED_OUT) { v.ty } else { v.kind },
            "cfg_expr": v.cfg,
            "file": v.file,
            "line": v.start_line,
        })
    };
    let (mut gates, mut gated_out) = (Vec::new(), Vec::new());
    for o in gated.value.ordinals() {
        if kind_of(&room, *o) == nk::CFG_GATED_OUT { gated_out.push(place(*o, &mut paths)) } else { gates.push(place(*o, &mut paths)) }
    }
    let by = room.edges(&EdgeSel { from: Some(&own), ..EdgeSel::kinds(&[EdgeKind::GatedBy]) }).map_err(internal)?;
    let gated_by: Vec<Value> = by
        .value
        .edges
        .iter()
        .map(|e| {
            let sites = room.sites(e.from, e.to, e.kind);
            json!({
                "item": paths.of(e.from),
                "feature": room.node(e.to).name,
                "feature_of": room.node(e.to).krate,
                "negated": e.negated,
                "partial": e.partial,
                "compiled_out": kind_of(&room, e.from) == nk::CFG_GATED_OUT,
                "file": sites.first().map(|s| s.file),
                "lines": sites.iter().map(|s| s.start_line).collect::<Vec<_>>(),
            })
        })
        .collect();
    // a feature is ON in this build iff its crate node ENABLES it
    let on: BTreeSet<u64> = room.edges(&EdgeSel { from: Some(&own), ..EdgeSel::kinds(&[EdgeKind::Enables]) }).map_err(internal)?.value.edges.iter().filter(|e| kind_of(&room, e.from) == nk::CRATE).map(|e| e.to).collect();
    let features: Vec<Value> = room
        .mask(&MaskSpec { repos, node_kinds: vec![nk::FEATURE.into()], ..Default::default() })
        .map_err(internal)?
        .value
        .ordinals()
        .iter()
        .map(|o| {
            let v = room.node(*o);
            json!({ "crate_name": v.krate, "feature": v.name, "declared": v.defined, "enabled_in_this_build": on.contains(o) })
        })
        .collect();
    let mut ev = gated.evidence;
    ev.query = "what is behind a cfg / a cargo feature".into();
    ev.missing.retain(|m| !m.input.starts_with("node kind"));
    if by.value.total == 0 && gated_out.is_empty() {
        miss(&mut ev, "GATED_BY edges", None, ACTIVE_CFG_ONLY);
    }
    settle(&mut ev, gates.is_empty() && gated_by.is_empty() && gated_out.is_empty());
    Ok(envelope(json!({ "gates": gates, "gated_by": gated_by, "features": features, "gated_out": gated_out }), &ev))
}

/// The knowledge block of a repo: what the room holds of it. The first
/// surface that needs nothing but the room.
pub fn knowledge_block(reach: &Reach<'_>, repo: &str) -> Result<Value, Refusal> {
    let room = open(reach, repo)?;
    let st = room.stats();
    let symbols = symbol_nodes(&room, (!repo.is_empty()).then_some(repo))?;
    let local = room
        .mask(&MaskSpec { repos: if repo.is_empty() { vec![] } else { vec![repo.to_string()] }, origins: vec![Origin::Local], defined_only: true, ..Default::default() })
        .map_err(internal)?;
    let calls = room.edges(&EdgeSel { from: Some(&local.value), cap: Some(0), ..EdgeSel::kinds(&[EdgeKind::Calls]) }).map_err(internal)?;
    let mut by_kind: BTreeMap<&str, u64> = BTreeMap::new();
    for o in symbols.ordinals() {
        *by_kind.entry(kind_of(&room, *o)).or_default() += 1;
    }
    let mut paths = Paths::new(&room);
    let sample: Vec<String> = symbols.ordinals().iter().filter(|o| [nk::FN, nk::METHOD, nk::STRUCT, nk::TRAIT].contains(&kind_of(&room, **o))).take(12).map(|o| paths.of(*o)).collect();
    let mut ev = local.evidence;
    ev.query = format!("what the room holds of {}", if repo.is_empty() { "every consumed repo" } else { repo });
    absorb(&mut ev, &calls.evidence);
    note(&mut ev, LOWER_BOUND);
    p1_gap(&room, &mut ev);
    for (r, rows) in &st.repo_rows {
        if rows.binary == 0 {
            miss(&mut ev, "binary stream", Some(r), "consumed without the binary layer: provenance `both` / `binary_only` and linked sizes are absent here, not zero");
        }
    }
    settle(&mut ev, symbols.is_empty());
    let offsets: Vec<Value> = room.scope().repos.iter().map(|(r, seq)| json!({ "repo": r, "seq": seq, "sha": room.scope().offset_shas.get(r) })).collect();
    Ok(envelope(
        json!({
            "source": "code_room",
            "consumed": offsets,
            "symbols": symbols.len(),
            "symbols_by_kind": by_kind,
            "call_edges": calls.value.total,
            "call_sites": calls.value.sites,
            "tests": room.mask(&MaskSpec { repos: if repo.is_empty() { vec![] } else { vec![repo.to_string()] }, is_test: Some(true), ..Default::default() }).map_err(internal)?.value.len(),
            "files": room.files().iter().filter(|f| repo.is_empty() || f.repo == repo).count(),
            "sample_symbols": sample,
            "room": {
                "scope": st.scope,
                "nodes": st.nodes,
                "edges": st.edges.iter().map(|(k, n)| (k.as_str().to_string(), *n)).collect::<BTreeMap<_, _>>(),
                "provenance": st.provenance.iter().map(|(k, n)| (k.as_str().to_string(), *n)).collect::<BTreeMap<_, _>>(),
                "linked_symbols": st.symbols,
                "repeated_keys": st.duplicate_keys,
            },
        }),
        &ev,
    ))
}

/// The graph half of a context slice: the seeds' call neighbourhood, each
/// node with its span — what a prompt needs to READ. The semantic half
/// (which spans answer a free-text question) is the embeddings lane.
fn context(reach: &Reach<'_>, p: &Value) -> Result<Value, Refusal> {
    let repo = text(p, "repo");
    let room = open(reach, repo)?;
    pinned_sha(&room, repo, text(p, "sha"))?;
    let seeds: Vec<&str> = p.get("seeds").and_then(Value::as_array).map(|a| a.iter().filter_map(Value::as_str).filter(|s| !s.trim().is_empty()).collect()).unwrap_or_default();
    if seeds.is_empty() {
        return Err(not_wired(format!(
            "knowledge context for the free-text query `{}`: ranking spans by meaning needs the embeddings index over the room's item spans (the vector lane) — \
             not built yet. Pass `seeds` (symbol names) and this reader answers their call neighbourhood with file spans from the room",
            text(p, "query")
        )));
    }
    let hops = p.get("hops").and_then(Value::as_u64).unwrap_or(1).clamp(0, 6) as u32;
    let callers_too = p.get("include_callers").and_then(Value::as_bool).unwrap_or(false);
    let limit = limit_of(p, 8).max(1);
    let mut start: Vec<u64> = Vec::new();
    let mut ev: Option<Evidence> = None;
    for s in &seeds {
        let (nodes, e) = resolve(&room, s, "seed")?;
        start.extend(nodes);
        match &mut ev {
            Some(acc) => absorb(acc, &e),
            None => ev = Some(e),
        }
    }
    let mut ev = ev.unwrap_or_default();
    let down = room.hops_from(&start, &[EdgeKind::Calls], Some(hops), None).map_err(internal)?;
    absorb(&mut ev, &down.evidence);
    let mut radius: BTreeMap<u64, u64> = start.iter().map(|o| (*o, 0)).collect();
    for (o, h) in &down.value {
        radius.entry(*o).or_insert(*h);
    }
    if callers_too {
        // the hop a caller sits at is not layered by this walk: it is "within `hops`"
        let up = room.dependents_by(&start, &[EdgeKind::Calls], Some(hops), None).map_err(internal)?;
        absorb(&mut ev, &up.evidence);
        for o in &up.value.reached {
            radius.entry(*o).or_insert(hops as u64);
        }
    }
    let mut paths = Paths::new(&room);
    let mut slices: Vec<(u64, Value)> = Vec::new();
    let mut unplaced = 0u64;
    for (o, h) in &radius {
        let v = room.node(*o);
        match (v.file, v.start_line, v.end_line) {
            (Some(file), Some(s), Some(e)) => slices.push((*h, json!({
                "repo": room.span(*o).map(|x| x.repo),
                "file": file,
                "start_line": s,
                "end_line": e,
                "radius_hops": h,
                "symbols": [paths.of(*o)],
                "kind": v.kind,
            }))),
            _ => unplaced += 1,
        }
    }
    slices.sort_by(|a, b| a.0.cmp(&b.0).then(a.1["file"].as_str().cmp(&b.1["file"].as_str())).then(a.1["start_line"].as_u64().cmp(&b.1["start_line"].as_u64())));
    let total = slices.len();
    slices.truncate(limit);
    ev.query = format!("call neighbourhood of {seeds:?} within {hops} hop(s)");
    if total > slices.len() {
        ev.capped = Some(mimir_room::Capped { shown: slices.len() as u64, total: total as u64 });
    }
    if unplaced > 0 {
        note(&mut ev, &format!("{unplaced} node(s) of the neighbourhood have no span in the room (dependency / std items, stubs): not sliceable here"));
    }
    miss(&mut ev, "embeddings", None, "no semantic ranking: slices are ordered by call distance from the seeds, not by similarity to the query (the vector lane)");
    note(&mut ev, LOWER_BOUND);
    settle(&mut ev, slices.is_empty());
    Ok(envelope(json!({ "slices": slices.into_iter().map(|s| s.1).collect::<Vec<_>>(), "ranking": "call_distance" }), &ev))
}

// ------------------------------------------------------------------ tests

/// The test inventory: the `is_test` nodes of the graph.
pub fn test_inventory(reach: &Reach<'_>, repo: &str) -> Result<(Vec<Value>, Evidence), Refusal> {
    let room = open(reach, repo)?;
    let tests = room
        .mask(&MaskSpec { repos: if repo.is_empty() { vec![] } else { vec![repo.to_string()] }, is_test: Some(true), ..Default::default() })
        .map_err(internal)?;
    let mut paths = Paths::new(&room);
    // `#[ignore]` as the driver recorded it; a test consumed before the
    // column existed has no value there
    let mut unknown = 0usize;
    let rows: Vec<Value> = tests
        .value
        .ordinals()
        .iter()
        .map(|o| {
            let v = room.node(*o);
            let mut row = json!({
                "repo": v.repo,
                "crate_name": v.krate,
                "module_path": v.parent.map(|x| paths.of(x)).unwrap_or_default(),
                "test_name": v.name,
                "file": v.file,
                "line": v.start_line,
            });
            match room.attr(*o, "ignored").and_then(|a| a.as_bool()) {
                Some(h) => row["is_heavy"] = json!(h),
                None => unknown += 1,
            }
            row
        })
        .collect();
    let mut ev = tests.evidence;
    ev.query = format!("test inventory of {}", if repo.is_empty() { "every consumed repo" } else { repo });
    if unknown > 0 {
        miss(
            &mut ev,
            "is_heavy",
            None,
            &format!("{unknown} test(s) have no `ignored` (`#[ignore]`) value in the log — consumed before mimir-schema 3 added the column and not consumed again since: `is_heavy` is left out of their rows, not false"),
        );
    }
    note(&mut ev, "tests of targets the consumer's `cargo check` did not compile (a test harness behind a feature that is off) are not in the graph");
    settle(&mut ev, rows.is_empty());
    Ok((rows, ev))
}

/// `Test.List`: the inventory joined with the latest result per test when
/// the door reaches a warehouse (ok | fail | X); the inventory alone, and
/// said so, when it does not.
fn test_list(reach: &Reach<'_>, p: &Value) -> Result<Value, Refusal> {
    let repo = text(p, "repo");
    let (mut rows, mut ev) = test_inventory(reach, repo)?;
    let results_state = match reach.warehouse {
        None => {
            miss(&mut ev, "test_results", None, "this front door has no warehouse in reach: the inventory is not joined with run results (every test would read `X`)");
            "not_joined"
        }
        Some(wh) => {
            use crate::warehouse::test_results::{TestSelector, query_test_results};
            let results = wh.block_on(query_test_results(wh, &TestSelector::All)).map_err(internal)?;
            // the latest result per (repo, test name as the runner prints it)
            let mut latest: HashMap<(String, String), (i64, String)> = HashMap::new();
            for r in results {
                let slot = latest.entry((r.repo.clone(), r.test_name.clone())).or_insert((i64::MIN, String::new()));
                if r.ts_micros >= slot.0 {
                    *slot = (r.ts_micros, r.status.clone());
                }
            }
            for row in &mut rows {
                let (r, name) = (row["repo"].as_str().unwrap_or("").to_string(), row["test_name"].as_str().unwrap_or("").to_string());
                let module = row["module_path"].as_str().unwrap_or("");
                // the runner prints `module::path::test` without the crate segment
                let tail = module.split_once("::").map(|(_, m)| format!("{m}::{name}")).unwrap_or_else(|| name.clone());
                let hit = [tail, format!("{module}::{name}"), name].into_iter().find_map(|k| latest.get(&(r.clone(), k)).map(|x| x.1.clone()));
                row["status"] = json!(match hit.as_deref() {
                    Some("pass") => "ok",
                    Some("fail") | Some("stalled") => "fail",
                    _ => "X",
                });
            }
            note(&mut ev, "joined with `test_results` on (repo, module path without the crate + test name): a test never run reads `X`");
            "joined"
        }
    };
    settle(&mut ev, rows.is_empty());
    Ok(envelope(json!({ "inventory": rows, "results_state": results_state }), &ev))
}

// ------------------------------------------------------------------ tables

/// Said with every answer about table access: what the producer cannot see.
pub const TABLES_LOWER_BOUND: &str = "a table reached through a closure parameter or a fn pointer, through a trait none of whose impls the producer saw, \
     or named only inside SQL text, is not a TOUCHES_TABLE edge: table access is a lower bound";

/// One fn → table edge of the room, read.
struct Touch {
    from: u64,
    /// `None` = a table whose name is not known at compile time.
    table: Option<String>,
    access: Option<mimir_schema::Access>,
    /// Sites the edge counts (≥ the positioned ones).
    count: u32,
    /// `(file, line)` of each positioned site.
    sites: Vec<(String, u32)>,
}

/// The TOUCHES_TABLE edges that leave the local code of `repo` (every repo
/// of the room when `None`), with the evidence of the read.
struct Touches {
    edges: Vec<Touch>,
    evidence: Evidence,
}

impl Touches {
    fn unresolved(&self) -> impl Iterator<Item = &Touch> {
        self.edges.iter().filter(|t| t.table.is_none())
    }

    /// The names of the tables touched, sorted.
    fn tables(&self) -> BTreeSet<&str> {
        self.edges.iter().filter_map(|t| t.table.as_deref()).collect()
    }

    /// The board's access rows: one per (fn, table, direction, site).
    fn access_rows(&self, room: &Room, paths: &mut Paths<'_>) -> Vec<AccessEdge> {
        let mut out = Vec::new();
        for t in &self.edges {
            let Some(table) = &t.table else { continue };
            let (caller, krate) = (paths.of(t.from), room.node(t.from).krate.unwrap_or("").to_string());
            let directions: &[Access] = match t.access {
                Some(mimir_schema::Access::Read) => &[Access::Read],
                Some(mimir_schema::Access::Write) => &[Access::Write],
                // `both`, and an edge with no value (a touch all the same)
                _ => &[Access::Read, Access::Write],
            };
            let at: Vec<(String, u32)> = if t.sites.is_empty() { vec![(room.node(t.from).file.unwrap_or("").to_string(), 0)] } else { t.sites.clone() };
            for access in directions {
                for (file, line) in &at {
                    out.push(AccessEdge { caller_fn: caller.clone(), crate_name: krate.clone(), table: table.clone(), access: *access, file: file.clone(), line: *line });
                }
            }
        }
        out
    }

    /// Say what the edges do not cover: the runtime-named sites, by fn.
    fn say_unresolved(&self, paths: &mut Paths<'_>, ev: &mut Evidence) {
        let fns: Vec<String> = self.unresolved().map(|t| paths.of(t.from)).collect();
        if fns.is_empty() {
            return;
        }
        let sites: u32 = self.unresolved().map(|t| t.count).sum();
        note(
            ev,
            &format!(
                "{sites} site(s) in {} fn(s) touch a table whose name is not known at compile time (a runtime string): they stand for no named table ({}{})",
                fns.len(),
                fns.iter().take(5).cloned().collect::<Vec<_>>().join(", "),
                if fns.len() > 5 { ", …" } else { "" }
            ),
        );
    }
}

fn touches(room: &Room, repo: Option<&str>) -> Result<Touches, Refusal> {
    let local = room
        .mask(&MaskSpec { repos: repo.map(|r| vec![r.to_string()]).unwrap_or_default(), origins: vec![Origin::Local], defined_only: true, ..Default::default() })
        .map_err(internal)?
        .value;
    let found = room.edges(&EdgeSel { from: Some(&local), ..EdgeSel::kinds(&[EdgeKind::TouchesTable]) }).map_err(internal)?;
    let mut ev = found.evidence;
    // In a lake created with the `access` column every edge row was written
    // by a producer that writes TOUCHES_TABLE: none is then a FACT about the
    // code. In an older lake (the column was added in place) a repo not
    // consumed since says nothing: none stays a gap.
    if !room.edge_column_added_in_place("access") {
        ev.missing.retain(|m| m.input != "TOUCHES_TABLE edges");
    } else if found.value.edges.is_empty() && ev.missing.iter().all(|m| m.input != "TOUCHES_TABLE edges") {
        miss(
            &mut ev,
            "TOUCHES_TABLE edges",
            repo,
            "none leaves this code, and the lake was created before mimir-schema 4: either it touches no table or it was last consumed by a producer that did not write the edge — its next consumed commit tells",
        );
    }
    let edges = found
        .value
        .edges
        .iter()
        .map(|e| Touch {
            from: e.from,
            table: mimir_schema::moniker::table_name(room.moniker(e.to)).filter(|t| t != mimir_schema::TABLE_UNRESOLVED),
            access: e.access,
            count: e.count,
            sites: room.sites(e.from, e.to, EdgeKind::TouchesTable).iter().map(|s| (s.file.to_string(), s.start_line)).collect(),
        })
        .collect();
    note(&mut ev, TABLES_LOWER_BOUND);
    Ok(Touches { edges, evidence: ev })
}

/// The 🚇 lines' termini: for every gRPC handler of `symbols`, the table(s)
/// NEAREST to it along CALLS + TOUCHES_TABLE, as `(handler fn, table)` —
/// nearest first is all there is: every pair of one handler is at the same
/// (fewest) number of hops, sorted by table name. A handler whose nearest
/// table is named at runtime gets `<unresolved>`.
fn handler_termini(room: &Room, symbols: &[SymbolRow], ev: &mut Evidence) -> Result<Vec<(String, String)>, Refusal> {
    let handlers = arch::grpc_handlers_from_symbols(symbols);
    if handlers.is_empty() {
        return Ok(Vec::new());
    }
    let kinds = [EdgeKind::Calls, EdgeKind::TouchesTable];
    let tables = MaskSpec::kinds(&[nk::TABLE]);
    let near = room.min_hops_to(&tables, &kinds, None).map_err(internal)?;
    if near.value.is_empty() {
        return Ok(Vec::new());
    }
    let hops: HashMap<u64, u64> = near.value.into_iter().collect();
    let is_table = room.mask(&tables).map_err(internal)?.value;
    let mut paths = Paths::new(room);
    let mut out: Vec<(String, String)> = Vec::new();
    let mut reaching = 0usize;
    // the handler fns: a symbol node whose `module::name` the classifier took
    for o in symbol_nodes(room, None)?.ordinals() {
        let v = room.node(*o);
        if !matches!(v.kind, Some(nk::FN) | Some(nk::METHOD)) {
            continue;
        }
        let (Some(parent), Some(name)) = (v.parent, v.name) else { continue };
        let fq = format!("{}::{name}", paths.of(parent));
        if !handlers.contains_key(&fq) {
            continue;
        }
        let Some(k) = hops.get(o) else { continue };
        reaching += 1;
        let walk = room.reach(&[*o][..], &kinds, Some(*k as u32), None).map_err(internal)?;
        let mut names: Vec<String> = walk.value.reached.iter().filter(|t| is_table.contains(**t)).filter_map(|t| room.node(*t).name.map(str::to_string)).collect();
        names.sort();
        out.extend(names.into_iter().map(|t| (fq.clone(), t)));
    }
    note(ev, &format!("metro termini: {reaching} of {} gRPC handler(s) reach a warehouse table along CALLS + TOUCHES_TABLE; each ends at its NEAREST table(s)", handlers.len()));
    out.sort();
    Ok(out)
}

// ------------------------------------------------------------------ boards

/// The architecture board of one repo: the kept classifier over the room's
/// symbol / call rows, with the room's TOUCHES_TABLE edges as the board's
/// Table nodes and reads / writes edges.
fn board(room: &Room, repo: Option<&str>) -> Result<(ArchGraph, Vec<SymbolRow>, Vec<CallEdgeRow>, Evidence), Refusal> {
    let (symbols, calls, mut ev) = view_rows(room, repo)?;
    let touched = touches(room, repo)?;
    let mut paths = Paths::new(room);
    let graph = arch::generate(&symbols, &calls, &touched.access_rows(room, &mut paths));
    ev.query = format!("architecture board of {}", repo.unwrap_or("every consumed repo"));
    absorb(&mut ev, &touched.evidence);
    touched.say_unresolved(&mut paths, &mut ev);
    settle(&mut ev, graph.nodes.is_empty());
    Ok((graph, symbols, calls, ev))
}

/// [`super::arch_graph`].
pub fn arch_graph(reach: &Reach<'_>, repo: &str) -> Result<ArchGraph, Refusal> {
    if repo.is_empty() {
        return Err(invalid("the architecture board is per repo: `repo` is empty"));
    }
    let room = open(reach, repo)?;
    Ok(board(&room, Some(repo))?.0)
}

/// `Viz.Architecture`: the merged board of every consumed repo + the metro
/// fuel (the symbol / call rows), in the tab's own wire type.
pub fn arch_payload(reach: &Reach<'_>) -> Result<(ArchPayload, Evidence), Refusal> {
    let room = open(reach, "")?;
    let (graph, symbols, calls, mut ev) = board(&room, None)?;
    let access = handler_termini(&room, &symbols, &mut ev)?;
    let mut members: BTreeSet<String> = BTreeSet::new();
    for o in symbol_nodes(&room, None)?.ordinals() {
        if let Some(r) = room.node(*o).repo {
            members.insert(r.to_string());
        }
    }
    let mut errors = Vec::new();
    let (symbols, calls) = if calls.len() > METRO_FUEL_CAP {
        errors.push(format!("metro fuel left out: {} call rows exceed the {METRO_FUEL_CAP} one message carries — the board is complete, the 🚇 lines are not drawn", calls.len()));
        miss(&mut ev, "metro fuel", None, format!("{} call rows exceed the wire cap {METRO_FUEL_CAP}", calls.len()));
        (Vec::new(), Vec::new())
    } else {
        (symbols, calls)
    };
    Ok((
        ArchPayload {
            graph,
            members_with_data: members.into_iter().collect(),
            errors,
            // `surface_coverage` is written by the autonom gate, which fails closed (gap P1)
            coverage: BTreeMap::new(),
            symbols,
            calls,
            metro_fuel_source: "resolved/scip".to_string(),
            access,
        },
        ev,
    ))
}

fn arch_verb(reach: &Reach<'_>, surface: &str, p: &Value) -> Result<Value, Refusal> {
    let repo = text(p, "repo");
    if repo.is_empty() {
        return Err(invalid(format!("{surface}: `repo` is empty")));
    }
    let room = open(reach, repo)?;
    let (graph, symbols, _, ev) = board(&room, Some(repo))?;
    match surface {
        "Arch.Graph" => Ok(envelope(json!({ "repo": repo, "graph": graph }), &ev)),
        "Arch.Generate" => Ok(envelope(
            json!({
                "repo": repo,
                "nodes": graph.nodes.len(),
                "edges": graph.edges.len(),
                "graph": graph,
                "svg": graph.to_svg(),
                "historized": false,
                "note": "the board is derived from the code room on every ask; nothing is generated or stored (its history is the room at an earlier seq: `arch history`)",
            }),
            &ev,
        )),
        "Arch.Trace" => {
            let entry = text(p, "entrypoint");
            let sub = arch::trace_from(&graph, entry);
            if sub.nodes.is_empty() {
                return Err(invalid(format!("arch trace: no node of the board of `{repo}` matches the entrypoint `{entry}` ({} nodes on the board)", graph.nodes.len())));
            }
            Ok(envelope(json!({ "repo": repo, "entrypoint": entry, "nodes": sub.nodes.len(), "edges": sub.edges.len(), "graph": sub, "svg": sub.to_svg() }), &ev))
        }
        _ => {
            let symbol = text(p, "symbol");
            let locs = arch::resolve_symbol(&symbols, symbol);
            let mut ev = ev;
            ev.query = format!("source location of `{symbol}`");
            settle(&mut ev, locs.is_empty());
            Ok(envelope(json!({ "repo": repo, "symbol": symbol, "locations": locs }), &ev))
        }
    }
}

/// `Arch.History`: the board at the last consumer ordinals, newest first.
fn arch_history(reach: &Reach<'_>, p: &Value) -> Result<Value, Refusal> {
    let repo = text(p, "repo");
    if repo.is_empty() {
        return Err(invalid("arch history: `repo` is empty"));
    }
    let handle = reach.code_room()?;
    let head = open_in(handle, repo)?;
    let top = head.scope().repos.get(repo).copied().unwrap_or(0);
    let depth = limit_of(p, 5).clamp(1, 20) as i64;
    let mut rows = Vec::new();
    let mut newer: Option<ArchGraph> = None;
    let mut ev = Evidence::default();
    for seq in ((top - depth + 1).max(1)..=top).rev() {
        let room = handle.room(&Scope::repo(repo, Some(seq)))?;
        let (graph, _, _, e) = board(&room, Some(repo))?;
        if seq == top {
            ev = e;
        }
        if let (Some(n), Some(last)) = (&newer, rows.last_mut()) {
            let (old_n, new_n): (BTreeSet<&str>, BTreeSet<&str>) = (graph.nodes.iter().map(|x| x.id.as_str()).collect(), n.nodes.iter().map(|x| x.id.as_str()).collect());
            let last: &mut Value = last;
            last["added_nodes"] = json!(new_n.difference(&old_n).collect::<Vec<_>>());
            last["removed_nodes"] = json!(old_n.difference(&new_n).collect::<Vec<_>>());
            last["edges_delta"] = json!(n.edges.len() as i64 - graph.edges.len() as i64);
        }
        rows.push(json!({ "seq": seq, "sha": room.scope().offset_shas.get(repo), "nodes": graph.nodes.len(), "edges": graph.edges.len() }));
        newer = Some(graph);
    }
    ev.query = format!("architecture board of `{repo}` at its last {} consumer ordinal(s)", rows.len());
    note(&mut ev, "each row is the board of the room at that seq; `added_nodes` / `removed_nodes` / `edges_delta` compare it with the seq before");
    settle(&mut ev, rows.is_empty());
    Ok(envelope(json!({ "repo": repo, "history": rows }), &ev))
}

/// 30-day heat per (repo, crate): the commits of the last 30 days on the
/// crate's HOTTEST file — each repo's newest `git_heat_facts` scan joined on
/// the files the crate's items lie in — plus the repos that have a scan, and
/// why the table was not read when it was not.
fn crate_heat(reach: &Reach<'_>, room: &Room) -> (HashMap<(String, String), i64>, BTreeSet<String>, Vec<String>) {
    use crate::mimir_lens::room_feed::{Basis, Warehouse};
    let mut basis = Basis::default();
    let scan = Warehouse::of(reach).latest(&mut basis, "git_heat_facts");
    let c = scan.cursor(&["repo", "file", "commits_30d"]);
    let mut per_file: HashMap<(&str, &str), i64> = HashMap::new();
    let mut scanned = BTreeSet::new();
    for r in c.rows() {
        scanned.insert(c.get(r, 0).to_string());
        per_file.insert((c.get(r, 0), c.get(r, 1)), c.get(r, 2).parse().unwrap_or(0));
    }
    let mut heat: HashMap<(String, String), i64> = HashMap::new();
    for f in room.files() {
        let Some(commits) = per_file.get(&(f.repo, f.path)) else { continue };
        // a file belongs to the crate of the items in it
        let Some(krate) = room.nodes_in_file(Some(f.repo), f.path).first().and_then(|o| room.node(*o).krate) else { continue };
        let slot = heat.entry((f.repo.to_string(), krate.to_string())).or_default();
        *slot = (*slot).max(*commits);
    }
    (heat, scanned, basis.gaps)
}

/// `Viz.Knowledge`: one bubble per local crate, one row per consumed repo.
pub fn knowledge_summary(reach: &Reach<'_>) -> Result<(KnowledgeSummary, Evidence), Refusal> {
    let room = open(reach, "")?;
    let q = room.quotient(GroupBy::Crate, &[EdgeKind::Calls], None).map_err(internal)?;
    let mut ev = q.evidence;
    let (heat, heat_scanned, heat_unread) = crate_heat(reach, &room);
    let mut crates: Vec<Bubble> = Vec::new();
    let mut per_repo: BTreeMap<String, RepoScanSummary> =
        room.scope().repos.keys().map(|r| (r.clone(), RepoScanSummary { repo: r.clone(), ok: true, symbols: 0, calls: 0, features: 0, git_files: 0, error: None })).collect();
    for f in room.files() {
        if let Some(r) = per_repo.get_mut(f.repo) {
            r.git_files += 1;
        }
    }
    for g in &q.value.groups {
        // a local crate: its crate node is a local definition of a repo in scope
        let Some(node) = g.node else { continue };
        let v = room.node(node);
        let (Some(repo), true, Some(Origin::Local)) = (v.repo, v.defined, v.origin) else { continue };
        let Some(summary) = per_repo.get_mut(repo) else { continue };
        let symbols = g.node_kinds.iter().filter(|(k, _)| SYMBOL_KINDS.contains(&k.as_str())).map(|(_, n)| *n).sum::<u64>() as usize;
        let gates = room
            .mask(&MaskSpec { crates: vec![g.key.clone()], with_cfg: true, defined_only: true, ..Default::default() })
            .map_err(internal)?
            .value
            .len();
        let calls = (g.internal + g.fan_out) as usize;
        summary.symbols += symbols;
        summary.calls += calls;
        summary.features += gates;
        let heat_30d = v.krate.and_then(|k| heat.get(&(repo.to_string(), k.to_string()))).copied().unwrap_or(0);
        crates.push(Bubble { repo: repo.to_string(), krate: g.label.clone(), symbols, calls, files: g.files as usize, gates, heat_30d });
    }
    for (r, s) in per_repo.iter_mut() {
        if s.symbols == 0 {
            s.ok = false;
            s.error = Some(format!("`{r}` is consumed, and the room holds no local crate of it (nothing compiled at its offset?)"));
        }
    }
    // crate → crate between LOCAL definitions, with the names that justify it
    let local = room.mask(&MaskSpec { origins: vec![Origin::Local], defined_only: true, ..Default::default() }).map_err(internal)?.value;
    let cross = room.edges(&EdgeSel::kinds(&[EdgeKind::Calls, EdgeKind::UsesType]).inside(&local)).map_err(internal)?;
    let mut acc: BTreeMap<(String, String), (u64, BTreeSet<String>)> = BTreeMap::new();
    for e in &cross.value.edges {
        let (from, to) = (room.node(e.from), room.node(e.to));
        let (Some(a), Some(b)) = (from.krate, to.krate) else { continue };
        if a == b {
            continue;
        }
        let slot = acc.entry((a.to_string(), b.to_string())).or_default();
        slot.0 += 1;
        if slot.1.len() < 6 && let Some(n) = to.name {
            slot.1.insert(n.to_string());
        }
    }
    let crate_edges: Vec<CrateEdge> = acc.into_iter().map(|((from, to), (edges, via))| CrateEdge { from, to, edges, via: via.into_iter().collect() }).collect();
    ev.query = "knowledge map: one bubble per local crate".into();
    // heat 0 is "cold" only where a scan of the repo was read
    if heat_scanned.is_empty() {
        let why = heat_unread.first().cloned().unwrap_or_else(|| {
            "`git_heat_facts` holds no row: the consumer's `git_heat` feed (one scan per consumed (repo, sha)) has not run for this workspace yet".to_string()
        });
        miss(&mut ev, "git_heat_facts", None, format!("{why} — every bubble's heat is 0, not cold"));
    } else {
        for r in per_repo.keys().filter(|r| !heat_scanned.contains(*r)) {
            miss(&mut ev, "git_heat_facts", Some(r), "no git-heat scan of this repo (its `git_heat` feed has not run yet): its bubbles' heat is 0, not cold");
        }
    }
    note(&mut ev, "symbols = item-level definitions; calls = CALLS edges leaving the crate's nodes (to anything); files = files its spans lie in; gates = items with a cfg; heat_30d = commits of the last 30 days on the crate's hottest file (the repo's newest git-heat scan)");
    settle(&mut ev, crates.is_empty());
    Ok((KnowledgeSummary { repos: per_repo.into_values().collect(), crates, crate_edges }, ev))
}

/// The call graph of a repo for the 3D lens: nodes with provenance, CALLS
/// edges between the repo's own definitions, heaviest first, capped.
pub fn call_graph(reach: &Reach<'_>, repo: &str, cap: usize) -> Result<(Value, Evidence), Refusal> {
    let room = open(reach, repo)?;
    let own = room
        .mask(&MaskSpec { repos: if repo.is_empty() { vec![] } else { vec![repo.to_string()] }, origins: vec![Origin::Local], defined_only: true, node_kinds: vec![nk::FN.into(), nk::METHOD.into()], ..Default::default() })
        .map_err(internal)?
        .value;
    let found = room.edges(&EdgeSel { heaviest_first: true, ..EdgeSel::kinds(&[EdgeKind::Calls]).inside(&own).cap(cap) }).map_err(internal)?;
    let mut paths = Paths::new(&room);
    let mut ids: BTreeMap<u64, usize> = BTreeMap::new();
    let mut nodes: Vec<Value> = Vec::new();
    let mut index = |o: u64, nodes: &mut Vec<Value>, paths: &mut Paths<'_>| -> usize {
        *ids.entry(o).or_insert_with(|| {
            let v = room.node(o);
            nodes.push(json!({
                "id": paths.of(o),
                "moniker": v.moniker,
                "crate": v.krate,
                "kind": v.kind,
                "provenance": v.provenance.map(|p| p.as_str()),
                "file": v.file,
                "line": v.start_line,
            }));
            nodes.len() - 1
        })
    };
    let edges: Vec<Value> = found
        .value
        .edges
        .iter()
        .map(|e| {
            let (f, t) = (index(e.from, &mut nodes, &mut paths), index(e.to, &mut nodes, &mut paths));
            json!({ "from": f, "to": t, "sites": e.count })
        })
        .collect();
    let mut ev = found.evidence;
    ev.query = format!("call graph of {} (fns and methods, heaviest {cap} edges)", if repo.is_empty() { "every consumed repo" } else { repo });
    note(&mut ev, LOWER_BOUND);
    settle(&mut ev, edges.is_empty());
    Ok((obj([("nodes", Value::Array(nodes)), ("edges", Value::Array(edges)), ("total_edges", json!(found.value.total))]), ev))
}

/// The repo × repo quotient of CALLS ∪ USES_TYPE: the release tab's deep edges.
pub fn repo_edges(reach: &Reach<'_>) -> Result<(Vec<(String, String, u64)>, Evidence), Refusal> {
    let room = open(reach, "")?;
    let q = room.quotient(GroupBy::Repo, &[EdgeKind::Calls, EdgeKind::UsesType], None).map_err(internal)?;
    let edges: Vec<(String, String, u64)> = q.value.edges.iter().map(|e| (q.value.groups[e.from].key.clone(), q.value.groups[e.to].key.clone(), e.edges)).collect();
    let mut ev = q.evidence;
    ev.query = "repo × repo edges (CALLS ∪ USES_TYPE)".into();
    note(&mut ev, "an edge A → B: a definition of repo A calls or uses a type that repo B's rows define — counted at item level, in the consumed commits");
    note(&mut ev, LOWER_BOUND);
    settle(&mut ev, edges.is_empty());
    Ok((edges, ev))
}

/// [`super::api_surface`].
pub fn api_surface(reach: &Reach<'_>, repo: &str) -> Result<super::ApiSurface, Refusal> {
    if repo.is_empty() {
        return Err(invalid("the API surface is per repo: `repo` is empty"));
    }
    let room = open(reach, repo)?;
    if !room.has_attr("effective_visibility") {
        return Err(not_wired(format!(
            "the log of `{repo}` carries no visibility (written before mimir-schema 2): which fn is public API cannot be told — re-consume it with the current driver"
        )));
    }
    let fns = vec![nk::FN.to_string(), nk::METHOD.to_string()];
    let own = MaskSpec { repos: vec![repo.to_string()], origins: vec![Origin::Local], defined_only: true, ..Default::default() };
    let tests = room.mask(&MaskSpec { is_test: Some(true), ..own.clone() }).map_err(internal)?.value;
    let api = room.mask(&MaskSpec { node_kinds: fns, public_api: Some(true), is_test: Some(false), ..own.clone() }).map_err(internal)?;
    let mut paths = Paths::new(&room);
    let mut out = super::ApiSurface::default();
    for (set, is_test) in [(&tests, true), (&api.value, false)] {
        for o in set.ordinals() {
            if let Some(name) = room.node(*o).name {
                out.fns.push(super::ApiFn { name: name.to_string(), path: paths.of(*o), is_test });
            }
        }
    }
    let local = room.mask(&own).map_err(internal)?.value;
    let edges = room.edges(&EdgeSel { from: Some(&local), ..EdgeSel::kinds(&[EdgeKind::Calls, EdgeKind::Tests]) }).map_err(internal)?;
    let mut calls: BTreeSet<(String, String)> = BTreeSet::new();
    for e in &edges.value.edges {
        if let (Some(a), Some(b)) = (room.node(e.from).name, room.node(e.to).name) {
            calls.insert((a.to_string(), b.to_string()));
        }
    }
    out.calls = calls.into_iter().collect();
    out.notes = edges.evidence.missing.iter().map(|m| format!("missing {}: {}", m.input, m.why)).chain([LOWER_BOUND.to_string()]).collect();
    Ok(out)
}

// ------------------------------------------------------------------ templates

const TEMPLATES: [(&str, &str, &str); 4] = [
    ("def-use", "a symbol's definition and every site that uses it (one hop, every edge kind)", "repo, symbol"),
    ("change-radius", "the symbols defined in a file and everything that must be rechecked when they change", "repo, file"),
    ("call-chain", "the direct callers and callees of a symbol", "repo, symbol"),
    ("table-access-lineage", "which functions read / write a warehouse table", "table"),
];

fn sql_template(reach: &Reach<'_>, p: &Value) -> Result<Value, Refusal> {
    let template = text(p, "template");
    let params = p.get("params").cloned().unwrap_or(Value::Null);
    if template.is_empty() {
        return Ok(json!({
            "templates": TEMPLATES.iter().map(|(n, d, ps)| json!({
                "name": n, "description": d, "params": ps,
                "state": "code_room",
            })).collect::<Vec<_>>(),
            "note": "the templates are answered by the code room, not by SQL over the removed tables",
        }));
    }
    if p.get("render_only").and_then(Value::as_bool).unwrap_or(false) {
        return Ok(json!({ "template": template, "sql": null, "note": "there is no SQL to render: this template is a question to the code room (the tables it used to SELECT from are gone)" }));
    }
    let repo = text(&params, "repo");
    match template {
        "table-access-lineage" => table_lineage(reach, repo, text(&params, "table")),
        "call-chain" => {
            let room = open(reach, repo)?;
            let symbol = text(&params, "symbol");
            let (callers, mut ev) = one_hop(&room, Dir::Callers, symbol, None, 500)?;
            let (callees, ev2) = one_hop(&room, Dir::Callees, symbol, None, 500)?;
            absorb(&mut ev, &ev2);
            let mut paths = Paths::new(&room);
            let (a, b) = (edge_rows(&room, &mut paths, &callers), edge_rows(&room, &mut paths, &callees));
            ev.query = format!("call-chain of `{symbol}`");
            settle(&mut ev, a.is_empty() && b.is_empty());
            Ok(envelope(json!({ "template": template, "callers": a, "callees": b }), &ev))
        }
        "def-use" => {
            let room = open(reach, repo)?;
            let symbol = text(&params, "symbol");
            let (defs, lookup_ev) = resolve(&room, symbol, "symbol")?;
            let seed = room.mask_of(&defs);
            let kinds: Vec<EdgeKind> = EdgeKind::ALL.iter().copied().filter(|k| *k != EdgeKind::Contains && *k != EdgeKind::DependsOn).collect();
            let uses = room.edges(&EdgeSel { to: Some(&seed), cap: Some(1000), ..EdgeSel::kinds(&kinds) }).map_err(internal)?;
            let traits = impl_traits(&room)?;
            let mut paths = Paths::new(&room);
            let definitions: Vec<Value> = defs.iter().map(|o| symbol_json(room.node(*o).repo, &symbol_row(&room, &mut paths, &traits, *o))).collect();
            let rows: Vec<Value> = uses
                .value
                .edges
                .iter()
                .map(|e| {
                    let sites = room.sites(e.from, e.to, e.kind);
                    json!({ "kind": e.kind.as_str(), "user": paths.of(e.from), "used": paths.of(e.to), "sites": e.count, "file": sites.first().map(|s| s.file), "lines": sites.iter().map(|s| s.start_line).collect::<Vec<_>>() })
                })
                .collect();
            let mut ev = uses.evidence;
            // kinds no producer writes are not what this template is missing
            ev.missing.retain(|m| !m.input.ends_with(" edges") || rows.is_empty());
            absorb(&mut ev, &lookup_ev);
            ev.query = format!("def-use of `{symbol}`");
            note(&mut ev, LOWER_BOUND);
            settle(&mut ev, rows.is_empty());
            Ok(envelope(json!({ "template": template, "definitions": definitions, "uses": rows }), &ev))
        }
        "change-radius" => {
            let room = open(reach, repo)?;
            let file = text(&params, "file");
            let changed: Vec<u64> = room.files().iter().filter(|f| f.path.ends_with(file) && !file.is_empty()).flat_map(|f| room.nodes_in_file(Some(f.repo), f.path)).collect();
            if changed.is_empty() {
                return Err(invalid(format!("change-radius: no span of the room [{}] lies in a file ending with `{file}`", room.scope())));
            }
            let walk = room.dependents(&changed, None).map_err(internal)?;
            let mut paths = Paths::new(&room);
            let place = |o: u64, paths: &mut Paths<'_>| {
                let v = room.node(o);
                json!({ "symbol": paths.of(o), "kind": v.kind, "file": v.file, "line": v.start_line })
            };
            let symbols: Vec<Value> = changed.iter().filter(|o| SYMBOL_KINDS.contains(&kind_of(&room, **o))).map(|o| place(*o, &mut paths)).collect();
            let affected: Vec<Value> = walk.value.reached.iter().filter(|o| !changed.contains(o) && SYMBOL_KINDS.contains(&kind_of(&room, **o))).take(2000).map(|o| place(*o, &mut paths)).collect();
            let mut ev = walk.evidence;
            ev.query = format!("change-radius of `{file}`");
            note(&mut ev, "everything that (transitively) calls, uses, implements or is an impl for a symbol of the file — the Jikes walk, not one hop");
            note(&mut ev, LOWER_BOUND);
            settle(&mut ev, affected.is_empty());
            Ok(envelope(json!({ "template": template, "symbols": symbols, "affected": affected }), &ev))
        }
        other => Err(invalid(format!("unknown template `{other}` (def-use | change-radius | call-chain | table-access-lineage)"))),
    }
}

/// `table-access-lineage`: the fns that read / write `table` directly (one
/// row per site), and every fn that reaches one of them along CALLS. `repo`
/// narrows the code looked at; the table is the same node for every repo.
fn table_lineage(reach: &Reach<'_>, repo: &str, table: &str) -> Result<Value, Refusal> {
    let room = open(reach, repo)?;
    let touched = touches(&room, (!repo.is_empty()).then_some(repo))?;
    let known = touched.tables();
    let runtime = table == mimir_schema::TABLE_UNRESOLVED;
    if table.is_empty() || !(runtime || known.contains(table)) {
        // a name the code does not touch is not "no reader": say which it does
        let unresolved: u32 = touched.unresolved().map(|t| t.count).sum();
        return Err(invalid(format!(
            "table-access-lineage: {} — the code of [{}] touches {} table(s) by name: {}{}",
            if table.is_empty() { "`table` is empty".to_string() } else { format!("no fn touches a table named `{table}`") },
            room.scope(),
            known.len(),
            if known.is_empty() { "(none)".to_string() } else { known.iter().copied().collect::<Vec<_>>().join(", ") },
            if unresolved > 0 { format!("; {unresolved} more site(s) name their table at runtime (ask for `{}`)", mimir_schema::TABLE_UNRESOLVED) } else { String::new() },
        )));
    }
    let mut paths = Paths::new(&room);
    let mine: Vec<&Touch> = touched.edges.iter().filter(|t| if runtime { t.table.is_none() } else { t.table.as_deref() == Some(table) }).collect();
    let mut rows: Vec<Value> = Vec::new();
    for t in &mine {
        let v = room.node(t.from);
        let row = |file: &str, line: u32| {
            json!({
                "caller_fn": room.display_path(t.from),
                "crate_name": v.krate,
                "repo": v.repo,
                "table": table,
                "access": t.access.map(|a| a.as_str()),
                "file": file,
                "line": line,
                "moniker": v.moniker,
            })
        };
        if t.sites.is_empty() {
            rows.push(row(v.file.unwrap_or(""), 0));
        }
        rows.extend(t.sites.iter().map(|(f, l)| row(f, *l)));
    }
    let direct: Vec<u64> = mine.iter().map(|t| t.from).collect::<BTreeSet<_>>().into_iter().collect();
    let walk = room.dependents_by(&direct, &[EdgeKind::Calls], None, None).map_err(internal)?;
    const SHOW: usize = 500;
    let reachers: Vec<String> = walk.value.reached.iter().take(SHOW).map(|o| paths.of(*o)).collect();
    let mut ev = touched.evidence.clone();
    absorb(&mut ev, &walk.evidence);
    ev.query = format!("table-access-lineage of `{table}`");
    if !runtime {
        touched.say_unresolved(&mut paths, &mut ev);
    }
    note(&mut ev, LOWER_BOUND);
    settle(&mut ev, rows.is_empty());
    Ok(envelope(
        json!({
            "template": "table-access-lineage",
            "table": table,
            "rows": rows,
            "readers": mine.iter().filter(|t| t.access.is_none_or(|a| a.reads())).count(),
            "writers": mine.iter().filter(|t| t.access.is_none_or(|a| a.writes())).count(),
            "sites": mine.iter().map(|t| t.count).sum::<u32>(),
            "reached_from": reachers,
            "reached_from_total": walk.value.reached.len(),
        }),
        &ev,
    ))
}

// ------------------------------------------------------------------ the rest

/// `repo@seq` → (repo, seq).
fn repo_at(spec: &str) -> Option<(&str, i64)> {
    let (repo, seq) = spec.rsplit_once('@')?;
    Some((repo, seq.parse().ok()?)).filter(|(r, _)| !r.is_empty())
}

fn snapshot_diff(reach: &Reach<'_>, p: &Value) -> Result<Value, Refusal> {
    let (mut a, mut b) = (text(p, "snap_a").to_string(), text(p, "snap_b").to_string());
    let mut first_commit: Option<String> = None;
    if a.is_empty() && b.is_empty() {
        // no states named: the last two consumer ordinals of `repo`
        (a, b, first_commit) = panes::last_two(reach.code_room()?, text(p, "repo"))?;
    }
    let (Some((ra, sa)), Some((rb, sb))) = (repo_at(&a), repo_at(&b)) else {
        return Err(invalid(format!(
            "snapshot-diff takes two states of the code room as `repo@seq` (consumer ordinals), got `{a}` and `{b}`: \
             the world-ring snapshot ids of the removed coderoom ingest have no counterpart"
        )));
    };
    let handle = reach.code_room()?;
    let older = handle.room(&Scope::repo(ra, Some(sa)))?;
    let newer = handle.room(&Scope::repo(rb, Some(sb)))?;
    let d = older.diff(&newer).map_err(internal)?;
    const SHOW: usize = 200;
    let cut = |v: &[String]| v.iter().take(SHOW).cloned().collect::<Vec<_>>();
    let edges: BTreeMap<String, Value> = d
        .value
        .edges
        .iter()
        .map(|(k, e)| (k.as_str().to_string(), json!({ "added": e.added.len(), "removed": e.removed.len(), "added_sample": e.added.iter().take(20).collect::<Vec<_>>(), "removed_sample": e.removed.iter().take(20).collect::<Vec<_>>() })))
        .collect();
    let mut ev = d.evidence.clone();
    if let Some(why) = &first_commit {
        note(&mut ev, why);
    }
    Ok(envelope(
        json!({
            "from": a, "to": b,
            "added_nodes": d.value.added_nodes.len(), "removed_nodes": d.value.removed_nodes.len(), "changed_nodes": d.value.changed.len(),
            "added": cut(&d.value.added_nodes), "removed": cut(&d.value.removed_nodes), "changed": cut(&d.value.changed),
            "edges": edges,
            "provenance_changed": d.value.provenance_changed.len(),
            "lists_capped_at": SHOW,
            "by_crate": panes::diff_by_crate(&older, &newer, &d.value.added_nodes, &d.value.removed_nodes, &d.value.changed),
            "room_from": panes::identity(handle, &older),
            "room_to": panes::identity(handle, &newer),
        }),
        &ev,
    ))
}

/// Anchors for a task text: the symbols whose names its words name.
fn funnel_retrieve(reach: &Reach<'_>, p: &Value) -> Result<Value, Refusal> {
    let repo = text(p, "repo");
    let room = open(reach, repo)?;
    let query = text(p, "query");
    let limit = limit_of(p, 12);
    let words: BTreeSet<String> = query.split(|c: char| !(c.is_alphanumeric() || c == '_')).filter(|w| w.len() >= 3).map(str::to_lowercase).collect();
    if words.is_empty() {
        return Err(invalid("funnel retrieve: `query` holds no word of three letters or more"));
    }
    let scope = symbol_nodes(&room, (!repo.is_empty()).then_some(repo))?;
    let mut score: BTreeMap<u64, u32> = BTreeMap::new();
    let mut ev = Evidence::default();
    for w in &words {
        let found = room.lookup(&NameQuery { within: Some(scope.clone()), ..NameQuery::new(w.as_str()).mode(NameMatch::Substring) }).map_err(internal)?;
        for o in &found.value {
            *score.entry(*o).or_default() += 1;
        }
        if ev.scope.is_empty() {
            ev = found.evidence;
        }
    }
    let mut ranked: Vec<(u64, u32)> = score.into_iter().collect();
    ranked.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
    let total = ranked.len();
    ranked.truncate(limit);
    let mut paths = Paths::new(&room);
    let anchors: Vec<Value> = ranked
        .iter()
        .map(|(o, hits)| {
            let v = room.node(*o);
            json!({ "symbol": paths.of(*o), "kind": v.kind, "crate": v.krate, "file": v.file, "line": v.start_line, "words_matched": hits })
        })
        .collect();
    ev.query = format!("anchors for {words:?}");
    ev.notes.retain(|n| !n.starts_with("matched as"));
    ev.capped = (total > anchors.len()).then_some(mimir_room::Capped { shown: anchors.len() as u64, total: total as u64 });
    note(&mut ev, "ranked by how many words of the task a symbol's name contains — a name match, not a semantic one");
    settle(&mut ev, anchors.is_empty());
    Ok(envelope(json!({ "anchors": anchors }), &ev))
}

/// Every surface, by name.
pub fn answer(surface: &str, reach: &Reach<'_>, p: &Value) -> Result<Value, Refusal> {
    // a door with no room is told so before its request is looked at: what it
    // lacks is the room, not an argument (the menu of templates needs neither)
    let needs_no_room = matches!(surface, "Coderoom.Ingest" | "Coderoom.Compat" | "Coderoom.WhatBreaks") || (surface == "Mimir.SqlTemplate" && text(p, "template").is_empty());
    if !needs_no_room {
        reach.code_room()?;
    }
    match surface {
        "Introspect.Callers" | "Introspect.Callees" | "Introspect.PathBetween" | "Dwarf.Callers" | "Dwarf.Callees" | "Dwarf.CallPath" => binary_verb(reach, surface, p),
        "Knowledge.SymbolLookup" => symbol_lookup(reach, p),
        "Knowledge.DefinedIn" => defined_in(reach, p),
        "Knowledge.Callers" => knowledge_calls(reach, Dir::Callers, p),
        "Knowledge.Callees" => knowledge_calls(reach, Dir::Callees, p),
        "Knowledge.CallPath" => call_path(reach, p),
        "Knowledge.FeatureGates" => feature_gates(reach, p),
        "Knowledge.Context" => context(reach, p),
        "Knowledge.Map" => knowledge_block(reach, text(p, "repo")),
        "Viz.Knowledge" => {
            let (summary, ev) = knowledge_summary(reach)?;
            Ok(envelope(serde_json::to_value(&summary).map_err(internal)?, &ev))
        }
        "Viz.Architecture" => {
            let (payload, ev) = arch_payload(reach)?;
            Ok(envelope(serde_json::to_value(&payload).map_err(internal)?, &ev))
        }
        "Viz.CrateGraph" => panes::crate_graph(reach, p),
        "Viz.CallGraph" => panes::call_graph_answer(reach, p),
        "Viz.FunctionTier" => panes::function_tier(reach, p),
        "Viz.CodeRoom3D" => panes::code_room_3d(reach, p),
        "Viz.CodeMap" => codemap::code_map(reach, p),
        "Viz.Binary" => panes::binary(reach, p),
        "Knowledge.Symbols" => panes::symbols(reach, p),
        "Knowledge.Source" => panes::source(reach, p),
        "Knowledge.TestReach" => panes::test_reach(reach, p),
        "Viz.TestMatrix.inventory" => {
            let (rows, ev) = test_inventory(reach, text(p, "repo"))?;
            Ok(envelope(json!({ "inventory": rows }), &ev))
        }
        "Test.List" => test_list(reach, p),
        "Test.Affected" => affected::affected_tests(reach, p),
        "Arch.Generate" | "Arch.Trace" | "Arch.Open" | "Arch.Graph" => arch_verb(reach, surface, p),
        "Arch.History" => arch_history(reach, p),
        "Mimir.SqlTemplate" => sql_template(reach, p),
        // the system boundary view (the `system_boundary` lens): the classes
        // of system call a repo's code reaches, and from where
        "Mimir.SystemBoundary" => {
            let mut v = crate::mimir_lens::answer("system_boundary", reach, p);
            // a lens that could not compute is the door's refusal (no room,
            // nothing consumed, a wrong class), like every other surface
            if let Some(state) = v["lens_meta"]["state"].as_str().and_then(crate::code_room::State::parse) {
                let note = v["evidence_note"].as_str().unwrap_or("").trim_start_matches("system_boundary: ").to_string();
                return Err(Refusal::new(state, note));
            }
            v["state"] = v["lens_meta"]["state"].clone();
            Ok(v)
        }
        "Coderoom.SnapshotDiff" => snapshot_diff(reach, p),
        // the build board: what the consumer's builds are doing, unit by unit
        // (no room is opened)
        "Coderoom.Units" => Ok(crate::code_room::units::answer(&reach.code_room()?.layout().clones(), p)),
        // builder 2: the built artifacts (no room is opened)
        "Coderoom.Artifacts" => crate::code_room::products::answer(reach.code_room()?, p),
        "Funnel.Retrieve" => funnel_retrieve(reach, p),
        "Coderoom.Ingest" | "Coderoom.Compat" => Err(not_wired(format!(
            "{surface}: the world ring (crates.io sources of other crates, COMPATIBLE edges, semver diffs) has no v2 producer — the tarball ingest is gone and \
             the driver dumps what a consumed repo compiles, not the registry (gap P5)"
        ))),
        "Coderoom.WhatBreaks" => Err(not_wired(
            "Coderoom.WhatBreaks: `breaks` = a PUBLIC item of a crate VERSION that went away or changed in another. The room can diff two states, tell the public API \
             (effective visibility) and walk the dependents — but only of commits a consumer walked: the registry versions of other crates (the world ring) are not \
             ingested (gap P5), so there are no two versions to diff. For a consumed repo use `coderoom snapshot-diff repo@seq repo@seq`",
        )),
        "Vector.SlicesIndex" | "Vector.SlicesSearch" => {
            let room = open(reach, text(p, "repo"))?;
            Err(not_wired(format!(
                "{surface}: the room holds {} item spans to root slices on, but the slice index (embeddings over those spans) is the vector lane and is not built yet",
                room.stats().spans
            )))
        }
        other => Err(invalid(format!("`{other}` has no reader"))),
    }
}

/// The readers behind a thin client's panes.
pub mod panes;

/// `Test.Affected`: the tests a commit can break.
pub mod affected;

/// `Viz.CodeMap`: the room as a map, laid out once per room version.
pub mod codemap;

/// A traced run on the map: traced functions → room functions, and the
/// replay's `Viz.CodeMap` lods (`trace_runs`, `trace`, `profile`).
pub mod trace;

#[cfg(test)]
mod trace_tests;

#[cfg(test)]
mod tests;