use std::collections::{BTreeMap, HashMap};
use std::io::Write;
use std::path::Path;
use serde_json::Value;
pub const SYNCED: [&str; 6] = ["lessons.jsonl", "stats.jsonl", borg::outcome::TEAM_FILE, borg::veto::FILE, borg::msg::FILE, ABSORBED];
pub const ABSORBED: &str = borg::absorb::FILE;
pub const REASONING: &str = "reasoning.jsonl";
pub type Hash = [u8; 32];
pub fn logs(home: &Path) -> Vec<std::path::PathBuf> {
let mut out: Vec<std::path::PathBuf> = SYNCED.iter().map(|f| home.join(f)).collect();
out.push(home.join(REASONING));
out
}
pub fn travels(file: &str, line: &str, key: &str, now: i64) -> bool {
if file == ABSORBED {
return borg::absorb::written(line, now).is_some();
}
if file != borg::msg::FILE {
return true;
}
let Some(l) = borg::msg::Line::parse(line) else { return false };
l.node() == key && borg::msg::fresh_at(l.ms(), now) && l.message().is_none_or(|m| !m.stays_here())
}
pub fn hash(line: &str) -> Hash {
*blake3::hash(line.as_bytes()).as_bytes()
}
fn complete(line: &str) -> bool {
!line.trim().is_empty() && serde_json::from_str::<Value>(line).is_ok()
}
pub fn read(path: &Path) -> BTreeMap<Hash, String> {
let text = std::fs::read_to_string(path).unwrap_or_default();
text.lines().filter(|l| complete(l)).map(|l| (hash(l), l.to_string())).collect()
}
pub fn digest(set: &BTreeMap<Hash, String>) -> Hash {
let mut h = blake3::Hasher::new();
for k in set.keys() {
h.update(k);
}
*h.finalize().as_bytes()
}
pub fn merge(path: &Path, incoming: &[String]) -> Result<usize, String> {
let mut known = read(path);
let new: Vec<&String> = incoming.iter().filter(|l| complete(l) && known.insert(hash(l), (*l).clone()).is_none()).collect();
append(path, &new)?;
settle(path)?;
Ok(new.len())
}
fn append(path: &Path, lines: &[&String]) -> Result<(), String> {
if lines.is_empty() {
return Ok(());
}
if let Some(d) = path.parent() {
std::fs::create_dir_all(d).map_err(|e| e.to_string())?;
}
let ends_open = std::fs::read(path).map(|b| b.last().is_some_and(|c| *c != b'\n')).unwrap_or(false);
let mut out = String::from(if ends_open { "\n" } else { "" });
for l in lines {
out.push_str(l);
out.push('\n');
}
let mut f = std::fs::OpenOptions::new().create(true).append(true).open(path).map_err(|e| format!("{}: {e}", path.display()))?;
f.write_all(out.as_bytes()).map_err(|e| format!("{}: {e}", path.display()))
}
pub fn settle(path: &Path) -> Result<(), String> {
if path.file_name().is_some_and(|f| f == ABSORBED) {
return Ok(());
}
let text = std::fs::read_to_string(path).unwrap_or_default();
let mut winner: HashMap<String, ((u64, Hash), &str)> = HashMap::new();
let mut last: HashMap<String, Hash> = HashMap::new();
for l in text.lines() {
let Ok(v) = serde_json::from_str::<Value>(l) else { continue };
let (Some(id), Some(version)) = (v["id"].as_str(), v["version"].as_u64()) else { continue };
let rank = (version, hash(l));
last.insert(id.to_string(), rank.1);
if winner.get(id).is_none_or(|(r, _)| rank > *r) {
winner.insert(id.to_string(), (rank, l));
}
}
let mut stale: Vec<(&String, String)> = winner.iter().filter(|(id, (r, _))| last.get(*id) != Some(&r.1)).map(|(id, (_, l))| (id, l.to_string())).collect();
stale.sort();
append(path, &stale.iter().map(|(_, l)| l).collect::<Vec<_>>())
}
#[cfg(test)]
mod tests {
use super::*;
fn lesson(id: &str, version: u32, q: &str) -> String {
serde_json::json!({"id": id, "origin_ms": 1, "question": q, "version": version}).to_string()
}
fn tmp(tag: &str) -> std::path::PathBuf {
let d = std::env::temp_dir().join(format!("borg-mesh-lines-{tag}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&d);
std::fs::create_dir_all(&d).unwrap();
d
}
#[test]
fn a_local_broadcast_never_travels() {
let key = "ab".repeat(32);
let from = borg::msg::Sender { by: "s1".into(), name: "One of Two".into(), harness: "claude".into(), node: key.clone() };
let line = |to: &str| serde_json::to_string(&borg::msg::Message::new(borg::msg::TELL, &from, to, None, 1_000, "freeze merges")).unwrap();
assert!(!travels(borg::msg::FILE, &line(&format!("{}@{key}", borg::msg::LOCAL)), &key, 1_000));
assert!(travels(borg::msg::FILE, &line(borg::msg::ALL), &key, 1_000));
assert!(travels(borg::msg::FILE, &line(&format!("s2@{}", "cd".repeat(32))), &key, 1_000));
}
#[test]
fn a_seen_line_travels_only_under_its_node() {
let (key, other) = ("ab".repeat(32), "cd".repeat(32));
let seen = |node: &str, ms: i64, ids: &[&str]| serde_json::to_string(&borg::msg::Seen::new("s2", node, ms, ids)).unwrap();
assert!(travels(borg::msg::FILE, &seen(&key, 1_000, &["m0123456789ab"]), &key, 1_000));
assert!(!travels(borg::msg::FILE, &seen(&key, 1_000, &["m0123456789ab"]), &other, 1_000), "signed by another node");
assert!(!travels(borg::msg::FILE, &seen(&key, 1_000, &["m0123456789ab"]), &key, 1_000 + borg::msg::TTL_MS + 1), "expired");
assert!(!travels(borg::msg::FILE, &seen(&key, 1_000, &["not an id"]), &key, 1_000), "malformed");
}
#[test]
fn an_absorb_travels_only_well_formed() {
let key = "ab".repeat(32);
let ok = |v: Value| travels(ABSORBED, &v.to_string(), &key, 10);
const ABSORB_NOTE: usize = borg::absorb::NOTE_CHARS;
let absorb = serde_json::json!({"id": "L1", "kind": "absorb", "ms": 5, "by": "lead@x", "note": "skills/review@2026-10-30", "version": 2});
assert!(ok(absorb.clone()));
assert!(ok(serde_json::json!({"id": "L1", "kind": "release", "ms": 6, "by": "lead@x"})));
assert!(ok(serde_json::json!({"id": "L1", "kind": "release", "ms": 6, "by": "lead@x", "note": null, "version": null})), "null is absent");
assert!(ok(serde_json::json!({"id": "L1", "kind": "absorb", "ms": 5, "by": "lead@x", "note": "é".repeat(ABSORB_NOTE)})), "characters, not bytes");
let with = |k: &str, v: Value| {
let mut a = absorb.clone();
a[k] = v;
a
};
for (what, bad) in [
("an oversize note", with("note", Value::from("n".repeat(ABSORB_NOTE + 1)))),
("another kind", with("kind", Value::from("delete"))),
("no kind", serde_json::json!({"id": "L1", "ms": 5, "by": "lead@x"})),
("a numeric id", with("id", Value::from(1))),
("an empty id", with("id", Value::from(""))),
("a blank id", with("id", Value::from(" "))),
("a version borg cannot read", with("version", Value::from(5_000_000_000_u64))),
("stamped past the clock skew", with("ms", Value::from(10 + borg::msg::SKEW_MS + 1))),
("a text ms", with("ms", Value::from("5"))),
("a fractional ms", with("ms", Value::from(5.5))),
("no by", serde_json::json!({"id": "L1", "kind": "absorb", "ms": 5})),
("a note that is no text", with("note", serde_json::json!({"x": 1}))),
("a negative version", with("version", Value::from(-1))),
("an unknown field", with("reach", Value::from("team"))),
("no object", serde_json::json!(["L1", "absorb"])),
] {
assert!(!ok(bad), "{what} is refused");
}
assert!(!travels(ABSORBED, "not json", &key, 1));
assert!(!travels(ABSORBED, r#"{"id":"A","id":"B","kind":"absorb","ms":1,"by":"a@x"}"#, &key, 10), "a duplicate key");
assert!(logs(Path::new("s")).contains(&Path::new("s").join(ABSORBED)), "stamped, signed and quarantined like every synced log");
}
#[test]
fn absorbs_are_not_settled_as_versions() {
let d = tmp("absorbed");
let p = d.join(ABSORBED);
let a = |version: u32, ms: i64| serde_json::json!({"id": "L1", "kind": "absorb", "ms": ms, "by": "lead@x", "version": version}).to_string();
merge(&p, &[a(3, 1)]).unwrap();
merge(&p, &[a(2, 2)]).unwrap();
assert_eq!(std::fs::read_to_string(&p).unwrap(), format!("{}\n{}\n", a(3, 1), a(2, 2)));
let _ = std::fs::remove_dir_all(d);
}
#[test]
fn union_skips_known_and_torn_lines() {
let d = tmp("union");
let p = d.join("lessons.jsonl");
std::fs::write(&p, format!("{}\n{{\"id\":\"L2\",\"ver", lesson("L1", 1, "a"))).unwrap();
assert_eq!(read(&p).len(), 1, "a half-written line does not count");
let n = merge(&p, &[lesson("L1", 1, "a"), lesson("L3", 1, "c"), "garbage".into()]).unwrap();
assert_eq!(n, 1, "only the unknown complete line is new");
assert_eq!(merge(&p, &[lesson("L3", 1, "c")]).unwrap(), 0, "idempotent");
let _ = std::fs::remove_dir_all(d);
}
#[test]
fn ratified_constraints_sync_and_load_as_the_same_standing_lesson_everywhere() {
let d = tmp("constraints");
assert!(logs(&d).contains(&d.join(borg::veto::FILE)), "stamped, signed and quarantined like the lessons");
let c = |by: &str, ms: i64| {
serde_json::json!({"id": "cabc", "text": "merge lookups go through the review API", "lesson": "L1", "by": by, "ms": ms, "source": "veto"})
.to_string()
};
let (a, b) = (d.join("a"), d.join("b"));
for (home, own) in [(&a, c("bob@x", 9)), (&b, c("ann@x", 5))] {
std::fs::create_dir_all(home).unwrap();
std::fs::write(home.join("lessons.jsonl"), format!("{}\n", lesson("L1", 1, "lookup?"))).unwrap();
merge(&home.join(borg::veto::FILE), &[own]).unwrap();
}
merge(&a.join(borg::veto::FILE), &read(&b.join(borg::veto::FILE)).into_values().collect::<Vec<_>>()).unwrap();
merge(&b.join(borg::veto::FILE), &read(&a.join(borg::veto::FILE)).into_values().collect::<Vec<_>>()).unwrap();
let standing = |h: &Path| {
let v: Vec<_> = borg::conscience::load(&h.join("lessons.jsonl"), None)
.into_iter()
.filter(|c| c.standing())
.map(|c| (c.id, c.engineers, c.consolidated))
.collect();
v
};
assert_eq!(standing(&a), [("cabc".to_string(), vec!["ann@x".to_string()], true)], "the earliest ratification, whatever the order");
assert_eq!(standing(&a), standing(&b));
let _ = std::fs::remove_dir_all(d);
}
#[test]
fn concurrent_versions_settle_on_the_same_winner_everywhere() {
let d = tmp("settle");
let (a, b) = (d.join("a/lessons.jsonl"), d.join("b/lessons.jsonl"));
let (v1, v2a, v2b) = (lesson("L1", 1, "orig"), lesson("L1", 2, "alice's merge"), lesson("L1", 2, "bob's merge"));
merge(&a, &[v1.clone(), v2a.clone()]).unwrap();
merge(&b, &[v1.clone(), v2b.clone()]).unwrap();
merge(&a, &read(&b).into_values().collect::<Vec<_>>()).unwrap();
merge(&b, &read(&a).into_values().collect::<Vec<_>>()).unwrap();
assert_eq!(digest(&read(&a)), digest(&read(&b)), "same set");
let q = |p: &Path| borg::conscience::load(p, None).into_iter().map(|c| c.question).collect::<Vec<_>>();
assert_eq!(q(&a), q(&b), "the conscience asks the same version on both nodes");
assert_ne!(q(&a), vec!["orig".to_string()], "an older version never wins");
let c = d.join("c/lessons.jsonl");
merge(&c, &[v2a]).unwrap();
merge(&c, &[v1]).unwrap();
assert_eq!(q(&c), vec!["alice's merge".to_string()]);
let _ = std::fs::remove_dir_all(d);
}
}