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}