diff --git a/hix.cabal b/hix.cabal
--- a/hix.cabal
+++ b/hix.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           hix
-version:        0.7.0
+version:        0.7.1
 synopsis:       Haskell/Nix development build tools
 description:    See https://hackage.haskell.org/package/hix/docs/Hix.html
 category:       Build
@@ -90,6 +90,7 @@
       Hix.Managed.Cabal.Repo
       Hix.Managed.Cabal.Resources
       Hix.Managed.Cabal.Solve
+      Hix.Managed.Cabal.Sort
       Hix.Managed.Cabal.Targets
       Hix.Managed.Constraints
       Hix.Managed.Data.BuildConfig
@@ -337,9 +338,11 @@
       Hix.Test.Managed.LowerNativeTest
       Hix.Test.Managed.LowerOptimize.CandidatesTest
       Hix.Test.Managed.LowerOptimize.MutationTest
+      Hix.Test.Managed.LowerOptimize.OrderTest
       Hix.Test.Managed.LowerStabilize.MutationTest
       Hix.Test.Managed.ProjectContextProtoTest
       Hix.Test.Managed.Run
+      Hix.Test.Managed.SolveTest
       Hix.Test.Managed.UnsafeIsString
       Hix.Test.ManagedTest
       Hix.Test.NewTest
diff --git a/lib/Hix/Data/VersionBounds.hs b/lib/Hix/Data/VersionBounds.hs
--- a/lib/Hix/Data/VersionBounds.hs
+++ b/lib/Hix/Data/VersionBounds.hs
@@ -8,6 +8,7 @@
   VersionRange,
   earlierVersion,
   intersectVersionRanges,
+  orEarlierVersion,
   orLaterVersion,
   simplifyVersionRange,
   thisVersion,
@@ -117,6 +118,15 @@
 
 instance Pretty VersionBounds where
   pretty = pretty . majorRange
+
+maybeInclusiveRange :: VersionBounds -> Maybe VersionRange
+maybeInclusiveRange =
+  maybeRange \case
+    BoundLower -> orLaterVersion
+    BoundUpper -> orEarlierVersion
+
+inclusiveRange :: VersionBounds -> VersionRange
+inclusiveRange = fromMaybe range0 . maybeInclusiveRange
 
 anyBounds :: VersionBounds
 anyBounds =
diff --git a/lib/Hix/Managed/Build.hs b/lib/Hix/Managed/Build.hs
--- a/lib/Hix/Managed/Build.hs
+++ b/lib/Hix/Managed/Build.hs
@@ -33,8 +33,10 @@
 import Hix.Managed.Data.StageContext (StageContext (StageContext))
 import qualified Hix.Managed.Data.StageState
 import Hix.Managed.Data.StageState (
+  BuildResult (Finished, TimedOut),
   BuildStatus (Failure, Success),
   StageState,
+  buildStatus,
   failed,
   initStageState,
   iterations,
@@ -42,6 +44,8 @@
   )
 import qualified Hix.Managed.Handlers.Build
 import Hix.Managed.Handlers.Build (EnvBuilder)
+import qualified Hix.Managed.Handlers.Cabal
+import Hix.Managed.Handlers.Cabal (CabalHandlers (CabalHandlers))
 import qualified Hix.Managed.Handlers.Mutation
 import Hix.Managed.Handlers.Mutation (MutationHandlers)
 import Hix.Managed.StageState (updateStageState)
