use std::str::FromStr;
use uuid::Uuid;
use khive_storage::types::PageRequest;
use khive_storage::EntityFilter;
use crate::error::{RuntimeError, RuntimeResult};
use crate::operations::Resolved;
use crate::reference_ring::ReferenceRing;
use crate::runtime::{KhiveRuntime, NamespaceToken};
#[derive(Clone, Debug, PartialEq)]
pub struct ReferenceCandidate {
pub id: Uuid,
pub name: Option<String>,
pub score: f64,
}
#[derive(Clone, Debug, PartialEq)]
pub enum ReferenceResolution {
Resolved { id: Uuid, confidence: f64 },
Ambiguous { candidates: Vec<ReferenceCandidate> },
NotFound,
}
const RING_EXACT_CONFIDENCE: f64 = 0.95;
const RING_SUBSTRING_CONFIDENCE: f64 = 0.7;
const RING_AUTO_RESOLVE_CONFIDENCE: f64 = 0.7;
const EXACT_NAME_CONFIDENCE: f64 = 0.98;
const SEARCH_MARGIN_RATIO: f64 = 2.0;
const SEARCH_VECTOR_SIMILARITY_FLOOR: f64 = 0.3;
const SEARCH_RESOLVED_CONFIDENCE: f64 = 0.6;
const STAGE4_MIN_SEARCH_LIMIT: u32 = 20;
pub async fn resolve_reference(
runtime: &KhiveRuntime,
ring: &ReferenceRing,
token: &NamespaceToken,
nl_ref: &str,
limit: u32,
entity_kind: Option<&str>,
) -> RuntimeResult<ReferenceResolution> {
resolve_reference_with_entity_type(runtime, ring, token, nl_ref, limit, entity_kind, None).await
}
#[allow(clippy::too_many_arguments)]
pub async fn resolve_reference_with_entity_type(
runtime: &KhiveRuntime,
ring: &ReferenceRing,
token: &NamespaceToken,
nl_ref: &str,
limit: u32,
entity_kind: Option<&str>,
entity_type: Option<&str>,
) -> RuntimeResult<ReferenceResolution> {
let trimmed = nl_ref.trim();
if trimmed.is_empty() {
return Ok(ReferenceResolution::NotFound);
}
if let Ok(uuid) = Uuid::from_str(trimmed) {
return match runtime.resolve_by_id(token, uuid).await? {
Some(Resolved::Entity(_)) => Ok(ReferenceResolution::Resolved {
id: uuid,
confidence: 1.0,
}),
Some(_) | None => Ok(ReferenceResolution::NotFound),
};
}
if is_hex_prefix(trimmed) {
return match runtime.resolve_prefix_unfiltered(trimmed).await {
Ok(Some(uuid)) => match runtime.resolve_by_id(token, uuid).await? {
Some(Resolved::Entity(_)) => Ok(ReferenceResolution::Resolved {
id: uuid,
confidence: 1.0,
}),
Some(_) | None => Ok(ReferenceResolution::NotFound),
},
Ok(None) => Ok(ReferenceResolution::NotFound),
Err(RuntimeError::AmbiguousPrefix { matches, .. }) => {
let mut entity_matches = Vec::with_capacity(matches.len());
for id in matches {
if matches!(
runtime.resolve_by_id(token, id).await?,
Some(Resolved::Entity(_))
) {
entity_matches.push(id);
}
}
match entity_matches.len() {
0 => Ok(ReferenceResolution::NotFound),
1 => Ok(ReferenceResolution::Resolved {
id: entity_matches[0],
confidence: 1.0,
}),
_ => Ok(ReferenceResolution::Ambiguous {
candidates: entity_matches
.into_iter()
.map(|id| ReferenceCandidate {
id,
name: None,
score: 1.0,
})
.collect(),
}),
}
}
Err(e) => Err(e),
};
}
let actor = token.actor();
let actor_key = format!("{}:{}", actor.kind, actor.id);
let ring_entries = ring.snapshot(token.namespace().as_str(), &actor_key);
let needle = trimmed.to_ascii_lowercase();
let exact: Vec<ReferenceCandidate> = ring_entries
.iter()
.filter(|e| {
e.name
.as_deref()
.is_some_and(|n| n.to_ascii_lowercase() == needle)
})
.map(|e| ReferenceCandidate {
id: e.id,
name: e.name.clone(),
score: RING_EXACT_CONFIDENCE,
})
.collect();
if let Some(resolution) = resolve_from_candidates(exact) {
return Ok(resolution);
}
let substring: Vec<ReferenceCandidate> = ring_entries
.iter()
.filter(|e| {
e.name.as_deref().is_some_and(|n| {
let n_lower = n.to_ascii_lowercase();
n_lower.contains(&needle) || needle.contains(&n_lower)
})
})
.map(|e| ReferenceCandidate {
id: e.id,
name: e.name.clone(),
score: RING_SUBSTRING_CONFIDENCE,
})
.collect();
if let Some(resolution) = resolve_from_candidates(substring) {
return Ok(resolution);
}
if let Some(resolution) =
exact_name_match(runtime, token, trimmed, entity_kind, entity_type).await?
{
return Ok(resolution);
}
let candidate_limit = limit.max(1);
let search_limit = candidate_limit.max(STAGE4_MIN_SEARCH_LIMIT);
let hits = runtime
.hybrid_search_with_vector_similarity_floor(
token,
trimmed,
None,
search_limit,
entity_kind,
entity_type,
&[],
None,
SEARCH_VECTOR_SIMILARITY_FLOOR,
)
.await?;
let candidates: Vec<ReferenceCandidate> = hits
.into_iter()
.map(|h| ReferenceCandidate {
id: h.entity_id,
name: h.title,
score: h.score.to_f64(),
})
.collect();
match candidates.len() {
0 => Ok(ReferenceResolution::NotFound),
1 => Ok(ReferenceResolution::Resolved {
id: candidates[0].id,
confidence: SEARCH_RESOLVED_CONFIDENCE,
}),
_ => {
let top_score = candidates[0].score;
let second_score = candidates[1].score;
let decisive =
second_score <= f64::EPSILON || top_score / second_score >= SEARCH_MARGIN_RATIO;
if decisive {
Ok(ReferenceResolution::Resolved {
id: candidates[0].id,
confidence: SEARCH_RESOLVED_CONFIDENCE,
})
} else {
let mut candidates = candidates;
candidates.truncate(candidate_limit as usize);
Ok(ReferenceResolution::Ambiguous { candidates })
}
}
}
}
fn resolve_from_candidates(candidates: Vec<ReferenceCandidate>) -> Option<ReferenceResolution> {
match candidates.len() {
0 => None,
1 => {
let top = &candidates[0];
Some(if top.score >= RING_AUTO_RESOLVE_CONFIDENCE {
ReferenceResolution::Resolved {
id: top.id,
confidence: top.score,
}
} else {
ReferenceResolution::Ambiguous { candidates }
})
}
_ => Some(ReferenceResolution::Ambiguous { candidates }),
}
}
async fn exact_name_match(
runtime: &KhiveRuntime,
token: &NamespaceToken,
name: &str,
entity_kind: Option<&str>,
entity_type: Option<&str>,
) -> RuntimeResult<Option<ReferenceResolution>> {
let mut filter = EntityFilter {
name_exact: Some(name.to_string()),
kinds: entity_kind.map(|k| vec![k.to_string()]).unwrap_or_default(),
..EntityFilter::default()
};
if let (Some(kind), Some(entity_type)) = (entity_kind, entity_type) {
filter
.entity_types_by_kind
.insert(kind.to_string(), vec![entity_type.to_string()]);
}
let page = runtime
.entities(token)?
.query_entities(
token.namespace().as_str(),
filter,
PageRequest {
offset: 0,
limit: 10,
},
)
.await
.map_err(RuntimeError::Storage)?;
let total = page.total.unwrap_or(page.items.len() as u64);
let exact: Vec<ReferenceCandidate> = page
.items
.into_iter()
.map(|e| ReferenceCandidate {
id: e.id,
name: Some(e.name),
score: EXACT_NAME_CONFIDENCE,
})
.collect();
Ok(match total {
0 => None,
1 => exact
.into_iter()
.next()
.map(|top| ReferenceResolution::Resolved {
id: top.id,
confidence: EXACT_NAME_CONFIDENCE,
}),
_ => Some(ReferenceResolution::Ambiguous { candidates: exact }),
})
}
fn is_hex_prefix(s: &str) -> bool {
s.len() >= 8 && s.chars().all(|c| c.is_ascii_hexdigit())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::{NamespaceToken as TokenCtor, RuntimeConfig};
use crate::embedder_registry::EmbedderProvider;
use crate::retrieval::SearchSource;
use khive_gate::ActorRef;
use khive_types::{namespace::Namespace, SubstrateKind};
use lattice_embed::{EmbeddingModel, EmbeddingService};
use std::sync::Arc;
struct ConstantEmbeddingService {
dimensions: usize,
}
#[async_trait::async_trait]
impl EmbeddingService for ConstantEmbeddingService {
async fn embed(
&self,
texts: &[String],
_model: EmbeddingModel,
) -> Result<Vec<Vec<f32>>, lattice_embed::EmbedError> {
Ok(texts.iter().map(|_| vec![1.0; self.dimensions]).collect())
}
fn supports_model(&self, _model: EmbeddingModel) -> bool {
true
}
fn name(&self) -> &'static str {
"resolve-test-constant-embedding"
}
}
struct ConstantEmbedderProvider {
name: String,
dimensions: usize,
}
#[async_trait::async_trait]
impl EmbedderProvider for ConstantEmbedderProvider {
fn name(&self) -> &str {
&self.name
}
fn dimensions(&self) -> usize {
self.dimensions
}
async fn build(&self) -> RuntimeResult<Arc<dyn EmbeddingService>> {
Ok(Arc::new(ConstantEmbeddingService {
dimensions: self.dimensions,
}))
}
}
fn runtime_with_constant_embeddings() -> KhiveRuntime {
let model = EmbeddingModel::AllMiniLmL6V2;
let runtime = KhiveRuntime::new(RuntimeConfig {
db_path: None,
embedding_model: Some(model),
packs: vec!["kg".to_string()],
..RuntimeConfig::no_embeddings()
})
.expect("in-memory runtime");
runtime.register_embedder(ConstantEmbedderProvider {
name: model.to_string(),
dimensions: model.dimensions(),
});
runtime
}
fn actor_token(actor_id: &str) -> NamespaceToken {
TokenCtor::mint_authorized(Namespace::local(), ActorRef::new("agent", actor_id))
}
#[tokio::test]
async fn id_string_passthrough_resolves_full_uuid() {
let rt = KhiveRuntime::memory().expect("in-memory runtime");
let token = actor_token("resolver-test");
let ring = ReferenceRing::new();
let entity = rt
.create_entity(
&token,
"concept",
None,
"PassthroughTarget",
None,
None,
vec![],
)
.await
.expect("create entity");
let resolution = resolve_reference(&rt, &ring, &token, &entity.id.to_string(), 5, None)
.await
.expect("resolve_reference");
assert_eq!(
resolution,
ReferenceResolution::Resolved {
id: entity.id,
confidence: 1.0
}
);
}
#[tokio::test]
async fn id_string_passthrough_never_errors_on_a_miss() {
let rt = KhiveRuntime::memory().expect("in-memory runtime");
let token = actor_token("resolver-test");
let ring = ReferenceRing::new();
let missing = Uuid::new_v4();
let resolution = resolve_reference(&rt, &ring, &token, &missing.to_string(), 5, None)
.await
.expect("must not error, only report NotFound");
assert_eq!(resolution, ReferenceResolution::NotFound);
}
#[tokio::test]
async fn ring_exact_match_resolves_without_search() {
let rt = KhiveRuntime::memory().expect("in-memory runtime");
let token = actor_token("resolver-test");
let ring = ReferenceRing::new();
let actor = token.actor();
let actor_key = format!("{}:{}", actor.kind, actor.id);
let id = Uuid::new_v4();
ring.admit(
token.namespace().as_str(),
&actor_key,
id,
Some("the old record".to_string()),
);
let resolution = resolve_reference(&rt, &ring, &token, "the old record", 5, None)
.await
.expect("resolve_reference");
assert_eq!(
resolution,
ReferenceResolution::Resolved {
id,
confidence: RING_EXACT_CONFIDENCE
}
);
}
#[tokio::test]
async fn ring_ambiguous_on_multiple_exact_matches() {
let rt = KhiveRuntime::memory().expect("in-memory runtime");
let token = actor_token("resolver-test");
let ring = ReferenceRing::new();
let actor = token.actor();
let actor_key = format!("{}:{}", actor.kind, actor.id);
let id_a = Uuid::new_v4();
let id_b = Uuid::new_v4();
ring.admit(
token.namespace().as_str(),
&actor_key,
id_a,
Some("duplicate name".to_string()),
);
ring.admit(
token.namespace().as_str(),
&actor_key,
id_b,
Some("duplicate name".to_string()),
);
let resolution = resolve_reference(&rt, &ring, &token, "duplicate name", 5, None)
.await
.expect("resolve_reference");
match resolution {
ReferenceResolution::Ambiguous { candidates } => {
assert_eq!(candidates.len(), 2);
}
other => panic!("expected Ambiguous, got {other:?}"),
}
}
#[tokio::test]
async fn no_ring_entry_and_no_search_hit_is_not_found() {
let rt = KhiveRuntime::memory().expect("in-memory runtime");
let token = actor_token("resolver-test");
let ring = ReferenceRing::new();
let resolution =
resolve_reference(&rt, &ring, &token, "nothing matches this at all", 5, None)
.await
.expect("resolve_reference");
assert_eq!(resolution, ReferenceResolution::NotFound);
}
#[tokio::test]
async fn actor_isolation_blocks_cross_actor_ring_reads() {
let rt = KhiveRuntime::memory().expect("in-memory runtime");
let token_a = actor_token("actor-a");
let token_b = actor_token("actor-b");
let ring = ReferenceRing::new();
let actor_a = token_a.actor();
let actor_key_a = format!("{}:{}", actor_a.kind, actor_a.id);
let id = Uuid::new_v4();
ring.admit(
token_a.namespace().as_str(),
&actor_key_a,
id,
Some("shared-namespace-name".to_string()),
);
let resolution = resolve_reference(&rt, &ring, &token_b, "shared-namespace-name", 5, None)
.await
.expect("resolve_reference");
assert_eq!(resolution, ReferenceResolution::NotFound);
}
#[tokio::test]
async fn exact_name_stage_survives_many_newer_case_variant_decoys() {
let rt = KhiveRuntime::memory().expect("in-memory runtime");
let token = actor_token("resolver-test");
let ring = ReferenceRing::new();
let target = rt
.create_entity(&token, "concept", None, "RoLoRA", None, None, vec![])
.await
.expect("create target entity");
let decoy_cases = ["rolora", "ROLORA", "RoLoRa", "roLORA"];
for i in 0..120 {
rt.create_entity(
&token,
"concept",
None,
decoy_cases[i % decoy_cases.len()],
None,
None,
vec![],
)
.await
.expect("create decoy entity");
}
let resolution = resolve_reference(&rt, &ring, &token, "RoLoRA", 5, None)
.await
.expect("resolve_reference");
assert_eq!(
resolution,
ReferenceResolution::Resolved {
id: target.id,
confidence: EXACT_NAME_CONFIDENCE,
}
);
}
#[tokio::test]
async fn exact_name_ambiguous_decision_uses_storage_total_not_page_len() {
let rt = KhiveRuntime::memory().expect("in-memory runtime");
let token = actor_token("resolver-test");
let ring = ReferenceRing::new();
for _ in 0..11 {
rt.create_entity(&token, "concept", None, "DupeExactName", None, None, vec![])
.await
.expect("create duplicate-named entity");
}
let resolution = resolve_reference(&rt, &ring, &token, "DupeExactName", 5, None)
.await
.expect("resolve_reference");
match resolution {
ReferenceResolution::Ambiguous { candidates } => {
assert_eq!(
candidates.len(),
10,
"candidate set is a bounded 10-row sample of the 11 storage matches, \
not the complete set"
);
}
other => panic!("expected Ambiguous driven by storage total (11), got {other:?}"),
}
}
#[tokio::test]
async fn exact_name_resolves_ahead_of_competing_hybrid_matches() {
let rt = KhiveRuntime::memory().expect("in-memory runtime");
let token = actor_token("resolver-test");
let ring = ReferenceRing::new();
let target = rt
.create_entity(
&token,
"concept",
None,
"ADR-040",
Some("khive ADR-040"),
None,
vec![],
)
.await
.expect("create target entity");
for i in 0..12 {
rt.create_entity(
&token,
"concept",
None,
&format!("ADR-040 companion {i}"),
Some("ADR-040 ADR-040 ADR-040 ADR-040 ADR-040"),
None,
vec![],
)
.await
.expect("create competing entity");
}
let resolution = resolve_reference(&rt, &ring, &token, "ADR-040", 1, None)
.await
.expect("resolve_reference");
assert_eq!(
resolution,
ReferenceResolution::Resolved {
id: target.id,
confidence: EXACT_NAME_CONFIDENCE,
}
);
}
#[tokio::test]
async fn fallback_stage_bounds_non_exact_payload_to_limit() {
let rt = KhiveRuntime::memory().expect("in-memory runtime");
let token = actor_token("resolver-test");
let ring = ReferenceRing::new();
for i in 0..12 {
rt.create_entity(
&token,
"concept",
None,
&format!("Retrieval Fusion Note {i}"),
Some("khive retrieval fusion ranking note"),
None,
vec![],
)
.await
.expect("create entity");
}
let resolution = resolve_reference(
&rt,
&ring,
&token,
"khive retrieval fusion ranking",
5,
None,
)
.await
.expect("resolve_reference");
match resolution {
ReferenceResolution::Ambiguous { candidates } => {
assert_eq!(
candidates.len(),
5,
"a non-exact ref's ambiguity payload is bounded to the caller's limit"
);
}
other => panic!("expected a bounded Ambiguous sample, got {other:?}"),
}
}
#[tokio::test]
async fn fallback_stage_still_reports_ambiguous_on_genuine_tie() {
let rt = KhiveRuntime::memory().expect("in-memory runtime");
let token = actor_token("resolver-test");
let ring = ReferenceRing::new();
let a = rt
.create_entity(
&token,
"concept",
None,
"Twin Record",
Some("khive Twin Record document"),
None,
vec![],
)
.await
.expect("create entity a");
let b = rt
.create_entity(
&token,
"concept",
None,
"Twin Record",
Some("khive Twin Record document"),
None,
vec![],
)
.await
.expect("create entity b");
let resolution = resolve_reference(&rt, &ring, &token, "Twin Record", 5, None)
.await
.expect("resolve_reference");
match resolution {
ReferenceResolution::Ambiguous { candidates } => {
let ids: std::collections::HashSet<Uuid> =
candidates.iter().map(|c| c.id).collect();
assert!(ids.contains(&a.id) && ids.contains(&b.id));
}
other => panic!("expected Ambiguous on a genuine name tie, got {other:?}"),
}
}
#[tokio::test]
async fn fallback_stage_resolves_high_similarity_semantic_only_candidate() {
let rt = runtime_with_constant_embeddings();
let token = actor_token("resolver-test");
let ring = ReferenceRing::new();
let entity = rt
.create_entity(&token, "concept", None, "Canine", None, None, vec![])
.await
.expect("create semantic match");
let raw_hits = rt
.vector_search(
&token,
None,
Some("domestic dog"),
5,
Some(SubstrateKind::Entity),
)
.await
.expect("vector search");
assert_eq!(raw_hits.len(), 1);
assert_eq!(raw_hits[0].subject_id, entity.id);
assert!((raw_hits[0].score.to_f64() - 1.0).abs() < 1e-6);
let hits = rt
.hybrid_search(&token, "domestic dog", None, 5, None, None, &[], None)
.await
.expect("hybrid search");
assert_eq!(hits.len(), 1);
assert_eq!(hits[0].source, SearchSource::Vector);
let resolution = resolve_reference(&rt, &ring, &token, "domestic dog", 5, None)
.await
.expect("resolve_reference");
assert_eq!(
resolution,
ReferenceResolution::Resolved {
id: entity.id,
confidence: SEARCH_RESOLVED_CONFIDENCE,
}
);
}
#[tokio::test]
async fn fallback_stage_resolves_moderate_similarity_semantic_only_candidate() {
let rt = runtime_with_constant_embeddings();
let token = actor_token("resolver-test");
let ring = ReferenceRing::new();
let dimensions = EmbeddingModel::AllMiniLmL6V2.dimensions();
let entity = rt
.create_entity(
&token,
"concept",
None,
"Moderately Similar Candidate",
None,
None,
vec![],
)
.await
.expect("create moderate-similarity entity");
let vectors = rt.vectors(&token).expect("vector store");
vectors
.delete(entity.id)
.await
.expect("delete generated vector");
let aligned = dimensions * 3 / 4;
let mut moderate_vector = vec![1.0f32; aligned];
moderate_vector.extend(vec![-1.0f32; dimensions - aligned]);
vectors
.insert(
entity.id,
SubstrateKind::Entity,
token.namespace().as_str(),
"entity.body",
vec![moderate_vector],
)
.await
.expect("insert moderate-similarity vector");
let query = "totally-nonexistent-moderate-zzz";
let raw_hits = rt
.vector_search(&token, None, Some(query), 5, Some(SubstrateKind::Entity))
.await
.expect("vector search");
assert_eq!(raw_hits.len(), 1);
assert_eq!(raw_hits[0].subject_id, entity.id);
assert!((raw_hits[0].score.to_f64() - 0.5).abs() < 1e-6);
let resolution = resolve_reference(&rt, &ring, &token, query, 5, None)
.await
.expect("resolve_reference");
assert_eq!(
resolution,
ReferenceResolution::Resolved {
id: entity.id,
confidence: SEARCH_RESOLVED_CONFIDENCE,
}
);
}
#[tokio::test]
async fn fallback_stage_drops_orthogonal_semantic_only_candidate() {
let rt = runtime_with_constant_embeddings();
let token = actor_token("resolver-test");
let ring = ReferenceRing::new();
let dimensions = EmbeddingModel::AllMiniLmL6V2.dimensions();
let entity = rt
.create_entity(
&token,
"concept",
None,
"Unrelated Orthogonal",
None,
None,
vec![],
)
.await
.expect("create unrelated entity");
let vectors = rt.vectors(&token).expect("vector store");
vectors
.delete(entity.id)
.await
.expect("delete generated vector");
let mut orthogonal_vector = vec![1.0f32; dimensions / 2];
orthogonal_vector.extend(vec![-1.0f32; dimensions - dimensions / 2]);
vectors
.insert(
entity.id,
SubstrateKind::Entity,
token.namespace().as_str(),
"entity.body",
vec![orthogonal_vector],
)
.await
.expect("insert orthogonal vector");
let raw_hits = rt
.vector_search(
&token,
None,
Some("totally-nonexistent-orthogonal-zzz"),
5,
Some(SubstrateKind::Entity),
)
.await
.expect("vector search");
assert_eq!(raw_hits.len(), 1);
assert!((raw_hits[0].score.to_f64() - 0.0).abs() < 1e-6);
let resolution = resolve_reference(
&rt,
&ring,
&token,
"totally-nonexistent-orthogonal-zzz",
5,
None,
)
.await
.expect("resolve_reference");
assert_eq!(resolution, ReferenceResolution::NotFound);
}
#[tokio::test]
async fn fallback_stage_drops_low_similarity_semantic_only_candidates() {
let rt = runtime_with_constant_embeddings();
let token = actor_token("resolver-test");
let ring = ReferenceRing::new();
let dimensions = EmbeddingModel::AllMiniLmL6V2.dimensions();
let entity = rt
.create_entity(
&token,
"concept",
None,
"Unrelated Alpha",
None,
None,
vec![],
)
.await
.expect("create unrelated entity");
let vectors = rt.vectors(&token).expect("vector store");
vectors
.delete(entity.id)
.await
.expect("delete generated vector");
let quarter = dimensions / 4;
let mut mismatched_vector = vec![1.0f32; quarter];
mismatched_vector.extend(vec![-1.0f32; dimensions - quarter]);
vectors
.insert(
entity.id,
SubstrateKind::Entity,
token.namespace().as_str(),
"entity.body",
vec![mismatched_vector],
)
.await
.expect("insert mismatched vector");
let raw_hits = rt
.vector_search(
&token,
None,
Some("totally-nonexistent-zzz"),
5,
Some(SubstrateKind::Entity),
)
.await
.expect("vector search");
assert_eq!(raw_hits.len(), 1);
assert!((raw_hits[0].score.to_f64() + 0.5).abs() < 1e-6);
let resolution = resolve_reference(&rt, &ring, &token, "totally-nonexistent-zzz", 5, None)
.await
.expect("resolve_reference");
assert_eq!(resolution, ReferenceResolution::NotFound);
}
#[tokio::test]
async fn fallback_stage_still_not_found_on_garbage() {
let rt = KhiveRuntime::memory().expect("in-memory runtime");
let token = actor_token("resolver-test");
let ring = ReferenceRing::new();
rt.create_entity(
&token,
"concept",
None,
"ADR-040",
Some("khive ADR-040"),
None,
vec![],
)
.await
.expect("create unrelated entity");
let resolution = resolve_reference(
&rt,
&ring,
&token,
"zzqxw completely unrelated garbage nonsense",
5,
None,
)
.await
.expect("resolve_reference");
assert_eq!(resolution, ReferenceResolution::NotFound);
}
}