Skip to main content

qualia_core_db/modalities/
interaction_governance.rs

1//! Interaction governance (Phase 6, DEONTIC_LOGIC_PLAN §15) — the final stage that maps an
2//! abstract [`DeonticVerdict`] to a concrete runtime action in the Webizen VM.
3//!
4//! Once the deontic / spatial / argumentation logics yield a verdict, *something must
5//! happen*. This module is the **pure decision layer**: verdict (+ a little classification)
6//! → [`PolicyMode`]. The side effects each mode implies are performed by the caller:
7//!   * [`PolicyMode::PreventiveBlock`] → the VM injects a `DenyRollback` and halts the
8//!     transaction *before* harm (non-derogable violations: child safety, the ICCPR core).
9//!   * [`PolicyMode::PermissiveAudit`] → the transaction proceeds but a `BreachRecord` is
10//!     written to the WAL ([`super::meta_deontic::record_breach_to_wal`]) for the evidentiary
11//!     trail (system utility preserved, conduct still recorded).
12//!   * [`PolicyMode::Prioritize`] → QoS / routing preference for `hict:HumanitarianICT`
13//!     (peace infrastructure, medical access).
14//!   * [`PolicyMode::Interactive`] → halt and ask the human for a `sense:HumanCorrection`
15//!     (ambiguous or uninterpretable mappings — agency over meaning stays human).
16//!   * [`PolicyMode::Allow`] → nothing special (in force / no longer binding).
17//!
18//! Keeping the decision pure and separate from the effect is what makes the gate auditable
19//! and the same logic reusable by both the VM and the MCP cooperation interface (Track M).
20
21use crate::modalities::logic::deontic::{DeonticStatus, DeonticVerdict};
22
23/// What the runtime should DO about a verdict.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum PolicyMode {
26    /// Halt before harm — `DenyRollback`. For non-derogable violations.
27    PreventiveBlock,
28    /// Allow, but log an immutable `BreachRecord` to the WAL. For non-critical violations.
29    PermissiveAudit,
30    /// Grant QoS / routing / UI priority. For in-force humanitarian norms.
31    Prioritize,
32    /// Halt and request a human correction. For ambiguous / uninterpretable mappings.
33    Interactive,
34    /// No special action — proceed.
35    Allow,
36}
37
38/// The classification a verdict needs beyond its status to be governed: is the norm
39/// non-derogable (a Hohfeldian Immunity / MandatoryBaseline), humanitarian, and is its
40/// mapping ambiguous? These come from the graph (the `values:nonDerogable` overlay,
41/// `hict:HumanitarianICT`, and the resolver), not invented here.
42#[derive(Debug, Clone, Copy, Default)]
43pub struct Governance {
44    pub non_derogable: bool,
45    pub humanitarian: bool,
46    pub ambiguous: bool,
47}
48
49/// Map a deontic status + classification to the runtime [`PolicyMode`].
50///
51/// Order of precedence: ambiguity (ask the human) → violation (block if non-derogable, else
52/// audit) → in-force humanitarian (prioritize) → otherwise allow.
53pub fn map_policy(status: DeonticStatus, g: Governance) -> PolicyMode {
54    if g.ambiguous {
55        return PolicyMode::Interactive;
56    }
57    match status {
58        DeonticStatus::Violated => {
59            if g.non_derogable {
60                PolicyMode::PreventiveBlock
61            } else {
62                PolicyMode::PermissiveAudit
63            }
64        }
65        DeonticStatus::Active | DeonticStatus::Discharged => {
66            if g.humanitarian {
67                PolicyMode::Prioritize
68            } else {
69                PolicyMode::Allow
70            }
71        }
72        // Not yet binding, no longer binding, or uninterpretable.
73        DeonticStatus::Pending | DeonticStatus::Defeated | DeonticStatus::Expired => {
74            PolicyMode::Allow
75        }
76        DeonticStatus::Malformed => PolicyMode::Interactive, // cannot interpret → ask a human
77    }
78}
79
80/// Govern a full verdict (convenience over [`map_policy`]).
81#[inline]
82pub fn govern_verdict(verdict: &DeonticVerdict, g: Governance) -> PolicyMode {
83    map_policy(verdict.status, g)
84}
85
86/// Whether this mode lets the transaction proceed (audit/prioritize/allow) vs halts it
87/// (block/interactive). The VM uses this as the go/no-go bit.
88#[inline]
89pub fn permits_execution(mode: PolicyMode) -> bool {
90    matches!(
91        mode,
92        PolicyMode::PermissiveAudit | PolicyMode::Prioritize | PolicyMode::Allow
93    )
94}
95
96/// A short, stable label for logs / MCP responses.
97pub const fn policy_action(mode: PolicyMode) -> &'static str {
98    match mode {
99        PolicyMode::PreventiveBlock => "DenyRollback",
100        PolicyMode::PermissiveAudit => "AllowAndAuditToWAL",
101        PolicyMode::Prioritize => "GrantPriority",
102        PolicyMode::Interactive => "RequestHumanCorrection",
103        PolicyMode::Allow => "Allow",
104    }
105}
106
107// ─── Dynamic overriding rules (humanitarian emergency) ────────────────────────────
108
109/// A humanitarian emergency may downgrade a [`PolicyMode::PreventiveBlock`] to
110/// [`PolicyMode::PermissiveAudit`] (proceed but record) — EXCEPT for the non-overridable
111/// **hard core** (torture, child safety, the non-derogable absolute prohibitions), which never
112/// bypasses. All other modes pass through unchanged. This is the structured emergency exception,
113/// not an open backdoor.
114pub fn apply_emergency_override(base: PolicyMode, emergency: bool, hard_core: bool) -> PolicyMode {
115    if emergency && base == PolicyMode::PreventiveBlock && !hard_core {
116        PolicyMode::PermissiveAudit
117    } else {
118        base
119    }
120}
121
122// ─── Multi-stakeholder M-of-N threshold ───────────────────────────────────────────
123
124/// A multi-stakeholder governance decision is authorized iff at least `m` stakeholders approved
125/// (an M-of-N threshold; `approvals` is the count of approving stakeholders). `m == 0` is never
126/// authorized (a decision needs at least one approver).
127pub fn threshold_authorized(approvals: usize, m: usize) -> bool {
128    m > 0 && approvals >= m
129}
130
131// ─── Systemic circuit breaker (paraconsistent inconsistency spike) ────────────────
132
133/// Trip the systemic circuit breaker when inconsistency `saturation` (from
134/// `paraconsistent::local_saturation` / `global_saturation`) reaches `threshold`: the system
135/// halts into [`PolicyMode::Interactive`] (ask a human) rather than act on a saturated,
136/// self-contradictory graph. Returns the override mode if tripped, else `None`.
137pub fn circuit_breaker(saturation: f32, threshold: f32) -> Option<PolicyMode> {
138    if saturation >= threshold {
139        Some(PolicyMode::Interactive)
140    } else {
141        None
142    }
143}
144
145// ─── Proportionality binding (human-rights instruments) ───────────────────────────
146
147/// A governance action that **restricts individual agency** is justified only if proportionate —
148/// its `marginal_harm` to the person is strictly less than the `advantage` it secures. Binds
149/// algorithmic governance to the proportionality test of the human-rights instruments. A
150/// non-restricting action is always permitted.
151pub fn restriction_proportionate(
152    restricts_agency: bool,
153    marginal_harm: f64,
154    advantage: f64,
155) -> bool {
156    !restricts_agency || marginal_harm < advantage
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    fn g(non_derogable: bool, humanitarian: bool, ambiguous: bool) -> Governance {
164        Governance {
165            non_derogable,
166            humanitarian,
167            ambiguous,
168        }
169    }
170
171    #[test]
172    fn non_derogable_violation_is_preventive_block() {
173        let m = map_policy(DeonticStatus::Violated, g(true, false, false));
174        assert_eq!(m, PolicyMode::PreventiveBlock);
175        assert!(
176            !permits_execution(m),
177            "a non-derogable breach must NOT proceed"
178        );
179        assert_eq!(policy_action(m), "DenyRollback");
180    }
181
182    #[test]
183    fn ordinary_violation_is_permissive_audit() {
184        let m = map_policy(DeonticStatus::Violated, g(false, false, false));
185        assert_eq!(m, PolicyMode::PermissiveAudit);
186        assert!(
187            permits_execution(m),
188            "a non-critical breach proceeds but is recorded"
189        );
190    }
191
192    #[test]
193    fn humanitarian_in_force_is_prioritized() {
194        assert_eq!(
195            map_policy(DeonticStatus::Active, g(false, true, false)),
196            PolicyMode::Prioritize
197        );
198        // Non-humanitarian in force → just allow.
199        assert_eq!(
200            map_policy(DeonticStatus::Active, g(false, false, false)),
201            PolicyMode::Allow
202        );
203    }
204
205    #[test]
206    fn ambiguity_always_defers_to_a_human() {
207        // Ambiguity wins even over a non-derogable violation — the human decides the mapping.
208        assert_eq!(
209            map_policy(DeonticStatus::Violated, g(true, false, true)),
210            PolicyMode::Interactive
211        );
212        assert_eq!(
213            map_policy(DeonticStatus::Active, g(false, true, true)),
214            PolicyMode::Interactive
215        );
216        // Malformed verdicts also route to a human.
217        assert_eq!(
218            map_policy(DeonticStatus::Malformed, g(false, false, false)),
219            PolicyMode::Interactive
220        );
221    }
222
223    #[test]
224    fn non_binding_statuses_allow() {
225        for s in [
226            DeonticStatus::Pending,
227            DeonticStatus::Defeated,
228            DeonticStatus::Expired,
229        ] {
230            assert_eq!(map_policy(s, g(false, false, false)), PolicyMode::Allow);
231        }
232        // Discharged duty, humanitarian context → still prioritized.
233        assert_eq!(
234            map_policy(DeonticStatus::Discharged, g(false, true, false)),
235            PolicyMode::Prioritize
236        );
237    }
238
239    #[test]
240    fn humanitarian_emergency_overrides_non_core_blocks_only() {
241        // An ordinary non-derogable block downgrades to audit under emergency…
242        assert_eq!(
243            apply_emergency_override(PolicyMode::PreventiveBlock, true, false),
244            PolicyMode::PermissiveAudit
245        );
246        // …but the hard core (torture / child safety) NEVER bypasses.
247        assert_eq!(
248            apply_emergency_override(PolicyMode::PreventiveBlock, true, true),
249            PolicyMode::PreventiveBlock
250        );
251        // No emergency → unchanged; non-block modes pass through.
252        assert_eq!(
253            apply_emergency_override(PolicyMode::PreventiveBlock, false, false),
254            PolicyMode::PreventiveBlock
255        );
256        assert_eq!(
257            apply_emergency_override(PolicyMode::Allow, true, false),
258            PolicyMode::Allow
259        );
260    }
261
262    #[test]
263    fn m_of_n_threshold_governance() {
264        assert!(threshold_authorized(3, 3));
265        assert!(threshold_authorized(4, 3));
266        assert!(!threshold_authorized(2, 3));
267        assert!(
268            !threshold_authorized(0, 0),
269            "a decision needs at least one approver"
270        );
271    }
272
273    #[test]
274    fn circuit_breaker_trips_on_inconsistency_spike() {
275        assert_eq!(circuit_breaker(0.9, 0.8), Some(PolicyMode::Interactive));
276        assert_eq!(circuit_breaker(0.5, 0.8), None);
277    }
278
279    #[test]
280    fn agency_restriction_must_be_proportionate() {
281        // A restriction whose harm < advantage is justified; harm ≥ advantage is not.
282        assert!(restriction_proportionate(true, 1.0, 5.0));
283        assert!(!restriction_proportionate(true, 5.0, 1.0));
284        // A non-restricting action is always permitted.
285        assert!(restriction_proportionate(false, 100.0, 0.0));
286    }
287}