@@ -2,7 +2,8 @@ use super::*;
22use crate :: types:: {
33 ArchitectureEntity , ListPageRequest , Priority , RequirementFilter , RequirementItem , RequirementPriority ,
44 RequirementQuery , RequirementQuerySort , RequirementStatus , RequirementType , TaskCreateInput , TaskQuery ,
5- TaskQuerySort , TaskType , WorkflowFilter , WorkflowQuery , WorkflowQuerySort , WorkflowRunInput , WorkflowStatus ,
5+ TaskQuerySort , TaskType , WorkflowDecisionRisk , WorkflowFilter , WorkflowQuery , WorkflowQuerySort , WorkflowRunInput ,
6+ WorkflowStatus ,
67} ;
78
89fn scoped_ao_root ( project_root : & std:: path:: Path ) -> std:: path:: PathBuf {
@@ -642,6 +643,94 @@ async fn file_hub_persists_workflows_with_machine_state() {
642643 assert_eq ! ( loaded. machine_state, crate :: types:: WorkflowMachineState :: RunPhase ) ;
643644}
644645
646+ #[ tokio:: test]
647+ async fn file_hub_complete_phase_with_decision_honors_rework_routing ( ) {
648+ let temp = tempfile:: tempdir ( ) . expect ( "tempdir" ) ;
649+ let mut workflow_config = crate :: load_workflow_config ( temp. path ( ) ) . expect ( "load workflow config" ) ;
650+ for ( phase_id, label, category) in [
651+ ( "implement" , "Implement" , "build" ) ,
652+ ( "qa-review" , "QA Review" , "qa" ) ,
653+ ( "push-branch" , "Push Branch" , "git" ) ,
654+ ] {
655+ workflow_config. phase_catalog . insert (
656+ phase_id. to_string ( ) ,
657+ crate :: PhaseUiDefinition {
658+ label : label. to_string ( ) ,
659+ description : String :: new ( ) ,
660+ category : category. to_string ( ) ,
661+ icon : None ,
662+ docs_url : None ,
663+ tags : Vec :: new ( ) ,
664+ visible : true ,
665+ } ,
666+ ) ;
667+ }
668+ workflow_config. workflows . push ( crate :: WorkflowDefinition {
669+ id : "routed-rework" . to_string ( ) ,
670+ name : "Routed Rework" . to_string ( ) ,
671+ description : "Regression test for file-backed rework routing" . to_string ( ) ,
672+ phases : vec ! [
673+ "implement" . to_string( ) . into( ) ,
674+ crate :: WorkflowPhaseEntry :: Rich ( crate :: WorkflowPhaseConfig {
675+ id: "qa-review" . to_string( ) ,
676+ skip_if: Vec :: new( ) ,
677+ max_rework_attempts: 3 ,
678+ on_verdict: std:: collections:: HashMap :: from( [ (
679+ "rework" . to_string( ) ,
680+ crate :: PhaseTransitionConfig {
681+ target: "implement" . to_string( ) ,
682+ guard: None ,
683+ allow_agent_target: false ,
684+ allowed_targets: Vec :: new( ) ,
685+ } ,
686+ ) ] ) ,
687+ } ) ,
688+ "push-branch" . to_string( ) . into( ) ,
689+ ] ,
690+ post_success : None ,
691+ variables : Vec :: new ( ) ,
692+ } ) ;
693+ crate :: write_workflow_config ( temp. path ( ) , & workflow_config) . expect ( "write workflow config" ) ;
694+
695+ let hub = file_hub ( temp. path ( ) ) . expect ( "create hub" ) ;
696+ let workflow = WorkflowServiceApi :: run (
697+ & hub,
698+ WorkflowRunInput :: for_task ( "TASK-routed-rework" . to_string ( ) , Some ( "routed-rework" . to_string ( ) ) ) ,
699+ )
700+ . await
701+ . expect ( "run workflow" ) ;
702+
703+ let workflow =
704+ WorkflowServiceApi :: complete_current_phase ( & hub, & workflow. id ) . await . expect ( "complete implement phase" ) ;
705+ assert_eq ! ( workflow. current_phase. as_deref( ) , Some ( "qa-review" ) ) ;
706+ assert_eq ! ( workflow. current_phase_index, 1 ) ;
707+
708+ let workflow = WorkflowServiceApi :: complete_current_phase_with_decision (
709+ & hub,
710+ & workflow. id ,
711+ Some ( crate :: PhaseDecision {
712+ kind : "phase_result" . to_string ( ) ,
713+ phase_id : "qa-review" . to_string ( ) ,
714+ verdict : crate :: PhaseDecisionVerdict :: Rework ,
715+ reason : "needs fixes" . to_string ( ) ,
716+ confidence : 0.9 ,
717+ risk : WorkflowDecisionRisk :: Medium ,
718+ evidence : Vec :: new ( ) ,
719+ guardrail_violations : Vec :: new ( ) ,
720+ commit_message : None ,
721+ target_phase : None ,
722+ } ) ,
723+ )
724+ . await
725+ . expect ( "qa review should request rework" ) ;
726+
727+ assert_eq ! ( workflow. status, WorkflowStatus :: Running ) ;
728+ assert_eq ! ( workflow. current_phase. as_deref( ) , Some ( "implement" ) ) ;
729+ assert_eq ! ( workflow. current_phase_index, 0 ) ;
730+ assert_eq ! ( workflow. phases[ 0 ] . status, crate :: types:: WorkflowPhaseStatus :: Running ) ;
731+ assert_eq ! ( workflow. decision_history. last( ) . and_then( |entry| entry. target_phase. as_deref( ) ) , Some ( "implement" ) ) ;
732+ }
733+
645734#[ tokio:: test]
646735async fn file_hub_auto_prunes_checkpoints_on_completion_when_enabled ( ) {
647736 let temp = tempfile:: tempdir ( ) . expect ( "tempdir" ) ;
0 commit comments