@@ -56,13 +60,14 @@
   Log.info [exon|Building targets in '##{env}' with #{description}...|]
   Log.debugP (vcat ["Overrides:", prettyL overrides])
 
-logBuildResult :: Text -> BuildStatus -> M ()
-logBuildResult description status =
-  Log.info [exon|Build with ##{description} #{result status}|]
+logBuildResult :: Text -> BuildResult -> M ()
+logBuildResult description result =
+  Log.info [exon|Build with ##{description} #{describeResult result}|]
   where
-    result = \case
-      Success -> "succeeded"
-      Failure -> "failed"
+    describeResult = \case
+      Finished Success -> "succeeded"
+      Finished Failure -> "failed"
+      TimedOut -> "timed out"
 
 updateMutationState ::
   (Version -> VersionBounds -> VersionBounds) ->
@@ -92,7 +97,7 @@
   logBuildInputs context.env description reinstallable
   (overrides, status) <- builder.buildWithState versions reinstallable
   logBuildResult description status
-  pure (overrides, status)
+  pure (overrides, buildStatus status)
   where
     reinstallable = filter isReinstallableId overrideVersions
 
@@ -210,7 +215,10 @@
   s ->
   M (StageState a s)
 processQuery candidates handlers conf StageContext {env, builder, state, query = query} ext = do
-  mutations <- reinstallableCandidates candidates query
+  mutations <- postprocess =<< reinstallableCandidates candidates query
   convergeMutations handlers conf builder env stageState mutations
   where
+    postprocess | conf.toposortMutations = sortMutations
+                | otherwise = pure
     stageState = initStageState state ext
+    CabalHandlers {sortMutations} = builder.cabal
diff --git a/lib/Hix/Managed/Bump/App.hs b/lib/Hix/Managed/Bump/App.hs
--- a/lib/Hix/Managed/Bump/App.hs
+++ b/lib/Hix/Managed/Bump/App.hs
@@ -6,6 +6,7 @@
 import Hix.Data.Options (BumpOptions (BumpOptions))
 import Hix.Json (jsonConfigE)
 import Hix.Managed.Bump.Optimize (bumpOptimizeMain)
+import qualified Hix.Managed.Data.BuildConfig
 import qualified Hix.Managed.Data.ProjectContextProto
 import Hix.Managed.Handlers.Build.Test (chooseHandlers)
 import Hix.Managed.ProjectContext (withProjectContext)
@@ -13,5 +14,7 @@
 bumpCli :: BumpOptions -> M ()
 bumpCli BumpOptions {common = opts} = do
   context <- jsonConfigE Client opts.context
-  handlers <- liftIO (chooseHandlers opts.stateFile context.envs context.buildOutputsPrefix opts.cabal opts.handlers)
+  handlers <- liftIO $
+    chooseHandlers opts.stateFile context.envs context.buildOutputsPrefix opts.project.build.timeout opts.cabal
+      opts.handlers
   withProjectContext handlers opts.project context (bumpOptimizeMain handlers)
diff --git a/lib/Hix/Managed/Cabal/Print.hs b/lib/Hix/Managed/Cabal/Print.hs
--- a/lib/Hix/Managed/Cabal/Print.hs
+++ b/lib/Hix/Managed/Cabal/Print.hs
@@ -3,21 +3,31 @@
 import qualified Distribution.Client.SolverInstallPlan as SolverInstallPlan
 import Distribution.Client.SolverInstallPlan (ResolverPackage (..), SolverInstallPlan, SolverPlanPackage)
 import Distribution.InstalledPackageInfo (InstalledPackageInfo (InstalledPackageInfo, sourcePackageId))
-import Distribution.Pretty (pretty)
 import Distribution.Solver.Types.InstSolverPackage (InstSolverPackage (InstSolverPackage, instSolverPkgIPI))
 import Distribution.Solver.Types.SolverPackage (SolverPackage (..))
 import Distribution.Solver.Types.SourcePackage (SourcePackage (..))
 import Exon (exon)
 
 import Hix.Data.Monad (M)
+import qualified Hix.Data.PackageId as PackageId
+import Hix.Data.PackageId (PackageId)
 import qualified Hix.Log as Log
+import Hix.Pretty (showP)
 
-showPackage :: SolverPlanPackage -> String
-showPackage = \case
+resolverPackageId :: ResolverPackage a -> PackageId
+resolverPackageId = \case
   PreExisting InstSolverPackage {instSolverPkgIPI = InstalledPackageInfo {sourcePackageId}} ->
-    "existing: " <> show (pretty sourcePackageId)
+    PackageId.fromCabal sourcePackageId
   Configured SolverPackage {solverPkgSource = SourcePackage {srcpkgPackageId}} ->
-    "configured: " <> show (pretty srcpkgPackageId)
+    PackageId.fromCabal srcpkgPackageId
+
+showPackage :: SolverPlanPackage -> String
+showPackage pkg =
+  [exon|#{desc}: #{showP (resolverPackageId pkg)}|]
+  where
+    desc = case pkg of
+      PreExisting {} -> "existing"
+      Configured {} -> "configured"
 
 printPlan :: SolverInstallPlan -> M ()
 printPlan plan = do
diff --git a/lib/Hix/Managed/Cabal/Repo.hs b/lib/Hix/Managed/Cabal/Repo.hs
--- a/lib/Hix/Managed/Cabal/Repo.hs
+++ b/lib/Hix/Managed/Cabal/Repo.hs
@@ -1,20 +1,26 @@
 module Hix.Managed.Cabal.Repo where
 
-import Data.Time (UTCTime (..), showGregorian, timeToTimeOfDay)
+import Data.Time (
+  NominalDiffTime,
+  UTCTime (..),
+  diffUTCTime,
+  getCurrentTime,
+  nominalDay,
+  showGregorian,
+  timeToTimeOfDay,
+  )
 import Distribution.Client.CmdUpdate (updateAction)
 import qualified Distribution.Client.GlobalFlags
 import Distribution.Client.GlobalFlags (GlobalFlags (..))
 import Distribution.Client.IndexUtils (currentIndexTimestamp, indexBaseName)
-import Distribution.Client.IndexUtils.Timestamp (timestampToUTCTime)
+import Distribution.Client.IndexUtils.Timestamp (Timestamp, timestampToUTCTime)
 import Distribution.Client.NixStyleOptions (NixStyleFlags (..))
 import Distribution.Client.Setup (RepoContext, withRepoContext)
 import Distribution.Client.Types (RemoteRepo (..), Repo (RepoSecure), RepoName (RepoName))
 import qualified Distribution.Client.Types.Repo
-import Distribution.Compat.Time (getFileAge)
 import Distribution.Verbosity (Verbosity)
 import Exon (exon)
-import Path (Abs, File, Path, addExtension, parseAbsFile, toFilePath)
-import Path.IO (doesFileExist)
+import Path (Abs, File, Path, addExtension, parseAbsFile)
 
 import Hix.Data.Error (Error (Fatal))
 import Hix.Error (pathText)
@@ -26,8 +32,8 @@
   HackageRepoName (HackageRepoName),
   SolveConfig (SolveConfig),
   )
-import Hix.Maybe (justIf)
 import Hix.Monad (M, catchIOM, eitherFatalShow, noteFatal, tryIOM, tryIOMWith, withLower)
+import Hix.Pretty (showP)
 
 withRepoContextM ::
   SolveConfig ->
@@ -51,14 +57,18 @@
 data IndexProblem =
   IndexMissing
   |
-  IndexOutdated
+  IndexOutdated NominalDiffTime
   |
   IndexMismatch
+  |
+  IndexCorrupt Timestamp
   deriving stock (Eq, Show, Generic)
 
-indexOutdated :: Path Abs File -> IO Bool
-indexOutdated (toFilePath -> index) =
-  (> 7) <$> getFileAge index
+data ValidIndex =
+  IndexMatch HackageIndexState
+  |
+  IndexRecent NominalDiffTime
+  deriving stock (Eq, Show, Generic)
 
 updateRequest ::
   SolveConfig ->
@@ -72,6 +82,12 @@
 
     HackageRepoName (toString -> hackage) = hackageRepoName
 
+maxIndexAgeDays :: Int
+maxIndexAgeDays = 7
+
+maxIndexAge :: NominalDiffTime
+maxIndexAge = nominalDay * fromIntegral maxIndexAgeDays
+
 updateIndex ::
   SolveConfig ->
   GlobalFlags ->
@@ -80,13 +96,14 @@
   M ()
 updateIndex conf global main problem = do
   Log.verbose [exon|Hackage snapshot #{message}, fetching...|]
-  tryIOMWith (\ err -> Fatal [exon|Fetching hackage snapshot failed: #{err}|]) do
+  tryIOMWith (\ err -> Fatal [exon|Fetching Hackage snapshot failed: #{err}|]) do
     updateAction main [updateRequest conf] global
   where
     message = case problem of
       IndexMissing -> "doesn't exist"
-      IndexOutdated -> "is older than 7 days"
+      IndexOutdated age -> [exon|is older than #{show maxIndexAgeDays} days (#{show age})|]
       IndexMismatch -> "differs from requested index state"
+      IndexCorrupt stamp -> [exon|has corrupt timestamp (#{show stamp})|]
 
 indexPath :: Repo -> M (Path Abs File)
 indexPath repo =
@@ -96,34 +113,59 @@
   where
     err = "Bad Hackage repo config"
 
-matchIndexState ::
+currentIndexState ::
   Verbosity ->
   RepoContext ->
   Repo ->
-  HackageIndexState ->
-  M (Maybe IndexProblem)
-matchIndexState verbosity ctx repo (HackageIndexState target) =
-  catchIOM (match <$> currentIndexTimestamp verbosity ctx repo) (\ _ -> pure (Just IndexMissing))
-  where
-    match current | current == target = Nothing
-                  | otherwise = Just IndexMismatch
+  M (Maybe HackageIndexState)
+currentIndexState verbosity ctx repo =
+  catchIOM (Just . HackageIndexState <$> currentIndexTimestamp verbosity ctx repo) (const (pure Nothing))
 
 indexProblem ::
   SolveConfig ->
   RepoContext ->
   Repo ->
   Path Abs File ->
-  M (Maybe IndexProblem)
+  M (Either IndexProblem ValidIndex)
 indexProblem SolveConfig {verbosity, cabal} ctx repo path = do
-  Log.debug [exon|Checking hackage snapshot at #{pathText path}|]
-  tryIOM (doesFileExist path) >>= \case
-    False -> pure (Just IndexMissing)
-    True
+  now <- tryIOM getCurrentTime
+  Log.debug [exon|Checking Hackage snapshot at #{pathText path}|]
+  currentIndexState verbosity ctx repo <&> \case
+    -- There is no index, so the target is irrelevant.
+    Nothing -> Left IndexMissing
+    Just currentState
+      -- If a target index state was specified, we only care whether it matches exactly.
+      -- The later conditions are irrelevant.
       | Just target <- cabal.indexState
-      -> matchIndexState verbosity ctx repo target
+      -> if currentState == target
+         then Right (IndexMatch target)
+         else Left IndexMismatch
+      -- If no target was specified, the age of the current index must be below the threshold.
+      -- The guard expresses that the timestamp could be parsed.
+      | Just current <- currentUTC
+      , let age = diffUTCTime now current
+      -> if diffUTCTime now current > maxIndexAge
+         then Left (IndexOutdated age)
+         else Right (IndexRecent age)
+      -- If the current state's timestamp can't be converted to UTCTime, it may be corrupt, so we update.
       | otherwise
-      -> flip justIf IndexOutdated <$> tryIOM (indexOutdated path)
+      -> Left (IndexCorrupt (coerce currentState))
+      where
+        currentUTC = timestampToUTCTime (coerce currentState)
 
+logValid ::
+  ValidIndex ->
+  M ()
+logValid = \case
+  IndexMatch (HackageIndexState ts) -> Log.debug [exon|Snapshot matches target: #{showP ts}|]
+  IndexRecent age ->
+    Log.debug [exon|Snapshot is less than #{desc} old (maximum #{show maxIndexAgeDays}).|]
+    where
+      desc | days == 1 = "a day"
+           | otherwise = [exon|#{show days} days|]
+      days :: Int
+      days = maybe 0 ceiling (age / nominalDay)
+
 -- TODO currentIndexTimestamp has a different signature in later versions
 ensureHackageIndex ::
   SolveConfig ->
@@ -134,4 +176,4 @@
   withRepoContextM conf global \ ctx -> do
     repo <- fullHackageRepo conf ctx
     path <- indexPath repo
-    traverse_ (updateIndex conf global main) =<< indexProblem conf ctx repo path
+    either (updateIndex conf global main) logValid =<< indexProblem conf ctx repo path
diff --git a/lib/Hix/Managed/Cabal/Sort.hs b/lib/Hix/Managed/Cabal/Sort.hs
new file mode 100644
--- /dev/null
+++ b/lib/Hix/Managed/Cabal/Sort.hs
@@ -0,0 +1,45 @@
+module Hix.Managed.Cabal.Sort where
+
+import qualified Data.Map.Strict as Map
+import Data.Map.Strict ((!?))
+import Distribution.Client.SolverInstallPlan (topologicalOrder)
+import Exon (exon)
+
+import Hix.Data.Monad (M)
+import qualified Hix.Data.PackageId
+import qualified Hix.Log as Log
+import Hix.Managed.Cabal.Data.SolveResources (SolveResources)
+import Hix.Managed.Cabal.Print (resolverPackageId)
+import Hix.Managed.Cabal.Solve (solveForTargets)
+import Hix.Managed.Cabal.Targets (solveTarget)
+import Hix.Managed.Data.Mutable (MutableDep (MutableDep))
+import Hix.Managed.Data.Mutation (DepMutation (..))
+import Hix.Data.PackageName (PackageName)
+
+sortDeps ::
+  ∀ a .
+  SolveResources ->
+  (a -> PackageName) ->
+  [a] ->
+  M [a]
+sortDeps resources name mutations =
+  solveForTargets resources id targets >>= \case
+    Right plan ->
+      pure (mapMaybe original (topologicalOrder plan))
+    Left err -> do
+      Log.error [exon|No plan for mutations: ##{err}|]
+      pure mutations
+  where
+    original k = byName !? (resolverPackageId k).name
+    byName = Map.fromList [(name m, m) | m <- mutations]
+    targets = [solveTarget (name m) mempty | m <- mutations]
+
+sortMutations ::
+  ∀ a .
+  SolveResources ->
+  [DepMutation a] ->
+  M [DepMutation a]
+sortMutations resources =
+  sortDeps resources name
+  where
+    name DepMutation {package = MutableDep package} = package
diff --git a/lib/Hix/Managed/Data/BuildConfig.hs b/lib/Hix/Managed/Data/BuildConfig.hs
--- a/lib/Hix/Managed/Data/BuildConfig.hs
+++ b/lib/Hix/Managed/Data/BuildConfig.hs
@@ -1,5 +1,7 @@
 module Hix.Managed.Data.BuildConfig where
 
+import Hix.Managed.Handlers.Build (BuildTimeout)
+
 data BuildConfig =
   BuildConfig {
     maxIterations :: Natural,
@@ -7,7 +9,9 @@
     maxFailedPost :: Natural,
     lookup :: Bool,
     validate :: Bool,
-    buildOutput :: Bool
+    buildOutput :: Bool,
+    toposortMutations :: Bool,
+    timeout :: Maybe BuildTimeout
   }
   deriving stock (Eq, Show, Generic)
 
@@ -18,5 +22,7 @@
     maxFailedPost = 0,
     lookup = False,
     validate = False,
-    buildOutput = False
+    buildOutput = False,
+    toposortMutations = True,
+    timeout = Nothing
   }
diff --git a/lib/Hix/Managed/Data/Constraints.hs b/lib/Hix/Managed/Data/Constraints.hs
--- a/lib/Hix/Managed/Data/Constraints.hs
+++ b/lib/Hix/Managed/Data/Constraints.hs
@@ -8,7 +8,7 @@
 
 import Hix.Class.Map (LookupMaybe, NMap, nPretty)
 import Hix.Data.PackageName (PackageName)
-import Hix.Data.VersionBounds (VersionBounds, anyBounds)
+import Hix.Data.VersionBounds (VersionBounds, anyBounds, inclusiveRange)
 import Hix.Pretty (prettyL)
 
 -- | Left-biased semigroup op.
@@ -37,7 +37,7 @@
 
 instance Pretty MutationConstraints where
   pretty MutationConstraints {mutation, oldest, installed, force, prefer} =
-    pretty mutation <+>
+    pretty (inclusiveRange mutation) <+>
     flags [("oldest", oldest), ("installed", installed)] <+>
     foldMap pref prefer <+>
     foldMap spec force
diff --git a/lib/Hix/Managed/Data/Query.hs b/lib/Hix/Managed/Data/Query.hs
--- a/lib/Hix/Managed/Data/Query.hs
+++ b/lib/Hix/Managed/Data/Query.hs
@@ -5,11 +5,15 @@
 import Hix.Data.PackageName (PackageName)
 import Hix.Managed.Data.QueryDep (QueryDep)
 
+-- | User-selected restriction of packages that should be processed.
+-- Given as positional arguments on the command line.
+-- If empty, all packages are processed.
 newtype RawQuery =
   RawQuery [PackageName]
   deriving stock (Eq, Show, Generic)
   deriving newtype (IsList)
 
+-- | Stage-specific subset of the validated environment subset of 'RawQuery', adorned with metadata.
 newtype Query =
   Query (NonEmpty QueryDep)
   deriving stock (Eq, Show, Generic)
diff --git a/lib/Hix/Managed/Data/StageState.hs b/lib/Hix/Managed/Data/StageState.hs
--- a/lib/Hix/Managed/Data/StageState.hs
+++ b/lib/Hix/Managed/Data/StageState.hs
@@ -18,10 +18,22 @@
   Success -> Just a
   Failure -> Nothing
 
-buildStatus :: Bool -> BuildStatus
+data BuildResult =
+  Finished BuildStatus
+  |
+  TimedOut
+  deriving stock (Eq, Show, Generic)
+
+buildUnsuccessful :: BuildResult -> Bool
+buildUnsuccessful = \case
+  Finished Success -> False
+  Finished Failure -> True
+  TimedOut -> True
+
+buildStatus :: BuildResult -> BuildStatus
 buildStatus = \case
-  True -> Success
-  False -> Failure
+  Finished s -> s
+  TimedOut -> Failure
 
 data BuildSuccess =
   CandidateBuilt MutableId
diff --git a/lib/Hix/Managed/Data/Targets.hs b/lib/Hix/Managed/Data/Targets.hs
--- a/lib/Hix/Managed/Data/Targets.hs
+++ b/lib/Hix/Managed/Data/Targets.hs
@@ -6,6 +6,7 @@
   singleTarget,
   sortTargets,
   allMTargets,
+  firstMTargets,
   overTargets,
 ) where
 
@@ -78,6 +79,22 @@
     simple :: Packages [LocalPackage]
     simple = nMap (onlyFrom targetSet) (nRestrictKeys targetSet deps)
     targetSet = Set.fromList targets
+
+firstMTargets ::
+  Monad m =>
+  a ->
+  (a -> Bool) ->
+  (LocalPackage -> m a) ->
+  Targets ->
+  m a
+firstMTargets success cond f (Targets targets) =
+  foldr chain (pure success) targets
+  where
+    chain a z = do
+      res <- f a
+      if cond res
+      then pure res
+      else z
 
 allMTargets ::
   Monad m =>
diff --git a/lib/Hix/Managed/Handlers/Build.hs b/lib/Hix/Managed/Handlers/Build.hs
--- a/lib/Hix/Managed/Handlers/Build.hs
+++ b/lib/Hix/Managed/Handlers/Build.hs
@@ -16,7 +16,7 @@
 import Hix.Managed.Data.Initial (Initial)
 import Hix.Managed.Data.ManagedPackage (ManagedPackage)
 import Hix.Managed.Data.Packages (Packages)
-import Hix.Managed.Data.StageState (BuildStatus (Failure))
+import Hix.Managed.Data.StageState (BuildResult (Finished), BuildStatus (Failure))
 import qualified Hix.Managed.Handlers.Cabal as Solve
 import qualified Hix.Managed.Handlers.Cabal as Cabal
 import Hix.Managed.Handlers.Cabal (CabalHandlers)
@@ -32,10 +32,15 @@
   deriving stock (Eq, Show, Generic)
   deriving newtype (IsString, Ord, FromJSON)
 
+newtype BuildTimeout =
+  BuildTimeout Int
+  deriving stock (Eq, Show, Generic)
+  deriving newtype (Num, Real, Enum, Integral, Ord, FromJSON)
+
 data EnvBuilder =
   EnvBuilder {
     cabal :: CabalHandlers,
-    buildWithState :: Versions -> [PackageId] -> M (Overrides, BuildStatus)
+    buildWithState :: Versions -> [PackageId] -> M (Overrides, BuildResult)
   }
 
 data Builder =
@@ -56,11 +61,11 @@
     latestVersion :: PackageName -> M (Maybe Version)
   }
 
-testBuilder :: (Versions -> [PackageId] -> M (Overrides, BuildStatus)) -> (Builder -> M a) -> M a
+testBuilder :: (Versions -> [PackageId] -> M (Overrides, BuildResult)) -> (Builder -> M a) -> M a
 testBuilder buildWithState use =
   use Builder {withEnvBuilder = \ cabal _ _ useE -> useE EnvBuilder {cabal, buildWithState}}
 
-versionsBuilder :: HackageHandlers -> (Versions -> M BuildStatus) -> (Builder -> M a) -> M a
+versionsBuilder :: HackageHandlers -> (Versions -> M BuildResult) -> (Builder -> M a) -> M a
 versionsBuilder hackage build =
   testBuilder \ versions overrideVersions -> do
     overrides <- packageOverrides hackage overrideVersions
@@ -73,7 +78,7 @@
     stateFile = StateFileHandlers.handlersNull,
     report = Report.handlersNull,
     cabal = \ _ _ -> pure Solve.handlersNull,
-    withBuilder = testBuilder \ _ _ -> pure (mempty, Failure),
+    withBuilder = testBuilder \ _ _ -> pure (mempty, Finished Failure),
     versions = \ _ -> pure [],
     latestVersion = \ _ -> pure Nothing
   }
diff --git a/lib/Hix/Managed/Handlers/Build/Prod.hs b/lib/Hix/Managed/Handlers/Build/Prod.hs
--- a/lib/Hix/Managed/Handlers/Build/Prod.hs
+++ b/lib/Hix/Managed/Handlers/Build/Prod.hs
@@ -14,11 +14,13 @@
   inherit,
   nullStream,
   proc,
-  runProcess,
   setStderr,
   setStdout,
   setWorkingDir,
+  waitExitCode,
+  withProcessTerm,
   )
+import System.Timeout (timeout)
 
 import Hix.Data.EnvName (EnvName)
 import Hix.Data.Error (Error (Fatal))
@@ -38,12 +40,18 @@
 import Hix.Managed.Data.EnvState (EnvState)
 import Hix.Managed.Data.Envs (Envs)
 import Hix.Managed.Data.Initial (Initial)
-import Hix.Managed.Data.StageState (BuildStatus, buildStatus)
+import Hix.Managed.Data.StageState (BuildResult (Finished, TimedOut), BuildStatus (Failure, Success), buildUnsuccessful)
 import qualified Hix.Managed.Data.StateFileConfig
 import Hix.Managed.Data.StateFileConfig (StateFileConfig)
-import Hix.Managed.Data.Targets (allMTargets)
+import Hix.Managed.Data.Targets (firstMTargets)
 import qualified Hix.Managed.Handlers.Build
-import Hix.Managed.Handlers.Build (BuildHandlers (..), BuildOutputsPrefix, Builder (Builder), EnvBuilder (EnvBuilder))
+import Hix.Managed.Handlers.Build (
+  BuildHandlers (..),
+  BuildOutputsPrefix,
+  BuildTimeout,
+  Builder (Builder),
+  EnvBuilder (EnvBuilder),
+  )
 import Hix.Managed.Handlers.Cabal (CabalHandlers)
 import qualified Hix.Managed.Handlers.Cabal.Prod as CabalHandlers
 import Hix.Managed.Handlers.Hackage (HackageHandlers)
@@ -62,7 +70,8 @@
     stateFileHandlers :: StateFileHandlers,
     envsConf :: Envs EnvConfig,
     buildOutputsPrefix :: Maybe BuildOutputsPrefix,
-    root :: Path Abs Dir
+    root :: Path Abs Dir,
+    buildTimeout :: Maybe BuildTimeout
   }
 
 data EnvBuilderResources =
@@ -103,17 +112,22 @@
     args = toString <$> cmd ++ [exon|path:#{".#"}#{installable}|] : extra
 
 buildPackage ::
+  Maybe BuildTimeout ->
   Path Abs Dir ->
   EnvName ->
   LocalPackage ->
-  M Bool
-buildPackage root env target = do
+  M BuildResult
+buildPackage processTimeout root env target = do
   debug <- M (asks (.debug))
   conf <- nixProc root ["-L", "build"] [exon|env.##{env}.##{target}|] []
-  tryIOM (runProcess (err debug conf)) <&> \case
-    ExitSuccess -> True
-    ExitFailure _ -> False
+  tryIOM (withProcessTerm (err debug conf) (limit . waitExitCode)) <&> \case
+    Just ExitSuccess -> Finished Success
+    Just (ExitFailure _) -> Finished Failure
+    Nothing -> TimedOut
   where
+    limit | Just t <- processTimeout = timeout (coerce t * 1_000_000)
+          | otherwise = fmap Just
+
     err = \case
       True -> setStdout inherit
       False -> id
@@ -122,11 +136,11 @@
   EnvBuilderResources ->
   Versions ->
   [PackageId] ->
-  M (Overrides, BuildStatus)
+  M (Overrides, BuildResult)
 buildWithState EnvBuilderResources {global, context = context@EnvContext {env, targets}} _ overrideVersions = do
   overrides <- packageOverrides global.hackage overrideVersions
   writeBuildStateFor "current build" global.stateFileHandlers global.root context overrides
-  status <- buildStatus <$> allMTargets (buildPackage global.root env) targets
+  status <- firstMTargets (Finished Success) buildUnsuccessful (buildPackage global.buildTimeout global.root env) targets
   pure (overrides, status)
 
 -- | This used to have the purpose of reading an updated GHC package db using the current managed state, but this has
@@ -159,9 +173,10 @@
   StateFileConfig ->
   Envs EnvConfig ->
   Maybe BuildOutputsPrefix ->
+  Maybe BuildTimeout ->
   (Builder -> M a) ->
   M a
-withBuilder hackage stateFileHandlers stateFileConf envsConf buildOutputsPrefix use =
+withBuilder hackage stateFileHandlers stateFileConf envsConf buildOutputsPrefix buildTimeout use =
   withTempProject stateFileConf.projectRoot \ root -> do
     let resources = BuilderResources {..}
     use Builder {withEnvBuilder = withEnvBuilder resources}
@@ -171,10 +186,11 @@
   StateFileConfig ->
   Envs EnvConfig ->
   Maybe BuildOutputsPrefix ->
+  Maybe BuildTimeout ->
   CabalConfig ->
   Bool ->
   m BuildHandlers
-handlersProd stateFileConf envsConf buildOutputsPrefix cabalConf oldest = do
+handlersProd stateFileConf envsConf buildOutputsPrefix buildTimeout cabalConf oldest = do
   manager <- liftIO (newManager tlsManagerSettings)
   hackage <- HackageHandlers.handlersProd
   let stateFile = StateFileHandlers.handlersProd stateFileConf
@@ -182,7 +198,7 @@
     stateFile,
     report = ReportHandlers.handlersProd,
     cabal = CabalHandlers.handlersProd cabalConf oldest,
-    withBuilder = withBuilder hackage stateFile stateFileConf envsConf buildOutputsPrefix,
+    withBuilder = withBuilder hackage stateFile stateFileConf envsConf buildOutputsPrefix buildTimeout,
     versions = versionsHackage manager,
     latestVersion = latestVersionHackage manager
   }
diff --git a/lib/Hix/Managed/Handlers/Build/Test.hs b/lib/Hix/Managed/Handlers/Build/Test.hs
--- a/lib/Hix/Managed/Handlers/Build/Test.hs
+++ b/lib/Hix/Managed/Handlers/Build/Test.hs
@@ -16,11 +16,12 @@
 import Hix.Managed.Data.EnvConfig (EnvConfig)
 import Hix.Managed.Data.Envs (Envs)
 import Hix.Managed.Data.Mutation (FailedMutation)
-import Hix.Managed.Data.StageState (BuildStatus)
+import Hix.Managed.Data.StageState (BuildResult)
 import Hix.Managed.Data.StateFileConfig (StateFileConfig)
 import Hix.Managed.Handlers.Build (
   BuildHandlers (..),
   BuildOutputsPrefix,
+  BuildTimeout,
   SpecialBuildHandlers (TestBumpHandlers),
   versionsBuilder,
   )
@@ -35,7 +36,7 @@
 handlersUnitTest ::
   MonadIO m =>
   GhcPackages ->
-  (Versions -> M BuildStatus) ->
+  (Versions -> M BuildResult) ->
   m (BuildHandlers, IORef [Expr], IORef [FailedMutation])
 handlersUnitTest ghcPackages builder = do
   (stateFile, stateFileRef) <- StateFile.handlersUnitTest
@@ -67,11 +68,12 @@
   StateFileConfig ->
   Envs EnvConfig ->
   Maybe BuildOutputsPrefix ->
+  Maybe BuildTimeout ->
   CabalConfig ->
   Bool ->
   m BuildHandlers
-handlersBumpTest stateFileConf envsConf buildOutputsPrefix cabalConf oldest = do
-  handlers <- handlersProd stateFileConf envsConf buildOutputsPrefix cabalConf oldest
+handlersBumpTest stateFileConf envsConf buildOutputsPrefix buildTimeout cabalConf oldest = do
+  handlers <- handlersProd stateFileConf envsConf buildOutputsPrefix buildTimeout cabalConf oldest
   pure handlers {
     cabal = CabalHandlers.handlersTest cabalConf oldest,
     latestVersion = latestVersionNixTestBump
@@ -82,11 +84,12 @@
   StateFileConfig ->
   Envs EnvConfig ->
   Maybe BuildOutputsPrefix ->
+  Maybe BuildTimeout ->
   CabalConfig ->
   Maybe SpecialBuildHandlers ->
   m BuildHandlers
-chooseHandlers stateFileConf envsConf buildOutputsPrefix cabalConf = \case
-  Just TestBumpHandlers -> handlersBumpTest stateFileConf envsConf buildOutputsPrefix cabalConf oldest
-  Nothing -> handlersProd stateFileConf envsConf buildOutputsPrefix cabalConf oldest
+chooseHandlers stateFileConf envsConf buildOutputsPrefix buildTimeout cabalConf = \case
+  Just TestBumpHandlers -> handlersBumpTest stateFileConf envsConf buildOutputsPrefix buildTimeout cabalConf oldest
+  Nothing -> handlersProd stateFileConf envsConf buildOutputsPrefix buildTimeout cabalConf oldest
   where
     oldest = False
diff --git a/lib/Hix/Managed/Handlers/Cabal.hs b/lib/Hix/Managed/Handlers/Cabal.hs
--- a/lib/Hix/Managed/Handlers/Cabal.hs
+++ b/lib/Hix/Managed/Handlers/Cabal.hs
@@ -10,18 +10,21 @@
 import Hix.Managed.Cabal.Data.SolverState (SolverState)
 import Hix.Managed.Data.Constraints (EnvConstraints)
 import Hix.Managed.Data.Mutable (MutableDep, MutableVersions, depName)
+import Hix.Managed.Data.Mutation (DepMutation)
 
 data CabalHandlers =
   CabalHandlers {
     solveForVersion :: SolverState -> M (Maybe SolverPlan),
-    installedVersion :: PackageName -> Maybe Version
+    installedVersion :: PackageName -> Maybe Version,
+    sortMutations :: ∀ a . [DepMutation a] -> M [DepMutation a]
   }
 
 handlersNull :: CabalHandlers
 handlersNull =
   CabalHandlers {
     solveForVersion = \ _ -> pure Nothing,
-    installedVersion = const Nothing
+    installedVersion = const Nothing,
+    sortMutations = pure
   }
 
 installedVersions ::
diff --git a/lib/Hix/Managed/Handlers/Cabal/Prod.hs b/lib/Hix/Managed/Handlers/Cabal/Prod.hs
--- a/lib/Hix/Managed/Handlers/Cabal/Prod.hs
+++ b/lib/Hix/Managed/Handlers/Cabal/Prod.hs
@@ -13,12 +13,13 @@
 import Hix.Data.PackageId (PackageId (PackageId))
 import qualified Hix.Data.PackageName as PackageName
 import Hix.Data.PackageName (PackageName)
-import Hix.Managed.Cabal.Data.Config (GhcDb, CabalConfig)
+import Hix.Managed.Cabal.Data.Config (CabalConfig, GhcDb)
 import qualified Hix.Managed.Cabal.Data.SolveResources as SolveResources
 import Hix.Managed.Cabal.Data.SolveResources (SolveResources (SolveResources), solverParams)
 import Hix.Managed.Cabal.Installed (installedVersion)
 import qualified Hix.Managed.Cabal.Resources as SolveResources
 import Hix.Managed.Cabal.Solve (solveWithCabal)
+import Hix.Managed.Cabal.Sort (sortMutations)
 import Hix.Managed.Data.ManagedPackage (ManagedPackage)
 import Hix.Managed.Data.Packages (Packages)
 import Hix.Managed.Handlers.Cabal (CabalHandlers (..))
@@ -35,7 +36,8 @@
   solveResources <- trans <$> SolveResources.acquire packages cabalConf ghc
   pure CabalHandlers {
     solveForVersion = solveWithCabal solveResources {solverParams},
-    installedVersion = installedVersion solveResources.installedPkgIndex
+    installedVersion = installedVersion solveResources.installedPkgIndex,
+    sortMutations = sortMutations solveResources
   }
   where
     solverParams | oldest = setPreferenceDefault PreferAllOldest
diff --git a/lib/Hix/Managed/Lower/App.hs b/lib/Hix/Managed/Lower/App.hs
--- a/lib/Hix/Managed/Lower/App.hs
+++ b/lib/Hix/Managed/Lower/App.hs
@@ -5,6 +5,7 @@
 import qualified Hix.Data.Options
 import Hix.Data.Options (LowerOptions (LowerOptions))
 import Hix.Json (jsonConfigE)
+import qualified Hix.Managed.Data.BuildConfig
 import Hix.Managed.Data.LowerConfig (LowerConfig, lowerConfig)
 import Hix.Managed.Data.ProjectContext (ProjectContext)
 import qualified Hix.Managed.Data.ProjectContextProto
@@ -23,7 +24,8 @@
   M ()
 lowerCli main opts@LowerOptions {common} = do
   context <- jsonConfigE Client common.context
-  handlers <- chooseHandlers common.stateFile context.envs context.buildOutputsPrefix common.cabal common.handlers
+  handlers <- chooseHandlers common.stateFile context.envs context.buildOutputsPrefix common.project.build.timeout
+    common.cabal common.handlers
   withProjectContext handlers common.project context (main conf handlers)
   where
     conf = lowerConfig opts
diff --git a/lib/Hix/Managed/Lower/Stabilize.hs b/lib/Hix/Managed/Lower/Stabilize.hs
--- a/lib/Hix/Managed/Lower/Stabilize.hs
+++ b/lib/Hix/Managed/Lower/Stabilize.hs
@@ -60,12 +60,11 @@
   execStatelessStage "stabilize-initial" \ StageContext {env, initial, builder} ->
     buildVersions builder env "initial lower bounds" initial <&> \case
       Success -> StageNoAction (Just "Env builds successfully with the initial bounds.")
-      Failure ->
-        StageFailure (FailedPrecondition msg)
+      Failure -> StageFailure (FailedPrecondition msg)
   where
     msg =
       [
-        "Cannot stabilize since the build with initial bounds failed.",
+        [exon|Cannot stabilize since the build with initial bounds failed.|],
         "Please run 'lower.init --reset' or fix the build manually."
       ]
 
@@ -136,7 +135,7 @@
 lowerStabilizeStages handlers conf =
   validateCurrent >>= \case
     Success -> unit
-    Failure -> stabilizeIfPossible handlers conf
+    _ -> stabilizeIfPossible handlers conf
 
 lowerStabilizeMain ::
   BuildHandlers ->
diff --git a/lib/Hix/Options.hs b/lib/Hix/Options.hs
--- a/lib/Hix/Options.hs
+++ b/lib/Hix/Options.hs
@@ -77,6 +77,7 @@
 import Hix.Managed.Data.Query (RawQuery (RawQuery))
 import qualified Hix.Managed.Data.StateFileConfig
 import Hix.Managed.Data.StateFileConfig (StateFileConfig (StateFileConfig))
+import Hix.Managed.Handlers.Build (BuildTimeout (BuildTimeout))
 import Hix.Optparse (
   JsonConfig,
   absDirOption,
@@ -256,12 +257,15 @@
   lookup <- switch (long "lookup" <> short 'n' <> help "Only print latest versions (bump)")
   validate <- switch (long "validate" <> help "Validate new versions, but don't update the project state")
   buildOutput <- switch (long "build-output" <> help "Show output from Nix builds")
+  timeout <- fmap BuildTimeout <$> optional (option auto (long "build-timeout" <> help buildTimeoutHelp))
   pure BuildConfig {..}
   where
     maxIterationsHelp = "Number of restarts when some dependencies fail"
     maxFailedPreHelp = maxFailedHelp "prior to the first"
     maxFailedPostHelp = maxFailedHelp "after the last"
     maxFailedHelp variant = [exon|Number of majors that may fail before aborting, #{variant} success|]
+    buildTimeoutHelp = "Kill Nix builds after this duration in seconds"
+    toposortMutations = True
 
 projectOptionsParser :: Parser ProjectOptions
 projectOptionsParser = do
diff --git a/test/Hix/Test/Managed/LowerInit/MutationTest.hs b/test/Hix/Test/Managed/LowerInit/MutationTest.hs
--- a/test/Hix/Test/Managed/LowerInit/MutationTest.hs
+++ b/test/Hix/Test/Managed/LowerInit/MutationTest.hs
@@ -27,7 +27,7 @@
 import Hix.NixExpr (renderRootExpr)
 import Hix.Pretty (showP)
 import Hix.Test.Hedgehog (eqLines, listEqZip)
-import Hix.Test.Managed.Run (Result (..), TestParams (..), lowerTest, testParams)
+import Hix.Test.Managed.Run (Result (..), TestParams (..), lowerTest, testParams, nosortOptions)
 import Hix.Test.Utils (UnitTest)
 
 packages :: Packages ManagedPackageProto
@@ -433,6 +433,6 @@
         log = True,
         ghcPackages,
         state,
-        projectOptions = def {envs = ["lower-main", "lower-special"], readUpperBounds = True},
+        projectOptions = nosortOptions {envs = ["lower-main", "lower-special"], readUpperBounds = True},
         build
       }
diff --git a/test/Hix/Test/Managed/LowerNativeTest.hs b/test/Hix/Test/Managed/LowerNativeTest.hs
--- a/test/Hix/Test/Managed/LowerNativeTest.hs
+++ b/test/Hix/Test/Managed/LowerNativeTest.hs
@@ -233,7 +233,7 @@
       projectRoot = Just root
     }
     envsConfig = [("lower", EnvConfig {targets = ["root"], ghc = GhcDbSystem Nothing})]
-  handlers <- Build.handlersProd stateFileConf envsConfig Nothing def False
+  handlers <- Build.handlersProd stateFileConf envsConfig Nothing Nothing def False
   let
     opts = projectOptions ["lower"]
 
diff --git a/test/Hix/Test/Managed/LowerOptimize/OrderTest.hs b/test/Hix/Test/Managed/LowerOptimize/OrderTest.hs
new file mode 100644
--- /dev/null
+++ b/test/Hix/Test/Managed/LowerOptimize/OrderTest.hs
@@ -0,0 +1,133 @@
+module Hix.Test.Managed.LowerOptimize.OrderTest where
+
+import Hix.Data.Monad (M)
+import Hix.Data.Version (Versions)
+import Hix.Managed.Cabal.Changes (SolverPlan (..))
+import qualified Hix.Managed.Cabal.Data.Packages
+import Hix.Managed.Cabal.Data.Packages (GhcPackages (GhcPackages))
+import Hix.Managed.Cabal.Data.SourcePackage (SourcePackages)
+import Hix.Managed.Data.Constraints (EnvConstraints)
+import Hix.Managed.Data.ManagedPackageProto (ManagedPackageProto, managedPackages)
+import Hix.Managed.Data.Packages (Packages)
+import qualified Hix.Managed.Data.ProjectStateProto
+import Hix.Managed.Data.ProjectStateProto (ProjectStateProto (ProjectStateProto))
+import Hix.Managed.Data.StageState (BuildStatus (Success))
+import Hix.Managed.Lower.Optimize (lowerOptimizeMain)
+import Hix.Test.Hedgehog (listEqZip)
+import Hix.Test.Managed.Run (Result (..), TestParams (..), lowerTest, testParams)
+import Hix.Test.Utils (UnitTest)
+
+packages :: Packages ManagedPackageProto
+packages =
+  managedPackages [(("local1", "1.0"), ["dep1", "dep2", "dep3"])]
+
+packageDb :: SourcePackages
+packageDb =
+  [
+    ("dep1", [
+      ([1, 8], []),
+      ([1, 9], []),
+      ([2, 0], []),
+      ([2, 1], [])
+    ]),
+    ("dep2", [
+      ([1, 9], ["dep1 <1.9"]),
+      ([2, 0], []),
+      ([2, 1], [])
+    ]),
+    ("dep3", [
+      ([1, 9], ["dep2 <2"]),
+      ([2, 0], ["dep2 <2"]),
+      ([2, 1], [])
+    ])
+  ]
+
+ghcPackages :: GhcPackages
+ghcPackages = GhcPackages {installed = [], available = packageDb}
+
+state :: ProjectStateProto
+state =
+  ProjectStateProto {
+    bounds = [
+      ("local1", [
+        ("dep1", ">=2.1"),
+        ("dep2", ">=2.1"),
+        ("dep3", ">=2.1")
+      ])
+    ],
+    versions = [
+      ("lower", [
+        ("dep1", [2, 1]),
+        ("dep2", [2, 1]),
+        ("dep3", [2, 1])
+      ])
+    ],
+    overrides = [("lower", [])],
+    initial = [
+      ("lower", [
+        ("dep1", [2, 1]),
+        ("dep2", [2, 1]),
+        ("dep3", [2, 1])
+      ])
+    ],
+    resolving = False
+  }
+
+build :: Versions -> M BuildStatus
+build _ = pure Success
+
+cabalTarget :: [(EnvConstraints, Maybe SolverPlan)]
+cabalTarget =
+  [
+    ([
+      "dep1 <=2.1",
+      "dep2 <=2.1",
+      "dep3 ==2.0"
+    ], plan ["dep1-1.8", "dep2-1.9", "dep3-2.0"]),
+    ([
+      "dep1 <=2.1",
+      "dep2 <=2.1",
+      "dep3 ==1.9"
+    ], plan ["dep1-1.8", "dep2-1.9", "dep3-1.9"]),
+    ([
+      "dep1 <=1.8",
+      "dep2 ==2.0",
+      "dep3 <=1.9"
+    ], Nothing),
+    ([
+      "dep1 <=1.8",
+      "dep2 ==1.9",
+      "dep3 <=1.9"
+    ], plan ["dep1-1.8", "dep2-1.9", "dep3-1.9"]),
+    ([
+      "dep1 ==2.0",
+      "dep2 <=1.9",
+      "dep3 <=1.9"
+    ], Nothing),
+    ([
+      "dep1 ==1.9",
+      "dep2 <=1.9",
+      "dep3 <=1.9"
+    ], Nothing),
+    ([
+      "dep1 ==1.8",
+      "dep2 <=1.9",
+      "dep3 <=1.9"
+    ], plan ["dep1-1.8", "dep2-1.9", "dep3-1.9"])
+  ]
+  where
+    plan changes = Just SolverPlan {changes, matching = [], nonReinstallable = Nothing}
+
+test_mutationOrder :: UnitTest
+test_mutationOrder = do
+  Result {cabalLog} <- lowerTest params lowerOptimizeMain
+  listEqZip cabalTarget cabalLog
+  where
+    params =
+      (testParams False packages) {
+        cabalLog = True,
+        ghcPackages,
+        state,
+        build,
+        projectOptions = def
+      }
diff --git a/test/Hix/Test/Managed/Run.hs b/test/Hix/Test/Managed/Run.hs
--- a/test/Hix/Test/Managed/Run.hs
+++ b/test/Hix/Test/Managed/Run.hs
@@ -7,6 +7,7 @@
 import Hix.Data.EnvName (EnvName)
 import Hix.Data.Monad (M)
 import Hix.Data.NixExpr (Expr)
+import qualified Hix.Data.Options as ProjectOptions
 import Hix.Data.Options (ProjectOptions)
 import Hix.Data.PackageName (LocalPackage)
 import Hix.Data.Version (Versions)
@@ -14,6 +15,7 @@
 import Hix.Managed.Cabal.Data.Config (GhcDb (GhcDbSynthetic))
 import qualified Hix.Managed.Cabal.Data.Packages
 import Hix.Managed.Cabal.Data.Packages (GhcPackages (GhcPackages))
+import Hix.Managed.Data.BuildConfig (BuildConfig (toposortMutations))
 import Hix.Managed.Data.Constraints (EnvConstraints)
 import qualified Hix.Managed.Data.EnvConfig
 import Hix.Managed.Data.EnvConfig (EnvConfig (EnvConfig))
@@ -24,7 +26,7 @@
 import Hix.Managed.Data.ProjectContextProto (ProjectContextProto (..))
 import Hix.Managed.Data.ProjectResult (ProjectResult)
 import Hix.Managed.Data.ProjectStateProto (ProjectStateProto)
-import Hix.Managed.Data.StageState (BuildStatus (Failure))
+import Hix.Managed.Data.StageState (BuildResult (Finished), BuildStatus (Failure))
 import qualified Hix.Managed.Handlers.Build as BuildHandlers
 import Hix.Managed.Handlers.Build (BuildHandlers (..))
 import qualified Hix.Managed.Handlers.Build.Test as BuildHandlers
@@ -45,6 +47,9 @@
     build :: Versions -> M BuildStatus
   }
 
+nosortOptions :: ProjectOptions
+nosortOptions = def {ProjectOptions.build = def {toposortMutations = False}}
+
 testParams ::
   Bool ->
   Packages ManagedPackageProto ->
@@ -58,15 +63,16 @@
     packages,
     ghcPackages = GhcPackages {installed = [], available = []},
     state = def,
-    projectOptions = def,
+    projectOptions = nosortOptions,
     build = const (pure Failure)
   }
 
-data Result =
+data Result a =
   Result {
     stateFile :: Expr,
     cabalLog :: [(EnvConstraints, Maybe SolverPlan)],
-    log :: [Text]
+    log :: [Text],
+    result :: a
   }
 
 testProjectContext ::
@@ -85,14 +91,14 @@
 
     defaultEnv = [(defaultEnvName, nKeys params.packages)]
 
-managedTest ::
+managedTest' ::
   EnvName ->
   TestParams ->
-  (BuildHandlers -> ProjectContext -> M ProjectResult) ->
-  TestT IO Result
-managedTest defaultEnvName params main =
+  (BuildHandlers -> ProjectOptions -> ProjectContextProto -> M a) ->
+  TestT IO (Result a)
+managedTest' defaultEnvName params main =
   withFrozenCallStack do
-    (handlers0, stateFileRef, _) <- BuildHandlers.handlersUnitTest params.ghcPackages params.build
+    (handlers0, stateFileRef, _) <- BuildHandlers.handlersUnitTest params.ghcPackages (fmap Finished . params.build)
     (cabalRef, handlers1) <-
       if params.cabalLog
       then first Just <$> BuildHandlers.logCabal handlers0
@@ -102,8 +108,8 @@
         if params.log
         then handlers1 {report = ReportHandlers.handlersProd}
         else handlers1
-    (log, result) <- liftIO (runner (withProjectContext handlers params.projectOptions context (main handlers)))
-    evalEither result
+    (log, e_result) <- liftIO (runner (main handlers params.projectOptions context))
+    result <- evalEither e_result
     stateFile <- evalMaybe . head =<< liftIO (readIORef stateFileRef)
     cabalLog <- fold <$> for cabalRef \ ref -> reverse <$> liftIO (readIORef ref)
     pure Result {..}
@@ -113,10 +119,20 @@
 
     context = testProjectContext defaultEnvName params
 
+managedTest ::
+  EnvName ->
+  TestParams ->
+  (BuildHandlers -> ProjectContext -> M ProjectResult) ->
+  TestT IO (Result ())
+managedTest defaultEnvName params main =
+  withFrozenCallStack do
+    managedTest' defaultEnvName params \ handlers options context ->
+      withProjectContext handlers options context (main handlers)
+
 lowerTest ::
   TestParams ->
   (BuildHandlers -> ProjectContext -> M ProjectResult) ->
-  TestT IO Result
+  TestT IO (Result ())
 lowerTest params main =
   withFrozenCallStack do
     managedTest "lower" params main
@@ -124,7 +140,7 @@
 bumpTest ::
   TestParams ->
   (BuildHandlers -> ProjectContext -> M ProjectResult) ->
-  TestT IO Result
+  TestT IO (Result ())
 bumpTest params main =
   withFrozenCallStack do
     managedTest "latest" params main
diff --git a/test/Hix/Test/Managed/SolveTest.hs b/test/Hix/Test/Managed/SolveTest.hs
new file mode 100644
--- /dev/null
+++ b/test/Hix/Test/Managed/SolveTest.hs
@@ -0,0 +1,46 @@
+module Hix.Test.Managed.SolveTest where
+
+import qualified Data.Set as Set
+import Hedgehog (evalEither)
+
+import Hix.Class.Map (nKeys)
+import qualified Hix.Managed.Cabal.Changes
+import Hix.Managed.Cabal.Data.Config (GhcDb (GhcDbSystem))
+import Hix.Managed.Cabal.Data.SolverState (solverState)
+import qualified Hix.Managed.Cabal.Resources as SolveResources
+import Hix.Managed.Cabal.Solve (solveWithCabal)
+import Hix.Managed.Cabal.Sort (sortDeps)
+import Hix.Managed.Data.EnvContext (EnvDeps (EnvDeps))
+import Hix.Managed.Data.Mutable (unsafeMutableDep)
+import Hix.Pretty (prettyL, showP, showPL)
+import Hix.Test.Managed.UnsafeIsString ()
+import Hix.Test.Utils (UnitTest, runMTest)
+
+test_solve :: UnitTest
+test_solve =
+  evalEither =<< liftIO do
+    runMTest True do
+      res <- SolveResources.acquire mempty def (GhcDbSystem Nothing)
+      result <- solveWithCabal res state
+      sorted <- sortDeps res id (nKeys constraints)
+      liftIO do
+        putStrLn ""
+        putStrLn "Constraints:"
+        putStrLn (showP constraints)
+        putStrLn ""
+        putStrLn "Changes:"
+        putStrLn (maybe "no plan" (show . prettyL . (.changes)) result)
+        putStrLn ""
+        putStrLn "Sorted:"
+        putStrLn (showPL sorted)
+  where
+    state = solverState [] (EnvDeps (Set.fromList (unsafeMutableDep <$> nKeys constraints)) mempty) constraints def
+    constraints =
+      [
+        ("ansi-terminal", mempty),
+        ("async", mempty),
+        ("polysemy-conc", mempty),
+        ("polysemy-time", mempty),
+        ("stm", mempty),
+        ("time", mempty)
+      ]
diff --git a/test/Hix/Test/ManagedTest.hs b/test/Hix/Test/ManagedTest.hs
--- a/test/Hix/Test/ManagedTest.hs
+++ b/test/Hix/Test/ManagedTest.hs
@@ -12,6 +12,7 @@
 import Hix.Test.Managed.LowerNativeTest ()
 import Hix.Test.Managed.LowerOptimize.CandidatesTest (test_candidatesOptimize)
 import Hix.Test.Managed.LowerOptimize.MutationTest (test_lowerOptimizeMutation)
+import Hix.Test.Managed.LowerOptimize.OrderTest (test_mutationOrder)
 import Hix.Test.Managed.LowerStabilize.MutationTest (test_lowerStabilizeMutation)
 import Hix.Test.Managed.ProjectContextProtoTest (test_parseProjectContextProto)
 import Hix.Test.Utils (unitTest)
@@ -29,7 +30,8 @@
         unitTest "lower.optimize" test_lowerOptimizeMutation,
         unitTest "lower.stabilize" test_lowerStabilizeMutation,
         unitTest "lower.auto stabilize" test_lowerAutoMutationStabilize,
-        test_lowerAutoMutationOptimize
+        test_lowerAutoMutationOptimize,
+        unitTest "mutation ordering" test_mutationOrder
       ],
       unitTest "parse ProjectContext" test_parseProjectContextProto,
       test_buildOutput,
