Skip to main content

a3s_code_core/capability/
projection_error.rs

1use thiserror::Error;
2
3use super::CapabilityKind;
4
5const MAX_ADAPTER_ERROR_BYTES: usize = 1_024;
6
7/// Bounded failure returned by a surface-owned capability projection adapter.
8#[derive(Clone, Debug, Eq, Error, PartialEq)]
9#[error("{message}")]
10pub struct CapabilityAdapterError {
11    message: Box<str>,
12}
13
14impl CapabilityAdapterError {
15    pub fn new(message: impl Into<String>) -> Self {
16        let message = message.into();
17        let message = if message.is_empty() {
18            "capability adapter preparation failed".to_owned()
19        } else {
20            truncate_utf8(message, MAX_ADAPTER_ERROR_BYTES)
21        };
22        Self {
23            message: message.into_boxed_str(),
24        }
25    }
26
27    pub fn message(&self) -> &str {
28        &self.message
29    }
30}
31
32/// Failure that leaves the currently published projection unchanged.
33#[derive(Clone, Debug, Eq, Error, PartialEq)]
34pub enum CapabilityProjectionError {
35    #[error("Capability projection is missing runtime value '{capability}'")]
36    MissingValue { capability: String },
37    #[error("Capability projection contains unknown runtime value '{capability}'")]
38    UnexpectedValue { capability: String },
39    #[error("Capability projection repeats runtime value '{capability}'")]
40    DuplicateValue { capability: String },
41    #[error("Capability kind '{kind}' has no supported A3S Code runtime projection")]
42    UnsupportedKind { kind: CapabilityKind },
43    #[error(
44        "Capability '{capability}' descriptor kind '{descriptor_kind}' does not match runtime value kind '{value_kind}'"
45    )]
46    KindMismatch {
47        capability: String,
48        descriptor_kind: CapabilityKind,
49        value_kind: CapabilityKind,
50    },
51    #[error(
52        "Capability '{capability}' publishes name '{expected}', but its runtime value publishes '{actual}'"
53    )]
54    PublicNameMismatch {
55        capability: String,
56        expected: String,
57        actual: String,
58    },
59    #[error(
60        "Capability '{capability}' descriptor surface digest {expected} does not match its runtime value digest {actual}"
61    )]
62    SurfaceDigestMismatch {
63        capability: String,
64        expected: String,
65        actual: String,
66    },
67    #[error(
68        "UI capability '{capability}' cannot bind {dependency_kind} dependency '{dependency}'"
69    )]
70    UnsupportedUiDependencyKind {
71        capability: String,
72        dependency: String,
73        dependency_kind: CapabilityKind,
74    },
75    #[error("Capability transaction stages unknown target value '{capability}'")]
76    UnknownStagedCapability { capability: String },
77    #[error("Capability transaction stages value '{capability}' more than once")]
78    DuplicateStagedCapability { capability: String },
79    #[error("Capability transaction is missing staged target value '{capability}'")]
80    MissingStagedCapability { capability: String },
81    #[error(
82        "Capability surface dependency graph contains a cycle ({blocked_count} capabilities blocked; first canonical capability '{first_blocked}')"
83    )]
84    DependencyCycle {
85        first_blocked: String,
86        blocked_count: usize,
87    },
88    #[error(
89        "Capability readiness graph references missing dependency '{dependency}' from '{capability}'"
90    )]
91    ReadinessDependencyMissing {
92        capability: String,
93        dependency: String,
94    },
95    #[error("Capability readiness field '{field}' exceeds its bound of {max}")]
96    ReadinessBoundExceeded { field: &'static str, max: usize },
97    #[error("Capability readiness graph violated an internal invariant: {message}")]
98    ReadinessGraphInvariant { message: &'static str },
99    #[error(
100        "Capability readiness plan does not match its target set (expected generation {expected_generation}, found {actual_generation}; digest mismatch: {digest_mismatch})"
101    )]
102    ReadinessPlanMismatch {
103        expected_generation: u64,
104        actual_generation: u64,
105        digest_mismatch: bool,
106    },
107    #[error(
108        "Capability transaction target generation must be {expected}, but the target set is {actual}"
109    )]
110    TargetGenerationMismatch { expected: u64, actual: u64 },
111    #[error(
112        "Capability recovery bootstrap requires the untouched empty generation zero catalog (found generation {actual_generation} with {actual_capabilities} capabilities)"
113    )]
114    BootstrapUnavailable {
115        actual_generation: u64,
116        actual_capabilities: usize,
117    },
118    #[error("Capability recovery bootstrap target generation must be greater than zero (found {actual})")]
119    BootstrapTargetGeneration { actual: u64 },
120    #[error("Capability catalog generation is exhausted")]
121    GenerationExhausted,
122    #[error("Capability adapter for '{capability}' failed to prepare: {message}")]
123    PrepareFailed { capability: String, message: String },
124    #[error("Capability transaction preparation was cancelled")]
125    Cancelled,
126    #[error("Capability transaction exceeds its effect bound of {max}")]
127    EffectBoundExceeded { max: usize },
128    #[error(
129        "Capability commit lost its catalog compare-and-swap race (expected generation {expected_generation} digest {expected_digest}, found generation {actual_generation} digest {actual_digest})"
130    )]
131    CommitConflict {
132        expected_generation: u64,
133        expected_digest: String,
134        actual_generation: u64,
135        actual_digest: String,
136    },
137    #[error("Capability transaction entered an invalid internal typestate")]
138    InvalidTransactionState,
139}
140
141fn truncate_utf8(mut value: String, max: usize) -> String {
142    if value.len() <= max {
143        return value;
144    }
145    let mut boundary = max;
146    while !value.is_char_boundary(boundary) {
147        boundary -= 1;
148    }
149    value.truncate(boundary);
150    value
151}