theater 0.3.14

A WebAssembly actor system for AI agents
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
//! # Event Chain System
//!
//! The `chain` module provides Theater's core event tracking and verification system,
//! implementing an append-only log that records all actions and state changes within an actor.
//! This creates a verifiable history of an actor's execution that can be used for debugging,
//! auditing, and recovery.
//!
//! ## Core Features
//!
//! * **Immutable Event Logging**: All actor actions are recorded as immutable events in a chain
//! * **Cryptographic Verification**: Events are linked with cryptographic hashes for integrity
//! * **State Reconstruction**: An actor's state can be reconstructed by replaying its chain
//! * **Event Serialization**: Events can be stored, transmitted, and restored
//! * **Content-Addressed Storage**: Events are identified by their content hash
//!
//! ## Architecture
//!
//! The module is built around these key components:
//!
//! * `ChainEvent`: A single immutable event in the chain with cryptographic hash linking
//! * `StateChain`: Collection of events representing the complete execution history of an actor
//! * `ChainEventData`: Typed event data with specific payload structures for different event types
//!
//! ## Security
//!
//! The event chain system uses cryptographic hashes to ensure the integrity of the event history,
//! making it possible to detect any tampering or corruption. Each event links to its parent, creating
//! a tamper-evident chain of custody for all actor state changes.

mod chain_reader;
mod chain_writer;
pub mod format;

pub use chain_reader::ChainReader;
pub use chain_writer::{ChainWriter, RunMeta};

use std::fmt;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};

use anyhow::Result;
use console::style;
use serde::{Deserialize, Serialize};
use tokio::sync::broadcast;
use tokio::sync::mpsc::Sender;
use tracing::{debug, info, warn};
use wasmtime::component::{ComponentType, Lift, Lower};

use crate::events::ChainEventData;
use crate::messages::TheaterCommand;
use crate::store::ContentRef;
use crate::TheaterId;
use theater_chain::event::EventType;

/// Wrapper type for replay chain events stored in ActorStore extensions.
/// Used by handlers like WasiHttpHandler to detect replay mode and access recorded events.
#[derive(Debug, Clone)]
pub struct HttpReplayChain(pub Vec<ChainEvent>);

impl HttpReplayChain {
    /// Get events filtered by event type.
    pub fn events_by_type(&self, event_type: &str) -> Vec<&ChainEvent> {
        self.0
            .iter()
            .filter(|e| e.event_type == event_type)
            .collect()
    }

    /// Get all HTTP incoming handler events.
    pub fn http_incoming_events(&self) -> Vec<&ChainEvent> {
        self.events_by_type("wasi:http/incoming-handler@0.2.0/handle")
    }
}

/// # Chain Event
///
/// `ChainEvent` represents a single immutable event in an actor's execution history.
/// Each event is cryptographically linked to its parent through hash references,
/// forming a tamper-evident chain of events that can be verified for integrity.
///
/// ## Purpose
///
/// This struct is the core building block of the audit system in Theater. It captures
/// a specific action or state change, stores the relevant data, and maintains the
/// cryptographic linking that ensures the integrity of the event history. Events are
/// content-addressed, meaning they are identified by a hash of their content.
///
/// ## Example
///
/// ```rust
/// use theater::chain::ChainEvent;
///
/// // Create an event
/// let data = vec![1, 2, 3, 4]; // Example binary data
/// let event = ChainEvent {
///     hash: vec![],           // Will be filled by the system
///     parent_hash: None,      // This is a root event with no parent
///     event_type: "example".to_string(),
///     data,
/// };
/// ```
///
/// ## Security
///
/// The hash of each event is calculated based on its content, and each event references
/// its parent's hash. This creates a chain of cryptographic links that makes it impossible
/// to modify any event without breaking the chain, allowing for verification of the
/// entire history.
///
/// ## Implementation Notes
///
/// Events are serialized to JSON for storage and transmission. The `Display` implementation
/// provides a human-readable representation of the event, making events easier to read in logs
/// and debugging output.
#[derive(Debug, Clone, Serialize, Deserialize, ComponentType, Lift, Lower, Eq)]
#[component(record)]
pub struct ChainEvent {
    /// Cryptographic hash of this event's content, used as its identifier.
    /// This is calculated based on all other fields except the hash itself.
    pub hash: Vec<u8>,
    /// Hash of the parent event, or None if this is the first event in the chain.
    /// This creates the cryptographic linking between events.
    #[component(name = "parent-hash")]
    pub parent_hash: Option<Vec<u8>>,
    /// Type identifier for the event, used to categorize and filter events.
    /// Common types include "state_change", "message", "http_request", etc.
    #[component(name = "event-type")]
    pub event_type: String,
    /// The actual payload of the event, serialized as JSON bytes.
    pub data: Vec<u8>,
}

