use std::fs;
use std::path::PathBuf;
use serde_json::{json, Value as Json};
pub const SCHEMA_VERSION: &str = "card/v0";
fn cards_dir() -> Result<PathBuf, String> {
let home = dirs::home_dir().ok_or("Cannot determine home directory")?;
let dir = home.join(".algocline").join("cards");
if !dir.exists() {
fs::create_dir_all(&dir).map_err(|e| format!("Failed to create cards dir: {e}"))?;
}
Ok(dir)
}
fn pkg_dir(pkg: &str) -> Result<PathBuf, String> {
validate_name(pkg, "pkg")?;
let dir = cards_dir()?.join(pkg);
if !dir.exists() {
fs::create_dir_all(&dir).map_err(|e| format!("Failed to create pkg dir: {e}"))?;
}
Ok(dir)
}
fn validate_name(name: &str, kind: &str) -> Result<(), String> {
if name.is_empty()
|| name.contains('/')
|| name.contains('\\')
|| name.contains("..")
|| name.contains('\0')
{
return Err(format!("Invalid {kind} name: '{name}'"));
}
Ok(())
}
fn djb2_hex(s: &str) -> String {
let mut h: u64 = 5381;
for b in s.bytes() {
h = h.wrapping_mul(33).wrapping_add(b as u64);
}
format!("{h:016x}")
}
fn hash6(s: &str) -> String {
let hex = djb2_hex(s);
let start = hex.len().saturating_sub(6);
hex[start..].to_string()
}
fn stable_json(v: &Json) -> String {
let mut buf = String::new();
stable_json_into(v, &mut buf);
buf
}
fn stable_json_into(v: &Json, buf: &mut String) {
match v {
Json::Null => buf.push_str("null"),
Json::Bool(b) => buf.push_str(if *b { "true" } else { "false" }),
Json::Number(n) => buf.push_str(&n.to_string()),
Json::String(s) => {
buf.push('"');
buf.push_str(s);
buf.push('"');
}
Json::Array(a) => {
buf.push('[');
for (i, item) in a.iter().enumerate() {
if i > 0 {
buf.push(',');
}
stable_json_into(item, buf);
}
buf.push(']');
}
Json::Object(m) => {
let mut keys: Vec<&String> = m.keys().collect();
keys.sort();
buf.push('{');
for (i, k) in keys.iter().enumerate() {
if i > 0 {
buf.push(',');
}
buf.push('"');
buf.push_str(k);
buf.push_str("\":");
stable_json_into(&m[*k], buf);
}
buf.push('}');
}
}
}
fn short_model(id: &str) -> String {
if id.is_empty() {
return "model".into();
}
let stripped = id
.strip_prefix("claude-")
.or_else(|| id.strip_prefix("gpt-"))
.unwrap_or(id);
let s: String = stripped
.chars()
.filter(|c| c.is_ascii_alphanumeric())
.collect();
if s.is_empty() {
"model".into()
} else {
s
}
}
fn now_rfc3339() -> String {
let secs = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0) as i64;
let days = secs.div_euclid(86400);
let tod = secs.rem_euclid(86400);
let (y, mo, d) = civil_from_days(days);
let hh = tod / 3600;
let mm = (tod % 3600) / 60;
let ss = tod % 60;
format!("{y:04}-{mo:02}-{d:02}T{hh:02}:{mm:02}:{ss:02}Z")
}
fn now_compact() -> String {
let secs = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0) as i64;
let days = secs.div_euclid(86400);
let tod = secs.rem_euclid(86400);
let (y, mo, d) = civil_from_days(days);
let hh = tod / 3600;
let mm = (tod % 3600) / 60;
let ss = tod % 60;
format!("{y:04}{mo:02}{d:02}T{hh:02}{mm:02}{ss:02}")
}
fn civil_from_days(z: i64) -> (i32, u32, u32) {
let z = z + 719468;
let era = if z >= 0 { z } else { z - 146096 } / 146097;
let doe = (z - era * 146097) as u64;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe as i64 + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
let m = (if mp < 10 { mp + 3 } else { mp - 9 }) as u32;
let y = y + if m <= 2 { 1 } else { 0 };
(y as i32, m, d)
}
fn json_to_toml(v: Json) -> Result<toml::Value, String> {
Ok(match v {
Json::Null => return Err("TOML does not support null values".into()),
Json::Bool(b) => toml::Value::Boolean(b),
Json::Number(n) => {
if let Some(i) = n.as_i64() {
toml::Value::Integer(i)
} else if let Some(f) = n.as_f64() {
toml::Value::Float(f)
} else {
return Err(format!("Unsupported number: {n}"));
}
}
Json::String(s) => toml::Value::String(s),
Json::Array(a) => {
let mut out = Vec::with_capacity(a.len());
for item in a {
if !item.is_null() {
out.push(json_to_toml(item)?);
}
}
toml::Value::Array(out)
}
Json::Object(m) => {
let mut table = toml::map::Map::new();
for (k, val) in m {
if val.is_null() {
continue;
}
table.insert(k, json_to_toml(val)?);
}
toml::Value::Table(table)
}
})
}
fn toml_to_json(v: toml::Value) -> Json {
match v {
toml::Value::String(s) => Json::String(s),
toml::Value::Integer(i) => json!(i),
toml::Value::Float(f) => json!(f),
toml::Value::Boolean(b) => Json::Bool(b),
toml::Value::Datetime(dt) => Json::String(dt.to_string()),
toml::Value::Array(a) => Json::Array(a.into_iter().map(toml_to_json).collect()),
toml::Value::Table(t) => {
let mut m = serde_json::Map::new();
for (k, v) in t {
m.insert(k, toml_to_json(v));
}
Json::Object(m)
}
}
}
fn require_pkg_name(input: &Json) -> Result<String, String> {
let name = input
.get("pkg")
.and_then(|p| p.get("name"))
.and_then(|n| n.as_str())
.ok_or_else(|| "alc.card.create: pkg.name is required".to_string())?
.to_string();
validate_name(&name, "pkg")?;
Ok(name)
}
pub fn create(mut input: Json) -> Result<(String, PathBuf), String> {
if !input.is_object() {
return Err("alc.card.create: input must be a table".into());
}
let pkg_name = require_pkg_name(&input)?;
let obj = input.as_object_mut().unwrap();
obj.entry("schema_version".to_string())
.or_insert_with(|| json!(SCHEMA_VERSION));
obj.entry("created_at".to_string())
.or_insert_with(|| json!(now_rfc3339()));
obj.entry("created_by".to_string())
.or_insert_with(|| json!(format!("alc@{}", env!("CARGO_PKG_VERSION"))));
if let Some(params) = obj.get("params").cloned() {
if params.is_object() {
let fp = djb2_hex(&stable_json(¶ms));
obj.insert("param_fingerprint".to_string(), json!(fp));
}
}
let card_id = match obj.get("card_id").and_then(|v| v.as_str()) {
Some(id) if !id.is_empty() => id.to_string(),
_ => {
let model_id = obj
.get("model")
.and_then(|m| m.get("id"))
.and_then(|v| v.as_str())
.unwrap_or("");
let model_short = short_model(model_id);
let ts = now_compact();
let fp_seed = stable_json(&Json::Object(obj.clone()));
let h = hash6(&fp_seed);
format!("{pkg_name}_{model_short}_{ts}_{h}")
}
};
validate_name(&card_id, "card_id")?;
obj.insert("card_id".to_string(), json!(card_id.clone()));
let dir = pkg_dir(&pkg_name)?;
let path = dir.join(format!("{card_id}.toml"));
if path.exists() {
return Err(format!(
"alc.card.create: card '{card_id}' already exists (immutable)"
));
}
let toml_val = json_to_toml(input)?;
let text = toml::to_string_pretty(&toml_val)
.map_err(|e| format!("Failed to serialize card TOML: {e}"))?;
let tmp = path.with_extension("toml.tmp");
fs::write(&tmp, &text).map_err(|e| format!("Failed to write card tmp: {e}"))?;
fs::rename(&tmp, &path).map_err(|e| format!("Failed to rename card file: {e}"))?;
Ok((card_id, path))
}
fn find_card_path(card_id: &str) -> Result<Option<PathBuf>, String> {
validate_name(card_id, "card_id")?;
let root = cards_dir()?;
let entries = fs::read_dir(&root).map_err(|e| format!("Failed to read cards dir: {e}"))?;
for entry in entries.flatten() {
let p = entry.path();
if p.is_dir() {
let candidate = p.join(format!("{card_id}.toml"));
if candidate.exists() {
return Ok(Some(candidate));
}
}
}
Ok(None)
}
pub fn get(card_id: &str) -> Result<Option<Json>, String> {
let path = match find_card_path(card_id)? {
Some(p) => p,
None => return Ok(None),
};
let text =
fs::read_to_string(&path).map_err(|e| format!("Failed to read card '{card_id}': {e}"))?;
let val: toml::Value =
toml::from_str(&text).map_err(|e| format!("Failed to parse card '{card_id}': {e}"))?;
Ok(Some(toml_to_json(val)))
}
#[derive(Debug, Clone)]
pub struct Summary {
pub card_id: String,
pub pkg: String,
pub created_at: Option<String>,
pub model: Option<String>,
pub scenario: Option<String>,
pub pass_rate: Option<f64>,
}
impl Summary {
fn to_json(&self) -> Json {
let mut m = serde_json::Map::new();
m.insert("card_id".into(), json!(self.card_id));
m.insert("pkg".into(), json!(self.pkg));
if let Some(v) = &self.created_at {
m.insert("created_at".into(), json!(v));
}
if let Some(v) = &self.model {
m.insert("model".into(), json!(v));
}
if let Some(v) = &self.scenario {
m.insert("scenario".into(), json!(v));
}
if let Some(v) = self.pass_rate {
m.insert("pass_rate".into(), json!(v));
}
Json::Object(m)
}
}
fn summarize(path: &std::path::Path, pkg: &str) -> Option<Summary> {
let text = fs::read_to_string(path).ok()?;
let val: toml::Value = toml::from_str(&text).ok()?;
let card_id = val
.get("card_id")
.and_then(|v| v.as_str())
.or_else(|| path.file_stem().and_then(|s| s.to_str()))?
.to_string();
let created_at = val
.get("created_at")
.and_then(|v| v.as_str())
.map(String::from);
let model = val
.get("model")
.and_then(|m| m.get("id"))
.and_then(|v| v.as_str())
.map(String::from);
let scenario = val
.get("scenario")
.and_then(|s| s.get("name"))
.and_then(|v| v.as_str())
.map(String::from);
let pass_rate = val
.get("stats")
.and_then(|s| s.get("pass_rate"))
.and_then(|v| v.as_float());
Some(Summary {
card_id,
pkg: pkg.to_string(),
created_at,
model,
scenario,
pass_rate,
})
}
pub fn list(pkg_filter: Option<&str>) -> Result<Vec<Summary>, String> {
let root = cards_dir()?;
let mut out = Vec::new();
let pkg_dirs: Vec<PathBuf> = if let Some(p) = pkg_filter {
validate_name(p, "pkg")?;
let d = root.join(p);
if d.is_dir() {
vec![d]
} else {
vec![]
}
} else {
fs::read_dir(&root)
.map_err(|e| format!("Failed to read cards dir: {e}"))?
.flatten()
.map(|e| e.path())
.filter(|p| p.is_dir())
.collect()
};
for pdir in pkg_dirs {
let pkg = pdir
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("")
.to_string();
let entries = match fs::read_dir(&pdir) {
Ok(e) => e,
Err(_) => continue,
};
for entry in entries.flatten() {
let p = entry.path();
if p.extension().and_then(|s| s.to_str()) != Some("toml") {
continue;
}
if let Some(s) = summarize(&p, &pkg) {
out.push(s);
}
}
}
out.sort_by(|a, b| {
b.created_at
.cmp(&a.created_at)
.then_with(|| b.card_id.cmp(&a.card_id))
});
Ok(out)
}
pub fn summaries_to_json(rows: &[Summary]) -> Json {
Json::Array(rows.iter().map(|s| s.to_json()).collect())
}
pub fn append(card_id: &str, fields: Json) -> Result<Json, String> {
let path = find_card_path(card_id)?
.ok_or_else(|| format!("alc.card.append: card '{card_id}' not found"))?;
let fields_obj = match fields {
Json::Object(m) => m,
_ => return Err("alc.card.append: fields must be a table".into()),
};
let text =
fs::read_to_string(&path).map_err(|e| format!("Failed to read card '{card_id}': {e}"))?;
let existing: toml::Value =
toml::from_str(&text).map_err(|e| format!("Failed to parse card '{card_id}': {e}"))?;
let mut existing_json = toml_to_json(existing);
let existing_obj = existing_json
.as_object_mut()
.ok_or_else(|| format!("Card '{card_id}' is not a table"))?;
for (k, v) in fields_obj {
if existing_obj.contains_key(&k) {
return Err(format!(
"alc.card.append: key '{k}' already set on card '{card_id}' (immutable)"
));
}
if !v.is_null() {
existing_obj.insert(k, v);
}
}
let toml_val = json_to_toml(existing_json.clone())?;
let text = toml::to_string_pretty(&toml_val)
.map_err(|e| format!("Failed to serialize card TOML: {e}"))?;
let tmp = path.with_extension("toml.tmp");
fs::write(&tmp, &text).map_err(|e| format!("Failed to write card tmp: {e}"))?;
fs::rename(&tmp, &path).map_err(|e| format!("Failed to rename card file: {e}"))?;
Ok(existing_json)
}
fn aliases_path() -> Result<PathBuf, String> {
Ok(cards_dir()?.join("_aliases.toml"))
}
#[derive(Debug, Clone)]
pub struct Alias {
pub name: String,
pub card_id: String,
pub pkg: Option<String>,
pub set_at: String,
pub note: Option<String>,
}
impl Alias {
fn to_json(&self) -> Json {
let mut m = serde_json::Map::new();
m.insert("name".into(), json!(self.name));
m.insert("card_id".into(), json!(self.card_id));
if let Some(p) = &self.pkg {
m.insert("pkg".into(), json!(p));
}
m.insert("set_at".into(), json!(self.set_at));
if let Some(n) = &self.note {
m.insert("note".into(), json!(n));
}
Json::Object(m)
}
}
fn read_aliases() -> Result<Vec<Alias>, String> {
let path = aliases_path()?;
if !path.exists() {
return Ok(Vec::new());
}
let text =
fs::read_to_string(&path).map_err(|e| format!("Failed to read aliases file: {e}"))?;
let val: toml::Value =
toml::from_str(&text).map_err(|e| format!("Failed to parse aliases file: {e}"))?;
let arr = val
.get("alias")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
let mut out = Vec::with_capacity(arr.len());
for entry in arr {
let t = match entry {
toml::Value::Table(t) => t,
_ => continue,
};
let name = match t.get("name").and_then(|v| v.as_str()) {
Some(s) => s.to_string(),
None => continue,
};
let card_id = match t.get("card_id").and_then(|v| v.as_str()) {
Some(s) => s.to_string(),
None => continue,
};
out.push(Alias {
name,
card_id,
pkg: t.get("pkg").and_then(|v| v.as_str()).map(String::from),
set_at: t
.get("set_at")
.and_then(|v| v.as_str())
.map(String::from)
.unwrap_or_default(),
note: t.get("note").and_then(|v| v.as_str()).map(String::from),
});
}
Ok(out)
}
fn write_aliases(aliases: &[Alias]) -> Result<(), String> {
let path = aliases_path()?;
let mut arr = Vec::with_capacity(aliases.len());
for a in aliases {
let mut t = toml::map::Map::new();
t.insert("name".into(), toml::Value::String(a.name.clone()));
t.insert("card_id".into(), toml::Value::String(a.card_id.clone()));
if let Some(p) = &a.pkg {
t.insert("pkg".into(), toml::Value::String(p.clone()));
}
t.insert("set_at".into(), toml::Value::String(a.set_at.clone()));
if let Some(n) = &a.note {
t.insert("note".into(), toml::Value::String(n.clone()));
}
arr.push(toml::Value::Table(t));
}
let mut root = toml::map::Map::new();
root.insert("alias".into(), toml::Value::Array(arr));
let text = toml::to_string_pretty(&toml::Value::Table(root))
.map_err(|e| format!("Failed to serialize aliases: {e}"))?;
let tmp = path.with_extension("toml.tmp");
fs::write(&tmp, &text).map_err(|e| format!("Failed to write aliases tmp: {e}"))?;
fs::rename(&tmp, &path).map_err(|e| format!("Failed to rename aliases file: {e}"))?;
Ok(())
}
pub fn alias_set(
name: &str,
card_id: &str,
pkg: Option<&str>,
note: Option<&str>,
) -> Result<Alias, String> {
validate_name(name, "alias")?;
if find_card_path(card_id)?.is_none() {
return Err(format!("alc.card.alias_set: card '{card_id}' not found"));
}
let mut aliases = read_aliases()?;
aliases.retain(|a| a.name != name);
let entry = Alias {
name: name.to_string(),
card_id: card_id.to_string(),
pkg: pkg.map(String::from),
set_at: now_rfc3339(),
note: note.map(String::from),
};
aliases.push(entry.clone());
write_aliases(&aliases)?;
Ok(entry)
}
pub fn get_by_alias(name: &str) -> Result<Option<Json>, String> {
validate_name(name, "alias")?;
let aliases = read_aliases()?;
let Some(alias) = aliases.into_iter().find(|a| a.name == name) else {
return Ok(None);
};
match get(&alias.card_id)? {
Some(card) => Ok(Some(card)),
None => Err(format!(
"alc.card.get_by_alias: alias '{name}' points at missing card '{}'",
alias.card_id
)),
}
}
pub fn alias_list(pkg_filter: Option<&str>) -> Result<Vec<Alias>, String> {
let mut aliases = read_aliases()?;
if let Some(p) = pkg_filter {
aliases.retain(|a| a.pkg.as_deref() == Some(p));
}
Ok(aliases)
}
pub fn aliases_to_json(rows: &[Alias]) -> Json {
Json::Array(rows.iter().map(|a| a.to_json()).collect())
}
#[derive(Debug, Clone, PartialEq)]
pub enum CmpOp {
Eq,
Ne,
Lt,
Lte,
Gt,
Gte,
In,
Nin,
Exists,
Contains,
StartsWith,
}
impl CmpOp {
fn from_key(k: &str) -> Option<Self> {
Some(match k {
"eq" => Self::Eq,
"ne" => Self::Ne,
"lt" => Self::Lt,
"lte" => Self::Lte,
"gt" => Self::Gt,
"gte" => Self::Gte,
"in" => Self::In,
"nin" => Self::Nin,
"exists" => Self::Exists,
"contains" => Self::Contains,
"starts_with" => Self::StartsWith,
_ => return None,
})
}
}
#[derive(Debug, Clone)]
pub struct Comparison {
pub path: Vec<String>,
pub op: CmpOp,
pub value: Json,
}
#[derive(Debug, Clone)]
pub enum Predicate {
And(Vec<Predicate>),
Or(Vec<Predicate>),
Not(Box<Predicate>),
Cmp(Comparison),
}
fn is_operator_object(obj: &serde_json::Map<String, Json>) -> bool {
if obj.is_empty() {
return false;
}
obj.keys().all(|k| CmpOp::from_key(k).is_some())
}
pub fn parse_where(value: &Json) -> Result<Predicate, String> {
parse_predicate(value, &[])
}
fn parse_predicate(value: &Json, prefix: &[String]) -> Result<Predicate, String> {
let obj = value
.as_object()
.ok_or_else(|| "where clause must be a table".to_string())?;
let mut clauses: Vec<Predicate> = Vec::new();
for (key, val) in obj {
match key.as_str() {
"_and" => {
let arr = val
.as_array()
.ok_or_else(|| "_and must be an array of sub-predicates".to_string())?;
let mut subs = Vec::with_capacity(arr.len());
for sub in arr {
subs.push(parse_predicate(sub, prefix)?);
}
clauses.push(Predicate::And(subs));
}
"_or" => {
let arr = val
.as_array()
.ok_or_else(|| "_or must be an array of sub-predicates".to_string())?;
let mut subs = Vec::with_capacity(arr.len());
for sub in arr {
subs.push(parse_predicate(sub, prefix)?);
}
clauses.push(Predicate::Or(subs));
}
"_not" => {
clauses.push(Predicate::Not(Box::new(parse_predicate(val, prefix)?)));
}
_ => {
let mut new_path = prefix.to_vec();
new_path.push(key.clone());
match val {
Json::Object(m) if is_operator_object(m) => {
for (op_key, op_val) in m {
let op = CmpOp::from_key(op_key).expect("validated above");
clauses.push(Predicate::Cmp(Comparison {
path: new_path.clone(),
op,
value: op_val.clone(),
}));
}
}
Json::Object(_) => {
clauses.push(parse_predicate(val, &new_path)?);
}
_ => {
clauses.push(Predicate::Cmp(Comparison {
path: new_path,
op: CmpOp::Eq,
value: val.clone(),
}));
}
}
}
}
}
if clauses.len() == 1 {
Ok(clauses.remove(0))
} else {
Ok(Predicate::And(clauses))
}
}
fn fetch_path<'a>(card: &'a Json, path: &[String]) -> Option<&'a Json> {
let mut node = card;
for key in path {
let obj = node.as_object()?;
node = obj.get(key)?;
}
Some(node)
}
fn json_cmp(a: &Json, b: &Json) -> Option<std::cmp::Ordering> {
match (a, b) {
(Json::Number(x), Json::Number(y)) => {
let xf = x.as_f64()?;
let yf = y.as_f64()?;
xf.partial_cmp(&yf)
}
(Json::String(x), Json::String(y)) => Some(x.cmp(y)),
(Json::Bool(x), Json::Bool(y)) => Some(x.cmp(y)),
_ => None,
}
}
fn json_eq(a: &Json, b: &Json) -> bool {
match (a, b) {
(Json::Number(x), Json::Number(y)) => match (x.as_f64(), y.as_f64()) {
(Some(xf), Some(yf)) => xf == yf,
_ => a == b,
},
_ => a == b,
}
}
fn eval_cmp(cmp: &Comparison, card: &Json) -> bool {
let actual = fetch_path(card, &cmp.path);
let exists = actual.is_some();
match cmp.op {
CmpOp::Exists => {
let want = cmp.value.as_bool().unwrap_or(true);
exists == want
}
CmpOp::Ne => match actual {
None => true,
Some(v) => !json_eq(v, &cmp.value),
},
CmpOp::Nin => match actual {
None => true,
Some(v) => match cmp.value.as_array() {
Some(arr) => !arr.iter().any(|e| json_eq(e, v)),
None => false,
},
},
CmpOp::Eq => actual.is_some_and(|v| json_eq(v, &cmp.value)),
CmpOp::In => actual.is_some_and(|v| match cmp.value.as_array() {
Some(arr) => arr.iter().any(|e| json_eq(e, v)),
None => false,
}),
CmpOp::Lt | CmpOp::Lte | CmpOp::Gt | CmpOp::Gte => {
let Some(v) = actual else { return false };
let Some(ord) = json_cmp(v, &cmp.value) else {
return false;
};
use std::cmp::Ordering::{Equal, Greater, Less};
matches!(
(&cmp.op, ord),
(CmpOp::Lt, Less)
| (CmpOp::Lte, Less | Equal)
| (CmpOp::Gt, Greater)
| (CmpOp::Gte, Greater | Equal)
)
}
CmpOp::Contains => {
let Some(Json::String(haystack)) = actual else {
return false;
};
let Some(needle) = cmp.value.as_str() else {
return false;
};
haystack.contains(needle)
}
CmpOp::StartsWith => {
let Some(Json::String(haystack)) = actual else {
return false;
};
let Some(needle) = cmp.value.as_str() else {
return false;
};
haystack.starts_with(needle)
}
}
}
pub fn eval_predicate(pred: &Predicate, card: &Json) -> bool {
match pred {
Predicate::And(subs) => subs.iter().all(|p| eval_predicate(p, card)),
Predicate::Or(subs) => subs.iter().any(|p| eval_predicate(p, card)),
Predicate::Not(sub) => !eval_predicate(sub, card),
Predicate::Cmp(c) => eval_cmp(c, card),
}
}
#[derive(Debug, Clone)]
pub struct OrderKey {
pub path: Vec<String>,
pub desc: bool,
}
impl OrderKey {
fn parse(raw: &str) -> Result<Self, String> {
if raw.is_empty() {
return Err("order_by key must not be empty".into());
}
let (desc, rest) = if let Some(r) = raw.strip_prefix('-') {
(true, r)
} else {
(false, raw)
};
let path: Vec<String> = rest.split('.').map(|s| s.to_string()).collect();
if path.iter().any(|p| p.is_empty()) {
return Err(format!("invalid order_by key: '{raw}'"));
}
Ok(Self { path, desc })
}
}
pub fn parse_order_by(value: &Json) -> Result<Vec<OrderKey>, String> {
match value {
Json::String(s) => Ok(vec![OrderKey::parse(s)?]),
Json::Array(arr) => {
let mut out = Vec::with_capacity(arr.len());
for v in arr {
let s = v
.as_str()
.ok_or_else(|| "order_by array must contain strings".to_string())?;
out.push(OrderKey::parse(s)?);
}
Ok(out)
}
_ => Err("order_by must be a string or array of strings".into()),
}
}
#[derive(Debug, Default, Clone)]
pub struct FindQuery {
pub pkg: Option<String>,
pub where_: Option<Predicate>,
pub order_by: Vec<OrderKey>,
pub limit: Option<usize>,
pub offset: Option<usize>,
}
#[derive(Debug, Clone)]
struct CardRow {
full: Json,
summary: Summary,
}
fn load_full(path: &std::path::Path, pkg: &str) -> Option<CardRow> {
let text = fs::read_to_string(path).ok()?;
let val: toml::Value = toml::from_str(&text).ok()?;
let json = toml_to_json(val);
let card_id = json
.get("card_id")
.and_then(|v| v.as_str())
.or_else(|| path.file_stem().and_then(|s| s.to_str()))?
.to_string();
let created_at = json
.get("created_at")
.and_then(|v| v.as_str())
.map(String::from);
let model = json
.get("model")
.and_then(|m| m.get("id"))
.and_then(|v| v.as_str())
.map(String::from);
let scenario = json
.get("scenario")
.and_then(|s| s.get("name"))
.and_then(|v| v.as_str())
.map(String::from);
let pass_rate = json
.get("stats")
.and_then(|s| s.get("pass_rate"))
.and_then(|v| v.as_f64());
Some(CardRow {
full: json,
summary: Summary {
card_id,
pkg: pkg.to_string(),
created_at,
model,
scenario,
pass_rate,
},
})
}
fn order_cards(a: &CardRow, b: &CardRow, keys: &[OrderKey]) -> std::cmp::Ordering {
use std::cmp::Ordering;
for k in keys {
let va = fetch_path(&a.full, &k.path);
let vb = fetch_path(&b.full, &k.path);
let ord = match (va, vb) {
(None, None) => Ordering::Equal,
(None, Some(_)) => Ordering::Greater, (Some(_), None) => Ordering::Less,
(Some(x), Some(y)) => json_cmp(x, y).unwrap_or(Ordering::Equal),
};
let ord = if k.desc { ord.reverse() } else { ord };
if ord != Ordering::Equal {
return ord;
}
}
Ordering::Equal
}
const SUMMARY_SORT_FIELDS: &[&str] = &[
"card_id",
"created_at",
"stats.pass_rate",
"scenario.name",
"model.id",
];
fn is_lightweight_query(q: &FindQuery) -> bool {
q.where_.is_none()
&& q.order_by
.iter()
.all(|k| SUMMARY_SORT_FIELDS.contains(&k.path.join(".").as_str()))
}
fn order_summaries(a: &Summary, b: &Summary, keys: &[OrderKey]) -> std::cmp::Ordering {
use std::cmp::Ordering;
for k in keys {
let key_str = k.path.join(".");
let ord = match key_str.as_str() {
"card_id" => a.card_id.cmp(&b.card_id),
"created_at" => a.created_at.cmp(&b.created_at),
"stats.pass_rate" => match (a.pass_rate, b.pass_rate) {
(None, None) => Ordering::Equal,
(None, Some(_)) => Ordering::Greater,
(Some(_), None) => Ordering::Less,
(Some(x), Some(y)) => x.partial_cmp(&y).unwrap_or(Ordering::Equal),
},
"scenario.name" => a.scenario.cmp(&b.scenario),
"model.id" => a.model.cmp(&b.model),
_ => Ordering::Equal,
};
let ord = if k.desc { ord.reverse() } else { ord };
if ord != Ordering::Equal {
return ord;
}
}
Ordering::Equal
}
pub fn find(q: FindQuery) -> Result<Vec<Summary>, String> {
if is_lightweight_query(&q) {
let mut rows = list(q.pkg.as_deref())?;
if q.order_by.is_empty() {
rows.sort_by(|a, b| {
b.created_at
.cmp(&a.created_at)
.then_with(|| b.card_id.cmp(&a.card_id))
});
} else {
rows.sort_by(|a, b| order_summaries(a, b, &q.order_by));
}
let out: Vec<Summary> = rows
.into_iter()
.skip(q.offset.unwrap_or(0))
.take(q.limit.unwrap_or(usize::MAX))
.collect();
return Ok(out);
}
let root = cards_dir()?;
let pkg_dirs: Vec<PathBuf> = if let Some(p) = q.pkg.as_deref() {
validate_name(p, "pkg")?;
let d = root.join(p);
if d.is_dir() {
vec![d]
} else {
return Ok(Vec::new());
}
} else {
fs::read_dir(&root)
.map_err(|e| format!("Failed to read cards dir: {e}"))?
.flatten()
.map(|e| e.path())
.filter(|p| p.is_dir())
.collect()
};
let all_rows = scan_pkg_dirs(&pkg_dirs)?;
let mut rows: Vec<CardRow> = if let Some(pred) = &q.where_ {
all_rows
.into_iter()
.filter(|row| eval_predicate(pred, &row.full))
.collect()
} else {
all_rows
};
if q.order_by.is_empty() {
rows.sort_by(|a, b| {
b.summary
.created_at
.cmp(&a.summary.created_at)
.then_with(|| b.summary.card_id.cmp(&a.summary.card_id))
});
} else {
rows.sort_by(|a, b| order_cards(a, b, &q.order_by));
}
let out: Vec<Summary> = rows
.into_iter()
.skip(q.offset.unwrap_or(0))
.take(q.limit.unwrap_or(usize::MAX))
.map(|r| r.summary)
.collect();
Ok(out)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LineageDirection {
#[default]
Up,
Down,
Both,
}
impl LineageDirection {
pub fn parse(s: &str) -> Result<Self, String> {
match s {
"up" => Ok(Self::Up),
"down" => Ok(Self::Down),
"both" => Ok(Self::Both),
other => Err(format!(
"direction must be 'up', 'down', or 'both' (got '{other}')"
)),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct LineageQuery {
pub card_id: String,
pub direction: LineageDirection,
pub depth: Option<usize>,
pub include_stats: bool,
pub relation_filter: Option<Vec<String>>,
}
#[derive(Debug, Clone)]
pub struct LineageNode {
pub card_id: String,
pub pkg: String,
pub prior_card_id: Option<String>,
pub prior_relation: Option<String>,
pub depth: i32,
pub stats: Option<Json>,
}
#[derive(Debug, Clone)]
pub struct LineageEdge {
pub from: String,
pub to: String,
pub relation: Option<String>,
}
#[derive(Debug, Clone)]
pub struct LineageResult {
pub root: String,
pub nodes: Vec<LineageNode>,
pub edges: Vec<LineageEdge>,
pub truncated: bool,
}
const DEFAULT_LINEAGE_DEPTH: usize = 10;
fn lineage_fields(card: &Json) -> (Option<String>, Option<String>) {
let meta = card.get("metadata");
let prior_card_id = meta
.and_then(|m| m.get("prior_card_id"))
.and_then(|v| v.as_str())
.map(String::from);
let prior_relation = meta
.and_then(|m| m.get("prior_relation"))
.and_then(|v| v.as_str())
.map(String::from);
(prior_card_id, prior_relation)
}
fn make_node(row: &CardRow, depth: i32, include_stats: bool) -> LineageNode {
let (prior_card_id, prior_relation) = lineage_fields(&row.full);
let stats = if include_stats {
row.full.get("stats").cloned()
} else {
None
};
LineageNode {
card_id: row.summary.card_id.clone(),
pkg: row.summary.pkg.clone(),
prior_card_id,
prior_relation,
depth,
stats,
}
}
fn relation_passes(filter: &Option<Vec<String>>, relation: &Option<String>) -> bool {
match filter {
None => true,
Some(allowed) => match relation {
Some(r) => allowed.iter().any(|a| a == r),
None => false,
},
}
}
struct CardIndex {
cards: std::collections::HashMap<String, CardRow>,
children: std::collections::HashMap<String, Vec<String>>,
}
fn load_card_index() -> Result<CardIndex, String> {
let root = cards_dir()?;
let rows = scan_all_cards(&root)?;
let mut cards = std::collections::HashMap::with_capacity(rows.len());
let mut children: std::collections::HashMap<String, Vec<String>> =
std::collections::HashMap::new();
for row in rows {
let id = row.summary.card_id.clone();
let (prior_id, _) = lineage_fields(&row.full);
if let Some(parent) = prior_id {
children.entry(parent).or_default().push(id.clone());
}
cards.insert(id, row);
}
Ok(CardIndex { cards, children })
}
fn scan_all_cards(root: &std::path::Path) -> Result<Vec<CardRow>, String> {
let pkg_dirs: Vec<PathBuf> = fs::read_dir(root)
.map_err(|e| format!("Failed to read cards dir: {e}"))?
.flatten()
.map(|e| e.path())
.filter(|p| p.is_dir())
.collect();
scan_pkg_dirs(&pkg_dirs)
}
fn scan_pkg_dirs(pkg_dirs: &[PathBuf]) -> Result<Vec<CardRow>, String> {
let mut rows = Vec::new();
for pdir in pkg_dirs {
let pkg = pdir
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("")
.to_string();
let entries = match fs::read_dir(pdir) {
Ok(e) => e,
Err(_) => continue,
};
for entry in entries.flatten() {
let p = entry.path();
if p.extension().and_then(|s| s.to_str()) != Some("toml") {
continue;
}
if let Some(row) = load_full(&p, &pkg) {
rows.push(row);
}
}
}
Ok(rows)
}
struct LineageCtx<'a> {
index: &'a CardIndex,
relation_filter: &'a Option<Vec<String>>,
include_stats: bool,
max_depth: usize,
}
struct LineageAccum {
nodes: Vec<LineageNode>,
edges: Vec<LineageEdge>,
visited: std::collections::HashSet<String>,
truncated: bool,
}
fn walk_up(start_id: &str, ctx: &LineageCtx<'_>, acc: &mut LineageAccum) {
let mut cur = start_id.to_string();
for step in 1..=ctx.max_depth {
let Some(row) = ctx.index.cards.get(&cur) else {
return;
};
let (prior_id, prior_rel) = lineage_fields(&row.full);
let Some(prior_id) = prior_id else {
return;
};
if !relation_passes(ctx.relation_filter, &prior_rel) {
return;
}
if acc.visited.contains(&prior_id) {
return;
}
let Some(parent) = ctx.index.cards.get(&prior_id) else {
return;
};
acc.nodes
.push(make_node(parent, -(step as i32), ctx.include_stats));
acc.edges.push(LineageEdge {
from: row.summary.card_id.clone(),
to: parent.summary.card_id.clone(),
relation: prior_rel,
});
acc.visited.insert(prior_id.clone());
cur = prior_id;
}
if let Some(row) = ctx.index.cards.get(&cur) {
let (prior_id, _) = lineage_fields(&row.full);
if prior_id
.as_ref()
.is_some_and(|p| ctx.index.cards.contains_key(p) && !acc.visited.contains(p))
{
acc.truncated = true;
}
}
}
fn walk_down(start_id: &str, ctx: &LineageCtx<'_>, acc: &mut LineageAccum) {
let mut frontier: Vec<String> = vec![start_id.to_string()];
for depth in 1..=ctx.max_depth {
let mut next_frontier: Vec<String> = Vec::new();
for parent_id in &frontier {
let children = match ctx.index.children.get(parent_id) {
Some(c) => c,
None => continue,
};
for child_id in children {
if acc.visited.contains(child_id) {
continue;
}
let Some(child) = ctx.index.cards.get(child_id) else {
continue;
};
let (_, prior_rel) = lineage_fields(&child.full);
if !relation_passes(ctx.relation_filter, &prior_rel) {
continue;
}
acc.nodes
.push(make_node(child, depth as i32, ctx.include_stats));
acc.edges.push(LineageEdge {
from: child.summary.card_id.clone(),
to: parent_id.clone(),
relation: prior_rel,
});
acc.visited.insert(child_id.clone());
next_frontier.push(child_id.clone());
}
}
if next_frontier.is_empty() {
return;
}
frontier = next_frontier;
}
for parent_id in &frontier {
let children = match ctx.index.children.get(parent_id) {
Some(c) => c,
None => continue,
};
for child_id in children {
if acc.visited.contains(child_id) {
continue;
}
let Some(child) = ctx.index.cards.get(child_id) else {
continue;
};
let (_, prior_rel) = lineage_fields(&child.full);
if relation_passes(ctx.relation_filter, &prior_rel) {
acc.truncated = true;
return;
}
}
}
}
pub fn lineage(q: LineageQuery) -> Result<Option<LineageResult>, String> {
let index = load_card_index()?;
let Some(root_row) = index.cards.get(&q.card_id) else {
return Ok(None);
};
let ctx = LineageCtx {
index: &index,
relation_filter: &q.relation_filter,
include_stats: q.include_stats,
max_depth: q.depth.unwrap_or(DEFAULT_LINEAGE_DEPTH),
};
let mut acc = LineageAccum {
nodes: Vec::new(),
edges: Vec::new(),
visited: std::collections::HashSet::new(),
truncated: false,
};
acc.nodes.push(make_node(root_row, 0, q.include_stats));
acc.visited.insert(q.card_id.clone());
if matches!(q.direction, LineageDirection::Up | LineageDirection::Both) {
walk_up(&q.card_id, &ctx, &mut acc);
}
if matches!(q.direction, LineageDirection::Down | LineageDirection::Both) {
walk_down(&q.card_id, &ctx, &mut acc);
}
Ok(Some(LineageResult {
root: q.card_id,
nodes: acc.nodes,
edges: acc.edges,
truncated: acc.truncated,
}))
}
pub fn lineage_to_json(r: &LineageResult) -> Json {
let nodes: Vec<Json> = r
.nodes
.iter()
.map(|n| {
let mut m = serde_json::Map::new();
m.insert("card_id".into(), json!(n.card_id));
m.insert("pkg".into(), json!(n.pkg));
m.insert("depth".into(), json!(n.depth));
if let Some(p) = &n.prior_card_id {
m.insert("prior_card_id".into(), json!(p));
}
if let Some(rel) = &n.prior_relation {
m.insert("prior_relation".into(), json!(rel));
}
if let Some(s) = &n.stats {
m.insert("stats".into(), s.clone());
}
Json::Object(m)
})
.collect();
let edges: Vec<Json> = r
.edges
.iter()
.map(|e| {
let mut m = serde_json::Map::new();
m.insert("from".into(), json!(e.from));
m.insert("to".into(), json!(e.to));
if let Some(rel) = &e.relation {
m.insert("relation".into(), json!(rel));
}
Json::Object(m)
})
.collect();
json!({
"root": r.root,
"nodes": nodes,
"edges": edges,
"truncated": r.truncated,
})
}
pub fn import_from_dir(
source_dir: &std::path::Path,
pkg: &str,
) -> Result<(Vec<String>, Vec<String>), String> {
validate_name(pkg, "pkg")?;
let dest = pkg_dir(pkg)?;
let mut imported = Vec::new();
let mut skipped = Vec::new();
let entries =
fs::read_dir(source_dir).map_err(|e| format!("Failed to read card source dir: {e}"))?;
for entry in entries.flatten() {
let path = entry.path();
let fname = match path.file_name().and_then(|n| n.to_str()) {
Some(n) => n.to_string(),
None => continue,
};
if !fname.ends_with(".toml") {
continue;
}
let card_id = fname.trim_end_matches(".toml");
let dest_toml = dest.join(&fname);
if dest_toml.exists() {
skipped.push(card_id.to_string());
continue;
}
let text = fs::read_to_string(&path)
.map_err(|e| format!("Failed to read card file '{fname}': {e}"))?;
let val: toml::Value = toml::from_str(&text)
.map_err(|e| format!("Failed to parse card file '{fname}': {e}"))?;
if val.get("schema_version").and_then(|v| v.as_str()) != Some(SCHEMA_VERSION) {
continue; }
fs::copy(&path, &dest_toml).map_err(|e| format!("Failed to copy card '{fname}': {e}"))?;
let samples_name = format!("{card_id}.samples.jsonl");
let samples_src = source_dir.join(&samples_name);
if samples_src.exists() {
let samples_dest = dest.join(&samples_name);
if !samples_dest.exists() {
fs::copy(&samples_src, &samples_dest)
.map_err(|e| format!("Failed to copy samples '{samples_name}': {e}"))?;
}
}
imported.push(card_id.to_string());
}
Ok((imported, skipped))
}
fn samples_path(card_id: &str) -> Result<PathBuf, String> {
let card_path =
find_card_path(card_id)?.ok_or_else(|| format!("card '{card_id}' not found"))?;
let dir = card_path
.parent()
.ok_or_else(|| format!("card '{card_id}' has no parent directory"))?;
Ok(dir.join(format!("{card_id}.samples.jsonl")))
}
pub fn write_samples(card_id: &str, samples: Vec<Json>) -> Result<PathBuf, String> {
let path = samples_path(card_id)?;
if path.exists() {
return Err(format!(
"alc.card.write_samples: samples already exist for card '{card_id}' (write-once)"
));
}
let mut buf = String::new();
for (idx, s) in samples.iter().enumerate() {
let line = serde_json::to_string(s).map_err(|e| {
format!("alc.card.write_samples: failed to serialize sample #{idx}: {e}")
})?;
buf.push_str(&line);
buf.push('\n');
}
let tmp = path.with_extension("jsonl.tmp");
fs::write(&tmp, &buf).map_err(|e| format!("Failed to write samples tmp: {e}"))?;
fs::rename(&tmp, &path).map_err(|e| format!("Failed to rename samples file: {e}"))?;
Ok(path)
}
#[derive(Debug, Default, Clone)]
pub struct SamplesQuery {
pub offset: usize,
pub limit: Option<usize>,
pub where_: Option<Predicate>,
}
pub fn read_samples(card_id: &str, q: SamplesQuery) -> Result<Vec<Json>, String> {
let path = samples_path(card_id)?;
if !path.exists() {
return Ok(Vec::new());
}
let text =
fs::read_to_string(&path).map_err(|e| format!("Failed to read samples file: {e}"))?;
let mut matched: usize = 0;
let mut out = Vec::new();
for (i, line) in text.lines().enumerate() {
if line.trim().is_empty() {
continue;
}
let val: Json = serde_json::from_str(line)
.map_err(|e| format!("Failed to parse sample line {i}: {e}"))?;
if let Some(pred) = &q.where_ {
if !eval_predicate(pred, &val) {
continue;
}
}
if matched < q.offset {
matched += 1;
continue;
}
if let Some(lim) = q.limit {
if out.len() >= lim {
break;
}
}
matched += 1;
out.push(val);
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
fn unique_pkg() -> String {
let ns = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
format!("_test_card_{ns}")
}
fn cleanup(pkg: &str) {
if let Ok(d) = pkg_dir(pkg) {
let _ = fs::remove_dir_all(&d);
}
}
#[test]
fn minimum_valid_card() {
let pkg = unique_pkg();
let input = json!({ "pkg": { "name": pkg } });
let (id, path) = create(input).unwrap();
assert!(path.exists());
assert!(id.starts_with(&pkg));
let got = get(&id).unwrap().unwrap();
assert_eq!(got["schema_version"], json!(SCHEMA_VERSION));
assert_eq!(got["card_id"], json!(id));
assert_eq!(got["pkg"]["name"], json!(pkg));
assert!(got.get("created_at").is_some());
assert!(got.get("created_by").is_some());
cleanup(&pkg);
}
#[test]
fn create_rejects_missing_pkg_name() {
let err = create(json!({})).unwrap_err();
assert!(err.contains("pkg.name"));
}
#[test]
fn create_is_immutable() {
let pkg = unique_pkg();
let input = json!({
"card_id": "fixed_id_001",
"pkg": { "name": pkg }
});
create(input.clone()).unwrap();
let err = create(input).unwrap_err();
assert!(err.contains("already exists"));
cleanup(&pkg);
}
#[test]
fn create_injects_param_fingerprint() {
let pkg = unique_pkg();
let input = json!({
"pkg": { "name": pkg },
"params": { "depth": 3, "temperature": 0.0 }
});
let (id, _) = create(input).unwrap();
let got = get(&id).unwrap().unwrap();
assert!(got["param_fingerprint"].is_string());
cleanup(&pkg);
}
#[test]
fn list_returns_newest_first() {
let pkg = unique_pkg();
let (id1, _) = create(json!({
"card_id": format!("{pkg}_a"),
"pkg": { "name": pkg },
"created_at": "2025-01-01T00:00:00Z"
}))
.unwrap();
let (id2, _) = create(json!({
"card_id": format!("{pkg}_b"),
"pkg": { "name": pkg },
"created_at": "2026-01-01T00:00:00Z"
}))
.unwrap();
let rows = list(Some(&pkg)).unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(rows[0].card_id, id2); assert_eq!(rows[1].card_id, id1);
cleanup(&pkg);
}
#[test]
fn list_extracts_summary_fields() {
let pkg = unique_pkg();
let (id, _) = create(json!({
"pkg": { "name": pkg },
"model": { "id": "claude-opus-4-6" },
"scenario": { "name": "gsm8k_sample100" },
"stats": { "pass_rate": 0.82 }
}))
.unwrap();
let rows = list(Some(&pkg)).unwrap();
let row = rows.iter().find(|r| r.card_id == id).unwrap();
assert_eq!(row.model.as_deref(), Some("claude-opus-4-6"));
assert_eq!(row.scenario.as_deref(), Some("gsm8k_sample100"));
assert_eq!(row.pass_rate, Some(0.82));
cleanup(&pkg);
}
#[test]
fn get_missing_returns_none() {
assert!(get("does_not_exist_xyz").unwrap().is_none());
}
#[test]
fn card_id_embeds_compact_timestamp() {
let pkg = unique_pkg();
let (id, _) = create(json!({ "pkg": { "name": pkg } })).unwrap();
let tail = id.strip_prefix(&format!("{pkg}_")).unwrap();
let parts: Vec<&str> = tail.split('_').collect();
assert_eq!(parts.len(), 3, "unexpected card_id shape: {id}");
let ts = parts[1];
assert_eq!(ts.len(), 15, "timestamp segment wrong length: {ts}");
assert!(ts.chars().nth(8) == Some('T'), "missing T separator: {ts}");
cleanup(&pkg);
}
#[test]
fn now_compact_format() {
let s = now_compact();
assert_eq!(s.len(), 15);
assert_eq!(s.chars().nth(8), Some('T'));
for (i, c) in s.chars().enumerate() {
if i != 8 {
assert!(c.is_ascii_digit(), "non-digit at pos {i}: {s}");
}
}
}
#[test]
fn short_model_variants() {
assert_eq!(short_model("claude-opus-4-6"), "opus46");
assert_eq!(short_model("gpt-4o"), "4o");
assert_eq!(short_model(""), "model");
}
#[test]
fn two_cards_same_second_different_stats_get_distinct_ids() {
let pkg = unique_pkg();
let input1 = json!({
"pkg": { "name": pkg },
"scenario": { "name": "gsm8k" },
"stats": { "pass_rate": 0.4 }
});
let input2 = json!({
"pkg": { "name": pkg },
"scenario": { "name": "gsm8k" },
"stats": { "pass_rate": 0.9 }
});
let (id1, _) = create(input1).unwrap();
let (id2, _) = create(input2).unwrap();
assert_ne!(id1, id2, "distinct stats must yield distinct card_ids");
cleanup(&pkg);
}
#[test]
fn append_adds_new_fields() {
let pkg = unique_pkg();
let (id, _) = create(json!({
"pkg": { "name": pkg },
"stats": { "pass_rate": 0.5 }
}))
.unwrap();
let merged = append(
&id,
json!({
"caveats": { "notes": "rescored after fix" },
"metadata": { "reviewer": "yn" }
}),
)
.unwrap();
assert_eq!(merged["caveats"]["notes"], json!("rescored after fix"));
assert_eq!(merged["metadata"]["reviewer"], json!("yn"));
let got = get(&id).unwrap().unwrap();
assert_eq!(got["caveats"]["notes"], json!("rescored after fix"));
assert_eq!(got["stats"]["pass_rate"], json!(0.5));
cleanup(&pkg);
}
#[test]
fn append_rejects_existing_key() {
let pkg = unique_pkg();
let (id, _) = create(json!({
"pkg": { "name": pkg },
"stats": { "pass_rate": 0.5 }
}))
.unwrap();
let err = append(&id, json!({ "stats": { "pass_rate": 0.9 } })).unwrap_err();
assert!(err.contains("already set"), "got: {err}");
let got = get(&id).unwrap().unwrap();
assert_eq!(got["stats"]["pass_rate"], json!(0.5));
cleanup(&pkg);
}
#[test]
fn append_errors_on_missing_card() {
let err = append("does_not_exist_xyz", json!({ "x": 1 })).unwrap_err();
assert!(err.contains("not found"));
}
#[test]
fn alias_set_and_list_roundtrip() {
let pkg = unique_pkg();
let (id, _) = create(json!({ "pkg": { "name": pkg } })).unwrap();
let alias_name = format!("test_alias_{}", &pkg);
alias_set(&alias_name, &id, Some(&pkg), Some("smoke")).unwrap();
let rows = alias_list(Some(&pkg)).unwrap();
let a = rows.iter().find(|a| a.name == alias_name).unwrap();
assert_eq!(a.card_id, id);
assert_eq!(a.pkg.as_deref(), Some(pkg.as_str()));
assert_eq!(a.note.as_deref(), Some("smoke"));
assert!(!a.set_at.is_empty());
let (id2, _) = create(json!({
"card_id": format!("{pkg}_b"),
"pkg": { "name": pkg }
}))
.unwrap();
alias_set(&alias_name, &id2, Some(&pkg), None).unwrap();
let rows = alias_list(Some(&pkg)).unwrap();
let matching: Vec<&Alias> = rows.iter().filter(|a| a.name == alias_name).collect();
assert_eq!(matching.len(), 1, "alias should be unique by name");
assert_eq!(matching[0].card_id, id2);
let remaining: Vec<Alias> = read_aliases()
.unwrap()
.into_iter()
.filter(|a| a.name != alias_name)
.collect();
write_aliases(&remaining).unwrap();
cleanup(&pkg);
}
#[test]
fn alias_set_rejects_unknown_card() {
let err = alias_set("x", "does_not_exist_xyz", None, None).unwrap_err();
assert!(err.contains("not found"));
}
fn where_from(v: Json) -> Predicate {
parse_where(&v).expect("parse where")
}
fn order_from(v: Json) -> Vec<OrderKey> {
parse_order_by(&v).expect("parse order_by")
}
#[test]
fn find_where_nested_eq_and_gte() {
let pkg = unique_pkg();
create(json!({
"card_id": format!("{pkg}_low"),
"pkg": { "name": pkg },
"scenario": { "name": "gsm8k" },
"stats": { "pass_rate": 0.4 }
}))
.unwrap();
create(json!({
"card_id": format!("{pkg}_high"),
"pkg": { "name": pkg },
"scenario": { "name": "gsm8k" },
"stats": { "pass_rate": 0.9 }
}))
.unwrap();
create(json!({
"card_id": format!("{pkg}_other"),
"pkg": { "name": pkg },
"scenario": { "name": "other" },
"stats": { "pass_rate": 1.0 }
}))
.unwrap();
let rows = find(FindQuery {
pkg: Some(pkg.clone()),
where_: Some(where_from(json!({
"scenario": { "name": "gsm8k" },
}))),
order_by: order_from(json!("-stats.pass_rate")),
..Default::default()
})
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(rows[0].pass_rate, Some(0.9));
assert_eq!(rows[1].pass_rate, Some(0.4));
let rows = find(FindQuery {
pkg: Some(pkg.clone()),
where_: Some(where_from(json!({
"stats": { "pass_rate": { "gte": 0.8 } },
}))),
order_by: order_from(json!("-stats.pass_rate")),
..Default::default()
})
.unwrap();
assert_eq!(rows.len(), 2);
assert!(rows.iter().all(|r| r.pass_rate.unwrap() >= 0.8));
let rows = find(FindQuery {
pkg: Some(pkg.clone()),
order_by: order_from(json!("-stats.pass_rate")),
limit: Some(1),
..Default::default()
})
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].pass_rate, Some(1.0));
cleanup(&pkg);
}
#[test]
fn find_where_implicit_eq_and_logical() {
let pkg = unique_pkg();
create(json!({
"card_id": format!("{pkg}_a"),
"pkg": { "name": pkg },
"model": { "id": "claude-opus-4-6" },
"stats": { "equilibrium_position": "dead", "survival_rate": 0.0 }
}))
.unwrap();
create(json!({
"card_id": format!("{pkg}_b"),
"pkg": { "name": pkg },
"model": { "id": "claude-opus-4-6" },
"stats": { "equilibrium_position": "niche_leader", "survival_rate": 1.0 }
}))
.unwrap();
create(json!({
"card_id": format!("{pkg}_c"),
"pkg": { "name": pkg },
"model": { "id": "claude-haiku-4-5-20251001" },
"stats": { "equilibrium_position": "fragile", "survival_rate": 0.2 }
}))
.unwrap();
let rows = find(FindQuery {
pkg: Some(pkg.clone()),
where_: Some(where_from(json!({
"stats": { "equilibrium_position": "dead" },
}))),
..Default::default()
})
.unwrap();
assert_eq!(rows.len(), 1);
assert!(rows[0].card_id.ends_with("_a"));
let rows = find(FindQuery {
pkg: Some(pkg.clone()),
where_: Some(where_from(json!({
"_or": [
{ "stats": { "equilibrium_position": "dead" } },
{ "stats": { "survival_rate": { "gte": 0.9 } } },
],
}))),
..Default::default()
})
.unwrap();
assert_eq!(rows.len(), 2);
let rows = find(FindQuery {
pkg: Some(pkg.clone()),
where_: Some(where_from(json!({
"_not": { "model": { "id": "claude-haiku-4-5-20251001" } },
}))),
..Default::default()
})
.unwrap();
assert_eq!(rows.len(), 2);
let rows = find(FindQuery {
pkg: Some(pkg.clone()),
where_: Some(where_from(json!({
"stats": {
"equilibrium_position": { "in": ["dead", "fragile"] },
},
}))),
..Default::default()
})
.unwrap();
assert_eq!(rows.len(), 2);
let rows = find(FindQuery {
pkg: Some(pkg.clone()),
where_: Some(where_from(json!({
"strategy_params": { "temperature": { "exists": false } },
}))),
..Default::default()
})
.unwrap();
assert_eq!(rows.len(), 3, "none of the cards have strategy_params");
cleanup(&pkg);
}
#[test]
fn find_order_by_multi_key() {
let pkg = unique_pkg();
create(json!({
"card_id": format!("{pkg}_a"),
"pkg": { "name": pkg },
"stats": { "pass_rate": 0.5 }
}))
.unwrap();
create(json!({
"card_id": format!("{pkg}_b"),
"pkg": { "name": pkg },
"stats": { "pass_rate": 0.9 }
}))
.unwrap();
create(json!({
"card_id": format!("{pkg}_c"),
"pkg": { "name": pkg },
"stats": { "pass_rate": 0.9 }
}))
.unwrap();
let rows = find(FindQuery {
pkg: Some(pkg.clone()),
order_by: order_from(json!(["-stats.pass_rate", "card_id"])),
..Default::default()
})
.unwrap();
assert_eq!(rows.len(), 3);
assert_eq!(rows[0].pass_rate, Some(0.9));
assert_eq!(rows[1].pass_rate, Some(0.9));
assert_eq!(rows[2].pass_rate, Some(0.5));
assert!(rows[0].card_id < rows[1].card_id);
cleanup(&pkg);
}
#[test]
fn find_offset_and_limit() {
let pkg = unique_pkg();
for i in 0..5 {
create(json!({
"card_id": format!("{pkg}_{i}"),
"pkg": { "name": pkg },
"stats": { "pass_rate": 0.1 * (i + 1) as f64 }
}))
.unwrap();
}
let rows = find(FindQuery {
pkg: Some(pkg.clone()),
order_by: order_from(json!("-stats.pass_rate")),
offset: Some(1),
limit: Some(2),
..Default::default()
})
.unwrap();
assert_eq!(rows.len(), 2);
let pr0 = rows[0].pass_rate.unwrap();
let pr1 = rows[1].pass_rate.unwrap();
assert!((pr0 - 0.4).abs() < 1e-9, "got {pr0}");
assert!((pr1 - 0.3).abs() < 1e-9, "got {pr1}");
cleanup(&pkg);
}
#[test]
fn parse_where_rejects_non_object() {
assert!(parse_where(&json!("not an object")).is_err());
assert!(parse_where(&json!(42)).is_err());
}
#[test]
fn parse_order_by_accepts_string_and_array() {
let k = parse_order_by(&json!("-stats.pass_rate")).unwrap();
assert_eq!(k.len(), 1);
assert_eq!(k[0].path, vec!["stats", "pass_rate"]);
assert!(k[0].desc);
let k = parse_order_by(&json!(["created_at", "-stats.n"])).unwrap();
assert_eq!(k.len(), 2);
assert!(!k[0].desc);
assert!(k[1].desc);
}
#[test]
fn find_where_string_ops_contains_and_starts_with() {
let pkg = unique_pkg();
create(json!({
"card_id": format!("{pkg}_a"),
"pkg": { "name": pkg },
"model": { "id": "claude-opus-4-6" },
"metadata": { "tag": "experiment_alpha" },
}))
.unwrap();
create(json!({
"card_id": format!("{pkg}_b"),
"pkg": { "name": pkg },
"model": { "id": "claude-haiku-4-5-20251001" },
"metadata": { "tag": "experiment_beta" },
}))
.unwrap();
create(json!({
"card_id": format!("{pkg}_c"),
"pkg": { "name": pkg },
"model": { "id": "claude-sonnet-4-5" },
"metadata": { "tag": "baseline" },
}))
.unwrap();
let rows = find(FindQuery {
pkg: Some(pkg.clone()),
where_: Some(where_from(json!({
"metadata": { "tag": { "contains": "experiment" } },
}))),
..Default::default()
})
.unwrap();
assert_eq!(rows.len(), 2);
let rows = find(FindQuery {
pkg: Some(pkg.clone()),
where_: Some(where_from(json!({
"model": { "id": { "starts_with": "claude-opus" } },
}))),
..Default::default()
})
.unwrap();
assert_eq!(rows.len(), 1);
assert!(rows[0].card_id.ends_with("_a"));
let rows = find(FindQuery {
pkg: Some(pkg.clone()),
where_: Some(where_from(json!({
"metadata": { "missing_field": { "contains": "x" } },
}))),
..Default::default()
})
.unwrap();
assert_eq!(rows.len(), 0);
let rows = find(FindQuery {
pkg: Some(pkg.clone()),
where_: Some(where_from(json!({
"metadata": { "tag": { "starts_with": 42 } },
}))),
..Default::default()
})
.unwrap();
assert_eq!(rows.len(), 0);
cleanup(&pkg);
}
#[test]
fn where_missing_field_ne_is_true() {
let pkg = unique_pkg();
create(json!({
"card_id": format!("{pkg}_x"),
"pkg": { "name": pkg },
}))
.unwrap();
let rows = find(FindQuery {
pkg: Some(pkg.clone()),
where_: Some(where_from(json!({
"strategy_params": { "temperature": { "ne": 0.5 } },
}))),
..Default::default()
})
.unwrap();
assert_eq!(rows.len(), 1, "missing field is ne to anything");
cleanup(&pkg);
}
fn create_child(pkg: &str, suffix: &str, parent_id: &str, relation: &str) -> String {
let (id, _) = create(json!({
"card_id": format!("{pkg}_{suffix}"),
"pkg": { "name": pkg },
"stats": { "pass_rate": 0.5 },
"metadata": {
"prior_card_id": parent_id,
"prior_relation": relation,
},
}))
.unwrap();
id
}
#[test]
fn lineage_up_walks_prior_card_id_chain() {
let pkg = unique_pkg();
let (a, _) = create(json!({
"card_id": format!("{pkg}_a"),
"pkg": { "name": pkg },
}))
.unwrap();
let b = create_child(&pkg, "b", &a, "rerun_of");
let c = create_child(&pkg, "c", &b, "rerun_of");
let res = lineage(LineageQuery {
card_id: c.clone(),
direction: LineageDirection::Up,
depth: None,
include_stats: false,
relation_filter: None,
})
.unwrap()
.expect("lineage result");
assert_eq!(res.root, c);
assert_eq!(res.nodes.len(), 3, "root + 2 ancestors");
assert_eq!(res.nodes[0].card_id, c);
assert_eq!(res.nodes[0].depth, 0);
assert_eq!(res.nodes[1].card_id, b);
assert_eq!(res.nodes[1].depth, -1);
assert_eq!(res.nodes[2].card_id, a);
assert_eq!(res.nodes[2].depth, -2);
assert_eq!(res.edges.len(), 2);
assert!(!res.truncated);
cleanup(&pkg);
}
#[test]
fn lineage_down_walks_descendants_breadth_first() {
let pkg = unique_pkg();
let (a, _) = create(json!({
"card_id": format!("{pkg}_a"),
"pkg": { "name": pkg },
}))
.unwrap();
let _b = create_child(&pkg, "b", &a, "sweep_variant");
let c = create_child(&pkg, "c", &a, "sweep_variant");
let _d = create_child(&pkg, "d", &c, "rerun_of");
let res = lineage(LineageQuery {
card_id: a.clone(),
direction: LineageDirection::Down,
depth: None,
include_stats: false,
relation_filter: None,
})
.unwrap()
.expect("lineage result");
assert_eq!(res.nodes.len(), 4);
assert_eq!(res.edges.len(), 3);
assert!(!res.truncated);
cleanup(&pkg);
}
#[test]
fn lineage_depth_truncation_sets_flag() {
let pkg = unique_pkg();
let (a, _) = create(json!({
"card_id": format!("{pkg}_a"),
"pkg": { "name": pkg },
}))
.unwrap();
let b = create_child(&pkg, "b", &a, "rerun_of");
let _c = create_child(&pkg, "c", &b, "rerun_of");
let res = lineage(LineageQuery {
card_id: a,
direction: LineageDirection::Down,
depth: Some(1),
include_stats: false,
relation_filter: None,
})
.unwrap()
.unwrap();
assert_eq!(res.nodes.len(), 2, "root + 1 level");
assert!(res.truncated, "should be truncated at depth=1");
cleanup(&pkg);
}
#[test]
fn lineage_relation_filter_skips_unlisted() {
let pkg = unique_pkg();
let (a, _) = create(json!({
"card_id": format!("{pkg}_a"),
"pkg": { "name": pkg },
}))
.unwrap();
let _b = create_child(&pkg, "b", &a, "sweep_variant");
let _c = create_child(&pkg, "c", &a, "rerun_of");
let res = lineage(LineageQuery {
card_id: a,
direction: LineageDirection::Down,
depth: None,
include_stats: false,
relation_filter: Some(vec!["sweep_variant".to_string()]),
})
.unwrap()
.unwrap();
assert_eq!(res.nodes.len(), 2, "root + only sweep_variant child");
assert_eq!(res.edges[0].relation.as_deref(), Some("sweep_variant"));
cleanup(&pkg);
}
#[test]
fn lineage_missing_card_returns_none() {
let res = lineage(LineageQuery {
card_id: "nonexistent_card_id_xyz".into(),
direction: LineageDirection::Up,
depth: None,
include_stats: false,
relation_filter: None,
})
.unwrap();
assert!(res.is_none());
}
#[test]
fn write_and_read_samples_roundtrip() {
let pkg = unique_pkg();
let (id, _) = create(json!({
"pkg": { "name": pkg },
"stats": { "pass_rate": 0.5 }
}))
.unwrap();
let samples = vec![
json!({ "case": "c0", "passed": true, "score": 1.0 }),
json!({ "case": "c1", "passed": false, "score": 0.0 }),
json!({ "case": "c2", "passed": true, "score": 0.75 }),
];
let path = write_samples(&id, samples.clone()).unwrap();
assert!(path.exists());
assert!(path.to_string_lossy().ends_with(".samples.jsonl"));
let got = read_samples(&id, SamplesQuery::default()).unwrap();
assert_eq!(got.len(), 3);
assert_eq!(got[0]["case"], json!("c0"));
assert_eq!(got[2]["score"], json!(0.75));
let slice = read_samples(
&id,
SamplesQuery {
offset: 1,
limit: Some(1),
where_: None,
},
)
.unwrap();
assert_eq!(slice.len(), 1);
assert_eq!(slice[0]["case"], json!("c1"));
cleanup(&pkg);
}
#[test]
fn write_samples_is_write_once() {
let pkg = unique_pkg();
let (id, _) = create(json!({ "pkg": { "name": pkg } })).unwrap();
write_samples(&id, vec![json!({ "x": 1 })]).unwrap();
let err = write_samples(&id, vec![json!({ "x": 2 })]).unwrap_err();
assert!(err.contains("already exist"), "got: {err}");
cleanup(&pkg);
}
#[test]
fn read_samples_empty_when_absent() {
let pkg = unique_pkg();
let (id, _) = create(json!({ "pkg": { "name": pkg } })).unwrap();
let got = read_samples(&id, SamplesQuery::default()).unwrap();
assert!(got.is_empty());
cleanup(&pkg);
}
#[test]
fn read_samples_where_filters_rows() {
let pkg = unique_pkg();
let (id, _) = create(json!({ "pkg": { "name": pkg } })).unwrap();
write_samples(
&id,
vec![
json!({ "case": "c0", "passed": true, "score": 1.0 }),
json!({ "case": "c1", "passed": false, "score": 0.0 }),
json!({ "case": "c2", "passed": true, "score": 0.25 }),
json!({ "case": "c3", "passed": true, "score": 0.75 }),
json!({ "case": "c4", "passed": false, "score": 0.5 }),
],
)
.unwrap();
let pred = parse_where(&json!({ "passed": true })).unwrap();
let got = read_samples(
&id,
SamplesQuery {
offset: 0,
limit: None,
where_: Some(pred),
},
)
.unwrap();
assert_eq!(got.len(), 3);
assert_eq!(got[0]["case"], json!("c0"));
assert_eq!(got[1]["case"], json!("c2"));
assert_eq!(got[2]["case"], json!("c3"));
let pred = parse_where(&json!({ "score": { "gte": 0.5 } })).unwrap();
let got = read_samples(
&id,
SamplesQuery {
offset: 0,
limit: None,
where_: Some(pred),
},
)
.unwrap();
assert_eq!(got.len(), 3);
assert_eq!(got[0]["case"], json!("c0"));
assert_eq!(got[1]["case"], json!("c3"));
assert_eq!(got[2]["case"], json!("c4"));
let pred = parse_where(&json!({ "passed": true })).unwrap();
let slice = read_samples(
&id,
SamplesQuery {
offset: 1,
limit: Some(1),
where_: Some(pred),
},
)
.unwrap();
assert_eq!(slice.len(), 1);
assert_eq!(slice[0]["case"], json!("c2"));
cleanup(&pkg);
}
#[test]
fn get_by_alias_roundtrip() {
let pkg = unique_pkg();
let (id, _) = create(json!({
"pkg": { "name": pkg },
"stats": { "pass_rate": 0.85 }
}))
.unwrap();
let alias_name = format!("best_{pkg}");
alias_set(&alias_name, &id, Some(&pkg), None).unwrap();
let card = get_by_alias(&alias_name).unwrap().unwrap();
assert_eq!(card["card_id"], json!(id));
assert_eq!(card["stats"]["pass_rate"], json!(0.85));
assert!(get_by_alias("nonexistent_alias_xyz").unwrap().is_none());
cleanup(&pkg);
}
#[test]
fn samples_errors_on_missing_card() {
let err = write_samples("does_not_exist_xyz_samples", vec![json!({})]).unwrap_err();
assert!(err.contains("not found"));
}
#[test]
fn import_from_dir_copies_cards() {
let pkg = unique_pkg();
let tmp = tempfile::tempdir().unwrap();
let card_id = format!("{pkg}_imported");
let card_content = format!(
"schema_version = \"{SCHEMA_VERSION}\"\ncard_id = \"{card_id}\"\npkg = \"{pkg}\"\n"
);
fs::write(tmp.path().join(format!("{card_id}.toml")), &card_content).unwrap();
fs::write(
tmp.path().join(format!("{card_id}.samples.jsonl")),
"{\"case\":\"c0\"}\n",
)
.unwrap();
let (imported, skipped) = import_from_dir(tmp.path(), &pkg).unwrap();
assert_eq!(imported, vec![card_id.clone()]);
assert!(skipped.is_empty());
let got = get(&card_id).unwrap().unwrap();
assert_eq!(got["card_id"], json!(card_id));
let samples = read_samples(&card_id, SamplesQuery::default()).unwrap();
assert_eq!(samples.len(), 1);
cleanup(&pkg);
}
#[test]
fn import_from_dir_skips_existing() {
let pkg = unique_pkg();
let (existing_id, _) = create(json!({
"pkg": { "name": pkg },
"stats": { "pass_rate": 0.5 }
}))
.unwrap();
let tmp = tempfile::tempdir().unwrap();
let card_content = format!(
"schema_version = \"{SCHEMA_VERSION}\"\ncard_id = \"{existing_id}\"\npkg = \"{pkg}\"\n"
);
fs::write(
tmp.path().join(format!("{existing_id}.toml")),
&card_content,
)
.unwrap();
let (imported, skipped) = import_from_dir(tmp.path(), &pkg).unwrap();
assert!(imported.is_empty());
assert_eq!(skipped, vec![existing_id.clone()]);
let got = get(&existing_id).unwrap().unwrap();
assert_eq!(got["stats"]["pass_rate"], json!(0.5));
cleanup(&pkg);
}
#[test]
fn import_from_dir_skips_non_card_toml() {
let pkg = unique_pkg();
let tmp = tempfile::tempdir().unwrap();
fs::write(tmp.path().join("not_a_card.toml"), "title = \"hello\"\n").unwrap();
let (imported, skipped) = import_from_dir(tmp.path(), &pkg).unwrap();
assert!(imported.is_empty());
assert!(skipped.is_empty());
cleanup(&pkg);
}
}