1use qualia_core_db::{
7 daemon_graph, mini_parser, q_hash, wal::WriteAheadLog, webizen_bytecode, NQuin,
8};
9
10use crate::qapp_manifest::{get_compiled_capability, CompiledCapability};
11
12pub const MAX_QUERY_RESULTS: usize = 2048;
14pub const FLOATS_PER_QUIN: usize = 6;
16pub const SCOPED_QUERY_MAX_FLOATS: usize = MAX_QUERY_RESULTS * FLOATS_PER_QUIN;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum ExecutionError {
20 UnregisteredApp,
21 SanctuaryOverrideRequired,
22 OutputBufferFull,
23 InvalidBytecode,
24 ClearanceViolation,
25}
26
27impl ExecutionError {
28 pub fn as_str(self) -> &'static str {
29 match self {
30 Self::UnregisteredApp => "unregistered_app",
31 Self::SanctuaryOverrideRequired => "sanctuary_override_required",
32 Self::OutputBufferFull => "output_buffer_full",
33 Self::InvalidBytecode => "invalid_bytecode",
34 Self::ClearanceViolation => "clearance_violation",
35 }
36 }
37}
38
39pub fn verify_execution_scope(
41 caps: &CompiledCapability,
42 query_bytecode: &[u8],
43) -> Result<(), ExecutionError> {
44 let mut i = 0usize;
45 while i < query_bytecode.len() {
46 let opcode = query_bytecode[i];
47 match opcode {
48 mini_parser::OP_MATCH_SUBJECT
49 | mini_parser::OP_MATCH_PREDICATE
50 | mini_parser::OP_MATCH_OBJECT => {
51 if i + 9 > query_bytecode.len() {
52 return Err(ExecutionError::InvalidBytecode);
53 }
54 let mut bytes = [0u8; 8];
55 bytes.copy_from_slice(&query_bytecode[i + 1..i + 9]);
56 let operand = u64::from_le_bytes(bytes);
57 if operand != 0 && !domain_permitted(caps, operand) {
58 return Err(ExecutionError::SanctuaryOverrideRequired);
59 }
60 i += 9;
61 }
62 mini_parser::OP_HALT_IF_FALSE => i += 1,
63 mini_parser::OP_END => break,
64 _ => return Err(ExecutionError::InvalidBytecode),
65 }
66 }
67 Ok(())
68}
69
70#[inline]
71fn domain_permitted(caps: &CompiledCapability, hash: u64) -> bool {
72 if hash == caps.app_id_hash {
73 return true;
74 }
75 let count = caps.domain_count as usize;
76 for slot in caps.permitted_domains.iter().take(count) {
77 if *slot == hash {
78 return true;
79 }
80 }
81 false
82}
83
84fn wal_path() -> Option<String> {
85 let state = crate::state::APP_STATE.get()?;
86 let storage = state.config.lock().ok()?.storage_path.clone();
87 Some(format!("{storage}/qualia_global.wal"))
88}
89
90pub fn append_sanctuary_violation(app_id_hash: u64, reason: &'static str) {
92 let mut quin = NQuin::new_conduct_violation(reason.as_bytes());
93 quin.subject = app_id_hash;
94 quin.predicate = q_hash("q42:conductViolation");
95 quin.context = q_hash("q42:qappSanctuaryOverride");
96 quin.parity = quin.subject ^ quin.predicate ^ quin.object ^ quin.context;
97
98 if let Some(path) = wal_path() {
99 if let Ok(mut wal) = WriteAheadLog::open(path) {
100 let _ = wal.append_mutation(&quin);
101 }
102 }
103}
104
105pub fn translate_quin_to_geometry(quin: &NQuin, out: &mut [f32], base: usize) -> usize {
108 if base + FLOATS_PER_QUIN > out.len() {
109 return 0;
110 }
111 out[base] = f32::from_bits(quin.subject as u32);
112 out[base + 1] = f32::from_bits(quin.predicate as u32);
113 out[base + 2] = f32::from_bits(quin.object as u32);
114 out[base + 3] = f32::from_bits(quin.context as u32);
115 out[base + 4] = f32::from_bits(quin.metadata as u32);
116 out[base + 5] = f32::from_bits(quin.parity as u32);
117 FLOATS_PER_QUIN
118}
119
120#[inline]
121fn result_passes_clearance(quin: &NQuin, clearance: u8) -> bool {
122 quin.get_sensitivity_byte() <= clearance
123}
124
125pub fn execute_scoped_query_in_place(
127 app_id_hash: u64,
128 query_bytecode: &[u8],
129 geometry_out: &mut [f32],
130) -> Result<usize, ExecutionError> {
131 let caps = get_compiled_capability(app_id_hash).ok_or(ExecutionError::UnregisteredApp)?;
132
133 if let Err(err @ ExecutionError::SanctuaryOverrideRequired) =
134 verify_execution_scope(&caps, query_bytecode)
135 {
136 append_sanctuary_violation(app_id_hash, err.as_str());
137 return Err(err);
138 }
139
140 let graph_guard = daemon_graph::graph_read_guard();
141 let db = graph_guard.as_slice();
142
143 let mut result_buffer = [NQuin::default(); MAX_QUERY_RESULTS];
144 let (match_count, _vm_cycles) =
145 webizen_bytecode::execute_program(query_bytecode, db, &mut result_buffer, None).map_err(
146 |e| match e {
147 webizen_bytecode::VmError::OutputBufferFull => ExecutionError::OutputBufferFull,
148 webizen_bytecode::VmError::InvalidProgram => ExecutionError::InvalidBytecode,
149 webizen_bytecode::VmError::HaltViolation => ExecutionError::InvalidBytecode,
150 },
151 )?;
152
153 let mut float_offset = 0usize;
154 for quin in &result_buffer[..match_count] {
155 if !result_passes_clearance(quin, caps.clearance_level) {
156 append_sanctuary_violation(app_id_hash, ExecutionError::ClearanceViolation.as_str());
157 return Err(ExecutionError::ClearanceViolation);
158 }
159 let written = translate_quin_to_geometry(quin, geometry_out, float_offset);
160 if written == 0 {
161 return Err(ExecutionError::OutputBufferFull);
162 }
163 float_offset += written;
164 }
165
166 Ok(float_offset)
167}
168
169pub fn execute_scoped_query(
171 app_id_hash: u64,
172 query_bytecode: Vec<u8>,
173) -> Result<Vec<f32>, ExecutionError> {
174 let mut scratch = [0f32; SCOPED_QUERY_MAX_FLOATS];
175 let len = execute_scoped_query_in_place(app_id_hash, &query_bytecode, &mut scratch)?;
176 Ok(scratch[..len].to_vec())
177}
178
179pub fn compile_wildcard_graph_query(program: &mut [u8; 1024]) -> Result<usize, ExecutionError> {
181 mini_parser::compile_ntriples_to_bytecode(b"?subject ?predicate ?object .", program)
182 .map_err(|_| ExecutionError::InvalidBytecode)
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use crate::qapp_manifest::{
189 compile_and_register_qapp, CapabilityClaims, HostMetadata, QappManifest,
190 };
191 use qualia_core_db::daemon_graph;
192
193 fn register_anatomy_app() -> u64 {
194 let manifest = QappManifest {
195 app_id: "did:qualia:qapp:anatomy".to_string(),
196 host_metadata: HostMetadata::default(),
197 capability_claims: CapabilityClaims {
198 required_ontologies: vec![
199 "q42:anatomy".to_string(),
200 "https://qualia.anatomy.example/ontology/bio#".to_string(),
201 ],
202 optional_remote_endpoints: vec![],
203 max_sensitivity_clearance: "0x00".to_string(),
204 required_pinn_models: vec![],
205 supports_ternary_quantization: false,
206 },
207 };
208 compile_and_register_qapp(manifest).unwrap()
209 }
210
211 #[test]
212 fn rejects_forbidden_domain_operands() {
213 let app_id_hash = register_anatomy_app();
214 let caps = get_compiled_capability(app_id_hash).unwrap();
215
216 let mut prog = [0u8; 64];
217 prog[0] = mini_parser::OP_MATCH_SUBJECT;
218 let forbidden = q_hash("q42:financial");
219 prog[1..9].copy_from_slice(&forbidden.to_le_bytes());
220 prog[9] = mini_parser::OP_END;
221
222 assert_eq!(
223 verify_execution_scope(&caps, &prog[..10]),
224 Err(ExecutionError::SanctuaryOverrideRequired)
225 );
226 }
227
228 #[test]
229 fn zero_allocation_scoped_query_hot_path() {
230 daemon_graph::init_daemon_graph("/tmp/qualia-qapp-test");
231 let app_id_hash = register_anatomy_app();
232
233 let mut prog = [0u8; 1024];
234 let prog_len =
235 mini_parser::compile_ntriples_to_bytecode(b"?subject ?predicate ?object .", &mut prog)
236 .unwrap();
237
238 let _profiler = dhat::Profiler::builder().testing().build();
239 let mut out = [0f32; 512];
240 let result = execute_scoped_query_in_place(app_id_hash, &prog[..prog_len], &mut out);
241 assert!(result.is_ok());
242 assert!(result.unwrap() > 0);
243
244 let stats = dhat::HeapStats::get();
245 assert_eq!(
246 stats.curr_blocks, 0,
247 "execute_scoped_query_in_place must not allocate on the heap"
248 );
249 assert_eq!(stats.curr_bytes, 0);
250 }
251}
252
253fn storage_root() -> Result<std::path::PathBuf, String> {
256 let state = crate::state::APP_STATE
257 .get()
258 .ok_or("APP_STATE not initialized")?;
259 Ok(std::path::PathBuf::from(
260 state
261 .config
262 .lock()
263 .map_err(|e| e.to_string())?
264 .storage_path
265 .clone(),
266 ))
267}
268
269pub fn submit_dicom_ingest(file_path: String, patient_did_hash: u64) -> Result<u64, String> {
271 let root = storage_root()?;
272 qualia_core_db::dicom_ingest::init_core3_dicom_worker(root.clone());
273 qualia_core_db::dicom_ingest::submit_dicom_ingest(
274 &root,
275 std::path::Path::new(&file_path),
276 patient_did_hash,
277 )
278 .map_err(|e| e.to_string())
279}
280
281pub fn dicom_ingest_status(job_id: u64) -> u8 {
283 qualia_core_db::dicom_ingest::dicom_ingest_status(job_id)
284}
285
286pub fn execute_dicom_volume_query(
288 patient_did_hash: u64,
289 series_hash: u64,
290) -> Result<Vec<u8>, String> {
291 let root = storage_root()?;
292 let record = qualia_core_db::dicom_ingest::find_series_record(patient_did_hash, series_hash)
293 .ok_or_else(|| "DICOM series not found in ingest registry".to_string())?;
294 qualia_core_db::dicom_ingest::read_volume_bytes(&root, &record).map_err(|e| e.to_string())
295}
296
297pub fn eval_comorbidity_json_from_daemon(
299 patient_did_hash: u64,
300 target_organ_hash: u64,
301) -> Result<String, String> {
302 let graph_guard = qualia_core_db::daemon_graph::graph_read_guard();
303 eval_comorbidity_json(patient_did_hash, target_organ_hash, graph_guard.as_slice())
304}
305
306pub fn eval_comorbidity_json(
308 patient_did_hash: u64,
309 target_organ_hash: u64,
310 graph_quins: &[qualia_core_db::NQuin],
311) -> Result<String, String> {
312 use qualia_core_db::comorbidity_eval::{
313 eval_comorbidity, ComorbidityVerdict, MAX_COMORBIDITY_VERDICTS,
314 };
315
316 let mut out = [ComorbidityVerdict {
317 condition_hash: 0,
318 compounded_risk_milli: 0,
319 status: qualia_core_db::comorbidity_eval::ComorbidityStatus::Active,
320 _pad: [0; 3],
321 }; MAX_COMORBIDITY_VERDICTS];
322
323 let count = eval_comorbidity(patient_did_hash, target_organ_hash, graph_quins, &mut out)
324 .map_err(|e| format!("{e:?}"))?;
325
326 let verdicts: Vec<serde_json::Value> = out[..count]
327 .iter()
328 .map(|v| {
329 serde_json::json!({
330 "conditionHash": format!("{:#018x}", v.condition_hash),
331 "compoundedRiskMilli": v.compounded_risk_milli,
332 "status": v.status as u8,
333 })
334 })
335 .collect();
336
337 serde_json::to_string(&serde_json::json!({ "verdicts": verdicts })).map_err(|e| e.to_string())
338}