Skip to main content

moloch_core/
event.rs

1//! Audit event types.
2//!
3//! An audit event is the atomic unit of the Moloch chain. Unlike financial
4//! transactions, events are immutable records of something that happened.
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8
9use crate::crypto::{hash, Hash, PublicKey, Sig};
10use crate::error::{Error, Result};
11
12/// Unique identifier for an event (content-addressed).
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
14pub struct EventId(pub Hash);
15
16impl EventId {
17    /// Get the underlying hash.
18    pub fn as_hash(&self) -> &Hash {
19        &self.0
20    }
21}
22
23impl std::fmt::Display for EventId {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        write!(f, "{}", self.0)
26    }
27}
28
29/// Identifies the actor (who did something).
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31pub struct ActorId {
32    /// The actor's public key (cryptographic identity).
33    pub key: PublicKey,
34    /// Human-readable name (not authenticated).
35    pub name: Option<String>,
36    /// Actor type.
37    pub kind: ActorKind,
38}
39
40impl ActorId {
41    /// Create a new actor ID.
42    pub fn new(key: PublicKey, kind: ActorKind) -> Self {
43        Self {
44            key,
45            name: None,
46            kind,
47        }
48    }
49
50    /// Add a display name.
51    pub fn with_name(mut self, name: impl Into<String>) -> Self {
52        self.name = Some(name.into());
53        self
54    }
55
56    /// Get the unique hash ID for this actor.
57    pub fn id(&self) -> Hash {
58        self.key.id()
59    }
60}
61
62/// Type of actor.
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
64#[serde(rename_all = "snake_case")]
65pub enum ActorKind {
66    /// A human user.
67    User,
68    /// An automated system/service.
69    System,
70    /// An AI agent.
71    Agent,
72    /// External service integration.
73    Integration,
74}
75
76/// Identifies what was affected.
77#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
78pub struct ResourceId {
79    /// Resource type.
80    pub kind: ResourceKind,
81    /// Unique identifier within the resource type.
82    pub id: String,
83    /// Optional parent resource (for hierarchical resources).
84    pub parent: Option<Box<ResourceId>>,
85}
86
87impl ResourceId {
88    /// Create a new resource ID.
89    pub fn new(kind: ResourceKind, id: impl Into<String>) -> Self {
90        Self {
91            kind,
92            id: id.into(),
93            parent: None,
94        }
95    }
96
97    /// Add a parent resource.
98    pub fn with_parent(mut self, parent: ResourceId) -> Self {
99        self.parent = Some(Box::new(parent));
100        self
101    }
102}
103
104/// Type of resource.
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
106#[serde(rename_all = "snake_case")]
107pub enum ResourceKind {
108    /// Git repository.
109    Repository,
110    /// Git commit.
111    Commit,
112    /// Git branch.
113    Branch,
114    /// Git tag.
115    Tag,
116    /// Pull/merge request.
117    PullRequest,
118    /// Issue.
119    Issue,
120    /// File.
121    File,
122    /// User account.
123    User,
124    /// Organization.
125    Organization,
126    /// API key or token.
127    Credential,
128    /// Configuration.
129    Config,
130    /// Generic document.
131    Document,
132    /// Other resource type.
133    Other,
134}
135
136/// What happened.
137#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
138#[serde(rename_all = "snake_case")]
139pub enum EventType {
140    // === Repository Events ===
141    /// Repository was created.
142    RepoCreated,
143    /// Repository was deleted.
144    RepoDeleted,
145    /// Repository ownership transferred.
146    RepoTransferred,
147    /// Repository visibility changed.
148    RepoVisibilityChanged,
149
150    // === Git Events ===
151    /// Commits pushed to a branch.
152    Push { force: bool, commits: u32 },
153    /// Branch created.
154    BranchCreated,
155    /// Branch deleted.
156    BranchDeleted,
157    /// Branch protection rules changed.
158    BranchProtectionChanged,
159    /// Tag created.
160    TagCreated,
161    /// Tag deleted.
162    TagDeleted,
163
164    // === Collaboration Events ===
165    /// Pull request opened.
166    PullRequestOpened,
167    /// Pull request merged.
168    PullRequestMerged,
169    /// Pull request closed (not merged).
170    PullRequestClosed,
171    /// Code review submitted.
172    ReviewSubmitted { verdict: ReviewVerdict },
173    /// Issue opened.
174    IssueOpened,
175    /// Issue closed.
176    IssueClosed,
177
178    // === Access Events ===
179    /// Access granted to resource.
180    AccessGranted { permission: String },
181    /// Access revoked from resource.
182    AccessRevoked,
183    /// User logged in.
184    Login { method: String },
185    /// User logged out.
186    Logout,
187    /// Login attempt failed.
188    LoginFailed { reason: String },
189    /// MFA configured.
190    MfaConfigured,
191
192    // === Agent Events ===
193    /// AI agent performed an action.
194    AgentAction {
195        action: String,
196        reasoning: Option<String>,
197    },
198    /// Agent authorization granted.
199    AgentAuthorized { scope: Vec<String> },
200    /// Agent authorization revoked.
201    AgentRevoked,
202
203    // === Compliance Events ===
204    /// Data export requested (GDPR).
205    DataExportRequested,
206    /// Data export completed.
207    DataExportCompleted,
208    /// Data deletion requested.
209    DataDeletionRequested,
210    /// Data deletion completed.
211    DataDeletionCompleted,
212    /// Consent given.
213    ConsentGiven { purpose: String },
214    /// Consent revoked.
215    ConsentRevoked { purpose: String },
216
217    // === System Events ===
218    /// Configuration changed.
219    ConfigChanged { key: String },
220    /// Release published.
221    ReleasePublished { version: String },
222    /// Backup created.
223    BackupCreated,
224    /// Security scan completed.
225    SecurityScan { findings: u32 },
226
227    // === Agent Accountability Events (v2) ===
228    /// Agent accountability event with full structured data (Section 11).
229    ///
230    /// Replaces `AgentAction` for new agent deployments.
231    /// Contains the serialized `AgentEventType` for structured processing.
232    AgentAccountability {
233        /// The specific agent event type label.
234        event_label: String,
235        /// Serialized `AgentEventType` (JSON bytes stored externally in metadata).
236        summary: String,
237    },
238
239    // === Generic ===
240    /// Custom event type.
241    Custom { name: String },
242}
243
244/// Code review verdict.
245#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
246#[serde(rename_all = "snake_case")]
247pub enum ReviewVerdict {
248    Approved,
249    ChangesRequested,
250    Commented,
251}
252
253/// Result of an action.
254#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
255#[serde(rename_all = "snake_case")]
256pub enum Outcome {
257    /// Action succeeded.
258    Success,
259    /// Action failed.
260    Failure { reason: String },
261    /// Action was denied (authorization).
262    Denied { reason: String },
263    /// Action is pending.
264    Pending,
265}
266
267/// Attestation from the submitting system.
268#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
269pub struct Attestation {
270    /// Public key of the attesting system.
271    pub attester: PublicKey,
272    /// Signature over the event's canonical form.
273    pub signature: Sig,
274    /// Optional chain of attestations (for forwarded events).
275    pub chain: Vec<AttestationLink>,
276}
277
278/// A link in an attestation chain.
279#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
280pub struct AttestationLink {
281    pub attester: PublicKey,
282    pub signature: Sig,
283    #[serde(with = "chrono::serde::ts_milliseconds")]
284    pub timestamp: DateTime<Utc>,
285}
286
287impl Attestation {
288    /// Create a new attestation.
289    pub fn new(attester: PublicKey, signature: Sig) -> Self {
290        Self {
291            attester,
292            signature,
293            chain: Vec::new(),
294        }
295    }
296
297    /// Verify this attestation against an event's canonical bytes.
298    pub fn verify(&self, canonical_bytes: &[u8]) -> Result<()> {
299        self.attester.verify(canonical_bytes, &self.signature)?;
300
301        // Verify chain if present
302        // Each link attests to the previous signature
303        let mut prev_sig_bytes = self.signature.to_bytes().to_vec();
304        for link in &self.chain {
305            link.attester.verify(&prev_sig_bytes, &link.signature)?;
306            prev_sig_bytes = link.signature.to_bytes().to_vec();
307        }
308
309        Ok(())
310    }
311}
312
313/// An audit event.
314#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
315pub struct AuditEvent {
316    /// When the event occurred (claimed by submitter), as Unix timestamp in millis.
317    #[serde(with = "chrono::serde::ts_milliseconds")]
318    pub event_time: DateTime<Utc>,
319
320    /// What happened.
321    pub event_type: EventType,
322
323    /// Who did it.
324    pub actor: ActorId,
325
326    /// What was affected.
327    pub resource: ResourceId,
328
329    /// Result of the action.
330    pub outcome: Outcome,
331
332    /// Additional structured data (JSON serialized to bytes).
333    pub metadata: Vec<u8>,
334
335    /// Attestation from submitting system.
336    pub attestation: Attestation,
337}
338
339impl AuditEvent {
340    /// Create a new event builder.
341    pub fn builder() -> AuditEventBuilder {
342        AuditEventBuilder::default()
343    }
344
345    /// Compute the canonical bytes for this event (for hashing/signing).
346    pub fn canonical_bytes(&self) -> Vec<u8> {
347        // We serialize without the attestation for signing
348        let signable = SignableEvent {
349            event_time: self.event_time,
350            event_type: &self.event_type,
351            actor: &self.actor,
352            resource: &self.resource,
353            outcome: &self.outcome,
354            metadata: &self.metadata,
355        };
356        // Use bincode for deterministic serialization
357        bincode::serialize(&signable).expect("serialization should not fail")
358    }
359
360    /// Get metadata as JSON value (if parseable).
361    pub fn metadata_json(&self) -> Option<serde_json::Value> {
362        if self.metadata.is_empty() {
363            return None;
364        }
365        serde_json::from_slice(&self.metadata).ok()
366    }
367
368    /// Check if metadata is present.
369    pub fn has_metadata(&self) -> bool {
370        !self.metadata.is_empty()
371    }
372
373    /// Compute the content-addressed ID for this event.
374    pub fn id(&self) -> EventId {
375        EventId(hash(&self.canonical_bytes()))
376    }
377
378    /// Get the attester's public key.
379    pub fn attester(&self) -> &PublicKey {
380        &self.attestation.attester
381    }
382
383    /// Get the attestation signature.
384    pub fn signature(&self) -> &Sig {
385        &self.attestation.signature
386    }
387
388    /// Get verification components for batch verification.
389    ///
390    /// Returns (public_key, canonical_bytes, signature) tuple suitable for
391    /// passing to `batch_verify()`.
392    pub fn verification_tuple(&self) -> (&PublicKey, Vec<u8>, &Sig) {
393        (self.attester(), self.canonical_bytes(), self.signature())
394    }
395
396    /// Validate this event.
397    pub fn validate(&self) -> Result<()> {
398        // Verify attestation
399        self.attestation.verify(&self.canonical_bytes())?;
400
401        // Event time should be reasonable (not too far in future)
402        let now = Utc::now();
403        if self.event_time > now + chrono::Duration::minutes(5) {
404            return Err(Error::invalid_event("event_time is in the future"));
405        }
406
407        Ok(())
408    }
409}
410
411/// Helper struct for canonical serialization (excludes attestation).
412#[derive(Serialize)]
413struct SignableEvent<'a> {
414    #[serde(with = "chrono::serde::ts_milliseconds")]
415    event_time: DateTime<Utc>,
416    event_type: &'a EventType,
417    actor: &'a ActorId,
418    resource: &'a ResourceId,
419    outcome: &'a Outcome,
420    metadata: &'a [u8],
421}
422
423/// Builder for creating audit events.
424#[derive(Default)]
425pub struct AuditEventBuilder {
426    event_time: Option<DateTime<Utc>>,
427    event_type: Option<EventType>,
428    actor: Option<ActorId>,
429    resource: Option<ResourceId>,
430    outcome: Option<Outcome>,
431    metadata: Vec<u8>,
432}
433
434impl AuditEventBuilder {
435    /// Set the event time.
436    pub fn event_time(mut self, time: DateTime<Utc>) -> Self {
437        self.event_time = Some(time);
438        self
439    }
440
441    /// Use current time as event time.
442    pub fn now(mut self) -> Self {
443        self.event_time = Some(Utc::now());
444        self
445    }
446
447    /// Set the event type.
448    pub fn event_type(mut self, event_type: EventType) -> Self {
449        self.event_type = Some(event_type);
450        self
451    }
452
453    /// Set the actor.
454    pub fn actor(mut self, actor: ActorId) -> Self {
455        self.actor = Some(actor);
456        self
457    }
458
459    /// Set the resource.
460    pub fn resource(mut self, resource: ResourceId) -> Self {
461        self.resource = Some(resource);
462        self
463    }
464
465    /// Set the outcome.
466    pub fn outcome(mut self, outcome: Outcome) -> Self {
467        self.outcome = Some(outcome);
468        self
469    }
470
471    /// Set metadata from JSON value (serialized to bytes).
472    pub fn metadata(mut self, metadata: serde_json::Value) -> Self {
473        self.metadata = serde_json::to_vec(&metadata).unwrap_or_default();
474        self
475    }
476
477    /// Set metadata from raw bytes.
478    pub fn metadata_bytes(mut self, bytes: Vec<u8>) -> Self {
479        self.metadata = bytes;
480        self
481    }
482
483    /// Build and sign the event.
484    pub fn sign(self, key: &crate::crypto::SecretKey) -> Result<AuditEvent> {
485        let event_time = self
486            .event_time
487            .ok_or_else(|| Error::invalid_event("missing event_time"))?;
488        let event_type = self
489            .event_type
490            .ok_or_else(|| Error::invalid_event("missing event_type"))?;
491        let actor = self
492            .actor
493            .ok_or_else(|| Error::invalid_event("missing actor"))?;
494        let resource = self
495            .resource
496            .ok_or_else(|| Error::invalid_event("missing resource"))?;
497        let outcome = self.outcome.unwrap_or(Outcome::Success);
498
499        // Create event without attestation first to get canonical bytes
500        let signable = SignableEvent {
501            event_time,
502            event_type: &event_type,
503            actor: &actor,
504            resource: &resource,
505            outcome: &outcome,
506            metadata: &self.metadata,
507        };
508        let canonical = bincode::serialize(&signable)?;
509        let signature = key.sign(&canonical);
510
511        Ok(AuditEvent {
512            event_time,
513            event_type,
514            actor,
515            resource,
516            outcome,
517            metadata: self.metadata,
518            attestation: Attestation::new(key.public_key(), signature),
519        })
520    }
521}
522
523#[cfg(test)]
524mod tests {
525    use super::*;
526    use crate::crypto::SecretKey;
527
528    fn test_key() -> SecretKey {
529        SecretKey::generate()
530    }
531
532    #[test]
533    fn test_event_creation_and_validation() {
534        let key = test_key();
535        let actor = ActorId::new(key.public_key(), ActorKind::User).with_name("alice");
536        let resource = ResourceId::new(ResourceKind::Repository, "myrepo");
537
538        let event = AuditEvent::builder()
539            .now()
540            .event_type(EventType::Push {
541                force: false,
542                commits: 3,
543            })
544            .actor(actor)
545            .resource(resource)
546            .outcome(Outcome::Success)
547            .sign(&key)
548            .unwrap();
549
550        assert!(event.validate().is_ok());
551    }
552
553    #[test]
554    fn test_event_id_is_deterministic() {
555        let key = test_key();
556        let time = Utc::now();
557        let actor = ActorId::new(key.public_key(), ActorKind::System);
558        let resource = ResourceId::new(ResourceKind::Config, "settings");
559
560        let event1 = AuditEvent::builder()
561            .event_time(time)
562            .event_type(EventType::ConfigChanged {
563                key: "theme".into(),
564            })
565            .actor(actor.clone())
566            .resource(resource.clone())
567            .sign(&key)
568            .unwrap();
569
570        let event2 = AuditEvent::builder()
571            .event_time(time)
572            .event_type(EventType::ConfigChanged {
573                key: "theme".into(),
574            })
575            .actor(actor)
576            .resource(resource)
577            .sign(&key)
578            .unwrap();
579
580        assert_eq!(event1.id(), event2.id());
581    }
582
583    #[test]
584    fn test_tampered_event_fails_validation() {
585        let key = test_key();
586        let actor = ActorId::new(key.public_key(), ActorKind::User);
587        let resource = ResourceId::new(ResourceKind::Repository, "myrepo");
588
589        let mut event = AuditEvent::builder()
590            .now()
591            .event_type(EventType::RepoDeleted)
592            .actor(actor)
593            .resource(resource)
594            .sign(&key)
595            .unwrap();
596
597        // Tamper with the event
598        event.outcome = Outcome::Failure {
599            reason: "tampered".into(),
600        };
601
602        // Validation should fail
603        assert!(event.validate().is_err());
604    }
605
606    #[test]
607    fn test_bincode_roundtrip() {
608        let key = test_key();
609        let actor = ActorId::new(key.public_key(), ActorKind::User);
610        let resource = ResourceId::new(ResourceKind::Repository, "myrepo");
611
612        let event = AuditEvent::builder()
613            .now()
614            .event_type(EventType::Push {
615                force: false,
616                commits: 1,
617            })
618            .actor(actor)
619            .resource(resource)
620            .sign(&key)
621            .unwrap();
622
623        // Serialize
624        let bytes = bincode::serialize(&event).expect("serialize should work");
625        println!("Serialized event size: {} bytes", bytes.len());
626
627        // Deserialize
628        let restored: AuditEvent = bincode::deserialize(&bytes).expect("deserialize should work");
629
630        assert_eq!(event.id(), restored.id());
631        assert!(restored.validate().is_ok());
632    }
633
634    #[test]
635    fn test_datetime_bincode() {
636        use chrono::{DateTime, Utc};
637        use serde::{Deserialize, Serialize};
638
639        #[derive(Serialize, Deserialize, Debug)]
640        struct TestTime {
641            #[serde(with = "chrono::serde::ts_milliseconds")]
642            time: DateTime<Utc>,
643        }
644
645        let t = TestTime { time: Utc::now() };
646        let bytes = bincode::serialize(&t).expect("serialize");
647        let restored: TestTime = bincode::deserialize(&bytes).expect("deserialize");
648        // Note: ts_milliseconds loses sub-millisecond precision, so we check
649        // that the times are within 1ms of each other
650        let diff = (t.time - restored.time).num_milliseconds().abs();
651        assert!(diff <= 1, "times should be within 1ms");
652    }
653
654    #[test]
655    fn test_actor_bincode() {
656        let key = test_key();
657        let actor = ActorId::new(key.public_key(), ActorKind::User).with_name("alice");
658
659        let bytes = bincode::serialize(&actor).expect("serialize");
660        println!("ActorId size: {} bytes", bytes.len());
661        let restored: ActorId = bincode::deserialize(&bytes).expect("deserialize");
662        assert_eq!(actor.id(), restored.id());
663    }
664
665    #[test]
666    fn test_attestation_bincode() {
667        let key = test_key();
668        let sig = key.sign(b"test");
669        let att = Attestation::new(key.public_key(), sig);
670
671        let bytes = bincode::serialize(&att).expect("serialize");
672        println!("Attestation size: {} bytes", bytes.len());
673        let restored: Attestation = bincode::deserialize(&bytes).expect("deserialize");
674        assert_eq!(att.attester.as_bytes(), restored.attester.as_bytes());
675    }
676
677    #[test]
678    fn test_event_type_bincode() {
679        let et = EventType::Push {
680            force: true,
681            commits: 5,
682        };
683        let bytes = bincode::serialize(&et).expect("serialize");
684        println!("EventType size: {} bytes", bytes.len());
685        let restored: EventType = bincode::deserialize(&bytes).expect("deserialize");
686        assert_eq!(et, restored);
687    }
688
689    #[test]
690    fn test_resource_bincode() {
691        let r = ResourceId::new(ResourceKind::Repository, "myrepo");
692        let bytes = bincode::serialize(&r).expect("serialize");
693        println!("ResourceId size: {} bytes", bytes.len());
694        let restored: ResourceId = bincode::deserialize(&bytes).expect("deserialize");
695        assert_eq!(r.id, restored.id);
696    }
697
698    #[test]
699    fn test_outcome_bincode() {
700        let o = Outcome::Success;
701        let bytes = bincode::serialize(&o).expect("serialize");
702        println!("Outcome size: {} bytes", bytes.len());
703        let restored: Outcome = bincode::deserialize(&bytes).expect("deserialize");
704        assert_eq!(o, restored);
705    }
706
707    #[test]
708    fn test_minimal_struct_bincode() {
709        use crate::crypto::Sig;
710        use serde::{Deserialize, Serialize};
711
712        // Test struct with Sig inside
713        #[derive(Serialize, Deserialize, Debug)]
714        struct TestWithSig {
715            data: u64,
716            sig: Sig,
717        }
718
719        let key = test_key();
720        let sig = key.sign(b"test");
721        let t = TestWithSig { data: 42, sig };
722
723        let bytes = bincode::serialize(&t).expect("serialize");
724        println!("TestWithSig size: {} bytes", bytes.len());
725        let _restored: TestWithSig = bincode::deserialize(&bytes).expect("deserialize");
726    }
727
728    #[test]
729    fn test_vec_attestation_link_bincode() {
730        let chain: Vec<AttestationLink> = vec![];
731        let bytes = bincode::serialize(&chain).expect("serialize");
732        println!("Empty chain size: {} bytes", bytes.len());
733        let restored: Vec<AttestationLink> = bincode::deserialize(&bytes).expect("deserialize");
734        assert_eq!(chain.len(), restored.len());
735    }
736
737    #[test]
738    fn test_full_struct_bincode() {
739        use chrono::{DateTime, Utc};
740        use serde::{Deserialize, Serialize};
741
742        // Mirror AuditEvent exactly
743        #[derive(Serialize, Deserialize, Debug)]
744        struct TestEvent {
745            #[serde(with = "chrono::serde::ts_milliseconds")]
746            event_time: DateTime<Utc>,
747            event_type: EventType,
748            actor: ActorId,
749            resource: ResourceId,
750            outcome: Outcome,
751            metadata: Vec<u8>,
752            attestation: Attestation,
753        }
754
755        let key = test_key();
756        let sig = key.sign(b"test");
757
758        let t = TestEvent {
759            event_time: Utc::now(),
760            event_type: EventType::Push {
761                force: false,
762                commits: 1,
763            },
764            actor: ActorId::new(key.public_key(), ActorKind::User),
765            resource: ResourceId::new(ResourceKind::Repository, "myrepo"),
766            outcome: Outcome::Success,
767            metadata: vec![],
768            attestation: Attestation::new(key.public_key(), sig),
769        };
770
771        let bytes = bincode::serialize(&t).expect("serialize");
772        println!("TestEvent size: {} bytes", bytes.len());
773        let _restored: TestEvent = bincode::deserialize(&bytes).expect("deserialize");
774    }
775}