diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Revision history for railroad
 
+## 0.1.1.4 -- 2026-10-10
+* Widen dependency upper bounds (tests pass):
+  - `effectful < 2.8`
+  - `validation < 1.4`
+* Stop using `Data.Validation.toEither`, which `validation-1.3` no longer exports.
+* Declare `hspec-discover` as a `build-tool-depends` of the test suite, so the tests build outside the nix shell.
+
 ## 0.1.1.3 -- 2026-10-10
 * Documentation fixes: correct "success" typo and the doc comment of `(?)` in `Railroad.MonadError`.
 
diff --git a/railroad.cabal b/railroad.cabal
--- a/railroad.cabal
+++ b/railroad.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               railroad
-version:            0.1.1.3
+version:            0.1.1.4
 license:            BSD-3-Clause
 license-file:       LICENSE
 author:             Frederik Kallstrup Mastratisi
@@ -22,9 +22,9 @@
     ghc-options: -Wall
     build-depends:
       base >=4.17 && < 4.23,
-      effectful >= 2.5 && < 2.7,
+      effectful >= 2.5 && < 2.8,
       mtl >= 2.2 && < 2.4,
-      validation >= 1.1 && < 1.2
+      validation >= 1.1 && < 1.4
 
 library
     import:           stuff
@@ -42,6 +42,8 @@
     main-is:          Main.hs
     other-modules:    RailroadSpec
                       Railroad.MonadErrorSpec
+    build-tool-depends:
+                  hspec-discover:hspec-discover >= 2.11.14 && < 2.12
     build-depends:
                   base  >=4.17 && < 4.23,
                   hspec  >= 2.11.14 && < 2.12,
diff --git a/src/Railroad.hs b/src/Railroad.hs
--- a/src/Railroad.hs
+++ b/src/Railroad.hs
@@ -9,7 +9,7 @@
 import           Data.Bool               (bool)
 import           Data.Foldable           (toList)
 import           Data.Kind               (Type)
-import           Data.Validation         (Validation, toEither)
+import           Data.Validation         (Validation (..))
 import           Effectful               (Eff, type (:>))
 import           Effectful.Error.Dynamic (Error, throwError_)
 
@@ -40,7 +40,9 @@
   bifurcate = id
   -- either :: (e -> b) -> (a -> b) -> Either e a -> b
 instance Bifurcate (Validation e a) where
-  bifurcate = toEither
+  bifurcate = \case
+    Failure e -> Left e
+    Success a -> Right a
   -- validation :: (e -> b) -> (a -> b) -> Validation e a -> b
 
 instance (Traversable t, CErr (t Bool) ~ (), CRes (t Bool) ~ t ())
@@ -56,8 +58,7 @@
   -- pedagogically: bifurcate = bifurcate . sequenceA
 instance (Traversable t, Semigroup e, CErr (t (Validation e a)) ~ e, CRes (t (Validation e a)) ~ t a)
     => Bifurcate (t (Validation e a)) where
-  bifurcate = toEither . sequenceA
-  -- pedagogically: bifurcate = bifurcate . sequenceA
+  bifurcate = bifurcate . sequenceA
 
 -- | Collapses a structure into its inner type or an effectful error
 collapse :: (Error e :> es, Bifurcate f) => (CErr f -> e) -> f -> Eff es (CRes f)
diff --git a/test/Railroad/MonadErrorSpec.hs b/test/Railroad/MonadErrorSpec.hs
--- a/test/Railroad/MonadErrorSpec.hs
+++ b/test/Railroad/MonadErrorSpec.hs
@@ -5,7 +5,6 @@
 
 import           Control.Monad.Except
 import           Data.Functor.Identity
-import           Data.Validation
 import           Railroad.MonadError
 import           Test.Hspec
 
@@ -27,9 +26,9 @@
 
     describe "Predicate Operator (?>)" $ do
       it "passes when predicate is met" $ do
-        runMonadError (pure 10 ?> (> 5) $ const "too small") `shouldBe` Right 10
+        runMonadError (pure (10 :: Int) ?> (> 5) $ const "too small") `shouldBe` Right 10
       it "fails when predicate is not met" $ do
-        runMonadError (pure 4 ?> (> 5) $ const "too small") `shouldBe` Left "too small"
+        runMonadError (pure (4 :: Int) ?> (> 5) $ const "too small") `shouldBe` Left "too small"
 
     describe "Recovery Operators (?~ and ??~)" $ do
       it "recovers to a constant value with (?~)" $ do
@@ -40,21 +39,21 @@
     describe "Cardinality Operators" $ do
       describe "(?+)" $ do
         it "succeeds on non-empty list" $ do
