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}