Skip to main content

a3s_code_core/release/
error.rs

1use a3s_acl::{CanonicalError, ParseError, SchemaDiagnosticCode};
2use std::fmt;
3
4/// Typed Agent release field used by value-redacting admission errors.
5#[non_exhaustive]
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum AgentReleaseField {
8    Contract,
9    Protocol,
10    ArtifactDigest,
11    ArtifactMediaType,
12    EntrypointCommand,
13    EntrypointArgument,
14    HealthTransport,
15    HealthPort,
16    ReadinessPath,
17    LivenessPath,
18    ShutdownGraceSeconds,
19    WorkspaceMode,
20    CacheMode,
21    PersistentDataMode,
22    CapabilityName,
23    CapabilityLevel,
24    SecretName,
25    SecretTarget,
26    SecretDestination,
27    ProvenanceKind,
28    ProvenanceUri,
29    ProvenanceDigest,
30}
31
32impl fmt::Display for AgentReleaseField {
33    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
34        formatter.write_str(match self {
35            Self::Contract => "agent_release.schema",
36            Self::Protocol => "agent_release.protocol",
37            Self::ArtifactDigest => "agent_release.artifact.digest",
38            Self::ArtifactMediaType => "agent_release.artifact.media_type",
39            Self::EntrypointCommand => "agent_release.entrypoint.command",
40            Self::EntrypointArgument => "agent_release.entrypoint.args",
41            Self::HealthTransport => "agent_release.health.transport",
42            Self::HealthPort => "agent_release.health.port",
43            Self::ReadinessPath => "agent_release.health.readiness_path",
44            Self::LivenessPath => "agent_release.health.liveness_path",
45            Self::ShutdownGraceSeconds => "agent_release.health.shutdown_grace_seconds",
46            Self::WorkspaceMode => "agent_release.storage.workspace",
47            Self::CacheMode => "agent_release.storage.cache",
48            Self::PersistentDataMode => "agent_release.storage.persistent_data",
49            Self::CapabilityName => "agent_release.capability.name",
50            Self::CapabilityLevel => "agent_release.capability.level",
51            Self::SecretName => "agent_release.secret.name",
52            Self::SecretTarget => "agent_release.secret.target",
53            Self::SecretDestination => "agent_release.secret.destination",
54            Self::ProvenanceKind => "agent_release.provenance.kind",
55            Self::ProvenanceUri => "agent_release.provenance.uri",
56            Self::ProvenanceDigest => "agent_release.provenance.digest",
57        })
58    }
59}
60
61/// Stable, value-redacting failure returned by Agent release admission.
62#[non_exhaustive]
63#[derive(Debug)]
64pub enum AgentReleaseError {
65    Io(std::io::Error),
66    InputTooLarge,
67    InvalidEncoding,
68    Parse(ParseError),
69    Schema {
70        diagnostic: SchemaDiagnosticCode,
71        truncated: bool,
72    },
73    SchemaBudgetExceeded,
74    Canonical(CanonicalError),
75    CanonicalEncoding,
76    UnsupportedContract,
77    InvalidField(AgentReleaseField),
78    DuplicateCapability,
79    DuplicateSecret,
80    DuplicateProvenance,
81    IncompatibleProtocol,
82    UnsupportedCapability {
83        required_index: usize,
84    },
85}
86
87impl AgentReleaseError {
88    /// Stable machine-readable error code.
89    pub const fn code(&self) -> &'static str {
90        match self {
91            Self::Io(_) => "a3s.code.agent_release.io",
92            Self::InputTooLarge | Self::InvalidEncoding | Self::Parse(_) => {
93                "a3s.code.agent_release.parse"
94            }
95            Self::Schema { .. } | Self::SchemaBudgetExceeded => "a3s.code.agent_release.schema",
96            Self::Canonical(_) | Self::CanonicalEncoding => "a3s.code.agent_release.canonical",
97            Self::UnsupportedContract => "a3s.code.agent_release.unsupported_contract",
98            Self::InvalidField(_) => "a3s.code.agent_release.invalid_field",
99            Self::DuplicateCapability => "a3s.code.agent_release.duplicate_capability",
100            Self::DuplicateSecret => "a3s.code.agent_release.duplicate_secret",
101            Self::DuplicateProvenance => "a3s.code.agent_release.duplicate_provenance",
102            Self::IncompatibleProtocol => "a3s.code.agent_release.incompatible_protocol",
103            Self::UnsupportedCapability { .. } => "a3s.code.agent_release.unsupported_capability",
104        }
105    }
106
107    /// Invalid semantic field, when this is an `invalid_field` error.
108    pub const fn field(&self) -> Option<AgentReleaseField> {
109        match self {
110            Self::InvalidField(field) => Some(*field),
111            _ => None,
112        }
113    }
114}
115
116impl fmt::Display for AgentReleaseError {
117    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
118        match self {
119            Self::Io(error) => write!(formatter, "could not read Agent release manifest: {error}"),
120            Self::InputTooLarge => {
121                formatter.write_str("Agent release manifest exceeds its input budget")
122            }
123            Self::InvalidEncoding => {
124                formatter.write_str("Agent release manifest must be valid UTF-8")
125            }
126            Self::Parse(error) => write!(formatter, "Agent release manifest parse failed: {error}"),
127            Self::Schema {
128                diagnostic,
129                truncated,
130            } => {
131                write!(
132                    formatter,
133                    "Agent release manifest schema rejected the document ({diagnostic})"
134                )?;
135                if *truncated {
136                    formatter.write_str("; additional diagnostics were truncated")?;
137                }
138                Ok(())
139            }
140            Self::SchemaBudgetExceeded => {
141                formatter.write_str("Agent release schema diagnostics exceeded their budget")
142            }
143            Self::Canonical(error) => {
144                write!(formatter, "Agent release canonicalization failed: {error}")
145            }
146            Self::CanonicalEncoding => {
147                formatter.write_str("Agent release canonical bytes are not UTF-8")
148            }
149            Self::UnsupportedContract => {
150                formatter.write_str("Agent release contract version is not supported")
151            }
152            Self::InvalidField(field) => {
153                write!(formatter, "Agent release field {field} is invalid")
154            }
155            Self::DuplicateCapability => {
156                formatter.write_str("Agent release capability requirements must be unique")
157            }
158            Self::DuplicateSecret => {
159                formatter.write_str("Agent release secret requirements must be unique")
160            }
161            Self::DuplicateProvenance => {
162                formatter.write_str("Agent release provenance kinds must be unique")
163            }
164            Self::IncompatibleProtocol => {
165                formatter.write_str("Agent release protocol is not supported by the runtime")
166            }
167            Self::UnsupportedCapability { required_index } => write!(
168                formatter,
169                "Agent release capability requirement at index {required_index} is unavailable"
170            ),
171        }
172    }
173}
174
175impl std::error::Error for AgentReleaseError {
176    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
177        match self {
178            Self::Io(error) => Some(error),
179            Self::Parse(error) => Some(error),
180            Self::Canonical(error) => Some(error),
181            _ => None,
182        }
183    }
184}
185
186impl From<std::io::Error> for AgentReleaseError {
187    fn from(error: std::io::Error) -> Self {
188        Self::Io(error)
189    }
190}
191
192impl From<ParseError> for AgentReleaseError {
193    fn from(error: ParseError) -> Self {
194        Self::Parse(error)
195    }
196}
197
198impl From<CanonicalError> for AgentReleaseError {
199    fn from(error: CanonicalError) -> Self {
200        Self::Canonical(error)
201    }
202}