impl ChainEvent {}

impl fmt::Display for ChainEvent {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // Format hash as short hex string (first 7 characters)
        let hash_str = hex::encode(&self.hash);
        let short_hash = if hash_str.len() > 7 {
            &hash_str[0..7]
        } else {
            &hash_str
        };

        // Format parent hash if it exists
        let parent_str = match &self.parent_hash {
            Some(ph) => {
                let ph_str = hex::encode(ph);
                if ph_str.len() > 7 {
                    format!("(parent: {}...)", &ph_str[0..7])
                } else {
                    format!("(parent: {})", ph_str)
                }
            }
            None => "(root)".to_string(),
        };

        // Format data: try CGRF decode first, then JSON, then raw preview
        let content = if let Ok(value) = packr::abi::decode(&self.data) {
            format!("{}", value)
        } else if let Ok(text) = std::str::from_utf8(&self.data) {
            let preview = if text.len() > 80 {
                format!("{}...", &text[0..77])
            } else {
                text.to_string()
            };
            format!("'{}'", preview)
        } else {
            format!("{} bytes", self.data.len())
        };

        write!(
            f,
            "Event[{}] {} {} {}",
            short_hash,
            parent_str,
            style(&self.event_type).cyan(),
            content
        )
    }
}

impl EventType for ChainEvent {
    fn event_type(&self) -> String {
        self.event_type.clone()
    }

    fn len(&self) -> usize {
        self.data.len()
    }
}

impl PartialEq for ChainEvent {
    fn eq(&self, other: &Self) -> bool {
        self.hash == other.hash
    }
}

impl std::hash::Hash for ChainEvent {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.hash.hash(state);
    }
}

/// # State Chain
///
/// `StateChain` represents the complete execution history of an actor as an ordered
/// sequence of events. It maintains the append-only log of events and provides
/// methods for adding new events, verifying the chain's integrity, and accessing
/// the event history.
///
/// ## Purpose
///
/// This struct serves as the main interface for an actor's event history. It enforces
/// the append-only nature of the event log, handles the cryptographic linking between
/// events, and provides an API for working with the event history. Each actor in the
/// Theater system has its own associated StateChain.
///
/// ## Example
///
/// ```rust
/// use theater::chain::StateChain;
/// use theater::id::TheaterId;
/// use tokio::sync::mpsc;
///
/// async fn example() {
///     // Create channels for theater commands
///     let (theater_tx, _) = mpsc::channel(100);
///
///     // Create a new state chain for an actor
///     let actor_id = TheaterId::generate();
///     let chain = StateChain::new(actor_id, theater_tx);
///
///     // Verify the chain integrity
///     assert!(chain.verify());
/// }
/// ```
///
/// ## Security
///
/// The state chain enforces the append-only property and maintains the cryptographic
/// linking between events, ensuring that the entire history can be verified for
/// integrity. The verification process checks that each event properly links to its
/// parent and that the content hashes match.
///
/// ## Implementation Notes
///
/// When a new event is added to the chain, it's also sent to the Theater runtime
/// via a channel. This allows the runtime to monitor and react to events across
/// all actors in the system. The state chain can also be persisted to disk for
/// long-term storage or debugging.
#[derive(Clone, Serialize)]
pub struct StateChain {
    /// The ordered sequence of events in this chain, from oldest to newest.
    events: Vec<ChainEvent>,
    /// Hash of the most recent event in the chain, or None if the chain is empty.
    current_hash: Option<Vec<u8>>,
    /// Channel for sending events to the Theater runtime.
    #[serde(skip)]
    theater_tx: Sender<TheaterCommand>,
    /// The identifier of the actor that owns this chain.
    #[serde(skip)]
    actor_id: TheaterId,
    /// Run log for streaming events to disk.
    #[serde(skip)]
    chain_writer: Arc<Mutex<Option<ChainWriter>>>,
    /// Broadcast channel for direct event subscription.
    /// Handlers can subscribe to this channel to receive events as they're recorded.
    #[serde(skip)]
    event_broadcast: broadcast::Sender<ChainEvent>,
}

