qualia_client_core/
guardianship.rs1use std::sync::{Mutex, OnceLock};
4
5use qualia_core_db::crdt::SuspendedTransactionQueue;
6use qualia_core_db::wal::WriteAheadLog;
7use qualia_core_db::{q_hash, NQuin};
8use serde::{Deserialize, Serialize};
9
10const MAX_RATIFIED: usize = 32;
11
12static SUSPENDED_QUEUE: OnceLock<Mutex<SuspendedTransactionQueue>> = OnceLock::new();
13static RATIFIED_IDS: OnceLock<Mutex<[Option<u64>; MAX_RATIFIED]>> = OnceLock::new();
14
15pub fn suspended_queue() -> &'static Mutex<SuspendedTransactionQueue> {
16 SUSPENDED_QUEUE.get_or_init(|| Mutex::new(SuspendedTransactionQueue::new()))
17}
18
19fn ratified_ids() -> &'static Mutex<[Option<u64>; MAX_RATIFIED]> {
20 RATIFIED_IDS.get_or_init(|| Mutex::new([None; MAX_RATIFIED]))
21}
22
23fn mark_ratified(agreement_id: u64) {
24 let mut slots = ratified_ids().lock().expect("ratified_ids");
25 if slots.iter().any(|s| *s == Some(agreement_id)) {
26 return;
27 }
28 for slot in slots.iter_mut() {
29 if slot.is_none() {
30 *slot = Some(agreement_id);
31 return;
32 }
33 }
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct SuspendedTxView {
39 pub agreement_id: u64,
40 pub threshold: u8,
41 pub collected_signatures: u8,
42 pub subject: u64,
43 pub predicate: u64,
44 pub object: u64,
45 pub context: u64,
46 pub metadata: u64,
47 pub label: String,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
51#[serde(rename_all = "snake_case")]
52pub enum GuardianTokenOutcome {
53 Ratified,
54 Pending,
55 Denied,
56 NotFound,
57}
58
59pub fn list_pending_affirmations() -> Vec<SuspendedTxView> {
60 let guard = suspended_queue().lock().expect("suspended_queue");
61 let mut out = Vec::new();
62 for slot in guard.queue.iter() {
63 if let Some(tx) = slot {
64 let q = tx.suspended_quin;
65 out.push(SuspendedTxView {
66 agreement_id: tx.agreement_id,
67 threshold: tx.threshold,
68 collected_signatures: tx.collected_signatures,
69 subject: q.subject,
70 predicate: q.predicate,
71 object: q.object,
72 context: q.context,
73 metadata: q.metadata,
74 label: format!(
75 "Guardianship Proposal — agreement 0x{:016x}",
76 tx.agreement_id
77 ),
78 });
79 }
80 }
81 out
82}
83
84pub fn pending_affirmation_count() -> usize {
85 list_pending_affirmations().len()
86}
87
88pub fn is_agreement_ratified(agreement_id: u64) -> bool {
89 ratified_ids()
90 .lock()
91 .expect("ratified_ids")
92 .iter()
93 .any(|s| *s == Some(agreement_id))
94}
95
96pub fn apply_guardian_token(agreement_id: u64, token_fields: [u64; 6]) -> GuardianTokenOutcome {
98 let token = NQuin {
99 subject: token_fields[0],
100 predicate: token_fields[1],
101 object: token_fields[2],
102 context: token_fields[3],
103 metadata: token_fields[4],
104 parity: token_fields[5],
105 };
106 if token.context != agreement_id {
107 return GuardianTokenOutcome::NotFound;
108 }
109
110 let mut guard = suspended_queue().lock().expect("suspended_queue");
111 if let Some(tx) = guard.apply_consensus_token(&token) {
112 mark_ratified(agreement_id);
113 #[cfg(not(target_arch = "wasm32"))]
114 {
115 if let Ok(mut wal) = WriteAheadLog::open(".qualia_graph_mutations.wal") {
116 let mut quin = tx.suspended_quin;
117 let _ = wal.append_mutation_volatile(&mut quin);
118 }
119 }
120 GuardianTokenOutcome::Ratified
121 } else if guard.queue.iter().any(|s| {
122 s.as_ref()
123 .map(|tx| tx.agreement_id == agreement_id)
124 .unwrap_or(false)
125 }) {
126 GuardianTokenOutcome::Pending
127 } else {
128 GuardianTokenOutcome::NotFound
129 }
130}
131
132pub fn build_consent_token(agreement_id: u64, principal_hash: u64) -> [u64; 6] {
134 let q = NQuin {
135 subject: principal_hash,
136 predicate: q_hash("q42:issuesConsentToken"),
137 object: agreement_id,
138 context: agreement_id,
139 metadata: 0,
140 parity: 0,
141 };
142 [
143 q.subject,
144 q.predicate,
145 q.object,
146 q.context,
147 q.metadata,
148 q.parity,
149 ]
150}
151
152pub fn deny_guardian_affirmation(agreement_id: u64) -> bool {
154 let mut guard = suspended_queue().lock().expect("suspended_queue");
155 for slot in guard.queue.iter_mut() {
156 if let Some(tx) = slot {
157 if tx.agreement_id == agreement_id {
158 *slot = None;
159 return true;
160 }
161 }
162 }
163 false
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use qualia_core_db::crdt::SuspendedTransaction;
170
171 #[test]
172 fn list_and_deny_pending() {
173 let queue = suspended_queue();
174 let mut guard = queue.lock().unwrap();
175 *guard = SuspendedTransactionQueue::new();
176 drop(guard);
177
178 let tx = SuspendedTransaction {
179 agreement_id: 0xABCD,
180 threshold: 2,
181 collected_signatures: 1,
182 registers: [None; 16],
183 bytecode_buffer: [None; 64],
184 yielded_op: None,
185 suspended_quin: NQuin::default(),
186 };
187 suspended_queue().lock().unwrap().push(tx).unwrap();
188
189 assert_eq!(pending_affirmation_count(), 1);
190 assert!(deny_guardian_affirmation(0xABCD));
191 assert_eq!(pending_affirmation_count(), 0);
192 }
193}