use std::collections::HashSet;
use std::ops::ControlFlow;
use auths_id::keri::types::Prefix;
use serde::Serialize;
use crate::context::AuthsContext;
use crate::domains::org::audit::{AuthorityAtSigning, classify_authority_at_signing};
use crate::domains::org::delegation::resolve_member_authority;
use crate::domains::org::error::OrgError;
pub const MAX_CHAIN_DEPTH: u32 = 16;
#[derive(Debug, Clone, Serialize)]
pub struct ChainHop {
pub child_did: String,
pub delegator_did: String,
pub role: Option<String>,
pub capabilities: Vec<String>,
pub authority_at_signing: AuthorityAtSigning,
}
#[derive(Debug, Clone, Serialize)]
pub struct DelegationChain {
pub leaf_did: String,
pub root_did: String,
pub hops: Vec<ChainHop>,
pub depth: u32,
pub live_at_signing: bool,
}
fn did(prefix: &Prefix) -> String {
format!("did:keri:{}", prefix.as_str())
}
fn immediate_delegator(ctx: &AuthsContext, prefix: &Prefix) -> Result<Option<Prefix>, OrgError> {
let mut delegator: Option<Prefix> = None;
let mut found = false;
ctx.registry
.visit_events(prefix, 0, &mut |event| {
delegator = event.delegator().cloned();
found = true;
ControlFlow::Break(())
})
.map_err(OrgError::Storage)?;
if !found {
return Err(OrgError::ChainBrokenHop { did: did(prefix) });
}
Ok(delegator)
}
pub fn walk_delegation_chain(
ctx: &AuthsContext,
leaf_prefix: &Prefix,
signed_at: Option<u128>,
) -> Result<DelegationChain, OrgError> {
let leaf_did = did(leaf_prefix);
let mut hops: Vec<ChainHop> = Vec::new();
let mut seen: HashSet<String> = HashSet::new();
let mut current = leaf_prefix.clone();
loop {
if !seen.insert(current.as_str().to_string()) {
return Err(OrgError::ChainCycle { did: did(¤t) });
}
let Some(parent) = immediate_delegator(ctx, ¤t)? else {
break; };
if hops.len() as u32 >= MAX_CHAIN_DEPTH {
return Err(OrgError::ChainTooDeep {
max: MAX_CHAIN_DEPTH,
});
}
let position = if hops.is_empty() { signed_at } else { None };
let authority_at_signing = classify_authority_at_signing(ctx, &parent, ¤t, position)?;
let (role, capabilities) = match resolve_member_authority(ctx, &parent, ¤t)? {
Some(authority) => (
authority.role.map(|r| r.as_str().to_string()),
authority.capabilities,
),
None => (None, Vec::new()),
};
hops.push(ChainHop {
child_did: did(¤t),
delegator_did: did(&parent),
role,
capabilities,
authority_at_signing,
});
current = parent;
}
let live_at_signing = hops.iter().all(|h| {
matches!(
h.authority_at_signing,
AuthorityAtSigning::AuthorizedBeforeRevocation
)
});
Ok(DelegationChain {
leaf_did,
root_did: did(¤t),
depth: hops.len() as u32,
hops,
live_at_signing,
})
}