Skip to main content

exochain_sdk/
kernel.rs

1// Copyright 2026 Exochain Foundation
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at:
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15// SPDX-License-Identifier: Apache-2.0
16
17//! Constitutional Governance Runtime (CGR) Kernel interface.
18//!
19//! The **CGR kernel** is the heart of EXOCHAIN. Every action that matters —
20//! reading data, delegating authority, invoking a tool — is first submitted
21//! to the kernel, which checks the action against the constitution and the
22//! eight structural invariants (including `NoSelfGrant`, `ConsentRequired`,
23//! and `KernelImmutability`). Only if the kernel returns `Permitted` does the
24//! action run.
25//!
26//! [`ConstitutionalKernel`] is a simplified, ergonomic wrapper around
27//! [`exo_gatekeeper::Kernel`]. It initialises the kernel with the default
28//! EXOCHAIN constitution text and the full set of eight constitutional
29//! invariants. Adjudication requires caller-supplied authority signing material
30//! so the SDK never fabricates a trust root.
31//!
32//! ## Why use this module
33//!
34//! - You want to ask "is this action permitted?" without having to construct
35//!   the full [`exo_gatekeeper::AdjudicationContext`] by hand.
36//! - You want to exercise specific invariants (self-grant, kernel
37//!   modification, consent-required) in tests via the named `adjudicate_*`
38//!   helpers.
39//! - You want the same verdict enum to flow through your application and
40//!   your test vectors.
41//!
42//! ## Quick start
43//!
44//! ```
45//! use exochain_sdk::kernel::ConstitutionalKernel;
46//! use exo_core::Did;
47//!
48//! let authority = exochain_sdk::identity::Identity::generate("authority");
49//! let actor = exochain_sdk::identity::Identity::generate("alice");
50//! let kernel = ConstitutionalKernel::with_authority_identity(authority);
51//! let verdict = kernel.adjudicate_as(&actor, "data:medical:read");
52//! assert!(verdict.is_permitted());
53//! ```
54
55use std::sync::Arc;
56
57use exo_core::{Did, Hash256, PublicKey, Signature};
58use exo_gatekeeper::{
59    ActionRequest, AdjudicationContext, InvariantSet, Kernel, Verdict,
60    authority_link_signature_message, provenance_signature_message,
61    types::{
62        AuthorityChain, AuthorityLink, BailmentState, ConsentRecord, GovernmentBranch, Permission,
63        PermissionSet, Provenance, Role, TrustedAuthorityKeys, TrustedProvenanceKeys,
64    },
65};
66use serde::{Deserialize, Serialize};
67
68/// The default constitution bytes used by [`ConstitutionalKernel::new`].
69const DEFAULT_CONSTITUTION: &[u8] = b"EXOCHAIN Constitution v1.0: \
70    We the people of the EXOCHAIN fabric establish this constitution \
71    to secure the blessings of ordered, consented, and auditable agency.";
72
73/// Expected number of constitutional invariants enforced by the kernel.
74const INVARIANT_COUNT: usize = 8;
75
76/// Verdict returned by the SDK kernel.
77///
78/// This mirrors [`exo_gatekeeper::Verdict`] but flattens the violation list
79/// to a simple `Vec<String>` so SDK consumers do not need to depend on the
80/// full gatekeeper types.
81///
82/// # Examples
83///
84/// ```
85/// use exochain_sdk::kernel::KernelVerdict;
86///
87/// let ok = KernelVerdict::Permitted;
88/// assert!(ok.is_permitted());
89///
90/// let denied = KernelVerdict::Denied { violations: vec!["NoSelfGrant".into()] };
91/// assert!(denied.is_denied());
92///
93/// let escalated = KernelVerdict::Escalated { reason: "human review".into() };
94/// assert!(escalated.is_escalated());
95/// ```
96#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
97pub enum KernelVerdict {
98    /// The action is permitted.
99    Permitted,
100    /// The action is denied — one or more invariants were violated.
101    Denied {
102        /// Human-readable descriptions of the violated invariants.
103        violations: Vec<String>,
104    },
105    /// The action has been escalated for review.
106    Escalated {
107        /// Human-readable reason for escalation.
108        reason: String,
109    },
110}
111
112impl KernelVerdict {
113    /// Returns `true` if the verdict is [`KernelVerdict::Permitted`].
114    ///
115    /// # Examples
116    ///
117    /// ```
118    /// # use exochain_sdk::kernel::KernelVerdict;
119    /// assert!(KernelVerdict::Permitted.is_permitted());
120    /// assert!(!KernelVerdict::Denied { violations: vec![] }.is_permitted());
121    /// ```
122    #[must_use]
123    pub fn is_permitted(&self) -> bool {
124        matches!(self, Self::Permitted)
125    }
126
127    /// Returns `true` if the verdict is [`KernelVerdict::Denied`].
128    ///
129    /// # Examples
130    ///
131    /// ```
132    /// # use exochain_sdk::kernel::KernelVerdict;
133    /// let v = KernelVerdict::Denied { violations: vec!["NoSelfGrant".into()] };
134    /// assert!(v.is_denied());
135    /// ```
136    #[must_use]
137    pub fn is_denied(&self) -> bool {
138        matches!(self, Self::Denied { .. })
139    }
140
141    /// Returns `true` if the verdict is [`KernelVerdict::Escalated`].
142    ///
143    /// # Examples
144    ///
145    /// ```
146    /// # use exochain_sdk::kernel::KernelVerdict;
147    /// let v = KernelVerdict::Escalated { reason: "human in the loop".into() };
148    /// assert!(v.is_escalated());
149    /// ```
150    #[must_use]
151    pub fn is_escalated(&self) -> bool {
152        matches!(self, Self::Escalated { .. })
153    }
154}
155
156/// An ergonomic wrapper around the CGR [`Kernel`].
157///
158/// Provides a minimal adjudication interface suitable for common SDK use
159/// cases: a single actor performing an action, with caller-supplied authority
160/// signing material, actor-signed provenance, and an active bailment from that
161/// authority. Callers needing fine-grained control over the adjudication
162/// context should use [`exo_gatekeeper::Kernel`] directly.
163///
164/// # Examples
165///
166/// ```
167/// use exochain_sdk::kernel::ConstitutionalKernel;
168/// use exo_core::Did;
169///
170/// let authority = exochain_sdk::identity::Identity::generate("authority");
171/// let actor = exochain_sdk::identity::Identity::generate("alice");
172/// let kernel = ConstitutionalKernel::with_authority_identity(authority);
173/// assert!(kernel.verify_integrity());
174/// assert_eq!(kernel.invariant_count(), 8);
175///
176/// let verdict = kernel.adjudicate_as(&actor, "read:profile");
177/// assert!(verdict.is_permitted());
178/// ```
179pub struct ConstitutionalKernel {
180    inner: Kernel,
181    constitution: Vec<u8>,
182    authority: Option<KernelAuthority>,
183}
184
185type AuthoritySigner = Arc<dyn Fn(&[u8]) -> Signature + Send + Sync>;
186
187struct KernelAuthority {
188    did: Did,
189    public_key: PublicKey,
190    signer: AuthoritySigner,
191}
192
193#[derive(Clone, Copy)]
194struct AdjudicationFlags {
195    is_self_grant: bool,
196    modifies_kernel: bool,
197    include_bailment: bool,
198    human_override_preserved: bool,
199}
200
201impl AdjudicationFlags {
202    const DEFAULT: Self = Self {
203        is_self_grant: false,
204        modifies_kernel: false,
205        include_bailment: true,
206        human_override_preserved: true,
207    };
208
209    const SELF_GRANT: Self = Self {
210        is_self_grant: true,
211        ..Self::DEFAULT
212    };
213
214    const KERNEL_MODIFICATION: Self = Self {
215        modifies_kernel: true,
216        ..Self::DEFAULT
217    };
218
219    const WITHOUT_BAILMENT: Self = Self {
220        include_bailment: false,
221        ..Self::DEFAULT
222    };
223}
224
225struct ActorProvenanceSigner<'a> {
226    actor: &'a Did,
227    public_key: &'a PublicKey,
228    signer: &'a dyn Fn(&[u8]) -> Signature,
229}
230
231impl ConstitutionalKernel {
232    /// Construct a new kernel with the default constitution and all eight
233    /// constitutional invariants.
234    ///
235    /// # Examples
236    ///
237    /// ```
238    /// # use exochain_sdk::kernel::ConstitutionalKernel;
239    /// let kernel = ConstitutionalKernel::new();
240    /// assert_eq!(kernel.invariant_count(), 8);
241    /// assert!(kernel.verify_integrity());
242    /// ```
243    #[must_use]
244    pub fn new() -> Self {
245        Self {
246            inner: Kernel::new(DEFAULT_CONSTITUTION, InvariantSet::all()),
247            constitution: DEFAULT_CONSTITUTION.to_vec(),
248            authority: None,
249        }
250    }
251
252    /// Construct a new kernel with an authority signing identity.
253    ///
254    /// This is the common SDK path: the identity's DID becomes the authority
255    /// grantor and bailor for the default adjudication context, and its secret
256    /// key signs the canonical authority payloads that the kernel verifies.
257    /// Actor provenance must still be signed by the actor identity through
258    /// [`Self::adjudicate_as`].
259    ///
260    /// # Examples
261    ///
262    /// ```
263    /// # use exochain_sdk::{identity::Identity, kernel::ConstitutionalKernel};
264    /// let authority = Identity::generate("authority");
265    /// let kernel = ConstitutionalKernel::with_authority_identity(authority);
266    /// assert!(kernel.verify_integrity());
267    /// ```
268    #[must_use]
269    pub fn with_authority_identity(authority: crate::identity::Identity) -> Self {
270        let authority_did = authority.did().clone();
271        let authority_public_key = *authority.public_key();
272        Self::with_authority(
273            authority_did,
274            authority_public_key,
275            Arc::new(move |message: &[u8]| authority.sign(message)),
276        )
277    }
278
279    /// Construct a new kernel with caller-supplied authority signing material.
280    ///
281    /// The signer must produce an Ed25519 signature over the message bytes it
282    /// receives. The supplied public key is embedded in the adjudication
283    /// context, and the gatekeeper verifies every signature cryptographically.
284    ///
285    /// # Examples
286    ///
287    /// ```
288    /// # use std::sync::Arc;
289    /// # use exochain_sdk::{crypto, kernel::ConstitutionalKernel};
290    /// let authority_did = crypto::Did::new("did:exo:authority").expect("valid");
291    /// let (authority_public_key, authority_secret_key) = crypto::generate_keypair();
292    /// let kernel = ConstitutionalKernel::with_authority(
293    ///     authority_did,
294    ///     authority_public_key,
295    ///     Arc::new(move |message: &[u8]| crypto::sign(message, &authority_secret_key)),
296    /// );
297    /// assert!(kernel.verify_integrity());
298    /// ```
299    #[must_use]
300    pub fn with_authority(
301        authority_did: Did,
302        authority_public_key: PublicKey,
303        authority_signer: AuthoritySigner,
304    ) -> Self {
305        Self {
306            inner: Kernel::new(DEFAULT_CONSTITUTION, InvariantSet::all()),
307            constitution: DEFAULT_CONSTITUTION.to_vec(),
308            authority: Some(KernelAuthority {
309                did: authority_did,
310                public_key: authority_public_key,
311                signer: authority_signer,
312            }),
313        }
314    }
315
316    /// Adjudicate `action` performed by `actor` using the signed SDK context.
317    ///
318    /// The SDK supplies a minimal signed default context:
319    /// - A single Judicial role for `actor`.
320    /// - A one-link authority chain from the configured authority to `actor`
321    ///   granting `read`.
322    /// - An active bailment from the configured authority to `actor` scoped to
323    ///   the requested permission set.
324    /// - Full human-override preservation.
325    /// - Signed provenance with timestamp `"sdk"` when `actor` is the
326    ///   authority DID itself.
327    ///
328    /// If the kernel was created with [`Self::new`] rather than
329    /// [`Self::with_authority`] or [`Self::with_authority_identity`], this
330    /// method fails closed with a denied verdict.
331    ///
332    /// Passing an arbitrary DID without its signer fails closed. Use
333    /// [`Self::adjudicate_as`] when acting for a distinct SDK identity, or
334    /// reach for [`exo_gatekeeper::Kernel`] directly when building a fully
335    /// resolved adjudication context.
336    ///
337    /// The action is flagged as `is_self_grant = false` and
338    /// `modifies_kernel = false` by default. Helpers are available for the
339    /// common deny-cases used in tests: see
340    /// [`Self::adjudicate_self_grant`],
341    /// [`Self::adjudicate_kernel_modification`], and
342    /// [`Self::adjudicate_without_bailment`].
343    ///
344    /// # Examples
345    ///
346    /// ```
347    /// use exochain_sdk::kernel::ConstitutionalKernel;
348    ///
349    /// let authority = exochain_sdk::identity::Identity::generate("authority");
350    /// let actor = authority.did().clone();
351    /// let kernel = ConstitutionalKernel::with_authority_identity(authority);
352    /// let verdict = kernel.adjudicate(&actor, "data:read");
353    /// assert!(verdict.is_permitted());
354    /// ```
355    #[must_use]
356    pub fn adjudicate(&self, actor: &Did, action: &str) -> KernelVerdict {
357        self.adjudicate_internal(actor, action, AdjudicationFlags::DEFAULT)
358    }
359
360    /// Adjudicate `action` performed by `actor` using actor-signed
361    /// provenance and authority-signed delegation.
362    ///
363    /// This is the normal SDK path for actions performed by a principal other
364    /// than the configured authority. The actor identity signs the provenance
365    /// payload, and the trusted provenance key map is populated from that
366    /// same actor identity rather than from the authority signer.
367    #[must_use]
368    pub fn adjudicate_as(&self, actor: &crate::identity::Identity, action: &str) -> KernelVerdict {
369        self.adjudicate_identity_internal(actor, action, AdjudicationFlags::DEFAULT)
370    }
371
372    /// Same as [`Self::adjudicate`] but sets `is_self_grant = true` so the
373    /// kernel can enforce the `NoSelfGrant` invariant.
374    ///
375    /// Useful for exercising the invariant in tests: a permitted verdict
376    /// here would indicate a constitutional defect.
377    ///
378    /// # Examples
379    ///
380    /// ```
381    /// use exochain_sdk::kernel::ConstitutionalKernel;
382    ///
383    /// let authority = exochain_sdk::identity::Identity::generate("authority");
384    /// let actor = exochain_sdk::identity::Identity::generate("self-granter");
385    /// let kernel = ConstitutionalKernel::with_authority_identity(authority);
386    /// let verdict = kernel.adjudicate_self_grant_as(&actor, "escalate-self");
387    /// assert!(verdict.is_denied());
388    /// ```
389    #[must_use]
390    pub fn adjudicate_self_grant(&self, actor: &Did, action: &str) -> KernelVerdict {
391        self.adjudicate_internal(actor, action, AdjudicationFlags::SELF_GRANT)
392    }
393
394    /// Same as [`Self::adjudicate_as`] but sets `is_self_grant = true`.
395    #[must_use]
396    pub fn adjudicate_self_grant_as(
397        &self,
398        actor: &crate::identity::Identity,
399        action: &str,
400    ) -> KernelVerdict {
401        self.adjudicate_identity_internal(actor, action, AdjudicationFlags::SELF_GRANT)
402    }
403
404    /// Same as [`Self::adjudicate`] but sets `modifies_kernel = true` so the
405    /// kernel can enforce the `KernelImmutability` invariant.
406    ///
407    /// # Examples
408    ///
409    /// ```
410    /// use exochain_sdk::kernel::ConstitutionalKernel;
411    ///
412    /// let authority = exochain_sdk::identity::Identity::generate("authority");
413    /// let actor = exochain_sdk::identity::Identity::generate("patcher");
414    /// let kernel = ConstitutionalKernel::with_authority_identity(authority);
415    /// let verdict = kernel.adjudicate_kernel_modification_as(&actor, "patch-kernel");
416    /// assert!(verdict.is_denied());
417    /// ```
418    #[must_use]
419    pub fn adjudicate_kernel_modification(&self, actor: &Did, action: &str) -> KernelVerdict {
420        self.adjudicate_internal(actor, action, AdjudicationFlags::KERNEL_MODIFICATION)
421    }
422
423    /// Same as [`Self::adjudicate_as`] but sets `modifies_kernel = true`.
424    #[must_use]
425    pub fn adjudicate_kernel_modification_as(
426        &self,
427        actor: &crate::identity::Identity,
428        action: &str,
429    ) -> KernelVerdict {
430        self.adjudicate_identity_internal(actor, action, AdjudicationFlags::KERNEL_MODIFICATION)
431    }
432
433    /// Same as [`Self::adjudicate`] but omits the default bailment so the
434    /// kernel can enforce the `ConsentRequired` invariant.
435    ///
436    /// # Examples
437    ///
438    /// ```
439    /// use exochain_sdk::kernel::ConstitutionalKernel;
440    ///
441    /// let authority = exochain_sdk::identity::Identity::generate("authority");
442    /// let actor = exochain_sdk::identity::Identity::generate("unauth");
443    /// let kernel = ConstitutionalKernel::with_authority_identity(authority);
444    /// let verdict = kernel.adjudicate_without_bailment_as(&actor, "read-data");
445    /// assert!(verdict.is_denied());
446    /// ```
447    #[must_use]
448    pub fn adjudicate_without_bailment(&self, actor: &Did, action: &str) -> KernelVerdict {
449        self.adjudicate_internal(actor, action, AdjudicationFlags::WITHOUT_BAILMENT)
450    }
451
452    /// Same as [`Self::adjudicate_as`] but omits the default bailment.
453    #[must_use]
454    pub fn adjudicate_without_bailment_as(
455        &self,
456        actor: &crate::identity::Identity,
457        action: &str,
458    ) -> KernelVerdict {
459        self.adjudicate_identity_internal(actor, action, AdjudicationFlags::WITHOUT_BAILMENT)
460    }
461
462    /// Verify that the kernel's stored constitution hash matches the
463    /// configured constitution text.
464    ///
465    /// Returns `false` if the constitution in memory has drifted from the
466    /// hash the kernel was initialised with — which should never happen in
467    /// practice, but is checked defensively because constitutional integrity
468    /// is a load-bearing invariant.
469    ///
470    /// # Examples
471    ///
472    /// ```
473    /// # use exochain_sdk::kernel::ConstitutionalKernel;
474    /// let kernel = ConstitutionalKernel::new();
475    /// assert!(kernel.verify_integrity());
476    /// ```
477    #[must_use]
478    pub fn verify_integrity(&self) -> bool {
479        self.inner.verify_kernel_integrity(&self.constitution)
480    }
481
482    /// Number of constitutional invariants enforced by this kernel (always 8).
483    ///
484    /// # Examples
485    ///
486    /// ```
487    /// # use exochain_sdk::kernel::ConstitutionalKernel;
488    /// assert_eq!(ConstitutionalKernel::new().invariant_count(), 8);
489    /// ```
490    #[must_use]
491    pub fn invariant_count(&self) -> usize {
492        INVARIANT_COUNT
493    }
494
495    fn signed_authority_link(
496        authority: &KernelAuthority,
497        grantee: &Did,
498        permissions: &PermissionSet,
499    ) -> exo_core::Result<AuthorityLink> {
500        let mut link = AuthorityLink {
501            grantor: authority.did.clone(),
502            grantee: grantee.clone(),
503            permissions: permissions.clone(),
504            signature: Vec::new(),
505            grantor_public_key: Some(authority.public_key.as_bytes().to_vec()),
506        };
507        let message = authority_link_signature_message(&link)?;
508        let signature = (authority.signer)(message.as_bytes());
509        link.signature = signature.to_bytes().to_vec();
510        Ok(link)
511    }
512
513    fn signed_provenance(
514        actor: &Did,
515        action: &str,
516        actor_public_key: &PublicKey,
517        actor_signer: &dyn Fn(&[u8]) -> Signature,
518    ) -> exo_core::Result<Provenance> {
519        let timestamp = "sdk".to_owned();
520        let action_hash = Hash256::digest(action.as_bytes()).as_bytes().to_vec();
521
522        let mut provenance = Provenance {
523            actor: actor.clone(),
524            timestamp,
525            action_hash,
526            signature: Vec::new(),
527            public_key: Some(actor_public_key.as_bytes().to_vec()),
528            voice_kind: None,
529            independence: None,
530            review_order: None,
531        };
532        let message = provenance_signature_message(&provenance)?;
533        let signature = actor_signer(message.as_bytes());
534        provenance.signature = signature.to_bytes().to_vec();
535        Ok(provenance)
536    }
537
538    fn permission_scope(permissions: &PermissionSet) -> String {
539        let mut labels: Vec<&str> = permissions
540            .permissions
541            .iter()
542            .map(|permission| permission.0.as_str())
543            .collect();
544        labels.sort_unstable();
545        labels.dedup();
546        labels.join(";")
547    }
548
549    fn adjudicate_identity_internal(
550        &self,
551        actor: &crate::identity::Identity,
552        action: &str,
553        flags: AdjudicationFlags,
554    ) -> KernelVerdict {
555        let signer = |message: &[u8]| actor.sign(message);
556        self.adjudicate_internal_with_actor_provenance(
557            action,
558            flags,
559            ActorProvenanceSigner {
560                actor: actor.did(),
561                public_key: actor.public_key(),
562                signer: &signer,
563            },
564        )
565    }
566
567    fn adjudicate_internal(
568        &self,
569        actor: &Did,
570        action: &str,
571        flags: AdjudicationFlags,
572    ) -> KernelVerdict {
573        let Some(authority) = self.authority.as_ref() else {
574            return KernelVerdict::Denied {
575                violations: vec![
576                    "AuthorityChainValid: SDK authority signer is required for adjudication".into(),
577                ],
578            };
579        };
580        if actor != &authority.did {
581            return KernelVerdict::Denied {
582                violations: vec![
583                    "ProvenanceVerifiable: SDK actor identity signer is required for non-authority actor provenance".into(),
584                ],
585            };
586        }
587        self.adjudicate_internal_with_actor_provenance(
588            action,
589            flags,
590            ActorProvenanceSigner {
591                actor,
592                public_key: &authority.public_key,
593                signer: authority.signer.as_ref(),
594            },
595        )
596    }
597
598    fn adjudicate_internal_with_actor_provenance(
599        &self,
600        action: &str,
601        flags: AdjudicationFlags,
602        actor_provenance: ActorProvenanceSigner<'_>,
603    ) -> KernelVerdict {
604        let Some(authority) = self.authority.as_ref() else {
605            return KernelVerdict::Denied {
606                violations: vec![
607                    "AuthorityChainValid: SDK authority signer is required for adjudication".into(),
608                ],
609            };
610        };
611        let permissions = PermissionSet::new(vec![Permission::new("read")]);
612        let request = ActionRequest {
613            actor: actor_provenance.actor.clone(),
614            action: action.to_owned(),
615            required_permissions: permissions.clone(),
616            is_self_grant: flags.is_self_grant,
617            modifies_kernel: flags.modifies_kernel,
618        };
619
620        let scope = Self::permission_scope(&permissions);
621
622        let (bailment_state, consent_records) = if flags.include_bailment {
623            (
624                BailmentState::Active {
625                    bailor: authority.did.clone(),
626                    bailee: actor_provenance.actor.clone(),
627                    scope: scope.clone(),
628                },
629                vec![ConsentRecord {
630                    subject: authority.did.clone(),
631                    granted_to: actor_provenance.actor.clone(),
632                    scope,
633                    active: true,
634                }],
635            )
636        } else {
637            (BailmentState::None, Vec::new())
638        };
639
640        let authority_link = match Self::signed_authority_link(
641            authority,
642            actor_provenance.actor,
643            &permissions,
644        ) {
645            Ok(link) => link,
646            Err(err) => {
647                return KernelVerdict::Denied {
648                    violations: vec![format!(
649                        "AuthorityChainValid: canonical authority signature payload failed: {err}"
650                    )],
651                };
652            }
653        };
654        let provenance = match Self::signed_provenance(
655            actor_provenance.actor,
656            action,
657            actor_provenance.public_key,
658            actor_provenance.signer,
659        ) {
660            Ok(provenance) => provenance,
661            Err(err) => {
662                return KernelVerdict::Denied {
663                    violations: vec![format!(
664                        "ProvenanceVerifiable: canonical provenance signature payload failed: {err}"
665                    )],
666                };
667            }
668        };
669
670        let authority_chain = AuthorityChain {
671            links: vec![authority_link],
672        };
673        let mut trusted_authority_keys = TrustedAuthorityKeys::default();
674        for link in &authority_chain.links {
675            if let Some(public_key) = &link.grantor_public_key {
676                trusted_authority_keys.insert(link.grantor.clone(), vec![public_key.clone()]);
677            }
678        }
679        let mut trusted_provenance_keys = TrustedProvenanceKeys::default();
680        trusted_provenance_keys.insert(
681            actor_provenance.actor.clone(),
682            vec![actor_provenance.public_key.as_bytes().to_vec()],
683        );
684
685        let context = AdjudicationContext {
686            actor_roles: vec![Role {
687                name: "judge".into(),
688                branch: GovernmentBranch::Judicial,
689            }],
690            authority_chain,
691            consent_records,
692            bailment_state,
693            human_override_preserved: flags.human_override_preserved,
694            actor_permissions: permissions,
695            trusted_authority_keys,
696            trusted_provenance_keys,
697            provenance: Some(provenance),
698            quorum_evidence: None,
699            active_challenge_reason: None,
700        };
701
702        match self.inner.adjudicate(&request, &context) {
703            Verdict::Permitted => KernelVerdict::Permitted,
704            Verdict::Denied { violations } => KernelVerdict::Denied {
705                violations: violations
706                    .into_iter()
707                    .map(|v| format!("{}: {}", v.invariant.id(), v.description))
708                    .collect(),
709            },
710            Verdict::Escalated { reason } => KernelVerdict::Escalated { reason },
711        }
712    }
713}
714
715impl Default for ConstitutionalKernel {
716    fn default() -> Self {
717        Self::new()
718    }
719}
720
721impl core::fmt::Debug for ConstitutionalKernel {
722    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
723        f.debug_struct("ConstitutionalKernel")
724            .field("invariant_count", &INVARIANT_COUNT)
725            .finish()
726    }
727}
728
729// ===========================================================================
730// Tests
731// ===========================================================================
732
733#[cfg(test)]
734#[allow(clippy::expect_used, clippy::unwrap_used)]
735mod tests {
736    use super::*;
737
738    fn did(s: &str) -> Did {
739        Did::new(s).expect("valid DID")
740    }
741
742    fn signed_kernel() -> ConstitutionalKernel {
743        ConstitutionalKernel::with_authority_identity(crate::identity::Identity::generate(
744            "sdk-authority",
745        ))
746    }
747
748    #[test]
749    fn new_initialises_with_eight_invariants() {
750        let k = ConstitutionalKernel::new();
751        assert_eq!(k.invariant_count(), 8);
752    }
753
754    #[test]
755    fn verify_integrity_holds_after_new() {
756        let k = ConstitutionalKernel::new();
757        assert!(k.verify_integrity());
758    }
759
760    #[test]
761    fn default_matches_new() {
762        let a = ConstitutionalKernel::default();
763        let b = ConstitutionalKernel::new();
764        assert_eq!(a.invariant_count(), b.invariant_count());
765        assert_eq!(a.verify_integrity(), b.verify_integrity());
766    }
767
768    #[test]
769    fn valid_action_permitted() {
770        let k = signed_kernel();
771        let actor = crate::identity::Identity::generate("valid-actor");
772        let verdict = k.adjudicate_as(&actor, "read-medical-record");
773        assert!(
774            verdict.is_permitted(),
775            "expected Permitted, got {verdict:?}"
776        );
777    }
778
779    #[test]
780    fn authority_signer_cannot_masquerade_as_a_different_actor() {
781        let k = signed_kernel();
782        let actor = did("did:exo:independent-actor");
783        let verdict = k.adjudicate(&actor, "read-medical-record");
784        assert!(
785            verdict.is_denied(),
786            "authority signing material must not be promoted as another actor's provenance key: {verdict:?}"
787        );
788        match verdict {
789            KernelVerdict::Denied { violations } => {
790                assert!(
791                    violations.iter().any(|v| v.contains("Provenance")),
792                    "denial must identify provenance key binding: {violations:?}"
793                );
794            }
795            other => panic!("expected Denied, got {other:?}"),
796        }
797    }
798
799    #[test]
800    fn adjudicate_without_authority_signer_fails_closed() {
801        let k = ConstitutionalKernel::new();
802        let actor = did("did:exo:valid-actor");
803        let verdict = k.adjudicate(&actor, "read-medical-record");
804        assert!(verdict.is_denied(), "expected Denied, got {verdict:?}");
805        match verdict {
806            KernelVerdict::Denied { violations } => {
807                assert!(violations.iter().any(|v| v.contains("authority signer")));
808            }
809            other => panic!("expected Denied, got {other:?}"),
810        }
811    }
812
813    #[test]
814    fn self_grant_denied() {
815        let k = signed_kernel();
816        let actor = crate::identity::Identity::generate("self-granter");
817        let verdict = k.adjudicate_self_grant_as(&actor, "escalate-self");
818        assert!(verdict.is_denied(), "expected Denied, got {verdict:?}");
819        match verdict {
820            KernelVerdict::Denied { violations } => {
821                assert!(
822                    violations
823                        .iter()
824                        .any(|violation| violation.starts_with("no-self-grant: ")),
825                    "violations must use stable invariant IDs: {violations:?}"
826                );
827            }
828            other => panic!("expected Denied, got {other:?}"),
829        }
830    }
831
832    #[test]
833    fn kernel_modification_denied() {
834        let k = signed_kernel();
835        let actor = crate::identity::Identity::generate("patcher");
836        let verdict = k.adjudicate_kernel_modification_as(&actor, "patch-kernel");
837        assert!(verdict.is_denied(), "expected Denied, got {verdict:?}");
838    }
839
840    #[test]
841    fn no_bailment_denied() {
842        let k = signed_kernel();
843        let actor = crate::identity::Identity::generate("unauth");
844        let verdict = k.adjudicate_without_bailment_as(&actor, "read-data");
845        assert!(verdict.is_denied(), "expected Denied, got {verdict:?}");
846    }
847
848    #[test]
849    fn sdk_violation_labels_do_not_depend_on_debug_formatting() {
850        let source = include_str!("kernel.rs")
851            .split("// ===========================================================================\n// Tests")
852            .next()
853            .expect("production section");
854        assert!(
855            !source.contains("format!(\"{:?}: {}\", v.invariant, v.description)"),
856            "SDK violation labels must use stable invariant IDs"
857        );
858    }
859
860    #[test]
861    fn sdk_source_does_not_synthesize_authority_key_as_actor_provenance() {
862        let source = include_str!("kernel.rs")
863            .split("// ===========================================================================\n// Tests")
864            .next()
865            .expect("production section");
866        assert!(
867            !source.contains(
868                "trusted_provenance_keys.insert(\n            actor.clone(),\n            vec![authority.public_key.as_bytes().to_vec()],"
869            ),
870            "SDK trusted provenance keys must come from actor identity material, not authority key substitution"
871        );
872    }
873
874    #[test]
875    fn verdict_helpers() {
876        assert!(KernelVerdict::Permitted.is_permitted());
877        assert!(!KernelVerdict::Permitted.is_denied());
878        let denied = KernelVerdict::Denied { violations: vec![] };
879        assert!(denied.is_denied());
880        assert!(!denied.is_permitted());
881        let esc = KernelVerdict::Escalated { reason: "r".into() };
882        assert!(esc.is_escalated());
883        assert!(!esc.is_permitted());
884    }
885
886    #[test]
887    fn verdict_serde_roundtrip() {
888        let v = KernelVerdict::Denied {
889            violations: vec!["NoSelfGrant: reason".into()],
890        };
891        let json = serde_json::to_string(&v).expect("ser");
892        let decoded: KernelVerdict = serde_json::from_str(&json).expect("de");
893        assert_eq!(v, decoded);
894    }
895
896    #[test]
897    fn debug_impl_smoke() {
898        let k = ConstitutionalKernel::new();
899        let dbg = format!("{k:?}");
900        assert!(dbg.contains("ConstitutionalKernel"));
901        assert!(dbg.contains("8"));
902    }
903}