use super::store::HiveStore;
use super::{KnowledgeUnit, semantic_key};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MergeResult {
Accepted,
Updated,
RejectedLocal,
RejectedPeer,
Duplicate,
}
#[derive(Debug, Default)]
pub struct MergeStats {
pub accepted: u32,
pub updated: u32,
pub rejected: u32,
pub duplicates: u32,
}
pub fn merge_unit(
store: &mut HiveStore,
incoming: &KnowledgeUnit,
local_peer_id: &str,
) -> MergeResult {
let sk = semantic_key(incoming);
match store.find_by_semantic_key(&sk) {
None => {
store.insert(incoming.clone());
MergeResult::Accepted
}
Some(existing) => {
if existing.id == incoming.id && existing.version >= incoming.version {
return MergeResult::Duplicate;
}
if existing.source_peer == local_peer_id {
super::store::log_conflict(existing, incoming);
return MergeResult::RejectedLocal;
}
let existing_score = existing.confidence * existing.evidence_count as f64;
let incoming_score = incoming.confidence * incoming.evidence_count as f64;
let scores_equal = (incoming_score - existing_score).abs() < 1e-9;
if incoming_score > existing_score
|| (scores_equal && incoming.version > existing.version)
{
store.insert(incoming.clone());
MergeResult::Updated
} else {
super::store::log_conflict(existing, incoming);
MergeResult::RejectedPeer
}
}
}
}
pub fn merge_batch(
store: &mut HiveStore,
units: &[KnowledgeUnit],
local_peer_id: &str,
) -> MergeStats {
let mut stats = MergeStats::default();
for unit in units {
match merge_unit(store, unit, local_peer_id) {
MergeResult::Accepted => stats.accepted += 1,
MergeResult::Updated => stats.updated += 1,
MergeResult::RejectedLocal | MergeResult::RejectedPeer => stats.rejected += 1,
MergeResult::Duplicate => stats.duplicates += 1,
}
}
stats
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hive::{KnowledgeContent, KnowledgeScope};
fn make_unit(
id: &str,
tool: &str,
peer: &str,
confidence: f64,
evidence: u32,
) -> KnowledgeUnit {
KnowledgeUnit {
id: id.into(),
scope: KnowledgeScope::Universal,
category: crate::hive::KnowledgeCategory::BestPractice,
content: KnowledgeContent::Pattern {
tool: tool.into(),
command_pattern: Some("test".into()),
preferred_action: "approve".into(),
accept_rate: confidence,
sample_count: evidence,
conditions: vec![],
},
evidence_count: evidence,
confidence,
source_peer: peer.into(),
originated_at: 1000,
last_validated_at: 2000,
propagation_count: 0,
version: 1,
}
}
fn empty_store() -> HiveStore {
HiveStore::load_from(std::path::Path::new("/nonexistent"))
}
#[test]
fn accept_new_unit() {
let mut store = empty_store();
let unit = make_unit("ku_1", "Bash", "peer-a", 0.9, 10);
assert_eq!(
merge_unit(&mut store, &unit, "local"),
MergeResult::Accepted
);
assert_eq!(store.len(), 1);
}
#[test]
fn reject_when_local_exists() {
let mut store = empty_store();
let local_unit = make_unit("ku_local", "Bash", "local", 0.7, 5);
store.insert(local_unit);
let incoming = make_unit("ku_remote", "Bash", "peer-a", 0.99, 100);
assert_eq!(
merge_unit(&mut store, &incoming, "local"),
MergeResult::RejectedLocal
);
assert_eq!(store.len(), 1);
assert!(store.get("ku_local").is_some());
}
#[test]
fn update_peer_with_higher_confidence() {
let mut store = empty_store();
let old = make_unit("ku_old", "Bash", "peer-a", 0.5, 5);
store.insert(old);
let new = make_unit("ku_new", "Bash", "peer-b", 0.9, 20);
assert_eq!(merge_unit(&mut store, &new, "local"), MergeResult::Updated);
assert_eq!(store.len(), 1);
assert!(store.get("ku_new").is_some());
assert!(store.get("ku_old").is_none());
}
#[test]
fn reject_peer_with_lower_confidence() {
let mut store = empty_store();
let strong = make_unit("ku_strong", "Bash", "peer-a", 0.95, 50);
store.insert(strong);
let weak = make_unit("ku_weak", "Bash", "peer-b", 0.6, 3);
assert_eq!(
merge_unit(&mut store, &weak, "local"),
MergeResult::RejectedPeer
);
assert_eq!(store.len(), 1);
assert!(store.get("ku_strong").is_some());
}
#[test]
fn detect_duplicate() {
let mut store = empty_store();
let unit = make_unit("ku_1", "Bash", "peer-a", 0.9, 10);
store.insert(unit.clone());
assert_eq!(
merge_unit(&mut store, &unit, "local"),
MergeResult::Duplicate
);
}
#[test]
fn update_same_id_newer_version() {
let mut store = empty_store();
let mut v1 = make_unit("ku_1", "Bash", "peer-a", 0.9, 10);
v1.version = 1;
store.insert(v1);
let mut v2 = make_unit("ku_1", "Bash", "peer-a", 0.95, 15);
v2.version = 2;
assert_eq!(merge_unit(&mut store, &v2, "local"), MergeResult::Updated);
assert_eq!(store.get("ku_1").unwrap().version, 2);
}
#[test]
fn batch_merge_stats() {
let mut store = empty_store();
let local = make_unit("ku_local", "Bash", "local", 0.8, 10);
store.insert(local);
let units = vec![
make_unit("ku_new1", "Read", "peer-a", 0.9, 10), make_unit("ku_new2", "Bash", "peer-a", 0.99, 100), make_unit("ku_new3", "Write", "peer-b", 0.7, 5), ];
let stats = merge_batch(&mut store, &units, "local");
assert_eq!(stats.accepted, 2);
assert_eq!(stats.rejected, 1);
assert_eq!(stats.duplicates, 0);
}
}