gglib_core/ports/council_repository.rs
1//! Port trait for persisting orchestrator runs and events.
2//!
3//! Adapters implement this trait (e.g. [`gglib_db::SqliteCouncilRepository`])
4//! and inject it into the executor via [`crate::domain::council::executor::CouncilConfig`].
5//! The executor uses it to:
6//!
7//! - Create a run record at start-up.
8//! - Update run status on every state transition.
9//! - Append every emitted event to the event log.
10//! - Update the serialised graph whenever its state changes.
11
12use async_trait::async_trait;
13
14use crate::domain::council::run::{CouncilRun, CouncilRunEvent, CouncilRunStatus};
15use crate::ports::RepositoryError;
16
17// =============================================================================
18// CouncilRepositoryPort
19// =============================================================================
20
21/// Persistence operations for orchestrator runs.
22///
23/// All methods return a [`RepositoryError`] on failure. The `append_event`
24/// method is best-effort in the executor: a storage failure is logged but
25/// does NOT abort the run. All other methods that change run status propagate
26/// errors to the executor.
27#[async_trait]
28pub trait CouncilRepositoryPort: Send + Sync + 'static {
29 /// Persist a new run record.
30 ///
31 /// The run's `id` MUST be unique; callers generate a UUID v4 before
32 /// calling this method.
33 async fn create_run(&self, run: CouncilRun) -> Result<(), RepositoryError>;
34
35 /// Update the lifecycle status of an existing run.
36 async fn update_run_status(
37 &self,
38 run_id: &str,
39 status: CouncilRunStatus,
40 ) -> Result<(), RepositoryError>;
41
42 /// Replace the serialised task graph for an existing run.
43 ///
44 /// Called after plan approval and after each node completes so that the
45 /// persisted graph stays up to date for resume purposes.
46 async fn update_graph(&self, run_id: &str, graph_json: &str) -> Result<(), RepositoryError>;
47
48 /// Append a single event record to the run's event log.
49 async fn append_event(&self, event: CouncilRunEvent) -> Result<(), RepositoryError>;
50
51 /// Retrieve a single run by id.
52 ///
53 /// Returns `Ok(None)` if the run does not exist.
54 async fn get_run(&self, run_id: &str) -> Result<Option<CouncilRun>, RepositoryError>;
55
56 /// List runs optionally filtered by status.
57 ///
58 /// Results are ordered by `created_at` descending (most recent first).
59 async fn list_runs(
60 &self,
61 status_filter: Option<CouncilRunStatus>,
62 ) -> Result<Vec<CouncilRun>, RepositoryError>;
63
64 /// Return all events for a run in sequence order.
65 async fn list_events(&self, run_id: &str) -> Result<Vec<CouncilRunEvent>, RepositoryError>;
66
67 /// Mark all runs currently in [`CouncilRunStatus::Running`] as
68 /// [`CouncilRunStatus::Interrupted`].
69 ///
70 /// Called once on application boot to handle the case where a process
71 /// was killed mid-execution.
72 async fn mark_interrupted_runs(&self) -> Result<u64, RepositoryError>;
73
74 /// Delete all events for a run whose `wave_index` is **strictly greater
75 /// than** `wave_index`.
76 ///
77 /// Used by the Phase M rewind endpoint to truncate the event log back to
78 /// the state it was in at the end of the specified wave so the run can be
79 /// re-executed from that point.
80 async fn truncate_events_after_wave(
81 &self,
82 run_id: &str,
83 wave_index: u32,
84 ) -> Result<(), RepositoryError>;
85}