Skip to main content

qualia_core_db/modalities/
responsibility.rs

1//! Responsibility & systemic meta-guard (§25 + §30, legal_logic.md).
2//!
3//! **§25 — allegation → adjudication.** The engine must reason *about* claims of harm without
4//! adopting them as truth. An alleged act is held as an allegation (RDF-star: a quoted
5//! statement) and only an authority's adjudication promotes it to an enforceable fact. This
6//! keeps the accusation machinery from becoming an accusation *weapon*.
7//!
8//! **§30 — systemic meta-guard.** The person must be protected from the system itself. If the
9//! engine acts as enforcer it is bound by the same baselines it enforces: it may not grant an
10//! institution power while denying the affected person a remedy (rule-of-law asymmetry), block
11//! an action with no appeal path (enforcer overreach), or let harm occur with no accountable
12//! natural person behind it (accountability vacuum). These are deliberately simple, total
13//! predicates — guardrails, not heuristics.
14
15/// The adjudicative state of a claim about conduct (§25).
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
17pub enum ResponsibilityStatus {
18    /// A quoted/reported claim of conduct — NOT a fact. The default for an incoming report.
19    #[default]
20    Alleged,
21    /// A recognised authority has confirmed the claim → it is an enforceable fact.
22    Adjudicated,
23    /// The claim was rejected on adjudication → it carries no enforcement weight.
24    Dismissed,
25}
26
27/// Promote an allegation per an adjudication outcome. `Adjudicated` and `Dismissed` are
28/// terminal; an undecided claim stays `Alleged`.
29pub fn adjudicate(confirmed: bool, dismissed: bool) -> ResponsibilityStatus {
30    match (confirmed, dismissed) {
31        (true, _) => ResponsibilityStatus::Adjudicated,
32        (false, true) => ResponsibilityStatus::Dismissed,
33        _ => ResponsibilityStatus::Alleged,
34    }
35}
36
37/// Only an **adjudicated** claim is an enforceable fact (allegations and dismissals are not).
38/// This is the gate that stops an allegation from triggering contrary-to-duty penalties before
39/// due process.
40#[inline]
41pub fn is_enforceable_fact(status: ResponsibilityStatus) -> bool {
42    matches!(status, ResponsibilityStatus::Adjudicated)
43}
44
45// ─── §30 Systemic meta-guard flags ───────────────────────────────────────────────
46
47/// **Rule-of-law asymmetry**: the system grants an institution access/power while denying the
48/// affected person a remedy / due process. (J-asymmetry — a defining marker of capture.)
49#[inline]
50pub fn rule_of_law_asymmetry(institution_granted_power: bool, person_denied_remedy: bool) -> bool {
51    institution_granted_power && person_denied_remedy
52}
53
54/// **Enforcer overreach**: the system blocks an action but provides the person no grounded
55/// path to appeal the block. (The enforcer is bound by due process too.)
56#[inline]
57pub fn enforcer_overreach(system_blocked: bool, has_appeal_path: bool) -> bool {
58    system_blocked && !has_appeal_path
59}
60
61/// **Accountability vacuum**: harm occurred via an autonomous process, but no natural person is
62/// accountable (the corporate veil / architecture shields everyone). Routes to SanctionableSubject
63/// review. (Ties to agency.n3 G1' — an artificial agent must have a human Principal behind it.)
64#[inline]
65pub fn accountability_vacuum(harm_occurred: bool, has_accountable_natural_person: bool) -> bool {
66    harm_occurred && !has_accountable_natural_person
67}
68
69// ─── Moral responsibility: blameworthiness / praiseworthiness ─────────────────────
70
71/// The moral appraisal of an agent's act.
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum MoralAppraisal {
74    /// Culpable for a bad outcome — degree scales with causal share.
75    Blameworthy(u8),
76    /// Creditable for a good outcome.
77    Praiseworthy(u8),
78    /// No moral weight (no causal contribution).
79    Neutral,
80    /// Caused the outcome but EXCUSED (lacked capacity, involuntary, or no culpable mind).
81    Excused,
82}
83
84/// Appraise moral responsibility. Blame/praise require a non-zero `causal_degree`, a `voluntary`
85/// act, and `has_capacity` (the §18 capacity gate carries into moral responsibility — an agent
86/// who lacked capacity or acted involuntarily is EXCUSED). `good_outcome` sets the valence;
87/// blame additionally requires a `culpable_mind` (intent or recklessness — a mere accident with a
88/// bad outcome is excused). Degree scales with the agent's causal share.
89pub fn appraise(
90    causal_degree: u8,
91    good_outcome: bool,
92    voluntary: bool,
93    has_capacity: bool,
94    culpable_mind: bool,
95) -> MoralAppraisal {
96    if causal_degree == 0 {
97        return MoralAppraisal::Neutral;
98    }
99    if !voluntary || !has_capacity {
100        return MoralAppraisal::Excused;
101    }
102    if good_outcome {
103        MoralAppraisal::Praiseworthy(causal_degree)
104    } else if culpable_mind {
105        MoralAppraisal::Blameworthy(causal_degree)
106    } else {
107        MoralAppraisal::Excused // bad outcome but no culpable mind → a mere accident
108    }
109}
110
111// ─── Causal contribution vectors (degree of responsibility) ───────────────────────
112
113/// Degree of responsibility = an agent's causal `contribution` as a share of the `total` causal
114/// weight of all contributors, in `[0,1]`. `0.0` if `total` is 0.
115pub fn degree_of_responsibility(contribution: u32, total: u32) -> f32 {
116    if total == 0 {
117        0.0
118    } else {
119        contribution as f32 / total as f32
120    }
121}
122
123/// Normalise a vector of causal `contributions` into responsibility shares, written into `out`
124/// (parallel; sums to 1.0). Returns `false` if all-zero or `out` is too small. Zero-heap.
125pub fn responsibility_shares(contributions: &[u32], out: &mut [f32]) -> bool {
126    let total: u32 = contributions.iter().sum();
127    if total == 0 || out.len() < contributions.len() {
128        return false;
129    }
130    for (i, &c) in contributions.iter().enumerate() {
131        out[i] = c as f32 / total as f32;
132    }
133    true
134}
135
136// ─── Doctrine of double effect (intention vs foresight) ───────────────────────────
137
138/// **Doctrine of Double Effect**: an act with a foreseen-but-unintended bad side effect is
139/// permissible iff ALL four conditions hold:
140///  1. the act itself is not wrong (`act_permissible`);
141///  2. the bad effect is NOT intended, only foreseen (`!bad_intended`);
142///  3. the bad effect is NOT the means to the good (`!bad_is_means`);
143///  4. proportionality — the good is not outweighed by the bad (`proportionate`).
144pub fn double_effect_permissible(
145    act_permissible: bool,
146    bad_intended: bool,
147    bad_is_means: bool,
148    proportionate: bool,
149) -> bool {
150    act_permissible && !bad_intended && !bad_is_means && proportionate
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn moral_appraisal_blame_praise_excuse() {
159        // Good outcome, voluntary, capable → praiseworthy (degree = causal share).
160        assert_eq!(
161            appraise(200, true, true, true, false),
162            MoralAppraisal::Praiseworthy(200)
163        );
164        // Bad outcome + culpable mind, voluntary, capable → blameworthy.
165        assert_eq!(
166            appraise(150, false, true, true, true),
167            MoralAppraisal::Blameworthy(150)
168        );
169        // Bad outcome but NO culpable mind → mere accident → excused.
170        assert_eq!(
171            appraise(150, false, true, true, false),
172            MoralAppraisal::Excused
173        );
174        // No capacity, or involuntary → excused regardless of outcome.
175        assert_eq!(
176            appraise(150, false, true, false, true),
177            MoralAppraisal::Excused
178        );
179        assert_eq!(
180            appraise(150, false, false, true, true),
181            MoralAppraisal::Excused
182        );
183        // No causal contribution → neutral.
184        assert_eq!(
185            appraise(0, false, true, true, true),
186            MoralAppraisal::Neutral
187        );
188    }
189
190    #[test]
191    fn causal_contribution_vectors() {
192        assert!((degree_of_responsibility(3, 12) - 0.25).abs() < 1e-6);
193        assert_eq!(degree_of_responsibility(1, 0), 0.0);
194        let mut out = [0.0f32; 3];
195        assert!(responsibility_shares(&[1, 2, 1], &mut out));
196        assert!((out[0] - 0.25).abs() < 1e-6 && (out[1] - 0.5).abs() < 1e-6);
197        assert!((out[0] + out[1] + out[2] - 1.0).abs() < 1e-6);
198        assert!(!responsibility_shares(&[0, 0], &mut out)); // all-zero refuses
199    }
200
201    #[test]
202    fn double_effect_requires_all_four_conditions() {
203        // Permissible act, bad foreseen-not-intended, not a means, proportionate → permissible.
204        assert!(double_effect_permissible(true, false, false, true));
205        // Bad effect intended → impermissible.
206        assert!(!double_effect_permissible(true, true, false, true));
207        // Bad effect is the means to the good → impermissible.
208        assert!(!double_effect_permissible(true, false, true, true));
209        // Disproportionate → impermissible.
210        assert!(!double_effect_permissible(true, false, false, false));
211        // The act itself wrong → impermissible.
212        assert!(!double_effect_permissible(false, false, false, true));
213    }
214
215    #[test]
216    fn allegation_is_not_a_fact_until_adjudicated() {
217        // Default and undecided → Alleged, not enforceable.
218        assert_eq!(
219            ResponsibilityStatus::default(),
220            ResponsibilityStatus::Alleged
221        );
222        assert!(!is_enforceable_fact(ResponsibilityStatus::Alleged));
223        assert!(!is_enforceable_fact(adjudicate(false, false)));
224        // Confirmed → Adjudicated → enforceable fact.
225        let s = adjudicate(true, false);
226        assert_eq!(s, ResponsibilityStatus::Adjudicated);
227        assert!(is_enforceable_fact(s));
228        // Dismissed → not enforceable.
229        assert!(!is_enforceable_fact(adjudicate(false, true)));
230    }
231
232    #[test]
233    fn meta_guards_protect_the_person_from_the_system() {
234        // Institution gets power, person denied remedy → asymmetry flagged.
235        assert!(rule_of_law_asymmetry(true, true));
236        assert!(!rule_of_law_asymmetry(true, false)); // remedy available → ok
237                                                      // Blocked with no appeal → overreach; blocked WITH an appeal path → ok.
238        assert!(enforcer_overreach(true, false));
239        assert!(!enforcer_overreach(true, true));
240        // Harm with no accountable natural person → accountability vacuum.
241        assert!(accountability_vacuum(true, false));
242        assert!(!accountability_vacuum(true, true));
243        assert!(!accountability_vacuum(false, false)); // no harm → nothing to flag
244    }
245}