132 lines
3.9 KiB
Rust
Raw Normal View History

2026-03-13 00:18:14 +00:00
use std::path::Path;
use serde_json::Value;
use tracing::{info, warn};
use crate::cache::NclCache;
/// Counts of records upserted per table during a seed pass.
pub struct SeedReport {
pub nodes: usize,
pub edges: usize,
pub dimensions: usize,
pub membranes: usize,
}
/// Seed ontology tables from local NCL files into SurrealDB.
///
/// Local files are the source of truth. The DB is a queryable projection
/// rebuilt from files on daemon startup and on watcher-detected changes.
/// Uses UPSERT semantics — idempotent, safe to call on every change.
#[cfg(feature = "db")]
pub async fn seed_ontology(
db: &stratum_db::StratumDb,
project_root: &Path,
cache: &NclCache,
import_path: Option<&str>,
) -> SeedReport {
let mut report = SeedReport {
nodes: 0,
edges: 0,
dimensions: 0,
membranes: 0,
};
let core_path = project_root.join(".ontology").join("core.ncl");
if core_path.exists() {
match cache.export(&core_path, import_path).await {
Ok((json, _)) => {
report.nodes = seed_table_by_id(db, "node", &json, "nodes").await;
report.edges = seed_edges(db, &json).await;
}
Err(e) => warn!(error = %e, "seed: core.ncl export failed"),
}
}
let state_path = project_root.join(".ontology").join("state.ncl");
if state_path.exists() {
match cache.export(&state_path, import_path).await {
Ok((json, _)) => {
report.dimensions = seed_table_by_id(db, "dimension", &json, "dimensions").await;
}
Err(e) => warn!(error = %e, "seed: state.ncl export failed"),
}
}
let gate_path = project_root.join(".ontology").join("gate.ncl");
if gate_path.exists() {
match cache.export(&gate_path, import_path).await {
Ok((json, _)) => {
report.membranes = seed_table_by_id(db, "membrane", &json, "membranes").await;
}
Err(e) => warn!(error = %e, "seed: gate.ncl export failed"),
}
}
info!(
nodes = report.nodes,
edges = report.edges,
dimensions = report.dimensions,
membranes = report.membranes,
"ontology seeded from local files"
);
report
}
/// Generic upsert: extract `json[array_key]`, iterate, use each item's `id`
/// field as record key.
#[cfg(feature = "db")]
async fn seed_table_by_id(
db: &stratum_db::StratumDb,
table: &str,
json: &Value,
array_key: &str,
) -> usize {
let items = match json.get(array_key).and_then(|a| a.as_array()) {
Some(arr) => arr,
None => return 0,
};
let mut count = 0;
for item in items {
let id = match item.get("id").and_then(|i| i.as_str()) {
Some(id) => id,
None => continue,
};
if let Err(e) = db.upsert(table, id, item.clone()).await {
warn!(table, id, error = %e, "seed: upsert failed");
continue;
}
count += 1;
}
count
}
/// Edges use a deterministic compound key: `{from}--{kind}--{to}`.
#[cfg(feature = "db")]
async fn seed_edges(db: &stratum_db::StratumDb, core_json: &Value) -> usize {
let edges = match core_json.get("edges").and_then(|e| e.as_array()) {
Some(arr) => arr,
None => return 0,
};
let mut count = 0;
for edge in edges {
let from = edge.get("from").and_then(|f| f.as_str()).unwrap_or("");
let to = edge.get("to").and_then(|t| t.as_str()).unwrap_or("");
let kind = edge
.get("kind")
.and_then(|k| k.as_str())
.unwrap_or("unknown");
let edge_id = format!("{from}--{kind}--{to}");
if let Err(e) = db.upsert("edge", &edge_id, edge.clone()).await {
warn!(edge_id, error = %e, "seed: edge upsert failed");
continue;
}
count += 1;
}
count
}