a3s_code_core/capability/
projection_error.rs1use thiserror::Error;
2
3use super::CapabilityKind;
4
5const MAX_ADAPTER_ERROR_BYTES: usize = 1_024;
6
7#[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#[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}