Skip to main content

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}