Skip to main content

qualia_core_db/modalities/
meta_deontic.rs

1//! Meta-deontic (Phase 5, DEONTIC_LOGIC_PLAN §11) — provenance, endorsement, and the
2//! court-admissible record.
3//!
4//! An obligation is only as strong as the authority asserting it and the record proving its
5//! breach. This layer turns a [`DeonticVerdict`] into durable, attributable evidence:
6//!
7//! * **Provenance anchoring** — a breach record carries, in `context`, the instrument the
8//!   norm derived from (`prov:wasDerivedFrom`), so a violation points back to its ground.
9//! * **Court-admissible record** — a `Violated` verdict is written to the Write-Ahead Log
10//!   ([`crate::wal`]), which is Merkle-DAG–linked (`prev_dag_hash`) — an immutable,
11//!   time-ordered `BreachRecord` history.
12//! * **Cryptographic endorsement** — the Curation Directive: a *human* signs the
13//!   interpretation. The breach record is wrapped as a [`Credential`] claim and verified
14//!   with real Ed25519 ([`crate::verifiable_credential`]). The engine **never holds keys** —
15//!   signing is the identity layer's job (`verifiable_credential::issue`); this module only
16//!   *constructs* the endorsement envelope and *verifies* it.
17
18use crate::modalities::logic::deontic::{DeonticStatus, DeonticVerdict};
19use crate::verifiable_credential::Credential;
20use crate::{q_hash, NQuin};
21
22/// Predicate marking a breach record in the WAL / graph.
23#[inline]
24pub fn breach_predicate() -> u64 {
25    q_hash("q42:breachRecord")
26}
27
28/// Build a court-admissible breach record from a verdict — **only if it is `Violated`**.
29/// `subject` = the party in breach, `object` = the breached content, `context` = the source
30/// `instrument` (provenance anchor), `metadata` = the breach time. Zero-heap.
31pub 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, // provenance: the instrument the breached norm derived from
40        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/// The instrument a breach record is anchored to (its provenance ground).
48#[inline]
49pub fn breach_provenance(record: &NQuin) -> u64 {
50    record.context
51}
52
53/// Append a breach record to the WAL (court-admissible, Merkle-DAG–linked). Returns `true`
54/// iff a record was written (i.e. the verdict was `Violated`).
55pub 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
70/// Build the endorsement envelope: the Curation-Directive attestation that wraps a breach
71/// `record` as a [`Credential`] claim. `endorser` is the attesting (human) agent; `subject`
72/// is the party the breach concerns. **Unsigned** — the identity layer signs it with the
73/// endorser's key via `verifiable_credential::issue`, then anyone verifies with
74/// `verifiable_credential::verify`. Authenticates ORIGIN (who endorsed), not truth.
75pub 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// ─── Court-admissible evidence package compilation ────────────────────────────────
92
93/// A compiled, court-admissible evidence package: the breach `record`, the `provenance`
94/// instrument it is anchored to, and the human `endorsement` (the Curation-Directive attestation).
95/// Bundles the three artefacts a tribunal needs into one structure.
96#[derive(Debug, Clone)]
97pub struct EvidencePackage {
98    pub record: NQuin,
99    pub provenance: u64,
100    pub endorsement: Credential,
101}
102
103/// Compile a court-admissible evidence package from a `Violated` verdict: build the breach record,
104/// anchor it to its `instrument` (provenance), and wrap it as an endorsement credential by
105/// `endorser` (the human attestor). `None` if the verdict is not a violation (nothing to compile).
106/// The package is then SIGNED by the identity layer (`verifiable_credential::issue`) — the engine
107/// never holds keys.
108pub 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
125// ─── Cross-jurisdictional meta-norm translation ───────────────────────────────────
126
127/// Translate a norm's content across jurisdictions via a `mapping` of `(source, target)` content
128/// rows — e.g. mapping an ICCPR Article-7 prohibition to the equivalent domestic-statute
129/// provision. Gated by the Curation Directive: a mapping is applied only if `attested` (a human
130/// ratifies the cross-jurisdictional equivalence). Returns the translated norm (content remapped),
131/// or `None` if unmapped/unattested.
132pub 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        // _pad is private to the deontic module — construct via Default, set public fields.
168        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        // A non-violation produces no record.
197        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        // An Active verdict writes nothing.
224        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        // The identity layer signs (engine never holds the key); here a static test key.
237        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        // Anyone verifies the endorsement with the endorser's public key.
243        assert!(
244            verify(&cred, &sk.verifying_key(), &sig, 1500).is_ok(),
245            "valid endorsement verifies"
246        );
247        // Tampering with the claim breaks verification.
248        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        // A non-violation compiles nothing.
293        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        // Attested + mapped → content remapped to the domestic provision.
317        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        // Unattested → None (no machine-flattening across jurisdictions).
322        assert!(translate_norm_across_jurisdictions(&norm, &mapping, false).is_none());
323        // Unmapped content → None.
324        let mut other = norm;
325        other.object = q_hash("iccpr:art9");
326        assert!(translate_norm_across_jurisdictions(&other, &mapping, true).is_none());
327    }
328}