1use super::*;
2
3#[derive(Clone, Debug, PartialEq, serde::Serialize)]
4pub struct StructuredErrorReport {
5 pub code: String,
6 pub message: String,
7 pub context: serde_json::Value,
8 pub suggested_fix: String,
9}
10
11impl StructuredErrorReport {
12 pub fn new(
13 code: impl Into<String>,
14 message: impl Into<String>,
15 context: serde_json::Value,
16 suggested_fix: impl Into<String>,
17 ) -> Self {
18 Self {
19 code: code.into(),
20 message: message.into(),
21 context,
22 suggested_fix: suggested_fix.into(),
23 }
24 }
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum OverloadResource {
31 ReceiptMirror,
32 FederationCache,
33 VelocityBuckets,
34 AdmissionKeys,
35 ConcurrencyBuckets,
36 SessionJournal,
37 StreamBytes,
38 StreamChunks,
39 Allocation,
40}
41
42#[derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq)]
44pub enum SettlementRuntimeConfigError {
45 #[error("invalid settlement retry policy: {0}")]
46 InvalidRetryPolicy(#[from] chio_settle::RetryPolicyError),
47 #[error("settlement observer runtime requires a receipt store")]
48 MissingReceiptStore,
49 #[error("receipt store lacks timeout-aware atomic settlement observation projection")]
50 UnsupportedAtomicProjection,
51 #[error("receipt store lacks a settlement backend binding")]
52 MissingStoreBinding,
53 #[error("receipt store and outcome store use different settlement backend bindings")]
54 StoreBindingMismatch,
55 #[error("receipt store cannot be replaced while a settlement observer runtime is installed")]
56 ReceiptStoreReplacement,
57}
58
59impl SettlementRuntimeConfigError {
60 const fn as_str(self) -> &'static str {
61 match self {
62 Self::InvalidRetryPolicy(_) => "invalid_retry_policy",
63 Self::MissingReceiptStore => "missing_receipt_store",
64 Self::UnsupportedAtomicProjection => "unsupported_atomic_projection",
65 Self::MissingStoreBinding => "missing_store_binding",
66 Self::StoreBindingMismatch => "store_binding_mismatch",
67 Self::ReceiptStoreReplacement => "receipt_store_replacement",
68 }
69 }
70}
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
76#[serde(rename_all = "snake_case")]
77pub enum HotPathStage {
78 GuardPipeline,
79 Dispatch,
80 ReceiptAppend,
81}
82
83impl std::fmt::Display for HotPathStage {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 let s = match self {
86 Self::GuardPipeline => "guard_pipeline",
87 Self::Dispatch => "dispatch",
88 Self::ReceiptAppend => "receipt_append",
89 };
90 f.write_str(s)
91 }
92}
93
94#[derive(Debug, thiserror::Error)]
96pub enum KernelError {
97 #[error("unknown session: {0}")]
98 UnknownSession(SessionId),
99
100 #[error("session already exists: {0}")]
101 SessionAlreadyExists(SessionId),
102
103 #[error("session error: {0}")]
104 Session(#[from] SessionError),
105
106 #[error("capability has expired")]
107 CapabilityExpired,
108
109 #[error("capability not yet valid")]
110 CapabilityNotYetValid,
111
112 #[error("capability has been revoked: {0}")]
113 CapabilityRevoked(CapabilityId),
114
115 #[error("capability signature is invalid")]
116 InvalidSignature,
117
118 #[error("capability issuer is not a trusted CA")]
119 UntrustedIssuer,
120
121 #[error("capability issuance failed: {0}")]
122 CapabilityIssuanceFailed(String),
123
124 #[error("capability issuance denied: {0}")]
125 CapabilityIssuanceDenied(String),
126
127 #[error("requested tool {tool} on server {server} is not in capability scope")]
128 OutOfScope { tool: String, server: String },
129
130 #[error("requested resource {uri} is not in capability scope")]
131 OutOfScopeResource { uri: String },
132
133 #[error("requested prompt {prompt} is not in capability scope")]
134 OutOfScopePrompt { prompt: String },
135
136 #[error("invocation budget exhausted for capability {0}")]
137 BudgetExhausted(CapabilityId),
138
139 #[error("captured budget replay denied for capability {0}")]
140 CapturedBudgetReplay(CapabilityId),
141
142 #[error("direct tool dispatch is unavailable; use the full evaluation pipeline")]
143 DirectDispatchUnavailable,
144
145 #[error("request agent {actual} does not match capability subject {expected}")]
146 SubjectMismatch { expected: String, actual: String },
147
148 #[error("delegation chain revoked at ancestor {0}")]
149 DelegationChainRevoked(CapabilityId),
150
151 #[error("delegation admission failed: {0}")]
152 DelegationInvalid(String),
153
154 #[error("invalid capability constraint: {0}")]
155 InvalidConstraint(String),
156
157 #[error("governed transaction denied: {0}")]
158 GovernedTransactionDenied(String),
159
160 #[error("guard denied the request: {0}")]
161 GuardDenied(String),
162
163 #[error("tool server error: {0}")]
164 ToolServerError(String),
165
166 #[error("request stream incomplete: {0}")]
167 RequestIncomplete(String),
168
169 #[error("tool not registered: {0}")]
170 ToolNotRegistered(String),
171
172 #[error("resource not registered: {0}")]
173 ResourceNotRegistered(String),
174
175 #[error("resource read denied by session roots for {uri}: {reason}")]
176 ResourceRootDenied { uri: String, reason: String },
177
178 #[error("prompt not registered: {0}")]
179 PromptNotRegistered(String),
180
181 #[error("sampling is disabled by policy")]
182 SamplingNotAllowedByPolicy,
183
184 #[error("sampling was not negotiated with the client")]
185 SamplingNotNegotiated,
186
187 #[error("sampling context inclusion is not supported by the client")]
188 SamplingContextNotSupported,
189
190 #[error("sampling tool use is disabled by policy")]
191 SamplingToolUseNotAllowedByPolicy,
192
193 #[error("sampling tool use was not negotiated with the client")]
194 SamplingToolUseNotNegotiated,
195
196 #[error("elicitation is disabled by policy")]
197 ElicitationNotAllowedByPolicy,
198
199 #[error("elicitation was not negotiated with the client")]
200 ElicitationNotNegotiated,
201
202 #[error("elicitation form mode is not supported by the client")]
203 ElicitationFormNotSupported,
204
205 #[error("elicitation URL mode was not negotiated with the client")]
206 ElicitationUrlNotSupported,
207
208 #[error("{message}")]
209 UrlElicitationsRequired {
210 message: String,
211 elicitations: Vec<CreateElicitationOperation>,
212 },
213
214 #[error("roots/list was not negotiated with the client")]
215 RootsNotNegotiated,
216
217 #[error("sampling child requests require a ready session-bound parent request")]
218 InvalidChildRequestParent,
219
220 #[error("request {request_id} was cancelled: {reason}")]
221 RequestCancelled {
222 request_id: RequestId,
223 reason: String,
224 },
225
226 #[error("receipt signing failed: {0}")]
227 ReceiptSigningFailed(String),
228
229 #[error("receipt persistence failed: {0}")]
230 ReceiptPersistence(#[from] ReceiptStoreError),
231
232 #[error("revocation store error: {0}")]
233 RevocationStore(#[from] RevocationStoreError),
234
235 #[error("budget store error: {0}")]
236 BudgetStore(#[from] BudgetStoreError),
237
238 #[error("durable admission failed: {0}")]
239 DurableAdmission(String),
240
241 #[error(
242 "cross-currency budget enforcement failed: no price oracle configured for {base}/{quote}"
243 )]
244 NoCrossCurrencyOracle { base: String, quote: String },
245
246 #[error("cross-currency budget enforcement failed: {0}")]
247 CrossCurrencyOracle(String),
248
249 #[error("web3 evidence prerequisites unavailable: {0}")]
250 Web3EvidenceUnavailable(String),
251
252 #[error("settlement runtime configuration failed: {0}")]
253 SettlementConfiguration(#[from] SettlementRuntimeConfigError),
254
255 #[error("internal error: {0}")]
256 Internal(String),
257
258 #[error("DPoP proof verification failed: {0}")]
259 DpopVerificationFailed(String),
260
261 #[error("approval rejected: {0}")]
265 ApprovalRejected(String),
266
267 #[error(
279 "sync tool-dispatch bridge cannot drive an async tool server on a current-thread \
280 Tokio runtime; switch the host to a multi-thread Tokio runtime"
281 )]
282 SyncBridgeIncompatibleWithCurrentThreadRuntime,
283
284 #[error(
289 "reserving authorization must not receive a presented execution nonce; this entry \
290 point mints nonces, it does not settle them"
291 )]
292 ReservingAuthorizationRejectsPresentedNonce,
293
294 #[error("kernel overloaded: {resource:?} at capacity")]
297 Overloaded { resource: OverloadResource },
298
299 #[error("hot-path deadline exceeded at {stage}: budget {budget_ms}ms")]
304 HotPathDeadlineExceeded { stage: HotPathStage, budget_ms: u64 },
305
306 #[error("receipt commit writer unavailable: {0}")]
310 ReceiptWriterUnavailable(String),
311}
312
313impl KernelError {
314 fn report_with_context(
315 &self,
316 code: &str,
317 context: serde_json::Value,
318 suggested_fix: impl Into<String>,
319 ) -> StructuredErrorReport {
320 StructuredErrorReport::new(code, self.to_string(), context, suggested_fix)
321 }
322
323 pub fn report(&self) -> StructuredErrorReport {
324 match self {
325 Self::Overloaded { resource } => self.report_with_context(
326 "CHIO-KERNEL-OVERLOADED",
327 serde_json::json!({ "resource": format!("{resource:?}") }),
328 "The kernel shed load to stay within its memory budget. Retry with backoff; \
329 if sustained, raise the process memory budget or scale out.",
330 ),
331 Self::UnknownSession(session_id) => self.report_with_context(
332 "CHIO-KERNEL-UNKNOWN-SESSION",
333 serde_json::json!({ "session_id": session_id.to_string() }),
334 "Create the session first or reuse a session ID returned by the kernel before issuing follow-up operations.",
335 ),
336 Self::SessionAlreadyExists(session_id) => self.report_with_context(
337 "CHIO-KERNEL-SESSION-ALREADY-EXISTS",
338 serde_json::json!({ "session_id": session_id.to_string() }),
339 "Use a fresh session ID or drop the duplicate restored record before opening the session.",
340 ),
341 Self::Session(error) => self.report_with_context(
342 "CHIO-KERNEL-SESSION",
343 serde_json::json!({ "session_error": error.to_string() }),
344 "Inspect the session lifecycle and ordering of operations, then recreate the session if it is no longer valid.",
345 ),
346 Self::CapabilityExpired => self.report_with_context(
347 "CHIO-KERNEL-CAPABILITY-EXPIRED",
348 serde_json::json!({}),
349 "Refresh or reissue the capability so its validity window includes the current time.",
350 ),
351 Self::CapabilityNotYetValid => self.report_with_context(
352 "CHIO-KERNEL-CAPABILITY-NOT-YET-VALID",
353 serde_json::json!({}),
354 "Use a capability whose validity window has started, or correct the issuer clock skew if timestamps are wrong.",
355 ),
356 Self::CapabilityRevoked(capability_id) => self.report_with_context(
357 "CHIO-KERNEL-CAPABILITY-REVOKED",
358 serde_json::json!({ "capability_id": capability_id }),
359 "Request a new non-revoked capability or inspect the revocation record for this capability lineage.",
360 ),
361 Self::InvalidSignature => self.report_with_context(
362 "CHIO-KERNEL-INVALID-SIGNATURE",
363 serde_json::json!({}),
364 "Reissue the capability or receipt with the correct signing key and verify the payload was not mutated in transit.",
365 ),
366 Self::UntrustedIssuer => self.report_with_context(
367 "CHIO-KERNEL-UNTRUSTED-ISSUER",
368 serde_json::json!({}),
369 "Configure the issuing CA public key in the kernel trust set or use a capability issued by a trusted authority.",
370 ),
371 Self::CapabilityIssuanceFailed(reason) => self.report_with_context(
372 "CHIO-KERNEL-CAPABILITY-ISSUANCE-FAILED",
373 serde_json::json!({ "reason": reason }),
374 "Inspect the issuance pipeline inputs and upstream stores, then retry once the issuing dependency is healthy.",
375 ),
376 Self::CapabilityIssuanceDenied(reason) => self.report_with_context(
377 "CHIO-KERNEL-CAPABILITY-ISSUANCE-DENIED",
378 serde_json::json!({ "reason": reason }),
379 "Adjust the issuance request so it satisfies the policy, score, or trust requirements enforced by the authority.",
380 ),
381 Self::OutOfScope { tool, server } => self.report_with_context(
382 "CHIO-KERNEL-OUT-OF-SCOPE-TOOL",
383 serde_json::json!({ "tool": tool, "server": server }),
384 "Issue a capability that grants this tool on this server, or call a tool already inside the granted scope.",
385 ),
386 Self::OutOfScopeResource { uri } => self.report_with_context(
387 "CHIO-KERNEL-OUT-OF-SCOPE-RESOURCE",
388 serde_json::json!({ "uri": uri }),
389 "Issue a capability/resource grant that matches this URI, or request a resource already inside scope.",
390 ),
391 Self::OutOfScopePrompt { prompt } => self.report_with_context(
392 "CHIO-KERNEL-OUT-OF-SCOPE-PROMPT",
393 serde_json::json!({ "prompt": prompt }),
394 "Issue a capability/prompt grant that matches this prompt, or request a prompt already inside scope.",
395 ),
396 Self::BudgetExhausted(capability_id) => self.report_with_context(
397 "CHIO-KERNEL-BUDGET-EXHAUSTED",
398 serde_json::json!({ "capability_id": capability_id }),
399 "Increase the capability budget, wait for the budget window to reset, or lower the cost of the requested operation.",
400 ),
401 Self::CapturedBudgetReplay(capability_id) => self.report_with_context(
402 "CHIO-KERNEL-CAPTURED-BUDGET-REPLAY",
403 serde_json::json!({ "capability_id": capability_id }),
404 "Use a new request ID. A captured budget authorization cannot be reused by another dispatch.",
405 ),
406 Self::DirectDispatchUnavailable => self.report_with_context(
407 "CHIO-KERNEL-DIRECT-DISPATCH-UNAVAILABLE",
408 serde_json::json!({}),
409 "Use the full evaluation pipeline so admission, dispatch, compensation, and receipt persistence remain one lifecycle.",
410 ),
411 Self::SubjectMismatch { expected, actual } => self.report_with_context(
412 "CHIO-KERNEL-SUBJECT-MISMATCH",
413 serde_json::json!({ "expected": expected, "actual": actual }),
414 "Use a capability issued to the requesting subject, or correct the agent identity bound to the request.",
415 ),
416 Self::DelegationChainRevoked(capability_id) => self.report_with_context(
417 "CHIO-KERNEL-DELEGATION-CHAIN-REVOKED",
418 serde_json::json!({ "capability_id": capability_id }),
419 "Inspect the capability lineage and reissue the chain from a non-revoked ancestor.",
420 ),
421 Self::DelegationInvalid(reason) => self.report_with_context(
422 "CHIO-KERNEL-DELEGATION-INVALID",
423 serde_json::json!({ "reason": reason }),
424 "Reissue the delegated capability with a valid ancestor snapshot chain, delegator binding, attenuation proof, and delegated scope ceiling.",
425 ),
426 Self::InvalidConstraint(reason) => self.report_with_context(
427 "CHIO-KERNEL-INVALID-CONSTRAINT",
428 serde_json::json!({ "reason": reason }),
429 "Fix the capability constraint payload so it matches the kernel's supported schema and value rules.",
430 ),
431 Self::GovernedTransactionDenied(reason) => self.report_with_context(
432 "CHIO-KERNEL-GOVERNED-TRANSACTION-DENIED",
433 serde_json::json!({ "reason": reason }),
434 "Adjust the governed transaction intent so it satisfies the configured approval and policy requirements.",
435 ),
436 Self::GuardDenied(reason) => self.report_with_context(
437 "CHIO-KERNEL-GUARD-DENIED",
438 serde_json::json!({ "reason": reason }),
439 "Adjust the request or policy/guard configuration so the request satisfies the active guard pipeline.",
440 ),
441 Self::ToolServerError(reason) => self.report_with_context(
442 "CHIO-KERNEL-TOOL-SERVER",
443 serde_json::json!({ "reason": reason }),
444 "Inspect the wrapped tool server logs and protocol compatibility, then retry once the server is healthy.",
445 ),
446 Self::RequestIncomplete(reason) => self.report_with_context(
447 "CHIO-KERNEL-REQUEST-INCOMPLETE",
448 serde_json::json!({ "reason": reason }),
449 "Resubmit the request with all required fields and protocol state transitions present.",
450 ),
451 Self::ToolNotRegistered(tool) => self.report_with_context(
452 "CHIO-KERNEL-TOOL-NOT-REGISTERED",
453 serde_json::json!({ "tool": tool }),
454 "Register the tool on the target server or update the request to reference an exposed tool.",
455 ),
456 Self::ResourceNotRegistered(uri) => self.report_with_context(
457 "CHIO-KERNEL-RESOURCE-NOT-REGISTERED",
458 serde_json::json!({ "uri": uri }),
459 "Register the resource provider for this URI or request a resource that is actually exposed by the runtime.",
460 ),
461 Self::ResourceRootDenied { uri, reason } => self.report_with_context(
462 "CHIO-KERNEL-RESOURCE-ROOT-DENIED",
463 serde_json::json!({ "uri": uri, "reason": reason }),
464 "Expand the session filesystem roots if the access is intentional, or request a resource inside the approved root set.",
465 ),
466 Self::PromptNotRegistered(prompt) => self.report_with_context(
467 "CHIO-KERNEL-PROMPT-NOT-REGISTERED",
468 serde_json::json!({ "prompt": prompt }),
469 "Register the prompt provider for this prompt name or request a prompt that is actually exposed.",
470 ),
471 Self::SamplingNotAllowedByPolicy => self.report_with_context(
472 "CHIO-KERNEL-SAMPLING-NOT-ALLOWED",
473 serde_json::json!({}),
474 "Enable sampling in policy if this workflow requires it, or retry without a sampling request.",
475 ),
476 Self::SamplingNotNegotiated => self.report_with_context(
477 "CHIO-KERNEL-SAMPLING-NOT-NEGOTIATED",
478 serde_json::json!({}),
479 "Negotiate sampling support with the client before issuing sampling operations.",
480 ),
481 Self::SamplingContextNotSupported => self.report_with_context(
482 "CHIO-KERNEL-SAMPLING-CONTEXT-NOT-SUPPORTED",
483 serde_json::json!({}),
484 "Disable sampling context inclusion or upgrade the client to one that supports the negotiated feature.",
485 ),
486 Self::SamplingToolUseNotAllowedByPolicy => self.report_with_context(
487 "CHIO-KERNEL-SAMPLING-TOOL-USE-NOT-ALLOWED",
488 serde_json::json!({}),
489 "Enable sampling tool use in policy or retry without delegated tool execution inside the sampling branch.",
490 ),
491 Self::SamplingToolUseNotNegotiated => self.report_with_context(
492 "CHIO-KERNEL-SAMPLING-TOOL-USE-NOT-NEGOTIATED",
493 serde_json::json!({}),
494 "Negotiate sampling tool-use support with the client before attempting tool execution inside sampling.",
495 ),
496 Self::ElicitationNotAllowedByPolicy => self.report_with_context(
497 "CHIO-KERNEL-ELICITATION-NOT-ALLOWED",
498 serde_json::json!({}),
499 "Enable elicitation in policy or retry without requesting user input through the kernel.",
500 ),
501 Self::ElicitationNotNegotiated => self.report_with_context(
502 "CHIO-KERNEL-ELICITATION-NOT-NEGOTIATED",
503 serde_json::json!({}),
504 "Negotiate elicitation support with the client before attempting elicitation operations.",
505 ),
506 Self::ElicitationFormNotSupported => self.report_with_context(
507 "CHIO-KERNEL-ELICITATION-FORM-NOT-SUPPORTED",
508 serde_json::json!({}),
509 "Switch to a supported elicitation mode or upgrade the client to one that supports form-mode elicitation.",
510 ),
511 Self::ElicitationUrlNotSupported => self.report_with_context(
512 "CHIO-KERNEL-ELICITATION-URL-NOT-SUPPORTED",
513 serde_json::json!({}),
514 "Switch to a supported elicitation mode or negotiate URL-based elicitation support with the client.",
515 ),
516 Self::UrlElicitationsRequired {
517 message,
518 elicitations,
519 } => self.report_with_context(
520 "CHIO-KERNEL-URL-ELICITATIONS-REQUIRED",
521 serde_json::json!({
522 "message": message,
523 "elicitation_count": elicitations.len()
524 }),
525 "Complete the required URL-based elicitation flow and resubmit the request afterward.",
526 ),
527 Self::RootsNotNegotiated => self.report_with_context(
528 "CHIO-KERNEL-ROOTS-NOT-NEGOTIATED",
529 serde_json::json!({}),
530 "Negotiate roots/list support with the client before using root-scoped resource protections.",
531 ),
532 Self::InvalidChildRequestParent => self.report_with_context(
533 "CHIO-KERNEL-INVALID-CHILD-REQUEST-PARENT",
534 serde_json::json!({}),
535 "Create the child request from a ready session-bound parent request that is currently in flight.",
536 ),
537 Self::RequestCancelled { request_id, reason } => self.report_with_context(
538 "CHIO-KERNEL-REQUEST-CANCELLED",
539 serde_json::json!({ "request_id": request_id.to_string(), "reason": reason }),
540 "Stop using the cancelled request ID and restart the operation if the workflow still needs to continue.",
541 ),
542 Self::ReceiptSigningFailed(reason) => self.report_with_context(
543 "CHIO-KERNEL-RECEIPT-SIGNING-FAILED",
544 serde_json::json!({ "reason": reason }),
545 "Inspect the kernel signing key configuration and signing payload integrity, then retry receipt generation.",
546 ),
547 Self::ReceiptPersistence(error) => self.report_with_context(
548 "CHIO-KERNEL-RECEIPT-PERSISTENCE",
549 serde_json::json!({ "source": error.to_string() }),
550 "Check the configured receipt store connectivity, permissions, and schema health before retrying.",
551 ),
552 Self::RevocationStore(error) => self.report_with_context(
553 "CHIO-KERNEL-REVOCATION-STORE",
554 serde_json::json!({ "source": error.to_string() }),
555 "Check the configured revocation store connectivity, permissions, and schema health before retrying.",
556 ),
557 Self::BudgetStore(error) => self.report_with_context(
558 "CHIO-KERNEL-BUDGET-STORE",
559 serde_json::json!({ "source": error.to_string() }),
560 "Check the configured budget store connectivity, permissions, and schema health before retrying.",
561 ),
562 Self::DurableAdmission(reason) => self.report_with_context(
563 "CHIO-KERNEL-DURABLE-ADMISSION",
564 serde_json::json!({ "reason": reason }),
565 "Repair the fenced admission authority and reconcile the retained operation before retrying this request ID.",
566 ),
567 Self::NoCrossCurrencyOracle { base, quote } => self.report_with_context(
568 "CHIO-KERNEL-NO-CROSS-CURRENCY-ORACLE",
569 serde_json::json!({ "base": base, "quote": quote }),
570 "Configure a price oracle for this currency pair or avoid a cross-currency budget path for this request.",
571 ),
572 Self::CrossCurrencyOracle(reason) => self.report_with_context(
573 "CHIO-KERNEL-CROSS-CURRENCY-ORACLE",
574 serde_json::json!({ "reason": reason }),
575 "Inspect the price-oracle configuration and upstream quote availability for the requested currency conversion.",
576 ),
577 Self::Web3EvidenceUnavailable(reason) => self.report_with_context(
578 "CHIO-KERNEL-WEB3-EVIDENCE-UNAVAILABLE",
579 serde_json::json!({ "reason": reason }),
580 "Enable the required receipt-store, checkpoint, and oracle prerequisites before running the web3 evidence path.",
581 ),
582 Self::SettlementConfiguration(error) => self.report_with_context(
583 "CHIO-KERNEL-SETTLEMENT-CONFIGURATION",
584 serde_json::json!({ "kind": error.as_str() }),
585 "Install a receipt store and outcome store backed by the same atomic settlement writer, then correct the retry policy or backend capability before startup.",
586 ),
587 Self::Internal(reason) => self.report_with_context(
588 "CHIO-KERNEL-INTERNAL",
589 serde_json::json!({ "reason": reason }),
590 "Capture the error report and kernel logs, then treat this as a reproducible kernel bug if it persists.",
591 ),
592 Self::DpopVerificationFailed(reason) => self.report_with_context(
593 "CHIO-KERNEL-DPOP-VERIFICATION-FAILED",
594 serde_json::json!({ "reason": reason }),
595 "Attach a valid DPoP proof bound to the current capability, request, server, and tool before retrying.",
596 ),
597 Self::ApprovalRejected(reason) => self.report_with_context(
598 "CHIO-KERNEL-APPROVAL-REJECTED",
599 serde_json::json!({ "reason": reason }),
600 "Obtain a fresh approval token bound to this exact request and retry once a human approver has signed it.",
601 ),
602 Self::SyncBridgeIncompatibleWithCurrentThreadRuntime => self.report_with_context(
603 "CHIO-KERNEL-SYNC-BRIDGE-INCOMPATIBLE",
604 serde_json::json!({}),
605 "Move the host process to a multi-thread Tokio runtime so block_in_place can drive async tool dispatch. The public async evaluate_tool_call path is still backed by the blocking evaluator on this branch and is not a current-thread runtime workaround.",
606 ),
607 Self::ReservingAuthorizationRejectsPresentedNonce => self.report_with_context(
608 "CHIO-KERNEL-RESERVING-AUTHORIZATION-PRESENTED-NONCE",
609 serde_json::json!({}),
610 "Submit the reserving authorization request without a presented execution nonce; present the minted nonce to the tool server for settlement instead.",
611 ),
612 Self::HotPathDeadlineExceeded { stage, budget_ms } => self.report_with_context(
613 "CHIO-KERNEL-HOT-PATH-DEADLINE",
614 serde_json::json!({ "stage": stage, "budget_ms": budget_ms }),
615 "Raise the offending stage budget in [deadlines] if the workload is legitimately slow, or repair the slow guard, tool server, or writer. Do not retry blindly; the request already failed closed.",
616 ),
617 Self::ReceiptWriterUnavailable(reason) => self.report_with_context(
618 "CHIO-KERNEL-RECEIPT-WRITER-UNAVAILABLE",
619 serde_json::json!({ "reason": reason }),
620 "The receipt commit writer is not durably accepting writes; repair or restart the writer. Requests deny until liveness recovers.",
621 ),
622 }
623 }
624}
625
626impl From<crate::admission_operation::AdmissionOperationError> for KernelError {
627 fn from(error: crate::admission_operation::AdmissionOperationError) -> Self {
628 Self::DurableAdmission(error.to_string())
629 }
630}
631
632#[cfg(test)]
633mod overload_tests {
634 #![allow(clippy::unwrap_used, clippy::expect_used)]
635 use super::*;
636
637 #[test]
638 fn overloaded_reports_resource_and_fail_closed_code() {
639 let err = KernelError::Overloaded {
640 resource: OverloadResource::AdmissionKeys,
641 };
642 let report = err.report();
643 assert_eq!(report.code, "CHIO-KERNEL-OVERLOADED");
644 assert!(
645 err.to_string().contains("AdmissionKeys"),
646 "display must name the shed resource: {err}"
647 );
648 assert_eq!(
649 report.context.get("resource").and_then(|v| v.as_str()),
650 Some("AdmissionKeys")
651 );
652 }
653
654 #[test]
655 fn settlement_configuration_has_operator_guidance() {
656 let error = KernelError::SettlementConfiguration(
657 SettlementRuntimeConfigError::StoreBindingMismatch,
658 );
659 let report = error.report();
660
661 assert_eq!(report.code, "CHIO-KERNEL-SETTLEMENT-CONFIGURATION");
662 assert_eq!(
663 report
664 .context
665 .get("kind")
666 .and_then(serde_json::Value::as_str),
667 Some("store_binding_mismatch")
668 );
669 assert!(!report.suggested_fix.contains("kernel bug"));
670 }
671}
672
673#[cfg(test)]
674mod hot_path_error_taxonomy_tests {
675 use super::*;
676
677 #[test]
678 fn hot_path_stage_display_is_snake_case() {
679 assert_eq!(HotPathStage::GuardPipeline.to_string(), "guard_pipeline");
680 assert_eq!(HotPathStage::Dispatch.to_string(), "dispatch");
681 assert_eq!(HotPathStage::ReceiptAppend.to_string(), "receipt_append");
682 }
683
684 #[test]
685 fn hot_path_deadline_exceeded_reports_stable_code() {
686 let err = KernelError::HotPathDeadlineExceeded {
687 stage: HotPathStage::Dispatch,
688 budget_ms: 1500,
689 };
690 let report = err.report();
691 assert_eq!(report.code, "CHIO-KERNEL-HOT-PATH-DEADLINE");
692 assert!(err.to_string().contains("dispatch"));
693 assert!(err.to_string().contains("1500"));
694 }
695
696 #[test]
697 fn receipt_writer_unavailable_reports_stable_code() {
698 let err = KernelError::ReceiptWriterUnavailable("writer is Wedged".to_string());
699 assert_eq!(err.report().code, "CHIO-KERNEL-RECEIPT-WRITER-UNAVAILABLE");
700 }
701}