impl fmt::Debug for StateChain {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("StateChain")
            .field("events", &self.events)
            .field("current_hash", &self.current_hash)
            .field("actor_id", &self.actor_id)
            .finish()
    }
}

impl StateChain {
    /// Creates a new empty state chain for an actor.
    ///
    /// ## Purpose
    ///
    /// This constructor initializes a new state chain for an actor, establishing
    /// the event history tracking for that actor. The chain starts empty with no
    /// events, and will build up as the actor performs actions.
    ///
    /// ## Parameters
    ///
    /// * `actor_id` - The identifier of the actor that owns this chain
    /// * `theater_tx` - Channel for sending events to the Theater runtime
    ///
    /// ## Returns
    ///
    /// A new empty `StateChain` instance ready to track events for the specified actor
    ///
    /// ## Example
    ///
    /// ```rust
    /// # use theater::chain::StateChain;
    /// # use theater::id::TheaterId;
    /// # use tokio::sync::mpsc;
    /// #
    /// # fn example() {
    /// let (theater_tx, _) = mpsc::channel(100);
    /// let actor_id = TheaterId::generate();
    ///
    /// // Create a new empty state chain
    /// let chain = StateChain::new(actor_id, theater_tx);
    /// # }
    /// ```
    pub fn new(
        actor_id: TheaterId,
        theater_tx: Sender<TheaterCommand>,
        chain_dir: Option<&PathBuf>,
    ) -> Self {
        // Create run log for streaming events to disk
        let chain_writer = match ChainWriter::new(&actor_id, chain_dir) {
            Ok(log) => {
                info!("Created run log at {:?}", log.path());
                Some(log)
            }
            Err(e) => {
                warn!("Failed to create run log: {}", e);
                None
            }
        };

        // Create broadcast channel for direct event subscription
        // Buffer size of 1024 should be enough for most use cases
        let (event_broadcast, _) = broadcast::channel(1024);

        Self {
            events: Vec::new(),
            current_hash: None,
            theater_tx,
            actor_id,
            chain_writer: Arc::new(Mutex::new(chain_writer)),
            event_broadcast,
        }
    }

    /// Sets the run metadata (actor name, manifest path, etc.)
    pub fn set_run_meta(&self, actor_name: Option<String>, manifest_path: Option<String>) {
        let meta = RunMeta {
            actor_id: self.actor_id.to_string(),
            actor_name,
            manifest_path,
            started_at: std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap_or_default()
                .as_secs(),
        };

        if let Err(e) = ChainWriter::write_meta(&self.actor_id, &meta) {
            warn!("Failed to write run meta: {}", e);
        }
    }

