1use std::path::PathBuf;
2
3use runx_contracts::AuthorityVerb;
4use runx_core::state_machine::FanoutSyncDecision;
5use thiserror::Error;
6
7use crate::credentials::CredentialDeliveryError;
8
9#[derive(Debug, Error)]
10pub enum RuntimeError {
11 #[error("runtime I/O failed while {context}: {source}")]
12 Io {
13 context: String,
14 #[source]
15 source: std::io::Error,
16 },
17 #[error("graph parse failed: {0}")]
18 ParseGraph(#[from] runx_parser::ParseError),
19 #[error("graph validation failed: {0}")]
20 ValidateGraph(#[from] runx_parser::ValidationError),
21 #[error("JSON serialization failed while {context}: {source}")]
22 Json {
23 context: String,
24 #[source]
25 source: serde_json::Error,
26 },
27 #[error("graph step '{step_id}' is missing")]
28 StepMissing { step_id: String },
29 #[error("graph step '{step_id}' has no skill target")]
30 StepMissingSkill { step_id: String },
31 #[error("graph step '{step_id}' has invalid run configuration: {reason}")]
32 InvalidRunStep { step_id: String, reason: String },
33 #[error("graph step '{step_id}' uses unsupported run type '{run_type}'")]
34 UnsupportedRunStep { step_id: String, run_type: String },
35 #[error("graph step '{step_id}' is blocked: {reason}")]
36 GraphBlocked { step_id: String, reason: String },
37 #[error(
38 "context edge from step '{from_step}' could not resolve path '{output_path}': segment '{missing_segment}' is absent; available keys there: [{}]",
39 .available_keys.join(", ")
40 )]
41 ContextEdgeUnresolved {
42 from_step: String,
43 output_path: String,
44 missing_segment: String,
45 available_keys: Vec<String>,
46 },
47 #[error(
48 "context edge from step '{from_step}' binds base/diagnostic field '{base_field}' (path '{output_path}'); base fields (raw/skill_claim/stdout/stderr/status) are not addressable, bind the step contract (a declared output or artifact packet) instead"
49 )]
50 ContextEdgeBaseKey {
51 from_step: String,
52 output_path: String,
53 base_field: String,
54 },
55 #[error("authority {verb:?} denied graph step '{step_id}': {reason}")]
56 AuthorityDenied {
57 verb: AuthorityVerb,
58 step_id: String,
59 reason: String,
60 },
61 #[error("graph step '{step_id}' failed planning: {reason}")]
62 GraphPlanningFailed { step_id: String, reason: String },
63 #[error("graph step '{step_id}' paused: {reason}")]
64 GraphPaused {
65 step_id: String,
66 reason: String,
67 sync_decision: Box<FanoutSyncDecision>,
68 },
69 #[error("graph step '{step_id}' escalated: {reason}")]
70 GraphEscalated {
71 step_id: String,
72 reason: String,
73 sync_decision: Box<FanoutSyncDecision>,
74 },
75 #[error("checkpoint graph '{checkpoint_graph}' cannot resume graph '{graph}'")]
76 CheckpointGraphMismatch {
77 checkpoint_graph: String,
78 graph: String,
79 },
80 #[error("unsupported adapter '{adapter_type}'")]
81 UnsupportedAdapter { adapter_type: String },
82 #[error("unsupported source kind '{source_kind}'")]
83 UnsupportedSource { source_kind: String },
84 #[error("runner selection '{runner}' is not supported by the native runtime yet")]
85 UnsupportedRunnerSelection { runner: String },
86 #[error("cli-tool source is missing command")]
87 MissingCommand,
88 #[error("sandbox violation: {message}")]
89 SandboxViolation { message: String },
90 #[error("credential delivery failed: {0}")]
91 CredentialDelivery(#[from] CredentialDeliveryError),
92 #[error("effect state failed while {context}: {message}")]
93 EffectState { context: String, message: String },
94 #[error("skill file is missing at {path}")]
95 SkillFileMissing { path: PathBuf },
96 #[error("skill '{skill_name}' failed: {message}")]
97 SkillFailed { skill_name: String, message: String },
98 #[error("receipt validation failed: {message}")]
99 ReceiptInvalid { message: String },
100}
101
102impl RuntimeError {
103 pub(crate) fn io(context: impl Into<String>, source: std::io::Error) -> Self {
104 Self::Io {
105 context: context.into(),
106 source,
107 }
108 }
109
110 pub(crate) fn json(context: impl Into<String>, source: serde_json::Error) -> Self {
111 Self::Json {
112 context: context.into(),
113 source,
114 }
115 }
116
117 pub(crate) fn effect_state(context: impl Into<String>, source: impl std::fmt::Display) -> Self {
118 Self::EffectState {
119 context: context.into(),
120 message: source.to_string(),
121 }
122 }
123}