use std::collections::HashMap;
use serde_json::{Value, json};
use crate::effects::{AsgEffectStore, EffectStore};
use crate::engine::Engine;
use crate::index::{AsgIndexStore, IndexStore};
use crate::ledger::{AsgLedgerStore, LedgerStore};
use crate::schema::{LedgerKind, Symbol, VerificationStatus};
use crate::search_fts::SearchFtsDb;
use crate::{OwnershipSignal, discover_symbol_ownership, find_covering_tests};
pub fn assemble_symbol_context(
engine: &Engine,
index_store: &AsgIndexStore<'_>,
effect_store: &AsgEffectStore<'_>,
ledger_store: &AsgLedgerStore<'_>,
symbol: &Symbol,
id_map: &HashMap<String, Symbol>,
include_body: bool,
fts: Option<&SearchFtsDb>,
ownership_hint: Option<&OwnershipSignal>,
) -> crate::error::Result<Value> {
let callee_ids = index_store.get_callees(&engine.ref_name, &symbol.symbol_id)?;
let caller_ids = index_store.get_callers(&engine.ref_name, &symbol.symbol_id)?;
let resolve = |ids: &[String]| -> Vec<Value> {
ids.iter()
.map(|id| {
if let Some(s) = id_map.get(id) {
json!({ "qname": s.qname, "file": s.file, "line": s.start.line })
} else {
json!({ "symbol_id": id })
}
})
.collect()
};
let edge_ev: HashMap<String, (f64, bool)> = {
let tree = engine
.repo
.get_tree(
&engine.ref_name,
&crate::paths::edge_evidence_from_prefix(&symbol.symbol_id),
)
.unwrap_or(Value::Null);
let mut m = HashMap::new();
if let Some(obj) = tree.as_object() {
for (callee_id, v) in obj {
if let Ok(ev) =
serde_json::from_value::<crate::edge_confidence::EdgeEvidence>(v.clone())
{
m.insert(callee_id.clone(), (ev.confidence(), ev.static_known));
}
}
}
m
};
let round2 = |x: f64| (x * 100.0).round() / 100.0;
let callees_out: Vec<Value> = callee_ids
.iter()
.map(|id| {
let mut row = if let Some(s) = id_map.get(id) {
json!({ "qname": s.qname, "file": s.file, "line": s.start.line })
} else {
json!({ "symbol_id": id })
};
if let Some((conf, _)) = edge_ev.get(id) {
row["edge_confidence"] = json!(round2(*conf));
}
row
})
.collect();
let runtime_missed_callees: Vec<Value> = edge_ev
.iter()
.filter(|(_, (_, static_known))| !static_known)
.map(|(id, (conf, _))| {
let qname = id_map
.get(id)
.map(|s| s.qname.clone())
.unwrap_or_else(|| id.clone());
json!({ "qname": qname, "edge_confidence": round2(*conf) })
})
.collect();
let effects = effect_store.get_effects(&engine.ref_name, &symbol.symbol_id)?;
let ledger = ledger_store.list_entries(&engine.ref_name, &symbol.symbol_id)?;
let mut invariants: Vec<Value> = Vec::new();
let mut hazards: Vec<Value> = Vec::new();
let mut ownership: Vec<Value> = Vec::new();
let mut proofs: Vec<Value> = Vec::new();
let mut validation_scenarios: Vec<Value> = Vec::new();
let mut known_bugs: Vec<Value> = Vec::new();
let mut concepts: Vec<Value> = Vec::new();
let mut other_ledger: Vec<Value> = Vec::new();
for entry in &ledger {
let v = serde_json::to_value(entry)?;
match entry.kind {
LedgerKind::Invariant => invariants.push(v),
LedgerKind::Hazard => hazards.push(v),
LedgerKind::Ownership => ownership.push(v),
LedgerKind::Proof => proofs.push(v),
LedgerKind::ValidationScenario => validation_scenarios.push(v),
LedgerKind::KnownBug => known_bugs.push(v),
LedgerKind::Concept => concepts.push(v),
_ => other_ledger.push(v),
}
}
let mut sym_val = serde_json::to_value(symbol)?;
if !include_body {
if let Some(obj) = sym_val.as_object_mut() {
obj.remove("body");
}
}
let ownership_signal_owned;
let ownership_signal = if let Some(hint) = ownership_hint {
hint
} else {
ownership_signal_owned = discover_symbol_ownership(
&symbol.file,
symbol.start.line,
symbol.end.line,
symbol.doc.as_deref(),
);
&ownership_signal_owned
};
let mut discovered_ownership: serde_json::Map<String, Value> = serde_json::Map::new();
if let Some(ref author) = ownership_signal.primary_author {
discovered_ownership.insert("primary_author".into(), json!(author));
}
if let Some(ref doc_owner) = ownership_signal.doc_owner {
discovered_ownership.insert("doc_owner".into(), json!(doc_owner));
}
if !ownership_signal.recent_committers.is_empty() {
discovered_ownership.insert(
"recent_committers".into(),
json!(ownership_signal.recent_committers),
);
}
if !ownership_signal.annotated.is_empty() {
let annotated_val: Vec<Value> = ownership_signal
.annotated
.iter()
.map(|a| {
json!({
"name": a.name,
"source": serde_json::to_value(a.source).unwrap_or(json!("unknown")),
})
})
.collect();
discovered_ownership.insert("annotated".into(), json!(annotated_val));
}
let covering_tests: Vec<Value> = find_covering_tests(fts, &symbol.qname)
.into_iter()
.map(|ct| {
json!({
"qname": ct.qname,
"file": ct.file,
"line": ct.line,
"run_command": ct.run_command,
})
})
.collect();
let effects_detail: Vec<Value> = if let Some(ref decl) = effects {
let mismatch_effects: std::collections::HashSet<String> = decl
.verification
.as_ref()
.map(|v| {
v.mismatches
.iter()
.map(|m| m.effect.as_str().to_string())
.collect()
})
.unwrap_or_default();
let overall_ok = decl
.verification
.as_ref()
.map(|v| matches!(v.status, VerificationStatus::Ok))
.unwrap_or(false);
decl.declared
.iter()
.map(|e| {
let effect_str = e.effect.as_str();
let is_mismatched = mismatch_effects.contains(effect_str);
let status = if decl.verification.is_none() {
"unverified"
} else if is_mismatched {
"mismatch"
} else if overall_ok {
"ok"
} else {
"ok"
};
let mut obj = serde_json::Map::new();
obj.insert("effect".into(), json!(effect_str));
obj.insert("status".into(), json!(status));
if let Some(ref adapter) = e.adapter {
obj.insert("adapter".into(), json!(adapter));
}
if let Some(ref pattern) = e.source_pattern {
obj.insert("source_pattern".into(), json!(pattern));
}
if let Some(note) = &e.note {
obj.insert("note".into(), json!(note));
}
Value::Object(obj)
})
.collect()
} else {
Vec::new()
};
let cross_service = {
let tree = engine
.repo
.get_tree(&engine.ref_name, "/asd/v1/index/endpoints")
.unwrap_or(Value::Null);
let all = crate::cross_service::endpoints_from_tree(&tree);
let mine: Vec<&crate::cross_service::ServiceEndpoint> = all
.iter()
.filter(|e| e.symbol_id == symbol.symbol_id)
.collect();
if mine.is_empty() {
None
} else {
let edges = crate::cross_service::match_edges(&all);
let touching: Vec<Value> = edges
.iter()
.filter(|e| {
e.from.symbol_id == symbol.symbol_id || e.to.symbol_id == symbol.symbol_id
})
.map(|e| {
let is_handler = e.to.symbol_id == symbol.symbol_id;
let peer = if is_handler { &e.from } else { &e.to };
json!({
"role": if is_handler { "handler" } else { "consumer" },
"contract": e.contract,
"peer_qname": peer.qname,
"peer_repo": peer.repo_id,
"cross_repo": e.cross_repo,
"confidence": e.confidence,
})
})
.collect();
Some(json!({
"endpoints": mine.iter().map(|e| json!({
"transport": e.transport,
"direction": e.direction,
"contract": e.contract,
"confidence": e.confidence,
})).collect::<Vec<_>>(),
"edges": touching,
}))
}
};
let mut out = json!({
"symbol": sym_val,
"invariants": invariants,
"hazards": hazards,
"known_bugs": known_bugs,
"concepts": concepts,
"ownership": ownership,
"ownership_discovery": discovered_ownership,
"covering_tests": covering_tests,
"validation_scenarios": validation_scenarios,
"callers": resolve(&caller_ids),
"callees": callees_out,
"effects": effects,
"effects_detail": effects_detail,
"proofs": proofs,
"decisions_and_notes": other_ledger,
});
if let Some(cs) = cross_service {
out["cross_service"] = cs;
}
let dataflow = {
let tree = engine
.repo
.get_tree(&engine.ref_name, "/asd/v1/index/dataflow")
.unwrap_or(Value::Null);
let all = crate::dataflow::edges_from_tree(&tree);
let incoming: Vec<Value> = all
.iter()
.filter(|e| e.to_symbol_id == symbol.symbol_id)
.map(|e| json!({ "param": e.param, "from_arg": e.arg, "from_qname": e.from_qname }))
.collect();
let outgoing: Vec<Value> = all
.iter()
.filter(|e| e.from_symbol_id == symbol.symbol_id)
.map(|e| json!({ "arg": e.arg, "to_param": e.param, "to_qname": e.to_qname }))
.collect();
if incoming.is_empty() && outgoing.is_empty() {
None
} else {
Some(json!({ "incoming": incoming, "outgoing": outgoing }))
}
};
if let Some(df) = dataflow {
out["dataflow"] = df;
}
if !runtime_missed_callees.is_empty() {
out["runtime_missed_callees"] = json!(runtime_missed_callees);
}
Ok(out)
}