    /// Adds a new typed event to the chain.
    ///
    /// ## Purpose
    ///
    /// This method adds a new event to the chain, handling the cryptographic linking
    /// and content-addressed storage aspects. It ensures the event is properly linked
    /// to its parent, calculates its hash, and notifies the Theater runtime about
    /// the new event.
    ///
    /// ## Parameters
    ///
    /// * `event_data` - The typed event data to add to the chain
    ///
    /// ## Returns
    ///
    /// * `Ok(ChainEvent)` - The newly created and added event with its hash
    /// * `Err(serde_json::Error)` - If there was an error serializing the event
    ///
    /// ## Example
    ///
    /// ```rust
    /// # use theater::chain::StateChain;
    /// # use theater::events::{ChainEventData, ChainEventPayload};
    /// # use theater::events::wasm::WasmEventData;
    /// # use theater::id::TheaterId;
    /// # use tokio::sync::mpsc;
    /// # use anyhow::Result;
    /// #
    /// # async fn example() -> Result<()> {
    /// # let (theater_tx, _) = mpsc::channel(100);
    /// # let actor_id = TheaterId::generate();
    /// # let mut chain = StateChain::new(actor_id, theater_tx);
    /// #
    /// // Create event data
    /// let event_data = ChainEventData {
    ///     event_type: "state_change".to_string(),
    ///     data: ChainEventPayload::Wasm(WasmEventData::WasmCall {
    ///         function_name: "state_change".to_string(),
    ///         params: vec![],
    ///     }),
    /// };
    ///
    /// // Add the event to the chain
    /// let event = chain.add_typed_event(event_data)?;
    /// println!("Added event with hash: {:?}", event.hash);
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// ## Security
    ///
    /// This method enforces the append-only nature of the event chain by automatically
    /// linking each new event to the previous one through hash references. The hash of
    /// each event is calculated from its content, creating a content-addressed system
    /// that allows for verification of the entire history.
    ///
    /// ## Implementation Notes
    ///
    /// The method spawns an asynchronous task to notify the Theater runtime about the
    /// new event. This is done asynchronously to avoid blocking the caller waiting for
    /// the notification to be delivered.
    pub fn add_typed_event(
        &mut self,
        event_data: ChainEventData,
    ) -> Result<ChainEvent, serde_json::Error> {
        // Create initial event structure without hash
        let mut event = event_data.to_chain_event(self.current_hash.clone());

        // Store the event data in the content store
        let serialized_event = serde_json::to_vec(&event)?;
        let content_ref = ContentRef::from_content(&serialized_event);

        // Get the hash from ContentRef and use it as the event hash
        let hash_bytes = hex::decode(content_ref.hash()).unwrap();
        event.hash = hash_bytes.clone();

        // Now that we have the hash, store the updated event in memory
        self.events.push(event.clone());
        self.current_hash = Some(event.hash.clone());

        // Write to run log for crash recovery
        if let Ok(mut guard) = self.chain_writer.lock() {
            if let Some(ref mut log) = *guard {
                if let Err(e) = log.append(&event) {
                    warn!("Failed to append to run log: {}", e);
                }
            }
        }

        // notify the runtime of the event synchronously to preserve ordering
        if let Err(e) = self.theater_tx.try_send(TheaterCommand::NewEvent {
            actor_id: self.actor_id,
            event: event.clone(),
        }) {
            warn!("Failed to send event notification: {}", e);
        }

        // Broadcast to direct subscribers (e.g., ReplayHandler for streaming verification)
        // Ignore send errors - they just mean no active subscribers
        let _ = self.event_broadcast.send(event.clone());

        // I am removing storing the events in the content store for now because they are
        // accumulating too quickly. I need to build out the store local to each actor to store its
        // event that is cleaned up when the actor dies.
        /*
        let head_label = format!("{}:chain-head", self.actor_id);
        let content_store = self.content_store.clone();
        let prev_content_ref = content_ref.clone();

        tokio::spawn(async move {
            let stored_content_ref = content_store.store(serialized_event).await.unwrap();
            if stored_content_ref.hash() != prev_content_ref.hash() {
                tracing::error!(
                    "Content store hash mismatch: expected {}, got {}",
                    prev_content_ref.hash(),
                    stored_content_ref.hash()
                );
            }
            // Update chain head
            let _ = content_store
                .replace_at_label(head_label, stored_content_ref)
                .await;
        });
        */

        debug!(
            "Stored event {} in content store for actor {}",
            content_ref.hash(),
            self.actor_id
        );

        Ok(event)
    }

