1use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
8use std::sync::{Arc, Mutex};
9
10use arc_swap::ArcSwap;
11use chio_log_redact::redacted;
12use dashmap::DashMap;
13
14use super::*;
15
16#[derive(Debug, thiserror::Error)]
19pub enum KernelBuildError {
20 #[error("invalid hot-path deadline config: {0}")]
21 InvalidDeadlineConfig(String),
22}
23
24impl ChioKernel {
25 pub(crate) fn with_sessions_read<R>(
26 &self,
27 f: impl FnOnce(&DashMap<SessionId, Arc<Session>>) -> Result<R, KernelError>,
28 ) -> Result<R, KernelError> {
29 f(&self.sessions)
30 }
31
32 pub(crate) fn with_sessions_write<R>(
33 &self,
34 f: impl FnOnce(&DashMap<SessionId, Arc<Session>>) -> Result<R, KernelError>,
35 ) -> Result<R, KernelError> {
36 f(&self.sessions)
37 }
38
39 pub(crate) fn with_session<R>(
40 &self,
41 session_id: &SessionId,
42 f: impl FnOnce(&Arc<Session>) -> Result<R, KernelError>,
43 ) -> Result<R, KernelError> {
44 let session = self
45 .sessions
46 .get(session_id)
47 .map(|entry| Arc::clone(entry.value()))
48 .ok_or_else(|| KernelError::UnknownSession(session_id.clone()))?;
49 f(&session)
50 }
51
52 pub(crate) fn resolve_tenant_id_for_session(
64 &self,
65 session_id: Option<&SessionId>,
66 ) -> Option<String> {
67 let id = session_id?;
68 self.with_session(id, |session| {
69 let auth_context = session.auth_context();
70 Ok(extract_tenant_id_from_auth_context(&auth_context))
71 })
72 .ok()
73 .flatten()
74 }
75
76 pub(crate) fn scope_receipt_tenant_id_for_request(
77 &self,
78 request_id: &str,
79 tenant_id: Option<String>,
80 ) -> ScopedKernelReceiptTenantId {
81 let previous = match tenant_id {
82 Some(tenant_id) => self
83 .receipt_tenant_ids
84 .insert(request_id.to_string(), tenant_id),
85 None => self
86 .receipt_tenant_ids
87 .remove(request_id)
88 .map(|(_, previous)| previous),
89 };
90 ScopedKernelReceiptTenantId {
91 request_id: request_id.to_string(),
92 tenant_ids: Arc::clone(&self.receipt_tenant_ids),
93 previous,
94 }
95 }
96
97 pub(crate) fn receipt_tenant_id_for_request(&self, request_id: Option<&str>) -> Option<String> {
98 request_id.and_then(|request_id| {
99 self.receipt_tenant_ids
100 .get(request_id)
101 .map(|entry| entry.value().clone())
102 })
103 }
104
105 pub(crate) fn with_session_mut<R>(
106 &self,
107 session_id: &SessionId,
108 f: impl FnOnce(&Arc<Session>) -> Result<R, KernelError>,
109 ) -> Result<R, KernelError> {
110 self.with_session(session_id, f)
111 }
112
113 pub(crate) fn with_budget_store<R>(
114 &self,
115 f: impl FnOnce(&dyn BudgetStore) -> Result<R, KernelError>,
116 ) -> Result<R, KernelError> {
117 let _guard = match self.budget_store_lock.lock() {
118 Ok(guard) => guard,
119 Err(poisoned) => poisoned.into_inner(),
120 };
121 f(self.budget_store.as_ref())
122 }
123
124 pub(crate) fn with_revocation_store<R>(
125 &self,
126 f: impl FnOnce(&dyn RevocationStore) -> Result<R, KernelError>,
127 ) -> Result<R, KernelError> {
128 f(self.revocation_store.as_ref())
129 }
130
131 pub(crate) fn with_receipt_store<R>(
132 &self,
133 f: impl FnOnce(&dyn ReceiptStore) -> Result<R, KernelError>,
134 ) -> Result<Option<R>, KernelError> {
135 let Some(store) = self.receipt_store.as_ref() else {
136 return Ok(None);
137 };
138 f(store.as_ref()).map(Some)
139 }
140
141 pub fn try_new(config: KernelConfig) -> Result<Self, KernelBuildError> {
147 config.deadlines.validate()?;
148 Ok(Self::new(config))
149 }
150
151 pub fn flush_receipt_writes_with_timeout(
159 &self,
160 timeout: std::time::Duration,
161 ) -> Result<Option<crate::ReceiptFlushReport>, KernelError> {
162 self.with_receipt_store(|store| {
163 store
164 .flush_receipt_writes_with_timeout(timeout)
165 .map_err(KernelError::from)
166 })
167 }
168
169 pub fn new(config: KernelConfig) -> Self {
170 info!("initializing Chio kernel");
171 let authority_keypair = config.keypair.clone();
172 let checkpoint_batch_size = config.checkpoint_batch_size;
173 let signing_keypair = config.keypair.clone();
180 let configured_stream_max =
198 usize::try_from(config.max_stream_total_bytes).unwrap_or(usize::MAX);
199 let signing_queued_budget = if configured_stream_max == 0 {
200 signing_task::DEFAULT_MAX_SIGNING_QUEUED_BYTES
201 } else {
202 configured_stream_max
203 };
204 let signing_task = std::sync::Arc::new(
205 signing_task::SigningTaskHandle::with_capacity_max_content_and_queued_bytes(
206 signing_keypair,
207 signing_task::DEFAULT_SIGNING_CHANNEL_CAPACITY,
208 0,
209 signing_queued_budget,
210 ),
211 );
212 let receipt_mirror_capacity = config.memory_budget_receipt_mirror_capacity();
215 let federation_cache_capacity = config.memory_budget_federation_cache_capacity();
216 let federation_cache_idle_ttl = config.memory_budget_federation_cache_idle_ttl_secs();
217 let rss_soft_limit_bytes = config.memory_budget.rss_soft_limit_bytes;
218 let rss_sample_interval_secs = config.memory_budget.rss_sample_interval_secs;
219 let receipt_mirror_gauge;
222 let child_receipt_mirror_gauge;
223 let federation_dual_receipts_gauge;
224 let federation_dsse_envelopes_gauge;
225 let mut kernel = Self {
226 config,
227 durable_admission_mode: crate::admission_operation::DurableAdmissionMode::default(),
228 durable_admission_runtime: None,
229 unsafe_ephemeral_financial_dispatch: false,
230 guards: std::sync::Arc::new(Vec::new()),
231 post_invocation_pipeline: crate::post_invocation::PostInvocationPipeline::new(),
232 budget_store: Arc::new(InMemoryBudgetStore::new()),
233 budget_store_lock: Mutex::new(()),
234 revocation_store: Arc::new(InMemoryRevocationStore::new()),
235 capability_authority: Box::new(LocalCapabilityAuthority::new(authority_keypair)),
236 tool_servers: HashMap::new(),
237 resource_providers: Vec::new(),
238 prompt_providers: Vec::new(),
239 sessions: DashMap::new(),
240 receipt_log: {
241 let gauge = chio_bounded::SizeGauge::new();
242 receipt_mirror_gauge = gauge.clone();
243 Mutex::new(ReceiptLog::with_capacity(receipt_mirror_capacity, gauge))
244 },
245 child_receipt_log: {
246 let gauge = chio_bounded::SizeGauge::new();
247 child_receipt_mirror_gauge = gauge.clone();
248 Mutex::new(ChildReceiptLog::with_capacity(
249 receipt_mirror_capacity,
250 gauge,
251 ))
252 },
253 receipt_mirror_gauge,
254 child_receipt_mirror_gauge,
255 receipt_store: None,
256 receipt_store_write_lock: Mutex::new(()),
257 retention_maintenance: None,
258 payment_adapter: None,
259 price_oracle: None,
260 runtime_admission_hook: None,
261 attestation_trust_policy: None,
262 capability_crypto_floor: KernelCryptoFloor::AllowClassical,
263 checkpoint_batch_size,
264 checkpoint_seq_counter: AtomicU64::new(0),
265 last_checkpoint_seq: AtomicU64::new(0),
266 dpop_nonce_store: None,
267 dpop_config: None,
268 execution_nonce_config: None,
269 execution_nonce_store: None,
270 approval_replay_store: Some(dpop::DpopNonceStore::new(
271 8192,
272 std::time::Duration::from_secs(3600),
273 )),
274 threshold_approval_requirement_resolver: None,
275 supplemental_quota_verifier: None,
276 emergency_stopped: AtomicBool::new(false),
277 emergency_stopped_since: AtomicU64::new(0),
278 emergency_stop_reason: ArcSwap::from_pointee(Option::<String>::None),
279 lock_poison: chio_supervisor::HealthFlag::new(true),
280 memory_provenance: None,
281 federation_peers: ArcSwap::from_pointee(HashMap::new()),
282 capability_trust_roots: ArcSwap::from_pointee(HashMap::new()),
283 capability_trust_roots_write_lock: Mutex::new(()),
284 federation_cosigner: None,
285 federation_dual_receipts: {
286 let gauge = chio_bounded::SizeGauge::new();
287 federation_dual_receipts_gauge = gauge.clone();
288 Mutex::new(chio_bounded::BoundedMap::new(
289 federation_cache_capacity,
290 federation_cache_idle_ttl,
291 gauge,
292 ))
293 },
294 federation_dual_receipts_gauge,
295 federation_dsse_envelopes: {
296 let gauge = chio_bounded::SizeGauge::new();
297 federation_dsse_envelopes_gauge = gauge.clone();
298 Mutex::new(chio_bounded::BoundedMap::new(
299 federation_cache_capacity,
300 federation_cache_idle_ttl,
301 gauge,
302 ))
303 },
304 federation_dsse_envelopes_gauge,
305 federation_artifact_store: None,
306 receipt_tenant_ids: Arc::new(DashMap::new()),
307 receipt_federation_admissions: Arc::new(DashMap::new()),
308 federation_local_kernel_id: ArcSwap::from_pointee(Option::<String>::None),
309 signing_task,
310 settlement_observer: None,
311 revocation_view: None,
312 budget_registry: Mutex::new(chio_kernel_core::InMemoryBudgetRegistry::new()),
313 reserved_sibling_shares: Mutex::new(HashMap::new()),
314 restart_reserved_hold_gate: Mutex::new(kernel_struct::RestartReservedHoldGate::Clear),
315 rss_shed: Arc::new(AtomicBool::new(false)),
316 rss_sampler: None,
317 receipt_writer_watchdog: std::sync::Arc::new(
318 receipt_writer_watchdog::ReceiptWriterWatchdogHandle::new(),
319 ),
320 };
321 if let Some(limit) = rss_soft_limit_bytes {
324 kernel.rss_sampler = Some(kernel_struct::RssSamplerHandle::spawn(
325 Arc::clone(&kernel.rss_shed),
326 limit,
327 rss_sample_interval_secs,
328 ));
329 }
330 kernel
331 }
332
333 pub(crate) fn ensure_federated_receipt_persistence_ready(
334 &self,
335 remote_kernel_id: Option<&str>,
336 ) -> Result<(), KernelError> {
337 if remote_kernel_id.is_none() {
338 return Ok(());
339 }
340 match &self.receipt_store {
341 None => Err(KernelError::Internal(
342 "federated receipt persistence unavailable: no durable receipt store configured"
343 .to_string(),
344 )),
345 Some(store) if store.writer_serving_closed() => Err(KernelError::Internal(
346 "federated receipt persistence degraded: commit writer is not serving".to_string(),
347 )),
348 Some(_) => Ok(()),
349 }
350 }
351
352 pub(crate) fn record_tcb_lock_poison(&self, lock: &str) {
358 self.lock_poison.record_failure(
359 format!("{lock} lock poisoned by a prior panic"),
360 chio_supervisor::now_unix_ms(),
361 0,
362 );
363 }
364
365 pub(crate) fn ensure_tcb_locks_healthy(&self) -> Result<(), KernelError> {
369 if self.lock_poison.is_serving_closed() {
370 return Err(KernelError::Internal(
371 "trusted state degraded: a TCB lock was poisoned by a prior panic".to_string(),
372 ));
373 }
374 Ok(())
375 }
376
377 pub(crate) fn ensure_receipt_persistence_ready(&self) -> Result<(), KernelError> {
378 if let Some(store) = &self.receipt_store {
379 if store.writer_serving_closed() {
382 return Err(KernelError::Internal(
383 "durable receipt persistence degraded: commit writer is not serving"
384 .to_string(),
385 ));
386 }
387 }
388 if self.receipt_store.is_none() && !self.config.allow_ephemeral_receipt_log {
389 return Err(KernelError::Internal(
390 "durable receipt persistence unavailable: no receipt store configured".to_string(),
391 ));
392 }
393 let liveness = self.receipt_writer_liveness();
400 if liveness.healthy() {
401 Ok(())
402 } else {
403 Err(KernelError::ReceiptWriterUnavailable(format!(
404 "receipt commit writer is {liveness:?}; denying before dispatch"
405 )))
406 }
407 }
408
409 pub(crate) fn receipt_writer_liveness(&self) -> crate::receipt_store::ReceiptWriterLiveness {
418 let published = self.receipt_writer_watchdog.current();
419 if published != crate::receipt_store::ReceiptWriterLiveness::Unknown {
420 return published;
421 }
422 self.sample_receipt_writer_liveness()
423 }
424
425 fn sample_receipt_writer_liveness(&self) -> crate::receipt_store::ReceiptWriterLiveness {
430 let stall_threshold =
431 std::time::Duration::from_millis(self.config.deadlines.receipt_writer_stall_ms);
432 match self.with_receipt_store(|store| Ok(store.writer_liveness(stall_threshold))) {
433 Ok(Some(verdict)) => verdict,
434 _ => crate::receipt_store::ReceiptWriterLiveness::Unknown,
435 }
436 }
437
438 #[cfg(test)]
442 pub(crate) fn refresh_receipt_writer_liveness_for_test(&self) {
443 self.receipt_writer_watchdog
444 .publish(self.sample_receipt_writer_liveness());
445 }
446
447 #[cfg(test)]
449 pub(crate) fn receipt_writer_watchdog_is_running(&self) -> bool {
450 self.receipt_writer_watchdog.is_running()
451 }
452
453 pub fn spawn_receipt_writer_watchdog(self: &std::sync::Arc<Self>) {
457 if !super::dispatch::dispatch_timer_available() {
463 return;
464 }
465 let poll =
466 std::time::Duration::from_millis(self.config.deadlines.receipt_writer_poll_ms.max(1));
467 let kernel = std::sync::Arc::downgrade(self);
474 let handle = tokio::spawn(async move {
475 let mut ticker = tokio::time::interval(poll);
476 loop {
477 ticker.tick().await;
478 let Some(kernel) = kernel.upgrade() else {
479 return;
480 };
481 let verdict = kernel.sample_receipt_writer_liveness();
482 kernel.receipt_writer_watchdog.publish(verdict);
483 }
484 });
485 self.receipt_writer_watchdog.set_join_handle(handle);
486 }
487
488 pub(crate) fn ensure_revocation_durability_ready(&self) -> Result<(), KernelError> {
489 if self.revocation_view.is_some() {
494 return Ok(());
495 }
496 let ephemeral = self.with_revocation_store(|store| Ok(store.is_ephemeral()))?;
497 if !ephemeral || self.config.allow_ephemeral_revocation_store {
498 return Ok(());
499 }
500 Err(KernelError::Internal(
501 "durable revocation state unavailable: no revocation store configured".to_string(),
502 ))
503 }
504
505 pub(crate) fn scope_receipt_federation_admission_for_request(
506 &self,
507 request_id: &str,
508 admission: ReceiptFederationAdmission,
509 ) -> ScopedKernelReceiptFederationAdmission {
510 let previous = self
511 .receipt_federation_admissions
512 .insert(request_id.to_string(), admission);
513 ScopedKernelReceiptFederationAdmission {
514 request_id: request_id.to_string(),
515 admissions: Arc::clone(&self.receipt_federation_admissions),
516 previous,
517 }
518 }
519
520 pub(crate) fn receipt_federation_admission_for_request(
521 &self,
522 request_id: &str,
523 remote_kernel_id: Option<&str>,
524 ) -> Option<ReceiptFederationAdmission> {
525 let admission = self
526 .receipt_federation_admissions
527 .get(request_id)
528 .map(|entry| entry.value().clone())?;
529 if admission.remote_kernel_id.as_deref() == remote_kernel_id {
530 Some(admission)
531 } else {
532 None
533 }
534 }
535
536 pub(crate) fn kernel_receipt_admission_for_remote(
541 &self,
542 remote_kernel_id: Option<&str>,
543 now: u64,
544 ) -> Result<ReceiptFederationAdmission, KernelError> {
545 if let Some(remote) = remote_kernel_id {
546 if let Some(peer) = self.federation_peer(remote, now) {
547 return Ok(ReceiptFederationAdmission {
548 remote_kernel_id: Some(remote.to_string()),
549 peer: Some(peer),
550 });
551 }
552 return Err(KernelError::Internal(format!(
553 "named federation peer {remote} is not pinned fresh"
554 )));
555 }
556 Ok(ReceiptFederationAdmission {
557 remote_kernel_id: None,
558 peer: None,
559 })
560 }
561
562 pub fn set_revocation_view(&mut self, view: std::sync::Arc<chio_kernel_core::RevocationView>) {
571 self.revocation_view = Some(view);
572 }
573
574 #[must_use]
576 pub fn revocation_view(&self) -> Option<&std::sync::Arc<chio_kernel_core::RevocationView>> {
577 self.revocation_view.as_ref()
578 }
579
580 #[allow(dead_code)]
591 pub(crate) fn signing_task_handle(&self) -> &signing_task::SigningTaskHandle {
592 &self.signing_task
593 }
594
595 pub async fn sign_receipt_via_channel(
618 &self,
619 body: ChioReceiptBody,
620 canonical_content: Vec<u8>,
621 ) -> Result<ChioReceipt, KernelError> {
622 self.signing_task.sign(body, canonical_content).await
623 }
624
625 pub async fn shutdown(&self) {
639 self.signing_task.shutdown().await;
640 self.receipt_writer_watchdog.shutdown().await;
641 }
642
643 pub fn set_receipt_store(
644 &mut self,
645 receipt_store: Box<dyn ReceiptStore>,
646 ) -> Result<(), KernelError> {
647 self.set_receipt_store_handle(Arc::from(receipt_store))
648 }
649
650 pub fn configure_durable_admission(
651 &mut self,
652 mode: crate::admission_operation::DurableAdmissionMode,
653 unsafe_development: bool,
654 ) -> Result<(), crate::admission_operation::AdmissionOperationError> {
655 let receipts = if self.config.allow_ephemeral_receipt_log {
656 crate::admission_operation::AdmissionReceiptPersistence::Ephemeral
657 } else {
658 crate::admission_operation::AdmissionReceiptPersistence::Durable
659 };
660 self.durable_admission_mode = mode.validate_configuration(unsafe_development, receipts)?;
661 Ok(())
662 }
663
664 #[must_use]
665 pub fn durable_admission_mode(&self) -> crate::admission_operation::DurableAdmissionMode {
666 self.durable_admission_mode
667 }
668
669 pub fn set_durable_admission_store(
670 &mut self,
671 store: Arc<dyn crate::receipt_store::QualifiedAdmissionProjectionStore>,
672 outcome_store: Arc<dyn crate::tool_outcome::QualifiedToolOutcomeStore>,
673 fence: crate::admission_operation::StoreMutationFence,
674 ) -> Result<(), crate::admission_operation::AdmissionOperationError> {
675 self.durable_admission_runtime = Some(DurableAdmissionRuntime::new(
676 store,
677 outcome_store,
678 fence,
679 &self.config.keypair.public_key().to_hex(),
680 )?);
681 Ok(())
682 }
683
684 pub fn enable_unsafe_ephemeral_financial_dispatch_for_development(&mut self) {
691 self.unsafe_ephemeral_financial_dispatch = true;
692 }
693
694 pub fn set_channel_terminal_authority(
695 &mut self,
696 authority: Arc<dyn crate::admission_operation::QualifiedChannelTerminalAuthority>,
697 ) -> Result<(), KernelError> {
698 let runtime = self.durable_admission_runtime.as_mut().ok_or_else(|| {
699 KernelError::DurableAdmission(
700 "channel terminal authority requires a durable admission store".to_owned(),
701 )
702 })?;
703 runtime.set_channel_terminal_authority(authority);
704 Ok(())
705 }
706
707 pub fn set_receipt_store_handle(
708 &mut self,
709 receipt_store: Arc<dyn ReceiptStore>,
710 ) -> Result<(), KernelError> {
711 self.try_set_receipt_store_handle(receipt_store)
712 }
713
714 pub fn try_set_receipt_store_handle(
715 &mut self,
716 receipt_store: Arc<dyn ReceiptStore>,
717 ) -> Result<(), KernelError> {
718 if let Some(runtime) = self.settlement_observer.as_ref() {
719 Self::validate_settlement_receipt_store(receipt_store.as_ref(), runtime)?;
720 }
721 match receipt_store.load_latest_checkpoint() {
722 Ok(Some(checkpoint)) => {
723 self.checkpoint_seq_counter
724 .store(checkpoint.body.checkpoint_seq, Ordering::SeqCst);
725 self.last_checkpoint_seq
726 .store(checkpoint.body.batch_end_seq, Ordering::SeqCst);
727 }
728 Ok(None) => {
729 self.checkpoint_seq_counter.store(0, Ordering::SeqCst);
730 self.last_checkpoint_seq.store(0, Ordering::SeqCst);
731 }
732 Err(error) => {
733 return Err(KernelError::Internal(format!(
734 "failed to hydrate checkpoint counters from receipt store: {error}"
735 )));
736 }
737 }
738 if self.checkpoint_batch_size > 0 && receipt_store.supports_kernel_signed_checkpoints() {
746 let background_enabled = receipt_store
747 .enable_background_checkpoints(
748 self.config.keypair.clone(),
749 self.checkpoint_batch_size,
750 )
751 .map_err(|error| {
752 KernelError::Internal(format!(
753 "failed to enable background receipt checkpoints: {error}"
754 ))
755 })?;
756 if !background_enabled {
762 return Err(KernelError::Internal(
763 "receipt store reports kernel-signed checkpoint support but did not install a \
764 background checkpoint signer; refusing to attach a store that would append \
765 without producing checkpoints"
766 .to_string(),
767 ));
768 }
769 }
770 if let Some(config) = self.config.retention_config.clone() {
773 if self.checkpoint_batch_size == 0 {
783 return Err(KernelError::Internal(
784 "KernelConfig.retention_config is set but automatic checkpointing is disabled \
785 (checkpoint_batch_size == 0); prefix retention can only archive \
786 checkpoint-covered receipts, so refusing to attach a store that could never \
787 advance its retention watermark"
788 .to_string(),
789 ));
790 }
791 if !receipt_store.supports_retention() {
797 return Err(KernelError::Internal(
798 "receipt store does not support retention but KernelConfig.retention_config \
799 is set; refusing to attach a store that cannot honor the configured \
800 retention policy"
801 .to_string(),
802 ));
803 }
804 if config.tenant_id.is_some() && !receipt_store.supports_tenant_scoped_retention() {
811 return Err(KernelError::Internal(
812 "KernelConfig.retention_config sets a tenant scope but the receipt store does \
813 not support tenant-scoped retention; refusing to attach a store that cannot \
814 honor the configured retention policy"
815 .to_string(),
816 ));
817 }
818 self.retention_maintenance =
819 Some(crate::receipt_store::RetentionMaintenanceHandle::spawn(
820 Arc::clone(&receipt_store),
821 config,
822 ));
823 }
824 self.receipt_store = Some(receipt_store);
825 Ok(())
826 }
827
828 pub fn set_payment_adapter(&mut self, payment_adapter: Box<dyn PaymentAdapter>) {
829 self.payment_adapter = Some(payment_adapter);
830 }
831
832 pub fn set_price_oracle(&mut self, price_oracle: Box<dyn PriceOracle>) {
833 self.price_oracle = Some(price_oracle);
834 }
835
836 pub fn set_attestation_trust_policy(
837 &mut self,
838 attestation_trust_policy: AttestationTrustPolicy,
839 ) {
840 self.attestation_trust_policy = Some(attestation_trust_policy);
841 }
842
843 pub fn opt_in_ephemeral_revocation_store(&mut self) {
852 self.config.allow_ephemeral_revocation_store = true;
853 }
854
855 pub fn set_revocation_store(&mut self, revocation_store: Box<dyn RevocationStore>) {
856 self.set_revocation_store_handle(Arc::from(revocation_store));
857 }
858
859 pub fn set_revocation_store_handle(&mut self, revocation_store: Arc<dyn RevocationStore>) {
860 self.revocation_store = revocation_store;
861 }
862
863 pub fn set_capability_authority(&mut self, capability_authority: Box<dyn CapabilityAuthority>) {
864 self.capability_authority = capability_authority;
865 }
866
867 pub fn set_budget_store(&mut self, budget_store: Box<dyn BudgetStore>) {
868 self.set_budget_store_handle(Arc::from(budget_store));
869 }
870
871 pub fn set_budget_store_handle(&mut self, budget_store: Arc<dyn BudgetStore>) {
872 self.budget_store = budget_store;
873 }
874
875 pub fn set_post_invocation_pipeline(
876 &mut self,
877 pipeline: crate::post_invocation::PostInvocationPipeline,
878 ) {
879 self.post_invocation_pipeline = pipeline;
880 }
881
882 pub fn add_post_invocation_hook(
883 &mut self,
884 hook: Box<dyn crate::post_invocation::PostInvocationHook>,
885 ) {
886 self.post_invocation_pipeline.add(hook);
887 }
888
889 pub fn set_settlement_observer_runtime(
902 &mut self,
903 hook: Arc<dyn chio_settle::SettlementHook>,
904 outcome_store: Arc<dyn chio_settle::SettlementOutcomeStore>,
905 retry_policy: chio_settle::RetryPolicy,
906 ) -> Result<(), KernelError> {
907 let runtime = crate::settlement_routing::SettlementObserverRuntime::new(
908 hook,
909 outcome_store,
910 retry_policy,
911 )
912 .map_err(crate::SettlementRuntimeConfigError::from)?;
913 if let Some(receipt_store) = self.receipt_store.as_ref() {
914 Self::validate_settlement_receipt_store(receipt_store.as_ref(), &runtime)?;
915 }
916 self.settlement_observer = Some(runtime);
917 Ok(())
918 }
919
920 fn validate_settlement_receipt_store(
921 receipt_store: &dyn ReceiptStore,
922 runtime: &crate::settlement_routing::SettlementObserverRuntime,
923 ) -> Result<(), KernelError> {
924 if receipt_store.atomic_receipt_projection()
925 != crate::receipt_store::AtomicReceiptProjection::SettlementObservationV1
926 || !receipt_store.supports_atomic_receipt_projection_with_timeout()
927 {
928 return Err(crate::SettlementRuntimeConfigError::UnsupportedAtomicProjection.into());
929 }
930 let Some(receipt_binding) = receipt_store.settlement_store_binding() else {
931 return Err(crate::SettlementRuntimeConfigError::MissingStoreBinding.into());
932 };
933 if receipt_binding != runtime.store_binding() {
934 return Err(crate::SettlementRuntimeConfigError::StoreBindingMismatch.into());
935 }
936 Ok(())
937 }
938
939 #[must_use]
941 pub fn settlement_observer(&self) -> Option<Arc<dyn chio_settle::SettlementHook>> {
942 self.settlement_observer
943 .as_ref()
944 .map(crate::settlement_routing::SettlementObserverRuntime::hook)
945 }
946
947 #[must_use]
953 pub fn run_settlement_observer(
954 &self,
955 receipt: &chio_core::receipt::body::ChioReceipt,
956 idempotency_key: &chio_settle::SettlementIdempotencyKey,
957 ) -> settlement_observer::SettlementObserverStatus {
958 settlement_observer::run_observer(
959 self.settlement_observer
960 .as_ref()
961 .map(crate::settlement_routing::SettlementObserverRuntime::hook_ref),
962 receipt,
963 &[self.public_key()],
964 idempotency_key,
965 )
966 }
967
968 pub fn set_memory_provenance_store(
984 &mut self,
985 store: Arc<dyn crate::memory_provenance::MemoryProvenanceStore>,
986 ) {
987 self.memory_provenance = Some(store);
988 }
989
990 #[must_use]
996 pub fn memory_provenance_store(
997 &self,
998 ) -> Option<Arc<dyn crate::memory_provenance::MemoryProvenanceStore>> {
999 self.memory_provenance.as_ref().map(Arc::clone)
1000 }
1001
1002 #[must_use]
1011 pub fn with_federation_peers(
1012 self,
1013 peers: Vec<chio_federation::trust_establishment::FederationPeer>,
1014 ) -> Self {
1015 let mut next = HashMap::new();
1016 for peer in peers {
1017 next.insert(peer.kernel_id.clone(), peer);
1018 }
1019 self.federation_peers.store(Arc::new(next));
1020 self
1021 }
1022
1023 #[must_use]
1026 pub fn with_capability_trust_roots(
1027 self,
1028 roots: Vec<(
1029 chio_core::PublicKey,
1030 chio_core::capability::attenuation::ScopeHash,
1031 )>,
1032 ) -> Self {
1033 {
1034 let _guard = self
1035 .capability_trust_roots_write_lock
1036 .lock()
1037 .unwrap_or_else(|poisoned| poisoned.into_inner());
1038 let mut next: HashMap<String, chio_core::capability::attenuation::ScopeHash> =
1039 HashMap::new();
1040 for (issuer, root) in roots {
1041 next.insert(issuer.to_hex(), root);
1042 }
1043 self.capability_trust_roots.store(Arc::new(next));
1044 }
1045 self
1046 }
1047
1048 pub fn set_capability_trust_root(
1052 &self,
1053 issuer: chio_core::PublicKey,
1054 root: chio_core::capability::attenuation::ScopeHash,
1055 ) -> Option<chio_core::capability::attenuation::ScopeHash> {
1056 let _guard = self
1057 .capability_trust_roots_write_lock
1058 .lock()
1059 .unwrap_or_else(|poisoned| poisoned.into_inner());
1060 let current = self.capability_trust_roots.load_full();
1061 let mut next: HashMap<String, chio_core::capability::attenuation::ScopeHash> =
1062 (*current).clone();
1063 let prev = next.insert(issuer.to_hex(), root);
1064 self.capability_trust_roots.store(Arc::new(next));
1065 prev
1066 }
1067
1068 pub fn capability_trust_roots_snapshot(
1072 &self,
1073 ) -> HashMap<String, chio_core::capability::attenuation::ScopeHash> {
1074 (*self.capability_trust_roots.load_full()).clone()
1075 }
1076
1077 pub(crate) fn capability_trust_root_resolver_snapshot(
1082 &self,
1083 ) -> impl Fn(&chio_core::PublicKey) -> Option<chio_core::capability::attenuation::ScopeHash>
1084 + Send
1085 + Sync
1086 + 'static {
1087 let snapshot: Arc<HashMap<String, chio_core::capability::attenuation::ScopeHash>> =
1088 self.capability_trust_roots.load_full();
1089 move |issuer: &chio_core::PublicKey| -> Option<chio_core::capability::attenuation::ScopeHash> {
1090 snapshot.get(&issuer.to_hex()).cloned()
1091 }
1092 }
1093
1094 pub(crate) fn capability_negotiation_for_remote(
1095 &self,
1096 remote_kernel_id: Option<&str>,
1097 now: u64,
1098 ) -> Result<chio_core::capability::features::CapabilityNegotiation, String> {
1099 if let Some(remote) = remote_kernel_id {
1100 if let Some(peer) = self.federation_peer(remote, now) {
1101 return Ok(peer.capabilities);
1102 }
1103 return Err(format!(
1104 "no fresh federation peer negotiation profile pinned for remote kernel {remote}"
1105 ));
1106 }
1107 let mut local = chio_core::capability::features::CapabilityNegotiation::t1_default();
1108 local.features.insert(
1109 chio_core::capability::features::AGGREGATE_INVOCATION_BUDGET.to_string(),
1110 true,
1111 );
1112 local.features.insert(
1113 chio_core::capability::features::CUMULATIVE_APPROVAL_BUDGET.to_string(),
1114 true,
1115 );
1116 Ok(local)
1117 }
1118
1119 pub fn set_federation_cosigner(
1124 &mut self,
1125 cosigner: Arc<dyn chio_federation::bilateral::BilateralCoSigningProtocol>,
1126 ) {
1127 self.federation_cosigner = Some(cosigner);
1128 }
1129
1130 pub fn set_federation_artifact_store(
1148 &mut self,
1149 store: Arc<dyn crate::federation_artifact_store::FederationArtifactStore>,
1150 ) {
1151 self.federation_artifact_store = Some(store);
1152 }
1153
1154 pub fn set_federation_local_kernel_id(&self, kernel_id: impl Into<String>) {
1159 self.federation_local_kernel_id
1160 .store(Arc::new(Some(kernel_id.into())));
1161 }
1162
1163 pub fn federation_peer(
1166 &self,
1167 remote_kernel_id: &str,
1168 now: u64,
1169 ) -> Option<chio_federation::trust_establishment::FederationPeer> {
1170 let peers = self.federation_peers.load();
1171 let peer = peers.get(remote_kernel_id)?.clone();
1172 if peer.is_fresh(now) {
1173 Some(peer)
1174 } else {
1175 None
1176 }
1177 }
1178
1179 pub fn federation_peers_snapshot(
1181 &self,
1182 ) -> Vec<chio_federation::trust_establishment::FederationPeer> {
1183 self.federation_peers.load().values().cloned().collect()
1184 }
1185
1186 pub fn dual_signed_receipt(
1192 &self,
1193 receipt_id: &str,
1194 ) -> Option<chio_federation::bilateral::DualSignedReceipt> {
1195 let now = current_unix_timestamp();
1196 {
1197 let mut cache = match self.federation_dual_receipts.lock() {
1198 Ok(g) => g,
1199 Err(poisoned) => poisoned.into_inner(),
1200 };
1201 if let Some(hit) = cache.get(&receipt_id.to_string(), now) {
1202 return Some(hit.clone());
1203 }
1204 }
1205 self.federation_artifact_store.as_ref().and_then(|store| {
1206 match store.get_dual_signed(receipt_id) {
1211 Ok(hit) => hit,
1212 Err(error) => {
1213 debug!(
1214 receipt_id = %receipt_id,
1215 reason = %redacted!(&error.to_string()),
1216 "federation artifact-store dual-signed read failed; treating as absent"
1217 );
1218 None
1219 }
1220 }
1221 })
1222 }
1223
1224 pub fn federation_dsse_envelope(
1225 &self,
1226 receipt_id: &str,
1227 ) -> Option<chio_federation::bilateral_dsse::DsseEnvelope> {
1228 let now = current_unix_timestamp();
1229 {
1230 let mut cache = match self.federation_dsse_envelopes.lock() {
1231 Ok(g) => g,
1232 Err(poisoned) => poisoned.into_inner(),
1233 };
1234 if let Some(hit) = cache.get(&receipt_id.to_string(), now) {
1235 return Some(hit.clone());
1236 }
1237 }
1238 self.federation_artifact_store.as_ref().and_then(|store| {
1239 match store.get_dsse(receipt_id) {
1243 Ok(hit) => hit,
1244 Err(error) => {
1245 debug!(
1246 receipt_id = %receipt_id,
1247 reason = %redacted!(&error.to_string()),
1248 "federation artifact-store DSSE read failed; treating as absent"
1249 );
1250 None
1251 }
1252 }
1253 })
1254 }
1255
1256 pub fn federation_local_kernel_id(&self) -> String {
1259 if let Some(id) = self.federation_local_kernel_id.load_full().as_ref() {
1260 return id.clone();
1261 }
1262 self.config.keypair.public_key().to_hex()
1263 }
1264
1265 fn treaty_dsse_extensions_from_receipt_metadata(
1266 &self,
1267 receipt: &chio_core::receipt::body::ChioReceipt,
1268 ) -> Result<Option<chio_federation::bilateral_dsse::BilateralPredicateExtensions>, KernelError>
1269 {
1270 let Some(metadata) = receipt.metadata.as_ref() else {
1271 return Ok(None);
1272 };
1273 let Some(value) = metadata
1274 .get("chio_runtime")
1275 .and_then(|runtime| runtime.get("federation_treaty_dsse"))
1276 else {
1277 return Ok(None);
1278 };
1279 let material: KernelFederationTreatyDsseMetadata = serde_json::from_value(value.clone())
1280 .map_err(|error| {
1281 KernelError::Internal(format!(
1282 "federation treaty DSSE metadata is invalid: {error}"
1283 ))
1284 })?;
1285 let consistency_model = material.consistency_model.clone().ok_or_else(|| {
1286 KernelError::Internal(
1287 "federation treaty DSSE consistency model is missing from runtime material"
1288 .to_string(),
1289 )
1290 })?;
1291 if consistency_model != material.treaty_binding_ref.consistency_model {
1292 return Err(KernelError::Internal(
1293 "federation treaty DSSE consistency model does not match treaty binding"
1294 .to_string(),
1295 ));
1296 }
1297 let mut treaty_binding_ref = material.treaty_binding_ref;
1298 if treaty_binding_ref.request_sha256 != receipt.action.parameter_hash {
1299 return Err(KernelError::Internal(
1300 "federation treaty DSSE request hash does not match receipt action hash"
1301 .to_string(),
1302 ));
1303 }
1304 treaty_binding_ref.outcome_sha256 = receipt.content_hash.clone();
1305 treaty_binding_ref.remote_receipt_sha256 = chio_core::crypto::sha256_hex(
1306 &chio_core::canonical::canonical_json_bytes(receipt).map_err(|error| {
1307 KernelError::Internal(format!(
1308 "federation treaty DSSE receipt canonicalization failed: {error}"
1309 ))
1310 })?,
1311 );
1312 Ok(Some(
1313 chio_federation::bilateral_dsse::BilateralPredicateExtensions {
1314 capability_lease_ref: Some(material.capability_lease_ref),
1315 policy_evaluation_summary: Some(material.policy_evaluation_summary),
1316 governance_receipt_ref: material.governance_receipt_ref,
1317 consistency_anchor: material.consistency_anchor,
1318 consistency_model: Some(consistency_model),
1319 cross_org_visibility: material.cross_org_visibility,
1320 treaty_binding_ref: Some(treaty_binding_ref),
1321 },
1322 ))
1323 }
1324
1325 pub(crate) fn apply_federation_cosign(
1341 &self,
1342 request: &crate::runtime::ToolCallRequest,
1343 receipt: &chio_core::receipt::body::ChioReceipt,
1344 admitted_peer: Option<&chio_federation::trust_establishment::FederationPeer>,
1345 ) -> Result<(), KernelError> {
1346 let Some(origin_kernel_id) = request.federated_origin_kernel_id.as_ref() else {
1347 return Ok(());
1348 };
1349 let Some(cosigner) = self.federation_cosigner.as_ref() else {
1350 return Err(KernelError::Internal(format!(
1351 "federation cosigner missing for request {request_id} bound to origin kernel {origin_kernel_id}",
1352 request_id = request.request_id,
1353 )));
1354 };
1355 let peer = match admitted_peer {
1356 Some(peer) if peer.kernel_id == *origin_kernel_id => peer.clone(),
1357 _ => {
1358 let now = current_unix_timestamp();
1359 self.federation_peer(origin_kernel_id, now).ok_or_else(|| {
1360 KernelError::Internal(format!(
1361 "federation peer {origin_kernel_id} is not pinned fresh and no admission-time peer snapshot is in scope"
1362 ))
1363 })?
1364 }
1365 };
1366
1367 let local_kernel_id = self.federation_local_kernel_id();
1368 let extensions = match self.treaty_dsse_extensions_from_receipt_metadata(receipt)? {
1369 Some(extensions) => extensions,
1370 None => {
1371 if matches!(
1381 receipt.decision,
1382 Some(chio_core::receipt::decision::Decision::Deny { .. })
1383 ) {
1384 return Ok(());
1385 }
1386 return Err(KernelError::Internal(
1387 "federation runtime treaty material missing; refusing treaty-bound DSSE"
1388 .to_string(),
1389 ));
1390 }
1391 };
1392 let dual = chio_federation::bilateral::co_sign_with_origin(
1393 origin_kernel_id,
1394 &peer.public_key,
1395 &local_kernel_id,
1396 &self.config.keypair,
1397 receipt.clone(),
1398 cosigner.as_ref(),
1399 )
1400 .map_err(|e| KernelError::Internal(format!("bilateral co-sign failed: {e}")))?;
1401 let timestamp_unix_ms = current_unix_timestamp().saturating_mul(1000);
1402 let dsse_envelope =
1403 chio_federation::bilateral_dsse::sign_chio_bilateral_dsse_envelope_with_cosigner(
1404 receipt,
1405 &peer.public_key,
1406 &self.config.keypair,
1407 origin_kernel_id,
1408 &local_kernel_id,
1409 &request.tool_name,
1410 timestamp_unix_ms,
1411 extensions,
1412 cosigner.as_ref(),
1413 )
1414 .map_err(|e| KernelError::Internal(format!("bilateral DSSE co-sign failed: {e}")))?;
1415
1416 let durable_store_installed = self
1423 .federation_artifact_store
1424 .as_ref()
1425 .is_some_and(|store| store.is_durable());
1426 if let Some(store) = self.federation_artifact_store.as_ref() {
1427 store.put_dual_signed(&receipt.id, &dual)?;
1428 store.put_dsse(&receipt.id, &dsse_envelope)?;
1429 }
1430 let now = current_unix_timestamp();
1431 {
1432 let mut cache = match self.federation_dual_receipts.lock() {
1433 Ok(g) => g,
1434 Err(poisoned) => poisoned.into_inner(),
1435 };
1436 if cache.insert(receipt.id.clone(), dual, now).is_some() && !durable_store_installed {
1440 debug!(
1441 request_id = %request.request_id,
1442 "dropping an evicted dual-signed co-sign artifact: bounded federation cache is full and no durable FederationArtifactStore is installed"
1443 );
1444 }
1445 }
1446 {
1447 let mut cache = match self.federation_dsse_envelopes.lock() {
1448 Ok(g) => g,
1449 Err(poisoned) => poisoned.into_inner(),
1450 };
1451 if cache
1452 .insert(receipt.id.clone(), dsse_envelope, now)
1453 .is_some()
1454 && !durable_store_installed
1455 {
1456 debug!(
1457 request_id = %request.request_id,
1458 "dropping an evicted co-sign DSSE envelope: bounded federation cache is full and no durable FederationArtifactStore is installed"
1459 );
1460 }
1461 }
1462 Ok(())
1463 }
1464
1465 pub fn emergency_stop(&self, reason: &str) -> Result<(), KernelError> {
1480 let now_unix_ms = current_unix_timestamp_ms();
1481 let now = now_unix_ms / 1000;
1482 self.emergency_stopped_since.store(now, Ordering::SeqCst);
1485 self.emergency_stop_reason
1486 .store(Arc::new(Some(reason.to_string())));
1487 self.emergency_stopped.store(true, Ordering::SeqCst);
1488
1489 warn!(
1490 reason = %redacted!(reason),
1491 timestamp = now,
1492 "emergency stop engaged -- all evaluations will be denied"
1493 );
1494 Ok(())
1495 }
1496
1497 pub fn emergency_resume(&self) -> Result<(), KernelError> {
1504 self.emergency_stopped.store(false, Ordering::SeqCst);
1505 self.emergency_stopped_since.store(0, Ordering::SeqCst);
1506
1507 warn!("emergency stop disengaged -- evaluations will resume");
1508
1509 self.emergency_stop_reason
1510 .store(Arc::new(Option::<String>::None));
1511 Ok(())
1512 }
1513
1514 #[must_use]
1516 pub fn is_emergency_stopped(&self) -> bool {
1517 self.emergency_stopped.load(Ordering::SeqCst)
1518 }
1519
1520 #[must_use]
1524 pub fn is_rss_shedding(&self) -> bool {
1525 self.rss_shed.load(Ordering::Relaxed)
1526 }
1527
1528 #[must_use]
1533 pub fn bounded_structure_gauges(&self) -> Vec<(&'static str, usize)> {
1534 vec![
1535 ("receipt_mirror", self.receipt_mirror_gauge.get()),
1536 (
1537 "child_receipt_mirror",
1538 self.child_receipt_mirror_gauge.get(),
1539 ),
1540 (
1541 "federation_dual_receipts",
1542 self.federation_dual_receipts_gauge.get(),
1543 ),
1544 (
1545 "federation_dsse_envelopes",
1546 self.federation_dsse_envelopes_gauge.get(),
1547 ),
1548 ]
1549 }
1550
1551 #[cfg(test)]
1552 pub(crate) fn set_rss_shed_for_test(&self, on: bool) {
1553 self.rss_shed.store(on, Ordering::Relaxed);
1554 }
1555
1556 #[must_use]
1559 pub fn emergency_stopped_since(&self) -> Option<u64> {
1560 if !self.is_emergency_stopped() {
1561 return None;
1562 }
1563 let since = self.emergency_stopped_since.load(Ordering::SeqCst);
1564 if since == 0 {
1565 None
1566 } else {
1567 Some(since)
1568 }
1569 }
1570
1571 #[must_use]
1574 pub fn emergency_stop_reason(&self) -> Option<String> {
1575 if !self.is_emergency_stopped() {
1576 return None;
1577 }
1578 self.emergency_stop_reason.load_full().as_ref().clone()
1579 }
1580
1581 pub fn set_dpop_store(&mut self, nonce_store: dpop::DpopNonceStore, config: dpop::DpopConfig) {
1587 self.dpop_nonce_store = Some(nonce_store);
1588 self.dpop_config = Some(config);
1589 }
1590
1591 pub fn set_execution_nonce_store(
1605 &mut self,
1606 config: crate::execution_nonce::ExecutionNonceConfig,
1607 store: Box<dyn crate::execution_nonce::ExecutionNonceStore>,
1608 ) {
1609 self.execution_nonce_config = Some(config);
1610 self.execution_nonce_store = Some(store);
1611 }
1612
1613 #[must_use]
1621 pub fn execution_nonce_required(&self) -> bool {
1622 self.execution_nonce_config
1623 .as_ref()
1624 .is_some_and(|cfg| cfg.require_nonce)
1625 }
1626
1627 pub(crate) fn mint_execution_nonce_for_allow(
1636 &self,
1637 request: &ToolCallRequest,
1638 cap: &CapabilityToken,
1639 receipt: &ChioReceipt,
1640 ) -> Result<Option<Box<crate::execution_nonce::SignedExecutionNonce>>, KernelError> {
1641 self.mint_execution_nonce_for_allow_reserving(request, cap, receipt, None)
1642 }
1643
1644 pub(crate) fn mint_execution_nonce_for_allow_reserving(
1645 &self,
1646 request: &ToolCallRequest,
1647 cap: &CapabilityToken,
1648 receipt: &ChioReceipt,
1649 reserved_hold_id: Option<&str>,
1650 ) -> Result<Option<Box<crate::execution_nonce::SignedExecutionNonce>>, KernelError> {
1651 if request.execution_nonce.is_some() {
1652 return Ok(None);
1653 }
1654 let Some(config) = self.execution_nonce_config.as_ref() else {
1655 return Ok(None);
1656 };
1657 let now = i64::try_from(current_unix_timestamp()).unwrap_or(i64::MAX);
1658 let binding = crate::execution_nonce::NonceBinding {
1659 subject_id: cap.subject.to_hex(),
1660 request_id: request.request_id.clone(),
1661 capability_id: cap.id.clone(),
1662 tool_server: request.server_id.clone(),
1663 tool_name: request.tool_name.clone(),
1664 parameter_hash: receipt.action.parameter_hash.clone(),
1665 };
1666 let reserving_request_id = reserved_hold_id.map(|_| request.request_id.clone());
1667 let signed = crate::execution_nonce::mint_execution_nonce_with_reservation(
1668 &self.config.keypair,
1669 binding,
1670 reserved_hold_id.map(str::to_string),
1671 reserving_request_id,
1672 config,
1673 now,
1674 )?;
1675 Ok(Some(Box::new(signed)))
1676 }
1677
1678 pub fn verify_presented_execution_nonce(
1686 &self,
1687 presented: &crate::execution_nonce::SignedExecutionNonce,
1688 expected: &crate::execution_nonce::NonceBinding,
1689 ) -> Result<(), crate::execution_nonce::ExecutionNonceError> {
1690 let store = self.execution_nonce_store.as_deref().ok_or_else(|| {
1691 crate::execution_nonce::ExecutionNonceError::Store(
1692 "execution nonce store is not installed".to_string(),
1693 )
1694 })?;
1695 let now = i64::try_from(current_unix_timestamp()).unwrap_or(i64::MAX);
1696 crate::execution_nonce::verify_execution_nonce(
1697 presented,
1698 &self.config.keypair.public_key(),
1699 expected,
1700 now,
1701 store,
1702 )
1703 }
1704
1705 pub fn require_presented_execution_nonce(
1720 &self,
1721 request: &ToolCallRequest,
1722 cap: &CapabilityToken,
1723 ) -> Result<(), KernelError> {
1724 self.validate_required_execution_nonce(request, cap)?;
1725 self.reserve_presented_execution_nonce(request)
1726 }
1727
1728 pub(crate) fn validate_required_execution_nonce(
1729 &self,
1730 request: &ToolCallRequest,
1731 cap: &CapabilityToken,
1732 ) -> Result<(), KernelError> {
1733 let presented = request.execution_nonce.as_ref();
1734 if !self.execution_nonce_required() && presented.is_none() {
1735 return Ok(());
1736 }
1737 let presented = presented.ok_or_else(|| {
1738 KernelError::Internal(
1739 "execution nonce required but not presented on tool call".to_string(),
1740 )
1741 })?;
1742 if self.execution_nonce_store.is_none() {
1743 return Err(KernelError::Internal(
1744 "execution nonce store is not installed".to_string(),
1745 ));
1746 }
1747 let parameter_hash = chio_core::receipt::decision::ToolCallAction::from_parameters(
1748 request.arguments.clone(),
1749 )
1750 .map_err(|e| KernelError::ReceiptSigningFailed(format!("failed to hash parameters: {e}")))?
1751 .parameter_hash;
1752 let expected = crate::execution_nonce::NonceBinding {
1753 subject_id: cap.subject.to_hex(),
1754 request_id: request.request_id.clone(),
1755 capability_id: cap.id.clone(),
1756 tool_server: request.server_id.clone(),
1757 tool_name: request.tool_name.clone(),
1758 parameter_hash,
1759 };
1760 let now = i64::try_from(current_unix_timestamp()).unwrap_or(i64::MAX);
1761 crate::execution_nonce::validate_execution_nonce(
1762 presented,
1763 &self.config.keypair.public_key(),
1764 &expected,
1765 now,
1766 )
1767 .map_err(|e| KernelError::Internal(format!("{e}")))
1768 }
1769
1770 pub(crate) fn reserve_presented_execution_nonce(
1771 &self,
1772 request: &ToolCallRequest,
1773 ) -> Result<(), KernelError> {
1774 let Some(presented) = request.execution_nonce.as_ref() else {
1775 return Ok(());
1776 };
1777 let store = self.execution_nonce_store.as_deref().ok_or_else(|| {
1778 KernelError::Internal("execution nonce store is not installed".to_string())
1779 })?;
1780 let now = i64::try_from(current_unix_timestamp()).unwrap_or(i64::MAX);
1781 crate::execution_nonce::reserve_execution_nonce(presented, store, now)
1782 .map_err(|e| KernelError::Internal(format!("{e}")))
1783 }
1784
1785 #[must_use]
1793 pub(crate) fn execution_nonce_preflight_required(&self, request: &ToolCallRequest) -> bool {
1794 self.execution_nonce_required() && request.execution_nonce.is_none()
1795 }
1796
1797 pub fn requires_web3_evidence(&self) -> bool {
1798 self.config.require_web3_evidence
1799 }
1800
1801 pub fn validate_web3_evidence_prerequisites(&self) -> Result<(), KernelError> {
1802 if !self.requires_web3_evidence() {
1803 return Ok(());
1804 }
1805
1806 let Some(supports_kernel_signed_checkpoints) =
1807 self.with_receipt_store(|store| Ok(store.supports_kernel_signed_checkpoints()))?
1808 else {
1809 return Err(KernelError::Web3EvidenceUnavailable(
1810 "web3-enabled deployments require a durable receipt store".to_string(),
1811 ));
1812 };
1813
1814 if self.checkpoint_batch_size == 0 {
1815 return Err(KernelError::Web3EvidenceUnavailable(
1816 "web3-enabled deployments require checkpoint_batch_size > 0".to_string(),
1817 ));
1818 }
1819
1820 if !supports_kernel_signed_checkpoints {
1821 return Err(KernelError::Web3EvidenceUnavailable(
1822 "web3-enabled deployments require local receipt persistence with kernel-signed checkpoint support; append-only remote receipt mirrors are unsupported".to_string(),
1823 ));
1824 }
1825
1826 Ok(())
1827 }
1828
1829 pub fn add_guard(&mut self, guard: Box<dyn Guard>) {
1832 std::sync::Arc::make_mut(&mut self.guards).push(std::sync::Arc::from(guard));
1837 }
1838
1839 pub fn set_runtime_admission_hook(&mut self, hook: Arc<dyn RuntimeAdmissionHook>) {
1842 self.runtime_admission_hook = Some(hook);
1843 }
1844
1845 pub fn set_threshold_approval_requirement_resolver(
1846 &mut self,
1847 resolver: Arc<dyn crate::threshold_approval::ThresholdApprovalRequirementResolver>,
1848 ) {
1849 self.threshold_approval_requirement_resolver = Some(resolver);
1850 }
1851
1852 pub fn set_supplemental_quota_verifier(
1855 &mut self,
1856 verifier: Arc<dyn crate::supplemental_quota::SupplementalQuotaVerifier>,
1857 binding: crate::supplemental_quota::SupplementalQuotaVerifierBinding,
1858 ) -> Result<(), crate::supplemental_quota::SupplementalQuotaError> {
1859 self.supplemental_quota_verifier = Some(
1860 crate::supplemental_quota::SupplementalQuotaVerifierRuntime::new(verifier, binding)?,
1861 );
1862 Ok(())
1863 }
1864
1865 pub fn clear_runtime_admission_hook(&mut self) {
1867 self.runtime_admission_hook = None;
1868 }
1869
1870 pub fn register_tool_server(&mut self, connection: Box<dyn ToolServerConnection>) {
1872 let id = connection.server_id().to_owned();
1873 info!(server_id = %id, "registering tool server");
1874 self.tool_servers.insert(id, Arc::from(connection));
1875 }
1876
1877 pub fn register_resource_provider(&mut self, provider: Box<dyn ResourceProvider>) {
1879 info!("registering resource provider");
1880 self.resource_providers.push(provider);
1881 }
1882
1883 pub fn register_prompt_provider(&mut self, provider: Box<dyn PromptProvider>) {
1885 info!("registering prompt provider");
1886 self.prompt_providers.push(provider);
1887 }
1888}