refactor(shard_manager): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / rust-surface (push) Failing after 16s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 11s
CI Pipeline / go-surface (push) Canceled after 20s
CI Pipeline / canary (push) Canceled after 21s
CI Pipeline / gitea-offline (push) Canceled after 3s
Auto-version and Release / release (push) Canceled after 34s
CI Pipeline / lint (push) Canceled after 26s
CI Pipeline / unit-tests (push) Canceled after 26s
deploy / deploy (push) Canceled after 31s

This commit is contained in:
Nick committed 2026-10-12 01:28:04 +01:00
1 parent 7d049cc42c
commit 670d55ff62
1 file changed
+149 -75
+149 -75
View File
@@ -26,17 +26,76 @@ pub type OpsCb = extern "C" fn(*const c_char) -> *const c_char;
pub const OP_ENSURE_SCHEMA: &str = "ensure_schema";
pub const OP_COUNT_ENTRIES: &str = "count_entries";
// ── doc-envelope FFI plumbing ────────────────────────────────────────────────
fn read_arg(ptr: *const c_char) -> Result<Json, String> {
if ptr.is_null() {
return Err("null argument pointer".into());
}
let x = unsafe { CStr::from_ptr(ptr) }.to_str().map_err(|e| e.to_string())?;
pyjson::parse(x)
/// Typed kernel error (Phase B): every failure mode the port can produce,
/// exhaustively matched at the envelope boundary.
#[derive(Debug)]
pub enum SmError {
/// fs / os failure (makedirs, read, write)
Io(std::io::Error),
/// doc-envelope JSON problem (parse, missing/badly-typed field)
Doc(String),
/// the ops seam rejected an op the kernel had to have
OpRejected(&'static str),
}
fn envelope(r: Result<Json, String>) -> *mut c_char {
impl std::fmt::Display for SmError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SmError::Io(e) => write!(f, "io error: {}", e),
SmError::Doc(m) => write!(f, "doc error: {}", m),
SmError::OpRejected(op) => write!(f, "{} op failed", op),
}
}
}
impl std::error::Error for SmError {}
impl From<std::io::Error> for SmError {
fn from(e: std::io::Error) -> Self {
SmError::Io(e)
}
}
fn doc_err<T>(msg: impl Into<String>) -> Result<T, SmError> {
Err(SmError::Doc(msg.into()))
}
/// One seam op the kernel can issue. Exhaustive: adding an op forces a
/// compile error here (name + args live in one place).
enum OpKind {
EnsureSchema { path: String },
CountEntries { path: String },
}
impl OpKind {
fn name(&self) -> &'static str {
match self {
OpKind::EnsureSchema { .. } => OP_ENSURE_SCHEMA,
OpKind::CountEntries { .. } => OP_COUNT_ENTRIES,
}
}
fn args(&self) -> Json {
let path = match self {
OpKind::EnsureSchema { path } | OpKind::CountEntries { path } => path,
};
Json::Obj(vec![("path".into(), Json::Str(path.clone()))])
}
}
// ── doc-envelope FFI plumbing ────────────────────────────────────────────────
fn read_arg(ptr: *const c_char) -> Result<Json, SmError> {
if ptr.is_null() {
return doc_err("null argument pointer");
}
let x = unsafe { CStr::from_ptr(ptr) }
.to_str()
.map_err(|e| SmError::Doc(e.to_string()))?;
pyjson::parse(x).map_err(SmError::Doc)
}
fn envelope(r: Result<Json, SmError>) -> *mut c_char {
let v = match r {
Ok(x) => Json::Obj(vec![
("ok".into(), Json::Bool(true)),
@@ -44,7 +103,7 @@ fn envelope(r: Result<Json, String>) -> *mut c_char {
]),
Err(e) => Json::Obj(vec![
("ok".into(), Json::Bool(false)),
("err".into(), Json::Str(e)),
("err".into(), Json::Str(e.to_string())),
]),
};
CString::new(pyjson::dumps(&v))
@@ -54,10 +113,10 @@ fn envelope(r: Result<Json, String>) -> *mut c_char {
/// Issue one op through the callback; `None` on any failure (kernel then
/// degrades exactly like the oracle's best-effort branches).
fn issue_op(cb: OpsCb, op: &str, args: Json) -> Option<Json> {
fn try_op(cb: OpsCb, kind: &OpKind) -> Option<Json> {
let payload = CString::new(pyjson::dumps(&Json::Obj(vec![
("op".into(), Json::Str(op.into())),
("args".into(), args),
("op".into(), Json::Str(kind.name().into())),
("args".into(), kind.args()),
])))
.ok()?;
let ret = cb(payload.as_ptr());
@@ -99,29 +158,38 @@ fn join_path(dir: &str, name: &str) -> String {
// ── kernel (name-preserving port of the 9 oracle fns) ───────────────────────
/// __init__: makedirs(SHARD_DIR, exist_ok=True) + _load_index()
pub fn init(shard_dir: &str, index_path: &str) -> Result<Json, String> {
std::fs::create_dir_all(shard_dir).map_err(|e| e.to_string())?;
pub fn init(shard_dir: &str, index_path: &str) -> Result<Json, SmError> {
std::fs::create_dir_all(shard_dir)?;
load_index(index_path)
}
/// _load_index: missing file → {}; JSONDecodeError → {}; else the parsed dict.
pub fn load_index(index_path: &str) -> Result<Json, String> {
pub fn load_index(index_path: &str) -> Result<Json, SmError> {
let p = std::path::Path::new(index_path);
if !p.exists() {
return Ok(Json::Obj(Vec::new()));
}
let raw = std::fs::read_to_string(p).map_err(|e| e.to_string())?;
let raw = std::fs::read_to_string(p)?;
Ok(pyjson::parse(&raw).unwrap_or(Json::Obj(Vec::new())))
}
/// _save_index: json.dump(self.index, f, indent=2) — no trailing newline.
pub fn save_index(index_path: &str, index: &Json) -> Result<(), String> {
std::fs::write(index_path, pyjson::dump_indent2(index)).map_err(|e| e.to_string())
pub fn save_index(index_path: &str, index: &Json) -> Result<(), SmError> {
std::fs::write(index_path, pyjson::dump_indent2(index))?;
Ok(())
}
/// Shard file name for a topic (oracle: replace(' ', '_').replace('/', '_')).
/// Shard file name for a topic (oracle: replace(' ', '_').replace('/', '_')
/// — both chars map to the same target, so one pass is faithful).
pub fn shard_name(topic: &str) -> String {
format!("shard_{}.db", topic.replace([' ', '/'], "_"))
let normalized: String = topic
.chars()
.map(|c| match c {
' ' | '/' => '_',
other => other,
})
.collect();
format!("shard_{}.db", normalized)
}
/// create_shard: schema creation via the ensure_schema op; early-exists
@@ -133,16 +201,15 @@ pub fn create_shard(
topic: &str,
index: &Json,
cb: OpsCb,
) -> Result<(String, Json), String> {
) -> Result<(String, Json), SmError> {
let shard_path = join_path(shard_dir, &shard_name(topic));
if std::path::Path::new(&shard_path).exists() {
return Ok((shard_path, index.clone()));
}
issue_op(cb, OP_ENSURE_SCHEMA, Json::Obj(vec![(
"path".into(),
Json::Str(shard_path.clone()),
)]))
.ok_or_else(|| "ensure_schema op failed".to_string())?;
try_op(cb, &OpKind::EnsureSchema {
path: shard_path.clone(),
})
.ok_or(SmError::OpRejected(OP_ENSURE_SCHEMA))?;
let mut idx = index.clone();
index_set(&mut idx, topic, &shard_path);
save_index(index_path, &idx)?;
@@ -150,25 +217,23 @@ pub fn create_shard(
}
/// route_query: keyword overlap over the index (insertion order, strictly
/// greater wins → first topic on ties), then the >3-char 3-word fallback.
/// greater wins → first topic on ties — hence fold, not max_by), then the
/// >3-char 3-word fallback.
pub fn route_query(query: &str, index: &Json) -> String {
let query_lower = query.to_lowercase();
let query_words: HashSet<&str> = query_lower.split_whitespace().collect();
let mut best_topic: Option<&str> = None;
let mut best_score: i64 = 0;
if let Json::Obj(pairs) = index {
for (topic, _) in pairs {
let topic_lower = topic.to_lowercase();
let topic_words: HashSet<&str> = topic_lower.split('_').collect();
let overlap = topic_words.intersection(&query_words).count() as i64;
if overlap > best_score {
best_score = overlap;
best_topic = Some(topic.as_str());
}
let best = index_pairs(index).fold((None, 0i64), |(best_topic, best_score), topic| {
let topic_lower = topic.to_lowercase();
let topic_words: HashSet<&str> = topic_lower.split('_').collect();
let overlap = topic_words.intersection(&query_words).count() as i64;
if overlap > best_score {
(Some(topic), overlap)
} else {
(best_topic, best_score)
}
}
if let (Some(topic), true) = (best_topic, best_score > 0) {
});
if let (Some(topic), true) = (best.0, best.1 > 0) {
return topic.to_string();
}
@@ -183,6 +248,15 @@ pub fn route_query(query: &str, index: &Json) -> String {
"general".to_string()
}
/// Borrowed (topic, path) iteration over an index doc — zero-copy view.
fn index_pairs(index: &Json) -> impl Iterator<Item = &str> {
let mut topics: Vec<&str> = Vec::new();
if let Json::Obj(pairs) = index {
topics.extend(pairs.iter().map(|(k, _)| k.as_str()));
}
topics.into_iter()
}
fn merge_key(x: &Json) -> f64 {
for key in ["relevance", "wash_count"] {
if let Some(Json::Num(n)) = x.get(key) {
@@ -228,31 +302,29 @@ pub fn get_shard(
topic: &str,
index: &Json,
cb: OpsCb,
) -> Result<(String, Json), String> {
let existing = index.get(topic).and_then(|v| match v {
Json::Str(s) => Some(s.clone()),
_ => None,
});
if let Some(path) = existing {
return Ok((path, index.clone()));
) -> Result<(String, Json), SmError> {
// zero-copy hit path: borrow the stored path, clone only at the boundary
if let Json::Obj(pairs) = index {
if let Some((_, Json::Str(path))) = pairs.iter().find(|(k, _)| k == topic) {
return Ok((path.clone(), index.clone()));
}
}
create_shard(shard_dir, index_path, topic, index, cb)
}
/// get_shard_stats: one count_entries op per topic, index order; op failure
/// degrades to 0 (oracle except branch).
pub fn get_shard_stats(index: &Json, cb: OpsCb) -> Result<Json, String> {
pub fn get_shard_stats(index: &Json, cb: OpsCb) -> Result<Json, SmError> {
let mut stats = Vec::new();
if let Json::Obj(pairs) = index {
for (topic, path) in pairs {
let path = match path {
Json::Str(s) => s.clone(),
_ => String::new(),
Json::Str(s) => s.as_str(),
_ => "",
};
let count = issue_op(cb, OP_COUNT_ENTRIES, Json::Obj(vec![(
"path".into(),
Json::Str(path),
)]))
let count = try_op(cb, &OpKind::CountEntries {
path: path.to_string(),
})
.and_then(|v| match v {
Json::Num(n) => Some(n as i64),
_ => None,
@@ -265,16 +337,18 @@ pub fn get_shard_stats(index: &Json, cb: OpsCb) -> Result<Json, String> {
}
/// total_entries: sum of get_shard_stats values.
pub fn total_entries(index: &Json, cb: OpsCb) -> Result<i64, String> {
pub fn total_entries(index: &Json, cb: OpsCb) -> Result<i64, SmError> {
let stats = get_shard_stats(index, cb)?;
let mut total: i64 = 0;
if let Json::Obj(pairs) = &stats {
for (_, v) in pairs {
if let Json::Num(n) = v {
total += *n as i64;
}
}
}
let total = match &stats {
Json::Obj(pairs) => pairs
.iter()
.filter_map(|(_, v)| match v {
Json::Num(n) => Some(*n as i64),
_ => None,
})
.sum(),
_ => 0,
};
Ok(total)
}
@@ -282,7 +356,7 @@ pub fn total_entries(index: &Json, cb: OpsCb) -> Result<i64, String> {
#[no_mangle]
pub extern "C" fn sm_init(doc_ptr: *const c_char) -> *mut c_char {
let run = || -> Result<Json, String> {
let run = || -> Result<Json, SmError> {
let doc = read_arg(doc_ptr)?;
let dir = str_field(&doc, "shard_dir")?;
let idx_path = str_field(&doc, "index_path")?;
@@ -293,7 +367,7 @@ pub extern "C" fn sm_init(doc_ptr: *const c_char) -> *mut c_char {
#[no_mangle]
pub extern "C" fn sm_load_index(doc_ptr: *const c_char) -> *mut c_char {
let run = || -> Result<Json, String> {
let run = || -> Result<Json, SmError> {
let doc = read_arg(doc_ptr)?;
load_index(str_field(&doc, "index_path")?)
};
@@ -302,7 +376,7 @@ pub extern "C" fn sm_load_index(doc_ptr: *const c_char) -> *mut c_char {
#[no_mangle]
pub extern "C" fn sm_save_index(doc_ptr: *const c_char) -> *mut c_char {
let run = || -> Result<Json, String> {
let run = || -> Result<Json, SmError> {
let doc = read_arg(doc_ptr)?;
let idx_path = str_field(&doc, "index_path")?;
let index = doc.get("index").cloned().unwrap_or(Json::Obj(Vec::new()));
@@ -314,7 +388,7 @@ pub extern "C" fn sm_save_index(doc_ptr: *const c_char) -> *mut c_char {
#[no_mangle]
pub extern "C" fn sm_create_shard(doc_ptr: *const c_char, cb: OpsCb) -> *mut c_char {
let run = || -> Result<Json, String> {
let run = || -> Result<Json, SmError> {
let doc = read_arg(doc_ptr)?;
let dir = str_field(&doc, "shard_dir")?;
let idx_path = str_field(&doc, "index_path")?;
@@ -331,7 +405,7 @@ pub extern "C" fn sm_create_shard(doc_ptr: *const c_char, cb: OpsCb) -> *mut c_c
#[no_mangle]
pub extern "C" fn sm_route_query(doc_ptr: *const c_char) -> *mut c_char {
let run = || -> Result<Json, String> {
let run = || -> Result<Json, SmError> {
let doc = read_arg(doc_ptr)?;
let query = str_field(&doc, "query")?;
let index = doc.get("index").cloned().unwrap_or(Json::Obj(Vec::new()));
@@ -342,7 +416,7 @@ pub extern "C" fn sm_route_query(doc_ptr: *const c_char) -> *mut c_char {
#[no_mangle]
pub extern "C" fn sm_merge_results(doc_ptr: *const c_char) -> *mut c_char {
let run = || -> Result<Json, String> {
let run = || -> Result<Json, SmError> {
let doc = read_arg(doc_ptr)?;
let results_list = doc.get("results_list").cloned().unwrap_or(Json::Arr(Vec::new()));
Ok(merge_results(&results_list))
@@ -352,7 +426,7 @@ pub extern "C" fn sm_merge_results(doc_ptr: *const c_char) -> *mut c_char {
#[no_mangle]
pub extern "C" fn sm_get_shard(doc_ptr: *const c_char, cb: OpsCb) -> *mut c_char {
let run = || -> Result<Json, String> {
let run = || -> Result<Json, SmError> {
let doc = read_arg(doc_ptr)?;
let dir = str_field(&doc, "shard_dir")?;
let idx_path = str_field(&doc, "index_path")?;
@@ -369,7 +443,7 @@ pub extern "C" fn sm_get_shard(doc_ptr: *const c_char, cb: OpsCb) -> *mut c_char
#[no_mangle]
pub extern "C" fn sm_get_shard_stats(doc_ptr: *const c_char, cb: OpsCb) -> *mut c_char {
let run = || -> Result<Json, String> {
let run = || -> Result<Json, SmError> {
let doc = read_arg(doc_ptr)?;
let index = doc.get("index").cloned().unwrap_or(Json::Obj(Vec::new()));
get_shard_stats(&index, cb)
@@ -379,7 +453,7 @@ pub extern "C" fn sm_get_shard_stats(doc_ptr: *const c_char, cb: OpsCb) -> *mut
#[no_mangle]
pub extern "C" fn sm_total_entries(doc_ptr: *const c_char, cb: OpsCb) -> *mut c_char {
let run = || -> Result<Json, String> {
let run = || -> Result<Json, SmError> {
let doc = read_arg(doc_ptr)?;
let index = doc.get("index").cloned().unwrap_or(Json::Obj(Vec::new()));
Ok(Json::Num(total_entries(&index, cb)? as f64))
@@ -387,10 +461,10 @@ pub extern "C" fn sm_total_entries(doc_ptr: *const c_char, cb: OpsCb) -> *mut c_
envelope(run())
}
fn str_field<'a>(doc: &'a Json, key: &str) -> Result<&'a str, String> {
fn str_field<'a>(doc: &'a Json, key: &str) -> Result<&'a str, SmError> {
match doc.get(key) {
Some(Json::Str(s)) => Ok(s),
_ => Err(format!("missing string field '{}'", key)),
_ => doc_err(format!("missing string field '{}'", key)),
}
}