Task state transitions

Updated Jul 14, 2026

Task state transitions are validated against the domain state machine. Invalid transitions are rejected with a typed TaskDomainError::InvalidStateTransition error that includes the task ID and the requested from and to states.

Allowed transitions:

Table 1. Allowed task state transitions.

From state Allowed target states
draft in_progress, in_review, abandoned
in_progress in_review, paused, done, abandoned
in_review in_progress, done, abandoned
paused in_progress, abandoned
done (terminal)
abandoned (terminal)
use std::sync::Arc;

use corbusier::context::{CorrelationId, RequestContext, SessionId, UserId};
use corbusier::tenant::TenantId;
use corbusier::task::{
    adapters::memory::InMemoryTaskRepository,
    domain::{TaskDomainError, TaskState},
    services::{
        CreateTaskFromIssueRequest, TaskLifecycleError, TaskLifecycleService,
        TransitionTaskRequest,
    },
};
use mockable::DefaultClock;

async fn transition_task_states() -> Result<(), Box<dyn std::error::Error>> {
    let service = TaskLifecycleService::new(
        Arc::new(InMemoryTaskRepository::new()),
        Arc::new(DefaultClock),
    );
    let ctx = RequestContext::new(
        TenantId::new(),
        CorrelationId::new(),
        UserId::new(),
        SessionId::new(),
    );

    let task = service
        .create_from_issue(&ctx, CreateTaskFromIssueRequest::new(
            "github",
            "corbusier/core",
            330,
            "Validate task transitions",
        ))
        .await?;

    let transitioned = service
        .transition_task(&ctx, TransitionTaskRequest::new(task.id(), "in_progress"))
        .await?;
    assert_eq!(transitioned.state(), TaskState::InProgress);

    let invalid = service
        .transition_task(&ctx, TransitionTaskRequest::new(task.id(), "draft"))
        .await;

    assert!(matches!(
        invalid,
        Err(TaskLifecycleError::Domain(
            TaskDomainError::InvalidStateTransition {
                from: TaskState::InProgress,
                to: TaskState::Draft,
                ..
            }
        ))
    ));

    Ok(())
}