haematite-cli 0.21.7

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
use super::Record;
use serde_json::json;
use std::collections::BTreeSet;

impl Record {
    pub(super) fn failed(&mut self, code: &str) -> anyhow::Result<()> {
        let mut affected = BTreeSet::new();
        if let Some(batch) = &self.batch {
            for item in &batch.items {
                affected.insert(
                    item["conversation"]
                        .as_str()
                        .ok_or_else(|| anyhow::anyhow!("record_failure_without_conversation"))?
                        .to_owned(),
                );
            }
        } else {
            for item in &self.completed {
                affected.insert(
                    item["conversation"]
                        .as_str()
                        .ok_or_else(|| anyhow::anyhow!("record_failure_without_conversation"))?
                        .to_owned(),
                );
            }
        }
        anyhow::ensure!(!affected.is_empty(), "record_failure_without_conversation");
        for id in affected {
            if self
                .failures
                .get(&id)
                .is_some_and(|previous| previous == code)
            {
                continue;
            }
            let seq = self.next_sequence(&id)?;
            let mut item = json!({"kind":"turn_failed","source":"record","conversation":id,"seq":seq,"durable":false,"code":code,"message":"conversation write refused; completed items retained for the next batch"});
            super::super::stream::fit(&mut item, self.seat.harness.content_bytes)?;
            self.mark_live(&item)?;
            self.send(json!({"item":item}))?;
            self.failures.insert(id, code.to_owned());
        }
        self.dirty = true;
        Ok(())
    }
}