qualia_core_db/modalities/
meta_deontic.rs1use crate::modalities::logic::deontic::{DeonticStatus, DeonticVerdict};
19use crate::verifiable_credential::Credential;
20use crate::{q_hash, NQuin};
21
22#[inline]
24pub fn breach_predicate() -> u64 {
25 q_hash("q42:breachRecord")
26}
27
28pub fn build_breach_record(verdict: &DeonticVerdict, instrument: u64, now: u32) -> Option<NQuin> {
32 if verdict.status != DeonticStatus::Violated {
33 return None;
34 }
35 let mut rec = NQuin {
36 subject: verdict.norm.subject,
37 predicate: breach_predicate(),
38 object: verdict.norm.object,
39 context: instrument, metadata: now as u64,
41 parity: 0,
42 };
43 rec.parity = rec.subject ^ rec.predicate ^ rec.object ^ rec.context;
44 Some(rec)
45}
46
47#[inline]
49pub fn breach_provenance(record: &NQuin) -> u64 {
50 record.context
51}
52
53pub fn record_breach_to_wal(
56 wal: &mut crate::wal::WriteAheadLog,
57 verdict: &DeonticVerdict,
58 instrument: u64,
59 now: u32,
60) -> std::io::Result<bool> {
61 match build_breach_record(verdict, instrument, now) {
62 Some(rec) => {
63 wal.append_mutation(&rec)?;
64 Ok(true)
65 }
66 None => Ok(false),
67 }
68}
69
70pub fn endorsement_credential(
76 record: NQuin,
77 endorser: u64,
78 subject: u64,
79 issued_at: u32,
80 valid_until: u32,
81) -> Credential {
82 Credential {
83 issuer: endorser,
84 subject,
85 issued_at,
86 valid_until,
87 claims: vec![record],
88 }
89}
90
91#[derive(Debug, Clone)]
97pub struct EvidencePackage {
98 pub record: NQuin,
99 pub provenance: u64,
100 pub endorsement: Credential,
101}
102
103pub fn compile_evidence_package(
109 verdict: &DeonticVerdict,
110 instrument: u64,
111 endorser: u64,
112 subject: u64,
113 now: u32,
114 valid_until: u32,
115) -> Option<EvidencePackage> {
116 let record = build_breach_record(verdict, instrument, now)?;
117 let endorsement = endorsement_credential(record, endorser, subject, now, valid_until);
118 Some(EvidencePackage {
119 record,
120 provenance: instrument,
121 endorsement,
122 })
123}
124
125pub fn translate_norm_across_jurisdictions(
133 norm: &NQuin,
134 mapping: &[(u64, u64)],
135 attested: bool,
136) -> Option<NQuin> {
137 if !attested {
138 return None;
139 }
140 let target = mapping
141 .iter()
142 .find(|(src, _)| *src == norm.object)
143 .map(|(_, t)| *t)?;
144 let mut out = *norm;
145 out.object = target;
146 out.parity = out.subject ^ out.predicate ^ out.object ^ out.context;
147 Some(out)
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use crate::modalities::logic::deontic::{DeonticVerdict, OP_OBLIGATE};
154 use crate::verifiable_credential::{issue, verify};
155 use ed25519_dalek::SigningKey;
156
157 fn violated_verdict(party: u64, content: u64) -> DeonticVerdict {
158 let mut norm = NQuin {
159 subject: party,
160 predicate: OP_OBLIGATE as u64,
161 object: content,
162 context: 0,
163 metadata: 0,
164 parity: 0,
165 };
166 norm.parity = norm.subject ^ norm.predicate ^ norm.object ^ norm.context;
167 let mut v = DeonticVerdict::default();
169 v.norm = norm;
170 v.status = DeonticStatus::Violated;
171 v.opcode = OP_OBLIGATE;
172 v
173 }
174
175 #[test]
176 fn breach_record_only_for_violations_and_anchors_provenance() {
177 let party = q_hash("did:state");
178 let content = q_hash("q42:provideRemedy");
179 let instrument = q_hash("instrument:iccpr");
180
181 let v = violated_verdict(party, content);
182 let rec = build_breach_record(&v, instrument, 1_700_000_000).expect("violation → record");
183 assert_eq!(rec.subject, party);
184 assert_eq!(rec.object, content);
185 assert_eq!(
186 breach_provenance(&rec),
187 instrument,
188 "record is anchored to its instrument"
189 );
190 assert_eq!(rec.predicate, breach_predicate());
191 assert_eq!(
192 rec.parity,
193 rec.subject ^ rec.predicate ^ rec.object ^ rec.context
194 );
195
196 let mut active = violated_verdict(party, content);
198 active.status = DeonticStatus::Active;
199 assert!(build_breach_record(&active, instrument, 1_700_000_000).is_none());
200 }
201
202 #[test]
203 fn breach_record_persists_to_wal() {
204 use tempfile::NamedTempFile;
205 let tmp = NamedTempFile::new().unwrap();
206 let mut wal = crate::wal::WriteAheadLog::open(tmp.path()).unwrap();
207
208 let party = q_hash("did:debtor");
209 let content = q_hash("q42:repay");
210 let instrument = q_hash("instrument:loan");
211 let v = violated_verdict(party, content);
212
213 let wrote = record_breach_to_wal(&mut wal, &v, instrument, 42).unwrap();
214 assert!(wrote, "a Violated verdict must be recorded");
215 let recovered = wal.recover().unwrap();
216 assert_eq!(recovered.len(), 1, "the breach record is in the WAL");
217 assert_eq!(recovered[0].subject, party);
218 assert_eq!(
219 recovered[0].context, instrument,
220 "provenance survives the round-trip"
221 );
222
223 let mut active = v;
225 active.status = DeonticStatus::Active;
226 assert!(!record_breach_to_wal(&mut wal, &active, instrument, 43).unwrap());
227 }
228
229 #[test]
230 fn endorsement_is_a_real_signed_credential() {
231 let party = q_hash("did:state");
232 let content = q_hash("q42:provideRemedy");
233 let instrument = q_hash("instrument:iccpr");
234 let rec = build_breach_record(&violated_verdict(party, content), instrument, 1000).unwrap();
235
236 let sk = SigningKey::from_bytes(&[7u8; 32]);
238 let endorser = q_hash("did:human:adjudicator");
239 let cred = endorsement_credential(rec, endorser, party, 1000, 2000);
240 let sig = issue(&sk, &cred);
241
242 assert!(
244 verify(&cred, &sk.verifying_key(), &sig, 1500).is_ok(),
245 "valid endorsement verifies"
246 );
247 let mut tampered = endorsement_credential(
249 build_breach_record(
250 &violated_verdict(party, q_hash("q42:somethingElse")),
251 instrument,
252 1000,
253 )
254 .unwrap(),
255 endorser,
256 party,
257 1000,
258 2000,
259 );
260 tampered.claims[0].object ^= 0x1;
261 assert!(
262 verify(&tampered, &sk.verifying_key(), &sig, 1500).is_err(),
263 "tampered endorsement fails"
264 );
265 }
266
267 #[test]
268 fn evidence_package_compiles_only_for_violations() {
269 let party = q_hash("did:state");
270 let content = q_hash("q42:provideRemedy");
271 let instrument = q_hash("instrument:iccpr");
272 let endorser = q_hash("did:human:adjudicator");
273
274 let pkg = compile_evidence_package(
275 &violated_verdict(party, content),
276 instrument,
277 endorser,
278 party,
279 1000,
280 2000,
281 )
282 .expect("a violation compiles a package");
283 assert_eq!(pkg.provenance, instrument);
284 assert_eq!(pkg.record.subject, party);
285 assert_eq!(pkg.endorsement.issuer, endorser);
286 assert_eq!(
287 pkg.endorsement.claims.len(),
288 1,
289 "the breach record is the endorsed claim"
290 );
291
292 let mut active = violated_verdict(party, content);
294 active.status = DeonticStatus::Active;
295 assert!(
296 compile_evidence_package(&active, instrument, endorser, party, 1000, 2000).is_none()
297 );
298 }
299
300 #[test]
301 fn cross_jurisdictional_translation_honours_attestation() {
302 let party = q_hash("did:state");
303 let iccpr_art7 = q_hash("iccpr:art7");
304 let domestic = q_hash("au:crimes-act:s274");
305 let mut norm = NQuin {
306 subject: party,
307 predicate: q_hash("q42:forbids"),
308 object: iccpr_art7,
309 context: 0,
310 metadata: 0,
311 parity: 0,
312 };
313 norm.parity = norm.subject ^ norm.predicate ^ norm.object ^ norm.context;
314 let mapping = [(iccpr_art7, domestic)];
315
316 let t = translate_norm_across_jurisdictions(&norm, &mapping, true)
318 .expect("attested mapping applies");
319 assert_eq!(t.object, domestic);
320 assert_eq!(t.parity, t.subject ^ t.predicate ^ t.object ^ t.context);
321 assert!(translate_norm_across_jurisdictions(&norm, &mapping, false).is_none());
323 let mut other = norm;
325 other.object = q_hash("iccpr:art9");
326 assert!(translate_norm_across_jurisdictions(&other, &mapping, true).is_none());
327 }
328}