ralph_workflow/reducer/event.rs
1//! Pipeline event types for reducer architecture.
2//!
3//! Defines all possible events that can occur during pipeline execution.
4//! Each event represents a state transition that the reducer handles.
5//!
6//! # Event Categories
7//!
8//! Events are organized into logical categories for type-safe routing to
9//! category-specific reducers. Each category has a dedicated inner enum:
10//!
11//! - [`LifecycleEvent`] - Pipeline start/stop/resume
12//! - [`PlanningEvent`] - Plan generation events
13//! - [`DevelopmentEvent`] - Development iteration and continuation events
14//! - [`ReviewEvent`] - Review pass and fix attempt events
15//! - [`AgentEvent`] - Agent invocation and chain management events
16//! - [`RebaseEvent`] - Git rebase operation events
17//! - [`CommitEvent`] - Commit generation events
18//!
19//! The main [`PipelineEvent`] enum wraps these category enums to enable
20//! type-safe dispatch in the reducer.
21
22use crate::agents::AgentRole;
23use crate::reducer::state::{DevelopmentStatus, MaterializedPromptInput, PromptInputKind};
24use serde::{Deserialize, Serialize};
25use std::path::PathBuf;
26
27// ============================================================================
28// Event Category Enums
29// ============================================================================
30
31/// Pipeline lifecycle events (start, stop, resume).
32///
33/// These events control the overall pipeline execution lifecycle,
34/// distinct from phase-specific transitions. Use these for:
35///
36/// - Starting or resuming a pipeline run
37/// - Completing a successful pipeline execution
38///
39/// # When to Use
40///
41/// - `Started`: When a fresh pipeline run begins
42/// - `Resumed`: When resuming from a checkpoint
43/// - `Completed`: When all phases complete successfully
44///
45/// # ⚠️ FROZEN - DO NOT ADD VARIANTS ⚠️
46///
47/// This enum is **FROZEN**. Adding new variants is **PROHIBITED**.
48///
49/// ## Why is this frozen?
50///
51/// Lifecycle events control pipeline flow (start/stop/completion). Allowing effect
52/// handlers to emit new lifecycle events would violate the core architectural principle:
53/// **handlers describe what happened; reducers decide what happens next.**
54///
55/// ## What to do instead
56///
57/// If you need to express new observations or failures:
58///
59/// 1. **Reuse existing phase/category events** - Use `PlanningEvent`, `DevelopmentEvent`,
60/// `ReviewEvent`, `CommitEvent`, etc. to describe what happened within that phase.
61/// Example: `PlanningEvent::PlanXmlMissing` instead of creating a generic "Aborted" event.
62///
63/// 2. **Return errors from the event loop** - For truly unrecoverable failures (permission
64/// errors, invariant violations), return `Err` from the effect handler. The outer runner
65/// will handle termination, not the reducer.
66///
67/// 3. **Handle in orchestration** - Some conditions don't need events at all and can be
68/// handled in the effect handler or runner logic.
69///
70/// ## Enforcement
71///
72/// The freeze policy is enforced by the `lifecycle_event_is_frozen` test in this module,
73/// which will fail to compile if new variants are added. This is intentional.
74#[derive(Clone, Serialize, Deserialize, Debug)]
75pub enum LifecycleEvent {
76 /// Pipeline execution started fresh (not from checkpoint).
77 Started,
78 /// Pipeline execution resumed from a previous state.
79 Resumed {
80 /// Whether this resume is from a persisted checkpoint.
81 from_checkpoint: bool,
82 },
83 /// Pipeline execution completed successfully.
84 Completed,
85}
86
87/// Planning phase events.
88///
89/// Events related to plan generation and validation within the Planning phase.
90/// The planning phase generates a plan for the current development iteration.
91///
92/// # State Transitions
93///
94/// - `PhaseStarted`: Sets phase to Planning
95/// - `GenerationCompleted(valid=true)`: Transitions to Development
96/// - `GenerationCompleted(valid=false)`: Stays in Planning for retry
97/// - `PhaseCompleted`: Transitions to Development
98#[derive(Clone, Serialize, Deserialize, Debug)]
99pub enum PlanningEvent {
100 /// Planning phase has started.
101 PhaseStarted,
102 /// Planning phase completed, ready to proceed.
103 PhaseCompleted,
104 /// Planning prompt prepared for an iteration.
105 PromptPrepared {
106 /// The iteration number this plan is for.
107 iteration: u32,
108 },
109 /// Planning agent invoked for an iteration.
110 AgentInvoked {
111 /// The iteration number this plan is for.
112 iteration: u32,
113 },
114 /// Planning XML extracted for an iteration.
115 PlanXmlExtracted {
116 /// The iteration number this plan is for.
117 iteration: u32,
118 },
119 /// Planning XML missing for an iteration.
120 PlanXmlMissing {
121 /// The iteration number this plan is for.
122 iteration: u32,
123 /// The invalid output attempt count.
124 attempt: u32,
125 },
126 /// Planning XML validated for an iteration.
127 PlanXmlValidated {
128 /// The iteration number this plan is for.
129 iteration: u32,
130 /// Whether the generated plan passed validation.
131 valid: bool,
132 /// Markdown generated from the validated plan XML.
133 markdown: Option<String>,
134 },
135 /// Planning markdown written for an iteration.
136 PlanMarkdownWritten {
137 /// The iteration number this plan is for.
138 iteration: u32,
139 },
140 /// Planning XML archived for an iteration.
141 PlanXmlArchived {
142 /// The iteration number this plan is for.
143 iteration: u32,
144 },
145 /// Planning XML cleaned before invoking the planning agent.
146 PlanXmlCleaned {
147 /// The iteration number this plan is for.
148 iteration: u32,
149 },
150 /// Plan generation completed with validation result.
151 GenerationCompleted {
152 /// The iteration number this plan was for.
153 iteration: u32,
154 /// Whether the generated plan passed validation.
155 valid: bool,
156 },
157
158 /// Output validation failed (missing/empty or otherwise invalid plan output).
159 ///
160 /// Emitted when planning output cannot be validated. The reducer decides
161 /// whether to retry (same agent) or switch agents based on the attempt count.
162 OutputValidationFailed {
163 /// Current iteration number.
164 iteration: u32,
165 /// Current invalid output attempt number.
166 attempt: u32,
167 },
168}
169
170/// Prompt input oversize detection and materialization events.
171///
172/// These events make reducer-visible any transformation that affects the
173/// agent-visible prompt content (inline vs file reference, truncation, etc.).
174#[derive(Clone, Serialize, Deserialize, Debug)]
175pub enum PromptInputEvent {
176 OversizeDetected {
177 phase: PipelinePhase,
178 kind: PromptInputKind,
179 content_id_sha256: String,
180 size_bytes: u64,
181 limit_bytes: u64,
182 policy: String,
183 },
184 PlanningInputsMaterialized {
185 iteration: u32,
186 prompt: MaterializedPromptInput,
187 },
188 DevelopmentInputsMaterialized {
189 iteration: u32,
190 prompt: MaterializedPromptInput,
191 plan: MaterializedPromptInput,
192 },
193 ReviewInputsMaterialized {
194 pass: u32,
195 plan: MaterializedPromptInput,
196 diff: MaterializedPromptInput,
197 },
198 CommitInputsMaterialized {
199 attempt: u32,
200 diff: MaterializedPromptInput,
201 },
202 XsdRetryLastOutputMaterialized {
203 /// Phase that produced the invalid output being retried.
204 phase: PipelinePhase,
205 /// Scope id within the phase (iteration/pass/attempt).
206 scope_id: u32,
207 /// Materialized representation of the last invalid output.
208 last_output: MaterializedPromptInput,
209 },
210 /// A typed error event returned by an effect handler.
211 ///
212 /// Effect handlers surface failures by returning `Err(ErrorEvent::... .into())`.
213 /// The event loop extracts the underlying `ErrorEvent` and re-emits it through
214 /// this existing category so the reducer can decide recovery strategy without
215 /// adding new top-level `PipelineEvent` variants.
216 HandlerError {
217 /// Phase during which the error occurred (best-effort; derived from current state).
218 phase: PipelinePhase,
219 /// The typed error event.
220 error: ErrorEvent,
221 },
222}
223
224#[path = "event/development.rs"]
225mod development;
226pub use development::DevelopmentEvent;
227
228#[path = "event/review.rs"]
229mod review;
230pub use review::ReviewEvent;
231
232#[path = "event/agent.rs"]
233mod agent;
234pub use agent::AgentEvent;
235
236#[path = "event/error.rs"]
237mod error;
238pub use error::ErrorEvent;
239pub use error::WorkspaceIoErrorKind;
240
241/// Rebase operation events.
242///
243/// Events related to git rebase operations including conflict detection
244/// and resolution. Rebase operations can occur at multiple points in the
245/// pipeline (initial and post-review).
246///
247/// # State Machine
248///
249/// ```text
250/// NotStarted -> InProgress -> Conflicted -> InProgress -> Completed
251/// | |
252/// +---------> Skipped <-------+
253/// |
254/// +---------> Failed (resets to NotStarted)
255/// ```
256#[derive(Clone, Serialize, Deserialize, Debug)]
257pub enum RebaseEvent {
258 /// Rebase operation started.
259 Started {
260 /// The rebase phase (initial or post-review).
261 phase: RebasePhase,
262 /// The target branch to rebase onto.
263 target_branch: String,
264 },
265 /// Merge conflict detected during rebase.
266 ConflictDetected {
267 /// The files with conflicts.
268 files: Vec<PathBuf>,
269 },
270 /// Merge conflicts were resolved.
271 ConflictResolved {
272 /// The files that were resolved.
273 files: Vec<PathBuf>,
274 },
275 /// Rebase completed successfully.
276 Succeeded {
277 /// The rebase phase that completed.
278 phase: RebasePhase,
279 /// The new HEAD after rebase.
280 new_head: String,
281 },
282 /// Rebase failed and was reset.
283 Failed {
284 /// The rebase phase that failed.
285 phase: RebasePhase,
286 /// The reason for failure.
287 reason: String,
288 },
289 /// Rebase was aborted and state restored.
290 Aborted {
291 /// The rebase phase that was aborted.
292 phase: RebasePhase,
293 /// The commit that was restored.
294 restored_to: String,
295 },
296 /// Rebase was skipped (e.g., already up to date).
297 Skipped {
298 /// The rebase phase that was skipped.
299 phase: RebasePhase,
300 /// The reason for skipping.
301 reason: String,
302 },
303}
304
305/// Commit generation events.
306///
307/// Events related to commit message generation, validation, and creation.
308/// Commit generation occurs after development iterations and review fixes.
309///
310/// # State Machine
311///
312/// ```text
313/// NotStarted -> Generating -> Generated -> Committed
314/// | |
315/// +--> (retry) --+
316/// |
317/// +--> Skipped
318/// ```
319#[derive(Clone, Serialize, Deserialize, Debug)]
320pub enum CommitEvent {
321 /// Commit message generation started.
322 GenerationStarted,
323 /// Commit diff computed for commit generation.
324 DiffPrepared {
325 /// True when the diff is empty.
326 empty: bool,
327 /// Content identifier (sha256 hex) of the prepared diff content.
328 ///
329 /// This is used to guard against reusing stale materialized inputs when the
330 /// diff content changes across checkpoints or retries.
331 content_id_sha256: String,
332 },
333 /// Commit diff computation failed.
334 DiffFailed {
335 /// The error message for the diff failure.
336 error: String,
337 },
338 /// Commit diff is no longer available and must be recomputed.
339 ///
340 /// This is used for recoverability when `.agent/tmp` artifacts are cleaned between
341 /// checkpoints or when required diff files go missing during resume.
342 DiffInvalidated {
343 /// Reason for invalidation.
344 reason: String,
345 },
346 /// Commit prompt prepared for a commit attempt.
347 PromptPrepared {
348 /// The attempt number.
349 attempt: u32,
350 },
351 /// Commit agent invoked for a commit attempt.
352 AgentInvoked {
353 /// The attempt number.
354 attempt: u32,
355 },
356 /// Commit message XML extracted for a commit attempt.
357 CommitXmlExtracted {
358 /// The attempt number.
359 attempt: u32,
360 },
361 /// Commit message XML missing for a commit attempt.
362 CommitXmlMissing {
363 /// The attempt number.
364 attempt: u32,
365 },
366 /// Commit message XML validated successfully.
367 CommitXmlValidated {
368 /// The generated commit message.
369 message: String,
370 /// The attempt number.
371 attempt: u32,
372 },
373 /// Commit message XML validation failed.
374 CommitXmlValidationFailed {
375 /// The reason for validation failure.
376 reason: String,
377 /// The attempt number.
378 attempt: u32,
379 },
380 /// Commit message XML archived.
381 CommitXmlArchived {
382 /// The attempt number.
383 attempt: u32,
384 },
385 /// Commit message XML cleaned before invoking the commit agent.
386 CommitXmlCleaned {
387 /// The attempt number.
388 attempt: u32,
389 },
390 /// Commit message was generated.
391 MessageGenerated {
392 /// The generated commit message.
393 message: String,
394 /// The attempt number.
395 attempt: u32,
396 },
397 /// Commit message validation failed.
398 MessageValidationFailed {
399 /// The reason for validation failure.
400 reason: String,
401 /// The attempt number that failed.
402 attempt: u32,
403 },
404 /// Commit was created successfully.
405 Created {
406 /// The commit hash.
407 hash: String,
408 /// The commit message used.
409 message: String,
410 },
411 /// Commit generation failed completely.
412 GenerationFailed {
413 /// The reason for failure.
414 reason: String,
415 },
416 /// Commit was skipped (e.g., no changes to commit).
417 Skipped {
418 /// The reason for skipping.
419 reason: String,
420 },
421}
422
423/// Events for AwaitingDevFix phase.
424///
425/// This phase handles pipeline failure remediation by invoking the development
426/// agent to diagnose and fix the root cause before termination.
427#[derive(Clone, Serialize, Deserialize, Debug)]
428pub enum AwaitingDevFixEvent {
429 /// Dev-fix flow was triggered.
430 DevFixTriggered {
431 failed_phase: PipelinePhase,
432 failed_role: AgentRole,
433 },
434 /// Dev-fix flow was skipped (not yet implemented or disabled).
435 DevFixSkipped { reason: String },
436 /// Dev-fix flow completed (may or may not have fixed the issue).
437 DevFixCompleted {
438 success: bool,
439 summary: Option<String>,
440 },
441 /// Dev-fix agent is unavailable (quota/usage limit).
442 DevFixAgentUnavailable {
443 failed_phase: PipelinePhase,
444 reason: String,
445 },
446 /// Completion marker was emitted to filesystem.
447 CompletionMarkerEmitted { is_failure: bool },
448}
449
450// ============================================================================
451// Supporting Types
452// ============================================================================
453
454/// Pipeline phases for checkpoint tracking.
455///
456/// These phases represent the major stages of the Ralph pipeline.
457#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
458pub enum PipelinePhase {
459 Planning,
460 Development,
461 Review,
462 CommitMessage,
463 FinalValidation,
464 /// Finalizing phase for cleanup operations before completion.
465 ///
466 /// This phase handles:
467 /// - Restoring PROMPT.md write permissions
468 /// - Any other cleanup that must go through the effect system
469 Finalizing,
470 Complete,
471 /// Awaiting development agent to fix pipeline failure.
472 ///
473 /// This phase occurs when the pipeline encounters a terminal failure condition
474 /// (e.g., agent chain exhausted) but before transitioning to Interrupted. It
475 /// signals that the development agent should be invoked to diagnose and fix
476 /// the failure root cause.
477 ///
478 /// ## Failure Handling Flow
479 ///
480 /// 1. ErrorEvent::AgentChainExhausted occurs in any phase
481 /// 2. Reducer transitions state to AwaitingDevFix
482 /// 3. Orchestration determines Effect::TriggerDevFixFlow
483 /// 4. Handler executes TriggerDevFixFlow:
484 /// a. Writes completion marker to .agent/tmp/completion_marker (failure status)
485 /// b. Emits DevFixTriggered event
486 /// c. Dispatches dev-fix agent
487 /// d. Emits DevFixCompleted event
488 /// e. Emits CompletionMarkerEmitted event
489 /// 5. DevFixTriggered/DevFixCompleted events: no state change (stays in AwaitingDevFix)
490 /// 6. CompletionMarkerEmitted event: transitions to Interrupted
491 /// 7. Orchestration determines Effect::SaveCheckpoint for Interrupted
492 /// 8. Handler saves checkpoint, increments checkpoint_saved_count
493 /// 9. Event loop recognizes is_complete() == true and exits successfully
494 ///
495 /// ## Event Loop Termination Guarantees
496 ///
497 /// The event loop MUST NOT exit with completed=false when in AwaitingDevFix phase.
498 /// The failure handling flow is designed to always complete with:
499 /// - Completion marker written to filesystem
500 /// - State transitioned to Interrupted
501 /// - Checkpoint saved (checkpoint_saved_count > 0)
502 /// - Event loop returning completed=true
503 ///
504 /// If the event loop exits with completed=false from AwaitingDevFix, this indicates
505 /// a critical bug (e.g., max iterations reached before checkpoint saved).
506 ///
507 /// ## Completion Marker Requirement
508 ///
509 /// The completion marker MUST be written before transitioning to Interrupted.
510 /// This ensures external orchestration systems (CI, monitoring) can detect
511 /// pipeline termination even if the event loop exits unexpectedly.
512 ///
513 /// ## Agent Chain Exhaustion Handling
514 ///
515 /// When in AwaitingDevFix phase with an exhausted agent chain, orchestration
516 /// falls through to phase-specific logic (TriggerDevFixFlow) instead of reporting
517 /// exhaustion again. This prevents infinite loops where exhaustion is reported
518 /// repeatedly.
519 ///
520 /// Transitions:
521 /// - From: Any phase where AgentChainExhausted error occurs
522 /// - To: Interrupted (after dev-fix attempt completes or fails)
523 AwaitingDevFix,
524 Interrupted,
525}
526
527impl std::fmt::Display for PipelinePhase {
528 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
529 match self {
530 Self::Planning => write!(f, "Planning"),
531 Self::Development => write!(f, "Development"),
532 Self::Review => write!(f, "Review"),
533 Self::CommitMessage => write!(f, "Commit Message"),
534 Self::FinalValidation => write!(f, "Final Validation"),
535 Self::Finalizing => write!(f, "Finalizing"),
536 Self::Complete => write!(f, "Complete"),
537 Self::AwaitingDevFix => write!(f, "Awaiting Dev Fix"),
538 Self::Interrupted => write!(f, "Interrupted"),
539 }
540 }
541}
542
543/// Pipeline events representing all state transitions.
544///
545/// Events are organized into logical categories for type-safe routing
546/// to category-specific reducers. Each category has a dedicated inner enum.
547///
548/// # Event Categories
549///
550/// - `Lifecycle` - Pipeline start/stop/resume
551/// - `Planning` - Plan generation events
552/// - `Development` - Development iteration and continuation events
553/// - `Review` - Review pass and fix attempt events
554/// - `Agent` - Agent invocation and chain management events
555/// - `Rebase` - Git rebase operation events
556/// - `Commit` - Commit generation events
557/// - Miscellaneous events (context cleanup, checkpoints, finalization)
558///
559/// # Example
560///
561/// ```ignore
562/// // Type-safe event construction
563/// let event = PipelineEvent::Agent(AgentEvent::InvocationStarted {
564/// role: AgentRole::Developer,
565/// agent: "claude".to_string(),
566/// model: Some("opus".to_string()),
567/// });
568///
569/// // Pattern matching routes to category handlers
570/// match event {
571/// PipelineEvent::Agent(agent_event) => reduce_agent_event(state, agent_event),
572/// // ...
573/// }
574/// ```
575///
576/// # ⚠️ FROZEN - DO NOT ADD VARIANTS ⚠️
577///
578/// This enum is **FROZEN**. Adding new top-level variants is **PROHIBITED**.
579///
580/// ## Why is this frozen?
581///
582/// `PipelineEvent` provides category-based event routing to the reducer. The existing
583/// categories (Lifecycle, Planning, Development, Review, etc.) cover all pipeline phases.
584/// Adding new top-level variants would indicate a missing architectural abstraction or
585/// an attempt to bypass phase-specific event handling.
586///
587/// ## What to do instead
588///
589/// 1. **Express events through existing categories** - Use the category enums:
590/// - `PlanningEvent` for planning phase observations
591/// - `DevelopmentEvent` for development phase observations
592/// - `ReviewEvent` for review phase observations
593/// - `CommitEvent` for commit generation observations
594/// - `AgentEvent` for agent invocation observations
595/// - `RebaseEvent` for rebase state machine transitions
596///
597/// 2. **Return errors for unrecoverable failures** - Don't create events for conditions
598/// that should terminate the pipeline. Return `Err` from the effect handler instead.
599///
600/// 3. **Extend category enums if needed** - If you truly need a new event within an
601/// existing phase, add it to that phase's category enum (e.g., add a new variant to
602/// `ReviewEvent` rather than creating a new top-level category).
603///
604/// ## Enforcement
605///
606/// The freeze policy is enforced by the `pipeline_event_is_frozen` test in this module,
607/// which will fail to compile if new variants are added. This is intentional.
608///
609/// See `LifecycleEvent` documentation for additional context on the freeze policy rationale.
610#[derive(Clone, Serialize, Deserialize, Debug)]
611pub enum PipelineEvent {
612 /// Pipeline lifecycle events (start, stop, resume).
613 Lifecycle(LifecycleEvent),
614 /// Planning phase events.
615 Planning(PlanningEvent),
616 /// Development phase events.
617 Development(DevelopmentEvent),
618 /// Review phase events.
619 Review(ReviewEvent),
620 /// Prompt input materialization events.
621 PromptInput(PromptInputEvent),
622 /// Agent invocation and chain events.
623 Agent(AgentEvent),
624 /// Rebase operation events.
625 Rebase(RebaseEvent),
626 /// Commit generation events.
627 Commit(CommitEvent),
628 /// AwaitingDevFix phase events.
629 AwaitingDevFix(AwaitingDevFixEvent),
630
631 // ========================================================================
632 // Miscellaneous events that don't fit a category
633 // ========================================================================
634 /// Context cleanup completed.
635 ContextCleaned,
636 /// Checkpoint saved.
637 CheckpointSaved {
638 /// What triggered the checkpoint save.
639 trigger: CheckpointTrigger,
640 },
641 /// Finalization phase started.
642 FinalizingStarted,
643 /// PROMPT.md permissions restored.
644 PromptPermissionsRestored,
645 /// Loop recovery triggered (tight loop detected and broken).
646 LoopRecoveryTriggered {
647 /// String representation of the detected loop.
648 detected_loop: String,
649 /// Number of times the loop was repeated.
650 loop_count: u32,
651 },
652}
653
654// ============================================================================
655// Convenience Constructors
656// ============================================================================
657
658#[path = "event/constructors.rs"]
659mod constructors;
660
661/// Rebase phase (initial or post-review).
662#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
663pub enum RebasePhase {
664 Initial,
665 PostReview,
666}
667
668/// Error kind for agent failures.
669#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
670pub enum AgentErrorKind {
671 Network,
672 Authentication,
673 RateLimit,
674 Timeout,
675 InternalError,
676 ModelUnavailable,
677 ParsingError,
678 FileSystem,
679}
680
681/// Conflict resolution strategy.
682#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
683pub enum ConflictStrategy {
684 Abort,
685 Continue,
686 Skip,
687}
688
689/// Checkpoint save trigger.
690#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
691pub enum CheckpointTrigger {
692 PhaseTransition,
693 IterationComplete,
694 BeforeRebase,
695 Interrupt,
696}
697
698#[cfg(test)]
699mod tests {
700 use super::*;
701
702 #[test]
703 fn test_pipeline_phase_display() {
704 assert_eq!(format!("{}", PipelinePhase::Planning), "Planning");
705 assert_eq!(format!("{}", PipelinePhase::Development), "Development");
706 assert_eq!(format!("{}", PipelinePhase::Review), "Review");
707 assert_eq!(
708 format!("{}", PipelinePhase::CommitMessage),
709 "Commit Message"
710 );
711 assert_eq!(
712 format!("{}", PipelinePhase::FinalValidation),
713 "Final Validation"
714 );
715 assert_eq!(format!("{}", PipelinePhase::Finalizing), "Finalizing");
716 assert_eq!(format!("{}", PipelinePhase::Complete), "Complete");
717 assert_eq!(
718 format!("{}", PipelinePhase::AwaitingDevFix),
719 "Awaiting Dev Fix"
720 );
721 assert_eq!(format!("{}", PipelinePhase::Interrupted), "Interrupted");
722 }
723
724 /// This test enforces the FROZEN policy on LifecycleEvent.
725 ///
726 /// If you're here because this test failed to compile after adding
727 /// a variant, you are violating the freeze policy. See the FROZEN
728 /// comment on LifecycleEvent for alternatives.
729 #[test]
730 fn lifecycle_event_is_frozen() {
731 fn exhaustive_match(e: LifecycleEvent) -> &'static str {
732 match e {
733 LifecycleEvent::Started => "started",
734 LifecycleEvent::Resumed { .. } => "resumed",
735 LifecycleEvent::Completed => "completed",
736 // DO NOT ADD _ WILDCARD - intentionally exhaustive
737 }
738 }
739 // Just needs to compile; actual call proves exhaustiveness
740 let _ = exhaustive_match(LifecycleEvent::Started);
741 }
742
743 /// This test enforces the FROZEN policy on PipelineEvent.
744 ///
745 /// If you're here because this test failed to compile after adding
746 /// a variant, you are violating the freeze policy. See the FROZEN
747 /// comment on PipelineEvent for alternatives.
748 #[test]
749 fn pipeline_event_is_frozen() {
750 fn exhaustive_match(e: PipelineEvent) -> &'static str {
751 match e {
752 PipelineEvent::Lifecycle(_) => "lifecycle",
753 PipelineEvent::Planning(_) => "planning",
754 PipelineEvent::Development(_) => "development",
755 PipelineEvent::Review(_) => "review",
756 PipelineEvent::PromptInput(_) => "prompt_input",
757 PipelineEvent::Agent(_) => "agent",
758 PipelineEvent::Rebase(_) => "rebase",
759 PipelineEvent::Commit(_) => "commit",
760 PipelineEvent::AwaitingDevFix(_) => "awaiting_dev_fix",
761 PipelineEvent::ContextCleaned => "context_cleaned",
762 PipelineEvent::CheckpointSaved { .. } => "checkpoint_saved",
763 PipelineEvent::FinalizingStarted => "finalizing_started",
764 PipelineEvent::PromptPermissionsRestored => "prompt_permissions_restored",
765 PipelineEvent::LoopRecoveryTriggered { .. } => "loop_recovery_triggered",
766 // DO NOT ADD _ WILDCARD - intentionally exhaustive
767 }
768 }
769 let _ = exhaustive_match(PipelineEvent::ContextCleaned);
770 }
771}