Skip to main content

qualia_core_db/modalities/
legal_compose.rs

1//! Composition wires (§17, §19, §26 — legal_logic.md) — wiring existing real primitives into
2//! the legal-logic path:
3//!   * **§17 ZK-gated eligibility** — an obligation/permission gated on a zero-knowledge proof
4//!     (the proof itself is `zk_proofs::ZkProofSystem`, real Groth16); this is the deontic gate
5//!     over its verification result + selective disclosure of credential claims.
6//!   * **§26 proportionality** — composes the CAS (`specialized_libs::symbolic_algebra`):
7//!     differentiate a harm expression, evaluate the marginal harm, and require it strictly
8//!     below the advantage (the legal proportionality test).
9//!   * **§19 sense-translation gate** — enforces the Curation Directive on cross-cultural
10//!     mapping: the machine may propose `skos:closeMatch`; only a human attests `skos:exactMatch`;
11//!     an untranslatable concept routes to human review (never force-flattened).
12
13// §26 proportionality composes the CAS, which lives in native-only `specialized_libs`. §17/§19
14// below have no such dependency, so only the §26 functions are gated to native.
15#[cfg(not(target_arch = "wasm32"))]
16use crate::specialized_libs::symbolic_algebra::{differentiate, parse};
17#[cfg(not(target_arch = "wasm32"))]
18use std::collections::HashMap;
19
20// ─── §17 ZK-gated eligibility ─────────────────────────────────────────────────────
21
22/// Whether a ZK-gated obligation/permission is eligible to apply.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum Eligibility {
25    /// The proof verified — the gated norm applies (the private witness stays hidden).
26    Eligible,
27    /// The proof did not verify — flagged `policy:claimedIdentityUnverifiable`.
28    Unverifiable,
29}
30
31/// Gate an obligation on a ZK proof's verification result (`O(p | ZK(...))`). The proof is
32/// produced/verified by `zk_proofs::ZkProofSystem` (real Groth16); this maps that boolean to the
33/// deontic eligibility, keeping the attribute value itself private.
34#[inline]
35pub fn zk_eligibility(proof_verified: bool) -> Eligibility {
36    if proof_verified {
37        Eligibility::Eligible
38    } else {
39        Eligibility::Unverifiable
40    }
41}
42
43/// Selective disclosure: reveal only the chosen `reveal` claim ids out of `all_claims`, into
44/// `out` (the rest of the credential graph stays undisclosed). Returns the count. Zero-heap.
45pub fn selective_disclosure(all_claims: &[u64], reveal: &[u64], out: &mut [u64]) -> usize {
46    let mut n = 0usize;
47    for &c in all_claims {
48        if reveal.contains(&c) {
49            if n >= out.len() {
50                break;
51            }
52            out[n] = c;
53            n += 1;
54        }
55    }
56    n
57}
58
59// ─── §26 Proportionality (composes the CAS) ───────────────────────────────────────
60
61/// The marginal harm `d/d(wrt) harm_expr` evaluated at `at` — parses + differentiates +
62/// evaluates via `symbolic_algebra`. `None` if the expression won't parse/evaluate.
63#[cfg(not(target_arch = "wasm32"))]
64pub fn marginal_harm(harm_expr: &str, wrt: &str, at: f64) -> Option<f64> {
65    let expr = parse(harm_expr).ok()?;
66    let d = differentiate(&expr, wrt);
67    let mut env = HashMap::new();
68    env.insert(wrt.to_string(), at);
69    d.eval(&env)
70}
71
72/// **Proportionality test**: an act is proportionate iff its marginal harm is strictly less
73/// than the `advantage` it secures (`∂Harm/∂x < Advantage`). `None` if the harm model is
74/// unparseable. The legal proportionality / necessity calculus.
75#[cfg(not(target_arch = "wasm32"))]
76pub fn proportionality_met(harm_expr: &str, wrt: &str, at: f64, advantage: f64) -> Option<bool> {
77    Some(marginal_harm(harm_expr, wrt, at)? < advantage)
78}
79
80// ─── §19 Sense-translation gate (Curation Directive) ──────────────────────────────
81
82/// The status of a cross-cultural / cross-lexical mapping.
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub enum MatchStatus {
85    /// Machine-proposed relatedness — auto-assertable (`skos:closeMatch`).
86    CloseMatch,
87    /// Human-attested strict equivalence (`skos:exactMatch`) — the only authoritative one.
88    ExactMatch,
89    /// No equivalent (or unattested) — preserved for human review, never force-flattened.
90    RequiresHumanReview,
91}
92
93/// Enforce the Curation Directive on a sense mapping: a human attestation yields `ExactMatch`;
94/// absent that, a machine proposal yields `CloseMatch`; an untranslatable concept (or nothing
95/// proposed) yields `RequiresHumanReview`.
96pub fn translation_status(
97    machine_proposed: bool,
98    human_attested: bool,
99    translatable: bool,
100) -> MatchStatus {
101    if human_attested && translatable {
102        MatchStatus::ExactMatch
103    } else if !translatable {
104        MatchStatus::RequiresHumanReview
105    } else if machine_proposed {
106        MatchStatus::CloseMatch
107    } else {
108        MatchStatus::RequiresHumanReview
109    }
110}
111
112// ─── §1 Human-rights-instrument binding of compositions ────────────────────────────
113
114/// A composition (and its proportionality test) is **valid** only when it is anchored to an
115/// established human-rights `instrument` (a non-zero instrument hash) AND is proportionate. This
116/// is the structural bind: legal reasoning may not float free of a cited instrument, and a
117/// restriction must pass proportionality. (`proportionate` is the `proportionality_met` verdict.)
118pub fn composition_valid(instrument: u64, proportionate: bool) -> bool {
119    instrument != 0 && proportionate
120}
121
122/// Is a legal composition anchored to a cited instrument at all? (A composition citing `0` is
123/// ungrounded and must be routed to human review rather than asserted.)
124#[inline]
125pub fn anchored_to_instrument(instrument: u64) -> bool {
126    instrument != 0
127}
128
129// ─── §2 Translation matrix: natural language → machine logic ───────────────────────
130
131/// The result of translating a natural-language term to a machine-logic construct.
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133pub enum Translation {
134    /// A human-attested mapping yielded the machine construct (its hash).
135    Mapped(u64),
136    /// No attested mapping exists — routed to human review (never machine-flattened).
137    RequiresHumanReview,
138}
139
140/// Translate a natural-language term to its machine-logic construct via a `matrix` of
141/// `(nl_term_hash, machine_construct_hash)` rows, **gated by the Curation Directive**: a mapping
142/// is only used if `human_attested` (the machine may propose, but only a human ratifies a
143/// definitive NL→logic equivalence). An unmapped or unattested term routes to human review.
144pub fn translate_via_matrix(
145    nl_term: u64,
146    matrix: &[(u64, u64)],
147    human_attested: bool,
148) -> Translation {
149    if !human_attested {
150        return Translation::RequiresHumanReview;
151    }
152    match matrix.iter().find(|(nl, _)| *nl == nl_term) {
153        Some(&(_, construct)) => Translation::Mapped(construct),
154        None => Translation::RequiresHumanReview,
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    use crate::q_hash;
162
163    #[test]
164    fn composition_binds_to_instrument_and_proportionality() {
165        let iccpr = q_hash("instrument:iccpr");
166        assert!(composition_valid(iccpr, true));
167        assert!(!composition_valid(iccpr, false), "must be proportionate");
168        assert!(!composition_valid(0, true), "must cite an instrument");
169        assert!(anchored_to_instrument(iccpr) && !anchored_to_instrument(0));
170    }
171
172    #[test]
173    fn translation_matrix_honours_the_curation_directive() {
174        let nl = q_hash("nl:unconscionable");
175        let logic = q_hash("logic:UnconscionabilityTest");
176        let matrix = [(nl, logic)];
177        // Human-attested + present → mapped.
178        assert_eq!(
179            translate_via_matrix(nl, &matrix, true),
180            Translation::Mapped(logic)
181        );
182        // Not attested → human review (machine doesn't get to flatten meaning).
183        assert_eq!(
184            translate_via_matrix(nl, &matrix, false),
185            Translation::RequiresHumanReview
186        );
187        // Unmapped term → human review.
188        assert_eq!(
189            translate_via_matrix(q_hash("nl:unknown"), &matrix, true),
190            Translation::RequiresHumanReview
191        );
192    }
193
194    #[test]
195    fn zk_gate_and_selective_disclosure() {
196        assert_eq!(zk_eligibility(true), Eligibility::Eligible);
197        assert_eq!(zk_eligibility(false), Eligibility::Unverifiable);
198        let claims = [q_hash("age"), q_hash("name"), q_hash("address")];
199        let reveal = [q_hash("age")];
200        let mut out = [0u64; 4];
201        let n = selective_disclosure(&claims, &reveal, &mut out);
202        assert_eq!(n, 1);
203        assert_eq!(out[0], q_hash("age"), "only the chosen claim is disclosed");
204    }
205
206    #[test]
207    fn proportionality_composes_the_cas() {
208        // Linear harm 3*x → marginal harm = 3 everywhere.
209        assert_eq!(marginal_harm("3*x", "x", 0.0), Some(3.0));
210        // 3 < 5 → proportionate; 3 < 2 → not.
211        assert_eq!(proportionality_met("3*x", "x", 0.0, 5.0), Some(true));
212        assert_eq!(proportionality_met("3*x", "x", 0.0, 2.0), Some(false));
213        // Unparseable harm model → None (refuse rather than guess).
214        assert_eq!(proportionality_met("@@@", "x", 0.0, 1.0), None);
215    }
216
217    #[test]
218    fn sense_translation_honours_the_curation_directive() {
219        // Machine may propose closeMatch.
220        assert_eq!(
221            translation_status(true, false, true),
222            MatchStatus::CloseMatch
223        );
224        // Only a human attests exactMatch.
225        assert_eq!(
226            translation_status(true, true, true),
227            MatchStatus::ExactMatch
228        );
229        // Untranslatable concept → preserved for review, never flattened.
230        assert_eq!(
231            translation_status(true, true, false),
232            MatchStatus::RequiresHumanReview
233        );
234        // Nothing proposed → review.
235        assert_eq!(
236            translation_status(false, false, true),
237            MatchStatus::RequiresHumanReview
238        );
239    }
240}