1use crate::modalities::epistemic::{
2 self, EpistemicStatus, OP_BELIEVES, OP_COMMON_KNOWLEDGE, OP_KNOWS,
3};
4use crate::modalities::logic::deontic::{
5 evaluate_deontic_contract, DeonticStatus, DeonticVerdict, OP_FORBID, OP_OBLIGATE, OP_PERMIT,
6};
7use crate::{q_hash, NQuin};
8
9#[derive(Debug, Clone, Copy, PartialEq)]
11pub enum ShaclDatatype {
12 String,
13 Integer,
14 Decimal,
15 Boolean,
16 DateTime,
17}
18
19impl ShaclDatatype {
20 pub fn from_iri_hash(hash: u64) -> Option<Self> {
22 match hash {
23 h if h == q_hash("xsd:string") => Some(ShaclDatatype::String),
24 h if h == q_hash("xsd:integer") => Some(ShaclDatatype::Integer),
25 h if h == q_hash("xsd:decimal") => Some(ShaclDatatype::Decimal),
26 h if h == q_hash("xsd:boolean") => Some(ShaclDatatype::Boolean),
27 h if h == q_hash("xsd:dateTime") => Some(ShaclDatatype::DateTime),
28 _ => None,
29 }
30 }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq)]
36pub enum ShaclConstraint {
37 Datatype(ShaclDatatype),
38 MinLength(u32),
39 MaxLength(u32),
40 MinCount(u32),
41 MaxCount(u32),
42 In {
45 count: u8,
46 values: [u64; 8],
47 },
48
49 DeonticObligate,
51 DeonticPermit,
52 DeonticForbid,
53 DeonticNotExpired {
54 now_unix: u32,
55 },
56 EpistemicKnowledge {
57 min_certainty: u8,
58 },
59 EpistemicBelief {
60 min_certainty: u8,
61 },
62 CommonKnowledge,
63}
64
65pub fn validate_shacl_property(
68 quins: &[NQuin],
69 target_subject: u64,
70 target_property: u64,
71 constraints: &[ShaclConstraint],
72) -> bool {
73 let mut matching_count = 0;
74
75 for quin in quins {
76 if quin.subject == target_subject && quin.predicate == target_property {
77 matching_count += 1;
78
79 for constraint in constraints {
80 match constraint {
81 ShaclConstraint::Datatype(expected_dt) => {
82 if quin.object >> 63 != 0 {
84 return false;
86 }
87 let type_tag = (quin.object >> 60) & 0b111;
88 let valid = match expected_dt {
89 ShaclDatatype::String => type_tag == 0b000,
90 ShaclDatatype::Integer => type_tag == 0b001,
91 ShaclDatatype::Decimal => type_tag == 0b010,
92 ShaclDatatype::Boolean => type_tag == 0b011,
93 ShaclDatatype::DateTime => type_tag == 0b001, };
95 if !valid {
96 return false;
97 }
98 }
99 ShaclConstraint::MinLength(_) | ShaclConstraint::MaxLength(_) => {
100 }
105 ShaclConstraint::In { count, values } => {
106 let payload = quin.object & 0x0FFF_FFFF_FFFF_FFFF;
107 let mut found = false;
108 for i in 0..*count as usize {
109 if values[i] == payload {
110 found = true;
111 break;
112 }
113 }
114 if !found {
115 return false;
116 }
117 }
118 ShaclConstraint::DeonticObligate => {
119 if !deontic_quin_matches(quins, quin, OP_OBLIGATE, DeonticStatus::Active) {
120 return false;
121 }
122 }
123 ShaclConstraint::DeonticPermit => {
124 if !deontic_quin_matches(quins, quin, OP_PERMIT, DeonticStatus::Active) {
125 return false;
126 }
127 }
128 ShaclConstraint::DeonticForbid => {
129 if deontic_quin_matches(quins, quin, OP_FORBID, DeonticStatus::Active) {
130 return false;
131 }
132 }
133 ShaclConstraint::DeonticNotExpired { now_unix } => {
134 if !deontic_not_expired(quin, *now_unix) {
135 return false;
136 }
137 }
138 ShaclConstraint::EpistemicKnowledge { min_certainty } => {
139 if !epistemic_quin_matches(
140 quins,
141 quin,
142 OP_KNOWS,
143 *min_certainty,
144 EpistemicStatus::Active,
145 ) {
146 return false;
147 }
148 }
149 ShaclConstraint::EpistemicBelief { min_certainty } => {
150 if !epistemic_quin_matches(
151 quins,
152 quin,
153 OP_BELIEVES,
154 *min_certainty,
155 EpistemicStatus::Active,
156 ) {
157 return false;
158 }
159 }
160 ShaclConstraint::CommonKnowledge => {
161 if !epistemic_quin_matches(
162 quins,
163 quin,
164 OP_COMMON_KNOWLEDGE,
165 0,
166 EpistemicStatus::Active,
167 ) {
168 return false;
169 }
170 }
171 ShaclConstraint::MinCount(_) | ShaclConstraint::MaxCount(_) => {}
172 }
173 }
174 }
175 }
176
177 for constraint in constraints {
179 match constraint {
180 ShaclConstraint::MinCount(min) => {
181 if matching_count < *min {
182 return false;
183 }
184 }
185 ShaclConstraint::MaxCount(max) => {
186 if matching_count > *max {
187 return false;
188 }
189 }
190 ShaclConstraint::DeonticObligate
191 | ShaclConstraint::DeonticPermit
192 | ShaclConstraint::DeonticForbid
193 | ShaclConstraint::DeonticNotExpired { .. }
194 | ShaclConstraint::EpistemicKnowledge { .. }
195 | ShaclConstraint::EpistemicBelief { .. }
196 | ShaclConstraint::CommonKnowledge => {
197 if matching_count == 0 {
198 return false;
199 }
200 }
201 _ => {}
202 }
203 }
204
205 true
206}
207
208fn deontic_not_expired(quin: &NQuin, now_unix: u32) -> bool {
209 let expiry = (quin.metadata & 0xFFFF_FFFF) as u32;
210 expiry == 0 || now_unix <= expiry
211}
212
213fn deontic_quin_matches(
214 quins: &[NQuin],
215 focus: &NQuin,
216 expected_opcode: u8,
217 required_status: DeonticStatus,
218) -> bool {
219 let mut verdicts = [DeonticVerdict::default(); 32];
220 let now = std::time::SystemTime::now()
221 .duration_since(std::time::UNIX_EPOCH)
222 .map(|d| d.as_secs() as u32)
223 .unwrap_or(0);
224 let count = evaluate_deontic_contract(quins, now, &mut verdicts).unwrap_or(0);
225 for verdict in &verdicts[..count] {
226 if verdict.norm.subject == focus.subject
227 && verdict.norm.predicate == focus.predicate
228 && verdict.norm.object == focus.object
229 && (verdict.norm.predicate & 0xFF) as u8 == expected_opcode
230 && verdict.status == required_status
231 {
232 return true;
233 }
234 }
235 false
236}
237
238fn epistemic_quin_matches(
239 quins: &[NQuin],
240 focus: &NQuin,
241 expected_opcode: u8,
242 min_certainty: u8,
243 required_status: EpistemicStatus,
244) -> bool {
245 let mut verdicts = [epistemic::EpistemicVerdict {
246 claim: NQuin::default(),
247 status: EpistemicStatus::Skipped,
248 certainty: 0,
249 }; 32];
250 let count =
251 epistemic::evaluate_epistemic_frame(quins, focus.subject, focus.context, &mut verdicts)
252 .unwrap_or(0);
253 for verdict in &verdicts[..count] {
254 if verdict.claim.object == focus.object
255 && (verdict.claim.predicate & 0xFF) as u8 == expected_opcode
256 && verdict.status == required_status
257 && verdict.certainty >= min_certainty
258 {
259 return true;
260 }
261 }
262 false
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268
269 #[test]
270 fn test_shacl_datatype_integer() {
271 let subj = q_hash("did:q42:patient1");
272 let prop = q_hash("q42:age");
273
274 let quin_int = NQuin {
275 subject: subj,
276 predicate: prop,
277 object: (0b001 << 60) | 42, context: 0,
279 metadata: 0,
280 parity: 0,
281 };
282
283 let constraints = [ShaclConstraint::Datatype(ShaclDatatype::Integer)];
284
285 assert!(validate_shacl_property(
286 &[quin_int],
287 subj,
288 prop,
289 &constraints
290 ));
291
292 let quin_str = NQuin {
294 subject: subj,
295 predicate: prop,
296 object: (0b000 << 60) | q_hash("forty-two"),
297 context: 0,
298 metadata: 0,
299 parity: 0,
300 };
301 assert!(!validate_shacl_property(
302 &[quin_str],
303 subj,
304 prop,
305 &constraints
306 ));
307 }
308
309 #[test]
310 fn test_shacl_deontic_obligate() {
311 let subj = q_hash("did:q42:party1");
312 let prop = q_hash("q42:mustSign");
313 let obj = q_hash("contract:nda");
314 let mut norm = crate::modalities::logic::deontic::compile_norm_quin(
315 subj,
316 OP_OBLIGATE,
317 prop,
318 obj,
319 q_hash("ctx:nda"),
320 u32::MAX,
321 false,
322 );
323 norm.parity = norm.subject ^ norm.predicate ^ norm.object ^ norm.context;
324
325 let constraints = [ShaclConstraint::DeonticObligate];
326 assert!(validate_shacl_property(
327 &[norm],
328 subj,
329 norm.predicate,
330 &constraints
331 ));
332 }
333
334 #[test]
335 fn test_shacl_epistemic_knowledge() {
336 let agent = q_hash("agent_a");
337 let claim_obj = q_hash("claim:p");
338 let mut knows = NQuin {
339 subject: agent,
340 predicate: (200u64 << 8) | OP_KNOWS as u64,
341 object: claim_obj,
342 context: q_hash("world_w"),
343 metadata: 0,
344 parity: 0,
345 };
346 knows.parity = knows.subject ^ knows.predicate ^ knows.object ^ knows.context;
347
348 let prop = knows.predicate;
349 let constraints = [ShaclConstraint::EpistemicKnowledge { min_certainty: 128 }];
350 assert!(validate_shacl_property(&[knows], agent, prop, &constraints));
351 }
352
353 #[test]
354 fn test_shacl_cardinality() {
355 let subj = q_hash("did:q42:user1");
356 let prop = q_hash("schema:email");
357
358 let quin = NQuin {
359 subject: subj,
360 predicate: prop,
361 object: (0b000 << 60) | q_hash("test@example.com"),
362 context: 0,
363 metadata: 0,
364 parity: 0,
365 };
366
367 assert!(validate_shacl_property(
369 &[quin.clone()],
370 subj,
371 prop,
372 &[ShaclConstraint::MinCount(1)]
373 ));
374
375 assert!(!validate_shacl_property(
377 &[quin.clone()],
378 subj,
379 prop,
380 &[ShaclConstraint::MinCount(2)]
381 ));
382 }
383}