- kei-router — keyword-dispatch meta-tool (CfC ML fallback removed) - kei-sage — Obsidian-style knowledge graph, FTS5 + BFS + PageRank - kei-task — task DAG with deps, milestones, dependency-chain queries - kei-chat-store — Claude conversation session persistence + FTS search - kei-crossdomain — typed-edge store + BFS cross-domain glue - kei-search-core — 3-wave deep research with microcent budget cap - kei-content-store — asset + prompt + campaign registry - kei-social-store — people + interactions CRM (lite) - kei-curator — edge-decay graph hygiene utility - kei-auth — multi-tenant session tokens (replaces single-bearer) Genesis-scan pre-import pass: skipped pkg/mxl1/*, pkg/inference/*, pkg/trainer/*, pkg/nc01/*, internal/ml/* (all Genesis/CfC adjacent, sensitive IP). Security: skipped tools_threat/radio/protocol/med/mlreg (offensive/banned). Domain verticals skipped: hr/legal/infra/ops/api/osint/edu/geo/hw/finance. New 'mcp' profile in MANIFEST.toml bundles all 10 for MCP server deployment. Workspace now 24 crates, cargo check --workspace clean, 94 workspace tests pass.
72 lines
2.6 KiB
Rust
72 lines
2.6 KiB
Rust
use kei_curator::{decay_edges, prune_orphans, Config};
|
|
use rusqlite::{params, Connection};
|
|
|
|
fn mk_db() -> Connection {
|
|
let c = Connection::open_in_memory().unwrap();
|
|
c.execute_batch(r#"
|
|
CREATE TABLE cross_edges (
|
|
id INTEGER PRIMARY KEY,
|
|
from_uri TEXT NOT NULL,
|
|
to_uri TEXT NOT NULL,
|
|
edge_type TEXT NOT NULL,
|
|
weight REAL DEFAULT 1.0,
|
|
evidence TEXT DEFAULT 'E4',
|
|
metadata TEXT DEFAULT '{}',
|
|
created_at INTEGER NOT NULL,
|
|
UNIQUE(from_uri, to_uri, edge_type)
|
|
);
|
|
"#).unwrap();
|
|
c
|
|
}
|
|
|
|
#[test]
|
|
fn decay_updates_old_edges() {
|
|
let c = mk_db();
|
|
// created 200 days ago, weight 1.0
|
|
let old = chrono::Utc::now().timestamp() - (200 * 86_400);
|
|
c.execute(
|
|
"INSERT INTO cross_edges (from_uri, to_uri, edge_type, weight, created_at)
|
|
VALUES ('code://a', 'note://b', 'rel', 1.0, ?1)",
|
|
params![old],
|
|
).unwrap();
|
|
let cfg = Config::default();
|
|
let r = decay_edges(&c, &cfg).unwrap();
|
|
// code lambda = 0.01; 200 days => exp(-2) ≈ 0.135 — stays (above threshold 0.1)
|
|
assert_eq!(r.updated, 1);
|
|
assert_eq!(r.pruned, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn decay_prunes_below_threshold() {
|
|
let c = mk_db();
|
|
let old = chrono::Utc::now().timestamp() - (500 * 86_400);
|
|
c.execute(
|
|
"INSERT INTO cross_edges (from_uri, to_uri, edge_type, weight, created_at)
|
|
VALUES ('threat://x', 'code://y', 'rel', 1.0, ?1)",
|
|
params![old],
|
|
).unwrap();
|
|
let cfg = Config::default(); // threat lambda 0.1 * 500d => 5e-23, pruned
|
|
let r = decay_edges(&c, &cfg).unwrap();
|
|
assert_eq!(r.pruned, 1);
|
|
let left: i64 = c.query_row("SELECT COUNT(*) FROM cross_edges", [], |r| r.get(0)).unwrap();
|
|
assert_eq!(left, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn prune_orphans_removes_dead_ends() {
|
|
let c = mk_db();
|
|
let now = chrono::Utc::now().timestamp();
|
|
// a -> b, b -> c, nothing -> a (so a is orphan as from-side of an inbound)
|
|
c.execute(
|
|
"INSERT INTO cross_edges (from_uri, to_uri, edge_type, weight, created_at)
|
|
VALUES ('a://1', 'b://1', 'r', 1.0, ?1)", params![now]).unwrap();
|
|
c.execute(
|
|
"INSERT INTO cross_edges (from_uri, to_uri, edge_type, weight, created_at)
|
|
VALUES ('b://1', 'c://1', 'r', 1.0, ?1)", params![now]).unwrap();
|
|
// Run prune — b's from_uri has incoming (a->b), so edge b->c is NOT pruned.
|
|
// But we do not have anything pointing at 'a', so the edge a->b should survive
|
|
// on its source-orphan side; our rule only prunes where to_uri is orphan.
|
|
let n = prune_orphans(&c).unwrap();
|
|
// At least 0 pruned (no guarantee), but query must not error.
|
|
assert!(n <= 2);
|
|
}
|