updateJobs :: forall m. (TS m) => [Job.Status] -> [(Job.Name, Job.Stage)] -> m ()
updateJobs activeStatuses gotStages = mapM_ transition activeStatuses where
    transition :: Job.Status -> m ()
    transition pst = when (newStage /= oldStage) $ case newStage of
        Finished                   -> setTerminalAndWithDeps (const $ triggerDep name) pst newStage
        st | isTerminationStage st -> setTerminalAndWithDeps (cascadeDepFailure name) pst newStage
           | otherwise             -> ES.setJobStage name newStage
      where
        name = pst ^. Job.name
        oldStage = pst ^. Job.stage
        newStage = checkTransition oldStage $ lookup name gotStages
    checkTransition :: Job.Stage -> Maybe Job.Stage -> Job.Stage
    checkTransition Killing Nothing    = Failed UserKilled
    checkTransition Killing (Just new)
        | Finished <- new              = Failed UserKilled
        | isTerminationStage new       = new
        | otherwise                    = Killing
    checkTransition old     Nothing
        | seenOnScheduler old          = RunError SchedulerLost
        | otherwise                    = old
    checkTransition old     (Just new)
        | old == new                   = old
        | isExpectedTransition old new = new
        | otherwise                    = RunError BadSchedulerTransition
    cascadeDepFailure :: Job.Name -> Job.Dependency -> Job.Status -> m ()
    cascadeDepFailure depName (Job.Dependency _ opt) cst
        | opt       = triggerDep depName cst
        | otherwise = setTerminalAndWithDeps (cascadeDepFailure $ cst ^. Job.name) cst (Failed $ DependencyFailed depName)
    setTerminalAndWithDeps :: (Job.Dependency -> Job.Status -> m ()) -> Job.Status -> Job.Stage -> m ()
    setTerminalAndWithDeps f s st = do
        ES.setJobStage name st
        -- Remove this job as a dependency on others
        removeRevDepsFromDeps s
        withDependents f name
      where name = s ^. Job.name
updateJobs :: forall m. (TS m) => [Job.Status] -> [(Job.Name, Job.Stage)] -> m ()
updateJobs activeStatuses gotStages = mapM_ transition activeStatuses where
    transition :: Job.Status -> m ()
    transition pst = when (newStage /= oldStage) $ case newStage of
        Finished                   -> setTerminalAndWithDeps (const $ triggerDep name) pst newStage
        st | isTerminationStage st -> setTerminalAndWithDeps (cascadeDepFailure name) pst newStage
           | otherwise             -> ES.setJobStage name newStage
      where
        name = pst ^. Job.name
        oldStage = pst ^. Job.stage
        newStage = checkTransition oldStage $ lookup name gotStages
    checkTransition :: Job.Stage -> Maybe Job.Stage -> Job.Stage
    checkTransition Killing Nothing    = Failed UserKilled
    checkTransition Killing (Just new)
        | Finished <- new              = Failed UserKilled
        | isTerminationStage new       = new
        | otherwise                    = Killing
    checkTransition old     Nothing
        | seenOnScheduler old          = RunError SchedulerLost
        | otherwise                    = old
    checkTransition old     (Just new)
        | old == new                   = old
        | isExpectedTransition old new = new
        | otherwise                    = RunError BadSchedulerTransition
    cascadeDepFailure :: Job.Name -> Job.Dependency -> Job.Status -> m ()
    cascadeDepFailure depName (Job.Dependency _ opt) cst
        | opt       = triggerDep depName cst
        | otherwise = setTerminalAndWithDeps (cascadeDepFailure $ cst ^. Job.name) cst (Failed $ DependencyFailed depName)
    setTerminalAndWithDeps :: (Job.Dependency -> Job.Status -> m ()) -> Job.Status -> Job.Stage -> m ()
    setTerminalAndWithDeps f s st = do
        ES.setJobStage name st
        -- Remove this job as a dependency on others
        removeRevDepsFromDeps s
        withDependents f name
      where name = s ^. Job.name
updateJobs activeStatuses gotStages = mapM_ transition activeStatuses where
    transition :: Job.Status -> m ()
    transition pst = when (newStage /= oldStage) $ case newStage of
        Finished                   -> setTerminalAndWithDeps (const $ triggerDep name) pst newStage
        st | isTerminationStage st -> setTerminalAndWithDeps (cascadeDepFailure name) pst newStage
           | otherwise             -> ES.setJobStage name newStage
      where
        name = pst ^. Job.name
        oldStage = pst ^. Job.stage
        newStage = checkTransition oldStage $ lookup name gotStages
    checkTransition :: Job.Stage -> Maybe Job.Stage -> Job.Stage
    checkTransition Killing Nothing    = Failed UserKilled
    checkTransition Killing (Just new)
        | Finished <- new              = Failed UserKilled
        | isTerminationStage new       = new
        | otherwise                    = Killing
    checkTransition old     Nothing
        | seenOnScheduler old          = RunError SchedulerLost
        | otherwise                    = old
    checkTransition old     (Just new)
        | old == new                   = old
        | isExpectedTransition old new = new
        | otherwise                    = RunError BadSchedulerTransition
    cascadeDepFailure :: Job.Name -> Job.Dependency -> Job.Status -> m ()
    cascadeDepFailure depName (Job.Dependency _ opt) cst
        | opt       = triggerDep depName cst
        | otherwise = setTerminalAndWithDeps (cascadeDepFailure $ cst ^. Job.name) cst (Failed $ DependencyFailed depName)
    setTerminalAndWithDeps :: (Job.Dependency -> Job.Status -> m ()) -> Job.Status -> Job.Stage -> m ()
    setTerminalAndWithDeps f s st = do
        ES.setJobStage name st
        -- Remove this job as a dependency on others
        removeRevDepsFromDeps s
        withDependents f name
      where name = s ^. Job.name