haematite-cli 0.21.3

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::{Pump, json};
use serde_json::Value;

impl Pump {
    pub(super) async fn decision(
        &mut self,
        id: &Value,
        change: &Value,
        person: &str,
    ) -> anyhow::Result<()> {
        let Some(draft) = change["draft"].as_str() else {
            return self.refuse(id, "invalid_decision").await;
        };
        let decision = change["decision"].as_str();
        if !matches!(decision, Some("approve" | "refuse")) {
            return self.refuse(id, "invalid_decision").await;
        }
        if !self.known_drafts.iter().any(|known| known == draft)
            && !self
                .pending
                .values()
                .any(|pending| pending.draft.as_deref() == Some(draft))
            && !self
                .answers
                .values()
                .any(|(_, item)| item["draft"] == draft)
        {
            return self.refuse(id, "draft_unknown").await;
        }
        anyhow::ensure!(!person.is_empty(), "permission_decider_missing");
        let reason = match change.get("reason") {
            None => return self.ack(id).await,
            Some(Value::String(reason))
                if decision == Some("refuse")
                    && !reason.trim().is_empty()
                    && (1..=500).contains(&reason.chars().count()) =>
            {
                reason
            }
            Some(_) => return self.refuse(id, "permission_reason_invalid").await,
        };
        let queued = self
            .answers
            .iter()
            .find(|(_, (_, item))| item["draft"] == draft)
            .map(|(key, answer)| (key.clone(), answer.clone()));
        let answered = queued
            .as_ref()
            .map(|(_, answer)| answer)
            .or_else(|| self.answered_drafts.get(draft));
        if let Some((conversation, item)) = answered {
            if item["decided"] != "refuse" || item["person"] != person {
                return self.refuse(id, "permission_decision_differs").await;
            }
            if let Some(previous) = item.get("reason") {
                return if previous == reason {
                    self.ack(id).await
                } else {
                    self.refuse(id, "permission_reason_differs").await
                };
            }
            let conversation = conversation.clone();
            let mut item = item.clone();
            item["reason"] = json!(reason);
            if !self.reason_fits(&conversation, item.clone())? {
                return self
                    .refuse(id, "permission_reason_exceeds_content_bytes")
                    .await;
            }
            if let Some((key, _)) = queued {
                self.answers.insert(key, (conversation, item));
            } else {
                self.say_in(&conversation, item.clone()).await?;
                self.answered_drafts
                    .insert(draft.to_owned(), (conversation, item));
            }
        } else {
            if self
                .decision_reasons
                .get(draft)
                .is_some_and(|(previous_person, previous)| {
                    previous_person != person || previous != reason
                })
            {
                return self.refuse(id, "permission_reason_differs").await;
            }
            let pending = self
                .pending
                .values()
                .find(|pending| pending.draft.as_deref() == Some(draft))
                .ok_or_else(|| anyhow::anyhow!("permission_reason_request_unknown"))?;
            let item = json!({"kind":"permission_answered","draft":draft,"decided":"refuse","option_id":crate::assistant::permission::decide(&pending.request, crate::assistant::permission::Decision::Refuse).0["outcome"]["optionId"],"person":person,"code":null,"reason":reason});
            if !self.reason_fits(&pending.conversation, item)? {
                return self
                    .refuse(id, "permission_reason_exceeds_content_bytes")
                    .await;
            }
            self.decision_reasons
                .insert(draft.to_owned(), (person.to_owned(), reason.clone()));
        }
        self.ack(id).await
    }

    fn reason_fits(&self, conversation: &str, mut item: Value) -> anyhow::Result<bool> {
        item["conversation"] = json!(conversation);
        item["seq"] = json!(u64::MAX);
        item["durable"] = json!(false);
        Ok(serde_json::to_vec(&item)?.len() <= self.seat.harness.content_bytes)
    }
}