use meerkat_core::SessionId;
use super::types::{
ForkedParticipantAttachmentId, ForkedParticipantForkProtection, ForkedParticipantOwnerRoute,
ForkedParticipantRef, bridge_ref,
};
use crate::ids::AgentIdentity;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ForkedParticipantResumeProof {
Absent,
LocalAttachedSpawn {
member: AgentIdentity,
session: SessionId,
association: Option<LocalAssociationEvidence>,
},
HostCapabilityAttachment {
full_ref: ForkedParticipantRef,
attachment_id: String,
owner_route: ForkedParticipantOwnerRoute,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LocalAssociationEvidence {
pub member: AgentIdentity,
pub capability: ForkedParticipantRef,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ForkedParticipantResumeAdmission {
Unprotected,
LocalCustody,
LocalAttachedSpawnInFlight,
HostCapability,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ForkedParticipantResumeRejection {
AuthorityRequired {
capability_hint: String,
},
ReservedNotActivated { capability_hint: String },
ReferenceMismatch { capability_hint: String },
ForeignRoute { capability_hint: String },
MemberMismatch { capability_hint: String },
SessionMismatch { capability_hint: String },
MalformedAttachmentId { capability_hint: String },
}
impl ForkedParticipantResumeRejection {
#[must_use]
pub fn capability_hint(&self) -> &str {
match self {
Self::AuthorityRequired { capability_hint }
| Self::ReservedNotActivated { capability_hint }
| Self::ReferenceMismatch { capability_hint }
| Self::ForeignRoute { capability_hint }
| Self::MemberMismatch { capability_hint }
| Self::SessionMismatch { capability_hint }
| Self::MalformedAttachmentId { capability_hint } => capability_hint,
}
}
}
impl std::fmt::Display for ForkedParticipantResumeRejection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let hint = self.capability_hint();
match self {
Self::AuthorityRequired { .. } => write!(
f,
"the session is capability-protected ({hint}); the authenticated forked \
participant authority is required"
),
Self::ReservedNotActivated { .. } => write!(
f,
"the session is capability-protected ({hint}) but the capability has no \
activated reference to authenticate against"
),
Self::ReferenceMismatch { .. } => write!(
f,
"the presented forked participant reference is not the one its owner recorded \
({hint})"
),
Self::ForeignRoute { .. } => {
write!(f, "the capability is owned by another route ({hint})")
}
Self::MemberMismatch { .. } => write!(
f,
"the durable association names a different member than this seat ({hint})"
),
Self::SessionMismatch { .. } => write!(
f,
"the durable association names a different fork session than this seat ({hint})"
),
Self::MalformedAttachmentId { .. } => {
write!(f, "the presented attachment id is malformed ({hint})")
}
}
}
}
pub fn adjudicate_protected_resume(
protection: Option<&ForkedParticipantForkProtection>,
proof: &ForkedParticipantResumeProof,
) -> Result<ForkedParticipantResumeAdmission, ForkedParticipantResumeRejection> {
let Some(protection) = protection else {
return Ok(ForkedParticipantResumeAdmission::Unprotected);
};
let hint = || protection.capability_hint.clone();
let ForkedParticipantResumeProof::Absent = proof else {
let Some(recorded) = protection.capability.as_ref() else {
return Err(ForkedParticipantResumeRejection::ReservedNotActivated {
capability_hint: hint(),
});
};
return match proof {
ForkedParticipantResumeProof::Absent => unreachable!("guarded by the outer let-else"),
ForkedParticipantResumeProof::LocalAttachedSpawn {
member,
session,
association,
} => adjudicate_local(protection, recorded, member, session, association.as_ref()),
ForkedParticipantResumeProof::HostCapabilityAttachment {
full_ref,
attachment_id,
owner_route,
} => adjudicate_host(protection, recorded, full_ref, attachment_id, owner_route),
};
};
Err(ForkedParticipantResumeRejection::AuthorityRequired {
capability_hint: hint(),
})
}
fn adjudicate_local(
protection: &ForkedParticipantForkProtection,
recorded: &ForkedParticipantRef,
member: &AgentIdentity,
session: &SessionId,
association: Option<&LocalAssociationEvidence>,
) -> Result<ForkedParticipantResumeAdmission, ForkedParticipantResumeRejection> {
let hint = || protection.capability_hint.clone();
if !matches!(
protection.owner_route,
ForkedParticipantOwnerRoute::Local { .. }
) {
return Err(ForkedParticipantResumeRejection::ForeignRoute {
capability_hint: hint(),
});
}
let Some(association) = association else {
return Ok(ForkedParticipantResumeAdmission::LocalAttachedSpawnInFlight);
};
if association.member != *member {
return Err(ForkedParticipantResumeRejection::MemberMismatch {
capability_hint: hint(),
});
}
if association.capability.fork_session_id() != session {
return Err(ForkedParticipantResumeRejection::SessionMismatch {
capability_hint: hint(),
});
}
if association.capability != *recorded {
return Err(ForkedParticipantResumeRejection::ReferenceMismatch {
capability_hint: hint(),
});
}
Ok(ForkedParticipantResumeAdmission::LocalCustody)
}
fn adjudicate_host(
protection: &ForkedParticipantForkProtection,
recorded: &ForkedParticipantRef,
presented: &ForkedParticipantRef,
attachment_id: &str,
owner_route: &ForkedParticipantOwnerRoute,
) -> Result<ForkedParticipantResumeAdmission, ForkedParticipantResumeRejection> {
let hint = || protection.capability_hint.clone();
if bridge_ref(recorded) != bridge_ref(presented) {
return Err(ForkedParticipantResumeRejection::ReferenceMismatch {
capability_hint: hint(),
});
}
if recorded.owner_route() != owner_route {
return Err(ForkedParticipantResumeRejection::ForeignRoute {
capability_hint: hint(),
});
}
if ForkedParticipantAttachmentId::new(attachment_id).is_err() {
return Err(ForkedParticipantResumeRejection::MalformedAttachmentId {
capability_hint: hint(),
});
}
Ok(ForkedParticipantResumeAdmission::HostCapability)
}