1use std::sync::Arc;
2
3use chio_appraisal::VerifiedRuntimeAttestationRecord;
4use chio_core::receipt::metadata::GuardEvidence;
5use dashmap::DashMap;
6
7use crate::budget_store::BudgetCommitMetadata;
8use crate::*;
9
10#[path = "admission_coordinator.rs"]
11mod admission_coordinator;
12mod error;
13mod kernel_drop_guard;
14mod kernel_scopes;
15mod kernel_struct;
16
17pub use construction::KernelBuildError;
18pub use error::{
19 HotPathStage, KernelError, OverloadResource, SettlementRuntimeConfigError,
20 StructuredErrorReport,
21};
22pub use kernel_struct::{
23 ChioKernel, HotPathDeadlineConfig, HybridSigningConfig, KernelConfig, MemoryBudgetConfig,
24 DEFAULT_CHECKPOINT_BATCH_SIZE, DEFAULT_MAX_SIZE_BYTES, DEFAULT_MAX_STREAM_DURATION_SECS,
25 DEFAULT_MAX_STREAM_TOTAL_BYTES, DEFAULT_RECEIPT_APPEND_BUDGET_MS,
26 DEFAULT_RECEIPT_WRITER_POLL_MS, DEFAULT_RECEIPT_WRITER_STALL_MS, DEFAULT_RETENTION_DAYS,
27 MIN_RECEIPT_APPEND_BUDGET_MS,
28};
29
30pub(crate) use admission_coordinator::{
31 DurableAdmissionRuntime, DurableToolAdmission, DurableToolReturnInput,
32};
33pub(crate) use kernel_drop_guard::{PostAdmissionDropGuard, PostAdmissionReceiptContext};
34pub(crate) use kernel_scopes::{
35 current_scoped_receipt_federation_admission, current_scoped_receipt_tenant_id,
36 extract_tenant_id_from_auth_context, scope_receipt_federation_admission,
37 scope_receipt_tenant_id, ReceiptFederationAdmission, ScopedKernelReceiptFederationAdmission,
38 ScopedKernelReceiptTenantId,
39};
40pub(crate) use kernel_struct::{
41 capability_crypto_floor, receipt_crypto_floor, ReservedSiblingShare, RestartReservedHoldGate,
42};
43
44pub type AgentId = String;
45
46pub type CapabilityId = String;
48
49pub type ServerId = String;
51
52pub const EMERGENCY_STOP_DENY_REASON: &str = "kernel emergency stop active";
56
57pub struct RuntimeAdmissionContext<'a> {
61 pub request: &'a ToolCallRequest,
62 pub extra_metadata: Option<&'a serde_json::Value>,
63 pub now_unix_secs: u64,
64 pub now_unix_ms: u64,
65 pub matched_grant_index: Option<usize>,
66 pub local_kernel_id: String,
67}
68
69#[derive(Debug, Clone, PartialEq)]
71pub struct RuntimeAdmissionDecision {
72 pub allowed: bool,
73 pub reason: Option<String>,
74 pub metadata: Option<serde_json::Value>,
75}
76
77impl RuntimeAdmissionDecision {
78 #[must_use]
79 pub fn allow(metadata: Option<serde_json::Value>) -> Self {
80 Self {
81 allowed: true,
82 reason: None,
83 metadata,
84 }
85 }
86
87 #[must_use]
88 pub fn deny(reason: impl Into<String>, metadata: Option<serde_json::Value>) -> Self {
89 Self {
90 allowed: false,
91 reason: Some(reason.into()),
92 metadata,
93 }
94 }
95}
96
97pub trait RuntimeAdmissionHook: Send + Sync {
99 fn name(&self) -> &str;
100
101 fn evaluate(
102 &self,
103 context: &RuntimeAdmissionContext<'_>,
104 ) -> Result<RuntimeAdmissionDecision, KernelError>;
105
106 fn release_reserved(&self, _metadata: &serde_json::Value) -> Result<(), KernelError> {
107 Ok(())
108 }
109}
110
111#[derive(Debug, serde::Deserialize)]
112#[serde(deny_unknown_fields)]
113struct KernelFederationTreatyDsseMetadata {
114 capability_lease_ref: chio_federation::bilateral_dsse::CapabilityLeaseRef,
115 policy_evaluation_summary: chio_federation::bilateral_dsse::PolicyEvaluationSummary,
116 #[serde(default)]
117 governance_receipt_ref: Option<chio_federation::bilateral_dsse::GovernanceReceiptRef>,
118 #[serde(default)]
119 consistency_anchor: Option<String>,
120 #[serde(default)]
121 consistency_model: Option<String>,
122 #[serde(default)]
123 cross_org_visibility: Option<String>,
124 treaty_binding_ref: chio_federation::bilateral_dsse::TreatyBindingRef,
125}
126
127#[derive(Debug)]
128pub(crate) struct ReceiptContent {
129 pub(crate) content_hash: String,
130 pub(crate) metadata: Option<serde_json::Value>,
131 pub(crate) canonical_content: Vec<u8>,
137}
138
139#[derive(Debug, Clone, Default)]
140pub(crate) struct ValidatedGovernedCallChainProof {
141 upstream_proof: Option<chio_core::capability::governance::GovernedUpstreamCallChainProof>,
142 continuation_token_id: Option<String>,
143 session_anchor_id: Option<String>,
144}
145
146#[derive(Debug, Clone, Default)]
147pub(crate) struct ValidatedGovernedAdmission {
148 call_chain_proof: Option<ValidatedGovernedCallChainProof>,
149 verified_runtime_attestation: Option<VerifiedRuntimeAttestationRecord>,
150 verified_payee_binding: Option<VerifiedGovernedPayeeBinding>,
151 approval_intent_hash: String,
152 approval_reservation: Option<VerifiedApprovalReservation>,
153}
154
155#[derive(Debug, Clone)]
156pub(crate) struct VerifiedApprovalReservation {
157 pub(crate) threshold_proposal_hash: String,
158 pub(crate) approval_set_hash: String,
159 pub(crate) threshold_replay: Option<ThresholdApprovalReplayReservationV1>,
160}
161
162#[derive(Debug)]
163pub(crate) struct VerifiedThresholdApprovalSet {
164 pub(crate) requirement: chio_core::capability::threshold_approval::ThresholdApprovalRequirement,
165 pub(crate) body: chio_core::capability::governance::VerifiedApprovalSetBody,
166 pub(crate) replay: ThresholdApprovalReplayReservationV1,
167}
168
169pub(crate) enum BudgetAdmissionOutcome {
170 Authorized {
171 grant_index: usize,
172 mutation: Box<PreExecutionBudgetMutation>,
173 },
174 PendingApproval {
175 grant_index: usize,
176 proposal: Box<chio_core::capability::governance::ThresholdApprovalProposal>,
177 },
178}
179
180#[cfg(test)]
181impl BudgetAdmissionOutcome {
182 pub(crate) fn into_authorized(
183 self,
184 ) -> Result<(usize, PreExecutionBudgetMutation), KernelError> {
185 match self {
186 Self::Authorized {
187 grant_index,
188 mutation,
189 } => Ok((grant_index, *mutation)),
190 Self::PendingApproval { .. } => Err(KernelError::Internal(
191 "budget admission remained pending approval".to_owned(),
192 )),
193 }
194 }
195}
196
197pub(crate) struct GovernedValidationContext<'a> {
198 parent_context: Option<&'a OperationContext>,
199 now: u64,
200}
201
202#[derive(Debug, Clone, PartialEq, Eq)]
203pub(crate) struct VerifiedGovernedPayeeBinding {
204 beneficiary_id: String,
205 settlement_destination_ref: String,
206 payee_binding_digest: String,
207 economic_intent_digest: String,
208 pre_action_authority_digest: String,
209}
210
211impl VerifiedGovernedPayeeBinding {
212 pub(in crate::kernel) fn new(
213 beneficiary_id: String,
214 settlement_destination_ref: String,
215 economic_intent_digest: String,
216 pre_action_authority_digest: String,
217 ) -> Result<Self, chio_credit::obligation::ObligationError> {
218 let payee_binding_digest = chio_credit::obligation::derive_obligation_payee_binding_digest(
219 &beneficiary_id,
220 &settlement_destination_ref,
221 )?;
222 Ok(Self {
223 beneficiary_id,
224 settlement_destination_ref,
225 payee_binding_digest,
226 economic_intent_digest,
227 pre_action_authority_digest,
228 })
229 }
230
231 #[cfg(test)]
232 pub(crate) fn for_test(
233 beneficiary_id: &str,
234 settlement_destination_ref: &str,
235 economic_intent_digest: &str,
236 pre_action_authority_digest: &str,
237 ) -> Result<Self, chio_credit::obligation::ObligationError> {
238 Self::new(
239 beneficiary_id.to_owned(),
240 settlement_destination_ref.to_owned(),
241 economic_intent_digest.to_owned(),
242 pre_action_authority_digest.to_owned(),
243 )
244 }
245
246 #[must_use]
247 pub(crate) fn beneficiary_id(&self) -> &str {
248 &self.beneficiary_id
249 }
250
251 #[must_use]
252 pub(crate) fn settlement_destination_ref(&self) -> &str {
253 &self.settlement_destination_ref
254 }
255
256 #[must_use]
257 pub(crate) fn payee_binding_digest(&self) -> &str {
258 &self.payee_binding_digest
259 }
260
261 #[must_use]
262 pub(crate) fn economic_intent_digest(&self) -> &str {
263 &self.economic_intent_digest
264 }
265
266 #[must_use]
267 pub(crate) fn pre_action_authority_digest(&self) -> &str {
268 &self.pre_action_authority_digest
269 }
270}
271
272#[derive(Debug, Clone)]
273pub(crate) enum LocalReceiptArtifact {
274 Tool(Box<chio_core::receipt::body::ChioReceipt>),
275 Child(Box<chio_core::receipt::lineage::ChildRequestReceipt>),
276}
277
278impl LocalReceiptArtifact {
279 fn verify_signature_with_floor(
280 &self,
281 floor: chio_core::receipt::crypto_floor::ReceiptCryptoFloor,
282 ) -> Result<bool, KernelError> {
283 match self {
284 Self::Tool(receipt) => receipt.verify_signature_with_floor(floor).map_err(|error| {
285 KernelError::GovernedTransactionDenied(format!(
286 "governed call_chain parent receipt failed signature verification: {error}"
287 ))
288 }),
289 Self::Child(receipt) => receipt.verify_signature_with_floor(floor).map_err(|error| {
290 KernelError::GovernedTransactionDenied(format!(
291 "governed call_chain parent receipt failed signature verification: {error}"
292 ))
293 }),
294 }
295 }
296
297 fn artifact_hash(&self) -> Result<String, KernelError> {
298 let canonical = match self {
299 Self::Tool(receipt) => canonical_json_bytes(receipt),
300 Self::Child(receipt) => canonical_json_bytes(receipt),
301 }
302 .map_err(|error| {
303 KernelError::GovernedTransactionDenied(format!(
304 "failed to hash governed call_chain parent receipt: {error}"
305 ))
306 })?;
307 Ok(sha256_hex(&canonical))
308 }
309
310 fn session_anchor_reference(&self) -> Option<chio_core::session::SessionAnchorReference> {
311 let metadata = match self {
312 Self::Tool(receipt) => receipt.metadata.as_ref(),
313 Self::Child(receipt) => receipt.metadata.as_ref(),
314 };
315 extract_session_anchor_reference_from_metadata(metadata)
316 }
317}
318
319fn block_on_async_tool_dispatch<F, T>(future: F) -> Result<T, KernelError>
343where
344 F: std::future::Future<Output = Result<T, KernelError>>,
345{
346 match tokio::runtime::Handle::try_current() {
347 Ok(handle) if handle.runtime_flavor() == tokio::runtime::RuntimeFlavor::MultiThread => {
348 tokio::task::block_in_place(|| handle.block_on(future))
349 }
350 Ok(_handle) => {
351 Err(KernelError::SyncBridgeIncompatibleWithCurrentThreadRuntime)
357 }
358 Err(_) => {
359 futures::executor::block_on(future)
364 }
365 }
366}
367
368fn extract_session_anchor_reference_from_metadata(
369 metadata: Option<&serde_json::Value>,
370) -> Option<chio_core::session::SessionAnchorReference> {
371 let metadata = metadata?;
372 let candidates = [
373 metadata
374 .get("governed_transaction")
375 .and_then(|value| value.get("call_chain")),
376 metadata.get("lineageReferences"),
377 ];
378
379 for candidate in candidates.into_iter().flatten() {
380 let Some(session_anchor_id) = candidate
381 .get("sessionAnchorId")
382 .and_then(serde_json::Value::as_str)
383 .filter(|value| !value.trim().is_empty())
384 else {
385 continue;
386 };
387 let Some(session_anchor_hash) = candidate
388 .get("sessionAnchorHash")
389 .and_then(serde_json::Value::as_str)
390 .filter(|value| !value.trim().is_empty())
391 else {
392 continue;
393 };
394 return Some(chio_core::session::SessionAnchorReference::new(
395 session_anchor_id,
396 session_anchor_hash,
397 ));
398 }
399
400 None
401}
402
403#[derive(Debug, Clone)]
408pub struct GuardDecision {
409 pub verdict: Verdict,
410 pub evidence: Vec<GuardEvidence>,
411}
412
413impl GuardDecision {
414 #[must_use]
415 pub fn allow() -> Self {
416 Self {
417 verdict: Verdict::Allow,
418 evidence: Vec::new(),
419 }
420 }
421
422 #[must_use]
423 pub fn allow_with_evidence(evidence: Vec<GuardEvidence>) -> Self {
424 Self {
425 verdict: Verdict::Allow,
426 evidence,
427 }
428 }
429
430 #[must_use]
431 pub fn deny(evidence: Vec<GuardEvidence>) -> Self {
432 Self {
433 verdict: Verdict::Deny,
434 evidence,
435 }
436 }
437
438 #[must_use]
439 pub fn pending_approval(evidence: Vec<GuardEvidence>) -> Self {
440 Self {
441 verdict: Verdict::PendingApproval,
442 evidence,
443 }
444 }
445
446 #[must_use]
447 pub fn from_verdict(verdict: Verdict) -> Self {
448 match verdict {
449 Verdict::Allow => Self::allow(),
450 Verdict::Deny => Self::deny(Vec::new()),
451 Verdict::PendingApproval => Self::pending_approval(Vec::new()),
452 }
453 }
454}
455
456impl PartialEq<Verdict> for GuardDecision {
457 fn eq(&self, other: &Verdict) -> bool {
458 self.verdict == *other
459 }
460}
461
462impl PartialEq<GuardDecision> for Verdict {
463 fn eq(&self, other: &GuardDecision) -> bool {
464 *self == other.verdict
465 }
466}
467
468pub trait Guard: Send + Sync {
469 fn name(&self) -> &str;
471
472 fn evaluate(&self, ctx: &GuardContext) -> Result<GuardDecision, KernelError>;
477}
478
479pub struct GuardContext<'a> {
481 pub request: &'a ToolCallRequest,
483 pub scope: &'a ChioScope,
485 pub agent_id: &'a AgentId,
487 pub server_id: &'a ServerId,
489 pub session_filesystem_roots: Option<&'a [String]>,
492 pub matched_grant_index: Option<usize>,
495}
496
497pub trait ResourceProvider: Send + Sync {
499 fn list_resources(&self) -> Vec<ResourceDefinition>;
501
502 fn list_resource_templates(&self) -> Vec<ResourceTemplateDefinition> {
504 vec![]
505 }
506
507 fn read_resource(&self, uri: &str) -> Result<Option<Vec<ResourceContent>>, KernelError>;
509
510 fn complete_resource_argument(
512 &self,
513 _uri: &str,
514 _argument_name: &str,
515 _value: &str,
516 _context: &serde_json::Value,
517 ) -> Result<Option<CompletionResult>, KernelError> {
518 Ok(None)
519 }
520}
521
522pub trait PromptProvider: Send + Sync {
524 fn list_prompts(&self) -> Vec<PromptDefinition>;
526
527 fn get_prompt(
529 &self,
530 name: &str,
531 arguments: serde_json::Value,
532 ) -> Result<Option<PromptResult>, KernelError>;
533
534 fn complete_prompt_argument(
536 &self,
537 _name: &str,
538 _argument_name: &str,
539 _value: &str,
540 _context: &serde_json::Value,
541 ) -> Result<Option<CompletionResult>, KernelError> {
542 Ok(None)
543 }
544}
545
546const DEFAULT_RECEIPT_MIRROR_CAPACITY: usize = 4096;
550
551#[derive(Clone)]
556pub struct ReceiptLog {
557 ring: chio_bounded::Ring<ChioReceipt>,
558}
559
560impl ReceiptLog {
561 pub fn new() -> Self {
562 Self::with_capacity(
563 DEFAULT_RECEIPT_MIRROR_CAPACITY,
564 chio_bounded::SizeGauge::new(),
565 )
566 }
567
568 pub fn with_capacity(capacity: usize, gauge: chio_bounded::SizeGauge) -> Self {
569 Self {
570 ring: chio_bounded::Ring::with_capacity(capacity, gauge),
571 }
572 }
573
574 pub fn append(&mut self, receipt: ChioReceipt) {
575 let _evicted = self.ring.push(receipt);
584 }
585
586 pub fn len(&self) -> usize {
587 self.ring.len()
588 }
589
590 pub fn is_empty(&self) -> bool {
591 self.ring.is_empty()
592 }
593
594 pub fn iter(&self) -> impl Iterator<Item = &ChioReceipt> {
595 self.ring.iter()
596 }
597
598 pub fn receipts(&self) -> Vec<ChioReceipt> {
601 self.ring.iter().cloned().collect()
602 }
603
604 pub fn get(&self, index: usize) -> Option<&ChioReceipt> {
605 self.ring.iter().nth(index)
606 }
607}
608
609impl Default for ReceiptLog {
610 fn default() -> Self {
611 Self::new()
612 }
613}
614
615#[derive(Clone)]
617pub struct ChildReceiptLog {
618 ring: chio_bounded::Ring<ChildRequestReceipt>,
619}
620
621impl ChildReceiptLog {
622 pub fn new() -> Self {
623 Self::with_capacity(
624 DEFAULT_RECEIPT_MIRROR_CAPACITY,
625 chio_bounded::SizeGauge::new(),
626 )
627 }
628
629 pub fn with_capacity(capacity: usize, gauge: chio_bounded::SizeGauge) -> Self {
630 Self {
631 ring: chio_bounded::Ring::with_capacity(capacity, gauge),
632 }
633 }
634
635 pub fn append(&mut self, receipt: ChildRequestReceipt) {
636 let _evicted = self.ring.push(receipt);
637 }
638
639 pub fn len(&self) -> usize {
640 self.ring.len()
641 }
642
643 pub fn is_empty(&self) -> bool {
644 self.ring.is_empty()
645 }
646
647 pub fn iter(&self) -> impl Iterator<Item = &ChildRequestReceipt> {
648 self.ring.iter()
649 }
650
651 pub fn receipts(&self) -> Vec<ChildRequestReceipt> {
654 self.ring.iter().cloned().collect()
655 }
656
657 pub fn get(&self, index: usize) -> Option<&ChildRequestReceipt> {
658 self.ring.iter().nth(index)
659 }
660}
661
662impl Default for ChildReceiptLog {
663 fn default() -> Self {
664 Self::new()
665 }
666}
667
668#[derive(Clone, Copy)]
669pub(crate) struct MatchingGrant<'a> {
670 pub(crate) index: usize,
671 pub(crate) grant: &'a ToolGrant,
672 pub(crate) specificity: (u8, u8, usize),
673}
674
675pub(crate) struct BudgetChargeResult {
679 grant_index: usize,
680 cost_charged: u64,
681 currency: String,
682 budget_total: u64,
683 new_committed_cost_units: u64,
685 budget_hold_id: String,
686 authorize_metadata: BudgetCommitMetadata,
687 invocation_capture: Option<Box<crate::budget_store::BudgetHoldMutationDecision>>,
688}
689
690impl BudgetChargeResult {
691 fn reverse_event_id(&self) -> String {
694 let authorize_event_id = self
695 .authorize_metadata
696 .event_id
697 .as_deref()
698 .unwrap_or(&self.budget_hold_id);
699 let authorize_commit_index = self.authorize_metadata.budget_commit_index.unwrap_or(0);
700 format!("{authorize_event_id}:rollback:{authorize_commit_index}")
701 }
702
703 fn capture_invocation_event_id(&self) -> String {
704 let authorize_commit_index = self.authorize_metadata.budget_commit_index.unwrap_or(0);
705 format!(
706 "{}:capture-invocation:{authorize_commit_index}",
707 self.budget_hold_id
708 )
709 }
710
711 fn cancel_captured_before_dispatch_event_id(&self) -> String {
712 self.reverse_event_id()
713 }
714
715 fn reconcile_event_id(&self) -> String {
716 format!("{}:reconcile", self.budget_hold_id)
717 }
718}
719
720pub(crate) enum PreExecutionBudgetMutation {
721 None,
722 Invocation { grant_index: usize },
723 InvocationHold(BudgetChargeResult),
724 Charge(BudgetChargeResult),
725}
726
727impl PreExecutionBudgetMutation {
728 fn charge_result(&self) -> Option<&BudgetChargeResult> {
729 match self {
730 Self::Charge(charge) => Some(charge),
731 Self::None | Self::Invocation { .. } | Self::InvocationHold(_) => None,
732 }
733 }
734
735 fn durable_hold_result(&self) -> Option<&BudgetChargeResult> {
736 match self {
737 Self::Charge(charge) | Self::InvocationHold(charge) => Some(charge),
738 Self::None | Self::Invocation { .. } => None,
739 }
740 }
741
742 fn durable_hold_result_mut(&mut self) -> Option<&mut BudgetChargeResult> {
743 match self {
744 Self::Charge(charge) | Self::InvocationHold(charge) => Some(charge),
745 Self::Invocation { .. } | Self::None => None,
746 }
747 }
748
749 fn into_charge_result(self) -> Option<BudgetChargeResult> {
750 match self {
751 Self::Charge(charge) => Some(charge),
752 Self::None | Self::Invocation { .. } | Self::InvocationHold(_) => None,
753 }
754 }
755}
756
757struct SessionNestedFlowBridge<'a, C> {
758 sessions: &'a DashMap<SessionId, Arc<Session>>,
759 child_receipts: &'a mut Vec<ChildRequestReceipt>,
760 parent_context: &'a OperationContext,
761 allow_sampling: bool,
762 allow_sampling_tool_use: bool,
763 allow_elicitation: bool,
764 policy_hash: &'a str,
765 kernel_keypair: &'a Keypair,
766 client: &'a mut C,
767}
768
769impl<C> SessionNestedFlowBridge<'_, C> {
770 fn complete_child_request_with_receipt<T: serde::Serialize>(
771 &mut self,
772 child_context: &OperationContext,
773 operation_kind: OperationKind,
774 result: &Result<T, KernelError>,
775 ) -> Result<(), KernelError> {
776 let terminal_state = child_terminal_state(&child_context.request_id, result);
777 complete_session_request_with_terminal_state_in_sessions(
778 self.sessions,
779 &child_context.session_id,
780 &child_context.request_id,
781 terminal_state.clone(),
782 )?;
783
784 let receipt = build_child_request_receipt(
785 self.policy_hash,
786 self.kernel_keypair,
787 child_context,
788 operation_kind,
789 terminal_state,
790 child_outcome_payload(result)?,
791 )?;
792 self.child_receipts.push(receipt);
793 Ok(())
794 }
795}
796
797impl<C: NestedFlowClient> NestedFlowBridge for SessionNestedFlowBridge<'_, C> {
798 fn parent_request_id(&self) -> &RequestId {
799 &self.parent_context.request_id
800 }
801
802 fn poll_parent_cancellation(&mut self) -> Result<(), KernelError> {
803 self.client.poll_parent_cancellation(self.parent_context)
804 }
805
806 fn list_roots(&mut self) -> Result<Vec<RootDefinition>, KernelError> {
807 let (child_context, _start) = begin_child_request_in_sessions(
808 self.sessions,
809 self.parent_context,
810 nested_child_request_id(&self.parent_context.request_id, "roots"),
811 OperationKind::ListRoots,
812 None,
813 false,
814 )?;
815
816 let result = (|| {
817 let session = session_from_map(self.sessions, &child_context.session_id)?;
818 session.validate_context(&child_context)?;
819 session.ensure_operation_allowed(OperationKind::ListRoots)?;
820 if !session.peer_capabilities().supports_roots {
821 return Err(KernelError::RootsNotNegotiated);
822 }
823
824 let roots = self
825 .client
826 .list_roots(self.parent_context, &child_context)?;
827 session_from_map(self.sessions, &child_context.session_id)?
828 .replace_roots(roots.clone());
829 Ok(roots)
830 })();
831 if matches!(
832 &result,
833 Err(KernelError::RequestCancelled { request_id, .. })
834 if request_id == &child_context.request_id
835 ) {
836 session_from_map(self.sessions, &child_context.session_id)?
837 .request_cancellation(&child_context.request_id)?;
838 }
839 self.complete_child_request_with_receipt(
840 &child_context,
841 OperationKind::ListRoots,
842 &result,
843 )?;
844
845 result
846 }
847
848 fn create_message(
849 &mut self,
850 operation: CreateMessageOperation,
851 ) -> Result<CreateMessageResult, KernelError> {
852 let (child_context, _start) = begin_child_request_in_sessions(
853 self.sessions,
854 self.parent_context,
855 nested_child_request_id(&self.parent_context.request_id, "sample"),
856 OperationKind::CreateMessage,
857 None,
858 true,
859 )?;
860
861 let result = (|| {
862 validate_sampling_request_in_sessions(
863 self.sessions,
864 self.allow_sampling,
865 self.allow_sampling_tool_use,
866 &child_context,
867 &operation,
868 )?;
869 self.client
870 .create_message(self.parent_context, &child_context, &operation)
871 })();
872 if matches!(
873 &result,
874 Err(KernelError::RequestCancelled { request_id, .. })
875 if request_id == &child_context.request_id
876 ) {
877 session_from_map(self.sessions, &child_context.session_id)?
878 .request_cancellation(&child_context.request_id)?;
879 }
880 self.complete_child_request_with_receipt(
881 &child_context,
882 OperationKind::CreateMessage,
883 &result,
884 )?;
885
886 result
887 }
888
889 fn create_elicitation(
890 &mut self,
891 operation: CreateElicitationOperation,
892 ) -> Result<CreateElicitationResult, KernelError> {
893 let (child_context, _start) = begin_child_request_in_sessions(
894 self.sessions,
895 self.parent_context,
896 nested_child_request_id(&self.parent_context.request_id, "elicit"),
897 OperationKind::CreateElicitation,
898 None,
899 true,
900 )?;
901
902 let result = (|| {
903 validate_elicitation_request_in_sessions(
904 self.sessions,
905 self.allow_elicitation,
906 &child_context,
907 &operation,
908 )?;
909 self.client
910 .create_elicitation(self.parent_context, &child_context, &operation)
911 })();
912 if matches!(
913 &result,
914 Err(KernelError::RequestCancelled { request_id, .. })
915 if request_id == &child_context.request_id
916 ) {
917 session_from_map(self.sessions, &child_context.session_id)?
918 .request_cancellation(&child_context.request_id)?;
919 }
920 self.complete_child_request_with_receipt(
921 &child_context,
922 OperationKind::CreateElicitation,
923 &result,
924 )?;
925
926 result
927 }
928
929 fn notify_elicitation_completed(&mut self, elicitation_id: &str) -> Result<(), KernelError> {
930 let session = session_from_map(self.sessions, &self.parent_context.session_id)?;
931 session.validate_context(self.parent_context)?;
932 session.ensure_operation_allowed(OperationKind::ToolCall)?;
933
934 self.client
935 .notify_elicitation_completed(self.parent_context, elicitation_id)
936 }
937
938 fn notify_resource_updated(&mut self, uri: &str) -> Result<(), KernelError> {
939 let session = session_from_map(self.sessions, &self.parent_context.session_id)?;
940 session.validate_context(self.parent_context)?;
941 session.ensure_operation_allowed(OperationKind::ToolCall)?;
942
943 if !session.is_resource_subscribed(uri) {
944 return Ok(());
945 }
946
947 self.client
948 .notify_resource_updated(self.parent_context, uri)
949 }
950
951 fn notify_resources_list_changed(&mut self) -> Result<(), KernelError> {
952 let session = session_from_map(self.sessions, &self.parent_context.session_id)?;
953 session.validate_context(self.parent_context)?;
954 session.ensure_operation_allowed(OperationKind::ToolCall)?;
955
956 self.client
957 .notify_resources_list_changed(self.parent_context)
958 }
959}
960
961fn extract_guard_name(message: &str) -> Option<String> {
970 let start_marker = "guard \"";
971 let start = message.find(start_marker)? + start_marker.len();
972 let rest = &message[start..];
973 let end = rest.find('"')?;
974 Some(rest[..end].to_string())
975}
976
977fn scope_from_capability_snapshot(
978 snapshot: &crate::capability_lineage::CapabilitySnapshot,
979) -> Result<ChioScope, KernelError> {
980 serde_json::from_str(&snapshot.grants_json).map_err(|error| {
981 KernelError::Internal(format!(
982 "invalid capability snapshot scope for {}: {error}",
983 snapshot.capability_id
984 ))
985 })
986}
987
988fn validate_delegation_scope_step(
989 parent_capability_id: &str,
990 child_capability_id: &str,
991 parent_scope: &ChioScope,
992 child_scope: &ChioScope,
993 child_expires_at: u64,
994 link: &chio_core::capability::attenuation::DelegationLink,
995) -> Result<(), KernelError> {
996 validate_delegatable_subset(
997 parent_capability_id,
998 child_capability_id,
999 parent_scope,
1000 child_scope,
1001 )?;
1002 validate_declared_attenuations(child_capability_id, child_scope, child_expires_at, link)?;
1003 Ok(())
1004}
1005
1006fn validate_delegatable_subset(
1007 parent_capability_id: &str,
1008 child_capability_id: &str,
1009 parent_scope: &ChioScope,
1010 child_scope: &ChioScope,
1011) -> Result<(), KernelError> {
1012 for child_grant in &child_scope.grants {
1013 let allowed = parent_scope.grants.iter().any(|parent_grant| {
1014 parent_grant.operations.contains(&Operation::Delegate)
1015 && child_grant.is_subset_of(parent_grant)
1016 });
1017 if !allowed {
1018 return Err(KernelError::DelegationInvalid(format!(
1019 "parent capability {} does not authorize delegated tool grant {}/{} on child capability {}",
1020 parent_capability_id,
1021 child_grant.server_id,
1022 child_grant.tool_name,
1023 child_capability_id
1024 )));
1025 }
1026 }
1027
1028 for child_grant in &child_scope.resource_grants {
1029 let allowed = parent_scope.resource_grants.iter().any(|parent_grant| {
1030 parent_grant.operations.contains(&Operation::Delegate)
1031 && child_grant.is_subset_of(parent_grant)
1032 });
1033 if !allowed {
1034 return Err(KernelError::DelegationInvalid(format!(
1035 "parent capability {} does not authorize delegated resource grant {} on child capability {}",
1036 parent_capability_id, child_grant.uri_pattern, child_capability_id
1037 )));
1038 }
1039 }
1040
1041 for child_grant in &child_scope.prompt_grants {
1042 let allowed = parent_scope.prompt_grants.iter().any(|parent_grant| {
1043 parent_grant.operations.contains(&Operation::Delegate)
1044 && child_grant.is_subset_of(parent_grant)
1045 });
1046 if !allowed {
1047 return Err(KernelError::DelegationInvalid(format!(
1048 "parent capability {} does not authorize delegated prompt grant {} on child capability {}",
1049 parent_capability_id, child_grant.prompt_name, child_capability_id
1050 )));
1051 }
1052 }
1053
1054 Ok(())
1055}
1056
1057fn validate_declared_attenuations(
1058 child_capability_id: &str,
1059 child_scope: &ChioScope,
1060 child_expires_at: u64,
1061 link: &chio_core::capability::attenuation::DelegationLink,
1062) -> Result<(), KernelError> {
1063 for attenuation in &link.attenuations {
1064 match attenuation {
1065 chio_core::capability::attenuation::Attenuation::RemoveTool {
1066 server_id,
1067 tool_name,
1068 } => {
1069 if child_scope
1070 .grants
1071 .iter()
1072 .any(|grant| tool_grant_covers_target(grant, server_id, tool_name))
1073 {
1074 return Err(KernelError::DelegationInvalid(format!(
1075 "child capability {} still grants removed tool {}/{}",
1076 child_capability_id, server_id, tool_name
1077 )));
1078 }
1079 }
1080 chio_core::capability::attenuation::Attenuation::RemoveOperation {
1081 server_id,
1082 tool_name,
1083 operation,
1084 } => {
1085 if child_scope.grants.iter().any(|grant| {
1086 tool_grant_covers_target(grant, server_id, tool_name)
1087 && grant.operations.contains(operation)
1088 }) {
1089 return Err(KernelError::DelegationInvalid(format!(
1090 "child capability {} still grants removed operation {:?} on {}/{}",
1091 child_capability_id, operation, server_id, tool_name
1092 )));
1093 }
1094 }
1095 chio_core::capability::attenuation::Attenuation::AddConstraint {
1096 server_id,
1097 tool_name,
1098 constraint,
1099 } => {
1100 if child_scope.grants.iter().any(|grant| {
1101 tool_grant_covers_target(grant, server_id, tool_name)
1102 && !grant.constraints.contains(constraint)
1103 }) {
1104 return Err(KernelError::DelegationInvalid(format!(
1105 "child capability {} is missing declared constraint on {}/{}",
1106 child_capability_id, server_id, tool_name
1107 )));
1108 }
1109 }
1110 chio_core::capability::attenuation::Attenuation::ReduceBudget {
1111 server_id,
1112 tool_name,
1113 max_invocations,
1114 } => {
1115 if child_scope.grants.iter().any(|grant| {
1116 tool_grant_covers_target(grant, server_id, tool_name)
1117 && grant
1118 .max_invocations
1119 .is_none_or(|value| value > *max_invocations)
1120 }) {
1121 return Err(KernelError::DelegationInvalid(format!(
1122 "child capability {} exceeds declared invocation budget on {}/{}",
1123 child_capability_id, server_id, tool_name
1124 )));
1125 }
1126 }
1127 chio_core::capability::attenuation::Attenuation::ShortenExpiry { new_expires_at } => {
1128 if child_expires_at > *new_expires_at {
1129 return Err(KernelError::DelegationInvalid(format!(
1130 "child capability {} expires after declared shortened expiry {}",
1131 child_capability_id, new_expires_at
1132 )));
1133 }
1134 }
1135 chio_core::capability::attenuation::Attenuation::ReduceCostPerInvocation {
1136 server_id,
1137 tool_name,
1138 max_cost_per_invocation,
1139 } => {
1140 if child_scope.grants.iter().any(|grant| {
1141 tool_grant_covers_target(grant, server_id, tool_name)
1142 && grant.max_cost_per_invocation.as_ref().is_none_or(|value| {
1143 value.currency != max_cost_per_invocation.currency
1144 || value.units > max_cost_per_invocation.units
1145 })
1146 }) {
1147 return Err(KernelError::DelegationInvalid(format!(
1148 "child capability {} exceeds declared per-invocation cost ceiling on {}/{}",
1149 child_capability_id, server_id, tool_name
1150 )));
1151 }
1152 }
1153 chio_core::capability::attenuation::Attenuation::ReduceTotalCost {
1154 server_id,
1155 tool_name,
1156 max_total_cost,
1157 } => {
1158 if child_scope.grants.iter().any(|grant| {
1159 tool_grant_covers_target(grant, server_id, tool_name)
1160 && grant.max_total_cost.as_ref().is_none_or(|value| {
1161 value.currency != max_total_cost.currency
1162 || value.units > max_total_cost.units
1163 })
1164 }) {
1165 return Err(KernelError::DelegationInvalid(format!(
1166 "child capability {} exceeds declared total-cost ceiling on {}/{}",
1167 child_capability_id, server_id, tool_name
1168 )));
1169 }
1170 }
1171 }
1172 }
1173
1174 Ok(())
1175}
1176
1177fn tool_grant_covers_target(grant: &ToolGrant, server_id: &str, tool_name: &str) -> bool {
1178 (grant.server_id == "*" || grant.server_id == server_id)
1179 && (grant.tool_name == "*" || grant.tool_name == tool_name)
1180}
1181
1182pub(crate) struct ReceiptParams<'a> {
1184 request_id: Option<&'a str>,
1185 capability_id: &'a str,
1186 tool_name: &'a str,
1187 server_id: &'a str,
1188 decision: Decision,
1189 action: ToolCallAction,
1190 content_hash: String,
1191 canonical_content: Vec<u8>,
1196 metadata: Option<serde_json::Value>,
1197 timestamp: u64,
1198 trust_level: chio_core::receipt::kinds::TrustLevel,
1202 tenant_id: Option<String>,
1211}
1212
1213pub(crate) fn current_unix_timestamp() -> u64 {
1214 if let Some(now) = fixed_runtime_unix_secs_for_current_thread() {
1215 return now;
1216 }
1217 SystemTime::now()
1218 .duration_since(UNIX_EPOCH)
1219 .map(|d| d.as_secs())
1220 .unwrap_or(0)
1221}
1222
1223pub(crate) fn current_unix_timestamp_ms() -> u64 {
1224 if let Some(now) = fixed_runtime_unix_secs_for_current_thread() {
1225 return now.saturating_mul(1000);
1226 }
1227 SystemTime::now()
1228 .duration_since(UNIX_EPOCH)
1229 .map(|duration| u64::try_from(duration.as_millis()).unwrap_or(u64::MAX))
1230 .unwrap_or(0)
1231}
1232
1233#[cfg(feature = "delegation")]
1234#[path = "delegation.rs"]
1235pub(crate) mod delegation;
1236#[path = "construction.rs"]
1240mod construction;
1241mod evaluation;
1244#[path = "validation.rs"]
1246mod validation;
1247#[path = "reconciliation.rs"]
1249mod reconciliation;
1250#[path = "governed_validation.rs"]
1252mod governed_validation;
1253#[path = "dispatch.rs"]
1255mod dispatch;
1256#[path = "evaluator.rs"]
1257pub mod evaluator;
1258mod responses;
1259#[path = "session_ops.rs"]
1260mod session_ops;
1261#[path = "settlement_observer.rs"]
1266pub mod settlement_observer;
1267#[path = "signing_task.rs"]
1271pub(crate) mod signing_task;
1272#[path = "receipt_writer_watchdog.rs"]
1275mod receipt_writer_watchdog;
1276#[cfg(test)]
1277#[path = "tests.rs"]
1278mod tests;