memoryd: sync begins when Honcho is turned on, and conclusions are matched by peer

Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
This commit is contained in:
2026-09-28 13:40:48 -07:00
co-authored by ornith-1.5-35b-a3b
parent fb2afa1979
commit 5f4e0d9997
25 changed files with 720 additions and 80 deletions
+53 -7
View File
@@ -50,7 +50,11 @@ pub enum Action {
text: String,
},
/// Delete the conclusion that a sent `Conclude` with this text created.
Unconclude { workspace: Workspace, text: String },
Unconclude {
workspace: Workspace,
peer: String,
text: String,
},
/// A message from the agent's peer in the fleet workspace.
Fleet { session: SessionId, text: String },
/// `retracted: <text>`, from the same peer in the same workspace.
@@ -63,6 +67,9 @@ pub enum Action {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
pub enum Record {
/// When sync began: always the ledger's first line, written once, by `memoryd` at its first
/// start with `[honcho]`. A turn from before it is not synced.
Began { time: Timestamp },
Decided {
time: Timestamp,
item: Item,
@@ -115,6 +122,10 @@ pub enum LedgerError {
Encode(serde_json::Error),
AlreadyDecided(Item),
NotWaiting(Item),
/// A `Began` after other records, or a second one.
BeganNotFirst,
/// A ledger whose first line is not its `Began`.
NotBegun,
}
impl std::fmt::Display for LedgerError {
@@ -129,6 +140,8 @@ impl std::fmt::Display for LedgerError {
LedgerError::NotWaiting(item) => {
write!(f, "{}: not waiting to be sent", label(item))
}
LedgerError::BeganNotFirst => write!(f, "sync began, but not on the first line"),
LedgerError::NotBegun => write!(f, "the first line is not when sync began"),
}
}
}
@@ -165,6 +178,10 @@ pub struct Ledger {
sends: Vec<Item>,
/// Every failed item, in the order of its `Failed` record.
fails: Vec<Item>,
/// When sync began: the `Began` record's time.
began: Option<Timestamp>,
/// How many records the ledger holds.
records: usize,
repaired: bool,
}
@@ -179,6 +196,8 @@ impl Ledger {
entries: HashMap::new(),
sends: Vec::new(),
fails: Vec::new(),
began: None,
records: 0,
repaired: false,
};
let Some(bytes) = read(path)? else {
@@ -188,7 +207,13 @@ impl Ledger {
let lines = bytes.get(..whole).unwrap_or_default();
for (index, line) in lines.split_inclusive(|b| *b == b'\n').enumerate() {
let record = parse(path, index + 1, line)?;
if let Err(e) = ledger.check(&record) {
let first_not_began = index == 0 && !matches!(record, Record::Began { .. });
let checked = if first_not_began {
Err(LedgerError::NotBegun)
} else {
ledger.check(&record)
};
if let Err(e) = checked {
return Err(LedgerError::Damaged {
path: path.to_path_buf(),
line: index + 1,
@@ -219,6 +244,11 @@ impl Ledger {
Ok(())
}
/// When sync began, once the ledger has its `Began` record.
pub fn began(&self) -> Option<Timestamp> {
self.began
}
/// Whether `item` has its `Decided` record.
pub fn is_decided(&self, item: &Item) -> bool {
self.entries.contains_key(item)
@@ -250,9 +280,14 @@ impl Ledger {
}
}
/// The latest sent `Conclude` of `text` in `workspace` that no sent `Unconclude` has undone:
/// its item and the conclusion's id.
pub fn conclusion(&self, workspace: Workspace, text: &str) -> Option<(Item, String)> {
/// The latest sent `Conclude` of `text` about `peer` in `workspace` that no sent `Unconclude`
/// of the same has undone: its item and the conclusion's id.
pub fn conclusion(
&self,
workspace: Workspace,
peer: &str,
text: &str,
) -> Option<(Item, String)> {
let mut live: Vec<(Item, String)> = Vec::new();
for item in &self.sends {
let Some(entry) = self.entries.get(item) else {
@@ -264,17 +299,19 @@ impl Ledger {
match &entry.action {
Action::Conclude {
workspace: w,
peer: p,
text: t,
..
} if *w == workspace && t == text => {
} if *w == workspace && p == peer && t == text => {
if let Some(id) = ids.first() {
live.push((item.clone(), id.clone()));
}
}
Action::Unconclude {
workspace: w,
peer: p,
text: t,
} if *w == workspace && t == text => {
} if *w == workspace && p == peer && t == text => {
live.pop();
}
_ => {}
@@ -319,6 +356,13 @@ impl Ledger {
/// is waiting.
fn check(&self, record: &Record) -> Result<(), LedgerError> {
match record {
Record::Began { .. } => {
if self.records > 0 {
Err(LedgerError::BeganNotFirst)
} else {
Ok(())
}
}
Record::Decided { item, .. } => {
if self.entries.contains_key(item) {
Err(LedgerError::AlreadyDecided(item.clone()))
@@ -336,7 +380,9 @@ impl Ledger {
/// Take a checked record into the state.
fn apply(&mut self, record: Record) {
self.records += 1;
match record {
Record::Began { time } => self.began = Some(time),
Record::Decided { item, action, .. } => {
let state = match action {
Action::Skip { .. } => State::Skipped,