-          runMonadError (pure [1, 2, 3] ?+ "empty") `shouldBe` Right [1, 2, 3]
+          runMonadError (pure [1, 2, 3 :: Int] ?+ "empty") `shouldBe` Right [1, 2, 3]
         it "fails on empty list" $ do
           runMonadError (pure ([] :: [Int]) ?+ "empty") `shouldBe` Left "empty"
 
       describe "(?!)" $ do
         let toErr = cardinalityErr "none" (const "too many")
         it "extracts the single element" $ do
-          runMonadError (pure [42] ?! toErr) `shouldBe` Right 42
+          runMonadError (pure [42 :: Int] ?! toErr) `shouldBe` Right 42
         it "fails on empty" $ do
           runMonadError (pure ([] :: [Int]) ?! toErr) `shouldBe` Left "none"
         it "fails on multiple elements" $ do
-          runMonadError (pure [1, 2] ?! toErr) `shouldBe` Left "too many"
+          runMonadError (pure [1, 2 :: Int] ?! toErr) `shouldBe` Left "too many"
 
       describe "(?∅)" $ do
         it "succeeds on empty" $ do
           runMonadError (pure [] ?∅ const "not empty") `shouldBe` Right ()
         it "fails on non-empty" $ do
-          runMonadError (pure [1] ?∅ const "not empty") `shouldBe` Left "not empty"
+          runMonadError (pure [1 :: Int] ?∅ const "not empty") `shouldBe` Left "not empty"
diff --git a/test/RailroadSpec.hs b/test/RailroadSpec.hs
--- a/test/RailroadSpec.hs
+++ b/test/RailroadSpec.hs
@@ -47,11 +47,11 @@
       bifurcate input1 `shouldBe` Left "first"
 
     it "List of Validation (Error Accumulation)" $ do
-      let input = [Success 1, Success 2] :: [Validation String Int]
-      bifurcate input `shouldBe` Right [1, 2]
+      let allOk = [Success 1, Success 2] :: [Validation String Int]
+      bifurcate allOk `shouldBe` Right [1, 2]
         -- Note: Validation accumulates because String is a Semigroup
-      let input = [Success 1, Failure "Fail A ", Failure "Fail B"] :: [Validation String Int]
-      bifurcate input `shouldBe` Left "Fail A Fail B"
+      let someBad = [Success 1, Failure "Fail A ", Failure "Fail B"] :: [Validation String Int]
+      bifurcate someBad `shouldBe` Left "Fail A Fail B"
 
   describe "Basic Operators (? and ??)" $ do
     it "unwraps success values with (?)" $ do
@@ -66,10 +66,10 @@
 
   describe "Predicate Operator (?>)" $ do
     it "passes when predicate is met" $ do
-      runRail (pure 10 ?> (> 5) $ const "too small") `shouldBe` Right 10
+      runRail (pure (10 :: Int) ?> (> 5) $ const "too small") `shouldBe` Right 10
 
     it "fails when predicate is not met" $ do
-      runRail (pure 4 ?> (> 5) $ const "too small") `shouldBe` Left "too small"
+      runRail (pure (4 :: Int) ?> (> 5) $ const "too small") `shouldBe` Left "too small"
 
   describe "Recovery Operators (?~ and ??~)" $ do
     it "recovers to a constant value with (?~)" $ do
@@ -81,21 +81,21 @@
   describe "Cardinality Operators" $ do
     describe "(?+)" $ do
       it "succeeds on non-empty list" $ do
-        runRail (pure [1, 2, 3] ?+ "empty") `shouldBe` Right [1, 2, 3]
+        runRail (pure [1, 2, 3 :: Int] ?+ "empty") `shouldBe` Right [1, 2, 3]
       it "fails on empty list" $ do
         runRail (pure ([] :: [Int]) ?+ "empty") `shouldBe` Left "empty"
 
     describe "(?!)" $ do
       let toErr = cardinalityErr "none" (const "too many")
       it "extracts the single element" $ do
-        runRail (pure [42] ?! toErr) `shouldBe` Right 42
+        runRail (pure [42 :: Int] ?! toErr) `shouldBe` Right 42
       it "fails on empty" $ do
         runRail (pure ([] :: [Int]) ?! toErr) `shouldBe` Left "none"
       it "fails on multiple elements" $ do
-        runRail (pure [1, 2] ?! toErr) `shouldBe` Left "too many"
+        runRail (pure [1, 2 :: Int] ?! toErr) `shouldBe` Left "too many"
 
     describe "(?∅)" $ do
       it "succeeds on empty" $ do
         runRail (pure [] ?∅ const "not empty") `shouldBe` Right ()
       it "fails on non-empty" $ do
-        runRail (pure [1] ?∅ const "not empty") `shouldBe` Left "not empty"
+        runRail (pure [1 :: Int] ?∅ const "not empty") `shouldBe` Left "not empty"
