qualia_core_db/governance/illocution.rs
1//! Illocutionary force + binding-weight conflict resolution (modal-junctures.n3, in code).
2//!
3//! Answers: when norms conflict on the same (party, action), how does a SOFT directive
4//! (Recommends/Urges) resolve against a HARD commissive (Undertakes) or prohibitive
5//! (Forbids)? By **binding-weight precedence**, with two refinements drawn from the
6//! taxonomy:
7//! * a directive's weight is first scaled by the SPEAKER'S AUTHORITY (a UN treaty
8//! body's "calls upon" outweighs an NGO's — the authority variable);
9//! * an EXEMPTIVE (derogation / waiver — the `q42:unless` defeater) OVERRIDES an
10//! otherwise-active obligation/prohibition regardless of weight;
11//! * equal effective weight is a GENUINE conflict, held PARACONSISTENTLY (both norms
12//! retained for adjudication — the engine does not crash or silently pick one).
13
14/// The engine state a juncture triggers (see modal-junctures.n3 mj:EngineState).
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum EngineState {
17 EpistemicAbsolute,
18 EpistemicWeighted,
19 ContextualAnchor,
20 ObligateSelf,
21 ObligateSelfLiability,
22 ObligateTarget,
23 Recommend,
24 QueueAwait,
25 Instantiate,
26 DefeatExpire,
27 ConceptBinding,
28 Permit,
29 DefeaterUnless,
30 Forbid,
31 ForbidTemporal,
32 AlignmentPositive,
33 AlignmentNegative,
34}
35
36/// Deontic binding weight (0 = no binding .. 255 = hardest). Mirrors mj:bindingWeight.
37pub const W_NONE: u8 = 0; // assertive / expressive / declarative-performative
38pub const W_REQUEST: u8 = 30; // rogative directive (queue/await)
39pub const W_RECOMMEND: u8 = 50; // exhortative directive (soft)
40pub const W_PERMIT: u8 = 128; // permissive authoritative
41pub const W_DIRECTIVE_HARD: u8 = 160; // imperative directive (pre-authority-scaling)
42pub const W_OBLIGATE: u8 = 200; // commissive promissive / prohibitive interdictive
43pub const W_GUARANTEE: u8 = 220; // commissive guarantive (+liability)
44pub const W_EXEMPT: u8 = 250; // permissive exemptive (derogation/waiver → defeater)
45
46/// True if this engine state is an Exemptive defeater (derogation / waiver / `q42:unless`).
47#[inline]
48pub fn is_exemptive(state: EngineState) -> bool {
49 matches!(state, EngineState::DefeaterUnless)
50}
51
52/// A directive's EFFECTIVE weight scales with the speaker's structural authority (0..255);
53/// non-authority-scaled junctures keep their base weight. So an NGO (low authority) that
54/// "demands" carries far less force than a treaty body that does.
55#[inline]
56pub fn effective_weight(base: u8, authority_scaled: bool, speaker_authority: u8) -> u8 {
57 if authority_scaled {
58 ((base as u16 * speaker_authority as u16) / 255) as u8
59 } else {
60 base
61 }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum Resolution {
66 /// The first norm governs.
67 AGoverns,
68 /// The second norm governs.
69 BGoverns,
70 /// Equal binding force — a genuine conflict, retained paraconsistently for adjudication.
71 GenuineConflict,
72}
73
74/// A norm participating in a conflict: effective binding weight, whether it is an
75/// Exemptive (derogation/waiver), and whether it is IMMUNE — non-derogable, a Hohfeldian
76/// immunity (e.g. ICCPR Art. 4(2): no derogation from the right to life, freedom from
77/// torture, etc.).
78#[derive(Debug, Clone, Copy)]
79pub struct Norm {
80 pub weight: u8,
81 pub exemptive: bool,
82 pub immune: bool,
83}
84
85impl Norm {
86 /// An ordinary norm of the given binding weight.
87 pub fn new(weight: u8) -> Self {
88 Self {
89 weight,
90 exemptive: false,
91 immune: false,
92 }
93 }
94 /// An Exemptive (derogation / waiver → the q42:unless defeater).
95 pub fn exemptive(weight: u8) -> Self {
96 Self {
97 weight,
98 exemptive: true,
99 immune: false,
100 }
101 }
102 /// A non-derogable norm (Hohfeldian immunity) — cannot be defeated by an Exemptive.
103 pub fn immune(weight: u8) -> Self {
104 Self {
105 weight,
106 exemptive: false,
107 immune: true,
108 }
109 }
110}
111
112/// Resolve two CONFLICTING norms (same party + action): the Exemptive override (BOUNDED
113/// by immunity), then effective binding weight.
114pub fn resolve_conflict(a: Norm, b: Norm) -> Resolution {
115 // An Exemptive defeats the other norm regardless of weight — UNLESS that norm is
116 // non-derogable (immune), in which case the derogation is invalid and the immune norm
117 // STANDS (ICCPR Art. 4(2): the right to life etc. cannot be derogated).
118 match (a.exemptive, b.exemptive) {
119 (true, false) => {
120 return if b.immune {
121 Resolution::BGoverns
122 } else {
123 Resolution::AGoverns
124 };
125 }
126 (false, true) => {
127 return if a.immune {
128 Resolution::AGoverns
129 } else {
130 Resolution::BGoverns
131 };
132 }
133 _ => {}
134 }
135 use std::cmp::Ordering::*;
136 match a.weight.cmp(&b.weight) {
137 Greater => Resolution::AGoverns,
138 Less => Resolution::BGoverns,
139 Equal => Resolution::GenuineConflict,
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn soft_directive_never_overrides_a_hard_commissive() {
149 // "Recommends X" vs "Undertakes not-X" on the same party+action.
150 let recommend = Norm::new(W_RECOMMEND);
151 let undertake = Norm::new(W_OBLIGATE);
152 assert_eq!(resolve_conflict(undertake, recommend), Resolution::AGoverns);
153 assert_eq!(resolve_conflict(recommend, undertake), Resolution::BGoverns);
154 }
155
156 #[test]
157 fn exemptive_overrides_a_derogable_obligation() {
158 // A derogation/waiver (Exemptive → q42:unless) defeats a DEROGABLE obligation.
159 let derogation = Norm::exemptive(W_EXEMPT);
160 let obligation = Norm::new(W_OBLIGATE);
161 assert_eq!(
162 resolve_conflict(derogation, obligation),
163 Resolution::AGoverns
164 );
165 assert_eq!(
166 resolve_conflict(obligation, derogation),
167 Resolution::BGoverns
168 );
169 }
170
171 #[test]
172 fn exemptive_cannot_defeat_a_non_derogable_norm() {
173 // ICCPR Art. 4 derogation (Exemptive) vs Art. 6 right to life — non-derogable per
174 // Art. 4(2). The immune norm STANDS; the derogation is invalid against it.
175 let derogation = Norm::exemptive(W_EXEMPT);
176 let right_to_life = Norm::immune(W_OBLIGATE);
177 assert_eq!(
178 resolve_conflict(derogation, right_to_life),
179 Resolution::BGoverns
180 );
181 assert_eq!(
182 resolve_conflict(right_to_life, derogation),
183 Resolution::AGoverns
184 );
185 }
186
187 #[test]
188 fn equal_force_is_a_genuine_paraconsistent_conflict() {
189 // e.g. an Obligate and a Forbid of equal weight on the same act — held, not crashed.
190 assert_eq!(
191 resolve_conflict(Norm::new(W_OBLIGATE), Norm::new(W_OBLIGATE)),
192 Resolution::GenuineConflict
193 );
194 }
195
196 #[test]
197 fn directive_weight_scales_with_speaker_authority() {
198 // The SAME imperative "demands" carries different force by who says it.
199 let ngo = effective_weight(W_DIRECTIVE_HARD, true, 30); // low authority
200 let treaty_body = effective_weight(W_DIRECTIVE_HARD, true, 255); // high authority
201 assert!(ngo < treaty_body);
202 // An NGO's demand loses to a hard commitment; the treaty body's competes/wins.
203 assert_eq!(
204 resolve_conflict(Norm::new(ngo), Norm::new(W_OBLIGATE)),
205 Resolution::BGoverns
206 );
207 assert_eq!(
208 resolve_conflict(Norm::new(treaty_body), Norm::new(W_RECOMMEND)),
209 Resolution::AGoverns
210 );
211 }
212}