use serde::{Deserialize, Serialize};
use crate::consciousness_profile::ConsciousnessTier;
pub const DEFAULT_MAX_MORAL_SEVERITY: f32 = 0.3;
pub const DEFAULT_TTL_US: u64 = 24 * 3600 * 1_000_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum DelegatedActionType {
Read = 0,
Analyze = 1,
Build = 2,
Write = 3,
VersionControl = 4,
Execute = 5,
Network = 6,
Govern = 7,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubPassport {
pub agent_did: String,
pub delegator_did: String,
pub max_tier: ConsciousnessTier,
pub effective_tier: ConsciousnessTier,
pub max_action: DelegatedActionType,
pub max_moral_severity: f32,
pub ahimsa_enforced: bool,
pub issued_at: u64,
pub expires_at: u64,
pub revoked: bool,
#[serde(default)]
pub revoked_at: Option<u64>,
#[serde(default)]
pub revoked_reason: Option<String>,
pub violation_count: u32,
pub correction_count: u32,
pub purpose: String,
#[serde(default)]
pub delegator_signature: Vec<u8>,
}
impl SubPassport {
pub fn new(agent_did: String, delegator_did: String, purpose: String, now_us: u64) -> Self {
Self {
agent_did,
delegator_did,
max_tier: ConsciousnessTier::Participant,
effective_tier: ConsciousnessTier::Participant,
max_action: DelegatedActionType::Read,
max_moral_severity: DEFAULT_MAX_MORAL_SEVERITY,
ahimsa_enforced: true,
issued_at: now_us,
expires_at: now_us + DEFAULT_TTL_US,
revoked: false,
revoked_at: None,
revoked_reason: None,
violation_count: 0,
correction_count: 0,
purpose,
delegator_signature: Vec::new(),
}
}
#[allow(clippy::too_many_arguments)]
pub fn with_constraints(
agent_did: String,
delegator_did: String,
purpose: String,
max_tier: ConsciousnessTier,
max_action: DelegatedActionType,
max_moral_severity: f32,
ttl_hours: u64,
now_us: u64,
) -> Self {
Self {
agent_did,
delegator_did,
max_tier,
effective_tier: max_tier,
max_action,
max_moral_severity: max_moral_severity.clamp(0.0, 1.0),
ahimsa_enforced: true,
issued_at: now_us,
expires_at: now_us + ttl_hours * 3600 * 1_000_000,
revoked: false,
revoked_at: None,
revoked_reason: None,
violation_count: 0,
correction_count: 0,
purpose,
delegator_signature: Vec::new(),
}
}
pub fn permits_action(&self, action: DelegatedActionType, now_us: u64) -> bool {
if !self.is_valid(now_us) {
return false;
}
action <= self.max_action
}
pub fn permits_severity(&self, severity: f32) -> bool {
severity <= self.max_moral_severity
}
pub fn is_valid(&self, now_us: u64) -> bool {
!self.revoked && now_us < self.expires_at
}
pub fn revoke(&mut self, reason: impl Into<String>, now_us: u64) {
self.revoked = true;
self.revoked_at = Some(now_us);
self.revoked_reason = Some(reason.into());
}
pub fn record_violation(&mut self) {
self.violation_count = self.violation_count.saturating_add(1);
if self.violation_count % 3 == 0 {
self.effective_tier = self.effective_tier.degrade(1);
}
}
pub fn record_correction(&mut self) {
self.correction_count = self.correction_count.saturating_add(1);
if self.correction_count % 10 == 0 {
self.effective_tier = self.effective_tier.upgrade_capped(self.max_tier);
}
}
pub fn hours_remaining(&self, now_us: u64) -> f64 {
if now_us >= self.expires_at {
return 0.0;
}
(self.expires_at - now_us) as f64 / (3600.0 * 1_000_000.0)
}
pub fn compliance_ratio(&self) -> f64 {
let total = self.violation_count + self.correction_count;
if total == 0 {
return 1.0; }
self.correction_count as f64 / total as f64
}
pub fn canonical_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(128);
buf.extend_from_slice(self.agent_did.as_bytes());
buf.extend_from_slice(self.delegator_did.as_bytes());
buf.push(self.max_tier as u8);
buf.push(self.max_action as u8);
buf.extend_from_slice(&self.max_moral_severity.to_le_bytes());
buf.extend_from_slice(&self.expires_at.to_le_bytes());
buf
}
pub fn sign_blake3(&mut self, key: &[u8; 32]) {
let data = self.canonical_bytes();
let mac = blake3::keyed_hash(key, &data);
self.delegator_signature = mac.as_bytes().to_vec();
}
pub fn verify_blake3(&self, key: &[u8; 32]) -> bool {
let data = self.canonical_bytes();
let mac = blake3::keyed_hash(key, &data);
self.delegator_signature == mac.as_bytes().as_slice()
}
pub fn is_signed(&self) -> bool {
!self.delegator_signature.is_empty()
}
#[cfg(feature = "identity")]
pub fn sign_ed25519(&mut self, key: &ed25519_dalek::SigningKey) {
use ed25519_dalek::Signer;
let data = self.canonical_bytes();
let sig = key.sign(&data);
self.delegator_signature = sig.to_bytes().to_vec();
}
#[cfg(feature = "identity")]
pub fn verify_ed25519(&self, pubkey: &ed25519_dalek::VerifyingKey) -> bool {
use ed25519_dalek::Verifier;
if self.delegator_signature.len() != 64 {
return false;
}
let sig_bytes: [u8; 64] = match self.delegator_signature.as_slice().try_into() {
Ok(b) => b,
Err(_) => return false,
};
let sig = ed25519_dalek::Signature::from_bytes(&sig_bytes);
let data = self.canonical_bytes();
pubkey.verify(&data, &sig).is_ok()
}
pub fn renew(&mut self, now_us: u64, ttl_hours: u64) -> bool {
if self.compliance_ratio() < 0.5 {
return false; }
self.issued_at = now_us;
self.expires_at = now_us + ttl_hours * 3600 * 1_000_000;
self.revoked = false;
true
}
}
#[cfg(test)]
mod tests {
use super::*;
fn now() -> u64 {
1_000_000_000_000 }
#[test]
fn default_sub_passport() {
let sp = SubPassport::new(
"did:mycelix:agent".into(),
"did:mycelix:human".into(),
"code review".into(),
now(),
);
assert_eq!(sp.max_tier, ConsciousnessTier::Participant);
assert_eq!(sp.max_action, DelegatedActionType::Read);
assert!(sp.ahimsa_enforced);
assert!(sp.is_valid(now()));
assert!(!sp.is_valid(now() + DEFAULT_TTL_US + 1));
}
#[test]
fn action_permission() {
let sp = SubPassport::with_constraints(
"did:mycelix:agent".into(),
"did:mycelix:human".into(),
"development".into(),
ConsciousnessTier::Citizen,
DelegatedActionType::Build,
0.3,
24,
now(),
);
assert!(sp.permits_action(DelegatedActionType::Read, now()));
assert!(sp.permits_action(DelegatedActionType::Analyze, now()));
assert!(sp.permits_action(DelegatedActionType::Build, now()));
assert!(!sp.permits_action(DelegatedActionType::Write, now()));
assert!(!sp.permits_action(DelegatedActionType::Execute, now()));
}
#[test]
fn moral_severity_gating() {
let sp = SubPassport::new(
"did:mycelix:agent".into(),
"did:mycelix:human".into(),
"review".into(),
now(),
);
assert!(sp.permits_severity(0.1)); assert!(sp.permits_severity(0.3)); assert!(!sp.permits_severity(0.5)); }
#[test]
fn violation_degrades_tier() {
let mut sp = SubPassport::with_constraints(
"did:mycelix:agent".into(),
"did:mycelix:human".into(),
"test".into(),
ConsciousnessTier::Citizen,
DelegatedActionType::Build,
0.3,
24,
now(),
);
assert_eq!(sp.effective_tier, ConsciousnessTier::Citizen);
sp.record_violation();
sp.record_violation();
sp.record_violation();
assert_eq!(sp.effective_tier, ConsciousnessTier::Participant);
sp.record_violation();
sp.record_violation();
sp.record_violation();
assert_eq!(sp.effective_tier, ConsciousnessTier::Observer);
}
#[test]
fn correction_restores_tier() {
let mut sp = SubPassport::with_constraints(
"did:mycelix:agent".into(),
"did:mycelix:human".into(),
"test".into(),
ConsciousnessTier::Citizen,
DelegatedActionType::Build,
0.3,
24,
now(),
);
for _ in 0..3 {
sp.record_violation();
}
assert_eq!(sp.effective_tier, ConsciousnessTier::Participant);
for _ in 0..10 {
sp.record_correction();
}
assert_eq!(sp.effective_tier, ConsciousnessTier::Citizen);
}
#[test]
fn cannot_upgrade_past_max() {
let mut sp = SubPassport::with_constraints(
"did:mycelix:agent".into(),
"did:mycelix:human".into(),
"test".into(),
ConsciousnessTier::Participant,
DelegatedActionType::Read,
0.3,
24,
now(),
);
for _ in 0..10 {
sp.record_correction();
}
assert_eq!(sp.effective_tier, ConsciousnessTier::Participant);
}
#[test]
fn revocation() {
let mut sp = SubPassport::new(
"did:mycelix:agent".into(),
"did:mycelix:human".into(),
"test".into(),
now(),
);
assert!(sp.is_valid(now()));
sp.revoke("test revocation", now());
assert!(!sp.is_valid(now()));
assert_eq!(sp.revoked_at, Some(now()));
assert_eq!(sp.revoked_reason.as_deref(), Some("test revocation"));
assert!(!sp.permits_action(DelegatedActionType::Read, now()));
}
#[test]
fn renewal_requires_compliance() {
let mut sp = SubPassport::new(
"did:mycelix:agent".into(),
"did:mycelix:human".into(),
"test".into(),
now(),
);
for _ in 0..5 {
sp.record_correction();
}
assert!(sp.renew(now() + DEFAULT_TTL_US, 24));
let mut bad_sp = SubPassport::new(
"did:mycelix:agent".into(),
"did:mycelix:human".into(),
"test".into(),
now(),
);
for _ in 0..10 {
bad_sp.record_violation();
}
assert!(!bad_sp.renew(now() + DEFAULT_TTL_US, 24));
}
#[test]
fn compliance_ratio() {
let mut sp = SubPassport::new(
"did:mycelix:agent".into(),
"did:mycelix:human".into(),
"test".into(),
now(),
);
assert_eq!(sp.compliance_ratio(), 1.0);
sp.record_violation();
sp.record_correction();
sp.record_correction();
assert!((sp.compliance_ratio() - 0.667).abs() < 0.01);
}
#[test]
fn hours_remaining() {
let sp = SubPassport::new(
"did:mycelix:agent".into(),
"did:mycelix:human".into(),
"test".into(),
now(),
);
let remaining = sp.hours_remaining(now());
assert!((remaining - 24.0).abs() < 0.01);
let remaining_half = sp.hours_remaining(now() + 12 * 3600 * 1_000_000);
assert!((remaining_half - 12.0).abs() < 0.01);
}
#[test]
fn unsigned_passport_not_signed() {
let sp = SubPassport::new(
"did:mycelix:agent".into(),
"did:mycelix:human".into(),
"test".into(),
now(),
);
assert!(!sp.is_signed());
}
#[test]
fn canonical_bytes_deterministic() {
let sp1 = SubPassport::new(
"did:mycelix:agent".into(),
"did:mycelix:human".into(),
"test".into(),
now(),
);
let sp2 = SubPassport::new(
"did:mycelix:agent".into(),
"did:mycelix:human".into(),
"test".into(),
now(),
);
assert_eq!(sp1.canonical_bytes(), sp2.canonical_bytes());
}
#[test]
fn blake3_sign_verify() {
let key = [42u8; 32];
let mut sp = SubPassport::new(
"did:mycelix:agent".into(),
"did:mycelix:human".into(),
"test".into(),
now(),
);
sp.sign_blake3(&key);
assert!(sp.is_signed());
assert!(sp.verify_blake3(&key));
}
#[test]
fn blake3_tamper_detection() {
let key = [42u8; 32];
let mut sp = SubPassport::new(
"did:mycelix:agent".into(),
"did:mycelix:human".into(),
"test".into(),
now(),
);
sp.sign_blake3(&key);
sp.max_action = DelegatedActionType::Execute;
assert!(!sp.verify_blake3(&key));
}
#[cfg(feature = "identity")]
#[test]
fn ed25519_sign_verify() {
let signing_key = ed25519_dalek::SigningKey::from_bytes(&[7u8; 32]);
let verifying_key = signing_key.verifying_key();
let mut sp = SubPassport::new(
"did:mycelix:agent".into(),
"did:mycelix:human".into(),
"test".into(),
now(),
);
sp.sign_ed25519(&signing_key);
assert!(sp.verify_ed25519(&verifying_key));
}
#[cfg(feature = "identity")]
#[test]
fn ed25519_tamper_detection() {
let signing_key = ed25519_dalek::SigningKey::from_bytes(&[7u8; 32]);
let verifying_key = signing_key.verifying_key();
let mut sp = SubPassport::new(
"did:mycelix:agent".into(),
"did:mycelix:human".into(),
"test".into(),
now(),
);
sp.sign_ed25519(&signing_key);
sp.max_moral_severity = 0.9;
assert!(!sp.verify_ed25519(&verifying_key));
}
#[test]
fn upgrade_capped_levels() {
assert_eq!(
ConsciousnessTier::Observer.upgrade_capped(ConsciousnessTier::Guardian),
ConsciousnessTier::Participant
);
assert_eq!(
ConsciousnessTier::Participant.upgrade_capped(ConsciousnessTier::Participant),
ConsciousnessTier::Participant
);
assert_eq!(
ConsciousnessTier::Citizen.upgrade_capped(ConsciousnessTier::Citizen),
ConsciousnessTier::Citizen
);
}
}