chio_weights/error.rs
1//! Typed model-card surface errors.
2//!
3//! Each variant is bound to a stable `urn:chio:error:weights:*` URN from the
4//! workspace error-code registry. Every `Err(_)` returned from this crate carries one of these
5//! variants; there is no untyped `String` error path. Variants are
6//! non-exhaustive so callers cannot pattern-match past a future variant and
7//! silently accept.
8
9/// Error variants emitted by the model-card surface.
10///
11/// Variants align with `urn:chio:error:weights:*` codes registered in
12/// `spec/errors/registry.yaml`.
13#[derive(Debug, thiserror::Error)]
14#[non_exhaustive]
15pub enum WeightsError {
16 /// Canonical-JSON encoding or decoding failed. Fatal: do not retry until
17 /// the encoding bug is resolved.
18 #[error("model card canonical-json encode/decode failed: {0}")]
19 Encoding(String),
20
21 /// Required field is missing from the model card structure.
22 #[error("model card missing required field: {0}")]
23 MissingField(&'static str),
24
25 /// Schema-level invariant rejected (e.g. weights_hash not 64 hex chars).
26 #[error("model card schema rejected: {0}")]
27 SchemaRejected(String),
28
29 /// Card expired before the verifier evaluated it.
30 #[error("model card expired at {expires_at}, now is {now}")]
31 Expired {
32 /// Card's `expires_at` field.
33 expires_at: chrono::DateTime<chrono::Utc>,
34 /// Verifier-side now.
35 now: chrono::DateTime<chrono::Utc>,
36 },
37
38 /// Cosign bundle verify path rejected the bundle. Wraps the upstream
39 /// `chio-attest-verify` error so receipt consumers can correlate.
40 #[error("model card cosign bundle rejected: {0}")]
41 BundleRejected(String),
42
43 /// Loaded `weights_hash` does not match the card's `weights_hash`.
44 /// Maps to `urn:chio:error:weights:card-mismatch`.
45 #[error("weights_hash mismatch: card declares {expected}, runtime loaded {found}")]
46 CardMismatch {
47 /// Card's declared `weights_hash` (lowercase hex sha-256).
48 expected: String,
49 /// Runtime-loaded hash (lowercase hex sha-256).
50 found: String,
51 },
52
53 /// Requested capability scope is not in the card's
54 /// `allowed_capability_set`. Maps to
55 /// `urn:chio:error:weights:scope-not-subset`.
56 #[error("requested capability scope {scope:?} is not in card's allowed_capability_set")]
57 ScopeNotSubset {
58 /// First scope that violated subset containment.
59 scope: String,
60 },
61
62 /// Requested tool is in the card's `banned_tools`. Maps to
63 /// `urn:chio:error:weights:tool-banned`.
64 #[error("requested tool {tool:?} is in card's banned_tools")]
65 ToolBanned {
66 /// Tool identifier that intersected `banned_tools`.
67 tool: String,
68 },
69}
70
71impl WeightsError {
72 /// Stable URN for this error. Consumed by the LSP-driven typed-enum
73 /// codegen and audit log surfaces.
74 #[must_use]
75 pub fn urn(&self) -> &'static str {
76 match self {
77 Self::Encoding(_) => "urn:chio:error:weights:internal-encoding",
78 Self::MissingField(_) | Self::SchemaRejected(_) => {
79 "urn:chio:error:weights:schema-rejected"
80 }
81 Self::Expired { .. } => "urn:chio:error:weights:card-expired",
82 Self::BundleRejected(_) => "urn:chio:error:weights:bundle-rejected",
83 Self::CardMismatch { .. } => "urn:chio:error:weights:card-mismatch",
84 Self::ScopeNotSubset { .. } => "urn:chio:error:weights:scope-not-subset",
85 Self::ToolBanned { .. } => "urn:chio:error:weights:tool-banned",
86 }
87 }
88}