    /// Verifies the integrity of the entire event chain.
    ///
    /// ## Purpose
    ///
    /// This method validates the cryptographic integrity of the entire event chain
    /// by recalculating each event's hash and ensuring it matches the stored hash,
    /// and by verifying that each event correctly references its parent. This allows
    /// for detecting any tampering with the event history.
    ///
    /// ## Returns
    ///
    /// * `true` - If the chain is valid and all links are intact
    /// * `false` - If any event's hash is invalid or parent links are broken
    ///
    /// ## Example
    ///
    /// ```rust
    /// # use theater::chain::StateChain;
    /// # use theater::id::TheaterId;
    /// # use tokio::sync::mpsc;
    /// #
    /// # fn example() {
    /// # let (theater_tx, _) = mpsc::channel(100);
    /// # let actor_id = TheaterId::generate();
    /// # let chain = StateChain::new(actor_id, theater_tx);
    /// #
    /// // Check if the chain is valid
    /// if chain.verify() {
    ///     println!("Chain integrity verified");
    /// } else {
    ///     println!("Chain verification failed - possible tampering detected");
    /// }
    /// # }
    /// ```
    ///
    /// ## Security
    ///
    /// This method is a critical security feature that enables detecting any
    /// tampering or corruption in the event history. It recalculates each event's
    /// hash based on its content and checks it against the stored hash, making it
    /// impossible to modify an event without detection.
    ///
    /// ## Implementation Notes
    ///
    /// The verification process ensures that:
    /// 1. Each event's hash matches its content
    /// 2. Each event's parent hash matches the previous event's hash
    /// 3. The chain of parent references is unbroken
    pub fn verify(&self) -> bool {
        let mut prev_hash = None;

        for event in &self.events {
            // Create a temporary event with everything except the hash
            let temp_event = ChainEvent {
                hash: vec![],
                parent_hash: prev_hash.clone(),
                event_type: event.event_type.clone(),
                data: event.data.clone(),
            };

            // Serialize the event (just like in add_typed_event)
            let serialized_event = match serde_json::to_vec(&temp_event) {
                Ok(data) => data,
                Err(_) => return false,
            };

            // Calculate hash using ContentRef (same as in add_typed_event)
            let content_ref = ContentRef::from_content(&serialized_event);
            let hash_bytes = match hex::decode(content_ref.hash()) {
                Ok(bytes) => bytes,
                Err(_) => return false,
            };

            // Verify this hash matches the stored hash
            if hash_bytes != event.hash {
                return false;
            }

            prev_hash = Some(event.hash.clone());
        }

        true
    }

    /// Saves the entire state chain to a JSON file.
    ///
    /// ## Purpose
    ///
    /// This method serializes the state chain to a JSON file, allowing the event
    /// history to be persisted for later analysis, debugging, or backup purposes.
    /// The saved file can be loaded for offline verification or examination.
    ///
    /// ## Parameters
    ///
    /// * `path` - Path where the JSON file will be written
    ///
    /// ## Returns
    ///
    /// * `Ok(())` - If the file was successfully written
    /// * `Err(anyhow::Error)` - If there was an error serializing or writing the file
    ///
    /// ## Example
    ///
    /// ```rust
    /// # use theater::chain::StateChain;
    /// # use theater::id::TheaterId;
    /// # use tokio::sync::mpsc;
    /// # use std::path::Path;
    /// # use anyhow::Result;
    /// #
    /// # fn example() -> Result<()> {
    /// # let (theater_tx, _) = mpsc::channel(100);
    /// # let actor_id = TheaterId::generate();
    /// # let chain = StateChain::new(actor_id, theater_tx);
    /// #
    /// // Save the chain to a file
    /// chain.save_to_file(Path::new("actor_events.json"))?;
    /// println!("Chain saved to file");
    /// # Ok(())
    /// # }
    /// ```
    pub fn save_to_file(&self, path: &Path) -> Result<()> {
        let json = serde_json::to_string_pretty(self)?;
        std::fs::write(path, json)?;
        Ok(())
    }

