qualia_client_core/wellfair/api/
disclosure.rs1use super::super::med_reminders::{
4 compute_due_reminders, load_prefs, save_prefs, DueMedReminder, MedReminderPrefs,
5};
6use super::super::sanctuary::{
7 load_prefs as load_sanctuary_prefs, lock_sanctuary, setup_sanctuary, unlock_sanctuary,
8 SanctuaryPrefs,
9};
10use sha2::{Digest, Sha256};
11use wellfare_core::sleep_analytics::{
12 self, SleepDebtReport, SleepHeatmapReport, SleepNightSample, DEFAULT_TARGET_SLEEP_MIN,
13};
14
15use super::super::personal_profile::{new_contact_id, EmergencyContact, EmergencyContactStore};
16
17use super::*;
18
19impl WebizenHostApi {
20 pub fn record_transparency_cc(
24 &self,
25 credential_id: &str,
26 informed_authority_did: &str,
27 purpose: &str,
28 ) -> Result<(), String> {
29 let cc = crate::disclosure_trace::TransparencyCc {
30 credential_id: credential_id.to_string(),
31 informed_authority_did: informed_authority_did.to_string(),
32 purpose: purpose.to_string(),
33 informed_unix: Self::now_unix(),
34 };
35 self.accountability_store()?
36 .record_transparency_cc(cc, &self.signing_key, Self::now_unix())
37 .map_err(|e| e.to_string())
38 }
39
40 pub fn record_disclosure(
43 &self,
44 commitment_hex: &str,
45 credential_id: &str,
46 recipient_did: &str,
47 acting_delegate_did: Option<String>,
48 onward_to: Option<String>,
49 ) -> Result<crate::disclosure_trace::DisclosureEvent, String> {
50 let commitment = crate::accountability_store::parse_commitment_hex(commitment_hex)?;
51 let now = Self::now_unix();
52 let actor = acting_delegate_did.as_deref().unwrap_or(recipient_did);
53 let digest = Sha256::digest(
55 format!("{}:{recipient_did}:{actor}:{now}", hex::encode(commitment)).as_bytes(),
56 );
57 let mut fingerprint = [0u8; 16];
58 fingerprint.copy_from_slice(&digest[..16]);
59 let id = format!("d-{}", hex::encode(&digest[16..22]));
60 let kind = match onward_to {
61 Some(to_did) => crate::disclosure_trace::DisclosureKind::OnwardShare { to_did },
62 None => crate::disclosure_trace::DisclosureKind::DirectAccess,
63 };
64 let event = crate::disclosure_trace::DisclosureEvent {
65 id,
66 payload_commitment: commitment,
67 credential_id: credential_id.to_string(),
68 recipient_did: recipient_did.to_string(),
69 acting_delegate_did,
70 time_unix: now,
71 fingerprint,
72 kind,
73 };
74 self.accountability_store()?
75 .record_disclosure_event(event.clone(), &self.signing_key, now)
76 .map_err(|e| e.to_string())?;
77 Ok(event)
78 }
79
80 pub fn disclosure_chain(
82 &self,
83 commitment_hex: &str,
84 ) -> Result<Vec<crate::disclosure_trace::DisclosureEvent>, String> {
85 let c = crate::accountability_store::parse_commitment_hex(commitment_hex)?;
86 self.accountability_store()?
87 .disclosure_chain(&c)
88 .map_err(|e| e.to_string())
89 }
90
91 pub fn actors_with_access(&self, commitment_hex: &str) -> Result<Vec<String>, String> {
93 let c = crate::accountability_store::parse_commitment_hex(commitment_hex)?;
94 self.accountability_store()?
95 .actors_with_access(&c)
96 .map_err(|e| e.to_string())
97 }
98
99 pub fn trace_leak(
101 &self,
102 fingerprint_hex: &str,
103 ) -> Result<Option<crate::disclosure_trace::DisclosureEvent>, String> {
104 let bytes =
105 hex::decode(fingerprint_hex.trim()).map_err(|e| format!("fingerprint not hex: {e}"))?;
106 let fp: crate::disclosure_trace::DisclosureFingerprint = bytes
107 .as_slice()
108 .try_into()
109 .map_err(|_| "fingerprint must be 16 bytes".to_string())?;
110 self.accountability_store()?
111 .trace_leak(&fp)
112 .map_err(|e| e.to_string())
113 }
114
115 pub fn list_transparency_ccs(
117 &self,
118 ) -> Result<Vec<crate::disclosure_trace::TransparencyCc>, String> {
119 self.accountability_store()?
120 .list_transparency_ccs()
121 .map_err(|e| e.to_string())
122 }
123
124 pub fn assess_duty_of_inquiry(
127 &self,
128 duty: crate::duty_of_inquiry::DutyOfInquiry,
129 conduct: crate::duty_of_inquiry::ConductAgainstDuty,
130 ) -> crate::duty_of_inquiry::InquiryVerdict {
131 crate::duty_of_inquiry::assess(&duty, &conduct)
132 }
133
134 pub fn sleep_analytics(
135 &self,
136 target_min: f64,
137 ) -> Result<(SleepDebtReport, SleepHeatmapReport), String> {
138 let sleep_rows = self.list_journal_by_kind("sleep", 128)?;
139 let mut samples = Vec::new();
140 for row in sleep_rows {
141 if let Some(ref summary) = row.summary {
142 if let Some((dur, eff)) = sleep_analytics::parse_sleep_summary_json(summary) {
143 samples.push(SleepNightSample {
144 night_unix: row.asserted_time_unix,
145 duration_min: dur,
146 efficiency: eff,
147 });
148 }
149 }
150 }
151 samples.sort_by_key(|s| s.night_unix);
152 let debt = sleep_analytics::compute_sleep_debt(&samples, target_min);
153 let heatmap = sleep_analytics::compute_weekly_heatmap(&samples, target_min);
154 Ok((debt, heatmap))
155 }
156
157 pub fn default_sleep_analytics(&self) -> Result<(SleepDebtReport, SleepHeatmapReport), String> {
158 self.sleep_analytics(DEFAULT_TARGET_SLEEP_MIN)
159 }
160
161 pub fn add_emergency_contact(
162 &self,
163 display_name: &str,
164 relationship: &str,
165 phone: Option<String>,
166 email: Option<String>,
167 notes: Option<String>,
168 ) -> Result<EmergencyContact, String> {
169 let now = Self::now_unix() as u32;
170 let contact = EmergencyContact {
171 id: new_contact_id(display_name, now),
172 display_name: display_name.to_string(),
173 relationship: relationship.to_string(),
174 phone,
175 email,
176 notes,
177 created_at_unix: now,
178 };
179 let store = EmergencyContactStore::open(&self.storage_root).map_err(|e| e.to_string())?;
180 store.append(&contact).map_err(|e| e.to_string())?;
181 Ok(contact)
182 }
183
184 pub fn list_emergency_contacts(&self) -> Result<Vec<EmergencyContact>, String> {
185 let store = EmergencyContactStore::open(&self.storage_root).map_err(|e| e.to_string())?;
186 store.list().map_err(|e| e.to_string())
187 }
188
189 pub fn med_reminder_prefs(&self) -> MedReminderPrefs {
190 load_prefs(&self.storage_root)
191 }
192
193 pub fn set_med_reminders_enabled(&self, enabled: bool) -> Result<MedReminderPrefs, String> {
194 let mut prefs = load_prefs(&self.storage_root);
195 if enabled && !prefs.permission_granted {
196 return Err("Grant reminder permission before enabling notifications".into());
197 }
198 prefs.enabled = enabled;
199 save_prefs(&self.storage_root, &prefs).map_err(|e| e.to_string())?;
200 Ok(prefs)
201 }
202
203 pub fn grant_med_reminder_permission(&self) -> Result<MedReminderPrefs, String> {
204 let mut prefs = load_prefs(&self.storage_root);
205 prefs.permission_granted = true;
206 prefs.permission_granted_at_unix = Some(Self::now_unix() as u32);
207 save_prefs(&self.storage_root, &prefs).map_err(|e| e.to_string())?;
208 Ok(prefs)
209 }
210
211 pub fn list_due_med_reminders(
212 &self,
213 window_minutes: i32,
214 ) -> Result<Vec<DueMedReminder>, String> {
215 let prefs = load_prefs(&self.storage_root);
216 if !prefs.enabled || !prefs.permission_granted {
217 return Ok(Vec::new());
218 }
219 let journal = self
220 .vault
221 .list_health_records(128)
222 .map_err(|e| e.to_string())?;
223 let now = chrono::Local::now().time();
224 Ok(compute_due_reminders(&journal, now, window_minutes))
225 }
226
227 pub fn sanctuary_prefs(&self) -> SanctuaryPrefs {
228 load_sanctuary_prefs(&self.storage_root)
229 }
230
231 pub fn setup_sanctuary(
232 &self,
233 real_pin: &str,
234 decoy_pin: &str,
235 ) -> Result<SanctuaryPrefs, String> {
236 setup_sanctuary(
237 &self.storage_root,
238 real_pin,
239 decoy_pin,
240 Self::now_unix() as u32,
241 )
242 }
243
244 pub fn lock_sanctuary(&self) -> Result<SanctuaryPrefs, String> {
245 lock_sanctuary(&self.storage_root)
246 }
247
248 pub fn unlock_sanctuary(&self, pin: &str) -> Result<SanctuaryPrefs, String> {
249 unlock_sanctuary(&self.storage_root, pin)
250 }
251
252 #[cfg(not(target_arch = "wasm32"))]
259 pub fn sanctuary_vault_configured(&self) -> bool {
260 super::super::sanctuary_vault::is_configured(&self.storage_root)
261 }
262
263 #[cfg(not(target_arch = "wasm32"))]
264 pub fn setup_sanctuary_vault(&self, real_pin: &str, decoy_pin: &str) -> Result<(), String> {
265 super::super::sanctuary_vault::setup(&self.storage_root, real_pin, decoy_pin)
266 }
267
268 #[cfg(not(target_arch = "wasm32"))]
270 pub fn sanctuary_vault_resolve_lane(
271 &self,
272 pin: &str,
273 ) -> Result<super::super::sanctuary_vault::SanctuaryLane, String> {
274 super::super::sanctuary_vault::resolve_lane(&self.storage_root, pin)
275 }
276
277 #[cfg(not(target_arch = "wasm32"))]
278 pub fn add_sanctuary_vault_note(
279 &self,
280 pin: &str,
281 body: &str,
282 ) -> Result<super::super::sanctuary_vault::SanctuaryLane, String> {
283 super::super::sanctuary_vault::add_note(
284 &self.storage_root,
285 pin,
286 body,
287 Self::now_unix() as u32,
288 )
289 }
290
291 #[cfg(not(target_arch = "wasm32"))]
292 pub fn list_sanctuary_vault_notes(
293 &self,
294 pin: &str,
295 ) -> Result<
296 (
297 super::super::sanctuary_vault::SanctuaryLane,
298 Vec<super::super::sanctuary_vault::SanctuaryVaultNote>,
299 ),
300 String,
301 > {
302 super::super::sanctuary_vault::list_notes(&self.storage_root, pin)
303 }
304}