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)
}
}