use crate::budget_store::BudgetReverseHoldDecision;
use chio_log_redact::redacted;
use super::*;
pub(crate) struct GuardRunError {
pub(crate) error: KernelError,
pub(crate) evidence: Vec<chio_core::receipt::metadata::GuardEvidence>,
}
impl GuardRunError {
fn new(error: KernelError, evidence: Vec<chio_core::receipt::metadata::GuardEvidence>) -> Self {
Self { error, evidence }
}
}
struct OwnedGuardInvocation {
request: ToolCallRequest,
scope: ChioScope,
session_filesystem_roots: Option<Vec<String>>,
matched_grant_index: Option<usize>,
}
fn evaluate_guards_sequential(
guards: &[Arc<dyn Guard>],
ctx: &GuardContext,
) -> Result<Vec<chio_core::receipt::metadata::GuardEvidence>, GuardRunError> {
let mut evidence = Vec::new();
for guard in guards {
match guard.evaluate(ctx) {
Ok(decision) => {
evidence.extend(decision.evidence);
match decision.verdict {
Verdict::Allow => {
debug!(guard = guard.name(), "guard passed");
}
Verdict::Deny => {
return Err(GuardRunError::new(
KernelError::GuardDenied(format!(
"guard \"{}\" denied the request",
guard.name()
)),
evidence,
));
}
Verdict::PendingApproval => {
return Err(GuardRunError::new(
KernelError::GuardDenied(format!(
"guard \"{}\" returned an unsupported approval verdict",
guard.name()
)),
evidence,
));
}
}
}
Err(e) => {
return Err(GuardRunError::new(
KernelError::GuardDenied(format!(
"guard \"{}\" error (fail-closed): {e}",
guard.name()
)),
evidence,
));
}
}
}
Ok(evidence)
}
fn run_guards_owned(
guards: &[Arc<dyn Guard>],
owned: &OwnedGuardInvocation,
) -> Result<Vec<chio_core::receipt::metadata::GuardEvidence>, GuardRunError> {
let ctx = GuardContext {
request: &owned.request,
scope: &owned.scope,
agent_id: &owned.request.agent_id,
server_id: &owned.request.server_id,
session_filesystem_roots: owned.session_filesystem_roots.as_deref(),
matched_grant_index: owned.matched_grant_index,
};
evaluate_guards_sequential(guards, &ctx)
}
fn budget_ms_saturating(budget: std::time::Duration) -> u64 {
budget.as_millis().min(u128::from(u64::MAX)) as u64
}
fn dispatch_deadline_exceeded(budget: std::time::Duration) -> KernelError {
KernelError::HotPathDeadlineExceeded {
stage: HotPathStage::Dispatch,
budget_ms: budget_ms_saturating(budget),
}
}
struct AbortOnDrop(tokio::task::AbortHandle);
impl Drop for AbortOnDrop {
fn drop(&mut self) {
self.0.abort();
}
}
thread_local! {
static DISPATCH_TIMER_AVAILABLE: std::cell::Cell<Option<(Option<tokio::runtime::Id>, bool)>> =
const { std::cell::Cell::new(None) };
}
static TIMER_PROBE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
pub(crate) fn dispatch_timer_available() -> bool {
let current_runtime = tokio::runtime::Handle::try_current()
.ok()
.map(|handle| handle.id());
DISPATCH_TIMER_AVAILABLE.with(|cached| {
if let Some((probed_runtime, available)) = cached.get() {
if probed_runtime == current_runtime {
return available;
}
}
let available = {
let _serialized = TIMER_PROBE_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let previous_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let result = std::panic::catch_unwind(|| {
drop(tokio::time::timeout(
std::time::Duration::ZERO,
std::future::ready(()),
));
})
.is_ok();
std::panic::set_hook(previous_hook);
result
};
cached.set(Some((current_runtime, available)));
available
})
}
pub(crate) fn dispatch_runtime_available() -> bool {
tokio::runtime::Handle::try_current().is_ok()
}
pub(crate) async fn dispatch_nested_call_within_budget<F, T>(
call: F,
budget: std::time::Duration,
) -> Result<T, KernelError>
where
F: std::future::Future<Output = Result<T, KernelError>>,
{
async fn bounded<F, T>(
call: F,
budget: std::time::Duration,
timer_available: bool,
) -> Result<T, KernelError>
where
F: std::future::Future<Output = Result<T, KernelError>>,
{
if timer_available {
match tokio::time::timeout(budget, call).await {
Ok(result) => result,
Err(_elapsed) => Err(dispatch_deadline_exceeded(budget)),
}
} else {
call.await
}
}
let timer_available = dispatch_timer_available();
let multi_thread = matches!(
tokio::runtime::Handle::try_current().map(|handle| handle.runtime_flavor()),
Ok(tokio::runtime::RuntimeFlavor::MultiThread)
);
if multi_thread {
let handle = tokio::runtime::Handle::current();
tokio::task::block_in_place(|| handle.block_on(bounded(call, budget, timer_available)))
} else {
bounded(call, budget, timer_available).await
}
}
impl ChioKernel {
pub(crate) fn validate_parent_request_continuation(
&self,
request: &ToolCallRequest,
parent_context: &OperationContext,
) -> Result<(), KernelError> {
let child_request_id = RequestId::new(request.request_id.clone());
self.with_session(&parent_context.session_id, |session| {
session.validate_context(parent_context)?;
session
.validate_parent_request_lineage(&child_request_id, &parent_context.request_id)?;
Ok(())
})
}
pub(crate) fn has_local_receipt_id(&self, receipt_id: &str) -> Result<bool, KernelError> {
if self.load_durable_admission_receipt(receipt_id)?.is_some() {
return Ok(true);
}
if self.receipt_store.is_some() {
if self
.with_receipt_store(|store| Ok(store.load_chio_receipt(receipt_id)?))?
.flatten()
.is_some()
{
return Ok(true);
}
if self
.with_receipt_store(|store| Ok(store.load_child_receipt(receipt_id)?))?
.flatten()
.is_some()
{
return Ok(true);
}
}
let chio_receipt_match = match self.receipt_log.lock() {
Ok(log) => log.iter().any(|receipt| receipt.id == receipt_id),
Err(poisoned) => poisoned
.into_inner()
.iter()
.any(|receipt| receipt.id == receipt_id),
};
if chio_receipt_match {
return Ok(true);
}
Ok(match self.child_receipt_log.lock() {
Ok(log) => log.iter().any(|receipt| receipt.id == receipt_id),
Err(poisoned) => poisoned
.into_inner()
.iter()
.any(|receipt| receipt.id == receipt_id),
})
}
pub(crate) fn local_receipt_artifact(
&self,
receipt_id: &str,
) -> Result<Option<LocalReceiptArtifact>, KernelError> {
if let Some(receipt) = self.load_durable_admission_receipt(receipt_id)? {
return Ok(Some(LocalReceiptArtifact::Tool(Box::new(receipt))));
}
if self.receipt_store.is_some() {
if let Some(receipt) = self
.with_receipt_store(|store| Ok(store.load_chio_receipt(receipt_id)?))?
.flatten()
{
return Ok(Some(LocalReceiptArtifact::Tool(Box::new(receipt))));
}
if let Some(child) = self
.with_receipt_store(|store| Ok(store.load_child_receipt(receipt_id)?))?
.flatten()
{
return Ok(Some(LocalReceiptArtifact::Child(Box::new(child))));
}
}
let tool_match = match self.receipt_log.lock() {
Ok(log) => log
.iter()
.find(|receipt| receipt.id == receipt_id)
.cloned()
.map(|receipt| LocalReceiptArtifact::Tool(Box::new(receipt))),
Err(poisoned) => poisoned
.into_inner()
.iter()
.find(|receipt| receipt.id == receipt_id)
.cloned()
.map(|receipt| LocalReceiptArtifact::Tool(Box::new(receipt))),
};
if tool_match.is_some() {
return Ok(tool_match);
}
Ok(match self.child_receipt_log.lock() {
Ok(log) => log
.iter()
.find(|receipt| receipt.id == receipt_id)
.cloned()
.map(|receipt| LocalReceiptArtifact::Child(Box::new(receipt))),
Err(poisoned) => poisoned
.into_inner()
.iter()
.find(|receipt| receipt.id == receipt_id)
.cloned()
.map(|receipt| LocalReceiptArtifact::Child(Box::new(receipt))),
})
}
pub(crate) fn is_trusted_governed_continuation_signer(
&self,
signer: &chio_core::PublicKey,
) -> bool {
if *signer == self.config.keypair.public_key() {
return true;
}
if self
.config
.ca_public_keys
.iter()
.any(|candidate| candidate == signer)
{
return true;
}
self.capability_authority
.trusted_public_keys()
.into_iter()
.any(|candidate| candidate == *signer)
}
pub(crate) fn unwind_pre_dispatch_monetary_invocation(
&self,
request: &ToolCallRequest,
cap: &CapabilityToken,
charge_result: Option<&BudgetChargeResult>,
payment_authorization: Option<&PaymentAuthorization>,
) -> Result<Option<BudgetReverseHoldDecision>, KernelError> {
if let Some(authorization) = payment_authorization {
let adapter = self.payment_adapter.as_ref().ok_or_else(|| {
KernelError::Internal(
"payment authorization present without configured adapter".to_string(),
)
})?;
let refund_amount = ChioKernel::mustprepay_quoted_amount(request).or_else(|| {
charge_result.map(|charge| (charge.cost_charged, charge.currency.clone()))
});
let (unwind_result, expected_status) = if authorization.state.is_final() {
let (amount_units, currency) = refund_amount.ok_or_else(|| {
KernelError::Internal(
"final payment authorization omitted a refundable amount".to_string(),
)
})?;
(
adapter.refund(
&authorization.authorization_id,
amount_units,
¤cy,
&request.request_id,
),
RailSettlementStatus::Refunded,
)
} else {
(
adapter.release(&authorization.authorization_id, &request.request_id),
RailSettlementStatus::Released,
)
};
match unwind_result {
Ok(result) if result.settlement_status == expected_status => {}
Ok(_) => {
return Err(KernelError::Internal(
"payment unwind returned an unconfirmed status".to_string(),
));
}
Err(_) => {
return Err(KernelError::Internal(
"payment unwind acknowledgement was not confirmed".to_string(),
));
}
}
}
let Some(charge) = charge_result else {
return Ok(None);
};
if charge.invocation_capture.is_some() {
Ok(Some(self.cancel_captured_monetary_before_dispatch(
&cap.id, charge,
)?))
} else {
Ok(Some(self.reverse_budget_charge(&cap.id, charge)?))
}
}
pub(crate) fn record_observed_capability_snapshot(
&self,
capability: &CapabilityToken,
) -> Result<(), KernelError> {
let parent_capability_id = capability
.delegation_chain
.last()
.map(|link| link.capability_id.as_str());
let budget = self.config.deadlines.receipt_append_budget();
let _ = self.with_receipt_store(|store| {
Ok(store.record_capability_snapshot_with_timeout(
capability,
parent_capability_id,
budget,
)?)
})?;
Ok(())
}
pub(crate) fn verify_dpop_for_request(
&self,
request: &ToolCallRequest,
cap: &CapabilityToken,
) -> Result<(), KernelError> {
let proof = request.dpop_proof.as_ref().ok_or_else(|| {
KernelError::DpopVerificationFailed(
"grant requires DPoP proof but none was provided".to_string(),
)
})?;
let nonce_store = self.dpop_nonce_store.as_ref().ok_or_else(|| {
KernelError::DpopVerificationFailed(
"kernel DPoP nonce store not configured".to_string(),
)
})?;
let config = self.dpop_config.as_ref().ok_or_else(|| {
KernelError::DpopVerificationFailed("kernel DPoP config not configured".to_string())
})?;
let args_bytes = canonical_json_bytes(&request.arguments).map_err(|e| {
KernelError::DpopVerificationFailed(format!(
"failed to serialize arguments for action hash: {e}"
))
})?;
let action_hash = sha256_hex(&args_bytes);
dpop::verify_dpop_proof(
proof,
cap,
&request.server_id,
&request.tool_name,
&action_hash,
nonce_store,
config,
)
}
pub fn verify_dpop_for_permission_preview(
&self,
proof: &dpop::DpopProof,
cap: &CapabilityToken,
expected_tool_server: &str,
expected_tool_name: &str,
arguments: &serde_json::Value,
) -> Result<(), KernelError> {
if self.dpop_nonce_store.is_none() {
return Err(KernelError::DpopVerificationFailed(
"kernel DPoP nonce store not configured".to_string(),
));
}
let config = self.dpop_config.as_ref().ok_or_else(|| {
KernelError::DpopVerificationFailed("kernel DPoP config not configured".to_string())
})?;
let args_bytes = canonical_json_bytes(arguments).map_err(|e| {
KernelError::DpopVerificationFailed(format!(
"failed to serialize arguments for action hash: {e}"
))
})?;
let action_hash = sha256_hex(&args_bytes);
dpop::verify_dpop_proof_stateless(
proof,
cap,
expected_tool_server,
expected_tool_name,
&action_hash,
config,
)
}
pub(crate) fn run_guards(
&self,
request: &ToolCallRequest,
scope: &ChioScope,
session_filesystem_roots: Option<&[String]>,
matched_grant_index: Option<usize>,
) -> Result<Vec<chio_core::receipt::metadata::GuardEvidence>, GuardRunError> {
let ctx = GuardContext {
request,
scope,
agent_id: &request.agent_id,
server_id: &request.server_id,
session_filesystem_roots,
matched_grant_index,
};
evaluate_guards_sequential(self.guards.as_slice(), &ctx)
}
pub(crate) async fn run_guards_within_budget(
&self,
request: &ToolCallRequest,
scope: &ChioScope,
session_filesystem_roots: Option<&[String]>,
matched_grant_index: Option<usize>,
) -> Result<Vec<chio_core::receipt::metadata::GuardEvidence>, GuardRunError> {
let has_per_guard = !self.config.deadlines.per_guard_budget_ms.is_empty();
let pipeline_budget = self.config.deadlines.guard_pipeline_budget();
let needs_timer = pipeline_budget.is_some() || has_per_guard;
let always_offload = self.config.deadlines.always_offload_guards;
let want_offload = needs_timer || always_offload;
if !want_offload || !dispatch_runtime_available() {
return self.run_guards(
request,
scope,
session_filesystem_roots,
matched_grant_index,
);
}
let timer_available = dispatch_timer_available();
if needs_timer && !timer_available && !always_offload {
return self.run_guards(
request,
scope,
session_filesystem_roots,
matched_grant_index,
);
}
let owned = Arc::new(OwnedGuardInvocation {
request: request.clone(),
scope: scope.clone(),
session_filesystem_roots: session_filesystem_roots.map(<[String]>::to_vec),
matched_grant_index,
});
if has_per_guard && timer_available {
let per_guard = self.run_guards_per_guard_offloaded(&owned);
return match pipeline_budget {
Some(budget) => match tokio::time::timeout(budget, per_guard).await {
Ok(result) => result,
Err(_elapsed) => Err(GuardRunError::new(
KernelError::HotPathDeadlineExceeded {
stage: HotPathStage::GuardPipeline,
budget_ms: budget_ms_saturating(budget),
},
Vec::new(),
)),
},
None => per_guard.await,
};
}
let guards = Arc::clone(&self.guards);
let owned_for_task = Arc::clone(&owned);
let join = tokio::task::spawn_blocking(move || run_guards_owned(&guards, &owned_for_task));
let _abort_on_drop = AbortOnDrop(join.abort_handle());
match pipeline_budget.filter(|_| timer_available) {
Some(budget) => match tokio::time::timeout(budget, join).await {
Ok(Ok(result)) => result,
Ok(Err(join_err)) => Err(GuardRunError::new(
KernelError::Internal(format!("guard task join failed: {join_err}")),
Vec::new(),
)),
Err(_elapsed) => Err(GuardRunError::new(
KernelError::HotPathDeadlineExceeded {
stage: HotPathStage::GuardPipeline,
budget_ms: budget_ms_saturating(budget),
},
Vec::new(),
)),
},
None => match join.await {
Ok(result) => result,
Err(join_err) => Err(GuardRunError::new(
KernelError::Internal(format!("guard task join failed: {join_err}")),
Vec::new(),
)),
},
}
}
async fn run_guards_per_guard_offloaded(
&self,
owned: &Arc<OwnedGuardInvocation>,
) -> Result<Vec<chio_core::receipt::metadata::GuardEvidence>, GuardRunError> {
let mut evidence = Vec::new();
for guard in self.guards.iter() {
let budget = self.config.deadlines.guard_budget_for(guard.name());
let guard = Arc::clone(guard);
let owned = Arc::clone(owned);
let run_one = tokio::task::spawn_blocking(move || {
run_guards_owned(std::slice::from_ref(&guard), &owned)
});
let _abort_on_drop = AbortOnDrop(run_one.abort_handle());
let outcome = match budget {
Some(budget) => match tokio::time::timeout(budget, run_one).await {
Ok(joined) => joined,
Err(_elapsed) => {
return Err(GuardRunError::new(
KernelError::HotPathDeadlineExceeded {
stage: HotPathStage::GuardPipeline,
budget_ms: budget_ms_saturating(budget),
},
std::mem::take(&mut evidence),
));
}
},
None => run_one.await,
};
match outcome {
Ok(Ok(mut guard_evidence)) => evidence.append(&mut guard_evidence),
Ok(Err(mut guard_error)) => {
guard_error.evidence.splice(0..0, evidence);
return Err(guard_error);
}
Err(join_err) => {
return Err(GuardRunError::new(
KernelError::Internal(format!("guard task join failed: {join_err}")),
std::mem::take(&mut evidence),
));
}
}
}
Ok(evidence)
}
pub(crate) fn run_runtime_admission_hook(
&self,
request: &ToolCallRequest,
extra_metadata: Option<&serde_json::Value>,
now: u64,
now_unix_ms: u64,
matched_grant_index: Option<usize>,
) -> RuntimeAdmissionDecision {
let Some(hook) = self.runtime_admission_hook.as_ref() else {
let has_runtime_context = request
.governed_intent
.as_ref()
.and_then(|intent| intent.context.as_ref())
.is_some_and(|context| {
context.get("chioAdmission").is_some()
|| context.get("chioTreaty").is_some()
|| context.get("chioSwarm").is_some()
});
if has_runtime_context {
return RuntimeAdmissionDecision::deny(
"chio runtime admission hook is required for governed runtime requests",
Some(serde_json::json!({
"chio_runtime": {
"accepted": false,
"failure_code": "runtime_admission_hook_missing"
}
})),
);
}
if request.federated_origin_kernel_id.is_some() {
return RuntimeAdmissionDecision::deny(
"chio treaty-bound runtime admission context missing",
Some(serde_json::json!({
"chio_runtime": {
"accepted": false,
"failure_code": "missing_chio_treaty_context"
}
})),
);
}
return RuntimeAdmissionDecision::allow(None);
};
let context = RuntimeAdmissionContext {
request,
extra_metadata,
now_unix_secs: now,
now_unix_ms,
matched_grant_index,
local_kernel_id: self.federation_local_kernel_id(),
};
match hook.evaluate(&context) {
Ok(decision) => decision,
Err(error) => RuntimeAdmissionDecision::deny(
format!(
"runtime admission hook \"{}\" error (fail-closed): {error}",
hook.name()
),
Some(serde_json::json!({
"runtime_admission": {
"hook": hook.name(),
"accepted": false,
"failure_code": "runtime_admission_hook_error"
}
})),
),
}
}
pub(crate) fn release_runtime_admission_reservations(
&self,
metadata: Option<&serde_json::Value>,
) -> Result<(), KernelError> {
let Some(metadata) = metadata else {
return Ok(());
};
let Some(hook) = self.runtime_admission_hook.as_ref() else {
return Ok(());
};
hook.release_reserved(metadata)
}
pub(crate) fn mark_runtime_admission_reservations_retained_fail_closed(
&self,
metadata: Option<serde_json::Value>,
) -> Option<serde_json::Value> {
let mut retained = serde_json::Map::new();
{
let Some(runtime) = metadata
.as_ref()
.and_then(|value| value.get("chio_runtime"))
.and_then(serde_json::Value::as_object)
else {
return metadata;
};
for (source, target) in [
(
"reserved_destructive_lease_id",
"retained_destructive_lease_id",
),
(
"reserved_treaty_continuation_id",
"retained_treaty_continuation_id",
),
(
"reserved_swarm_continuation_id",
"retained_swarm_continuation_id",
),
] {
if let Some(id) = runtime
.get(source)
.and_then(serde_json::Value::as_str)
.filter(|id| !id.is_empty())
{
retained.insert(target.to_string(), serde_json::json!(id));
}
}
if retained.is_empty() {
return metadata;
}
retained.insert(
"reservations_retained_fail_closed".to_string(),
serde_json::Value::Bool(true),
);
}
merge_metadata_objects(
metadata,
Some(serde_json::json!({ "chio_runtime": retained })),
)
}
pub(crate) fn release_runtime_admission_reservations_for_pre_dispatch_denial(
&self,
metadata: Option<serde_json::Value>,
) -> (Option<serde_json::Value>, bool) {
let Some(metadata_value) = metadata else {
return (None, true);
};
let Some(hook) = self.runtime_admission_hook.as_ref() else {
return (Some(metadata_value), true);
};
match hook.release_reserved(&metadata_value) {
Ok(()) => (Some(metadata_value), true),
Err(error) => {
warn!(
hook = hook.name(),
reason = %redacted!(&error),
"runtime admission reservation release failed on pre-dispatch denial"
);
(
merge_metadata_objects(
Some(metadata_value),
Some(serde_json::json!({
"chio_runtime": {
"reservation_release_failed": true,
"reservation_retained": true
}
})),
),
false,
)
}
}
}
#[cfg(test)]
pub(crate) async fn dispatch_tool_call_with_cost(
&self,
request: &ToolCallRequest,
has_monetary_grant: bool,
) -> Result<(ToolServerOutput, Option<ToolInvocationCost>), KernelError> {
self.validate_required_execution_nonce(request, &request.capability)?;
let request_has_monetary_grant = resolve_required_matching_grants(
&request.capability,
&request.tool_name,
&request.server_id,
&request.arguments,
request.model_metadata.as_ref(),
)?
.iter()
.any(|matching| {
matching.grant.max_cost_per_invocation.is_some()
|| matching.grant.max_total_cost.is_some()
});
if has_monetary_grant || request_has_monetary_grant {
return Err(KernelError::DirectDispatchUnavailable);
}
self.reserve_presented_execution_nonce(request)?;
self.dispatch_within_budget(request, has_monetary_grant)
.await
}
#[cfg(test)]
pub(crate) async fn dispatch_within_budget(
&self,
request: &ToolCallRequest,
has_monetary_grant: bool,
) -> Result<(ToolServerOutput, Option<ToolInvocationCost>), KernelError> {
let server = self
.tool_servers
.get(&request.server_id)
.cloned()
.ok_or_else(|| {
KernelError::ToolNotRegistered(format!(
"server \"{}\" / tool \"{}\"",
request.server_id, request.tool_name
))
})?;
self.dispatch_resolved_server_within_budget(server, request, has_monetary_grant)
.await
}
pub(crate) async fn dispatch_resolved_server_within_budget(
&self,
server: Arc<dyn ToolServerConnection>,
request: &ToolCallRequest,
has_monetary_grant: bool,
) -> Result<(ToolServerOutput, Option<ToolInvocationCost>), KernelError> {
let Some(budget) = self
.config
.deadlines
.dispatch_budget_for(&request.server_id)
else {
return Self::invoke_resolved_server(
server,
request.tool_name.clone(),
request.arguments.clone(),
has_monetary_grant,
)
.await;
};
let timer_available = dispatch_timer_available();
let multi_thread = matches!(
tokio::runtime::Handle::try_current().map(|handle| handle.runtime_flavor()),
Ok(tokio::runtime::RuntimeFlavor::MultiThread)
);
if !multi_thread {
let call = Self::invoke_resolved_server(
server,
request.tool_name.clone(),
request.arguments.clone(),
has_monetary_grant,
);
if timer_available {
return match tokio::time::timeout(budget, call).await {
Ok(result) => result,
Err(_elapsed) => Err(dispatch_deadline_exceeded(budget)),
};
}
return call.await;
}
let tool_name = request.tool_name.clone();
let arguments = request.arguments.clone();
let handle = tokio::runtime::Handle::current();
let join = tokio::task::spawn_blocking(move || {
let call =
Self::invoke_resolved_server(server, tool_name, arguments, has_monetary_grant);
if timer_available {
handle.block_on(async move {
match tokio::time::timeout(budget, call).await {
Ok(result) => result,
Err(_elapsed) => Err(dispatch_deadline_exceeded(budget)),
}
})
} else {
handle.block_on(call)
}
});
let _abort_on_drop = AbortOnDrop(join.abort_handle());
if timer_available {
match tokio::time::timeout(budget, join).await {
Ok(Ok(result)) => result,
Ok(Err(join_error)) => Err(KernelError::Internal(format!(
"dispatch task join failed: {join_error}"
))),
Err(_elapsed) => Err(dispatch_deadline_exceeded(budget)),
}
} else {
match join.await {
Ok(result) => result,
Err(join_error) => Err(KernelError::Internal(format!(
"dispatch task join failed: {join_error}"
))),
}
}
}
async fn invoke_resolved_server(
server: Arc<dyn ToolServerConnection>,
tool_name: String,
arguments: serde_json::Value,
has_monetary_grant: bool,
) -> Result<(ToolServerOutput, Option<ToolInvocationCost>), KernelError> {
if let Some(stream) = server
.invoke_stream(&tool_name, arguments.clone(), None)
.await?
{
return Ok((ToolServerOutput::Stream(stream), None));
}
if has_monetary_grant {
let (value, cost) = server.invoke_with_cost(&tool_name, arguments, None).await?;
Ok((ToolServerOutput::Value(value), cost))
} else {
let value = server.invoke(&tool_name, arguments, None).await?;
Ok((ToolServerOutput::Value(value), None))
}
}
pub(crate) fn record_child_receipt(
&self,
receipt: &ChildRequestReceipt,
) -> Result<(), KernelError> {
let receipt_store_write = self
.receipt_store_write_lock
.lock()
.map_err(|_| KernelError::Internal("receipt store write lock poisoned".to_string()))?;
self.with_receipt_store(|store| {
Ok(store.append_child_receipt_with_timeout(
receipt,
self.config.deadlines.receipt_append_budget(),
)?)
})?;
drop(receipt_store_write);
self.append_child_receipt_to_local_log(receipt.clone());
Ok(())
}
pub(crate) fn append_chio_receipt_to_local_log(&self, receipt: ChioReceipt) {
match self.receipt_log.lock() {
Ok(mut log) => log.append(receipt),
Err(poisoned) => poisoned.into_inner().append(receipt),
}
}
fn append_child_receipt_to_local_log(&self, receipt: ChildRequestReceipt) {
match self.child_receipt_log.lock() {
Ok(mut log) => log.append(receipt),
Err(poisoned) => poisoned.into_inner().append(receipt),
}
}
}
#[cfg(test)]
mod timer_probe_tests {
use super::dispatch_timer_available;
#[test]
fn re_probes_when_the_entered_runtime_changes_on_one_thread(
) -> Result<(), Box<dyn std::error::Error>> {
let timerless = tokio::runtime::Builder::new_current_thread().build()?;
timerless.block_on(async {
assert!(!dispatch_timer_available());
});
let timed = tokio::runtime::Builder::new_current_thread()
.enable_time()
.build()?;
timed.block_on(async {
assert!(dispatch_timer_available());
let elapsed = tokio::time::timeout(
std::time::Duration::from_millis(1),
std::future::pending::<()>(),
)
.await;
assert!(elapsed.is_err(), "the timer must actually fire here");
});
Ok(())
}
#[test]
fn reports_false_in_a_runtime_without_a_time_driver() -> Result<(), Box<dyn std::error::Error>>
{
let runtime = tokio::runtime::Builder::new_current_thread().build()?;
runtime.block_on(async {
assert!(!dispatch_timer_available());
let ran_inline = if dispatch_timer_available() {
tokio::time::timeout(std::time::Duration::from_millis(1), std::future::ready(()))
.await
.is_ok()
} else {
std::future::ready(()).await;
true
};
assert!(ran_inline);
});
Ok(())
}
#[test]
fn reports_true_in_a_runtime_with_a_time_driver() -> Result<(), Box<dyn std::error::Error>> {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_time()
.build()?;
runtime.block_on(async {
assert!(dispatch_timer_available());
let elapsed = tokio::time::timeout(
std::time::Duration::from_millis(1),
std::future::pending::<()>(),
)
.await;
assert!(elapsed.is_err(), "the timer must actually fire here");
});
Ok(())
}
}