Skip to main content

qualia_client_core/api/
agreements.rs

1//! Peer agreements
2
3#![allow(non_snake_case)]
4
5use super::*;
6
7use std::path::Path;
8
9/// All peer agreements.
10pub fn list_agreements() -> Result<serde_json::Value, String> {
11    serde_json::to_value(crate::agreements::list_agreements()).map_err(|e| e.to_string())
12}
13
14/// Agreements a DID is party to (or that govern its relationship) — fills the directory's agreement slot.
15pub fn agreements_for(did: String) -> Result<serde_json::Value, String> {
16    serde_json::to_value(crate::agreements::agreements_for(&did)).map_err(|e| e.to_string())
17}
18
19/// Create a new **draft** agreement for a relationship, grounded in the non-derogable values floor
20/// (defaults to UDHR), with a pending consent for each party. Returns the created agreement.
21pub fn create_agreement(
22    title: String,
23    relationship_did: String,
24    parties: Vec<String>,
25) -> Result<serde_json::Value, String> {
26    let now = mail_now_unix();
27    let a = crate::agreements::Agreement {
28        id: uuid::Uuid::new_v4().to_string(),
29        title,
30        relationship_did,
31        parties: parties.clone(),
32        values_anchors: vec!["urn:qualia:values:udhr".to_string()],
33        undertakings: vec![],
34        consents: parties
35            .into_iter()
36            .map(|did| crate::agreements::PartyConsent {
37                did,
38                consent: crate::agreements::ConsentState::Pending,
39                signature_hex: None,
40            })
41            .collect(),
42        stage: crate::agreements::FormationStage::Draft,
43        jurisdiction: None,
44        intents: Vec::new(),
45        artifact_context: None,
46        created_at: now,
47        updated_at: now,
48    };
49    crate::agreements::upsert_agreement(a.clone())?;
50    serde_json::to_value(a).map_err(|e| e.to_string())
51}
52
53/// Persist a full agreement (JSON) — for edits (undertakings, stage, etc.). Returns the refreshed list.
54pub fn save_agreement(agreement_json: String) -> Result<serde_json::Value, String> {
55    let a: crate::agreements::Agreement =
56        serde_json::from_str(&agreement_json).map_err(|e| format!("bad agreement: {e}"))?;
57    crate::agreements::upsert_agreement(a)?;
58    list_agreements()
59}
60
61/// Set a party's consent on an agreement (`state`: pending / granted / withdrawn).
62pub fn set_agreement_consent(
63    id: String,
64    did: String,
65    state: String,
66) -> Result<serde_json::Value, String> {
67    let mut all = crate::agreements::list_agreements();
68    let a = all
69        .iter_mut()
70        .find(|a| a.id == id)
71        .ok_or_else(|| format!("unknown agreement '{id}'"))?;
72    let cs = match state.to_lowercase().as_str() {
73        "granted" => crate::agreements::ConsentState::Granted,
74        "withdrawn" => crate::agreements::ConsentState::Withdrawn,
75        _ => crate::agreements::ConsentState::Pending,
76    };
77    crate::agreements::set_consent(a, &did, cs);
78    a.updated_at = mail_now_unix();
79    let updated = a.clone();
80    crate::agreements::upsert_agreement(updated)?;
81    list_agreements()
82}
83
84pub fn get_chat_graph(session_id: String) -> Result<serde_json::Value, String> {
85    let state = crate::state::APP_STATE.get().unwrap();
86    let storage = state.config.lock().unwrap().storage_path.clone();
87    let graph = crate::chat_graph::load_graph(Path::new(&storage), &session_id)
88        .map_err(|e| e.to_string())?;
89    let session = crate::chat_session::load_session(Path::new(&storage), &session_id)
90        .map_err(|e| e.to_string())?;
91    let reactions =
92        crate::chat_ontology::list_reactions(Path::new(&storage), &session_id).unwrap_or_default();
93    let branch_types = crate::chat_ontology::list_branch_types(Path::new(&storage));
94    serde_json::to_value(serde_json::json!({
95        "fragments": graph.fragments,
96        "edges": graph.edges,
97        "messages": session.messages,
98        "reactions": reactions,
99        "branch_types": branch_types,
100        "wordnet": crate::chat_ontology::resolve_wordnet_q42(Path::new(&storage))
101            .map(|p| p.to_string_lossy().to_string()),
102    }))
103    .map_err(|e| e.to_string())
104}
105
106pub fn create_chat_fragment(
107    session_id: String,
108    message_lamport: u64,
109    anchor_start: u32,
110    anchor_end: u32,
111) -> Result<serde_json::Value, String> {
112    let state = crate::state::APP_STATE.get().unwrap();
113    let storage = state.config.lock().unwrap().storage_path.clone();
114    let session = crate::chat_session::load_session(Path::new(&storage), &session_id)
115        .map_err(|e| e.to_string())?;
116    let msg = session
117        .messages
118        .iter()
119        .find(|m| m.lamport == message_lamport)
120        .ok_or_else(|| format!("message {message_lamport} not found"))?;
121    let fragment = crate::chat_graph::create_fragment_from_selection(
122        Path::new(&storage),
123        &session_id,
124        message_lamport,
125        &msg.content,
126        anchor_start,
127        anchor_end,
128    )
129    .map_err(|e| e.to_string())?;
130    serde_json::to_value(fragment).map_err(|e| e.to_string())
131}
132
133pub fn sync_chat_relay(session_id: Option<String>) -> Result<u64, String> {
134    let state = crate::state::APP_STATE.get().unwrap();
135    let storage = state.config.lock().unwrap().storage_path.clone();
136    if let Some(id) = session_id {
137        Ok(crate::chat_relay::sync_session_relay(Path::new(&storage), &id)? as u64)
138    } else {
139        Ok(crate::chat_relay::sync_all_group_sessions()? as u64)
140    }
141}
142
143pub fn start_chat_relay_poller() {
144    crate::chat_relay::start_relay_poller();
145}
146
147pub fn list_chat_branch_types() -> Result<serde_json::Value, String> {
148    let state = crate::state::APP_STATE.get().unwrap();
149    let storage = state.config.lock().unwrap().storage_path.clone();
150    let types = crate::chat_ontology::list_branch_types(Path::new(&storage));
151    serde_json::to_value(types).map_err(|e| e.to_string())
152}
153
154pub fn classify_chat_branch(
155    anchor_text: String,
156    reply_text: String,
157) -> Result<serde_json::Value, String> {
158    let state = crate::state::APP_STATE.get().unwrap();
159    let storage = state.config.lock().unwrap().storage_path.clone();
160    let c = crate::chat_ontology::classify_branch(Path::new(&storage), &anchor_text, &reply_text);
161    serde_json::to_value(c).map_err(|e| e.to_string())
162}
163
164pub fn toggle_chat_reaction(
165    session_id: String,
166    message_lamport: u64,
167    emoji: String,
168) -> Result<serde_json::Value, String> {
169    let state = crate::state::APP_STATE.get().unwrap();
170    let storage = state.config.lock().unwrap().storage_path.clone();
171    let reactions = crate::chat_ontology::toggle_reaction(
172        Path::new(&storage),
173        &session_id,
174        message_lamport,
175        &emoji,
176    )?;
177    serde_json::to_value(reactions).map_err(|e| e.to_string())
178}
179
180pub fn list_chat_reactions(session_id: String) -> Result<serde_json::Value, String> {
181    let state = crate::state::APP_STATE.get().unwrap();
182    let storage = state.config.lock().unwrap().storage_path.clone();
183    let reactions = crate::chat_ontology::list_reactions(Path::new(&storage), &session_id)?;
184    serde_json::to_value(reactions).map_err(|e| e.to_string())
185}
186
187pub fn wordnet_chat_ontology_status() -> Result<serde_json::Value, String> {
188    let state = crate::state::APP_STATE.get().unwrap();
189    let storage = state.config.lock().unwrap().storage_path.clone();
190    let path = crate::chat_ontology::resolve_wordnet_q42(Path::new(&storage));
191    Ok(serde_json::json!({
192        "available": path.is_some(),
193        "q42_path": path.as_ref().map(|p| p.to_string_lossy().to_string()),
194        "lex_path": path.as_ref().and_then(|p| crate::chat_ontology::resolve_wordnet_lex(p).map(|l| l.to_string_lossy().to_string())),
195    }))
196}
197
198pub fn default_chat_file_sharing(session_id: String) -> Result<serde_json::Value, String> {
199    let state = crate::state::APP_STATE.get().unwrap();
200    let storage = state.config.lock().unwrap().storage_path.clone();
201    let session = crate::chat_session::load_session(Path::new(&storage), &session_id)
202        .map_err(|e| e.to_string())?;
203    let sharing = crate::chat_files::default_sharing_for_session(session.meta.session_kind);
204    serde_json::to_value(sharing).map_err(|e| e.to_string())
205}
206
207pub fn attach_chat_file(
208    session_id: String,
209    source_path: String,
210    sharing_json: serde_json::Value,
211) -> Result<serde_json::Value, String> {
212    let state = crate::state::APP_STATE.get().unwrap();
213    let storage = state.config.lock().unwrap().storage_path.clone();
214    let sharing: crate::chat_files::ChatFileSharing =
215        serde_json::from_value(sharing_json).map_err(|e| e.to_string())?;
216    let result = crate::chat_files::attach_chat_file(
217        Path::new(&storage),
218        &session_id,
219        Path::new(&source_path),
220        sharing,
221    )?;
222    serde_json::to_value(result).map_err(|e| e.to_string())
223}
224
225pub fn list_chat_files(session_id: String) -> Result<serde_json::Value, String> {
226    let state = crate::state::APP_STATE.get().unwrap();
227    let storage = state.config.lock().unwrap().storage_path.clone();
228    let files = crate::chat_files::list_chat_files(Path::new(&storage), &session_id, None)?;
229    serde_json::to_value(files).map_err(|e| e.to_string())
230}
231
232pub fn set_chat_file_sharing(
233    session_id: String,
234    file_id: String,
235    sharing_json: serde_json::Value,
236) -> Result<serde_json::Value, String> {
237    let state = crate::state::APP_STATE.get().unwrap();
238    let storage = state.config.lock().unwrap().storage_path.clone();
239    let sharing: crate::chat_files::ChatFileSharing =
240        serde_json::from_value(sharing_json).map_err(|e| e.to_string())?;
241    let updated = crate::chat_files::set_chat_file_sharing(
242        Path::new(&storage),
243        &session_id,
244        &file_id,
245        sharing,
246    )?;
247    serde_json::to_value(updated).map_err(|e| e.to_string())
248}
249
250pub fn get_chat_file_local_path(
251    session_id: String,
252    file_id: String,
253    variant: String,
254) -> Result<String, String> {
255    let state = crate::state::APP_STATE.get().unwrap();
256    let storage = state.config.lock().unwrap().storage_path.clone();
257    let path = crate::chat_files::resolve_chat_file_path(
258        Path::new(&storage),
259        &session_id,
260        &file_id,
261        &variant,
262        None,
263    )?;
264    Ok(path.to_string_lossy().to_string())
265}
266
267pub fn parse_chat_file_preview(source_path: String) -> Result<serde_json::Value, String> {
268    let path = Path::new(&source_path);
269    if !path.is_file() {
270        return Err(format!("File not found: {}", path.display()));
271    }
272    let mut bytes = Vec::new();
273    std::fs::File::open(path)
274        .and_then(|mut f| std::io::Read::read_to_end(&mut f, &mut bytes))
275        .map_err(|e| e.to_string())?;
276    let name = path.file_name().and_then(|s| s.to_str()).unwrap_or("file");
277    let parsed = crate::chat_files::parse_document_bytes(name, &bytes);
278    serde_json::to_value(parsed).map_err(|e| e.to_string())
279}