    pub fn save_chain(&self) -> Result<()> {
        // this will be different than the save_to_file method, in that we are going to save each
        // of the events at their hash in the THEATER_DIR/events/{event_id} path, and then the chain (the
        // head of the chain) will be saved at the actor id in the THEATER_DIR/chains/{actor_id} path

        let theater_dir = std::env::var("THEATER_HOME").expect(
            "THEATER_HOME environment variable must be set to the directory where events are stored",
        );
        let events_dir = format!("{}/events", theater_dir);
        let chains_dir = format!("{}/chains", theater_dir);
        std::fs::create_dir_all(&events_dir)?;
        std::fs::create_dir_all(&chains_dir)?;
        let chain_path = format!("{}/{}", chains_dir, self.actor_id);

        // Save each event to its own file
        for event in &self.events {
            let event_path = format!("{}/{}", events_dir, hex::encode(&event.hash));
            std::fs::write(
                event_path,
                serde_json::to_string(event).expect("Failed to serialize event"),
            )
            .expect("Failed to write event file");
        }

        // Save the chain to a file
        std::fs::write(
            chain_path,
            serde_json::to_string(&self.current_hash).expect("Failed to serialize current hash"),
        )
        .expect("Failed to write chain file");
        Ok(())
    }

    /// Gets the most recent event in the chain.
    ///
    /// ## Purpose
    ///
    /// This method provides access to the most recent event in the chain, which
    /// represents the current state of the actor's execution history. It's useful
    /// for quickly accessing the latest event without having to traverse the entire
    /// chain.
    ///
    /// ## Returns
    ///
    /// * `Some(&ChainEvent)` - Reference to the most recent event, if the chain is not empty
    /// * `None` - If the chain is empty and there are no events
    ///
    /// ## Example
    ///
    /// ```rust
    /// # use theater::chain::StateChain;
    /// # use theater::id::TheaterId;
    /// # use tokio::sync::mpsc;
    /// #
    /// # fn example() {
    /// # let (theater_tx, _) = mpsc::channel(100);
    /// # let actor_id = TheaterId::generate();
    /// # let chain = StateChain::new(actor_id, theater_tx);
    /// #
    /// // Get the most recent event
    /// if let Some(last_event) = chain.get_last_event() {
    ///     println!("Last event type: {}", last_event.event_type);
    /// } else {
    ///     println!("No events in the chain yet");
    /// }
    /// # }
    /// ```
    pub fn get_last_event(&self) -> Option<&ChainEvent> {
        self.events.last()
    }

    /// Gets all events in the chain as an ordered slice.
    ///
    /// ## Purpose
    ///
    /// This method provides access to the complete event history in chronological
    /// order, allowing for traversal, analysis, or filtering of events. This is
    /// useful for debugging, auditing, or reconstructing actor state.
    ///
    /// ## Returns
    ///
    /// * `&[ChainEvent]` - A slice of all events in the chain, from oldest to newest
    ///
    /// ## Example
    ///
    /// ```rust
    /// # use theater::chain::StateChain;
    /// # use theater::id::TheaterId;
    /// # use tokio::sync::mpsc;
    /// #
    /// # fn example() {
    /// # let (theater_tx, _) = mpsc::channel(100);
    /// # let actor_id = TheaterId::generate();
    /// # let chain = StateChain::new(actor_id, theater_tx);
    /// #
    /// // Get all events and count them by type
    /// let events = chain.get_events();
    /// println!("Total events: {}", events.len());
    ///
    /// // Count events by type
    /// let mut type_counts = std::collections::HashMap::new();
    /// for event in events {
    ///     *type_counts.entry(event.event_type.clone()).or_insert(0) += 1;
    /// }
    ///
    /// // Print counts
    /// for (event_type, count) in type_counts {
    ///     println!("{}: {}", event_type, count);
    /// }
    /// # }
    /// ```
    pub fn get_events(&self) -> &[ChainEvent] {
        &self.events
    }

    /// Subscribe to events as they are recorded to the chain.
    ///
    /// Returns a broadcast receiver that will receive each event as it's added.
    /// This is useful for streaming verification during replay.
    ///
    /// ## Returns
    ///
    /// A broadcast receiver that receives `ChainEvent` values as they're recorded.
    ///
    /// ## Example
    ///
    /// ```rust,ignore
    /// let mut event_rx = chain.subscribe();
    /// tokio::spawn(async move {
    ///     while let Ok(event) = event_rx.recv().await {
    ///         println!("Event recorded: {}", event.event_type);
    ///     }
    /// });
    /// ```
    pub fn subscribe(&self) -> broadcast::Receiver<ChainEvent> {
        self.event_broadcast.subscribe()
    }
}