Skip to main content

auths_sdk/domains/org/
error.rs

1use auths_core::error::AuthsErrorInfo;
2use thiserror::Error;
3
4/// Errors from organization member management workflows.
5///
6/// Usage:
7/// ```ignore
8/// match result {
9///     Err(OrgError::AdminNotFound { .. }) => { /* 403 Forbidden */ }
10///     Err(OrgError::MemberNotFound { .. }) => { /* 404 Not Found */ }
11///     Err(e) => return Err(e.into()),
12///     Ok(att) => { /* proceed */ }
13/// }
14/// ```
15#[derive(Debug, Error)]
16#[non_exhaustive]
17pub enum OrgError {
18    /// No admin matching the given public key was found in the organization.
19    #[error("no admin with the given public key found in organization '{org}'")]
20    AdminNotFound {
21        /// The organization identifier.
22        org: String,
23    },
24
25    /// The specified member was not found in the organization.
26    #[error("member '{did}' not found in organization '{org}'")]
27    MemberNotFound {
28        /// The organization identifier.
29        org: String,
30        /// The DID of the member that was not found.
31        did: String,
32    },
33
34    /// The member has already been revoked.
35    #[error("member '{did}' is already revoked")]
36    AlreadyRevoked {
37        /// The DID of the already-revoked member.
38        did: String,
39    },
40
41    /// The capability string could not be parsed.
42    #[error("invalid capability '{cap}': {reason}")]
43    InvalidCapability {
44        /// The invalid capability string.
45        cap: String,
46        /// The reason parsing failed.
47        reason: String,
48    },
49
50    /// The organization DID is malformed.
51    #[error("invalid organization DID: {0}")]
52    InvalidDid(String),
53
54    /// The hex-encoded public key is invalid.
55    #[error("invalid public key: {0}")]
56    InvalidPublicKey(String),
57
58    /// A signing operation failed while creating or revoking an attestation.
59    #[error("signing error: {0}")]
60    Signing(String),
61
62    /// The identity could not be loaded from storage.
63    #[error("identity error: {0}")]
64    Identity(String),
65
66    /// A key storage operation failed.
67    #[error("key storage error: {0}")]
68    KeyStorage(String),
69
70    /// A storage operation failed.
71    #[error("storage error: {0}")]
72    Storage(#[source] auths_id::storage::registry::backend::RegistryError),
73
74    /// KEL anchoring failed.
75    #[error("anchor error: {0}")]
76    Anchor(#[from] auths_id::keri::AnchorError),
77
78    /// The organization's controller threshold is `kt≥2` (multi-signature).
79    /// KERI-native member delegation currently anchors single-author (`kt=1`)
80    /// interaction events only; multi-sig org anchoring is a tracked follow-up.
81    #[error(
82        "organization '{org}' uses a multi-signature controller (kt≥2); KERI-native member delegation requires a single-signature (kt=1) org"
83    )]
84    OrgThresholdDelegationUnsupported {
85        /// The organization identifier.
86        org: String,
87    },
88
89    /// A key already exists under the requested member alias — minting a member
90    /// there would clobber an existing delegated key. Choose a fresh alias.
91    #[error("a member key already exists under alias '{alias}'")]
92    MemberKeyExists {
93        /// The keychain alias already in use.
94        alias: String,
95    },
96
97    /// The supplied member identity is not a delegated identifier of this org —
98    /// its delegated inception (`dip`) does not name the org as delegator, so the
99    /// org cannot off-board it. Fail closed.
100    #[error("member '{did}' is not a delegated identifier of organization '{org}'")]
101    MemberNotDelegable {
102        /// The member's `did:keri:`.
103        did: String,
104        /// The organization identifier.
105        org: String,
106    },
107
108    /// A cryptographic operation failed (e.g. resolving the org key's curve).
109    #[error("crypto error: {0}")]
110    CryptoError(#[source] auths_core::AgentError),
111
112    /// Authoring or anchoring the member's delegated identifier failed.
113    #[error("member delegation failed: {0}")]
114    Delegation(#[source] auths_id::error::InitError),
115
116    /// An identity already exists where the org would be created — refusing to
117    /// clobber it.
118    #[error("an identity already exists at {location}; refusing to create an organization over it")]
119    IdentityExists {
120        /// Where the existing identity was found (repository path or registry).
121        location: String,
122    },
123
124    /// Initializing the organization's KERI identity failed.
125    #[error("failed to initialize organization identity: {0}")]
126    IdentityInit(#[source] auths_id::error::InitError),
127
128    /// Creating the organization's admin self-attestation failed.
129    #[error("failed to create admin attestation: {0}")]
130    Attestation(#[source] auths_verifier::error::AttestationError),
131
132    /// An air-gapped bundle failed offline verification (tampered event, missing
133    /// member KEL, missing delegation seal, or an invalid off-boarding record).
134    /// The fail-closed detail is the typed [`auths_verifier::org_bundle::OrgBundleError`].
135    #[error(transparent)]
136    Bundle(#[from] auths_verifier::org_bundle::OrgBundleError),
137
138    /// The supplied org policy did not parse or compile (invalid JSON, an invalid
139    /// DID/capability/glob, or it exceeds the policy size/complexity bounds). A
140    /// policy that does not compile is never anchored — fail closed.
141    #[error("invalid org policy: {reason}")]
142    PolicyCompile {
143        /// The compile/parse failure(s).
144        reason: String,
145    },
146
147    /// The supplied OIDC-subject policy did not parse (invalid JSON or empty
148    /// required fields). A policy that does not parse is never anchored — fail
149    /// closed.
150    #[error("invalid OIDC-subject policy: {reason}")]
151    OidcPolicyInvalid {
152        /// The parse failure.
153        reason: String,
154    },
155
156    /// The org KEL anchors a policy hash but its content-addressed blob is missing —
157    /// the policy cannot be loaded, so authority cannot be evaluated. Fail closed.
158    #[error("org policy blob for hash '{hash}' is missing from storage")]
159    PolicyBlobMissing {
160        /// The policy source-hash anchored on the org KEL.
161        hash: String,
162    },
163
164    /// The loaded policy blob does not hash to the value the org KEL committed — the
165    /// blob was tampered with after anchoring. Fail closed.
166    #[error(
167        "org policy integrity failure: KEL committed hash '{expected}' but the stored blob hashes to '{actual}'"
168    )]
169    PolicyIntegrity {
170        /// The hash anchored on the org KEL.
171        expected: String,
172        /// The recomputed hash of the stored blob.
173        actual: String,
174    },
175
176    /// A delegation chain walk followed a `di` link back to an identifier already on
177    /// the chain — a cyclic/malformed delegation. Fail closed (no infinite loop).
178    #[error("delegation chain cycle detected at '{did}'")]
179    ChainCycle {
180        /// The identifier the cycle returned to.
181        did: String,
182    },
183
184    /// A delegation chain exceeds the maximum hop depth the walker will follow.
185    #[error("delegation chain exceeds the maximum depth of {max} hops")]
186    ChainTooDeep {
187        /// The maximum number of hops allowed.
188        max: u32,
189    },
190
191    /// A delegation chain names a delegator/identifier whose KEL is absent from
192    /// storage — the chain cannot be reconstructed. Fail closed.
193    #[error("delegation chain is broken: no KEL found for '{did}'")]
194    ChainBrokenHop {
195        /// The identifier whose KEL is missing.
196        did: String,
197    },
198}
199
200impl AuthsErrorInfo for OrgError {
201    fn error_code(&self) -> &'static str {
202        match self {
203            Self::AdminNotFound { .. } => "AUTHS-E5601",
204            Self::MemberNotFound { .. } => "AUTHS-E5602",
205            Self::AlreadyRevoked { .. } => "AUTHS-E5603",
206            Self::InvalidCapability { .. } => "AUTHS-E5604",
207            Self::InvalidDid(_) => "AUTHS-E5605",
208            Self::InvalidPublicKey(_) => "AUTHS-E5606",
209            Self::Signing(_) => "AUTHS-E5607",
210            Self::Identity(_) => "AUTHS-E5608",
211            Self::KeyStorage(_) => "AUTHS-E5609",
212            Self::Storage(_) => "AUTHS-E5610",
213            Self::Anchor(_) => "AUTHS-E5611",
214            Self::OrgThresholdDelegationUnsupported { .. } => "AUTHS-E5612",
215            Self::MemberKeyExists { .. } => "AUTHS-E5613",
216            Self::MemberNotDelegable { .. } => "AUTHS-E5618",
217            Self::CryptoError(e) => e.error_code(),
218            Self::Delegation(_) => "AUTHS-E5614",
219            Self::IdentityExists { .. } => "AUTHS-E5615",
220            Self::IdentityInit(_) => "AUTHS-E5616",
221            Self::Attestation(_) => "AUTHS-E5617",
222            Self::Bundle(e) => e.error_code(),
223            Self::PolicyCompile { .. } => "AUTHS-E5622",
224            Self::OidcPolicyInvalid { .. } => "AUTHS-E5628",
225            Self::PolicyBlobMissing { .. } => "AUTHS-E5623",
226            Self::PolicyIntegrity { .. } => "AUTHS-E5624",
227            Self::ChainCycle { .. } => "AUTHS-E5625",
228            Self::ChainTooDeep { .. } => "AUTHS-E5626",
229            Self::ChainBrokenHop { .. } => "AUTHS-E5627",
230        }
231    }
232
233    fn suggestion(&self) -> Option<&'static str> {
234        match self {
235            Self::AdminNotFound { .. } => {
236                Some("Verify you are using the correct admin key for this organization")
237            }
238            Self::MemberNotFound { .. } => {
239                Some("Run `auths org list-members` to see current members")
240            }
241            Self::AlreadyRevoked { .. } => {
242                Some("This member has already been revoked from the organization")
243            }
244            Self::InvalidCapability { .. } => {
245                Some("Use a valid capability (e.g., 'sign_commit', 'manage_members', 'admin')")
246            }
247            Self::InvalidDid(_) => Some("Organization DIDs must be valid did:keri identifiers"),
248            Self::InvalidPublicKey(_) => Some("Public keys must be hex-encoded Ed25519 keys"),
249            Self::Signing(_) => {
250                Some("The signing operation failed; check your key access with `auths key list`")
251            }
252            Self::Identity(_) => {
253                Some("Failed to load identity; run `auths id show` to check identity status")
254            }
255            Self::KeyStorage(_) => {
256                Some("Failed to access key storage; run `auths doctor` to diagnose")
257            }
258            Self::Storage(_) => {
259                Some("Failed to access organization storage; check repository permissions")
260            }
261            Self::Anchor(_) => Some("KEL anchoring failed; check identity and registry state"),
262            Self::OrgThresholdDelegationUnsupported { .. } => Some(
263                "Multi-signature org anchoring is not yet supported; use a single-signature (kt=1) org",
264            ),
265            Self::MemberKeyExists { .. } => Some(
266                "Choose a different member alias; run `auths org list-members` to see existing members",
267            ),
268            Self::MemberNotDelegable { .. } => Some(
269                "The member must first incept a delegated identity naming this org as delegator (pairing) before it can be off-boarded",
270            ),
271            Self::CryptoError(e) => e.suggestion(),
272            Self::Delegation(_) => Some(
273                "The member delegation could not be authored or anchored; check the org identity",
274            ),
275            Self::IdentityExists { .. } => Some(
276                "An identity already exists here; use a fresh repository path to create a new organization",
277            ),
278            Self::IdentityInit(_) => {
279                Some("Failed to initialize the org identity; check key access and repository state")
280            }
281            Self::Attestation(_) => Some(
282                "Failed to sign the admin attestation; check your key access with `auths key list`",
283            ),
284            Self::Bundle(e) => e.suggestion(),
285            Self::PolicyCompile { .. } => Some(
286                "Fix the policy JSON (a serialized `Expr`); see `auths org policy show` for the current policy",
287            ),
288            Self::OidcPolicyInvalid { .. } => Some(
289                "Fix the OIDC-subject policy JSON (issuer + repository, optional workflow_ref); nothing was anchored",
290            ),
291            Self::PolicyBlobMissing { .. } => {
292                Some("The policy blob is missing; re-anchor it with `auths org policy set`")
293            }
294            Self::PolicyIntegrity { .. } => Some(
295                "The stored policy was modified after anchoring; re-anchor a trusted policy with `auths org policy set`",
296            ),
297            Self::ChainCycle { .. } => {
298                Some("The delegation chain is malformed (a cycle); inspect the identifiers' KELs")
299            }
300            Self::ChainTooDeep { .. } => {
301                Some("The delegation chain is too deep; reduce delegation nesting")
302            }
303            Self::ChainBrokenHop { .. } => Some(
304                "A KEL in the delegation chain is missing; ensure all delegators' KELs are present",
305            ),
306        }
307    }
308}