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}