1use crate::agent::A_NATURAL_PERSON;
21use crate::NQuin;
22
23pub const JURAL_CLAIM: u8 = 0x30;
26pub const JURAL_DUTY: u8 = 0x31;
27pub const JURAL_PRIVILEGE: u8 = 0x32;
28pub const JURAL_NO_RIGHT: u8 = 0x33;
29pub const JURAL_POWER: u8 = 0x34;
30pub const JURAL_LIABILITY: u8 = 0x35;
31pub const JURAL_IMMUNITY: u8 = 0x36;
32pub const JURAL_DISABILITY: u8 = 0x37;
33
34const CONTENT_MASK: u64 = 0x7FFF_FFFF_FFFF_FF00;
36
37pub const fn correlative(pos: u8) -> u8 {
40 match pos {
41 JURAL_CLAIM => JURAL_DUTY,
42 JURAL_DUTY => JURAL_CLAIM,
43 JURAL_PRIVILEGE => JURAL_NO_RIGHT,
44 JURAL_NO_RIGHT => JURAL_PRIVILEGE,
45 JURAL_POWER => JURAL_LIABILITY,
46 JURAL_LIABILITY => JURAL_POWER,
47 JURAL_IMMUNITY => JURAL_DISABILITY,
48 JURAL_DISABILITY => JURAL_IMMUNITY,
49 other => other,
50 }
51}
52
53pub const fn jural_opposite(pos: u8) -> u8 {
55 match pos {
56 JURAL_CLAIM => JURAL_NO_RIGHT,
57 JURAL_NO_RIGHT => JURAL_CLAIM,
58 JURAL_DUTY => JURAL_PRIVILEGE,
59 JURAL_PRIVILEGE => JURAL_DUTY,
60 JURAL_POWER => JURAL_DISABILITY,
61 JURAL_DISABILITY => JURAL_POWER,
62 JURAL_IMMUNITY => JURAL_LIABILITY,
63 JURAL_LIABILITY => JURAL_IMMUNITY,
64 other => other,
65 }
66}
67
68#[inline]
70pub const fn is_jural_position(pos: u8) -> bool {
71 matches!(
72 pos,
73 JURAL_CLAIM
74 | JURAL_DUTY
75 | JURAL_PRIVILEGE
76 | JURAL_NO_RIGHT
77 | JURAL_POWER
78 | JURAL_LIABILITY
79 | JURAL_IMMUNITY
80 | JURAL_DISABILITY
81 )
82}
83
84#[inline]
86pub const fn is_first_order(pos: u8) -> bool {
87 matches!(
88 pos,
89 JURAL_CLAIM | JURAL_DUTY | JURAL_PRIVILEGE | JURAL_NO_RIGHT
90 )
91}
92
93pub const fn position_name(pos: u8) -> Option<&'static str> {
95 Some(match pos {
96 JURAL_CLAIM => "Claim",
97 JURAL_DUTY => "Duty",
98 JURAL_PRIVILEGE => "Privilege",
99 JURAL_NO_RIGHT => "No-Right",
100 JURAL_POWER => "Power",
101 JURAL_LIABILITY => "Liability",
102 JURAL_IMMUNITY => "Immunity",
103 JURAL_DISABILITY => "Disability",
104 _ => return None,
105 })
106}
107
108#[inline]
109pub const fn jural_position(predicate: u64) -> u8 {
110 (predicate & 0xFF) as u8
111}
112
113#[inline]
114pub const fn jural_content(predicate: u64) -> u64 {
115 predicate & CONTENT_MASK
116}
117
118pub fn compile_jural_quin(
121 holder: u64,
122 position: u8,
123 content_path: u64,
124 counterparty: u64,
125 frame: u64,
126) -> NQuin {
127 let predicate = ((content_path << 8) & CONTENT_MASK) | (position as u64);
128 let parity = holder ^ predicate ^ counterparty ^ frame;
129 NQuin {
130 subject: holder,
131 predicate,
132 object: counterparty,
133 context: frame,
134 metadata: 0,
135 parity,
136 }
137}
138
139pub fn correlative_quin(rel: &NQuin) -> NQuin {
142 let pos = jural_position(rel.predicate);
143 let predicate = jural_content(rel.predicate) | (correlative(pos) as u64);
144 let holder = rel.object; let counterparty = rel.subject;
146 let frame = rel.context;
147 let parity = holder ^ predicate ^ counterparty ^ frame;
148 NQuin {
149 subject: holder,
150 predicate,
151 object: counterparty,
152 context: frame,
153 metadata: 0,
154 parity,
155 }
156}
157
158pub fn jural_correlativity_holds(rel: &NQuin, graph: &[NQuin]) -> bool {
160 let expected = correlative_quin(rel);
161 graph.iter().any(|q| {
162 q.subject == expected.subject
163 && q.object == expected.object
164 && q.predicate == expected.predicate
165 && q.context == expected.context
166 })
167}
168
169pub fn find_unmet_correlatives(rels: &[NQuin], out: &mut [NQuin]) -> usize {
174 let mut n = 0usize;
175 for rel in rels {
176 if !is_jural_position(jural_position(rel.predicate)) {
177 continue;
178 }
179 if !jural_correlativity_holds(rel, rels) {
180 if n >= out.len() {
181 break;
182 }
183 out[n] = correlative_quin(rel);
184 n += 1;
185 }
186 }
187 n
188}
189
190pub fn personhood_category_error(
201 holder_class: u64,
202 content_is_np_exclusive: bool,
203 position: u8,
204 tbox: &[NQuin],
205) -> bool {
206 if !content_is_np_exclusive {
207 return false;
208 }
209 if !matches!(position, JURAL_CLAIM | JURAL_PRIVILEGE | JURAL_IMMUNITY) {
211 return false;
212 }
213 !crate::modalities::dl::check_subsumption_quin(holder_class, A_NATURAL_PERSON, tbox)
215}
216
217#[inline]
222pub const fn is_second_order(pos: u8) -> bool {
223 matches!(
224 pos,
225 JURAL_POWER | JURAL_LIABILITY | JURAL_IMMUNITY | JURAL_DISABILITY
226 )
227}
228
229pub fn jural_chain_links(upstream: &NQuin, downstream: &NQuin) -> bool {
235 is_second_order(jural_position(upstream.predicate))
236 && is_jural_position(jural_position(downstream.predicate))
237 && upstream.object == downstream.subject
238}
239
240pub fn jural_chain_pivot(upstream: &NQuin, downstream: &NQuin) -> Option<u64> {
242 if jural_chain_links(upstream, downstream) {
243 Some(upstream.object)
244 } else {
245 None
246 }
247}
248
249pub fn jural_chain_valid(rels: &[NQuin]) -> bool {
252 rels.windows(2).all(|w| jural_chain_links(&w[0], &w[1]))
253}
254
255#[derive(Debug, Clone, Copy, PartialEq, Eq)]
259pub enum CollisionResolution {
260 FirstPrevails,
262 SecondPrevails,
264 RequiresHumanReview,
267 NoCollision,
269}
270
271pub fn jural_collision(a: &NQuin, b: &NQuin) -> bool {
275 a.subject == b.subject
276 && a.context == b.context
277 && is_jural_position(jural_position(a.predicate))
278 && jural_content(a.predicate) == jural_content(b.predicate)
279 && jural_opposite(jural_position(a.predicate)) == jural_position(b.predicate)
280}
281
282pub fn resolve_collision(
288 a: &NQuin,
289 b: &NQuin,
290 a_nonderogable: bool,
291 b_nonderogable: bool,
292) -> CollisionResolution {
293 if !jural_collision(a, b) {
294 return CollisionResolution::NoCollision;
295 }
296 match (a_nonderogable, b_nonderogable) {
297 (true, false) => CollisionResolution::FirstPrevails,
298 (false, true) => CollisionResolution::SecondPrevails,
299 _ => CollisionResolution::RequiresHumanReview,
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306 use crate::q_hash;
307
308 #[test]
309 fn correlatives_are_involutive_and_paired() {
310 for &p in &[
311 JURAL_CLAIM,
312 JURAL_DUTY,
313 JURAL_PRIVILEGE,
314 JURAL_NO_RIGHT,
315 JURAL_POWER,
316 JURAL_LIABILITY,
317 JURAL_IMMUNITY,
318 JURAL_DISABILITY,
319 ] {
320 assert_eq!(correlative(correlative(p)), p, "{:?}", position_name(p));
322 assert_eq!(jural_opposite(jural_opposite(p)), p);
323 assert_ne!(correlative(p), p);
325 assert_ne!(jural_opposite(p), p);
326 }
327 assert_eq!(correlative(JURAL_CLAIM), JURAL_DUTY);
328 assert_eq!(correlative(JURAL_POWER), JURAL_LIABILITY);
329 assert_eq!(correlative(JURAL_IMMUNITY), JURAL_DISABILITY);
330 assert_eq!(jural_opposite(JURAL_CLAIM), JURAL_NO_RIGHT);
331 }
332
333 #[test]
334 fn claim_implies_correlative_duty() {
335 let (alice, bob, frame) = (q_hash("alice"), q_hash("bob"), q_hash("nda"));
336 let content = q_hash("q42:repayLoan");
337 let claim = compile_jural_quin(alice, JURAL_CLAIM, content, bob, frame);
339 let duty = correlative_quin(&claim);
341 assert_eq!(jural_position(duty.predicate), JURAL_DUTY);
342 assert_eq!(duty.subject, bob);
343 assert_eq!(duty.object, alice);
344 assert_eq!(
345 jural_content(duty.predicate),
346 jural_content(claim.predicate)
347 );
348 assert_eq!(duty.context, frame);
349 assert_eq!(
351 duty.parity,
352 duty.subject ^ duty.predicate ^ duty.object ^ duty.context
353 );
354 }
355
356 #[test]
357 fn correlativity_holds_when_duty_is_present() {
358 let (alice, bob, frame) = (q_hash("alice"), q_hash("bob"), q_hash("nda"));
359 let content = q_hash("q42:repayLoan");
360 let claim = compile_jural_quin(alice, JURAL_CLAIM, content, bob, frame);
361 let duty = correlative_quin(&claim);
362 assert!(jural_correlativity_holds(&claim, &[claim, duty]));
364 assert!(!jural_correlativity_holds(&claim, &[claim]));
366 }
367
368 #[test]
369 fn unmet_correlative_duty_is_made_legible() {
370 let (alice, state, frame) = (q_hash("alice"), q_hash("state"), q_hash("icescr"));
371 let content = q_hash("q42:adequateHousing");
372 let claim = compile_jural_quin(alice, JURAL_CLAIM, content, state, frame);
374 let mut out = [NQuin::default(); 4];
375 let n = find_unmet_correlatives(&[claim], &mut out);
376 assert_eq!(n, 1, "the missing duty must be surfaced");
377 assert_eq!(jural_position(out[0].predicate), JURAL_DUTY);
378 assert_eq!(
379 out[0].subject, state,
380 "the State is the would-be duty-bearer"
381 );
382 assert_eq!(out[0].object, alice);
383
384 let duty = correlative_quin(&claim);
386 assert_eq!(find_unmet_correlatives(&[claim, duty], &mut out), 0);
387 }
388
389 #[test]
390 fn corporate_person_claiming_human_only_right_is_category_error() {
391 let np = A_NATURAL_PERSON;
393 let legal = q_hash("https://ns.webcivics.net/values/LegalPerson");
394 let corp = q_hash("https://ns.webcivics.net/values/CorporatePerson");
395 let agent = q_hash("https://ns.webcivics.net/values/Agent");
396 let sub = q_hash("http://www.w3.org/2000/01/rdf-schema#subClassOf");
397 let e = |s: u64, o: u64| NQuin {
398 subject: s,
399 predicate: sub,
400 object: o,
401 context: 0,
402 metadata: 0,
403 parity: 0,
404 };
405 let tbox = [e(corp, legal), e(legal, agent), e(np, agent)];
406
407 assert!(personhood_category_error(corp, true, JURAL_CLAIM, &tbox));
409 assert!(!personhood_category_error(np, true, JURAL_CLAIM, &tbox));
411 assert!(!personhood_category_error(corp, false, JURAL_CLAIM, &tbox));
413 assert!(!personhood_category_error(corp, true, JURAL_DUTY, &tbox));
415 }
416
417 #[test]
418 fn multi_party_chain_a_power_over_b_duty_to_c() {
419 let (a, b, c, frame) = (q_hash("A"), q_hash("B"), q_hash("C"), q_hash("frame"));
420 let content = q_hash("q42:performService");
421 let a_power = compile_jural_quin(a, JURAL_POWER, content, b, frame);
423 let b_duty = compile_jural_quin(b, JURAL_DUTY, content, c, frame);
424 assert!(
425 jural_chain_links(&a_power, &b_duty),
426 "A's power over B governs B's duty to C"
427 );
428 assert_eq!(
429 jural_chain_pivot(&a_power, &b_duty),
430 Some(b),
431 "the pivot is B"
432 );
433 assert!(jural_chain_valid(&[a_power, b_duty]));
434
435 let a_claim = compile_jural_quin(a, JURAL_CLAIM, content, b, frame);
437 assert!(!jural_chain_links(&a_claim, &b_duty));
438 let x_duty = compile_jural_quin(q_hash("X"), JURAL_DUTY, content, c, frame);
440 assert!(!jural_chain_links(&a_power, &x_duty));
441 assert_eq!(jural_chain_pivot(&a_power, &x_duty), None);
442 }
443
444 #[test]
445 fn colliding_rights_resolve_by_non_derogability_else_human_review() {
446 let (holder, cp, frame) = (q_hash("holder"), q_hash("counter"), q_hash("frame"));
447 let content = q_hash("q42:speak");
448 let duty = compile_jural_quin(holder, JURAL_DUTY, content, cp, frame);
450 let privilege = compile_jural_quin(holder, JURAL_PRIVILEGE, content, cp, frame);
451 assert!(
452 jural_collision(&duty, &privilege),
453 "Duty vs Privilege over same content = collision"
454 );
455
456 assert_eq!(
458 resolve_collision(&duty, &privilege, true, false),
459 CollisionResolution::FirstPrevails
460 );
461 assert_eq!(
462 resolve_collision(&duty, &privilege, false, true),
463 CollisionResolution::SecondPrevails
464 );
465 assert_eq!(
467 resolve_collision(&duty, &privilege, true, true),
468 CollisionResolution::RequiresHumanReview
469 );
470 assert_eq!(
471 resolve_collision(&duty, &privilege, false, false),
472 CollisionResolution::RequiresHumanReview
473 );
474
475 let duty2 = compile_jural_quin(holder, JURAL_DUTY, content, cp, frame);
477 assert!(!jural_collision(&duty, &duty2));
478 assert_eq!(
479 resolve_collision(&duty, &duty2, true, false),
480 CollisionResolution::NoCollision
481 );
482 }
483}