Skip to main content

lenso_app_plan/
error.rs

1use std::{fmt, time::Duration};
2
3use super::CapabilityOperationKind;
4
5/// A reason App Composition could not be materialized into a Plan.
6#[derive(Clone, Debug, Eq, PartialEq)]
7pub enum PlanResolutionError {
8    /// This Kernel does not yet execute the selected terminal policy.
9    UnsupportedTerminalPolicy,
10    /// Host-essential roots or their materialized closure are invalid.
11    InvalidTerminalPolicy { detail: String },
12    /// Contract version or selected runtime profile is invalid.
13    InvalidAuthoring { instance_key: String },
14    /// A requirement identity is invalid for the selected authoring version.
15    InvalidRequirementId {
16        consumer_instance: String,
17        requirement_id: String,
18    },
19    /// Two declarations share one consumer-local identity.
20    DuplicateRequirementId {
21        consumer_instance: String,
22        requirement_id: String,
23    },
24    /// The Plan schema cannot be executed by this Kernel version.
25    UnsupportedSchemaVersion { expected: u32, actual: u32 },
26    /// Two Plugin Instances use the same App-local key.
27    DuplicatePluginInstance { instance_key: String },
28    /// Every App must declare at least one Execution Lane.
29    MissingExecutionLane,
30    /// An Execution Lane identity is empty or whitespace-only.
31    InvalidExecutionLane { execution_lane: String },
32    /// Two Execution Lanes use the same App-local identity.
33    DuplicateExecutionLane { execution_lane: String },
34    /// A Plugin Instance names an Execution Lane absent from the Plan.
35    UndeclaredExecutionLane {
36        instance_key: String,
37        execution_lane: String,
38    },
39    /// A Plugin Instance has no executable entrypoint identity.
40    InvalidPluginEntrypoint { instance_key: String },
41    /// A Plugin declares the same provided Capability more than once.
42    DuplicateProvidedCapability {
43        provider_instance: String,
44        capability_id: String,
45    },
46    /// One endpoint declares an Operation more than once.
47    DuplicateOperation {
48        provider_instance: String,
49        capability_id: String,
50        operation: String,
51    },
52    /// A Plugin declares the same required Capability more than once.
53    DuplicateRequiredCapability {
54        consumer_instance: String,
55        capability_id: String,
56    },
57    /// A binding names a consumer that is not in the Composition.
58    InvalidConsumerReference {
59        consumer_instance: String,
60        capability_id: String,
61    },
62    /// A binding names a provider that is not in the Composition or does not provide the Capability.
63    InvalidProviderReference {
64        consumer_instance: String,
65        capability_id: String,
66        provider_instance: String,
67    },
68    /// A binding exists without a matching consumer requirement.
69    UndeclaredCapabilityRequirement {
70        consumer_instance: String,
71        capability_id: String,
72    },
73    /// The provider and consumer selected different exact Descriptor versions.
74    IncompatibleCapabilityVersion {
75        consumer_instance: String,
76        capability_id: String,
77        required: String,
78        provided: String,
79        provider_instance: String,
80    },
81    /// A binding crosses Execution Lanes but its generated contract types are not transferable.
82    CrossLaneTransferUnsupported {
83        consumer_instance: String,
84        provider_instance: String,
85        capability_id: String,
86    },
87    /// A host Runtime does not implement cross-lane transfer for the selected interaction kind.
88    ///
89    /// Retained so downstream code can classify failures produced by older Runtime versions.
90    CrossLaneInteractionUnsupported {
91        capability_id: String,
92        operation: String,
93        interaction: CapabilityOperationKind,
94    },
95    /// A `one` requirement has no explicit provider.
96    MissingOneBinding {
97        consumer_instance: String,
98        capability_id: String,
99    },
100    /// A `one` requirement has more than one explicit provider.
101    AmbiguousOneBinding {
102        consumer_instance: String,
103        capability_id: String,
104        providers: usize,
105    },
106    /// An `optional` requirement has more than one explicit provider.
107    AmbiguousOptionalBinding {
108        consumer_instance: String,
109        capability_id: String,
110        providers: usize,
111    },
112    /// A provider is repeated for the same requirement.
113    DuplicateBinding {
114        consumer_instance: String,
115        capability_id: String,
116        provider_instance: String,
117    },
118    /// A request Operation has an invalid bounded admission policy.
119    InvalidRequestAdmission {
120        capability_id: String,
121        operation: String,
122        queue_capacity: usize,
123        max_concurrency: usize,
124    },
125    /// Admission was configured for an Operation absent from the endpoint.
126    UnknownAdmissionOperation {
127        capability_id: String,
128        operation: String,
129    },
130    /// Interaction metadata was configured for an absent Operation.
131    UnknownOperationInteraction {
132        capability_id: String,
133        operation: String,
134    },
135    /// A Plugin Instance selected an unusable finite restart policy.
136    InvalidRestartPolicy {
137        instance_key: String,
138        max_attempts: usize,
139        window: Duration,
140    },
141    /// Explicit Capability activation dependencies contain a cycle.
142    ActivationCycle { instances: Vec<String> },
143}
144
145impl fmt::Display for PlanResolutionError {
146    #[allow(clippy::too_many_lines)]
147    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
148        match self {
149            Self::UnsupportedTerminalPolicy => {
150                formatter.write_str("unsupported host_essential terminal policy")
151            }
152            Self::InvalidTerminalPolicy { detail } => {
153                write!(formatter, "invalid terminal policy: {detail}")
154            }
155            Self::InvalidAuthoring { instance_key } => write!(
156                formatter,
157                "invalid authoring version or runtime profile for `{instance_key}`"
158            ),
159            Self::InvalidRequirementId {
160                consumer_instance,
161                requirement_id,
162            } => write!(
163                formatter,
164                "invalid requirement `{requirement_id}` for `{consumer_instance}`"
165            ),
166            Self::DuplicateRequirementId {
167                consumer_instance,
168                requirement_id,
169            } => write!(
170                formatter,
171                "duplicate requirement `{requirement_id}` for `{consumer_instance}`"
172            ),
173            Self::UnsupportedSchemaVersion { expected, actual } => write!(
174                formatter,
175                "unsupported Plan schema version {actual}; expected {expected}"
176            ),
177            Self::DuplicatePluginInstance { instance_key } => {
178                write!(formatter, "duplicate Plugin Instance `{instance_key}`")
179            }
180            Self::MissingExecutionLane => {
181                formatter.write_str("Resolved App Plan declares no Execution Lanes")
182            }
183            Self::InvalidExecutionLane { execution_lane } => {
184                write!(formatter, "invalid Execution Lane `{execution_lane}`")
185            }
186            Self::DuplicateExecutionLane { execution_lane } => {
187                write!(formatter, "duplicate Execution Lane `{execution_lane}`")
188            }
189            Self::UndeclaredExecutionLane {
190                instance_key,
191                execution_lane,
192            } => write!(
193                formatter,
194                "Plugin Instance `{instance_key}` is placed on undeclared Execution Lane `{execution_lane}`"
195            ),
196            Self::InvalidPluginEntrypoint { instance_key } => write!(
197                formatter,
198                "Plugin Instance `{instance_key}` has an empty entrypoint"
199            ),
200            Self::DuplicateProvidedCapability {
201                provider_instance,
202                capability_id,
203            } => write!(
204                formatter,
205                "Plugin Instance `{provider_instance}` provides Capability `{capability_id}` more than once"
206            ),
207            Self::DuplicateOperation {
208                provider_instance,
209                capability_id,
210                operation,
211            } => write!(
212                formatter,
213                "Plugin Instance `{provider_instance}` Capability `{capability_id}` declares Operation `{operation}` more than once"
214            ),
215            Self::DuplicateRequiredCapability {
216                consumer_instance,
217                capability_id,
218            } => write!(
219                formatter,
220                "Plugin Instance `{consumer_instance}` requires Capability `{capability_id}` more than once"
221            ),
222            Self::InvalidConsumerReference {
223                consumer_instance,
224                capability_id,
225            } => write!(
226                formatter,
227                "Capability `{capability_id}` names missing consumer `{consumer_instance}`"
228            ),
229            Self::InvalidProviderReference {
230                consumer_instance,
231                capability_id,
232                provider_instance,
233            } => write!(
234                formatter,
235                "consumer `{consumer_instance}` names invalid provider `{provider_instance}` for Capability `{capability_id}`"
236            ),
237            Self::UndeclaredCapabilityRequirement {
238                consumer_instance,
239                capability_id,
240            } => write!(
241                formatter,
242                "consumer `{consumer_instance}` has no declared requirement for Capability `{capability_id}`"
243            ),
244            Self::IncompatibleCapabilityVersion {
245                consumer_instance,
246                capability_id,
247                required,
248                provided,
249                provider_instance,
250            } => write!(
251                formatter,
252                "consumer `{consumer_instance}` requires Capability `{capability_id}` version `{required}`, but provider `{provider_instance}` provides `{provided}`"
253            ),
254            Self::CrossLaneTransferUnsupported {
255                consumer_instance,
256                provider_instance,
257                capability_id,
258            } => write!(
259                formatter,
260                "consumer `{consumer_instance}` binds Capability `{capability_id}` across Execution Lanes to provider `{provider_instance}`, but its contract types do not support cross-lane transfer"
261            ),
262            Self::CrossLaneInteractionUnsupported {
263                capability_id,
264                operation,
265                interaction,
266            } => write!(
267                formatter,
268                "Capability `{capability_id}` Operation `{operation}` uses {interaction:?}, which this Plan version cannot transfer across Execution Lanes"
269            ),
270            Self::MissingOneBinding {
271                consumer_instance,
272                capability_id,
273            } => write!(
274                formatter,
275                "consumer `{consumer_instance}` is missing one binding for Capability `{capability_id}`"
276            ),
277            Self::AmbiguousOneBinding {
278                consumer_instance,
279                capability_id,
280                providers,
281            } => write!(
282                formatter,
283                "consumer `{consumer_instance}` has {providers} bindings for one Capability `{capability_id}`"
284            ),
285            Self::AmbiguousOptionalBinding {
286                consumer_instance,
287                capability_id,
288                providers,
289            } => write!(
290                formatter,
291                "consumer `{consumer_instance}` has {providers} bindings for optional Capability `{capability_id}`"
292            ),
293            Self::DuplicateBinding {
294                consumer_instance,
295                capability_id,
296                provider_instance,
297            } => write!(
298                formatter,
299                "consumer `{consumer_instance}` binds Capability `{capability_id}` to provider `{provider_instance}` more than once"
300            ),
301            Self::InvalidRequestAdmission {
302                capability_id,
303                operation,
304                queue_capacity,
305                max_concurrency,
306            } => write!(
307                formatter,
308                "Capability `{capability_id}` Operation `{operation}` has invalid request admission (queue capacity {queue_capacity}, concurrency {max_concurrency})"
309            ),
310            Self::UnknownAdmissionOperation {
311                capability_id,
312                operation,
313            } => write!(
314                formatter,
315                "Capability `{capability_id}` configures request admission for unknown Operation `{operation}`"
316            ),
317            Self::UnknownOperationInteraction {
318                capability_id,
319                operation,
320            } => write!(
321                formatter,
322                "Capability `{capability_id}` configures interaction metadata for unknown Operation `{operation}`"
323            ),
324            Self::InvalidRestartPolicy {
325                instance_key,
326                max_attempts,
327                window,
328            } => write!(
329                formatter,
330                "Plugin Instance `{instance_key}` has invalid restart policy (attempts {max_attempts}, window {window:?})"
331            ),
332            Self::ActivationCycle { instances } => write!(
333                formatter,
334                "required Capability activation cycle: {}",
335                instances.join(" -> ")
336            ),
337        }
338    }
339}
340
341impl std::error::Error for PlanResolutionError {}