use std::collections::{BTreeMap, BTreeSet};
use exo_core::Did;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Permission(pub String);
impl Permission {
#[must_use]
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct PermissionSet {
pub permissions: Vec<Permission>,
}
impl PermissionSet {
#[must_use]
pub fn new(permissions: Vec<Permission>) -> Self {
Self { permissions }
}
pub fn contains(&self, p: &Permission) -> bool {
self.permissions.contains(p)
}
pub fn is_empty(&self) -> bool {
self.permissions.is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum GovernmentBranch {
Legislative,
Executive,
Judicial,
}
impl GovernmentBranch {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Legislative => "legislative",
Self::Executive => "executive",
Self::Judicial => "judicial",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum GovernedRoleName {
Senator,
Legislator,
Voter,
Executive,
ExecutiveAdmin,
Operator,
Worker,
Judge,
TransitionJudge,
}
impl GovernedRoleName {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Senator => "senator",
Self::Legislator => "legislator",
Self::Voter => "voter",
Self::Executive => "executive",
Self::ExecutiveAdmin => "executive-admin",
Self::Operator => "operator",
Self::Worker => "worker",
Self::Judge => "judge",
Self::TransitionJudge => "transition-judge",
}
}
#[must_use]
pub const fn branch(self) -> GovernmentBranch {
match self {
Self::Senator | Self::Legislator | Self::Voter => GovernmentBranch::Legislative,
Self::Executive | Self::ExecutiveAdmin | Self::Operator | Self::Worker => {
GovernmentBranch::Executive
}
Self::Judge | Self::TransitionJudge => GovernmentBranch::Judicial,
}
}
#[must_use]
pub fn parse(value: &str) -> Option<Self> {
match value {
"senator" => Some(Self::Senator),
"legislator" => Some(Self::Legislator),
"voter" => Some(Self::Voter),
"executive" => Some(Self::Executive),
"executive-admin" => Some(Self::ExecutiveAdmin),
"operator" => Some(Self::Operator),
"worker" => Some(Self::Worker),
"judge" => Some(Self::Judge),
"transition-judge" => Some(Self::TransitionJudge),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum RoleValidationError {
#[error("unknown governed role name")]
UnknownName { name: String },
#[error(
"role name does not match governed branch: expected {expected_branch}, actual {actual_branch}"
)]
BranchMismatch {
name: String,
expected_branch: &'static str,
actual_branch: &'static str,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Role {
pub name: String,
pub branch: GovernmentBranch,
}
impl Role {
#[must_use]
pub fn governed(name: GovernedRoleName) -> Self {
Self {
name: name.as_str().to_owned(),
branch: name.branch(),
}
}
pub fn validate_governed(&self) -> Result<GovernedRoleName, RoleValidationError> {
let Some(governed_name) = GovernedRoleName::parse(&self.name) else {
return Err(RoleValidationError::UnknownName {
name: self.name.clone(),
});
};
let expected_branch = governed_name.branch();
if expected_branch != self.branch {
return Err(RoleValidationError::BranchMismatch {
name: self.name.clone(),
expected_branch: expected_branch.as_str(),
actual_branch: self.branch.as_str(),
});
}
Ok(governed_name)
}
pub fn try_new(
name: impl Into<String>,
branch: GovernmentBranch,
) -> Result<Self, RoleValidationError> {
let role = Self {
name: name.into(),
branch,
};
role.validate_governed()?;
Ok(role)
}
}
pub const DAGDB_WRITEBACK_SCOPE: &str = "dag-db:writeback";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum BailmentState {
None,
Active {
bailor: Did,
bailee: Did,
scope: String,
},
Suspended { reason: String },
Terminated,
}
impl BailmentState {
pub fn is_active(&self) -> bool {
matches!(self, BailmentState::Active { .. })
}
pub fn authorizes_writeback(&self, agent_did: &str) -> bool {
matches!(
self,
BailmentState::Active { bailee, scope, .. }
if bailee.as_str() == agent_did && scope == DAGDB_WRITEBACK_SCOPE
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ConsentRecord {
pub subject: Did,
pub granted_to: Did,
pub scope: String,
pub active: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct AuthorityChain {
pub links: Vec<AuthorityLink>,
}
impl AuthorityChain {
pub fn is_empty(&self) -> bool {
self.links.is_empty()
}
pub fn depth(&self) -> usize {
self.links.len()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AuthorityLink {
pub grantor: Did,
pub grantee: Did,
pub permissions: PermissionSet,
pub signature: Vec<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub grantor_public_key: Option<Vec<u8>>,
}
pub type TrustedAuthorityKeys = BTreeMap<Did, Vec<Vec<u8>>>;
pub type TrustedProvenanceKeys = BTreeMap<Did, Vec<Vec<u8>>>;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct QuorumEvidence {
pub threshold: u32,
pub votes: Vec<QuorumVote>,
}
impl QuorumEvidence {
pub fn is_met(&self) -> bool {
let Some(threshold) = usize::try_from(self.threshold).ok() else {
return false;
};
self.distinct_approved_voter_count() >= threshold
}
pub fn is_met_authentic(&self) -> bool {
let Some(threshold) = usize::try_from(self.threshold).ok() else {
return false;
};
self.distinct_authentic_approved_voter_count() >= threshold
}
pub fn synthetic_vote_count(&self) -> usize {
self.votes
.iter()
.filter(|v| v.provenance.as_ref().is_some_and(|p| p.is_synthetic()))
.count()
}
#[must_use]
pub fn duplicate_voters(&self) -> BTreeSet<Did> {
let mut seen = BTreeSet::new();
let mut duplicates = BTreeSet::new();
for vote in &self.votes {
if !seen.insert(vote.voter.clone()) {
duplicates.insert(vote.voter.clone());
}
}
duplicates
}
#[must_use]
pub fn distinct_approved_voter_count(&self) -> usize {
self.votes
.iter()
.filter(|vote| vote.approved)
.map(|vote| vote.voter.clone())
.collect::<BTreeSet<_>>()
.len()
}
#[must_use]
pub fn distinct_authentic_approved_voter_count(&self) -> usize {
self.votes
.iter()
.filter(|vote| {
vote.approved
&& vote
.provenance
.as_ref()
.is_some_and(Provenance::is_authentic_human_quorum_voice)
})
.map(|vote| vote.voter.clone())
.collect::<BTreeSet<_>>()
.len()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct QuorumVote {
pub voter: Did,
pub approved: bool,
pub signature: Vec<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provenance: Option<Provenance>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum VoiceKind {
Human,
Synthetic,
System,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum IndependenceClaim {
Independent,
Coordinated,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum ReviewOrder {
FirstOrder,
Derivative,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Provenance {
pub actor: Did,
pub timestamp: String,
pub action_hash: Vec<u8>,
pub signature: Vec<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub public_key: Option<Vec<u8>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub voice_kind: Option<VoiceKind>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub independence: Option<IndependenceClaim>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub review_order: Option<ReviewOrder>,
}
impl Provenance {
pub fn is_signed(&self) -> bool {
!self.signature.is_empty()
}
pub fn is_human_voice(&self) -> bool {
self.voice_kind == Some(VoiceKind::Human)
}
pub fn is_independent(&self) -> bool {
self.independence == Some(IndependenceClaim::Independent)
}
pub fn is_first_order_review(&self) -> bool {
self.review_order == Some(ReviewOrder::FirstOrder)
}
pub fn is_authentic_human_quorum_voice(&self) -> bool {
self.is_human_voice() && self.is_independent() && self.is_first_order_review()
}
pub fn is_synthetic(&self) -> bool {
self.voice_kind == Some(VoiceKind::Synthetic)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn did(s: &str) -> Did {
Did::new(s).expect("valid DID")
}
#[test]
fn permission_set_contains() {
let set = PermissionSet::new(vec![Permission::new("read"), Permission::new("write")]);
assert!(set.contains(&Permission::new("read")));
assert!(!set.contains(&Permission::new("admin")));
}
#[test]
fn permission_set_empty() {
let set = PermissionSet::default();
assert!(set.is_empty());
}
#[test]
fn bailment_state_is_active() {
let active = BailmentState::Active {
bailor: did("did:exo:bailor"),
bailee: did("did:exo:bailee"),
scope: "data".into(),
};
assert!(active.is_active());
assert!(!BailmentState::None.is_active());
assert!(!BailmentState::Terminated.is_active());
let suspended = BailmentState::Suspended {
reason: "audit".into(),
};
assert!(!suspended.is_active());
}
#[test]
fn bailment_state_authorizes_writeback_for_active_bailee_and_scope() {
let active = BailmentState::Active {
bailor: did("did:exo:bailor"),
bailee: did("did:exo:bailee"),
scope: DAGDB_WRITEBACK_SCOPE.into(),
};
assert!(active.authorizes_writeback("did:exo:bailee"));
}
#[test]
fn bailment_state_authorizes_writeback_rejects_wrong_bailee() {
let active = BailmentState::Active {
bailor: did("did:exo:bailor"),
bailee: did("did:exo:bailee"),
scope: DAGDB_WRITEBACK_SCOPE.into(),
};
assert!(!active.authorizes_writeback("did:exo:other"));
}
#[test]
fn bailment_state_authorizes_writeback_rejects_wrong_scope() {
let active = BailmentState::Active {
bailor: did("did:exo:bailor"),
bailee: did("did:exo:bailee"),
scope: "dag-db:read".into(),
};
assert!(!active.authorizes_writeback("did:exo:bailee"));
}
#[test]
fn bailment_state_authorizes_writeback_rejects_inactive_states() {
assert!(!BailmentState::None.authorizes_writeback("did:exo:bailee"));
assert!(!BailmentState::Terminated.authorizes_writeback("did:exo:bailee"));
assert!(
!BailmentState::Suspended {
reason: "audit".into(),
}
.authorizes_writeback("did:exo:bailee")
);
}
#[test]
fn authority_chain_empty() {
let chain = AuthorityChain::default();
assert!(chain.is_empty());
assert_eq!(chain.depth(), 0);
}
#[test]
fn authority_chain_depth() {
let chain = AuthorityChain {
links: vec![
AuthorityLink {
grantor: did("did:exo:root"),
grantee: did("did:exo:mid"),
permissions: PermissionSet::default(),
signature: vec![1],
grantor_public_key: None,
},
AuthorityLink {
grantor: did("did:exo:mid"),
grantee: did("did:exo:leaf"),
permissions: PermissionSet::default(),
signature: vec![2],
grantor_public_key: None,
},
],
};
assert_eq!(chain.depth(), 2);
assert!(!chain.is_empty());
}
fn make_vote(voter: &str, approved: bool, sig: u8, voice: Option<VoiceKind>) -> QuorumVote {
QuorumVote {
voter: did(voter),
approved,
signature: vec![sig],
provenance: voice.map(|vk| Provenance {
actor: did(voter),
timestamp: "t".into(),
action_hash: vec![1],
signature: vec![sig],
public_key: None,
voice_kind: Some(vk),
independence: (vk == VoiceKind::Human).then_some(IndependenceClaim::Independent),
review_order: (vk == VoiceKind::Human).then_some(ReviewOrder::FirstOrder),
}),
}
}
#[test]
fn quorum_evidence_met() {
let ev = QuorumEvidence {
threshold: 2,
votes: vec![
QuorumVote {
voter: did("did:exo:v1"),
approved: true,
signature: vec![1],
provenance: None,
},
QuorumVote {
voter: did("did:exo:v2"),
approved: true,
signature: vec![2],
provenance: None,
},
QuorumVote {
voter: did("did:exo:v3"),
approved: false,
signature: vec![3],
provenance: None,
},
],
};
assert!(ev.is_met());
}
#[test]
fn quorum_evidence_not_met() {
let ev = QuorumEvidence {
threshold: 3,
votes: vec![
QuorumVote {
voter: did("did:exo:v1"),
approved: true,
signature: vec![1],
provenance: None,
},
QuorumVote {
voter: did("did:exo:v2"),
approved: false,
signature: vec![2],
provenance: None,
},
],
};
assert!(!ev.is_met());
}
#[test]
fn quorum_evidence_counts_distinct_voters_only() {
let ev = QuorumEvidence {
threshold: 2,
votes: vec![
QuorumVote {
voter: did("did:exo:v1"),
approved: true,
signature: vec![1],
provenance: None,
},
QuorumVote {
voter: did("did:exo:v1"),
approved: true,
signature: vec![2],
provenance: None,
},
],
};
assert!(
!ev.is_met(),
"duplicate voter DIDs must not inflate raw quorum evidence"
);
}
#[test]
fn quorum_is_met_authentic_excludes_synthetic() {
let ev = QuorumEvidence {
threshold: 3,
votes: vec![
make_vote("did:exo:h1", true, 1, Some(VoiceKind::Human)),
make_vote("did:exo:h2", true, 2, Some(VoiceKind::Human)),
make_vote("did:exo:ai1", true, 3, Some(VoiceKind::Synthetic)),
],
};
assert!(ev.is_met(), "raw count should pass (3 approvals)");
assert!(
!ev.is_met_authentic(),
"authentic count should fail (only 2 human)"
);
assert_eq!(ev.synthetic_vote_count(), 1);
}
#[test]
fn quorum_is_met_authentic_passes_all_human() {
let ev = QuorumEvidence {
threshold: 2,
votes: vec![
make_vote("did:exo:h1", true, 1, Some(VoiceKind::Human)),
make_vote("did:exo:h2", true, 2, Some(VoiceKind::Human)),
],
};
assert!(ev.is_met_authentic());
assert_eq!(ev.synthetic_vote_count(), 0);
}
#[test]
fn quorum_is_met_authentic_counts_distinct_humans_only() {
let ev = QuorumEvidence {
threshold: 2,
votes: vec![
make_vote("did:exo:h1", true, 1, Some(VoiceKind::Human)),
make_vote("did:exo:h1", true, 2, Some(VoiceKind::Human)),
],
};
assert!(
!ev.is_met_authentic(),
"duplicate human voter DIDs must not inflate authentic quorum evidence"
);
}
#[test]
fn quorum_is_met_authentic_rejects_legacy_votes_without_provenance() {
let ev = QuorumEvidence {
threshold: 2,
votes: vec![
QuorumVote {
voter: did("did:exo:v1"),
approved: true,
signature: vec![1],
provenance: None,
},
QuorumVote {
voter: did("did:exo:v2"),
approved: true,
signature: vec![2],
provenance: None,
},
],
};
assert!(!ev.is_met_authentic());
}
#[test]
fn quorum_is_met_authentic_rejects_votes_without_human_provenance() {
let ev = QuorumEvidence {
threshold: 2,
votes: vec![
QuorumVote {
voter: did("did:exo:v1"),
approved: true,
signature: vec![1],
provenance: None,
},
make_vote("did:exo:system1", true, 2, Some(VoiceKind::System)),
],
};
assert!(
!ev.is_met_authentic(),
"authentic quorum must not assume missing or system provenance is human"
);
}
#[test]
fn quorum_is_met_authentic_requires_independent_first_order_human_votes() {
let mut coordinated = make_vote("did:exo:h1", true, 1, Some(VoiceKind::Human));
coordinated
.provenance
.as_mut()
.expect("human provenance")
.independence = Some(IndependenceClaim::Coordinated);
coordinated
.provenance
.as_mut()
.expect("human provenance")
.review_order = Some(ReviewOrder::FirstOrder);
let mut derivative = make_vote("did:exo:h2", true, 2, Some(VoiceKind::Human));
derivative
.provenance
.as_mut()
.expect("human provenance")
.independence = Some(IndependenceClaim::Independent);
derivative
.provenance
.as_mut()
.expect("human provenance")
.review_order = Some(ReviewOrder::Derivative);
let ev = QuorumEvidence {
threshold: 2,
votes: vec![coordinated, derivative],
};
assert!(
!ev.is_met_authentic(),
"coordinated or derivative human claims must not count as authentic quorum"
);
}
#[test]
fn provenance_is_human_voice() {
let human_prov = Provenance {
actor: did("did:exo:h1"),
timestamp: "t".into(),
action_hash: vec![1],
signature: vec![1],
public_key: None,
voice_kind: Some(VoiceKind::Human),
independence: Some(IndependenceClaim::Independent),
review_order: Some(ReviewOrder::FirstOrder),
};
assert!(human_prov.is_human_voice());
assert!(human_prov.is_independent());
assert!(human_prov.is_first_order_review());
assert!(human_prov.is_authentic_human_quorum_voice());
assert!(!human_prov.is_synthetic());
}
#[test]
fn provenance_synthetic_not_human() {
let ai_prov = Provenance {
actor: did("did:exo:ai1"),
timestamp: "t".into(),
action_hash: vec![1],
signature: vec![1],
public_key: None,
voice_kind: Some(VoiceKind::Synthetic),
independence: None,
review_order: None,
};
assert!(!ai_prov.is_human_voice());
assert!(ai_prov.is_synthetic());
assert!(!ai_prov.is_independent());
}
#[test]
fn provenance_unspecified_voice_not_human() {
let prov = Provenance {
actor: did("did:exo:unknown"),
timestamp: "t".into(),
action_hash: vec![1],
signature: vec![1],
public_key: None,
voice_kind: None,
independence: None,
review_order: None,
};
assert!(!prov.is_human_voice());
assert!(!prov.is_synthetic());
assert!(!prov.is_independent());
}
#[test]
fn provenance_is_signed() {
let signed = Provenance {
actor: did("did:exo:actor"),
timestamp: "2025-01-01".into(),
action_hash: vec![1],
signature: vec![4, 5, 6],
public_key: None,
voice_kind: None,
independence: None,
review_order: None,
};
assert!(signed.is_signed());
let unsigned = Provenance {
actor: did("did:exo:actor"),
timestamp: "2025-01-01".into(),
action_hash: vec![1],
signature: vec![],
public_key: None,
voice_kind: None,
independence: None,
review_order: None,
};
assert!(!unsigned.is_signed());
}
}