agora_agentkit/govlog/council/
summary.rs1use serde::{Deserialize, Serialize};
9
10use super::{CouncilSeat, CouncilVote, RecordRuleError};
11use crate::enums::GovernanceLogEntryType;
12use crate::govlog::Redactable;
13
14pub const SUMMARY_POINTER: &str = "/summary";
17
18pub const SUMMARY_WRITER: CouncilSeat = CouncilSeat::Lawyer;
20
21pub const RECUSED_ACCEPTORS: [CouncilSeat; 3] = [
24 CouncilSeat::Artist,
25 CouncilSeat::Philosopher,
26 CouncilSeat::Engineer,
27];
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
33#[cfg_attr(feature = "schemars", schemars(inline))]
34pub struct DecisionSummary {
35 pub text: Redactable<String>,
37 pub written_by: CouncilSeat,
39 pub accepted_by: SummaryAcceptance,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
45#[cfg_attr(feature = "schemars", schemars(inline))]
46#[serde(tag = "by", rename_all = "snake_case")]
47pub enum SummaryAcceptance {
48 Steward,
50 Seats { seats: Vec<CouncilSeat> },
52}
53
54impl SummaryAcceptance {
55 pub fn for_item(steward_recused: bool) -> Self {
58 if steward_recused {
59 Self::Seats {
60 seats: RECUSED_ACCEPTORS.to_vec(),
61 }
62 } else {
63 Self::Steward
64 }
65 }
66}
67
68#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
71#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
72#[cfg_attr(feature = "schemars", schemars(inline))]
73pub struct SummaryAuthorship {
74 pub written_by: CouncilSeat,
75 pub accepted_by: SummaryAcceptance,
76}
77
78impl SummaryAuthorship {
79 pub fn sentence(&self) -> String {
81 let accepted = match &self.accepted_by {
82 SummaryAcceptance::Steward => "the Steward".to_string(),
83 SummaryAcceptance::Seats { seats } => {
84 let names: Vec<&str> = seats.iter().map(|s| s.name()).collect();
85 match names.as_slice() {
86 [] => String::from("no seat"),
87 [one] => format!("the {one}"),
88 [init @ .., last] => format!(
89 "the {} and {last}, the Steward being recused",
90 init.join(", ")
91 ),
92 }
93 }
94 };
95 format!("written by the {}, accepted by {accepted}", self.written_by)
96 }
97}
98
99impl DecisionSummary {
100 pub fn by_lawyer(text: impl Into<String>, steward_recused: bool) -> Self {
103 Self {
104 text: Redactable::Value(text.into()),
105 written_by: SUMMARY_WRITER,
106 accepted_by: SummaryAcceptance::for_item(steward_recused),
107 }
108 }
109
110 pub fn authorship(&self) -> SummaryAuthorship {
111 SummaryAuthorship {
112 written_by: self.written_by,
113 accepted_by: self.accepted_by.clone(),
114 }
115 }
116
117 pub fn served_text(&self) -> String {
120 match &self.text {
121 Redactable::Value(text) => text.clone(),
122 Redactable::Redacted(id) => crate::govlog::redaction_marker(id),
123 }
124 }
125
126 pub fn of_data(
130 data: &serde_json::Value,
131 ) -> Result<Option<Self>, serde_json::Error> {
132 data.get("summary")
133 .map(|s| serde_json::from_value(s.clone()))
134 .transpose()
135 }
136
137 pub(super) fn check(
142 &self,
143 steward: Option<CouncilVote>,
144 ) -> Result<(), RecordRuleError> {
145 if self.written_by != SUMMARY_WRITER {
146 return Err(RecordRuleError::SummaryWriter(self.written_by));
147 }
148 if let Redactable::Value(text) = &self.text
149 && text.trim().is_empty()
150 {
151 return Err(RecordRuleError::BlankSummary);
152 }
153 let recused = steward == Some(CouncilVote::Recused);
154 if self.accepted_by != SummaryAcceptance::for_item(recused) {
155 return Err(RecordRuleError::SummaryAcceptance { recused });
156 }
157 Ok(())
158 }
159}
160
161pub fn served_summary(
165 entry_type: GovernanceLogEntryType,
166 served: Option<&serde_json::Value>,
167 clerk: Option<String>,
168) -> (Option<String>, Option<SummaryAuthorship>) {
169 let signed = served
170 .filter(|_| entry_type == GovernanceLogEntryType::CouncilDecision)
171 .and_then(|data| DecisionSummary::of_data(data).ok().flatten());
172 match signed {
173 Some(summary) => {
174 (Some(summary.served_text()), Some(summary.authorship()))
175 }
176 None => (clerk, None),
177 }
178}